Fix preferences dialog.
[dhcpcd-ui] / src / dhcpcd-qt / dhcpcd-preferences.cpp
1 /*
2  * dhcpcd-qt
3  * Copyright 2014 Roy Marples <roy@marples.name>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 #include <QDialog>
28 #include <QCheckBox>
29 #include <QComboBox>
30 #include <QFormLayout>
31 #include <QFrame>
32 #include <QGridLayout>
33 #include <QHBoxLayout>
34 #include <QIcon>
35 #include <QLabel>
36 #include <QLineEdit>
37 #include <QMessageBox>
38 #include <QPixmap>
39 #include <QPushButton>
40 #include <QStandardItemModel>
41 #include <QVBoxLayout>
42
43 #include "config.h"
44 #include "dhcpcd-preferences.h"
45 #include "dhcpcd-ipv4validator.h"
46 #include "dhcpcd-qt.h"
47 #include "dhcpcd-wi.h"
48
49 DhcpcdPreferences::DhcpcdPreferences(DhcpcdQt *parent)
50     : QDialog(parent)
51 {
52         this->parent = parent;
53         resize(400, 200);
54         setWindowIcon(DhcpcdQt::getIcon("status", "network-transmit-receive"));
55         setWindowTitle(tr("Network Preferences"));
56
57         name = NULL;
58         config = NULL;
59         eWhat = eBlock = NULL;
60         iface = NULL;
61
62         QVBoxLayout *layout = new QVBoxLayout();
63
64         QGridLayout *topLayout = new QGridLayout();
65         QLabel *conf = new QLabel(tr("Configure:"));
66         topLayout->addWidget(conf, 0, 0);
67
68         what = new QComboBox();
69         connect(what, SIGNAL(currentIndexChanged(const QString &)),
70             this, SLOT(listBlocks(const QString &)));
71         topLayout->addWidget(what, 0, 1);
72         blocks = new QComboBox();
73         connect(blocks, SIGNAL(currentIndexChanged(const QString &)),
74             this, SLOT(showBlock(const QString &)));
75         topLayout->addWidget(blocks, 0, 2);
76
77         topLayout->setColumnStretch(2, 10);
78
79         QWidget *topBox = new QWidget();
80         topBox->setLayout(topLayout);
81         layout->addWidget(topBox);
82         QFrame *topSep = new QFrame();
83         topSep->setFrameShape(QFrame::HLine);
84         topSep->setFrameShadow(QFrame::Sunken);
85         layout->addWidget(topSep);
86
87         autoConf = new QCheckBox(tr("Automatically configure empty options"));
88         autoConf->setChecked(true);
89         layout->addWidget(autoConf);
90
91         DhcpcdIPv4Validator *v =
92             new DhcpcdIPv4Validator(DhcpcdIPv4Validator::Plain, this);
93         DhcpcdIPv4Validator *vc =
94             new DhcpcdIPv4Validator(DhcpcdIPv4Validator::CIDR, this);
95         DhcpcdIPv4Validator *vs =
96             new DhcpcdIPv4Validator(DhcpcdIPv4Validator::Spaced, this);
97         ip = new QLineEdit();
98         ip->setValidator(vc);
99         router = new QLineEdit();
100         router->setValidator(v);
101         rdnss = new QLineEdit();
102         rdnss->setValidator(vs);
103         dnssl = new QLineEdit();
104 #if defined(__NetBSD__) || (__OpenBSD__)
105         dnssl->setMaxLength(1024);
106 else
107         dnssl->setMaxLength(256);
108 #endif
109         QFormLayout *ipLayout = new QFormLayout();
110         ipLayout->addRow(tr("IP Address:"), ip);
111         ipLayout->addRow(tr("Router:"), router);
112         ipLayout->addRow(tr("DNS Servers:"), rdnss);
113         ipLayout->addRow(tr("DNS Search:"), dnssl);
114         ipSetup = new QWidget();
115         ipSetup->setLayout(ipLayout);
116         layout->addWidget(ipSetup);
117
118         QHBoxLayout *buttonLayout = new QHBoxLayout();
119         clear = new QPushButton(tr("&Clear"));
120         clear->setIcon(QIcon::fromTheme("edit-clear"));
121         buttonLayout->addWidget(clear);
122         QPushButton *rebind = new QPushButton(tr("&Rebind"));
123         rebind->setIcon(QIcon::fromTheme("application-x-executable"));
124         buttonLayout->addWidget(rebind);
125         QPushButton *close = new QPushButton(tr("&Close"));
126         close->setIcon(QIcon::fromTheme("window-close"));
127         buttonLayout->addWidget(close);
128         QWidget *buttons = new QWidget();
129         buttons->setLayout(buttonLayout);
130         layout->addWidget(buttons);
131
132         QIcon wired = DhcpcdQt::getIcon("devices", "network-wired");
133         what->addItem(wired, tr("Interface"));
134         QIcon wireless = DhcpcdQt::getIcon("devices", "network-wireless");
135         what->addItem(wireless, tr("SSID"));
136
137         connect(clear, SIGNAL(clicked()), this, SLOT(clearConfig()));
138         connect(rebind, SIGNAL(clicked()), this, SLOT(rebind()));
139         connect(close, SIGNAL(clicked()), this, SLOT(tryClose()));
140
141         setLayout(layout);
142
143         autoConf->setEnabled(false);
144         ipSetup->setEnabled(false);
145         clear->setEnabled(false);
146
147         DHCPCD_CONNECTION *con = parent->getConnection();
148         if (!dhcpcd_config_writeable(con))
149                 QMessageBox::warning(this, tr("Not writeable"),
150                     tr("The dhcpcd configuration file is not writeable\n\n%1")
151                     .arg(dhcpcd_cffile(con)));
152 }
153
154 DhcpcdPreferences::~DhcpcdPreferences()
155 {
156
157         free(eWhat);
158         eWhat = NULL;
159         free(eBlock);
160         eBlock = NULL;
161 }
162
163 void DhcpcdPreferences::closeEvent(QCloseEvent *e)
164 {
165
166         parent->dialogClosed(this);
167         QDialog::closeEvent(e);
168 }
169
170 void DhcpcdPreferences::listBlocks(const QString &txt)
171 {
172
173         /* clear and then disconnect so we trigger a save */
174         blocks->clear();
175         blocks->disconnect(this);
176         
177         free(eWhat);
178         eWhat = strdup(txt.toLower().toAscii());
179
180         if (txt == "Interface") {
181                 DHCPCD_IF *i;
182
183                 blocks->addItem(tr("Select an interface"));
184                 for (i = dhcpcd_interfaces(parent->getConnection());
185                     i; i = i->next)
186                 {
187                         if (strcmp(i->type, "link") == 0) {
188                                 QIcon icon = DhcpcdQt::getIcon("devices",
189                                     i->wireless ?
190                                     "network-wireless" : "network-wired");
191                                 blocks->addItem(icon, i->ifname);
192                         }
193                 }
194         } else {
195                 QList<DhcpcdWi *> *wis = parent->getWis();
196
197                 blocks->addItem(tr("Select a SSID"));
198                 for (int i = 0; i < wis->size(); i++) {
199                         DHCPCD_WI_SCAN *scan;
200                         DhcpcdWi *wi = wis->at(i);
201
202                         for (scan = wi->getScans(); scan; scan = scan->next) {
203                                 QIcon icon;
204
205                                 icon = DhcpcdQt::getIcon(
206                                     scan->flags[0] == '\0' ?"devices" :"status",
207                                     scan->flags[0] == '\0' ?"network-wireless" :
208                                         "network-wireless-encrypted");
209                                 blocks->addItem(icon, scan->ssid);
210                         }
211                 }
212         }
213
214         /* Now make the 1st item unselectable and reconnect */
215         qobject_cast<QStandardItemModel *>(blocks->model())->item(0)->setEnabled(false);
216         connect(blocks, SIGNAL(currentIndexChanged(const QString &)),
217             this, SLOT(showBlock(const QString &)));
218
219 }
220
221 void DhcpcdPreferences::clearConfig()
222 {
223
224         autoConf->setChecked(true);
225         ip->setText("");
226         router->setText("");
227         rdnss->setText("");
228         dnssl->setText("");
229 }
230
231 void DhcpcdPreferences::showConfig()
232 {
233         const char *val;
234         bool a;
235
236         if ((val = dhcpcd_config_get_static(config, "ip_address=")) != NULL)
237                 a = false;
238         else
239                 a = !((val = dhcpcd_config_get(config, "inform")) == NULL &&
240                     (iface && iface->flags & IFF_POINTOPOINT));
241         autoConf->setChecked(a);
242         ip->setText(val);
243         router->setText(dhcpcd_config_get_static(config, "routers="));
244         rdnss->setText(dhcpcd_config_get_static(config,"domain_name_servers="));
245         dnssl->setText(dhcpcd_config_get_static(config, "domain_search="));
246 }
247
248 bool DhcpcdPreferences::changedConfig()
249 {
250         const char *val;
251         bool a;
252
253         if ((val = dhcpcd_config_get_static(config, "ip_address=")) != NULL)
254                 a = false;
255         else
256                 a = !((val = dhcpcd_config_get(config, "inform")) == NULL &&
257                     (iface && iface->flags & IFF_POINTOPOINT));
258         if (autoConf->isChecked() != a)
259                 return true;
260         if (ip->text().compare(val))
261                 return true;
262         val = dhcpcd_config_get_static(config, "routers=");
263         if (router->text().compare(val))
264                 return true;
265         val = dhcpcd_config_get_static(config, "domain_name_servers=");
266         if (rdnss->text().compare(val))
267                 return true;
268         val = dhcpcd_config_get_static(config, "domain_search=");
269         if (rdnss->text().compare(val))
270                 return true;
271         return false;
272 }
273
274
275 const char *DhcpcdPreferences::getString(QLineEdit *le)
276 {
277         if (le->text().isEmpty())
278                 return NULL;
279         return le->text().trimmed().toAscii();
280 }
281
282 bool DhcpcdPreferences::setOption(const char *opt, const char *val, bool *ret)
283 {
284         if (opt[strlen(opt) - 1] == '=') {
285                 if (!dhcpcd_config_set_static(&config, opt, val))
286                         qCritical("dhcpcd_config_set_static: %s",
287                             strerror(errno));
288                 else
289                         return true;
290         } else {
291                 if (!dhcpcd_config_set(&config, opt, val))
292                         qCritical("dhcpcd_config_set: %s",
293                             strerror(errno));
294                 else
295                         return true;
296         }
297
298         if (ret)
299                 *ret = false;
300         return false;
301 }
302
303
304 bool DhcpcdPreferences::makeConfig()
305 {
306         const char ns[] = "", *val;
307         bool a, ret;
308
309         a = autoConf->isChecked();
310         ret = true;
311         if (iface && iface->flags & IFF_POINTOPOINT)
312                 setOption("ip_address=", a ? NULL : ns, &ret);
313         else {
314                 val = getString(ip);
315                 setOption("inform", a ? val : NULL, &ret);
316                 setOption("ip_address=", a ? NULL : val, &ret);
317         }
318
319         val = getString(router);
320         setOption("routers=", val, &ret);
321
322         val = getString(rdnss);
323         setOption("domain_name_servers=", val, &ret);
324
325         val = getString(dnssl);
326         setOption("domain_search=", val, &ret);
327
328         return ret;
329 }
330
331 bool DhcpcdPreferences::writeConfig(bool *cancel)
332 {
333
334         switch (QMessageBox::question(this, tr("Save Configuration"),
335             tr("Do you want to save your changes?"),
336             QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel)) {
337         case QMessageBox::Cancel:
338                 *cancel = true;
339                 return false;
340         case QMessageBox::Discard:
341                 return true;
342         default:
343                 break;
344         }
345         *cancel = false;
346
347         DHCPCD_CONNECTION *con = parent->getConnection();
348         if (!makeConfig()) {
349                 qCritical("failed to make config");
350                 goto err;
351         }
352         if (!dhcpcd_config_write(con, eWhat, eBlock, config)) {
353                 qCritical("dhcpcd_config_write: %s", strerror(errno));
354                 QMessageBox::critical(parent,
355                     tr("Failed to write configuration"),
356                     tr("Failed to write configuration:\n\n%1: %2")
357                     .arg(dhcpcd_cffile(con))
358                     .arg(strerror(errno)));
359                 goto err;
360         }
361         return true;
362
363 err:
364         /* Reload our config if there is a problem */
365         config = dhcpcd_config_read(con, eWhat, eBlock);
366         return false;
367 }
368
369 void DhcpcdPreferences::showBlock(const QString &txt)
370 {
371
372         if (eBlock) {
373                 if (changedConfig()) {
374                         bool cancel;
375                         if (!writeConfig(&cancel))
376                                 return;
377                 }
378                 free(eBlock);
379         }
380         if (txt.isEmpty())
381                 eBlock = NULL;
382         else
383                 eBlock = strdup(txt.toAscii());
384
385         dhcpcd_config_free(config);
386         iface = NULL;
387         DHCPCD_CONNECTION *con = parent->getConnection();
388
389         if (eBlock && eWhat) {
390                 if (strcmp(eWhat, "interface") == 0)
391                         iface = dhcpcd_get_if(con, eBlock, "link");
392                 ip->setEnabled(iface == NULL ||
393                     !(iface->flags & IFF_POINTOPOINT));
394                 errno = 0;
395                 config = dhcpcd_config_read(con, eWhat, eBlock);
396                 if (config == NULL && errno) {
397                         const char *s;
398
399                         s = strerror(errno);
400                         qCritical("dhcpcd_config_read: %s", s);
401                         QMessageBox::critical(this,
402                             tr("Error reading configuration"),
403                             tr("Error reading: ") + dhcpcd_cffile(con) +
404                             "\n\n" + s);
405                 }
406         } else
407                 config = NULL;
408
409         showConfig();
410         bool enabled = dhcpcd_config_writeable(con) && eBlock != NULL;
411         autoConf->setEnabled(enabled);
412         ipSetup->setEnabled(enabled);
413         clear->setEnabled(enabled);
414 }
415
416 bool DhcpcdPreferences::tryRebind(const char *ifname)
417 {
418
419         if (dhcpcd_rebind(parent->getConnection(), ifname) == 0)
420                 return true;
421         
422         qCritical("dhcpcd_rebind: %s", strerror(errno));
423         QMessageBox::critical(this,
424             tr("Rebind failed"),
425             ifname ? tr("Failed to rebind interface %1: %2")
426             .arg(ifname).arg(strerror(errno)) :
427             tr("Failed to rebind: %1")
428             .arg(strerror(errno)));
429         return false; 
430 }
431
432 void DhcpcdPreferences::rebind()
433 {
434
435         if (changedConfig()) {
436                 bool cancel;
437                 writeConfig(&cancel);
438                 if (cancel)
439                         return;
440         }
441
442         DHCPCD_CONNECTION *con = parent->getConnection();
443         DHCPCD_IF *i;
444         bool found = false;
445         bool worked;
446         if (strcmp(eWhat, "interface") == 0) {
447                 worked = tryRebind(iface ? iface->ifname : NULL);
448                 goto done;
449         }
450         
451         worked = true;
452         for (i = dhcpcd_interfaces(con); i; i = i->next) {
453                 if (strcmp(i->ssid, eBlock) == 0) {
454                         found = true;
455                         if (!tryRebind(i->ifname))
456                                 worked = false;
457                 }
458         }
459         if (!found) {
460                 QMessageBox::information(this,
461                     tr("No matching interface"),
462                     tr("No interface is bound to this SSID to rebind"));
463                 return;
464         }
465
466 done:           
467         if (worked)
468                 QMessageBox::information(this,
469                     tr("Rebind sent"),
470                     tr("The rebind signal has been sent to dhcpcd"));
471 }
472
473 void DhcpcdPreferences::tryClose()
474 {
475
476         if (changedConfig()) {
477                 bool cancel;
478                 writeConfig(&cancel);
479                 if (cancel)
480                         return;
481         }
482         close();
483 }