/* $Id$ */
/* Copyright (c) 2011-2020 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Desktop Panel */
/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. */
/* TODO:
 * - implement more control events
 * - make sure it works with WEP networks
 * - fix the case where every network available is disabled
 * - tooltip with details on the button (interface tracked...) */



#include <System.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <dirent.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <libintl.h>
#include "Panel/applet.h"
#define _(string) gettext(string)
#define N_(string) string

/* constants */
#ifndef PREFIX
# define PREFIX			"/usr/local"
#endif
#ifndef BINDIR
# define BINDIR			PREFIX "/bin"
#endif
#ifndef TMPDIR
# define TMPDIR			"/tmp"
#endif
#ifndef WPA_SUPPLICANT_PATH
# define WPA_SUPPLICANT_PATH	"/var/run/wpa_supplicant"
#endif

/* macros */
#define min(a, b)		((a) < (b) ? (a) : (b))

/* portability */
#ifndef SUN_LEN
# define SUN_LEN(su)		sizeof(struct sockaddr_un)
#endif


/* wpa_supplicant */
/* private */
/* types */
typedef enum _WPACommand
{
	WC_ADD_NETWORK = 0,	/* char const * ssid */
	WC_ATTACH,
	WC_DETACH,
	WC_DISABLE_NETWORK,	/* unsigned int id */
	WC_ENABLE_NETWORK,	/* unsigned int id */
	WC_LIST_NETWORKS,
	WC_LOGON,
	WC_LOGOFF,
	WC_REASSOCIATE,
	WC_REATTACH,
	WC_RECONFIGURE,
	WC_REMOVE_NETWORK,	/* unsigned int id */
	WC_SAVE_CONFIGURATION,
	WC_SCAN,
	WC_SCAN_RESULTS,
	WC_SELECT_NETWORK,	/* unsigned int id */
	WC_SET_NETWORK,		/* unsigned int id, gboolean quote,
				   char const * key, char const * value */
	WC_SET_PASSWORD,	/* unsigned int id, char const * password */
	WC_STATUS,
	WC_TERMINATE
} WPACommand;

typedef struct _WPAEntry
{
	WPACommand command;
	char * buf;
	size_t buf_cnt;
	/* for WC_ADD_NETWORK */
	char * ssid;
	uint32_t flags;
	gboolean connect;
} WPAEntry;

typedef struct _WPAChannel
{
	String * path;
	int fd;
	GIOChannel * channel;
	guint rd_source;
	guint wr_source;

	WPAEntry * queue;
	size_t queue_cnt;
} WPAChannel;

typedef struct _WPANetwork
{
	unsigned int id;
	char * name;
	int enabled;
} WPANetwork;

typedef enum _WPAScanResult
{
	WSR_UPDATED = 0,
	WSR_ICON,
	WSR_BSSID,
	WSR_FREQUENCY,
	WSR_LEVEL,
	WSR_FLAGS,
	WSR_SSID,
	WSR_SSID_DISPLAY,
	WSR_TOOLTIP,
	WSR_ENABLED,
	WSR_CAN_ENABLE
} WPAScanResult;
#define WSR_LAST WSR_CAN_ENABLE
#define WSR_COUNT (WSR_LAST + 1)

typedef enum _WPAScanResultFlag
{
	WSRF_IBSS	= 0x001,
	WSRF_WEP	= 0x002,
	WSRF_WPA	= 0x004,
	WSRF_WPA2	= 0x008,
	WSRF_PSK	= 0x010,
	WSRF_EAP	= 0x020,
	WSRF_CCMP	= 0x040,
	WSRF_TKIP	= 0x080,
	WSRF_PREAUTH	= 0x100,
	WSRF_ESS	= 0x200
} WPAScanResultFlag;

typedef struct _PanelApplet
{
	PanelAppletHelper * helper;

	guint source;
	WPAChannel channel[2];

	/* configuration */
	WPANetwork * networks;
	size_t networks_cnt;
	ssize_t networks_cur;
	gboolean autosave;

	/* status */
	gboolean connected;
	gboolean associated;
	guint level;
	uint32_t flags;

	/* widgets */
	GtkIconTheme * icontheme;
	GtkWidget * widget;
	GtkWidget * image;
	gboolean blink;
#ifndef EMBEDDED
	GtkWidget * label;
#endif
	GtkTreeStore * store;
	GtkWidget * pw_window;
	GtkWidget * pw_entry;
	unsigned int pw_id;
} WPA;


/* prototypes */
/* plug-in */
static WPA * _wpa_init(PanelAppletHelper * helper, GtkWidget ** widget);
static void _wpa_destroy(WPA * wpa);

/* accessors */
static GdkPixbuf * _wpa_get_icon(WPA * wpa, gint size, guint level,
		uint32_t flags);
static WPANetwork * _wpa_get_network(WPA * wpa, unsigned int id);
static int _wpa_set_current_network(WPA * wpa, WPANetwork * network);
static void _wpa_set_status(WPA * wpa, gboolean connected, gboolean associated,
		char const * network);

/* useful */
static int _wpa_error(WPA * wpa, char const * message, int ret);

static WPANetwork * _wpa_add_network(WPA * wpa, unsigned int id,
		char const * name, int enabled);

static void _wpa_connect(WPA * wpa, char const * ssid, uint32_t flags);
static void _wpa_connect_network(WPA * wpa, WPANetwork * network);
static void _wpa_disconnect(WPA * wpa);
static void _wpa_rescan(WPA * wpa);

static void _wpa_ask_password(WPA * wpa, WPANetwork * network);

static void _wpa_notify(WPA * wpa, char const * message);

static int _wpa_queue(WPA * wpa, WPAChannel * channel, WPACommand command, ...);

static int _wpa_reset(WPA * wpa);
static int _wpa_start(WPA * wpa);
static void _wpa_stop(WPA * wpa);

/* callbacks */
static void _on_clicked(gpointer data);
static gboolean _on_timeout(gpointer data);
static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition,
		gpointer data);
static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
		gpointer data);


/* public */
/* variables */
PanelAppletDefinition applet =
{
	N_("Wifi"),
	"network-wireless",
	NULL,
	_wpa_init,
	_wpa_destroy,
	NULL,
	FALSE,
	TRUE
};


/* private */
/* functions */
/* wpa_init */
static void _init_channel(WPAChannel * channel);

static WPA * _wpa_init(PanelAppletHelper * helper, GtkWidget ** widget)
{
	WPA * wpa;
	GtkWidget * hbox;
	PangoFontDescription * bold;
	char const * p;

	if((wpa = object_new(sizeof(*wpa))) == NULL)
		return NULL;
	wpa->helper = helper;
	wpa->source = 0;
	_init_channel(&wpa->channel[0]);
	_init_channel(&wpa->channel[1]);
	wpa->networks = NULL;
	wpa->networks_cnt = 0;
	wpa->networks_cur = -1;
	/* autosave except if explicitly disabled */
	p = helper->config_get(helper->panel, "wpa_supplicant", "autosave");
	wpa->autosave = (p == NULL || strtol(p, NULL, 10) != 0) ? TRUE : FALSE;
	wpa->connected = FALSE;
	wpa->associated = FALSE;
	wpa->level = 0;
	wpa->flags = 0;
	/* widgets */
	wpa->icontheme = gtk_icon_theme_get_default();
	bold = pango_font_description_new();
	pango_font_description_set_weight(bold, PANGO_WEIGHT_BOLD);
#if GTK_CHECK_VERSION(3, 0, 0)
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#else
	hbox = gtk_hbox_new(FALSE, 4);
#endif
	wpa->image = gtk_image_new();
	wpa->blink = FALSE;
	gtk_box_pack_start(GTK_BOX(hbox), wpa->image, FALSE, TRUE, 0);
#ifndef EMBEDDED
	wpa->label = gtk_label_new(" ");
# if GTK_CHECK_VERSION(3, 0, 0)
	gtk_widget_override_font(wpa->label, bold);
# else
	gtk_widget_modify_font(wpa->label, bold);
#endif
	gtk_box_pack_start(GTK_BOX(hbox), wpa->label, FALSE, TRUE, 0);
#endif
	wpa->store = gtk_tree_store_new(WSR_COUNT, G_TYPE_BOOLEAN,
			GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_UINT,
			G_TYPE_UINT, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING,
			G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
	_wpa_start(wpa);
	gtk_widget_show_all(hbox);
	pango_font_description_free(bold);
	if(helper->window == NULL
			|| panel_window_get_type(helper->window)
			== PANEL_WINDOW_TYPE_NOTIFICATION)
		wpa->widget = hbox;
	else
	{
		wpa->widget = gtk_button_new();
		gtk_button_set_relief(GTK_BUTTON(wpa->widget), GTK_RELIEF_NONE);
#if GTK_CHECK_VERSION(2, 12, 0)
		gtk_widget_set_tooltip_text(wpa->widget,
				_("Wireless networking"));
#endif
		g_signal_connect_swapped(wpa->widget, "clicked", G_CALLBACK(
					_on_clicked), wpa);
		gtk_container_add(GTK_CONTAINER(wpa->widget), hbox);
	}
	wpa->pw_window = NULL;
	wpa->pw_id = 0;
	_wpa_set_status(wpa, FALSE, FALSE, _("Unavailable"));
	*widget = wpa->widget;
	return wpa;
}

static void _init_channel(WPAChannel * channel)
{
	channel->path = NULL;
	channel->fd = -1;
	channel->channel = NULL;
	channel->rd_source = 0;
	channel->wr_source = 0;
	channel->queue = NULL;
	channel->queue_cnt = 0;
}


/* wpa_destroy */
static void _wpa_destroy(WPA * wpa)
{
	_wpa_stop(wpa);
	if(wpa->pw_window != NULL)
		gtk_widget_destroy(wpa->pw_window);
	gtk_widget_destroy(wpa->widget);
	object_delete(wpa);
}


/* accessors */
/* wpa_get_icon */
static GdkPixbuf * _wpa_get_icon(WPA * wpa, gint size, guint level,
		uint32_t flags)
{
	GdkPixbuf * ret;
	char const * name;
#if GTK_CHECK_VERSION(2, 14, 0)
	const int f = GTK_ICON_LOOKUP_USE_BUILTIN
		| GTK_ICON_LOOKUP_FORCE_SIZE;
#else
	const int f = GTK_ICON_LOOKUP_USE_BUILTIN;
#endif
	GdkPixbuf * pixbuf;
	char const * emblem = (flags & WSRF_WPA2) ? "security-high"
		: ((flags & WSRF_WPA) ? "security-medium"
				: ((flags & WSRF_WEP) ? "security-low" : NULL));

	/* FIXME check if the mapping is right (and use our own icons) */
	if(flags & WSRF_IBSS)
		name = "nm-adhoc";
	else if(level >= 200)
		name = "phone-signal-100";
	else if(level >= 150)
		name = "phone-signal-75";
	else if(level >= 100)
		name = "phone-signal-50";
	else if(level >= 50)
		name = "phone-signal-25";
	else
		name = "phone-signal-00";
	if((pixbuf = gtk_icon_theme_load_icon(wpa->icontheme, name, size, 0,
					NULL)) == NULL)
		return NULL;
	if((ret = gdk_pixbuf_copy(pixbuf)) == NULL)
		return pixbuf;
	g_object_unref(pixbuf);
	size = min(24, size / 2);
	if(emblem == NULL)
		return ret;
	pixbuf = gtk_icon_theme_load_icon(wpa->icontheme, emblem, size, f,
			NULL);
	if(pixbuf != NULL)
	{
		gdk_pixbuf_composite(pixbuf, ret, 0, 0, size, size, 0, 0, 1.0,
				1.0, GDK_INTERP_NEAREST, 255);
		g_object_unref(pixbuf);
	}
	return ret;
}


/* wpa_get_network */
static WPANetwork * _wpa_get_network(WPA * wpa, unsigned int id)
{
	size_t i;

	for(i = 0; i < wpa->networks_cnt; i++)
		if(wpa->networks[i].id == id)
			return &wpa->networks[i];
	return NULL;
}


/* wpa_set_current_network */
static int _wpa_set_current_network(WPA * wpa, WPANetwork * network)
{
	size_t i;

	for(i = 0; i < wpa->networks_cnt; i++)
		if(wpa->networks[i].id == network->id)
		{
			wpa->networks_cur = i;
			return 0;
		}
	return -1;
}


/* wpa_set_status */
static void _wpa_set_status(WPA * wpa, gboolean connected, gboolean associated,
		char const * network)
{
	GtkIconSize iconsize;
	char const * icon;
	gint size = 16;
	GdkPixbuf * pixbuf = NULL;
	char buf[80];

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%u, %u, \"%s\")\n", __func__, connected,
			associated, network);
#endif
	iconsize = (wpa->helper->window != NULL)
		? panel_window_get_icon_size(wpa->helper->window)
		: GTK_ICON_SIZE_SMALL_TOOLBAR;
	gtk_icon_size_lookup(iconsize, &size, &size);
#ifndef EMBEDDED
	gtk_widget_set_sensitive(wpa->widget, connected);
#else
	if(connected)
		gtk_widget_show(wpa->widget);
	else
		gtk_widget_hide(wpa->widget);
#endif
	if(connected == FALSE && network == NULL)
	{
		/* an error occurred */
		icon = "network-error";
		network = _("Error");
	}
	else if(connected == FALSE && associated == FALSE)
		/* not connected to wpa_supplicant */
		icon = "network-offline";
	else if(connected == FALSE && associated == TRUE)
	{
		/* connected to an interface */
		icon = "network-offline";
		if(wpa->connected != connected && network != NULL)
		{
			snprintf(buf, sizeof(buf),
					_("Connected to interface %s"),
					network);
			_wpa_notify(wpa, buf);
		}
		network = (network != NULL) ? network : _("Connecting...");
	}
	else if(associated == FALSE)
	{
		/* connected but not associated */
		icon = wpa->blink ? "network-idle"
			: "network-transmit-receive";
		wpa->blink = wpa->blink ? FALSE : TRUE;
		network = (network != NULL) ? network :  _("Scanning...");
	}
	else
	{
		/* connected and associated */
		icon = "network-idle";
		/* XXX use real values */
		pixbuf = _wpa_get_icon(wpa, size, wpa->level, wpa->flags);
		if(wpa->associated != associated && network != NULL)
		{
			snprintf(buf, sizeof(buf), _("Connected to network %s"),
					network);
			_wpa_notify(wpa, buf);
		}
	}
#if GTK_CHECK_VERSION(2, 6, 0)
	if(pixbuf != NULL)
	{
		gtk_image_set_from_pixbuf(GTK_IMAGE(wpa->image), pixbuf);
		g_object_unref(pixbuf);
	}
	else
		gtk_image_set_from_icon_name(GTK_IMAGE(wpa->image), icon,
				iconsize);
#else
	if(pixbuf != NULL || (pixbuf = gtk_icon_theme_load_icon(wpa->icontheme,
					icon, size, 0, NULL)) != NULL)
	{
		gtk_image_set_from_pixbuf(GTK_IMAGE(wpa->image), pixbuf);
		g_object_unref(pixbuf);
	}
	else
		gtk_image_set_from_stock(GTK_IMAGE(wpa->image),
				(connected && associated)
				? GTK_STOCK_CONNECT : GTK_STOCK_DISCONNECT,
				iconsize);
#endif
#ifndef EMBEDDED
	gtk_label_set_text(GTK_LABEL(wpa->label), network);
#endif
	wpa->connected = connected;
	wpa->associated = associated;
}


/* useful */
/* wpa_error */
static int _wpa_error(WPA * wpa, char const * message, int ret)
{
	error_set("%s: %s", applet.name, message);
	_wpa_set_status(wpa, FALSE, FALSE, NULL);
	return wpa->helper->error(NULL, error_get(NULL), ret);
}


/* wpa_ask_password */
static void _ask_password_window(WPA * wpa);
/* callbacks */
static gboolean _ask_password_on_closex(gpointer data);
static void _ask_password_on_response(GtkWidget * widget, gint response,
		gpointer data);
static void _ask_password_on_show(GtkWidget * widget, gpointer data);

static void _wpa_ask_password(WPA * wpa, WPANetwork * network)
{
	if(wpa->pw_window == NULL)
		_ask_password_window(wpa);
#if GTK_CHECK_VERSION(2, 6, 0)
	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(
				wpa->pw_window),
#else
	/* FIXME wrong */
	gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(wpa->pw_window),
#endif
			_("The network \"%s\" is protected by a key."),
			network->name);
	/* reset the text if the network has changed */
	if(wpa->pw_id != network->id)
		gtk_entry_set_text(GTK_ENTRY(wpa->pw_entry), "");
	wpa->pw_id = network->id;
	gtk_window_present(GTK_WINDOW(wpa->pw_window));
}

static void _ask_password_window(WPA * wpa)
{
	GtkWidget * dialog;
	GtkWidget * vbox;
	GtkWidget * hbox;
	GtkWidget * widget;

	dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_QUESTION,
			GTK_BUTTONS_OK_CANCEL, "%s", _("Password required"));
	wpa->pw_window = dialog;
	gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK);
#if GTK_CHECK_VERSION(2, 10, 0)
	wpa->pw_entry = gtk_image_new_from_icon_name("dialog-password",
			GTK_ICON_SIZE_DIALOG);
	gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog), wpa->pw_entry);
	gtk_widget_show(wpa->pw_entry);
#endif
#if GTK_CHECK_VERSION(2, 6, 0)
	gtk_window_set_icon_name(GTK_WINDOW(dialog), "dialog-password");
#endif
	gtk_window_set_keep_above(GTK_WINDOW(dialog), TRUE);
	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
	gtk_window_set_title(GTK_WINDOW(dialog), _("Wireless key"));
#if GTK_CHECK_VERSION(2, 14, 0)
	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
#else
	vbox = GTK_DIALOG(dialog)->vbox;
#endif
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
#if GTK_CHECK_VERSION(3, 0, 0)
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#else
	hbox = gtk_hbox_new(FALSE, 4);
#endif
	widget = gtk_label_new(_("Key: "));
	gtk_box_pack_start(GTK_BOX(hbox), widget, FALSE, TRUE, 0);
	wpa->pw_entry = gtk_entry_new();
	gtk_entry_set_activates_default(GTK_ENTRY(wpa->pw_entry), TRUE);
	gtk_entry_set_visibility(GTK_ENTRY(wpa->pw_entry), FALSE);
	gtk_box_pack_start(GTK_BOX(hbox), wpa->pw_entry, TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
#if GTK_CHECK_VERSION(3, 0, 0)
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#else
	hbox = gtk_hbox_new(FALSE, 4);
#endif
	widget = gtk_check_button_new_with_mnemonic(_("_Show password"));
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), FALSE);
	g_signal_connect(widget, "toggled", G_CALLBACK(_ask_password_on_show),
			wpa->pw_entry);
	gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
	gtk_widget_show_all(vbox);
	g_signal_connect_swapped(wpa->pw_window, "delete-event", G_CALLBACK(
				_ask_password_on_closex), wpa);
	g_signal_connect(wpa->pw_window, "response", G_CALLBACK(
				_ask_password_on_response), wpa);
}

static gboolean _ask_password_on_closex(gpointer data)
{
	WPA * wpa = data;

	gtk_widget_hide(wpa->pw_window);
	return TRUE;
}

static void _ask_password_on_response(GtkWidget * widget, gint response,
		gpointer data)
{
	WPA * wpa = data;
	char const * password;
	size_t i;
	WPAChannel * channel = &wpa->channel[0];
	(void) widget;

	if(response != GTK_RESPONSE_OK
			|| (password = gtk_entry_get_text(GTK_ENTRY(
						wpa->pw_entry))) == NULL)
		/* enable every network again */
		for(i = 0; i < wpa->networks_cnt; i++)
			_wpa_queue(wpa, &wpa->channel[0], WC_ENABLE_NETWORK, i);
	else
	{
		/* FIXME the network may have changed in the meantime */
		_wpa_queue(wpa, channel, WC_SET_PASSWORD, wpa->pw_id, password);
		if(wpa->autosave)
			_wpa_queue(wpa, channel, WC_SAVE_CONFIGURATION);
	}
	gtk_widget_hide(wpa->pw_window);
}

static void _ask_password_on_show(GtkWidget * widget, gpointer data)
{
	GtkWidget * entry = data;
	gboolean visible;

	visible = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
	gtk_entry_set_visibility(GTK_ENTRY(entry), visible);
}


/* wpa_add_network */
static WPANetwork * _wpa_add_network(WPA * wpa, unsigned int id,
		char const * name, int enabled)
{
	WPANetwork * n;

	if((n = realloc(wpa->networks, sizeof(*n) * (wpa->networks_cnt + 1)))
			== NULL)
		return NULL;
	wpa->networks = n;
	n = &wpa->networks[wpa->networks_cnt];
	n->id = id;
	if((n->name = strdup(name)) == NULL)
		return NULL;
	n->enabled = enabled;
	wpa->networks_cnt++;
	return n;
}


/* wpa_connect */
static void _wpa_connect(WPA * wpa, char const * ssid, uint32_t flags)
{
	WPAChannel * channel = &wpa->channel[0];
	size_t i;

	/* check if the network is already in the list */
	for(i = 0; i < wpa->networks_cnt; i++)
		if(strcmp(wpa->networks[i].name, ssid) == 0)
			break;
	if(i < wpa->networks_cnt)
		/* select this network directly */
		_wpa_connect_network(wpa, &wpa->networks[i]);
	else
		/* add (and then select) this network */
		_wpa_queue(wpa, channel, WC_ADD_NETWORK, ssid, flags, TRUE);
}


/* wpa_connect_network */
static void _wpa_connect_network(WPA * wpa, WPANetwork * network)
{
	WPAChannel * channel = &wpa->channel[0];

	/* select this network */
	_wpa_queue(wpa, channel, WC_SELECT_NETWORK, network->id);
	_wpa_queue(wpa, channel, WC_LIST_NETWORKS);
}


/* wpa_disconnect */
static void _wpa_disconnect(WPA * wpa)
{
	WPAChannel * channel = &wpa->channel[0];
	size_t i;

	/* enable every network again */
	for(i = 0; i < wpa->networks_cnt; i++)
		_wpa_queue(wpa, channel, WC_ENABLE_NETWORK, i);
	_wpa_queue(wpa, channel, WC_LIST_NETWORKS);
	_wpa_rescan(wpa);
}


/* wpa_notify */
static void _wpa_notify(WPA * wpa, char const * message)
{
	char const * p;
	char * argv[] = { BINDIR "/panel-message", "-N", NULL, "--", NULL,
		NULL };
	const unsigned int flags = 0;
	GError * error = NULL;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, message);
#endif
	/* check if notifications are enabled */
	if((p = wpa->helper->config_get(wpa->helper->panel, "wpa_supplicant",
					"notify")) == NULL
			|| strtol(p, NULL, 10) != 1)
		return;
	argv[2] = strdup(applet.icon);
	argv[4] = strdup(message);
	if(argv[2] == NULL || argv[4] == NULL)
		wpa->helper->error(NULL, strerror(errno), 1);
	else if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, NULL, &error)
			== FALSE)
	{
		wpa->helper->error(NULL, error->message, 1);
		g_error_free(error);
	}
	free(argv[4]);
	free(argv[2]);
}


/* wpa_queue */
static char * _queue_cmd(WPACommand command, va_list ap, char const ** ssid,
		uint32_t * flags, gboolean * connect);

static int _wpa_queue(WPA * wpa, WPAChannel * channel, WPACommand command, ...)
{
	va_list ap;
	char * cmd;
	WPAEntry * p;
	char const * ssid = NULL;
	uint32_t flags = 0;
	gboolean connect = FALSE;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(%u, ...)\n", __func__, command);
#endif
	if(channel->channel == NULL)
		return -1;
	va_start(ap, command);
	cmd = _queue_cmd(command, ap, &ssid, &flags, &connect);
	va_end(ap);
	if(cmd == NULL)
		return -1;
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, cmd);
#endif
	if((p = realloc(channel->queue, sizeof(*p) * (channel->queue_cnt + 1)))
			== NULL)
	{
		free(cmd);
		return -1;
	}
	channel->queue = p;
	p = &channel->queue[channel->queue_cnt];
	p->command = command;
	p->buf = cmd;
	p->buf_cnt = strlen(cmd);
	/* XXX may fail */
	p->ssid = (ssid != NULL) ? strdup(ssid) : NULL;
	p->flags = flags;
	p->connect = connect;
	if(channel->queue_cnt++ == 0)
		channel->wr_source = g_io_add_watch(channel->channel, G_IO_OUT,
				_on_watch_can_write, wpa);
	return 0;
}

static char * _queue_cmd(WPACommand command, va_list ap, char const ** ssid,
		uint32_t * flags, gboolean * connect)
{
	char * cmd = NULL;
	gboolean b;
	char const * s;
	char const * t;
	unsigned int u;

	switch(command)
	{
		case WC_ADD_NETWORK:
			cmd = g_strdup_printf("ADD_NETWORK");
			*ssid = va_arg(ap, char const *);
			*flags = va_arg(ap, uint32_t);
			*connect = va_arg(ap, gboolean);
			break;
		case WC_ATTACH:
			cmd = strdup("ATTACH");
			break;
		case WC_DETACH:
			cmd = strdup("DETACH");
			break;
		case WC_DISABLE_NETWORK:
			u = va_arg(ap, unsigned int);
			cmd = g_strdup_printf("DISABLE_NETWORK %u", u);
			break;
		case WC_ENABLE_NETWORK:
			u = va_arg(ap, unsigned int);
			cmd = g_strdup_printf("ENABLE_NETWORK %u", u);
			break;
		case WC_LIST_NETWORKS:
			cmd = strdup("LIST_NETWORKS");
			break;
		case WC_LOGON:
			cmd = strdup("LOGON");
			break;
		case WC_LOGOFF:
			cmd = strdup("LOGOFF");
			break;
		case WC_REASSOCIATE:
			cmd = strdup("REASSOCIATE");
			break;
		case WC_REATTACH:
			cmd = strdup("REATTACH");
			break;
		case WC_RECONFIGURE:
			cmd = strdup("RECONFIGURE");
			break;
		case WC_REMOVE_NETWORK:
			u = va_arg(ap, unsigned int);
			cmd = g_strdup_printf("REMOVE_NETWORK %u", u);
			break;
		case WC_SAVE_CONFIGURATION:
			cmd = strdup("SAVE_CONFIG");
			break;
		case WC_SCAN:
			cmd = strdup("SCAN");
			break;
		case WC_SCAN_RESULTS:
			cmd = strdup("SCAN_RESULTS");
			break;
		case WC_SELECT_NETWORK:
			u = va_arg(ap, unsigned int);
			cmd = g_strdup_printf("SELECT_NETWORK %u", u);
			break;
		case WC_SET_NETWORK:
			u = va_arg(ap, unsigned int);
			b = va_arg(ap, gboolean);
			s = va_arg(ap, char const *);
			t = va_arg(ap, char const *);
			cmd = g_strdup_printf("SET_NETWORK %u %s %s%s%s", u, s,
					b ? "\"" : "", t, b ? "\"" : "");
			break;
		case WC_SET_PASSWORD:
			/* FIXME really uses SET_NETWORK (and may not be psk) */
			u = va_arg(ap, unsigned int);
			s = va_arg(ap, char const *);
			cmd = g_strdup_printf("SET_NETWORK %u psk \"%s\"", u,
					s);
			break;
		case WC_STATUS:
			cmd = strdup("STATUS-VERBOSE");
			break;
		case WC_TERMINATE:
			cmd = strdup("TERMINATE");
			break;
	}
	return cmd;
}


/* wpa_rescan */
static void _wpa_rescan(WPA * wpa)
{
	WPAChannel * channel = &wpa->channel[0];

	_wpa_queue(wpa, channel, WC_SCAN);
}


/* wpa_reset */
static int _wpa_reset(WPA * wpa)
{
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	_wpa_stop(wpa);
	return _wpa_start(wpa);
}


/* wpa_start */
static gboolean _start_timeout(gpointer data);
static int _timeout_channel(WPA * wpa, WPAChannel * channel);
static int _timeout_channel_interface(WPA * wpa, WPAChannel * channel,
		char const * path, char const * interface);

static int _wpa_start(WPA * wpa)
{
	if(wpa->source != 0)
		g_source_remove(wpa->source);
	wpa->connected = FALSE;
	wpa->associated = FALSE;
	/* reconnect to the daemon */
	wpa->source = g_idle_add(_start_timeout, wpa);
	return 0;
}

static gboolean _start_timeout(gpointer data)
{
	const unsigned int timeout = 5000;
	WPA * wpa = data;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(_timeout_channel(wpa, &wpa->channel[0]) != 0
			|| _timeout_channel(wpa, &wpa->channel[1]) != 0)
	{
		_wpa_stop(wpa);
		wpa->source = g_timeout_add(timeout, _start_timeout, wpa);
		return FALSE;
	}
	_on_timeout(wpa);
	wpa->source = g_timeout_add(timeout, _on_timeout, wpa);
	_wpa_queue(wpa, &wpa->channel[1], WC_ATTACH);
	return FALSE;
}

static int _timeout_channel(WPA * wpa, WPAChannel * channel)
{
	int ret;
	char const path[] = WPA_SUPPLICANT_PATH;
	char const * interface;
	char const * p;
	DIR * dir;
	struct dirent * de;
	struct sockaddr_un lu;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if((p = getenv("TMPDIR")) == NULL)
		p = TMPDIR;
	if((channel->path = string_new_append(p, "/panel_wpa_supplicant.XXXXXX",
					NULL)) == NULL)
		return -wpa->helper->error(NULL, "snprintf", 1);
	if(mktemp(channel->path) == NULL)
		return -wpa->helper->error(NULL, "mktemp", 1);
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, channel->path);
#endif
	/* create the local socket */
	memset(&lu, 0, sizeof(lu));
	if(snprintf(lu.sun_path, sizeof(lu.sun_path), "%s", channel->path)
			>= (int)sizeof(lu.sun_path))
		/* XXX make sure this error is explicit enough */
		return -_wpa_error(wpa, channel->path, 1);
	lu.sun_family = AF_LOCAL;
	if((channel->fd = socket(AF_LOCAL, SOCK_DGRAM, 0)) == -1)
		return -_wpa_error(wpa, strerror(errno), 1);
	if(bind(channel->fd, (struct sockaddr *)&lu, SUN_LEN(&lu)) != 0)
		return -_wpa_error(wpa, channel->path, 1);
	if((interface = wpa->helper->config_get(wpa->helper->panel,
					"wpa_supplicant", "interface")) != NULL)
	{
		ret = _timeout_channel_interface(wpa, channel, path, interface);
		if(ret != 0)
			wpa->helper->error(NULL, interface, 1);
	}
	/* look for the first available interface */
	else if((dir = opendir(path)) != NULL)
	{
		for(ret = -1; ret != 0 && (de = readdir(dir)) != NULL;)
			ret = _timeout_channel_interface(wpa, channel, path,
					de->d_name);
		closedir(dir);
	}
	else
		ret = -wpa->helper->error(NULL, path, 1);
	return ret;
}

static int _timeout_channel_interface(WPA * wpa, WPAChannel * channel,
		char const * path, char const * interface)
{
	struct stat st;
	struct sockaddr_un ru;

	memset(&ru, 0, sizeof(ru));
	ru.sun_family = AF_UNIX;
	if(snprintf(ru.sun_path, sizeof(ru.sun_path), "%s/%s", path, interface)
			>= (int)sizeof(ru.sun_path)
			|| lstat(ru.sun_path, &st) != 0
			|| (st.st_mode & S_IFSOCK) != S_IFSOCK)
		return -1;
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, interface);
#endif
	/* connect to the wpa_supplicant daemon */
	if(connect(channel->fd, (struct sockaddr *)&ru, SUN_LEN(&ru)) != 0)
		return -wpa->helper->error(NULL, "connect", 1);
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() connected to %s\n", __func__, interface);
#endif
	/* XXX will be done for every channel */
	_wpa_set_status(wpa, FALSE, TRUE, interface);
	channel->channel = g_io_channel_unix_new(channel->fd);
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() %p\n", __func__, (void *)channel->channel);
#endif
	g_io_channel_set_encoding(channel->channel, NULL, NULL);
	g_io_channel_set_buffered(channel->channel, FALSE);
	channel->rd_source = g_io_add_watch(channel->channel, G_IO_IN,
			_on_watch_can_read, wpa);
	return 0;
}


/* wpa_stop */
static void _stop_channel(WPA * wpa, WPAChannel * channel);

static void _wpa_stop(WPA * wpa)
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	/* de-register the event source */
	if(wpa->source != 0)
		g_source_remove(wpa->source);
	wpa->source = 0;
	_stop_channel(wpa, &wpa->channel[0]);
	_stop_channel(wpa, &wpa->channel[1]);
	/* free the network list */
	gtk_tree_store_clear(wpa->store);
	for(i = 0; i < wpa->networks_cnt; i++)
		free(wpa->networks[i].name);
	free(wpa->networks);
	wpa->networks = NULL;
	wpa->networks_cnt = 0;
	wpa->networks_cur = -1;
	wpa->connected = FALSE;
	wpa->associated = FALSE;
	/* report the status */
	_wpa_set_status(wpa, FALSE, FALSE, _("Unavailable"));
	if(wpa->pw_window != NULL)
		gtk_widget_hide(wpa->pw_window);
}

static void _stop_channel(WPA * wpa, WPAChannel * channel)
{
	size_t i;

	if(channel->rd_source != 0)
		g_source_remove(channel->rd_source);
	channel->rd_source = 0;
	if(channel->wr_source != 0)
		g_source_remove(channel->wr_source);
	channel->wr_source = 0;
	/* free the command queue */
	for(i = 0; i < channel->queue_cnt; i++)
	{
		free(channel->queue[i].buf);
		free(channel->queue[i].ssid);
	}
	free(channel->queue);
	channel->queue = NULL;
	channel->queue_cnt = 0;
	/* close and remove the socket */
	if(channel->channel != NULL)
	{
		g_io_channel_shutdown(channel->channel, TRUE, NULL);
		g_io_channel_unref(channel->channel);
		channel->channel = NULL;
		channel->fd = -1;
	}
	if(channel->path != NULL)
		unlink(channel->path);
	if(channel->fd != -1 && close(channel->fd) != 0)
		wpa->helper->error(NULL, channel->path, 1);
	string_delete(channel->path);
	channel->path = NULL;
	channel->fd = -1;
}


/* callbacks */
/* on_clicked */
static void _clicked_available(WPA * wpa, GtkWidget * menu);
static void _clicked_network_view(WPA * wpa, GtkWidget * menu);
static void _clicked_position_menu(GtkMenu * menu, gint * x, gint * y,
		gboolean * push_in, gpointer data);
static void _clicked_preferences(WPA * wpa, GtkWidget * menu);
/* callbacks */
static void _clicked_on_disconnect(gpointer data);
static void _clicked_on_network_activated(GtkWidget * widget, gpointer data);
static void _clicked_on_preferences(gpointer data);
static void _clicked_on_reassociate(gpointer data);
static void _clicked_on_rescan(gpointer data);

static void _on_clicked(gpointer data)
{
	WPA * wpa = data;
	GtkWidget * menu;

	menu = gtk_menu_new();
	_clicked_preferences(wpa, menu);
	if(wpa->channel[0].fd >= 0)
		_clicked_available(wpa, menu);
	gtk_widget_show_all(menu);
	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, _clicked_position_menu,
			wpa, 0, gtk_get_current_event_time());
}

static void _clicked_available(WPA * wpa, GtkWidget * menu)
{
	GtkWidget * menuitem;
	GtkWidget * image;

	menuitem = gtk_separator_menu_item_new();
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
	if(wpa->networks_cur >= 0)
	{
		/* reassociate */
		menuitem = gtk_image_menu_item_new_with_label(_("Reassociate"));
#if GTK_CHECK_VERSION(2, 12, 0)
# if GTK_CHECK_VERSION(3, 10, 0)
		image = gtk_image_new_from_icon_name(GTK_STOCK_DISCARD,
				GTK_ICON_SIZE_MENU);
# else
		image = gtk_image_new_from_stock(GTK_STOCK_DISCARD,
				GTK_ICON_SIZE_MENU);
# endif
		gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
				image);
#endif
		g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
					_clicked_on_reassociate), wpa);
		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
		/* disconnect */
		menuitem = gtk_image_menu_item_new_with_label(_("Disconnect"));
#if GTK_CHECK_VERSION(3, 10, 0)
		image = gtk_image_new_from_icon_name(
#else
		image = gtk_image_new_from_stock(
#endif
				GTK_STOCK_DISCONNECT, GTK_ICON_SIZE_MENU);
		g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
					_clicked_on_disconnect), wpa);
		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
	}
	/* rescan */
	menuitem = gtk_image_menu_item_new_with_label(_("Rescan"));
#if GTK_CHECK_VERSION(3, 10, 0)
	image = gtk_image_new_from_icon_name(
#else
	image = gtk_image_new_from_stock(
#endif
			GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU);
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem), image);
	g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
				_clicked_on_rescan), wpa);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
	/* view */
	_clicked_network_view(wpa, menu);
}

static void _clicked_network_view(WPA * wpa, GtkWidget * menu)
{
	GtkTreeModel * model = GTK_TREE_MODEL(wpa->store);
	GtkWidget * menuitem;
	GtkWidget * image;
	GtkTreeIter iter;
	gboolean valid;
	GdkPixbuf * pixbuf;
	guint frequency;
	guint level;
	guint flags;
	gchar * dssid;
#if GTK_CHECK_VERSION(2, 12, 0)
	gchar * tooltip;
#endif
	GtkTreePath * path;
	GtkTreeRowReference * row;

	if((valid = gtk_tree_model_get_iter_first(model, &iter)) == FALSE)
		return;
	menuitem = gtk_separator_menu_item_new();
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
	for(; valid == TRUE; valid = gtk_tree_model_iter_next(model, &iter))
	{
		gtk_tree_model_get(model, &iter, WSR_ICON, &pixbuf,
				WSR_FREQUENCY, &frequency, WSR_LEVEL, &level,
				WSR_FLAGS, &flags, WSR_SSID_DISPLAY, &dssid,
#if GTK_CHECK_VERSION(2, 12, 0)
				WSR_TOOLTIP, &tooltip,
#endif
				-1);
		menuitem = gtk_image_menu_item_new_with_label(dssid);
#if GTK_CHECK_VERSION(2, 12, 0)
		gtk_widget_set_tooltip_text(menuitem, tooltip);
		g_free(tooltip);
#endif
		path = gtk_tree_model_get_path(model, &iter);
		row = gtk_tree_row_reference_new(model, path);
		gtk_tree_path_free(path);
		g_object_set_data(G_OBJECT(menuitem), "row", row);
		image = gtk_image_new_from_pixbuf(pixbuf);
		gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
				image);
		g_signal_connect(menuitem, "activate", G_CALLBACK(
					_clicked_on_network_activated), wpa);
		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
		g_free(dssid);
#if 0 /* XXX memory leak (for g_object_set_data() above) */
		gtk_tree_row_reference_free(treeref);
#endif
	}
}

static void _clicked_preferences(WPA * wpa, GtkWidget * menu)
{
	GtkWidget * menuitem;

#if GTK_CHECK_VERSION(3, 10, 0)
	menuitem = gtk_image_menu_item_new_with_label(_("Preferences"));
	gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menuitem),
			gtk_image_new_from_icon_name(GTK_STOCK_PREFERENCES,
				GTK_ICON_SIZE_MENU));
#else
	menuitem = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES,
			NULL);
#endif
	g_signal_connect_swapped(menuitem, "activate", G_CALLBACK(
				_clicked_on_preferences), wpa);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
}

/* callbacks */
static void _clicked_on_disconnect(gpointer data)
{
	WPA * wpa = data;

	_wpa_disconnect(wpa);
}

static void _clicked_on_network_activated(GtkWidget * widget, gpointer data)
{
	WPA * wpa = data;
	GtkTreeRowReference * row;
	GtkTreeModel * model = GTK_TREE_MODEL(wpa->store);
	GtkTreePath * path;
	GtkTreeIter iter;
	gchar * ssid;
	guint f;

	if((row = g_object_get_data(G_OBJECT(widget), "row")) == NULL)
		/* FIXME implement */
		return;
	if((path = gtk_tree_row_reference_get_path(row)) == NULL
			|| gtk_tree_model_get_iter(model, &iter, path) != TRUE)
	{
		gtk_tree_row_reference_free(row);
		return;
	}
	gtk_tree_model_get(model, &iter, WSR_SSID, &ssid, WSR_FLAGS, &f, -1);
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, ssid);
#endif
	_wpa_connect(wpa, ssid, f);
	g_free(ssid);
#if 1 /* XXX partly remediate memory leak (see above) */
	gtk_tree_row_reference_free(row);
#endif
}

static void _clicked_on_preferences(gpointer data)
{
	WPA * wpa = data;
	char * argv[] = { BINDIR "/wifibrowser", NULL };
	const unsigned int flags = 0;
	GError * error = NULL;

	if(g_spawn_async(NULL, argv, NULL, flags, NULL, NULL, NULL, &error)
			== FALSE)
	{
		wpa->helper->error(NULL, error->message, 1);
		g_error_free(error);
	}
}

static void _clicked_on_reassociate(gpointer data)
{
	WPA * wpa = data;
	WPAChannel * channel = &wpa->channel[0];

	_wpa_queue(wpa, channel, WC_REASSOCIATE);
}

static void _clicked_on_rescan(gpointer data)
{
	WPA * wpa = data;

	_wpa_rescan(wpa);
}

static void _clicked_position_menu(GtkMenu * menu, gint * x, gint * y,
		gboolean * push_in, gpointer data)
{
	WPA * wpa = data;
	GtkAllocation a;

#if GTK_CHECK_VERSION(2, 18, 0)
	gtk_widget_get_allocation(wpa->widget, &a);
#else
	a = wpa->widget->allocation;
#endif
	*x = a.x;
	*y = a.y;
	wpa->helper->position_menu(wpa->helper->panel, menu, x, y, push_in);
}


/* on_timeout */
static gboolean _on_timeout(gpointer data)
{
	WPA * wpa = data;
	WPAChannel * channel = &wpa->channel[0];

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(wpa->networks == NULL)
	{
		_wpa_queue(wpa, channel, WC_LIST_NETWORKS);
		_wpa_queue(wpa, channel, WC_SCAN_RESULTS);
	}
	_wpa_queue(wpa, channel, WC_STATUS);
	return TRUE;
}


/* on_watch_can_read */
static void _read_add_network(WPA * wpa, WPAChannel * channel, char const * buf,
		size_t cnt, char const * ssid, uint32_t flags,
		gboolean connect);
static WPAChannel * _read_channel(WPA * wpa, GIOChannel * source);
static void _read_event_ctrl(WPA * wpa, char const * event);
static void _read_event_wpa(WPA * wpa, char const * event);
static void _read_list_networks(WPA * wpa, char const * buf, size_t cnt);
static void _read_scan_results(WPA * wpa, char const * buf, size_t cnt);
static void _read_scan_results_cleanup(WPA * wpa, GtkTreeModel * model);
static char const * _read_scan_results_flag(WPA * wpa, char const * p,
		uint32_t * ret);
static uint32_t _read_scan_results_flags(WPA * wpa, char const * flags);
static void _read_scan_results_iter(WPA * wpa, GtkTreeIter * iter,
		char const * bssid);
static void _read_scan_results_iter_ssid(WPA * wpa, GtkTreeIter * iter,
		char const * bssid, char const * ssid, unsigned int level,
		unsigned int frequency, unsigned int flags, GdkPixbuf * pixbuf,
		char const * tooltip);
static void _read_scan_results_reset(WPA * wpa, GtkTreeModel * model);
static void _read_scan_results_tooltip(char * buf, size_t buf_cnt,
		unsigned int frequency, unsigned int level, uint32_t flags);
static void _read_status(WPA * wpa, char const * buf, size_t cnt);
static void _read_unsolicited(WPA * wpa, char const * buf, size_t cnt);

static gboolean _on_watch_can_read(GIOChannel * source, GIOCondition condition,
		gpointer data)
{
	WPA * wpa = data;
	WPAChannel * channel;
	WPAEntry * entry;
	char buf[4096]; /* XXX in wpa */
	gsize cnt;
	GError * error = NULL;
	GIOStatus status;
	char const * p;

	if(condition != G_IO_IN)
		return FALSE; /* should not happen */
	if((channel = _read_channel(wpa, source)) == NULL)
		return FALSE; /* should not happen */
	entry = (channel->queue_cnt > 0) ? &channel->queue[0] : NULL;
	status = g_io_channel_read_chars(source, buf, sizeof(buf), &cnt,
			&error);
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() \"", __func__);
	fwrite(buf, sizeof(*buf), cnt, stderr);
	fprintf(stderr, "\"\n");
#endif
	switch(status)
	{
		case G_IO_STATUS_NORMAL:
			if(entry == NULL)
			{
				_read_unsolicited(wpa, buf, cnt);
				break;
			}
			if(cnt == 3 && strncmp(buf, "OK\n", cnt) == 0)
				break;
			if(cnt == 5 && strncmp(buf, "FAIL\n", cnt) == 0)
			{
				/* FIXME improve the error message */
				p = _("Unknown error");
				if(entry->command == WC_SAVE_CONFIGURATION)
					p = _("Could not save the"
							" configuration");
				wpa->helper->error(NULL, p, 0);
				break;
			}
			if(entry->command == WC_ADD_NETWORK)
				_read_add_network(wpa, channel, buf, cnt,
						entry->ssid, entry->flags,
						entry->connect);
			else if(entry->command == WC_LIST_NETWORKS)
				_read_list_networks(wpa, buf, cnt);
			else if(entry->command == WC_SCAN_RESULTS)
				_read_scan_results(wpa, buf, cnt);
			else if(entry->command == WC_STATUS)
				_read_status(wpa, buf, cnt);
			break;
		case G_IO_STATUS_ERROR:
			_wpa_error(wpa, error->message, 1);
			g_error_free(error);
			/* fallthrough */
		case G_IO_STATUS_EOF:
		default: /* should not happen */
			_wpa_reset(wpa);
			return FALSE;
	}
	if(entry != NULL)
	{
		free(channel->queue[0].ssid);
		memmove(&channel->queue[0], &channel->queue[1],
				sizeof(channel->queue[0])
				* (--channel->queue_cnt));
	}
	/* schedule commands again */
	if(channel->queue_cnt > 0 && channel->wr_source == 0)
		channel->wr_source = g_io_add_watch(channel->channel, G_IO_OUT,
				_on_watch_can_write, wpa);
	return TRUE;
}

static void _read_add_network(WPA * wpa, WPAChannel * channel, char const * buf,
		size_t cnt, char const * ssid, uint32_t flags, gboolean connect)
{
	unsigned int id;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\", 0x%08x)\n", __func__, ssid, flags);
#endif
	if(cnt < 2 || buf[cnt - 1] != '\n')
		return;
	errno = 0;
	id = strtoul(buf, NULL, 10);
	if(buf[0] == '\0' || errno != 0)
		return;
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() %u \"%s\"\n", __func__, id, ssid);
#endif
	_wpa_queue(wpa, channel, WC_SET_NETWORK, id, TRUE, "ssid", ssid);
	if((flags & (WSRF_WPA | WSRF_WPA2)) != 0)
		_wpa_queue(wpa, channel, WC_SET_NETWORK, id, FALSE, "key_mgmt",
				"WPA-PSK");
	else
		/* required to be able to connect to open or WEP networks */
		_wpa_queue(wpa, channel, WC_SET_NETWORK, id, FALSE, "key_mgmt",
				"NONE");
	if(wpa->autosave)
		_wpa_queue(wpa, channel, WC_SAVE_CONFIGURATION);
	if(connect)
	{
		_wpa_queue(wpa, channel, WC_SELECT_NETWORK, id);
		_wpa_queue(wpa, channel, WC_LIST_NETWORKS);
	}
}

static WPAChannel * _read_channel(WPA * wpa, GIOChannel * source)
{
	if(source == wpa->channel[0].channel
			&& wpa->channel[0].queue_cnt > 0
			&& wpa->channel[0].queue[0].buf_cnt == 0)
		return &wpa->channel[0];
	else if(source == wpa->channel[1].channel)
		return &wpa->channel[1];
	return NULL;
}

static void _read_event_ctrl(WPA * wpa, char const * event)
{
	char const password_changed[] = "PASSWORD-CHANGED ";
	char const scan_results[] = "SCAN-RESULTS";

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, event);
#endif
	if(strncmp(event, password_changed, sizeof(password_changed) - 1) == 0)
		_wpa_notify(wpa, &event[sizeof(password_changed)]);
	else if(strncmp(event, password_changed, sizeof(password_changed) - 2)
			== 0)
		_wpa_notify(wpa, _("Password changed"));
	else if(strncmp(event, scan_results, sizeof(scan_results) - 1) == 0)
		_wpa_queue(wpa, &wpa->channel[0], WC_SCAN_RESULTS);
#ifdef DEBUG
	else
		fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, event);
#endif
}

static void _read_event_wpa(WPA * wpa, char const * event)
{
	char const handshake[] = "4-Way Handshake failed"
		" - pre-shared key may be incorrect";

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	/* XXX hackish, blame wpa_supplicant(8) */
	if(strcmp(event, handshake) == 0 && wpa->networks_cur >= 0)
		/* FIXME does not work if networks_cur is not set */
		_wpa_ask_password(wpa, &wpa->networks[wpa->networks_cur]);
}

static void _read_list_networks(WPA * wpa, char const * buf, size_t cnt)
{
	WPANetwork * n;
	size_t i;
	size_t j;
	char * p = NULL;
	char * q;
	unsigned int u;
	char ssid[80];
	char bssid[80];
	char flags[80];
	int res;

	for(i = 0; i < wpa->networks_cnt; i++)
		free(wpa->networks[i].name);
	free(wpa->networks);
	wpa->networks = NULL;
	wpa->networks_cnt = 0;
	wpa->networks_cur = -1;
	for(i = 0; i < cnt; i = j)
	{
		for(j = i; j < cnt; j++)
			if(buf[j] == '\n')
				break;
		if((q = realloc(p, ++j - i)) == NULL)
			continue;
		p = q;
		snprintf(p, j - i, "%s", &buf[i]);
		p[j - i - 1] = '\0';
#ifdef DEBUG
		fprintf(stderr, "DEBUG: line \"%s\"\n", p);
#endif
		if((res = sscanf(p, "%u %79[^\t] %79[^\t] [%79[^]]]", &u, ssid,
						bssid, flags)) >= 3)
		{
			ssid[sizeof(ssid) - 1] = '\0';
#ifdef DEBUG
			fprintf(stderr, "DEBUG: %s() \"%s\"\n", __func__, ssid);
#endif
			/* FIXME store the scan results instead */
			if((n = _wpa_add_network(wpa, u, ssid, 1)) == NULL)
				continue;
			if(res >= 4 && strcmp(flags, "DISABLED") == 0)
				n->enabled = 0;
			else if(res >= 4 && strcmp(flags, "CURRENT") == 0)
			{
				_wpa_set_current_network(wpa, n);
				_wpa_queue(wpa, &wpa->channel[0], WC_STATUS);
			}
		}
	}
	free(p);
	if(wpa->networks_cur < 0)
	{
		/* determine if only one network is enabled */
		for(i = 0, j = 0; i < wpa->networks_cnt; i++)
			if(wpa->networks[i].enabled)
				j++;
		if(wpa->networks_cnt > 1 && j == 1)
			for(i = 0, j = 0; i < wpa->networks_cnt; i++)
				if(wpa->networks[i].enabled)
				{
					/* set as the current network */
					wpa->networks_cur = i;
					break;
				}
	}
}

static void _read_scan_results(WPA * wpa, char const * buf, size_t cnt)
{
	GtkTreeModel * model = GTK_TREE_MODEL(wpa->store);
	gint size = 16;
	size_t i;
	size_t j;
	char * p = NULL;
	char * q;
	int res;
	GdkPixbuf * pixbuf;
	char bssid[18];
	unsigned int frequency;
	unsigned int level;
	char flags[80];
	uint32_t f;
	char ssid[80];
	char tooltip[80];
	GtkTreeIter iter;

	gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &size, &size);
	_read_scan_results_reset(wpa, model);
	for(i = 0; i < cnt; i = j)
	{
		for(j = i; j < cnt; j++)
			if(buf[j] == '\n')
				break;
		if((q = realloc(p, ++j - i)) == NULL)
			continue;
		p = q;
		snprintf(p, j - i, "%s", &buf[i]);
		p[j - i - 1] = '\0';
#ifdef DEBUG
		fprintf(stderr, "DEBUG: line \"%s\"\n", p);
#endif
		if((res = sscanf(p, "%17s %u %u %79s %79[^\n]", bssid,
						&frequency, &level, flags,
						ssid)) >= 3)
		{
			bssid[sizeof(bssid) - 1] = '\0';
			flags[sizeof(flags) - 1] = '\0';
			ssid[sizeof(ssid) - 1] = '\0';
#ifdef DEBUG
			fprintf(stderr, "DEBUG: %s() \"%s\" %u %u %s %s\n",
					__func__, bssid, frequency, level,
					flags, ssid);
#endif
			f = _read_scan_results_flags(wpa, flags);
			pixbuf = _wpa_get_icon(wpa, size, level, f);
			_read_scan_results_tooltip(tooltip, sizeof(tooltip),
					frequency, level, f);
			if(res == 5)
			{
				_read_scan_results_iter_ssid(wpa, &iter, bssid,
						ssid, level, frequency, f,
						pixbuf, tooltip);
				q = ssid;
			}
			else
			{
				_read_scan_results_iter(wpa, &iter, bssid);
				snprintf(ssid, sizeof(ssid), _("Hidden (%s)"),
						bssid);
				q = "";;
			}
			gtk_tree_store_set(wpa->store, &iter, WSR_UPDATED, TRUE,
					WSR_ICON, pixbuf, WSR_BSSID, bssid,
					WSR_FREQUENCY, frequency,
					WSR_LEVEL, level, WSR_FLAGS, f,
					WSR_TOOLTIP, tooltip, WSR_SSID, q,
					WSR_SSID_DISPLAY, ssid, -1);
		}
	}
	free(p);
	_read_scan_results_cleanup(wpa, model);
}

static void _read_scan_results_cleanup(WPA * wpa, GtkTreeModel * model)
{
	GtkTreeIter iter;
	GtkTreeIter child;
	gboolean valid;

	/* remove the outdated entries */
	for(valid = gtk_tree_model_get_iter_first(model, &iter); valid == TRUE;)
	{
		gtk_tree_model_get(model, &iter, WSR_UPDATED, &valid, -1);
		if(valid == FALSE)
		{
			valid = gtk_tree_store_remove(wpa->store, &iter);
			continue;
		}
		for(valid = gtk_tree_model_iter_children(model, &child, &iter);
				valid == TRUE;)
		{
			gtk_tree_model_get(model, &child, WSR_UPDATED, &valid,
					-1);
			if(valid == FALSE)
				valid = gtk_tree_store_remove(wpa->store,
						&child);
			else
				valid = gtk_tree_model_iter_next(model, &child);
		}
		valid = gtk_tree_model_iter_next(model, &iter);
	}
}

static uint32_t _read_scan_results_flags(WPA * wpa, char const * flags)
{
	uint32_t ret = 0;
	char const * p;

	for(p = flags; *p != '\0';)
		if(*(p++) == '[')
		{
			p = _read_scan_results_flag(wpa, p, &ret);
			for(p++; *p != '\0' && *p != ']'; p++);
		}
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() => 0x%x\n", __func__, ret);
#endif
	return ret;
}

static char const * _read_scan_results_flag(WPA * wpa, char const * p,
		uint32_t * ret)
{
	char const wep[] = "WEP";
	char const wep104[] = "WEP104";
	char const wpa1[] = "WPA-";
	char const wpa2[] = "WPA2-";
	char const psk[] = "PSK";
	char const eap[] = "EAP";
	char const ccmp[] = "CCMP";
	char const tkipccmp[] = "TKIP+CCMP";
	char const tkip[] = "TKIP";
	char const preauth[] = "preauth";
	char const ess[] = "ESS";
	char const ibss[] = "IBSS";
	(void) wpa;

	/* FIXME parse more consistently */
	if(strncmp(wep, p, sizeof(wep) - 1) == 0)
	{
		*ret |= WSRF_WEP;
		p += sizeof(wep) - 1;
	}
	else if(strncmp(wpa1, p, sizeof(wpa1) - 1) == 0)
	{
		*ret |= WSRF_WPA;
		p += sizeof(wpa1) - 1;
	}
	else if(strncmp(wpa2, p, sizeof(wpa2) - 1) == 0)
	{
		*ret |= WSRF_WPA2;
		p += sizeof(wpa2) - 1;
	}
	else if(strncmp(ess, p, sizeof(ess) - 1) == 0)
	{
		*ret |= WSRF_ESS;
		p += sizeof(ess) - 1;
	}
	else if(strncmp(ibss, p, sizeof(ibss) - 1) == 0)
	{
		*ret |= WSRF_IBSS;
		p += sizeof(ibss) - 1;
	}
	else
		return p;
	if(*p == '-')
		p++;
	if(strncmp(psk, p, sizeof(psk) - 1) == 0)
	{
		*ret |= WSRF_PSK;
		p += sizeof(psk) - 1;
	}
	else if(strncmp(eap, p, sizeof(eap) - 1) == 0)
	{
		*ret |= WSRF_EAP;
		p += sizeof(eap) - 1;
	}
	if(*p == '-')
		p++;
	if(strncmp(ccmp, p, sizeof(ccmp) - 1) == 0)
	{
		*ret |= WSRF_CCMP;
		p += sizeof(ccmp) - 1;
	}
	else if(strncmp(tkipccmp, p, sizeof(tkipccmp) - 1) == 0)
	{
		*ret |= WSRF_TKIP | WSRF_CCMP;
		p += sizeof(tkipccmp) - 1;
	}
	else if(strncmp(tkip, p, sizeof(tkip) - 1) == 0)
	{
		*ret |= WSRF_TKIP;
		p += sizeof(tkip) - 1;
	}
	else if(strncmp(wep104, p, sizeof(wep104) - 1) == 0)
	{
		*ret &= ~(WSRF_IBSS | WSRF_WPA | WSRF_WPA2);
		*ret |= WSRF_WEP;
		p += sizeof(wep104) - 1;
	}
	else
		return p;
	if(*p == '-')
		p++;
	if(strncmp(preauth, p, sizeof(preauth) - 1) == 0)
	{
		*ret |= WSRF_PREAUTH;
		p += sizeof(preauth) - 1;
	}
	else
		return p;
	/* FIXME implement more */
	return p;
}

static void _read_scan_results_iter(WPA * wpa, GtkTreeIter * iter,
		char const * bssid)
{
	GtkTreeModel * model = GTK_TREE_MODEL(wpa->store);
	gboolean valid;
	gchar * b;
	int res;

	for(valid = gtk_tree_model_get_iter_first(model, iter); valid == TRUE;
			valid = gtk_tree_model_iter_next(model, iter))
	{
		gtk_tree_model_get(model, iter, WSR_BSSID, &b, -1);
		res = (b != NULL && strcmp(b, bssid) == 0) ? 0 : -1;
		g_free(b);
		if(res == 0)
			return;
	}
	gtk_tree_store_append(wpa->store, iter, NULL);
}

static void _read_scan_results_iter_ssid(WPA * wpa, GtkTreeIter * iter,
		char const * bssid, char const * ssid, unsigned int level,
		unsigned int frequency, unsigned int flags, GdkPixbuf * pixbuf,
		char const * tooltip)
{
	GtkTreeModel * model = GTK_TREE_MODEL(wpa->store);
	gboolean valid;
	gchar * s;
	unsigned int f;
	unsigned int l = 0;
	int res;
	GtkTreeIter parent;

	/* look for the network in the list */
	for(valid = gtk_tree_model_get_iter_first(model, iter); valid == TRUE;
			valid = gtk_tree_model_iter_next(model, iter))
	{
		gtk_tree_model_get(model, iter, WSR_SSID, &s, WSR_LEVEL, &l,
				WSR_FLAGS, &f, -1);
		res = (s != NULL && strcmp(s, ssid) == 0 && f == flags)
			? 0 : -1;
		g_free(s);
		if(res == 0)
			break;
	}
	if(valid != TRUE)
	{
		gtk_tree_store_append(wpa->store, iter, NULL);
		/* FIXME determine the true value for WSR_ENABLED */
		gtk_tree_store_set(wpa->store, iter, WSR_UPDATED, TRUE,
				WSR_LEVEL, level, WSR_FREQUENCY, frequency,
				WSR_FLAGS, flags, WSR_SSID, ssid,
				WSR_SSID_DISPLAY, ssid, WSR_ICON, pixbuf,
				WSR_TOOLTIP, tooltip, WSR_ENABLED, FALSE,
				WSR_CAN_ENABLE, TRUE, -1);
	}
	else
		gtk_tree_store_set(wpa->store, iter, WSR_UPDATED, TRUE,
				/* refresh the level and icon if relevant */
				(level > l) ? WSR_LEVEL : -1, level,
				WSR_FREQUENCY, frequency, WSR_ICON, pixbuf,
				WSR_TOOLTIP, tooltip, WSR_ENABLED, FALSE,
				WSR_CAN_ENABLE, FALSE, -1);
	/* look for the BSSID in the network */
	parent = *iter;
	for(valid = gtk_tree_model_iter_children(model, iter, &parent);
			valid == TRUE;
			valid = gtk_tree_model_iter_next(model, iter))
	{
		gtk_tree_model_get(model, iter, WSR_BSSID, &s, -1);
		res = (s != NULL && strcmp(s, bssid) == 0) ? 0 : -1;
		g_free(s);
		if(res == 0)
			break;
	}
	if(valid != TRUE)
		gtk_tree_store_append(wpa->store, iter, &parent);
}

static void _read_scan_results_reset(WPA * wpa, GtkTreeModel * model)
{
	GtkTreeIter iter;
	GtkTreeIter child;
	gboolean valid;

	/* mark every entry as obsolete */
	for(valid = gtk_tree_model_get_iter_first(model, &iter); valid == TRUE;
			valid = gtk_tree_model_iter_next(model, &iter))
	{
		gtk_tree_store_set(wpa->store, &iter, WSR_UPDATED, FALSE, -1);
		for(valid = gtk_tree_model_iter_children(model, &child, &iter);
				valid == TRUE;
				valid = gtk_tree_model_iter_next(model, &child))
			gtk_tree_store_set(wpa->store, &child,
					WSR_UPDATED, FALSE, -1);
	}
}

static void _read_scan_results_tooltip(char * buf, size_t buf_cnt,
		unsigned int frequency, unsigned int level, uint32_t flags)
{
	char const * security = (flags & WSRF_WPA2) ? "WPA2"
		: ((flags & WSRF_WPA) ? "WPA"
				: ((flags & WSRF_WEP) ? "WEP" : NULL));

	/* FIXME mention the channel instead of the frequency */
	snprintf(buf, buf_cnt, _("Frequency: %u\nLevel: %u%s%s"), frequency,
			level, (security != NULL) ? _("\nSecurity: ") : "",
			(security != NULL) ? security : "");
}

static void _read_status(WPA * wpa, char const * buf, size_t cnt)
{
	gboolean associated = FALSE;
	int id = -1;
	char * network = NULL;
	size_t i;
	size_t j;
	char * p = NULL;
	char * q;
	char variable[80];
	char value[80];
	WPANetwork * n;

	wpa->flags = 0;
	for(i = 0; i < cnt; i = j)
	{
		for(j = i; j < cnt; j++)
			if(buf[j] == '\n')
				break;
		if((q = realloc(p, ++j - i)) == NULL)
			continue;
		p = q;
		snprintf(p, j - i, "%s", &buf[i]);
		p[j - i - 1] = '\0';
#ifdef DEBUG
		fprintf(stderr, "DEBUG: line \"%s\"\n", p);
#endif
		if(sscanf(p, "%79[^=]=%79[^\n]", variable, value) != 2)
			continue;
		variable[sizeof(variable) - 1] = '\0';
		value[sizeof(value) - 1] = '\0';
		if(strcmp(variable, "id") == 0)
		{
			errno = 0;
			id = strtol(value, NULL, 10);
			if(errno != 0 || value[0] == '\0' || id < 0)
				id = -1;
		}
		else if(strcmp(variable, "key_mgmt") == 0)
			/* XXX */
			_read_scan_results_flag(wpa, value, &wpa->flags);
		else if(strcmp(variable, "wpa_state") == 0)
		{
			if(strcmp(value, "COMPLETED") == 0)
				associated = TRUE;
			else if(strcmp(value, "4WAY_HANDSHAKE") == 0)
				associated = FALSE;
			else if(strcmp(value, "SCANNING") == 0)
				associated = FALSE;
			else if(strcmp(value, "INACTIVE") == 0)
				associated = FALSE;
		}
#ifndef EMBEDDED
		else if(strcmp(variable, "ssid") == 0)
		{
			free(network);
			/* XXX may fail */
			network = strdup(value);
		}
#endif
	}
	free(p);
	/* reflect the status */
	if(associated == TRUE)
	{
		/* no longer ask for any password */
		if(wpa->pw_window != NULL)
			gtk_widget_hide(wpa->pw_window);
	}
	_wpa_set_status(wpa, TRUE, associated, network);
	free(network);
	if(id >= 0 && (n = _wpa_get_network(wpa, id)) != NULL)
		_wpa_set_current_network(wpa, n);
}

static void _read_unsolicited(WPA * wpa, char const * buf, size_t cnt)
{
	char const ctrl_event[] = "CTRL-EVENT-";
	char const wpa_event[] = "WPA: ";
	size_t i;
	size_t j;
	char * p = NULL;
	char * q;
	unsigned int u;
	char event[128];

	for(i = 0; i < cnt; i = j)
	{
		for(j = i; j < cnt; j++)
			if(buf[j] == '\n')
				break;
		if((q = realloc(p, ++j - i)) == NULL)
			continue;
		p = q;
		snprintf(p, j - i, "%s", &buf[i]);
		p[j - i - 1] = '\0';
#ifdef DEBUG
		fprintf(stderr, "DEBUG: line \"%s\"\n", p);
#endif
		if(sscanf(p, "<%u>%127[^\n]\n", &u, event) != 2)
			continue;
		event[sizeof(event) - 1] = '\0';
		if(strncmp(event, ctrl_event, sizeof(ctrl_event) - 1) == 0)
			_read_event_ctrl(wpa, event + sizeof(ctrl_event) - 1);
		else if(strncmp(event, wpa_event, sizeof(wpa_event) - 1) == 0)
			_read_event_wpa(wpa, event + sizeof(wpa_event) - 1);
	}
	free(p);
}


/* on_watch_can_write */
static gboolean _on_watch_can_write(GIOChannel * source, GIOCondition condition,
		gpointer data)
{
	WPA * wpa = data;
	WPAChannel * channel;
	WPAEntry * entry;
	gsize cnt = 0;
	GError * error = NULL;
	GIOStatus status;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if(condition != G_IO_OUT)
		return FALSE; /* should not happen */
	if(source == wpa->channel[0].channel
			&& wpa->channel[0].queue_cnt > 0
			&& wpa->channel[0].queue[0].buf_cnt != 0)
		channel = &wpa->channel[0];
	else if(source == wpa->channel[1].channel
			&& wpa->channel[1].queue_cnt > 0
			&& wpa->channel[1].queue[0].buf_cnt != 0)
		channel = &wpa->channel[1];
	else
		return FALSE; /* should not happen */
	entry = &channel->queue[0];
#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s() \"", __func__);
	fwrite(entry->buf, sizeof(*entry->buf), entry->buf_cnt, stderr);
	fprintf(stderr, "\"\n");
#endif
	status = g_io_channel_write_chars(source, entry->buf, entry->buf_cnt,
			&cnt, &error);
	if(cnt != 0)
	{
		memmove(entry->buf, &entry->buf[cnt], entry->buf_cnt - cnt);
		entry->buf_cnt -= cnt;
	}
	switch(status)
	{
		case G_IO_STATUS_NORMAL:
			break;
		case G_IO_STATUS_ERROR:
			_wpa_error(wpa, error->message, 1);
			/* fallthrough */
		case G_IO_STATUS_EOF:
		default: /* should not happen */
			_wpa_reset(wpa);
			return FALSE;
	}
	if(entry->buf_cnt != 0)
		/* partial packet */
		_wpa_reset(wpa);
	else
		channel->wr_source = 0;
	return FALSE;
}
