manager: track Adapter objects + clear list on disable

Promote Adapter to a first-class manager object (Iwd_Adapter with
PropertiesChanged subscription). iwd_manager_set_powered now drives
the adapter directly, so Enable still works after Disable has torn
down the device hash. State recomputation also looks at any
powered adapter, and the popup hides the network list while
state == IWD_STATE_OFF.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-04-09 10:58:47 +07:00
commit 1b5dd32c0b
7 changed files with 239 additions and 12 deletions

View file

@ -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;

103
src/iwd/iwd_adapter.c Normal file
View file

@ -0,0 +1,103 @@
#include "iwd_adapter.h"
#include "iwd_dbus.h"
#include "iwd_props.h"
#include "iwd_manager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}

24
src/iwd/iwd_adapter.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef IWD_ADAPTER_H
#define IWD_ADAPTER_H
#include <Eina.h>
#include <Eldbus.h>
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

View file

@ -2,6 +2,7 @@
#include "iwd_dbus.h"
#include "iwd_props.h"
#include "iwd_manager.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -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, "");
}

View file

@ -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;

View file

@ -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 <stdlib.h>
@ -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);
}

View file

@ -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',