wpa_gui: Add AP and laptop icons for peer dialog
[libeap.git] / wpa_supplicant / wpa_gui-qt4 / peers.cpp
1 /*
2  * wpa_gui - Peers class
3  * Copyright (c) 2009, Atheros Communications
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 as
7  * published by the Free Software Foundation.
8  *
9  * Alternatively, this software may be distributed under the terms of BSD
10  * license.
11  *
12  * See README and COPYING for more details.
13  */
14
15 #include <cstdio>
16 #include <QImageReader>
17 #include <QMessageBox>
18
19 #include "wpa_ctrl.h"
20 #include "wpagui.h"
21 #include "stringquery.h"
22 #include "peers.h"
23
24
25 static const int peer_role_address = Qt::UserRole + 1;
26 static const int peer_role_type = Qt::UserRole + 2;
27 static const int peer_role_uuid = Qt::UserRole + 3;
28
29 /*
30  * TODO:
31  * - add current AP info (e.g., from WPS) in station mode
32  * - different icons to indicate peer type
33  */
34
35 enum peer_type {
36         PEER_TYPE_ASSOCIATED_STATION,
37         PEER_TYPE_AP,
38         PEER_TYPE_AP_WPS,
39         PEER_TYPE_WPS_PIN_NEEDED,
40         PEER_TYPE_WPS_ER_AP,
41         PEER_TYPE_WPS_ER_ENROLLEE
42 };
43
44
45 Peers::Peers(QWidget *parent, const char *, bool, Qt::WFlags)
46         : QDialog(parent)
47 {
48         setupUi(this);
49
50         if (QImageReader::supportedImageFormats().contains(QByteArray("svg")))
51         {
52                 default_icon = new QIcon(":/icons/wpa_gui.svg");
53                 ap_icon = new QIcon(":/icons/ap.svg");
54                 laptop_icon = new QIcon(":/icons/laptop.svg");
55         } else {
56                 default_icon = new QIcon(":/icons/wpa_gui.png");
57                 ap_icon = new QIcon(":/icons/ap.png");
58                 laptop_icon = new QIcon(":/icons/laptop.png");
59         }
60
61         peers->setModel(&model);
62         peers->setResizeMode(QListView::Adjust);
63
64         peers->setContextMenuPolicy(Qt::CustomContextMenu);
65         connect(peers, SIGNAL(customContextMenuRequested(const QPoint &)),
66                 this, SLOT(context_menu(const QPoint &)));
67
68         wpagui = NULL;
69 }
70
71
72 void Peers::setWpaGui(WpaGui *_wpagui)
73 {
74         wpagui = _wpagui;
75         update_peers();
76 }
77
78
79 Peers::~Peers()
80 {
81         delete default_icon;
82         delete ap_icon;
83         delete laptop_icon;
84 }
85
86
87 void Peers::languageChange()
88 {
89         retranslateUi(this);
90 }
91
92
93 void Peers::context_menu(const QPoint &pos)
94 {
95         QMenu *menu = new QMenu;
96         if (menu == NULL)
97                 return;
98
99         QModelIndex idx = peers->indexAt(pos);
100         if (idx.isValid()) {
101                 ctx_item = model.itemFromIndex(idx);
102                 int type = ctx_item->data(peer_role_type).toInt();
103                 QString title;
104                 switch (type) {
105                 case PEER_TYPE_ASSOCIATED_STATION:
106                         title = tr("Associated station");
107                         break;
108                 case PEER_TYPE_AP:
109                         title = tr("AP");
110                         break;
111                 case PEER_TYPE_AP_WPS:
112                         title = tr("WPS AP");
113                         break;
114                 case PEER_TYPE_WPS_PIN_NEEDED:
115                         title = tr("WPS PIN needed");
116                         break;
117                 case PEER_TYPE_WPS_ER_AP:
118                         title = tr("ER: WPS AP");
119                         break;
120                 case PEER_TYPE_WPS_ER_ENROLLEE:
121                         title = tr("ER: WPS Enrollee");
122                         break;
123                 }
124                 menu->addAction(title)->setEnabled(false);
125                 menu->addSeparator();
126
127                 if (type == PEER_TYPE_ASSOCIATED_STATION ||
128                     type == PEER_TYPE_AP_WPS ||
129                     type == PEER_TYPE_WPS_PIN_NEEDED ||
130                     type == PEER_TYPE_WPS_ER_ENROLLEE) {
131                         /* TODO: only for peers that are requesting WPS PIN
132                          * method */
133                         menu->addAction(QString("Enter WPS PIN"), this,
134                                         SLOT(enter_pin()));
135                 }
136         } else {
137                 ctx_item = NULL;
138                 menu->addAction(QString("Refresh"), this, SLOT(ctx_refresh()));
139         }
140
141         menu->exec(peers->mapToGlobal(pos));
142 }
143
144
145 void Peers::enter_pin()
146 {
147         if (ctx_item == NULL)
148                 return;
149
150         StringQuery input(tr("PIN:"));
151         input.setWindowTitle(tr("PIN for ") + ctx_item->text());
152         if (input.exec() != QDialog::Accepted)
153                 return;
154
155         char cmd[100];
156         char reply[100];
157         size_t reply_len;
158
159         if (ctx_item->data(peer_role_type).toInt() ==
160             PEER_TYPE_WPS_ER_ENROLLEE) {
161                 QString uuid = ctx_item->data(peer_role_uuid).toString();
162                 snprintf(cmd, sizeof(cmd), "WPS_ER_PIN %s %s",
163                          uuid.toAscii().constData(),
164                          input.get_string().toAscii().constData());
165         } else {
166                 QString addr = ctx_item->data(peer_role_address).toString();
167                 snprintf(cmd, sizeof(cmd), "WPS_PIN %s %s",
168                          addr.toAscii().constData(),
169                          input.get_string().toAscii().constData());
170         }
171         reply_len = sizeof(reply) - 1;
172         if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0) {
173                 QMessageBox msg;
174                 msg.setIcon(QMessageBox::Warning);
175                 msg.setText("Failed to set the WPS PIN.");
176                 msg.exec();
177         }
178 }
179
180
181 void Peers::ctx_refresh()
182 {
183         update_peers();
184 }
185
186
187 void Peers::add_station(QString info)
188 {
189         QStringList lines = info.split(QRegExp("\\n"));
190         QString name;
191
192         for (QStringList::Iterator it = lines.begin();
193              it != lines.end(); it++) {
194                 int pos = (*it).indexOf('=') + 1;
195                 if (pos < 1)
196                         continue;
197
198                 if ((*it).startsWith("wpsDeviceName="))
199                         name = (*it).mid(pos);
200         }
201
202         if (name.isEmpty())
203                 name = lines[0];
204
205         QStandardItem *item = new QStandardItem(*laptop_icon, name);
206         if (item) {
207                 item->setData(lines[0], peer_role_address);
208                 item->setData(PEER_TYPE_ASSOCIATED_STATION,
209                               peer_role_type);
210                 item->setToolTip(info);
211                 model.appendRow(item);
212         }
213 }
214
215
216 void Peers::add_stations()
217 {
218         char reply[2048];
219         size_t reply_len;
220         char cmd[30];
221         int res;
222
223         reply_len = sizeof(reply) - 1;
224         if (wpagui->ctrlRequest("STA-FIRST", reply, &reply_len) < 0)
225                 return;
226
227         do {
228                 reply[reply_len] = '\0';
229                 QString info(reply);
230                 char *txt = reply;
231                 while (*txt != '\0' && *txt != '\n')
232                         txt++;
233                 *txt++ = '\0';
234                 if (strncmp(reply, "FAIL", 4) == 0 ||
235                     strncmp(reply, "UNKNOWN", 7) == 0)
236                         break;
237
238                 add_station(info);
239
240                 reply_len = sizeof(reply) - 1;
241                 snprintf(cmd, sizeof(cmd), "STA-NEXT %s", reply);
242                 res = wpagui->ctrlRequest(cmd, reply, &reply_len);
243         } while (res >= 0);
244 }
245
246
247 void Peers::add_single_station(const char *addr)
248 {
249         char reply[2048];
250         size_t reply_len;
251         char cmd[30];
252
253         reply_len = sizeof(reply) - 1;
254         snprintf(cmd, sizeof(cmd), "STA %s", addr);
255         if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0)
256                 return;
257
258         reply[reply_len] = '\0';
259         QString info(reply);
260         char *txt = reply;
261         while (*txt != '\0' && *txt != '\n')
262                 txt++;
263         *txt++ = '\0';
264         if (strncmp(reply, "FAIL", 4) == 0 ||
265             strncmp(reply, "UNKNOWN", 7) == 0)
266                 return;
267
268         add_station(info);
269 }
270
271
272 void Peers::add_scan_results()
273 {
274         char reply[2048];
275         size_t reply_len;
276         int index;
277         char cmd[20];
278
279         index = 0;
280         while (wpagui) {
281                 snprintf(cmd, sizeof(cmd), "BSS %d", index++);
282                 if (index > 1000)
283                         break;
284
285                 reply_len = sizeof(reply) - 1;
286                 if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0)
287                         break;
288                 reply[reply_len] = '\0';
289
290                 QString bss(reply);
291                 if (bss.isEmpty() || bss.startsWith("FAIL"))
292                         break;
293
294                 QString ssid, bssid, flags, wps_name;
295
296                 QStringList lines = bss.split(QRegExp("\\n"));
297                 for (QStringList::Iterator it = lines.begin();
298                      it != lines.end(); it++) {
299                         int pos = (*it).indexOf('=') + 1;
300                         if (pos < 1)
301                                 continue;
302
303                         if ((*it).startsWith("bssid="))
304                                 bssid = (*it).mid(pos);
305                         else if ((*it).startsWith("flags="))
306                                 flags = (*it).mid(pos);
307                         else if ((*it).startsWith("ssid="))
308                                 ssid = (*it).mid(pos);
309                         else if ((*it).startsWith("wps_device_name="))
310                                 wps_name = (*it).mid(pos);
311                 }
312
313                 QString name = wps_name;
314                 if (name.isEmpty())
315                         name = ssid + "\n" + bssid;
316
317                 QStandardItem *item = new QStandardItem(*ap_icon, name);
318                 if (item) {
319                         item->setData(bssid, peer_role_address);
320                         if (flags.contains("[WPS"))
321                                 item->setData(PEER_TYPE_AP_WPS,
322                                               peer_role_type);
323                         else
324                                 item->setData(PEER_TYPE_AP, peer_role_type);
325
326                         for (int i = 0; i < lines.size(); i++) {
327                                 if (lines[i].length() > 60) {
328                                         lines[i].remove(
329                                                 60, lines[i].length());
330                                         lines[i] += "..";
331                                 }
332                         }
333                         item->setToolTip(lines.join("\n"));
334                         model.appendRow(item);
335                 }
336         }
337 }
338
339
340 void Peers::update_peers()
341 {
342         model.clear();
343         if (wpagui == NULL)
344                 return;
345
346         char reply[20];
347         size_t replylen = sizeof(reply) - 1;
348         wpagui->ctrlRequest("WPS_ER_START", reply, &replylen);
349
350         add_stations();
351         add_scan_results();
352 }
353
354
355 QStandardItem * Peers::find_addr(QString addr)
356 {
357         if (model.rowCount() == 0)
358                 return NULL;
359
360         QModelIndexList lst = model.match(model.index(0, 0), peer_role_address,
361                                           addr);
362         if (lst.size() == 0)
363                 return NULL;
364         return model.itemFromIndex(lst[0]);
365 }
366
367
368 QStandardItem * Peers::find_uuid(QString uuid)
369 {
370         if (model.rowCount() == 0)
371                 return NULL;
372
373         QModelIndexList lst = model.match(model.index(0, 0), peer_role_uuid,
374                                           uuid);
375         if (lst.size() == 0)
376                 return NULL;
377         return model.itemFromIndex(lst[0]);
378 }
379
380
381 void Peers::event_notify(WpaMsg msg)
382 {
383         QString text = msg.getMsg();
384
385         if (text.startsWith(WPS_EVENT_PIN_NEEDED)) {
386                 /*
387                  * WPS-PIN-NEEDED 5a02a5fa-9199-5e7c-bc46-e183d3cb32f7
388                  * 02:2a:c4:18:5b:f3
389                  * [Wireless Client|Company|cmodel|123|12345|1-0050F204-1]
390                  */
391                 QStringList items = text.split(' ');
392                 QString uuid = items[1];
393                 QString addr = items[2];
394                 QString name = "";
395
396                 QStandardItem *item = find_addr(addr);
397                 if (item)
398                         return;
399
400                 int pos = text.indexOf('[');
401                 if (pos >= 0) {
402                         int pos2 = text.lastIndexOf(']');
403                         if (pos2 >= pos) {
404                                 items = text.mid(pos + 1, pos2 - pos - 1).
405                                         split('|');
406                                 name = items[0];
407                                 items.append(addr);
408                         }
409                 }
410
411                 item = new QStandardItem(*laptop_icon, name);
412                 if (item) {
413                         item->setData(addr, peer_role_address);
414                         item->setData(PEER_TYPE_WPS_PIN_NEEDED,
415                                       peer_role_type);
416                         item->setToolTip(items.join(QString("\n")));
417                         model.appendRow(item);
418                 }
419                 return;
420         }
421
422         if (text.startsWith(AP_STA_CONNECTED)) {
423                 /* AP-STA-CONNECTED 02:2a:c4:18:5b:f3 */
424                 QStringList items = text.split(' ');
425                 QString addr = items[1];
426                 QStandardItem *item = find_addr(addr);
427                 if (item == NULL || item->data(peer_role_type).toInt() !=
428                     PEER_TYPE_ASSOCIATED_STATION)
429                         add_single_station(addr.toAscii().constData());
430                 return;
431         }
432
433         if (text.startsWith(AP_STA_DISCONNECTED)) {
434                 /* AP-STA-DISCONNECTED 02:2a:c4:18:5b:f3 */
435                 QStringList items = text.split(' ');
436                 QString addr = items[1];
437
438                 if (model.rowCount() == 0)
439                         return;
440
441                 QModelIndexList lst = model.match(model.index(0, 0),
442                                                   peer_role_address, addr);
443                 for (int i = 0; i < lst.size(); i++) {
444                         QStandardItem *item = model.itemFromIndex(lst[i]);
445                         if (item && item->data(peer_role_type).toInt() ==
446                             PEER_TYPE_ASSOCIATED_STATION)
447                                 model.removeRow(lst[i].row());
448                 }
449                 return;
450         }
451
452         if (text.startsWith(WPS_EVENT_ER_AP_ADD)) {
453                 /*
454                  * WPS-ER-AP-ADD 87654321-9abc-def0-1234-56789abc0002|
455                  * Very friendly name|Company|Long description of the model|
456                  * WAP|http://w1.fi/|http://w1.fi/hostapd/
457                  */
458                 int pos = text.indexOf(' ');
459                 if (pos < 0)
460                         return;
461                 QStringList items = text.mid(pos + 1).split('|');
462                 if (items.size() < 2)
463                         return;
464
465                 QStandardItem *item = find_uuid(items[0]);
466                 if (item)
467                         return;
468
469                 item = new QStandardItem(*ap_icon, items[1]);
470                 if (item) {
471                         item->setData(items[0], peer_role_uuid);
472                         item->setData(PEER_TYPE_WPS_ER_AP, peer_role_type);
473                         item->setToolTip(items.join(QString("\n")));
474                         model.appendRow(item);
475                 }
476
477                 return;
478         }
479
480         if (text.startsWith(WPS_EVENT_ER_AP_REMOVE)) {
481                 /* WPS-ER-AP-REMOVE 87654321-9abc-def0-1234-56789abc0002 */
482                 QStringList items = text.split(' ');
483                 if (items.size() < 2)
484                         return;
485                 if (model.rowCount() == 0)
486                         return;
487
488                 QModelIndexList lst = model.match(model.index(0, 0),
489                                                   peer_role_uuid, items[1]);
490                 for (int i = 0; i < lst.size(); i++) {
491                         QStandardItem *item = model.itemFromIndex(lst[i]);
492                         if (item && item->data(peer_role_type).toInt() ==
493                             PEER_TYPE_WPS_ER_AP)
494                                 model.removeRow(lst[i].row());
495                 }
496                 return;
497         }
498
499         if (text.startsWith(WPS_EVENT_ER_ENROLLEE_ADD)) {
500                 /*
501                  * WPS-ER-ENROLLEE-ADD 2b7093f1-d6fb-5108-adbb-bea66bb87333
502                  * 02:66:a0:ee:17:27 M1=1 config_methods=0x14d dev_passwd_id=0
503                  * pri_dev_type=1-0050F204-1
504                  * |Wireless Client|Company|cmodel|123|12345|
505                  */
506                 QStringList items = text.split(' ');
507                 if (items.size() < 3)
508                         return;
509                 QString uuid = items[1];
510                 QString addr = items[2];
511
512                 int pos = text.indexOf('|');
513                 if (pos < 0)
514                         return;
515                 items = text.mid(pos + 1).split('|');
516                 if (items.size() < 1)
517                         return;
518                 QString name = items[0];
519                 if (name.length() == 0)
520                         name = addr;
521
522                 remove_enrollee_uuid(uuid);
523
524                 QStandardItem *item;
525                 item = new QStandardItem(*laptop_icon, name);
526                 if (item) {
527                         item->setData(uuid, peer_role_uuid);
528                         item->setData(addr, peer_role_address);
529                         item->setData(PEER_TYPE_WPS_ER_ENROLLEE,
530                                       peer_role_type);
531                         item->setToolTip(items.join(QString("\n")));
532                         model.appendRow(item);
533                 }
534
535                 return;
536         }
537
538         if (text.startsWith(WPS_EVENT_ER_ENROLLEE_REMOVE)) {
539                 /*
540                  * WPS-ER-ENROLLEE-REMOVE 2b7093f1-d6fb-5108-adbb-bea66bb87333
541                  * 02:66:a0:ee:17:27
542                  */
543                 QStringList items = text.split(' ');
544                 if (items.size() < 2)
545                         return;
546                 remove_enrollee_uuid(items[1]);
547                 return;
548         }
549 }
550
551
552 void Peers::closeEvent(QCloseEvent *)
553 {
554         if (wpagui) {
555                 char reply[20];
556                 size_t replylen = sizeof(reply) - 1;
557                 wpagui->ctrlRequest("WPS_ER_STOP", reply, &replylen);
558         }
559 }
560
561
562 void Peers::done(int r)
563 {
564         QDialog::done(r);
565         close();
566 }
567
568
569 void Peers::remove_enrollee_uuid(QString uuid)
570 {
571         if (model.rowCount() == 0)
572                 return;
573
574         QModelIndexList lst = model.match(model.index(0, 0),
575                                           peer_role_uuid, uuid);
576         for (int i = 0; i < lst.size(); i++) {
577                 QStandardItem *item = model.itemFromIndex(lst[i]);
578                 if (item && item->data(peer_role_type).toInt() ==
579                     PEER_TYPE_WPS_ER_ENROLLEE)
580                         model.removeRow(lst[i].row());
581         }
582 }