* See README and COPYING for more details.
*/
+#include <cstdio>
#include <QImageReader>
+#include <QMessageBox>
+#include "wpa_ctrl.h"
#include "wpagui.h"
+#include "stringquery.h"
#include "peers.h"
+
+static const int peer_role_address = Qt::UserRole + 1;
+static const int peer_role_type = Qt::UserRole + 2;
+static const int peer_role_uuid = Qt::UserRole + 3;
+
/*
* TODO:
- * - add pending WPS queries (from M1/PIN, PBC?)
* - add current AP info (e.g., from WPS) in station mode
* - different icons to indicate peer type
*/
+enum peer_type {
+ PEER_TYPE_ASSOCIATED_STATION,
+ PEER_TYPE_AP,
+ PEER_TYPE_AP_WPS,
+ PEER_TYPE_WPS_PIN_NEEDED,
+ PEER_TYPE_WPS_ER_AP,
+ PEER_TYPE_WPS_ER_AP_UNCONFIGURED,
+ PEER_TYPE_WPS_ER_ENROLLEE
+};
+
+
Peers::Peers(QWidget *parent, const char *, bool, Qt::WFlags)
: QDialog(parent)
{
setupUi(this);
- connect(peers, SIGNAL(clicked(QModelIndex)), this,
- SLOT(clicked(QModelIndex)));
-
if (QImageReader::supportedImageFormats().contains(QByteArray("svg")))
+ {
default_icon = new QIcon(":/icons/wpa_gui.svg");
- else
+ ap_icon = new QIcon(":/icons/ap.svg");
+ laptop_icon = new QIcon(":/icons/laptop.svg");
+ } else {
default_icon = new QIcon(":/icons/wpa_gui.png");
+ ap_icon = new QIcon(":/icons/ap.png");
+ laptop_icon = new QIcon(":/icons/laptop.png");
+ }
peers->setModel(&model);
peers->setResizeMode(QListView::Adjust);
+ peers->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(peers, SIGNAL(customContextMenuRequested(const QPoint &)),
+ this, SLOT(context_menu(const QPoint &)));
+
wpagui = NULL;
}
Peers::~Peers()
{
delete default_icon;
+ delete ap_icon;
+ delete laptop_icon;
}
}
-void Peers::clicked(const QModelIndex & /*index*/)
+void Peers::context_menu(const QPoint &pos)
{
- /* QStandardItem *item = model.itemFromIndex(index); */
- /* TODO: give an option to provide PIN for WPS, etc. */
- /* printf("Clicked: %s\n", item->text().toAscii().constData()); */
+ QMenu *menu = new QMenu;
+ if (menu == NULL)
+ return;
+
+ QModelIndex idx = peers->indexAt(pos);
+ if (idx.isValid()) {
+ ctx_item = model.itemFromIndex(idx);
+ int type = ctx_item->data(peer_role_type).toInt();
+ QString title;
+ switch (type) {
+ case PEER_TYPE_ASSOCIATED_STATION:
+ title = tr("Associated station");
+ break;
+ case PEER_TYPE_AP:
+ title = tr("AP");
+ break;
+ case PEER_TYPE_AP_WPS:
+ title = tr("WPS AP");
+ break;
+ case PEER_TYPE_WPS_PIN_NEEDED:
+ title = tr("WPS PIN needed");
+ break;
+ case PEER_TYPE_WPS_ER_AP:
+ title = tr("ER: WPS AP");
+ break;
+ case PEER_TYPE_WPS_ER_AP_UNCONFIGURED:
+ title = tr("ER: WPS AP (Unconfigured)");
+ break;
+ case PEER_TYPE_WPS_ER_ENROLLEE:
+ title = tr("ER: WPS Enrollee");
+ break;
+ }
+ menu->addAction(title)->setEnabled(false);
+ menu->addSeparator();
+
+ if (type == PEER_TYPE_ASSOCIATED_STATION ||
+ type == PEER_TYPE_AP_WPS ||
+ type == PEER_TYPE_WPS_PIN_NEEDED ||
+ type == PEER_TYPE_WPS_ER_ENROLLEE) {
+ /* TODO: only for peers that are requesting WPS PIN
+ * method */
+ menu->addAction(QString("Enter WPS PIN"), this,
+ SLOT(enter_pin()));
+ }
+ } else {
+ ctx_item = NULL;
+ menu->addAction(QString("Refresh"), this, SLOT(ctx_refresh()));
+ }
+
+ menu->exec(peers->mapToGlobal(pos));
}
-void Peers::update_peers()
+void Peers::enter_pin()
+{
+ if (ctx_item == NULL)
+ return;
+
+ int peer_type = ctx_item->data(peer_role_type).toInt();
+ QString uuid;
+ QString addr;
+ if (peer_type == PEER_TYPE_WPS_ER_ENROLLEE)
+ uuid = ctx_item->data(peer_role_uuid).toString();
+ else
+ addr = ctx_item->data(peer_role_address).toString();
+
+ StringQuery input(tr("PIN:"));
+ input.setWindowTitle(tr("PIN for ") + ctx_item->text());
+ if (input.exec() != QDialog::Accepted)
+ return;
+
+ char cmd[100];
+ char reply[100];
+ size_t reply_len;
+
+ if (peer_type == PEER_TYPE_WPS_ER_ENROLLEE) {
+ snprintf(cmd, sizeof(cmd), "WPS_ER_PIN %s %s",
+ uuid.toAscii().constData(),
+ input.get_string().toAscii().constData());
+ } else {
+ snprintf(cmd, sizeof(cmd), "WPS_PIN %s %s",
+ addr.toAscii().constData(),
+ input.get_string().toAscii().constData());
+ }
+ reply_len = sizeof(reply) - 1;
+ if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0) {
+ QMessageBox msg;
+ msg.setIcon(QMessageBox::Warning);
+ msg.setText("Failed to set the WPS PIN.");
+ msg.exec();
+ }
+}
+
+
+void Peers::ctx_refresh()
+{
+ update_peers();
+}
+
+
+void Peers::add_station(QString info)
+{
+ QStringList lines = info.split(QRegExp("\\n"));
+ QString name;
+
+ for (QStringList::Iterator it = lines.begin();
+ it != lines.end(); it++) {
+ int pos = (*it).indexOf('=') + 1;
+ if (pos < 1)
+ continue;
+
+ if ((*it).startsWith("wpsDeviceName="))
+ name = (*it).mid(pos);
+ }
+
+ if (name.isEmpty())
+ name = lines[0];
+
+ QStandardItem *item = new QStandardItem(*laptop_icon, name);
+ if (item) {
+ item->setData(lines[0], peer_role_address);
+ item->setData(PEER_TYPE_ASSOCIATED_STATION,
+ peer_role_type);
+ item->setToolTip(info);
+ model.appendRow(item);
+ }
+}
+
+
+void Peers::add_stations()
{
char reply[2048];
size_t reply_len;
- char cmd[20];
+ char cmd[30];
int res;
- model.clear();
- if (wpagui == NULL)
- return;
-
reply_len = sizeof(reply) - 1;
if (wpagui->ctrlRequest("STA-FIRST", reply, &reply_len) < 0)
return;
while (*txt != '\0' && *txt != '\n')
txt++;
*txt++ = '\0';
- if (strncmp(reply, "FAIL", 4) == 0)
+ if (strncmp(reply, "FAIL", 4) == 0 ||
+ strncmp(reply, "UNKNOWN", 7) == 0)
+ break;
+
+ add_station(info);
+
+ reply_len = sizeof(reply) - 1;
+ snprintf(cmd, sizeof(cmd), "STA-NEXT %s", reply);
+ res = wpagui->ctrlRequest(cmd, reply, &reply_len);
+ } while (res >= 0);
+}
+
+
+void Peers::add_single_station(const char *addr)
+{
+ char reply[2048];
+ size_t reply_len;
+ char cmd[30];
+
+ reply_len = sizeof(reply) - 1;
+ snprintf(cmd, sizeof(cmd), "STA %s", addr);
+ if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0)
+ return;
+
+ reply[reply_len] = '\0';
+ QString info(reply);
+ char *txt = reply;
+ while (*txt != '\0' && *txt != '\n')
+ txt++;
+ *txt++ = '\0';
+ if (strncmp(reply, "FAIL", 4) == 0 ||
+ strncmp(reply, "UNKNOWN", 7) == 0)
+ return;
+
+ add_station(info);
+}
+
+
+void Peers::add_scan_results()
+{
+ char reply[2048];
+ size_t reply_len;
+ int index;
+ char cmd[20];
+
+ index = 0;
+ while (wpagui) {
+ snprintf(cmd, sizeof(cmd), "BSS %d", index++);
+ if (index > 1000)
break;
- QStringList lines = info.split(QRegExp("\\n"));
- QString name;
+ reply_len = sizeof(reply) - 1;
+ if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0)
+ break;
+ reply[reply_len] = '\0';
+
+ QString bss(reply);
+ if (bss.isEmpty() || bss.startsWith("FAIL"))
+ break;
+ QString ssid, bssid, flags, wps_name;
+
+ QStringList lines = bss.split(QRegExp("\\n"));
for (QStringList::Iterator it = lines.begin();
it != lines.end(); it++) {
int pos = (*it).indexOf('=') + 1;
if (pos < 1)
continue;
- if ((*it).startsWith("wpsDeviceName="))
- name = (*it).mid(pos);
+ if ((*it).startsWith("bssid="))
+ bssid = (*it).mid(pos);
+ else if ((*it).startsWith("flags="))
+ flags = (*it).mid(pos);
+ else if ((*it).startsWith("ssid="))
+ ssid = (*it).mid(pos);
+ else if ((*it).startsWith("wps_device_name="))
+ wps_name = (*it).mid(pos);
}
+ QString name = wps_name;
if (name.isEmpty())
- name = reply;
+ name = ssid + "\n" + bssid;
- QStandardItem *item = new QStandardItem(*default_icon, name);
+ QStandardItem *item = new QStandardItem(*ap_icon, name);
if (item) {
- item->setToolTip(info);
+ item->setData(bssid, peer_role_address);
+ if (flags.contains("[WPS"))
+ item->setData(PEER_TYPE_AP_WPS,
+ peer_role_type);
+ else
+ item->setData(PEER_TYPE_AP, peer_role_type);
+
+ for (int i = 0; i < lines.size(); i++) {
+ if (lines[i].length() > 60) {
+ lines[i].remove(
+ 60, lines[i].length());
+ lines[i] += "..";
+ }
+ }
+ item->setToolTip(lines.join("\n"));
model.appendRow(item);
}
+ }
+}
- reply_len = sizeof(reply) - 1;
- snprintf(cmd, sizeof(cmd), "STA-NEXT %s", reply);
- res = wpagui->ctrlRequest(cmd, reply, &reply_len);
- } while (res >= 0);
+
+void Peers::update_peers()
+{
+ model.clear();
+ if (wpagui == NULL)
+ return;
+
+ char reply[20];
+ size_t replylen = sizeof(reply) - 1;
+ wpagui->ctrlRequest("WPS_ER_START", reply, &replylen);
+
+ add_stations();
+ add_scan_results();
+}
+
+
+QStandardItem * Peers::find_addr(QString addr)
+{
+ if (model.rowCount() == 0)
+ return NULL;
+
+ QModelIndexList lst = model.match(model.index(0, 0), peer_role_address,
+ addr);
+ if (lst.size() == 0)
+ return NULL;
+ return model.itemFromIndex(lst[0]);
+}
+
+
+QStandardItem * Peers::find_uuid(QString uuid)
+{
+ if (model.rowCount() == 0)
+ return NULL;
+
+ QModelIndexList lst = model.match(model.index(0, 0), peer_role_uuid,
+ uuid);
+ if (lst.size() == 0)
+ return NULL;
+ return model.itemFromIndex(lst[0]);
+}
+
+
+void Peers::event_notify(WpaMsg msg)
+{
+ QString text = msg.getMsg();
+
+ if (text.startsWith(WPS_EVENT_PIN_NEEDED)) {
+ /*
+ * WPS-PIN-NEEDED 5a02a5fa-9199-5e7c-bc46-e183d3cb32f7
+ * 02:2a:c4:18:5b:f3
+ * [Wireless Client|Company|cmodel|123|12345|1-0050F204-1]
+ */
+ QStringList items = text.split(' ');
+ QString uuid = items[1];
+ QString addr = items[2];
+ QString name = "";
+
+ QStandardItem *item = find_addr(addr);
+ if (item)
+ return;
+
+ int pos = text.indexOf('[');
+ if (pos >= 0) {
+ int pos2 = text.lastIndexOf(']');
+ if (pos2 >= pos) {
+ items = text.mid(pos + 1, pos2 - pos - 1).
+ split('|');
+ name = items[0];
+ items.append(addr);
+ }
+ }
+
+ item = new QStandardItem(*laptop_icon, name);
+ if (item) {
+ item->setData(addr, peer_role_address);
+ item->setData(PEER_TYPE_WPS_PIN_NEEDED,
+ peer_role_type);
+ item->setToolTip(items.join(QString("\n")));
+ model.appendRow(item);
+ }
+ return;
+ }
+
+ if (text.startsWith(AP_STA_CONNECTED)) {
+ /* AP-STA-CONNECTED 02:2a:c4:18:5b:f3 */
+ QStringList items = text.split(' ');
+ QString addr = items[1];
+ QStandardItem *item = find_addr(addr);
+ if (item == NULL || item->data(peer_role_type).toInt() !=
+ PEER_TYPE_ASSOCIATED_STATION)
+ add_single_station(addr.toAscii().constData());
+ return;
+ }
+
+ if (text.startsWith(AP_STA_DISCONNECTED)) {
+ /* AP-STA-DISCONNECTED 02:2a:c4:18:5b:f3 */
+ QStringList items = text.split(' ');
+ QString addr = items[1];
+
+ if (model.rowCount() == 0)
+ return;
+
+ QModelIndexList lst = model.match(model.index(0, 0),
+ peer_role_address, addr);
+ for (int i = 0; i < lst.size(); i++) {
+ QStandardItem *item = model.itemFromIndex(lst[i]);
+ if (item && item->data(peer_role_type).toInt() ==
+ PEER_TYPE_ASSOCIATED_STATION)
+ model.removeRow(lst[i].row());
+ }
+ return;
+ }
+
+ if (text.startsWith(WPS_EVENT_ER_AP_ADD)) {
+ /*
+ * WPS-ER-AP-ADD 87654321-9abc-def0-1234-56789abc0002
+ * 02:11:22:33:44:55 pri_dev_type=6-0050F204-1 wps_state=1
+ * |Very friendly name|Company|Long description of the model|
+ * WAP|http://w1.fi/|http://w1.fi/hostapd/
+ */
+ QStringList items = text.split(' ');
+ if (items.size() < 5)
+ return;
+ QString uuid = items[1];
+ QString addr = items[2];
+ QString pri_dev_type = items[3];
+ int wps_state = items[4].mid(10).toInt();
+
+ int pos = text.indexOf('|');
+ if (pos < 0)
+ return;
+ items = text.mid(pos + 1).split('|');
+ if (items.size() < 1)
+ return;
+
+ QStandardItem *item = find_uuid(uuid);
+ if (item)
+ return;
+
+ item = new QStandardItem(*ap_icon, items[0]);
+ if (item) {
+ item->setData(uuid, peer_role_uuid);
+ item->setData(addr, peer_role_address);
+ item->setData(wps_state == 2 ? PEER_TYPE_WPS_ER_AP:
+ PEER_TYPE_WPS_ER_AP_UNCONFIGURED,
+ peer_role_type);
+ item->setToolTip(addr + QString("\n") +
+ pri_dev_type + QString("\n") +
+ items.join(QString("\n")));
+ model.appendRow(item);
+ }
+
+ return;
+ }
+
+ if (text.startsWith(WPS_EVENT_ER_AP_REMOVE)) {
+ /* WPS-ER-AP-REMOVE 87654321-9abc-def0-1234-56789abc0002 */
+ QStringList items = text.split(' ');
+ if (items.size() < 2)
+ return;
+ if (model.rowCount() == 0)
+ return;
+
+ QModelIndexList lst = model.match(model.index(0, 0),
+ peer_role_uuid, items[1]);
+ for (int i = 0; i < lst.size(); i++) {
+ QStandardItem *item = model.itemFromIndex(lst[i]);
+ if (item &&
+ (item->data(peer_role_type).toInt() ==
+ PEER_TYPE_WPS_ER_AP ||
+ item->data(peer_role_type).toInt() ==
+ PEER_TYPE_WPS_ER_AP_UNCONFIGURED))
+ model.removeRow(lst[i].row());
+ }
+ return;
+ }
+
+ if (text.startsWith(WPS_EVENT_ER_ENROLLEE_ADD)) {
+ /*
+ * WPS-ER-ENROLLEE-ADD 2b7093f1-d6fb-5108-adbb-bea66bb87333
+ * 02:66:a0:ee:17:27 M1=1 config_methods=0x14d dev_passwd_id=0
+ * pri_dev_type=1-0050F204-1
+ * |Wireless Client|Company|cmodel|123|12345|
+ */
+ QStringList items = text.split(' ');
+ if (items.size() < 3)
+ return;
+ QString uuid = items[1];
+ QString addr = items[2];
+
+ int pos = text.indexOf('|');
+ if (pos < 0)
+ return;
+ items = text.mid(pos + 1).split('|');
+ if (items.size() < 1)
+ return;
+ QString name = items[0];
+ if (name.length() == 0)
+ name = addr;
+
+ remove_enrollee_uuid(uuid);
+
+ QStandardItem *item;
+ item = new QStandardItem(*laptop_icon, name);
+ if (item) {
+ item->setData(uuid, peer_role_uuid);
+ item->setData(addr, peer_role_address);
+ item->setData(PEER_TYPE_WPS_ER_ENROLLEE,
+ peer_role_type);
+ item->setToolTip(items.join(QString("\n")));
+ model.appendRow(item);
+ }
+
+ return;
+ }
+
+ if (text.startsWith(WPS_EVENT_ER_ENROLLEE_REMOVE)) {
+ /*
+ * WPS-ER-ENROLLEE-REMOVE 2b7093f1-d6fb-5108-adbb-bea66bb87333
+ * 02:66:a0:ee:17:27
+ */
+ QStringList items = text.split(' ');
+ if (items.size() < 2)
+ return;
+ remove_enrollee_uuid(items[1]);
+ return;
+ }
+}
+
+
+void Peers::closeEvent(QCloseEvent *)
+{
+ if (wpagui) {
+ char reply[20];
+ size_t replylen = sizeof(reply) - 1;
+ wpagui->ctrlRequest("WPS_ER_STOP", reply, &replylen);
+ }
+}
+
+
+void Peers::done(int r)
+{
+ QDialog::done(r);
+ close();
+}
+
+
+void Peers::remove_enrollee_uuid(QString uuid)
+{
+ if (model.rowCount() == 0)
+ return;
+
+ QModelIndexList lst = model.match(model.index(0, 0),
+ peer_role_uuid, uuid);
+ for (int i = 0; i < lst.size(); i++) {
+ QStandardItem *item = model.itemFromIndex(lst[i]);
+ if (item && item->data(peer_role_type).toInt() ==
+ PEER_TYPE_WPS_ER_ENROLLEE)
+ model.removeRow(lst[i].row());
+ }
}