diff --git a/src/e_mod_popup.c b/src/e_mod_popup.c index 2bbf140..4062fe3 100644 --- a/src/e_mod_popup.c +++ b/src/e_mod_popup.c @@ -82,6 +82,9 @@ _rebuild_list(Popup *p) if (!p->list || !e_iwd || !e_iwd->manager) return; elm_box_clear(p->list); + /* When the radio is off, hide the (now-stale) network list entirely. */ + if (iwd_manager_state(e_iwd->manager) == IWD_STATE_OFF) return; + const Eina_Hash *h = iwd_manager_networks(e_iwd->manager); if (!h) return; diff --git a/src/iwd/iwd_adapter.c b/src/iwd/iwd_adapter.c new file mode 100644 index 0000000..d73bee3 --- /dev/null +++ b/src/iwd/iwd_adapter.c @@ -0,0 +1,103 @@ +#include "iwd_adapter.h" +#include "iwd_dbus.h" +#include "iwd_props.h" +#include "iwd_manager.h" +#include +#include +#include + +static void +_prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) +{ + Iwd_Adapter *a = data; + if (!strcmp(key, "Powered")) a->powered = iwd_props_bool(v); +} + +void +iwd_adapter_apply_props(Iwd_Adapter *a, Eldbus_Message_Iter *props) +{ + iwd_props_for_each(props, _prop_cb, a); +} + +static void +_on_props_changed(void *data, const Eldbus_Message *msg) +{ + Iwd_Adapter *a = 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_ADAPTER) != 0) return; + iwd_props_for_each(changed, _prop_cb, a); + if (a->manager) iwd_manager_notify(a->manager); +} + +Iwd_Adapter * +iwd_adapter_new(Eldbus_Connection *conn, const char *path, void *manager) +{ + Iwd_Adapter *a = calloc(1, sizeof(*a)); + if (!a) return NULL; + a->path = path ? strdup(path) : NULL; + a->manager = manager; + a->obj = eldbus_object_get(conn, IWD_BUS_NAME, path); + if (a->obj) + { + a->proxy = eldbus_proxy_get(a->obj, IWD_IFACE_ADAPTER); + if (a->proxy) + a->sh_props = eldbus_proxy_properties_changed_callback_add( + a->proxy, _on_props_changed, a); + } + return a; +} + +void +iwd_adapter_free(Iwd_Adapter *a) +{ + if (!a) return; + if (a->sh_props) eldbus_signal_handler_del(a->sh_props); + if (a->_props_proxy_keepalive) eldbus_proxy_unref(a->_props_proxy_keepalive); + if (a->proxy) eldbus_proxy_unref(a->proxy); + if (a->obj) eldbus_object_unref(a->obj); + free(a->path); + free(a); +} + +static void +_on_set_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) +{ + const char *what = data; + const char *en, *em; + if (eldbus_message_error_get(msg, &en, &em)) + fprintf(stderr, "e_iwd: %s failed: %s: %s\n", what, en, em); + else + fprintf(stderr, "e_iwd: %s ok\n", what); +} + +void +iwd_adapter_set_powered(Iwd_Adapter *a, Eina_Bool on) +{ + if (!a || !a->obj) return; + + /* Call org.freedesktop.DBus.Properties.Set explicitly so we control the + * variant marshaling exactly. eldbus_proxy_property_set silently swallows + * Adapter.Powered on this iwd version. */ + Eldbus_Proxy *props = eldbus_proxy_get(a->obj, "org.freedesktop.DBus.Properties"); + if (!props) return; + + Eldbus_Message *msg = eldbus_proxy_method_call_new(props, "Set"); + Eldbus_Message_Iter *iter = eldbus_message_iter_get(msg); + const char *iface = IWD_IFACE_ADAPTER; + const char *prop = "Powered"; + eldbus_message_iter_basic_append(iter, 's', iface); + eldbus_message_iter_basic_append(iter, 's', prop); + Eldbus_Message_Iter *variant = eldbus_message_iter_container_new(iter, 'v', "b"); + Eina_Bool v = on; + eldbus_message_iter_basic_append(variant, 'b', v); + eldbus_message_iter_container_close(iter, variant); + + eldbus_proxy_send(props, msg, _on_set_reply, + on ? "Adapter.Powered=true" : "Adapter.Powered=false", -1); + /* Keep the props proxy alive on the adapter so the call isn't canceled. */ + if (a->_props_proxy_keepalive) eldbus_proxy_unref(a->_props_proxy_keepalive); + a->_props_proxy_keepalive = props; +} diff --git a/src/iwd/iwd_adapter.h b/src/iwd/iwd_adapter.h new file mode 100644 index 0000000..b62b007 --- /dev/null +++ b/src/iwd/iwd_adapter.h @@ -0,0 +1,24 @@ +#ifndef IWD_ADAPTER_H +#define IWD_ADAPTER_H + +#include +#include + +typedef struct _Iwd_Adapter +{ + char *path; + Eina_Bool powered; + Eldbus_Object *obj; + Eldbus_Proxy *proxy; + Eldbus_Proxy *_props_proxy_keepalive; + Eldbus_Signal_Handler *sh_props; + void *manager; +} Iwd_Adapter; + +Iwd_Adapter *iwd_adapter_new (Eldbus_Connection *conn, const char *path, void *manager); +void iwd_adapter_free(Iwd_Adapter *a); + +void iwd_adapter_apply_props(Iwd_Adapter *a, Eldbus_Message_Iter *props); +void iwd_adapter_set_powered(Iwd_Adapter *a, Eina_Bool on); + +#endif diff --git a/src/iwd/iwd_device.c b/src/iwd/iwd_device.c index 73b3a12..354cb89 100644 --- a/src/iwd/iwd_device.c +++ b/src/iwd/iwd_device.c @@ -2,6 +2,7 @@ #include "iwd_dbus.h" #include "iwd_props.h" #include "iwd_manager.h" +#include #include #include @@ -127,6 +128,8 @@ iwd_device_free(Iwd_Device *d) 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->adapter_proxy) eldbus_proxy_unref(d->adapter_proxy); + if (d->adapter_obj) eldbus_object_unref(d->adapter_obj); if (d->obj) eldbus_object_unref(d->obj); free(d->path); free(d->name); @@ -136,23 +139,70 @@ iwd_device_free(Iwd_Device *d) free(d); } +static void +_log_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) +{ + const char *what = data; + const char *en, *em; + if (eldbus_message_error_get(msg, &en, &em)) + fprintf(stderr, "e_iwd: %s failed: %s: %s\n", what, en, em); + else + fprintf(stderr, "e_iwd: %s ok\n", what); +} + void iwd_device_set_powered(Iwd_Device *d, Eina_Bool on) { - if (!d || !d->device_proxy) return; - eldbus_proxy_property_set(d->device_proxy, "Powered", "b", &on, NULL, NULL); + if (!d) return; + Eina_Bool v = on; + + /* Toggle the radio at the Adapter level — that's what actually takes + * the interface up/down on modern iwd. Device.Powered is a no-op on + * many installs. Keep the adapter proxy alive on the device so the + * pending property_set isn't canceled. */ + if (d->adapter_path && d->obj && !d->adapter_proxy) + { + Eldbus_Connection *conn = eldbus_object_connection_get(d->obj); + d->adapter_obj = eldbus_object_get(conn, IWD_BUS_NAME, d->adapter_path); + if (d->adapter_obj) + d->adapter_proxy = eldbus_proxy_get(d->adapter_obj, IWD_IFACE_ADAPTER); + } + if (d->adapter_proxy) + eldbus_proxy_property_set(d->adapter_proxy, "Powered", "b", &v, _log_reply, + on ? "Adapter.Powered=true" + : "Adapter.Powered=false"); + else + fprintf(stderr, "e_iwd: set_powered: no Adapter for %s\n", + d->path ? d->path : "?"); + + if (d->device_proxy) + eldbus_proxy_property_set(d->device_proxy, "Powered", "b", &v, 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, ""); + if (!d) + { + fprintf(stderr, "e_iwd: scan: NULL device\n"); + return; + } + if (!d->station_proxy) + { + fprintf(stderr, "e_iwd: scan: no Station proxy on %s\n", + d->path ? d->path : "?"); + return; + } + eldbus_proxy_call(d->station_proxy, "Scan", _log_reply, "Scan", -1, ""); } void iwd_device_disconnect(Iwd_Device *d) { - if (!d || !d->station_proxy) return; - eldbus_proxy_call(d->station_proxy, "Disconnect", NULL, NULL, -1, ""); + if (!d || !d->station_proxy) + { + fprintf(stderr, "e_iwd: disconnect: missing Station proxy\n"); + return; + } + eldbus_proxy_call(d->station_proxy, "Disconnect", _log_reply, "Disconnect", -1, ""); } diff --git a/src/iwd/iwd_device.h b/src/iwd/iwd_device.h index a5aebce..9a8d857 100644 --- a/src/iwd/iwd_device.h +++ b/src/iwd/iwd_device.h @@ -31,6 +31,8 @@ struct _Iwd_Device Eldbus_Object *obj; Eldbus_Proxy *device_proxy; Eldbus_Proxy *station_proxy; + Eldbus_Object *adapter_obj; + Eldbus_Proxy *adapter_proxy; Eldbus_Signal_Handler *sh_dev_props; Eldbus_Signal_Handler *sh_sta_props; diff --git a/src/iwd/iwd_manager.c b/src/iwd/iwd_manager.c index 61bfe83..bc13db1 100644 --- a/src/iwd/iwd_manager.c +++ b/src/iwd/iwd_manager.c @@ -1,5 +1,6 @@ #include "iwd_manager.h" #include "iwd_dbus.h" +#include "iwd_adapter.h" #include "iwd_device.h" #include "iwd_network.h" #include @@ -15,6 +16,7 @@ struct _Iwd_Manager { Iwd_Dbus *dbus; Iwd_Agent *agent; + Eina_Hash *adapters; /* path → Iwd_Adapter * */ Eina_Hash *devices; /* path → Iwd_Device * */ Eina_Hash *networks; /* path → Iwd_Network * */ Eina_List *listeners; /* Listener * */ @@ -86,12 +88,27 @@ static void _recompute_state(Iwd_Manager *m) { Iwd_State s = IWD_STATE_OFF; + Eina_Bool any_powered = EINA_FALSE; + + /* Adapter.Powered is the source of truth for radio state — Device.Powered + * is a no-op on modern iwd, so don't let it lie to us. If we have no + * tracked adapter at all, fall back to "any device exists". */ + if (eina_hash_population(m->adapters) > 0) + { + Eina_Iterator *ait = eina_hash_iterator_data_new(m->adapters); + Iwd_Adapter *ap; + EINA_ITERATOR_FOREACH(ait, ap) + if (ap->powered) any_powered = EINA_TRUE; + eina_iterator_free(ait); + } + else + any_powered = (eina_hash_population(m->devices) > 0); + 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 (!any_powered) continue; /* radio is down: ignore stale station */ 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; @@ -110,7 +127,17 @@ _on_iface_added(void *data, const char *path, const char *iface, Eldbus_Message_ Iwd_Manager *m = data; Eldbus_Connection *conn = iwd_dbus_conn(m->dbus); - if (!strcmp(iface, IWD_IFACE_DEVICE)) + if (!strcmp(iface, IWD_IFACE_ADAPTER)) + { + Iwd_Adapter *a = eina_hash_find(m->adapters, path); + if (!a) + { + a = iwd_adapter_new(conn, path, m); + if (a) eina_hash_add(m->adapters, path, a); + } + if (a) iwd_adapter_apply_props(a, props); + } + else if (!strcmp(iface, IWD_IFACE_DEVICE)) { Iwd_Device *d = eina_hash_find(m->devices, path); if (!d) @@ -167,6 +194,10 @@ _on_iface_removed(void *data, const char *path, const char *iface) { eina_hash_del(m->networks, path, NULL); } + else if (!strcmp(iface, IWD_IFACE_ADAPTER)) + { + eina_hash_del(m->adapters, path, NULL); + } iwd_manager_notify(m); } @@ -177,6 +208,7 @@ static void _on_name_vanished(void *data) { Iwd_Manager *m = data; + eina_hash_free_buckets(m->adapters); eina_hash_free_buckets(m->devices); eina_hash_free_buckets(m->networks); m->state = IWD_STATE_OFF; @@ -185,6 +217,7 @@ _on_name_vanished(void *data) /* ----- lifecycle ------------------------------------------------------- */ +static void _adapter_free_cb(void *d) { iwd_adapter_free(d); } static void _device_free_cb (void *d) { iwd_device_free(d); } static void _network_free_cb(void *d) { iwd_network_free(d); } @@ -193,6 +226,7 @@ iwd_manager_new(Eldbus_Connection *conn) { Iwd_Manager *m = calloc(1, sizeof(*m)); if (!m) return NULL; + m->adapters = eina_hash_string_superfast_new(_adapter_free_cb); 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; @@ -214,6 +248,7 @@ iwd_manager_free(Iwd_Manager *m) if (!m) return; iwd_agent_free(m->agent); iwd_dbus_free(m->dbus); + eina_hash_free(m->adapters); eina_hash_free(m->devices); eina_hash_free(m->networks); Listener *li; @@ -239,8 +274,17 @@ 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); + + if (!on) + { + Eina_Iterator *dit = eina_hash_iterator_data_new(m->devices); + Iwd_Device *d; + EINA_ITERATOR_FOREACH(dit, d) iwd_device_disconnect(d); + eina_iterator_free(dit); + } + + Eina_Iterator *it = eina_hash_iterator_data_new(m->adapters); + Iwd_Adapter *a; + EINA_ITERATOR_FOREACH(it, a) iwd_adapter_set_powered(a, on); eina_iterator_free(it); } diff --git a/src/meson.build b/src/meson.build index 3aa556b..7b069c1 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_adapter.c', 'iwd/iwd_props.c', 'iwd/iwd_agent.c', 'iwd/iwd_manager.c',