From 73d17ff21cbe632cc5eb0675fb43c36e06fd0cf5 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Wed, 8 Apr 2026 22:41:14 +0700 Subject: [PATCH] Phase 1: implement iwd D-Bus core iwd_dbus watches net.connman.iwd name ownership, calls GetManagedObjects, and dispatches InterfacesAdded/Removed to a callback consumer. iwd_manager owns hashes of Iwd_Device and Iwd_Network keyed by object path; sub-objects subscribe to their PropertiesChanged signals via Eldbus and ping the manager so listeners can refresh. Aggregated state (off/idle/scanning/ connecting/connected) is recomputed from the active station. iwd_device exposes Powered toggle plus Station Scan/Disconnect. iwd_network calls Network.Connect() (the iwd Agent will be wired in next) and Forget via the referenced KnownNetwork object. Builds against EFL 1.28 / Enlightenment 0.27. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/iwd/iwd_dbus.c | 159 +++++++++++++++++++++++++++--- src/iwd/iwd_dbus.h | 17 +++- src/iwd/iwd_device.c | 142 ++++++++++++++++++++++++++- src/iwd/iwd_device.h | 44 +++++++-- src/iwd/iwd_manager.c | 218 ++++++++++++++++++++++++++++++++++++++---- src/iwd/iwd_manager.h | 10 +- src/iwd/iwd_network.c | 93 +++++++++++++++++- src/iwd/iwd_network.h | 16 +++- src/iwd/iwd_props.c | 38 ++++++++ src/iwd/iwd_props.h | 15 +++ src/meson.build | 1 + 11 files changed, 699 insertions(+), 54 deletions(-) create mode 100644 src/iwd/iwd_props.c create mode 100644 src/iwd/iwd_props.h diff --git a/src/iwd/iwd_dbus.c b/src/iwd/iwd_dbus.c index fd7ce53..b95c471 100644 --- a/src/iwd/iwd_dbus.c +++ b/src/iwd/iwd_dbus.c @@ -1,26 +1,159 @@ #include "iwd_dbus.h" -#include +#include +#include + +#define FDO_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" struct _Iwd_Dbus { - Eldbus_Connection *conn; - Eldbus_Object *root; - Eldbus_Proxy *object_manager; - Eldbus_Signal_Handler *name_owner_sh; + Eldbus_Connection *conn; + Iwd_Dbus_Callbacks cbs; + void *data; + + Eldbus_Object *root_obj; + Eldbus_Proxy *root_om; + Eldbus_Signal_Handler *sh_added; + Eldbus_Signal_Handler *sh_removed; + Eina_Bool present; }; -/* TODO: - * - watch IWD_BUS_NAME owner (NameOwnerChanged) for restart handling - * - call org.freedesktop.DBus.ObjectManager.GetManagedObjects on "/" - * - listen for InterfacesAdded / InterfacesRemoved - * - dispatch new objects to iwd_manager - */ +Eldbus_Connection * +iwd_dbus_conn(const Iwd_Dbus *d) { return d ? d->conn : NULL; } + +/* Walk the a{oa{sa{sv}}} reply from GetManagedObjects, emitting iface_added + * for every (path, interface) pair. */ +static void +_emit_managed(Iwd_Dbus *d, Eldbus_Message_Iter *objects) +{ + Eldbus_Message_Iter *entry; + while (eldbus_message_iter_get_and_next(objects, 'e', &entry)) + { + const char *path; + Eldbus_Message_Iter *ifaces; + if (!eldbus_message_iter_arguments_get(entry, "oa{sa{sv}}", &path, &ifaces)) + continue; + + Eldbus_Message_Iter *iface_entry; + while (eldbus_message_iter_get_and_next(ifaces, 'e', &iface_entry)) + { + const char *iface; + Eldbus_Message_Iter *props; + if (!eldbus_message_iter_arguments_get(iface_entry, "sa{sv}", &iface, &props)) + continue; + if (d->cbs.iface_added) + d->cbs.iface_added(d->data, path, iface, props); + } + } +} + +static void +_on_get_managed(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED) +{ + Iwd_Dbus *d = data; + const char *errname, *errmsg; + if (eldbus_message_error_get(msg, &errname, &errmsg)) + { + /* iwd not present yet — name watcher will retry. */ + return; + } + Eldbus_Message_Iter *objects; + if (!eldbus_message_arguments_get(msg, "a{oa{sa{sv}}}", &objects)) + return; + _emit_managed(d, objects); +} + +static void +_on_iface_added(void *data, const Eldbus_Message *msg) +{ + Iwd_Dbus *d = data; + const char *path; + Eldbus_Message_Iter *ifaces, *iface_entry; + if (!eldbus_message_arguments_get(msg, "oa{sa{sv}}", &path, &ifaces)) + return; + while (eldbus_message_iter_get_and_next(ifaces, 'e', &iface_entry)) + { + const char *iface; + Eldbus_Message_Iter *props; + if (!eldbus_message_iter_arguments_get(iface_entry, "sa{sv}", &iface, &props)) + continue; + if (d->cbs.iface_added) + d->cbs.iface_added(d->data, path, iface, props); + } +} + +static void +_on_iface_removed(void *data, const Eldbus_Message *msg) +{ + Iwd_Dbus *d = data; + const char *path; + Eldbus_Message_Iter *ifaces; + if (!eldbus_message_arguments_get(msg, "oas", &path, &ifaces)) + return; + const char *iface; + while (eldbus_message_iter_get_and_next(ifaces, 's', &iface)) + { + if (d->cbs.iface_removed) + d->cbs.iface_removed(d->data, path, iface); + } +} + +static void +_bind_root(Iwd_Dbus *d) +{ + if (d->root_om) return; + d->root_obj = eldbus_object_get(d->conn, IWD_BUS_NAME, "/"); + if (!d->root_obj) return; + d->root_om = eldbus_proxy_get(d->root_obj, FDO_OBJECT_MANAGER); + if (!d->root_om) return; + + d->sh_added = eldbus_proxy_signal_handler_add(d->root_om, "InterfacesAdded", + _on_iface_added, d); + d->sh_removed = eldbus_proxy_signal_handler_add(d->root_om, "InterfacesRemoved", + _on_iface_removed, d); + eldbus_proxy_call(d->root_om, "GetManagedObjects", _on_get_managed, d, -1, ""); +} + +static void +_unbind_root(Iwd_Dbus *d) +{ + if (d->sh_added) { eldbus_signal_handler_del(d->sh_added); d->sh_added = NULL; } + if (d->sh_removed) { eldbus_signal_handler_del(d->sh_removed); d->sh_removed = NULL; } + if (d->root_om) { eldbus_proxy_unref(d->root_om); d->root_om = NULL; } + if (d->root_obj) { eldbus_object_unref(d->root_obj); d->root_obj = NULL; } +} + +static void +_on_name_owner(void *data, const char *bus EINA_UNUSED, const char *old_id, const char *new_id) +{ + Iwd_Dbus *d = data; + Eina_Bool now = (new_id && new_id[0]); + Eina_Bool was = (old_id && old_id[0]); + + if (now && !d->present) + { + d->present = EINA_TRUE; + _bind_root(d); + if (d->cbs.name_appeared) d->cbs.name_appeared(d->data); + } + else if (!now && (was || d->present)) + { + d->present = EINA_FALSE; + _unbind_root(d); + if (d->cbs.name_vanished) d->cbs.name_vanished(d->data); + } +} Iwd_Dbus * -iwd_dbus_new(Eldbus_Connection *conn) +iwd_dbus_new(Eldbus_Connection *conn, const Iwd_Dbus_Callbacks *cbs, void *data) { Iwd_Dbus *d = calloc(1, sizeof(*d)); + if (!d) return NULL; d->conn = conn; + if (cbs) d->cbs = *cbs; + d->data = data; + + eldbus_name_owner_changed_callback_add(conn, IWD_BUS_NAME, + _on_name_owner, d, EINA_TRUE); return d; } @@ -28,5 +161,7 @@ void iwd_dbus_free(Iwd_Dbus *d) { if (!d) return; + eldbus_name_owner_changed_callback_del(d->conn, IWD_BUS_NAME, _on_name_owner, d); + _unbind_root(d); free(d); } diff --git a/src/iwd/iwd_dbus.h b/src/iwd/iwd_dbus.h index 133b782..e97a8fe 100644 --- a/src/iwd/iwd_dbus.h +++ b/src/iwd/iwd_dbus.h @@ -2,6 +2,7 @@ #define IWD_DBUS_H #include +#include #define IWD_BUS_NAME "net.connman.iwd" #define IWD_IFACE_ADAPTER "net.connman.iwd.Adapter" @@ -14,7 +15,21 @@ typedef struct _Iwd_Dbus Iwd_Dbus; -Iwd_Dbus *iwd_dbus_new (Eldbus_Connection *conn); +/* Callbacks the consumer (iwd_manager) registers to react to bus state. */ +typedef struct _Iwd_Dbus_Callbacks +{ + void (*name_appeared) (void *data); + void (*name_vanished) (void *data); + /* iface_props is an iter positioned on a{sv} for the given interface. */ + void (*iface_added) (void *data, const char *path, const char *iface, + Eldbus_Message_Iter *iface_props); + void (*iface_removed) (void *data, const char *path, const char *iface); +} Iwd_Dbus_Callbacks; + +Iwd_Dbus *iwd_dbus_new (Eldbus_Connection *conn, + const Iwd_Dbus_Callbacks *cbs, void *data); void iwd_dbus_free(Iwd_Dbus *d); +Eldbus_Connection *iwd_dbus_conn(const Iwd_Dbus *d); + #endif diff --git a/src/iwd/iwd_device.c b/src/iwd/iwd_device.c index 544e58d..73b3a12 100644 --- a/src/iwd/iwd_device.c +++ b/src/iwd/iwd_device.c @@ -1,26 +1,158 @@ #include "iwd_device.h" +#include "iwd_dbus.h" +#include "iwd_props.h" +#include "iwd_manager.h" +#include +#include + +static Iwd_Station_State +_state_from_str(const char *s) +{ + if (!s) return IWD_STATION_DISCONNECTED; + if (!strcmp(s, "connected")) return IWD_STATION_CONNECTED; + if (!strcmp(s, "connecting")) return IWD_STATION_CONNECTING; + if (!strcmp(s, "disconnecting")) return IWD_STATION_DISCONNECTING; + if (!strcmp(s, "roaming")) return IWD_STATION_ROAMING; + return IWD_STATION_DISCONNECTED; +} + +static void +_dev_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) +{ + Iwd_Device *d = data; + if (!strcmp(key, "Name")) { free(d->name); d->name = iwd_props_str_dup(v); } + else if (!strcmp(key, "Address")) { free(d->address); d->address = iwd_props_str_dup(v); } + else if (!strcmp(key, "Adapter")) { free(d->adapter_path); d->adapter_path = iwd_props_str_dup(v); } + else if (!strcmp(key, "Powered")) { d->powered = iwd_props_bool(v); } +} + +static void +_sta_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) +{ + Iwd_Device *d = data; + if (!strcmp(key, "State")) + { + char *s = iwd_props_str_dup(v); + d->station_state = _state_from_str(s); + free(s); + } + else if (!strcmp(key, "Scanning")) { d->scanning = iwd_props_bool(v); } + else if (!strcmp(key, "ConnectedNetwork")) { free(d->connected_network); d->connected_network = iwd_props_str_dup(v); } +} + +void +iwd_device_apply_device_props(Iwd_Device *d, Eldbus_Message_Iter *props) +{ + iwd_props_for_each(props, _dev_prop_cb, d); +} + +void +iwd_device_apply_station_props(Iwd_Device *d, Eldbus_Message_Iter *props) +{ + d->has_station = EINA_TRUE; + iwd_props_for_each(props, _sta_prop_cb, d); +} + +/* org.freedesktop.DBus.Properties.PropertiesChanged: (s, a{sv}, as) */ +static void +_on_dev_props_changed(void *data, const Eldbus_Message *msg) +{ + Iwd_Device *d = data; + const char *iface; + Eldbus_Message_Iter *changed, *invalidated; + if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated)) + return; + if (strcmp(iface, IWD_IFACE_DEVICE) != 0) return; + iwd_props_for_each(changed, _dev_prop_cb, d); + if (d->manager) iwd_manager_notify(d->manager); +} + +static void +_on_sta_props_changed(void *data, const Eldbus_Message *msg) +{ + Iwd_Device *d = data; + const char *iface; + Eldbus_Message_Iter *changed, *invalidated; + if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated)) + return; + if (strcmp(iface, IWD_IFACE_STATION) != 0) return; + iwd_props_for_each(changed, _sta_prop_cb, d); + if (d->manager) iwd_manager_notify(d->manager); +} Iwd_Device * -iwd_device_new(Eldbus_Connection *conn EINA_UNUSED, const char *path) +iwd_device_new(Eldbus_Connection *conn, const char *path, void *manager) { Iwd_Device *d = calloc(1, sizeof(*d)); - if (path) d->path = strdup(path); - /* TODO: build proxies for Device + Station; subscribe to PropertiesChanged */ + if (!d) return NULL; + d->path = path ? strdup(path) : NULL; + d->manager = manager; + d->obj = eldbus_object_get(conn, IWD_BUS_NAME, path); + if (d->obj) + { + d->device_proxy = eldbus_proxy_get(d->obj, IWD_IFACE_DEVICE); + if (d->device_proxy) + d->sh_dev_props = eldbus_proxy_properties_changed_callback_add( + d->device_proxy, _on_dev_props_changed, d); + } return d; } +void +iwd_device_attach_station(Iwd_Device *d) +{ + if (!d || d->station_proxy || !d->obj) return; + d->station_proxy = eldbus_proxy_get(d->obj, IWD_IFACE_STATION); + if (d->station_proxy) + { + d->has_station = EINA_TRUE; + d->sh_sta_props = eldbus_proxy_properties_changed_callback_add( + d->station_proxy, _on_sta_props_changed, d); + } +} + +void +iwd_device_detach_station(Iwd_Device *d) +{ + if (!d) return; + if (d->sh_sta_props) { eldbus_signal_handler_del(d->sh_sta_props); d->sh_sta_props = NULL; } + if (d->station_proxy) { eldbus_proxy_unref(d->station_proxy); d->station_proxy = NULL; } + d->has_station = EINA_FALSE; +} + void iwd_device_free(Iwd_Device *d) { if (!d) return; + iwd_device_detach_station(d); + if (d->sh_dev_props) eldbus_signal_handler_del(d->sh_dev_props); + if (d->device_proxy) eldbus_proxy_unref(d->device_proxy); + if (d->obj) eldbus_object_unref(d->obj); free(d->path); free(d->name); free(d->address); + free(d->adapter_path); + free(d->connected_network); free(d); } void -iwd_device_set_powered(Iwd_Device *d EINA_UNUSED, Eina_Bool on EINA_UNUSED) +iwd_device_set_powered(Iwd_Device *d, Eina_Bool on) { - /* TODO: Set("Powered") on Device interface */ + if (!d || !d->device_proxy) return; + eldbus_proxy_property_set(d->device_proxy, "Powered", "b", &on, NULL, NULL); +} + +void +iwd_device_scan(Iwd_Device *d) +{ + if (!d || !d->station_proxy) return; + eldbus_proxy_call(d->station_proxy, "Scan", NULL, NULL, -1, ""); +} + +void +iwd_device_disconnect(Iwd_Device *d) +{ + if (!d || !d->station_proxy) return; + eldbus_proxy_call(d->station_proxy, "Disconnect", NULL, NULL, -1, ""); } diff --git a/src/iwd/iwd_device.h b/src/iwd/iwd_device.h index e9ef0d3..a5aebce 100644 --- a/src/iwd/iwd_device.h +++ b/src/iwd/iwd_device.h @@ -6,17 +6,47 @@ typedef struct _Iwd_Device Iwd_Device; +typedef enum { + IWD_STATION_DISCONNECTED, + IWD_STATION_CONNECTING, + IWD_STATION_CONNECTED, + IWD_STATION_DISCONNECTING, + IWD_STATION_ROAMING, +} Iwd_Station_State; + struct _Iwd_Device { - char *path; /* D-Bus object path */ - char *name; - char *address; - Eina_Bool powered; - Eldbus_Proxy *station; /* may be NULL */ + char *path; + char *name; + char *address; + char *adapter_path; + Eina_Bool powered; + + /* Station-side state, valid when station_proxy != NULL */ + Eina_Bool has_station; + Iwd_Station_State station_state; + Eina_Bool scanning; + char *connected_network; + + Eldbus_Object *obj; + Eldbus_Proxy *device_proxy; + Eldbus_Proxy *station_proxy; + Eldbus_Signal_Handler *sh_dev_props; + Eldbus_Signal_Handler *sh_sta_props; + + void *manager; /* back-ref, opaque (Iwd_Manager *) */ }; -Iwd_Device *iwd_device_new (Eldbus_Connection *conn, const char *path); +Iwd_Device *iwd_device_new (Eldbus_Connection *conn, const char *path, void *manager); void iwd_device_free(Iwd_Device *d); -void iwd_device_set_powered(Iwd_Device *d, Eina_Bool on); + +void iwd_device_apply_device_props (Iwd_Device *d, Eldbus_Message_Iter *props); +void iwd_device_apply_station_props(Iwd_Device *d, Eldbus_Message_Iter *props); +void iwd_device_attach_station (Iwd_Device *d); +void iwd_device_detach_station (Iwd_Device *d); + +void iwd_device_set_powered(Iwd_Device *d, Eina_Bool on); +void iwd_device_scan (Iwd_Device *d); +void iwd_device_disconnect (Iwd_Device *d); #endif diff --git a/src/iwd/iwd_manager.c b/src/iwd/iwd_manager.c index 7a33e0c..622cbb3 100644 --- a/src/iwd/iwd_manager.c +++ b/src/iwd/iwd_manager.c @@ -1,26 +1,189 @@ #include "iwd_manager.h" #include "iwd_dbus.h" +#include "iwd_device.h" +#include "iwd_network.h" +#include +#include + +typedef struct _Listener +{ + Iwd_Manager_Cb cb; + void *data; +} Listener; struct _Iwd_Manager { - Iwd_Dbus *dbus; - Eina_List *devices; /* Iwd_Device * */ - Eina_List *listeners; - Iwd_State state; + Iwd_Dbus *dbus; + Eina_Hash *devices; /* path → Iwd_Device * */ + Eina_Hash *networks; /* path → Iwd_Network * */ + Eina_List *listeners; /* Listener * */ + Iwd_State state; + Eina_Bool notify_pending; }; -/* TODO: - * - Build the device/station/network tree from iwd_dbus events - * - Aggregate state from active station - * - Notify listeners on any change - */ +static void _recompute_state(Iwd_Manager *m); + +/* ----- listeners ------------------------------------------------------- */ + +void +iwd_manager_listener_add(Iwd_Manager *m, Iwd_Manager_Cb cb, void *data) +{ + if (!m || !cb) return; + Listener *l = calloc(1, sizeof(*l)); + l->cb = cb; l->data = data; + m->listeners = eina_list_append(m->listeners, l); +} + +void +iwd_manager_listener_del(Iwd_Manager *m, Iwd_Manager_Cb cb, void *data) +{ + if (!m) return; + Eina_List *l; + Listener *li; + EINA_LIST_FOREACH(m->listeners, l, li) + if (li->cb == cb && li->data == data) + { + m->listeners = eina_list_remove_list(m->listeners, l); + free(li); + return; + } +} + +void +iwd_manager_notify(Iwd_Manager *m) +{ + if (!m) return; + _recompute_state(m); + Eina_List *l; + Listener *li; + EINA_LIST_FOREACH(m->listeners, l, li) + li->cb(li->data, m); +} + +/* ----- state aggregation ---------------------------------------------- */ + +static void +_recompute_state(Iwd_Manager *m) +{ + Iwd_State s = IWD_STATE_OFF; + Eina_Iterator *it = eina_hash_iterator_data_new(m->devices); + Iwd_Device *d; + Eina_Bool any_powered = EINA_FALSE; + EINA_ITERATOR_FOREACH(it, d) + { + if (d->powered) any_powered = EINA_TRUE; + if (!d->has_station) continue; + if (d->scanning && s < IWD_STATE_SCANNING) s = IWD_STATE_SCANNING; + if (d->station_state == IWD_STATION_CONNECTING && s < IWD_STATE_CONNECTING) s = IWD_STATE_CONNECTING; + if (d->station_state == IWD_STATION_CONNECTED && s < IWD_STATE_CONNECTED) s = IWD_STATE_CONNECTED; + } + eina_iterator_free(it); + if (s == IWD_STATE_OFF && any_powered) s = IWD_STATE_IDLE; + m->state = s; +} + +/* ----- D-Bus callbacks ------------------------------------------------- */ + +static void +_on_iface_added(void *data, const char *path, const char *iface, Eldbus_Message_Iter *props) +{ + Iwd_Manager *m = data; + Eldbus_Connection *conn = iwd_dbus_conn(m->dbus); + + if (!strcmp(iface, IWD_IFACE_DEVICE)) + { + Iwd_Device *d = eina_hash_find(m->devices, path); + if (!d) + { + d = iwd_device_new(conn, path, m); + if (d) eina_hash_add(m->devices, path, d); + } + if (d) iwd_device_apply_device_props(d, props); + } + else if (!strcmp(iface, IWD_IFACE_STATION)) + { + Iwd_Device *d = eina_hash_find(m->devices, path); + if (!d) + { + d = iwd_device_new(conn, path, m); + if (d) eina_hash_add(m->devices, path, d); + } + if (d) + { + iwd_device_attach_station(d); + iwd_device_apply_station_props(d, props); + } + } + else if (!strcmp(iface, IWD_IFACE_NETWORK)) + { + Iwd_Network *n = eina_hash_find(m->networks, path); + if (!n) + { + n = iwd_network_new(conn, path, m); + if (n) eina_hash_add(m->networks, path, n); + } + if (n) iwd_network_apply_props(n, props); + } + /* Adapter / KnownNetwork: TODO (not needed for first connect path) */ + + iwd_manager_notify(m); +} + +static void +_on_iface_removed(void *data, const char *path, const char *iface) +{ + Iwd_Manager *m = data; + + if (!strcmp(iface, IWD_IFACE_STATION)) + { + Iwd_Device *d = eina_hash_find(m->devices, path); + if (d) iwd_device_detach_station(d); + } + else if (!strcmp(iface, IWD_IFACE_DEVICE)) + { + eina_hash_del(m->devices, path, NULL); + } + else if (!strcmp(iface, IWD_IFACE_NETWORK)) + { + eina_hash_del(m->networks, path, NULL); + } + iwd_manager_notify(m); +} + +static void +_on_name_appeared(void *data EINA_UNUSED) { /* GetManagedObjects will populate */ } + +static void +_on_name_vanished(void *data) +{ + Iwd_Manager *m = data; + eina_hash_free_buckets(m->devices); + eina_hash_free_buckets(m->networks); + m->state = IWD_STATE_OFF; + iwd_manager_notify(m); +} + +/* ----- lifecycle ------------------------------------------------------- */ + +static void _device_free_cb (void *d) { iwd_device_free(d); } +static void _network_free_cb(void *d) { iwd_network_free(d); } Iwd_Manager * iwd_manager_new(Eldbus_Connection *conn) { Iwd_Manager *m = calloc(1, sizeof(*m)); - m->dbus = iwd_dbus_new(conn); - m->state = IWD_STATE_OFF; + if (!m) return NULL; + m->devices = eina_hash_string_superfast_new(_device_free_cb); + m->networks = eina_hash_string_superfast_new(_network_free_cb); + m->state = IWD_STATE_OFF; + + Iwd_Dbus_Callbacks cbs = { + .name_appeared = _on_name_appeared, + .name_vanished = _on_name_vanished, + .iface_added = _on_iface_added, + .iface_removed = _on_iface_removed, + }; + m->dbus = iwd_dbus_new(conn, &cbs, m); return m; } @@ -29,16 +192,33 @@ iwd_manager_free(Iwd_Manager *m) { if (!m) return; iwd_dbus_free(m->dbus); - eina_list_free(m->devices); - eina_list_free(m->listeners); + eina_hash_free(m->devices); + eina_hash_free(m->networks); + Listener *li; + EINA_LIST_FREE(m->listeners, li) free(li); free(m); } -Iwd_State iwd_manager_state(const Iwd_Manager *m) { return m ? m->state : IWD_STATE_OFF; } -const Eina_List *iwd_manager_devices(const Iwd_Manager *m) { return m ? m->devices : NULL; } +Iwd_State iwd_manager_state (const Iwd_Manager *m) { return m ? m->state : IWD_STATE_OFF; } +const Eina_Hash *iwd_manager_devices (const Iwd_Manager *m) { return m ? m->devices : NULL; } +const Eina_Hash *iwd_manager_networks (const Iwd_Manager *m) { return m ? m->networks : NULL; } -void iwd_manager_scan_request(Iwd_Manager *m EINA_UNUSED) { /* TODO */ } -void iwd_manager_set_powered (Iwd_Manager *m EINA_UNUSED, Eina_Bool on EINA_UNUSED) { /* TODO */ } +void +iwd_manager_scan_request(Iwd_Manager *m) +{ + if (!m) return; + Eina_Iterator *it = eina_hash_iterator_data_new(m->devices); + Iwd_Device *d; + EINA_ITERATOR_FOREACH(it, d) iwd_device_scan(d); + eina_iterator_free(it); +} -void iwd_manager_listener_add(Iwd_Manager *m EINA_UNUSED, Iwd_Manager_Cb cb EINA_UNUSED, void *data EINA_UNUSED) { /* TODO */ } -void iwd_manager_listener_del(Iwd_Manager *m EINA_UNUSED, Iwd_Manager_Cb cb EINA_UNUSED, void *data EINA_UNUSED) { /* TODO */ } +void +iwd_manager_set_powered(Iwd_Manager *m, Eina_Bool on) +{ + if (!m) return; + Eina_Iterator *it = eina_hash_iterator_data_new(m->devices); + Iwd_Device *d; + EINA_ITERATOR_FOREACH(it, d) iwd_device_set_powered(d, on); + eina_iterator_free(it); +} diff --git a/src/iwd/iwd_manager.h b/src/iwd/iwd_manager.h index cc89950..0fd4b5b 100644 --- a/src/iwd/iwd_manager.h +++ b/src/iwd/iwd_manager.h @@ -19,14 +19,20 @@ Iwd_Manager *iwd_manager_new (Eldbus_Connection *conn); void iwd_manager_free(Iwd_Manager *m); Iwd_State iwd_manager_state (const Iwd_Manager *m); -const Eina_List *iwd_manager_devices (const Iwd_Manager *m); + +/* Hash */ +const Eina_Hash *iwd_manager_devices (const Iwd_Manager *m); +/* Hash */ +const Eina_Hash *iwd_manager_networks(const Iwd_Manager *m); void iwd_manager_scan_request (Iwd_Manager *m); void iwd_manager_set_powered (Iwd_Manager *m, Eina_Bool on); -/* Event callback signature for UI layer */ typedef void (*Iwd_Manager_Cb)(void *data, Iwd_Manager *m); void iwd_manager_listener_add (Iwd_Manager *m, Iwd_Manager_Cb cb, void *data); void iwd_manager_listener_del (Iwd_Manager *m, Iwd_Manager_Cb cb, void *data); +/* Internal: invoked by sub-objects when their state changes. */ +void iwd_manager_notify (Iwd_Manager *m); + #endif diff --git a/src/iwd/iwd_network.c b/src/iwd/iwd_network.c index 3a2ce4f..d46056d 100644 --- a/src/iwd/iwd_network.c +++ b/src/iwd/iwd_network.c @@ -1,11 +1,67 @@ #include "iwd_network.h" +#include "iwd_dbus.h" +#include "iwd_props.h" +#include "iwd_manager.h" +#include +#include + +static Iwd_Security +_sec_from_str(const char *s) +{ + if (!s) return IWD_SEC_UNKNOWN; + if (!strcmp(s, "open")) return IWD_SEC_OPEN; + if (!strcmp(s, "psk")) return IWD_SEC_PSK; + if (!strcmp(s, "8021x")) return IWD_SEC_8021X; + if (!strcmp(s, "wep")) return IWD_SEC_WEP; + return IWD_SEC_UNKNOWN; +} + +static void +_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) +{ + Iwd_Network *n = data; + if (!strcmp(key, "Name")) { free(n->ssid); n->ssid = iwd_props_str_dup(v); } + else if (!strcmp(key, "Type")) { char *s = iwd_props_str_dup(v); n->security = _sec_from_str(s); free(s); } + else if (!strcmp(key, "Connected")) { n->connected = iwd_props_bool(v); } + else if (!strcmp(key, "Device")) { free(n->device_path); n->device_path = iwd_props_str_dup(v); } + else if (!strcmp(key, "KnownNetwork")) { free(n->known_path); n->known_path = iwd_props_str_dup(v); } +} + +void +iwd_network_apply_props(Iwd_Network *n, Eldbus_Message_Iter *props) +{ + iwd_props_for_each(props, _prop_cb, n); +} + +static void +_on_props_changed(void *data, const Eldbus_Message *msg) +{ + Iwd_Network *n = data; + const char *iface; + Eldbus_Message_Iter *changed, *invalidated; + if (!eldbus_message_arguments_get(msg, "sa{sv}as", &iface, &changed, &invalidated)) + return; + if (strcmp(iface, IWD_IFACE_NETWORK) != 0) return; + iwd_props_for_each(changed, _prop_cb, n); + if (n->manager) iwd_manager_notify(n->manager); +} Iwd_Network * -iwd_network_new(Eldbus_Connection *conn EINA_UNUSED, const char *path) +iwd_network_new(Eldbus_Connection *conn, const char *path, void *manager) { Iwd_Network *n = calloc(1, sizeof(*n)); - if (path) n->path = strdup(path); - /* TODO: read Name, Type, KnownNetwork properties */ + if (!n) return NULL; + n->path = path ? strdup(path) : NULL; + n->manager = manager; + n->security = IWD_SEC_UNKNOWN; + n->obj = eldbus_object_get(conn, IWD_BUS_NAME, path); + if (n->obj) + { + n->proxy = eldbus_proxy_get(n->obj, IWD_IFACE_NETWORK); + if (n->proxy) + n->sh_props = eldbus_proxy_properties_changed_callback_add( + n->proxy, _on_props_changed, n); + } return n; } @@ -13,10 +69,37 @@ void iwd_network_free(Iwd_Network *n) { if (!n) return; + if (n->sh_props) eldbus_signal_handler_del(n->sh_props); + if (n->proxy) eldbus_proxy_unref(n->proxy); + if (n->obj) eldbus_object_unref(n->obj); free(n->path); free(n->ssid); + free(n->device_path); + free(n->known_path); free(n); } -void iwd_network_connect(Iwd_Network *n EINA_UNUSED) { /* TODO: Network.Connect() */ } -void iwd_network_forget (Iwd_Network *n EINA_UNUSED) { /* TODO: KnownNetwork.Forget() */ } +void +iwd_network_connect(Iwd_Network *n) +{ + if (!n || !n->proxy) return; + /* Network.Connect() takes no args; iwd will dial the registered Agent + * for a passphrase if needed. */ + eldbus_proxy_call(n->proxy, "Connect", NULL, NULL, -1, ""); +} + +void +iwd_network_forget(Iwd_Network *n) +{ + if (!n || !n->known_path || !n->obj) return; + Eldbus_Connection *conn = eldbus_object_connection_get(n->obj); + Eldbus_Object *kobj = eldbus_object_get(conn, IWD_BUS_NAME, n->known_path); + if (!kobj) return; + Eldbus_Proxy *kp = eldbus_proxy_get(kobj, IWD_IFACE_KNOWN_NETWORK); + if (kp) + { + eldbus_proxy_call(kp, "Forget", NULL, NULL, -1, ""); + eldbus_proxy_unref(kp); + } + eldbus_object_unref(kobj); +} diff --git a/src/iwd/iwd_network.h b/src/iwd/iwd_network.h index 52ef650..6e64c7e 100644 --- a/src/iwd/iwd_network.h +++ b/src/iwd/iwd_network.h @@ -9,6 +9,7 @@ typedef enum { IWD_SEC_PSK, IWD_SEC_8021X, IWD_SEC_WEP, + IWD_SEC_UNKNOWN, } Iwd_Security; typedef struct _Iwd_Network Iwd_Network; @@ -17,16 +18,25 @@ struct _Iwd_Network { char *path; char *ssid; + char *device_path; + char *known_path; Iwd_Security security; - int signal; /* 0..100 */ - Eina_Bool known; Eina_Bool connected; + + Eldbus_Object *obj; + Eldbus_Proxy *proxy; + Eldbus_Signal_Handler *sh_props; + + void *manager; }; -Iwd_Network *iwd_network_new (Eldbus_Connection *conn, const char *path); +Iwd_Network *iwd_network_new (Eldbus_Connection *conn, const char *path, void *manager); void iwd_network_free(Iwd_Network *n); +void iwd_network_apply_props(Iwd_Network *n, Eldbus_Message_Iter *props); + void iwd_network_connect (Iwd_Network *n); +/* Forget acts on the KnownNetwork object referenced by this network. */ void iwd_network_forget (Iwd_Network *n); #endif diff --git a/src/iwd/iwd_props.c b/src/iwd/iwd_props.c new file mode 100644 index 0000000..2f14696 --- /dev/null +++ b/src/iwd/iwd_props.c @@ -0,0 +1,38 @@ +#include "iwd_props.h" +#include + +void +iwd_props_for_each(Eldbus_Message_Iter *dict, Iwd_Prop_Cb cb, void *data) +{ + if (!dict || !cb) return; + Eldbus_Message_Iter *entry; + while (eldbus_message_iter_get_and_next(dict, 'e', &entry)) + { + const char *key; + Eldbus_Message_Iter *variant; + if (!eldbus_message_iter_arguments_get(entry, "sv", &key, &variant)) + continue; + cb(data, key, variant); + } +} + +char * +iwd_props_str_dup(Eldbus_Message_Iter *variant) +{ + if (!variant) return NULL; + const char *sig = eldbus_message_iter_signature_get(variant); + if (!sig || (sig[0] != 's' && sig[0] != 'o')) { free((void *)sig); return NULL; } + const char *s = NULL; + char want[2] = { sig[0], 0 }; + free((void *)sig); + if (!eldbus_message_iter_arguments_get(variant, want, &s)) return NULL; + return s ? strdup(s) : NULL; +} + +Eina_Bool +iwd_props_bool(Eldbus_Message_Iter *variant) +{ + Eina_Bool b = EINA_FALSE; + if (variant) eldbus_message_iter_arguments_get(variant, "b", &b); + return b; +} diff --git a/src/iwd/iwd_props.h b/src/iwd/iwd_props.h new file mode 100644 index 0000000..1ae3754 --- /dev/null +++ b/src/iwd/iwd_props.h @@ -0,0 +1,15 @@ +#ifndef IWD_PROPS_H +#define IWD_PROPS_H + +#include +#include + +/* Iterate an a{sv} message iter, calling cb for every entry. */ +typedef void (*Iwd_Prop_Cb)(void *data, const char *key, Eldbus_Message_Iter *value); +void iwd_props_for_each(Eldbus_Message_Iter *dict, Iwd_Prop_Cb cb, void *data); + +/* Extract a string from a variant iter ("s" or "o"). Returns strdup'd copy. */ +char *iwd_props_str_dup(Eldbus_Message_Iter *variant); +Eina_Bool iwd_props_bool(Eldbus_Message_Iter *variant); + +#endif diff --git a/src/meson.build b/src/meson.build index 09d74d9..a07b912 100644 --- a/src/meson.build +++ b/src/meson.build @@ -4,6 +4,7 @@ e_iwd_sources = [ 'e_mod_gadget.c', 'e_mod_popup.c', 'iwd/iwd_dbus.c', + 'iwd/iwd_props.c', 'iwd/iwd_manager.c', 'iwd/iwd_device.c', 'iwd/iwd_network.c',