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