Five sites allocated with calloc() and dereferenced the result on the very next line. Under OOM the module would have segfaulted instead of degrading. Each site now bails (or sends a Canceled D-Bus error, in the agent path) when the allocation fails. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
599 lines
21 KiB
C
599 lines
21 KiB
C
#include "e_mod_main.h"
|
|
#include "e_mod_popup.h"
|
|
#include "iwd/iwd_manager.h"
|
|
#include "iwd/iwd_device.h"
|
|
#include "iwd/iwd_network.h"
|
|
#include "iwd/iwd_agent.h"
|
|
#include "iwd/iwd_labels.h"
|
|
#include "ui/wifi_auth.h"
|
|
#include "ui/wifi_hidden.h"
|
|
#include <e_gadcon_popup.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
typedef struct _Popup
|
|
{
|
|
E_Gadcon_Popup *gp;
|
|
Evas_Object *box;
|
|
Evas_Object *status_lbl;
|
|
Evas_Object *list;
|
|
Evas_Object *btn_scan;
|
|
Evas_Object *btn_toggle;
|
|
Evas_Object *btn_hidden;
|
|
Evas_Object *btn_disconnect; /* shown only when connected */
|
|
Evas_Object *action_row;
|
|
Eina_Bool listening;
|
|
} Popup;
|
|
|
|
static Popup *_popup = NULL;
|
|
|
|
/* Pending passphrase request from the agent — only one at a time. */
|
|
static Iwd_Agent_Request *_pending_req = NULL;
|
|
/* Tracked so iwd's Cancel(reason) can tear down the dialog. */
|
|
static Evas_Object *_pending_dialog = NULL;
|
|
/* One-shot passphrase pre-armed by the hidden-network dialog. Bound to the
|
|
* SSID it was entered for, with a timeout that wipes it if iwd never comes
|
|
* asking — so a stashed passphrase can never leak to an unrelated network. */
|
|
static char *_hidden_pending_pass = NULL;
|
|
static char *_hidden_pending_ssid = NULL;
|
|
static Ecore_Timer *_hidden_pending_timer = NULL;
|
|
#define HIDDEN_PASS_TIMEOUT 30.0 /* seconds */
|
|
|
|
static void
|
|
_hidden_pending_clear(void)
|
|
{
|
|
if (_hidden_pending_pass)
|
|
{
|
|
explicit_bzero(_hidden_pending_pass, strlen(_hidden_pending_pass));
|
|
free(_hidden_pending_pass);
|
|
_hidden_pending_pass = NULL;
|
|
}
|
|
free(_hidden_pending_ssid);
|
|
_hidden_pending_ssid = NULL;
|
|
if (_hidden_pending_timer)
|
|
{
|
|
ecore_timer_del(_hidden_pending_timer);
|
|
_hidden_pending_timer = NULL;
|
|
}
|
|
}
|
|
|
|
static Eina_Bool
|
|
_hidden_pending_timeout(void *data EINA_UNUSED)
|
|
{
|
|
_hidden_pending_timer = NULL;
|
|
_hidden_pending_clear();
|
|
return ECORE_CALLBACK_CANCEL;
|
|
}
|
|
|
|
/* ----- helpers --------------------------------------------------------- */
|
|
|
|
static int
|
|
_net_cmp(const void *a, const void *b)
|
|
{
|
|
const Iwd_Network *na = a, *nb = b;
|
|
if (na->connected != nb->connected) return nb->connected - na->connected;
|
|
if (na->known_path && !nb->known_path) return -1;
|
|
if (!na->known_path && nb->known_path) return 1;
|
|
/* Higher signal first within same class. */
|
|
int ta = iwd_network_signal_tier(na);
|
|
int tb = iwd_network_signal_tier(nb);
|
|
if (ta != tb) return tb - ta;
|
|
if (!na->ssid) return 1;
|
|
if (!nb->ssid) return -1;
|
|
return strcasecmp(na->ssid, nb->ssid);
|
|
}
|
|
|
|
static const char *
|
|
_signal_bars(int tier)
|
|
{
|
|
switch (tier)
|
|
{
|
|
case 4: return "▂▄▆█";
|
|
case 3: return "▂▄▆ ";
|
|
case 2: return "▂▄ ";
|
|
case 1: return "▂ ";
|
|
default: return " ";
|
|
}
|
|
}
|
|
|
|
/* First device that has a station; used for "Disconnect" and hidden connect. */
|
|
static Iwd_Device *
|
|
_active_device(void)
|
|
{
|
|
if (!e_iwd || !e_iwd->manager) return NULL;
|
|
const Eina_Hash *h = iwd_manager_devices(e_iwd->manager);
|
|
if (!h) return NULL;
|
|
Eina_Iterator *it = eina_hash_iterator_data_new((Eina_Hash *)h);
|
|
Iwd_Device *d, *best = NULL;
|
|
EINA_ITERATOR_FOREACH(it, d)
|
|
{
|
|
if (!d->has_station) continue;
|
|
if (!best) best = d;
|
|
if (d->connected_network) { best = d; break; }
|
|
}
|
|
eina_iterator_free(it);
|
|
return best;
|
|
}
|
|
|
|
/* ----- list rendering -------------------------------------------------- */
|
|
|
|
/* Click data is a strdup'd object path, freed via the row's EVAS_CALLBACK_DEL.
|
|
* Holding the Iwd_Network * directly would UAF if iwd vanished (its hash is
|
|
* cleared in _on_name_vanished) between row paint and click. */
|
|
static void
|
|
_row_path_free(void *data, Evas *e EINA_UNUSED,
|
|
Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
|
{ free(data); }
|
|
|
|
static void
|
|
_on_net_clicked(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED)
|
|
{
|
|
const char *netpath = data;
|
|
if (!netpath || !e_iwd || !e_iwd->manager) return;
|
|
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
|
Iwd_Network *n = h ? eina_hash_find(h, netpath) : NULL;
|
|
if (!n) return;
|
|
iwd_manager_clear_error(e_iwd->manager);
|
|
iwd_network_connect(n);
|
|
}
|
|
|
|
/* The Iwd_Network captured when the confirmation popup was opened may have
|
|
* disappeared (scan refresh, iwd restart) by the time the user clicks. We
|
|
* stash its object path on the popup and re-resolve through the live hash
|
|
* at click time so a stale pointer is never dereferenced. */
|
|
static void _forget_confirm_free(void *data, Evas *e EINA_UNUSED,
|
|
Evas_Object *o EINA_UNUSED, void *ev EINA_UNUSED)
|
|
{ free(data); }
|
|
|
|
static void _forget_confirm_yes(void *data, Evas_Object *obj, void *ev EINA_UNUSED)
|
|
{
|
|
const char *netpath = data;
|
|
Evas_Object *pp = evas_object_data_get(obj, "_eiwd_confirm_popup");
|
|
if (netpath && e_iwd && e_iwd->manager)
|
|
{
|
|
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
|
Iwd_Network *n = h ? eina_hash_find(h, netpath) : NULL;
|
|
if (n) iwd_network_forget(n);
|
|
}
|
|
if (pp) evas_object_del(pp);
|
|
}
|
|
|
|
static void _forget_confirm_no(void *data EINA_UNUSED, Evas_Object *obj, void *ev EINA_UNUSED)
|
|
{
|
|
Evas_Object *pp = evas_object_data_get(obj, "_eiwd_confirm_popup");
|
|
if (pp) evas_object_del(pp);
|
|
}
|
|
|
|
static void
|
|
_on_net_forget(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED)
|
|
{
|
|
const char *netpath = data;
|
|
if (!netpath || !e_iwd || !e_iwd->manager) return;
|
|
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
|
Iwd_Network *n = h ? eina_hash_find(h, netpath) : NULL;
|
|
if (!n) return;
|
|
|
|
/* Forget destroys the saved passphrase irreversibly — confirm first.
|
|
* A stray click on the ✕ next to a known network would otherwise wipe
|
|
* credentials with no recovery. */
|
|
Evas_Object *parent = _popup ? _popup->box : e_comp->elm;
|
|
Evas_Object *pp = elm_popup_add(parent);
|
|
char msg[256];
|
|
snprintf(msg, sizeof(msg),
|
|
"Forget saved network <b>%s</b>?<br/>"
|
|
"The passphrase will be permanently deleted.",
|
|
n->ssid ? n->ssid : "(hidden)");
|
|
elm_object_part_text_set(pp, "title,text", "Forget network");
|
|
elm_object_text_set(pp, msg);
|
|
|
|
/* Weak ref by netpath — looked up at click time. Freed when the popup
|
|
* is destroyed (either button or the user closing the window). */
|
|
char *netpath_ref = n->path ? strdup(n->path) : NULL;
|
|
if (netpath_ref)
|
|
evas_object_event_callback_add(pp, EVAS_CALLBACK_DEL,
|
|
_forget_confirm_free, netpath_ref);
|
|
|
|
Evas_Object *yes = elm_button_add(pp);
|
|
elm_object_text_set(yes, "Forget");
|
|
evas_object_data_set(yes, "_eiwd_confirm_popup", pp);
|
|
evas_object_smart_callback_add(yes, "clicked", _forget_confirm_yes, netpath_ref);
|
|
elm_object_part_content_set(pp, "button1", yes);
|
|
|
|
Evas_Object *no = elm_button_add(pp);
|
|
elm_object_text_set(no, "Cancel");
|
|
evas_object_data_set(no, "_eiwd_confirm_popup", pp);
|
|
evas_object_smart_callback_add(no, "clicked", _forget_confirm_no, NULL);
|
|
elm_object_part_content_set(pp, "button2", no);
|
|
|
|
evas_object_show(pp);
|
|
}
|
|
|
|
static void
|
|
_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;
|
|
|
|
/* Snapshot into a list so we can sort. */
|
|
Eina_List *items = NULL;
|
|
Eina_Iterator *it = eina_hash_iterator_data_new((Eina_Hash *)h);
|
|
Iwd_Network *n;
|
|
EINA_ITERATOR_FOREACH(it, n) items = eina_list_append(items, n);
|
|
eina_iterator_free(it);
|
|
items = eina_list_sort(items, eina_list_count(items), _net_cmp);
|
|
|
|
Eina_List *l;
|
|
EINA_LIST_FOREACH(items, l, n)
|
|
{
|
|
Evas_Object *row = elm_box_add(p->list);
|
|
elm_box_horizontal_set(row, EINA_TRUE);
|
|
elm_box_padding_set(row, 4, 0);
|
|
evas_object_size_hint_weight_set(row, EVAS_HINT_EXPAND, 0);
|
|
evas_object_size_hint_align_set(row, EVAS_HINT_FILL, 0);
|
|
|
|
Evas_Object *btn = elm_button_add(row);
|
|
/* Truncate long SSIDs so the row never forces horizontal scrolling.
|
|
* Count codepoints, not bytes — SSIDs may contain UTF-8 and naive
|
|
* byte truncation would split a multi-byte sequence. */
|
|
const char *raw_ssid = n->ssid ? n->ssid : "(hidden)";
|
|
char ssid_buf[64];
|
|
const int max_chars = 21;
|
|
int iindex = 0, prev = 0, chars = 0;
|
|
while (chars < max_chars
|
|
&& eina_unicode_utf8_next_get(raw_ssid, &iindex))
|
|
{ prev = iindex; chars++; }
|
|
if (eina_unicode_utf8_next_get(raw_ssid, &iindex))
|
|
{
|
|
/* more remains — truncate at `prev` and append U+2026 */
|
|
int copy = prev < (int)sizeof(ssid_buf) - 4
|
|
? prev : (int)sizeof(ssid_buf) - 4;
|
|
memcpy(ssid_buf, raw_ssid, copy);
|
|
memcpy(ssid_buf + copy, "\xe2\x80\xa6", 4); /* "…" + NUL */
|
|
raw_ssid = ssid_buf;
|
|
}
|
|
char label[256];
|
|
snprintf(label, sizeof(label), "%s %s%s [%s]%s",
|
|
_signal_bars(iwd_network_signal_tier(n)),
|
|
n->known_path ? "★ " : " ",
|
|
raw_ssid,
|
|
iwd_security_label(n->security),
|
|
n->connected ? " ✔" : "");
|
|
elm_object_text_set(btn, label);
|
|
/* Spoken label: avoids the ▂▄▆█ glyphs and ★/✔ markers which
|
|
* screen readers announce as raw Unicode. */
|
|
char access[256];
|
|
snprintf(access, sizeof(access),
|
|
"%s, signal %d of 4, %s%s%s",
|
|
raw_ssid,
|
|
iwd_network_signal_tier(n),
|
|
iwd_security_label(n->security),
|
|
n->known_path ? ", saved" : "",
|
|
n->connected ? ", connected" : "");
|
|
elm_object_access_info_set(btn, access);
|
|
evas_object_size_hint_weight_set(btn, EVAS_HINT_EXPAND, 0);
|
|
evas_object_size_hint_align_set(btn, EVAS_HINT_FILL, 0);
|
|
/* Pass a copy of the object path, not the Iwd_Network *: the network
|
|
* may disappear (iwd_dbus name-vanished, scan refresh) before the
|
|
* user clicks, freeing the struct. The path is re-resolved through
|
|
* the live hash at click time. The buffer is freed via the button's
|
|
* EVAS_CALLBACK_DEL when the row is rebuilt or popup torn down. */
|
|
char *click_path = n->path ? strdup(n->path) : NULL;
|
|
if (click_path)
|
|
evas_object_event_callback_add(btn, EVAS_CALLBACK_DEL,
|
|
_row_path_free, click_path);
|
|
evas_object_smart_callback_add(btn, "clicked", _on_net_clicked, click_path);
|
|
elm_box_pack_end(row, btn);
|
|
evas_object_show(btn);
|
|
|
|
if (n->known_path)
|
|
{
|
|
Evas_Object *fb = elm_button_add(row);
|
|
elm_object_text_set(fb, "✕");
|
|
elm_object_tooltip_text_set(fb, "Forget network");
|
|
char facc[128];
|
|
snprintf(facc, sizeof(facc), "Forget %s", raw_ssid);
|
|
elm_object_access_info_set(fb, facc);
|
|
char *forget_path = n->path ? strdup(n->path) : NULL;
|
|
if (forget_path)
|
|
evas_object_event_callback_add(fb, EVAS_CALLBACK_DEL,
|
|
_row_path_free, forget_path);
|
|
evas_object_smart_callback_add(fb, "clicked", _on_net_forget, forget_path);
|
|
elm_box_pack_end(row, fb);
|
|
evas_object_show(fb);
|
|
}
|
|
|
|
elm_box_pack_end(p->list, row);
|
|
evas_object_show(row);
|
|
}
|
|
eina_list_free(items);
|
|
}
|
|
|
|
static void
|
|
_refresh(Popup *p)
|
|
{
|
|
if (!p || !e_iwd || !e_iwd->manager) return;
|
|
Iwd_State s = iwd_manager_state(e_iwd->manager);
|
|
/* Radio went off: the stash can no longer be useful (iwd won't ask)
|
|
* and we'd rather not keep a passphrase resident across a toggle. */
|
|
if (s == IWD_STATE_OFF) _hidden_pending_clear();
|
|
if (p->status_lbl)
|
|
{
|
|
const char *err = iwd_manager_last_error(e_iwd->manager);
|
|
if (err)
|
|
{
|
|
char buf[320];
|
|
snprintf(buf, sizeof(buf), "%s — %s", iwd_state_label(s), err);
|
|
elm_object_text_set(p->status_lbl, buf);
|
|
}
|
|
else
|
|
elm_object_text_set(p->status_lbl, iwd_state_label(s));
|
|
}
|
|
if (p->btn_toggle)
|
|
elm_object_text_set(p->btn_toggle, s == IWD_STATE_OFF ? "Enable" : "Disable");
|
|
if (p->btn_scan)
|
|
elm_object_disabled_set(p->btn_scan, s == IWD_STATE_OFF);
|
|
if (p->btn_hidden)
|
|
elm_object_disabled_set(p->btn_hidden, s == IWD_STATE_OFF);
|
|
if (p->btn_disconnect)
|
|
{
|
|
Eina_Bool show = (s == IWD_STATE_CONNECTED);
|
|
if (show) evas_object_show(p->btn_disconnect);
|
|
else evas_object_hide(p->btn_disconnect);
|
|
}
|
|
_rebuild_list(p);
|
|
}
|
|
|
|
static void
|
|
_on_manager_change(void *data, Iwd_Manager *m EINA_UNUSED)
|
|
{
|
|
_refresh(data);
|
|
}
|
|
|
|
/* ----- action buttons -------------------------------------------------- */
|
|
|
|
static void _on_rescan (void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
|
|
{
|
|
if (!e_iwd || !e_iwd->manager) return;
|
|
iwd_manager_clear_error(e_iwd->manager);
|
|
iwd_manager_scan_request(e_iwd->manager);
|
|
}
|
|
static void _on_toggle(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
|
|
{
|
|
if (!e_iwd || !e_iwd->manager) return;
|
|
iwd_manager_clear_error(e_iwd->manager);
|
|
Eina_Bool off = (iwd_manager_state(e_iwd->manager) == IWD_STATE_OFF);
|
|
iwd_manager_set_powered(e_iwd->manager, off);
|
|
}
|
|
static void _on_disconnect(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
|
|
{
|
|
Iwd_Device *dev = _active_device();
|
|
if (!dev) return;
|
|
if (e_iwd && e_iwd->manager) iwd_manager_clear_error(e_iwd->manager);
|
|
iwd_device_disconnect(dev);
|
|
}
|
|
|
|
static void
|
|
_on_hidden_done(void *data EINA_UNUSED, const char *ssid, const char *pass, Eina_Bool ok)
|
|
{
|
|
if (!ok || !ssid || !*ssid) return;
|
|
Iwd_Device *dev = _active_device();
|
|
if (!dev) return;
|
|
/* Pre-arm the agent reply so the next RequestPassphrase from iwd for
|
|
* *this SSID* is answered automatically. The stash is bound to the SSID
|
|
* and self-clears after HIDDEN_PASS_TIMEOUT seconds — so a typo'd or
|
|
* out-of-range SSID cannot cause the passphrase to leak to a later,
|
|
* unrelated network whose RequestPassphrase happens to land first. */
|
|
_hidden_pending_clear();
|
|
if (pass && *pass)
|
|
{
|
|
_hidden_pending_pass = strdup(pass);
|
|
_hidden_pending_ssid = strdup(ssid);
|
|
_hidden_pending_timer = ecore_timer_add(HIDDEN_PASS_TIMEOUT,
|
|
_hidden_pending_timeout, NULL);
|
|
}
|
|
iwd_device_connect_hidden(dev, ssid);
|
|
}
|
|
|
|
static void _on_hidden(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED)
|
|
{
|
|
wifi_hidden_prompt(_popup ? _popup->box : e_comp->elm, _on_hidden_done, NULL);
|
|
}
|
|
|
|
/* ----- passphrase plumbing -------------------------------------------- */
|
|
|
|
static void
|
|
_on_auth_done(void *data EINA_UNUSED, const char *pass, Eina_Bool ok)
|
|
{
|
|
_pending_dialog = NULL;
|
|
if (!_pending_req)
|
|
{
|
|
/* Request was already canceled by iwd; nothing to do. The caller
|
|
* (wifi_auth) wipes its own copy of `pass` after we return. */
|
|
return;
|
|
}
|
|
if (ok) iwd_agent_reply (_pending_req, pass ? pass : "");
|
|
else iwd_agent_cancel(_pending_req);
|
|
_pending_req = NULL;
|
|
}
|
|
|
|
static void
|
|
_on_agent_cancel(void *data EINA_UNUSED, const char *reason EINA_UNUSED)
|
|
{
|
|
/* iwd dropped the auth attempt — close any open dialog. The dialog's
|
|
* DEL handler will fire _on_auth_done(ok=FALSE), but _pending_req has
|
|
* already been consumed by iwd, so clear it first to avoid double-cancel. */
|
|
_pending_req = NULL;
|
|
if (_pending_dialog)
|
|
{
|
|
Evas_Object *d = _pending_dialog;
|
|
_pending_dialog = NULL;
|
|
evas_object_del(d);
|
|
}
|
|
}
|
|
|
|
static void _on_passphrase_request(void *data, Iwd_Agent_Request *req, const char *netpath);
|
|
|
|
void
|
|
e_iwd_popup_install_passphrase_handler(void)
|
|
{
|
|
if (e_iwd && e_iwd->manager)
|
|
{
|
|
iwd_manager_set_passphrase_handler(e_iwd->manager, _on_passphrase_request, NULL);
|
|
iwd_manager_set_cancel_handler (e_iwd->manager, _on_agent_cancel, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_on_passphrase_request(void *data EINA_UNUSED, Iwd_Agent_Request *req, const char *netpath)
|
|
{
|
|
/* Resolve netpath -> network so we can both match the hidden stash
|
|
* against the requested SSID *and* show a friendly label in the dialog. */
|
|
Iwd_Network *n = NULL;
|
|
if (e_iwd && e_iwd->manager)
|
|
{
|
|
const Eina_Hash *h = iwd_manager_networks(e_iwd->manager);
|
|
if (h) n = eina_hash_find(h, netpath);
|
|
}
|
|
const char *req_ssid = (n && n->ssid) ? n->ssid : NULL;
|
|
|
|
/* Use the hidden-network stash *only* if iwd is asking for the same
|
|
* SSID we entered it for. Anything else: drop the stash on the floor
|
|
* and prompt normally. */
|
|
if (_hidden_pending_pass && _hidden_pending_ssid &&
|
|
req_ssid && !strcmp(req_ssid, _hidden_pending_ssid))
|
|
{
|
|
iwd_agent_reply(req, _hidden_pending_pass);
|
|
_hidden_pending_clear();
|
|
return;
|
|
}
|
|
|
|
if (_pending_req)
|
|
{
|
|
iwd_agent_cancel(req);
|
|
return;
|
|
}
|
|
_pending_req = req;
|
|
|
|
const char *ssid = req_ssid ? req_ssid : "network";
|
|
const char *sec = n ? iwd_security_label(n->security) : NULL;
|
|
_pending_dialog = wifi_auth_prompt(_popup ? _popup->box : e_comp->elm,
|
|
ssid, sec, _on_auth_done, NULL);
|
|
}
|
|
|
|
/* ----- popup lifecycle ------------------------------------------------- */
|
|
|
|
static void
|
|
_destroy(void)
|
|
{
|
|
if (!_popup) return;
|
|
if (_popup->listening && e_iwd && e_iwd->manager)
|
|
iwd_manager_listener_del(e_iwd->manager, _on_manager_change, _popup);
|
|
if (_popup->gp) e_object_del(E_OBJECT(_popup->gp));
|
|
free(_popup);
|
|
_popup = NULL;
|
|
/* Drop any pre-armed hidden-network passphrase: if the user closes the
|
|
* popup before iwd asks, the stash would otherwise sit in the heap until
|
|
* the 30 s timer fires. The stash is process-global, not popup-scoped. */
|
|
_hidden_pending_clear();
|
|
}
|
|
|
|
void
|
|
e_iwd_popup_shutdown(void)
|
|
{
|
|
_destroy();
|
|
_hidden_pending_clear();
|
|
}
|
|
|
|
void
|
|
e_iwd_popup_close(void) { _destroy(); }
|
|
|
|
void
|
|
e_iwd_popup_refresh(void) { if (_popup) _refresh(_popup); }
|
|
|
|
void
|
|
e_iwd_popup_toggle(E_Gadcon_Client *gcc)
|
|
{
|
|
if (_popup) { _destroy(); return; }
|
|
if (!gcc || !e_iwd) return;
|
|
|
|
Popup *p = calloc(1, sizeof(*p));
|
|
if (!p) return;
|
|
_popup = p;
|
|
|
|
p->gp = e_gadcon_popup_new(gcc, EINA_FALSE);
|
|
|
|
Evas *evas = evas_object_evas_get(gcc->o_base);
|
|
Evas_Object *box = elm_box_add(e_comp->elm);
|
|
(void)evas;
|
|
elm_box_padding_set(box, 0, 4);
|
|
evas_object_size_hint_min_set(box, 240, 320);
|
|
p->box = box;
|
|
|
|
/* Status line */
|
|
Evas_Object *st = elm_label_add(box);
|
|
p->status_lbl = st;
|
|
elm_box_pack_end(box, st);
|
|
evas_object_show(st);
|
|
|
|
/* Network list (vertical box of buttons — keeps deps minimal) */
|
|
Evas_Object *scroller = elm_scroller_add(box);
|
|
evas_object_size_hint_weight_set(scroller, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
|
|
evas_object_size_hint_align_set(scroller, EVAS_HINT_FILL, EVAS_HINT_FILL);
|
|
Evas_Object *list_box = elm_box_add(scroller);
|
|
evas_object_size_hint_weight_set(list_box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
|
|
evas_object_size_hint_align_set(list_box, EVAS_HINT_FILL, EVAS_HINT_FILL);
|
|
elm_object_content_set(scroller, list_box);
|
|
evas_object_show(list_box);
|
|
elm_box_pack_end(box, scroller);
|
|
evas_object_show(scroller);
|
|
p->list = list_box;
|
|
|
|
/* Action row */
|
|
Evas_Object *row = elm_box_add(box);
|
|
elm_box_horizontal_set(row, EINA_TRUE);
|
|
elm_box_padding_set(row, 4, 0);
|
|
p->action_row = row;
|
|
|
|
p->btn_scan = elm_button_add(row);
|
|
elm_object_text_set(p->btn_scan, "Rescan");
|
|
evas_object_smart_callback_add(p->btn_scan, "clicked", _on_rescan, NULL);
|
|
elm_box_pack_end(row, p->btn_scan); evas_object_show(p->btn_scan);
|
|
|
|
p->btn_toggle = elm_button_add(row);
|
|
elm_object_text_set(p->btn_toggle, "Disable");
|
|
evas_object_smart_callback_add(p->btn_toggle, "clicked", _on_toggle, NULL);
|
|
elm_box_pack_end(row, p->btn_toggle); evas_object_show(p->btn_toggle);
|
|
|
|
p->btn_hidden = elm_button_add(row);
|
|
elm_object_text_set(p->btn_hidden, "Hidden…");
|
|
evas_object_smart_callback_add(p->btn_hidden, "clicked", _on_hidden, NULL);
|
|
elm_box_pack_end(row, p->btn_hidden); evas_object_show(p->btn_hidden);
|
|
|
|
p->btn_disconnect = elm_button_add(row);
|
|
elm_object_text_set(p->btn_disconnect, "Disconnect");
|
|
evas_object_smart_callback_add(p->btn_disconnect, "clicked", _on_disconnect, NULL);
|
|
elm_box_pack_end(row, p->btn_disconnect);
|
|
/* Visibility is driven by _refresh() based on connection state. */
|
|
|
|
elm_box_pack_end(box, row);
|
|
evas_object_show(row);
|
|
|
|
evas_object_show(box);
|
|
e_gadcon_popup_content_set(p->gp, box);
|
|
e_gadcon_popup_show(p->gp);
|
|
|
|
if (e_iwd->manager)
|
|
{
|
|
iwd_manager_listener_add(e_iwd->manager, _on_manager_change, p);
|
|
p->listening = EINA_TRUE;
|
|
iwd_manager_set_passphrase_handler(e_iwd->manager, _on_passphrase_request, NULL);
|
|
iwd_manager_scan_request(e_iwd->manager);
|
|
}
|
|
_refresh(p);
|
|
}
|