android: Fix XML form filter.
[qpwmc.git] / trayIcon.cpp
blob7a4389552d709dd3ae00871338c8b5c9c9037491
1 /*
2 Copyright (C) 2018-2023 Ben Kibbey <bjk@luxsci.net>
4 This file is part of qpwmc.
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with this library; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19 USA
21 #include <QMenu>
22 #include <QSettings>
23 #include <QClipboard>
24 #include <QTimer>
25 #include <QMessageBox>
26 #include "main.h"
27 #include "trayIcon.h"
28 #include "pwmdRemoteHost.h"
30 #define PwmdCmdIdGetContent 0
32 #define RotateStepDegrees 8
34 static bool waitingForClose;
36 TrayIcon::TrayIcon () : QSystemTrayIcon (QIcon (":icons/trayicon.svg"))
38 qRegisterMetaType <Pwmd::ConnectionState>("Pwmd::ConnectionState");
39 qRegisterMetaType <gpg_error_t>("gpg_error_t");
41 QCoreApplication::setOrganizationName ("QPwmc");
42 QCoreApplication::setOrganizationDomain ("qpwmc.sourceforge.net");
43 QCoreApplication::setApplicationName ("QPwmc");
44 QCoreApplication::setApplicationVersion (QPWMC_VERSION);
45 shortcutMenu = new QMenu ();
46 connect (shortcutMenu, SIGNAL (triggered (QAction *)), this,
47 SLOT (slotShortCut (QAction *)));
48 refreshShortcuts ();
49 connect (this, SIGNAL (activated (QSystemTrayIcon::ActivationReason)),
50 SLOT (slotSystemTrayActivated
51 (QSystemTrayIcon::ActivationReason)));
52 setContextMenu (shortcutMenu);
53 clipboardTimer = new QTimer (this);
54 clipboardTimer->setSingleShot (true);
55 connect (clipboardTimer, SIGNAL (timeout ()), SLOT (slotClearClipboard ()));
56 rotateTimer = new QTimer (this);
57 connect (rotateTimer, SIGNAL (timeout ()), SLOT (slotRotateIcon ()));
58 rotateIcon (true);
59 lingerTimer = new QTimer (this);
60 connect (lingerTimer, SIGNAL (timeout ()), SLOT (slotLinger ()));
61 lingerRemaining = 0;
62 lingerTime = 0;
63 closeFile = false;
64 statusData = nullptr;
65 pwm = nullptr;
68 TrayIcon::~TrayIcon ()
70 delete rotateTimer;
71 delete lingerTimer;
72 delete clipboardTimer;
73 if (statusData)
74 delete statusData;
77 void
78 TrayIcon::startStopTimer (bool start)
80 if (start)
82 setIcon (QIcon (":icons/trayicon-linger.svg"));
83 lingerTimer->start (1000);
84 return;
87 lingerTimer->stop ();
88 setIcon (QIcon (":icons/trayicon.svg"));
89 lingerRemaining = 0;
92 void
93 TrayIcon::slotLinger ()
95 lingerRemaining--;
96 if (pwm && lingerRemaining > 0)
97 return;
99 startStopTimer (false);
100 delete pwm;
101 pwm = nullptr;
104 void
105 TrayIcon::slotRotateIcon ()
107 rotateIcon ();
110 void
111 TrayIcon::rotateIcon (bool reset)
113 static float rotate = 1;
114 static float mod = -0.1;
116 if (reset)
118 rotate = 1;
119 mod = -0.1;
120 rotateTimer->stop ();
121 setIcon (QIcon (":icons/trayicon.svg"));
122 setToolTip (tr ("Double click to spawn the editor, right-click for shortcuts."));
123 return;
126 QSize size (22, 22);
127 QPixmap p = QIcon (":icons/trayicon.svg").pixmap (size);
128 QTransform t;
130 if (pwm->state () == Pwmd::Init || pwm->state () == Pwmd::Connecting)
132 if (rotate + mod < 0.0)
133 mod = 0.1;
134 else if (rotate + mod > 1)
135 mod = -0.1;
137 rotate += mod;
138 t.scale (rotate, rotate);
140 else
142 if (rotate + RotateStepDegrees > 360)
143 rotate = 0;
145 rotate += RotateStepDegrees;
146 t.rotate (rotate);
149 QPixmap pp = p.transformed (t);
150 setIcon (QIcon (pp));
153 void
154 TrayIcon::showMessage (unsigned rc, bool reset)
156 if (gpg_err_source (rc) == GPG_ERR_SOURCE_PINENTRY
157 && gpg_err_code (rc) == GPG_ERR_CANCELED)
158 goto done;
160 if (supportsMessages())
162 QSystemTrayIcon::showMessage(tr ("There was an error while communicating with pwmd."),
163 Pwmd::errorString(rc, pwm));
165 else
166 Pwmd::showError (rc, pwm);
168 done:
169 if (reset || !lingerRemaining)
171 startStopTimer (false);
172 waitingForClose = true;
176 void
177 TrayIcon::slotConnectionStateChanged (Pwmd::ConnectionState s)
179 pwmd_socket_t type;
180 static int last;
182 pwmd_socket_type (pwm->handle (), &type);
184 /* Fixes a race condition when Pwmd::connect() fails. The handle may have
185 * been reset (which clears the error code) before the dialog is shown.
187 pwm->tlsError = pwmd_gnutls_error(pwm->handle(), nullptr);
189 switch (s)
191 case Pwmd::Init:
192 startStopTimer (false);
193 pwmd_setopt (pwm->handle (), PWMD_OPTION_SOCKET_TIMEOUT,
194 currentHostData.connectTimeout ());
195 pwmd_setopt (pwm->handle (), PWMD_OPTION_SSH_AGENT,
196 currentHostData.sshAgent ());
197 pwmd_setopt (pwm->handle (), PWMD_OPTION_TLS_VERIFY,
198 currentHostData.tlsVerify ());
199 if (!currentHostData.tlsPriority().isEmpty ())
200 pwmd_setopt (pwm->handle (), PWMD_OPTION_TLS_PRIORITY,
201 currentHostData.tlsPriority ().toUtf8 ().data ());
202 pwmd_setopt (pwm->handle (), PWMD_OPTION_SOCKET_TIMEOUT,
203 currentHostData.connectTimeout ());
204 setToolTip (tr ("Connecting..."));
205 break;
206 case Pwmd::Connected:
207 pwmd_setopt (pwm->handle (), PWMD_OPTION_SOCKET_TIMEOUT,
208 currentHostData.socketTimeout ());
209 if (last != Pwmd::Opened && last != s)
210 setToolTip (tr ("Opening data file..."));
211 break;
212 case Pwmd::Opened:
213 setToolTip (tr ("Retrieving content..."));
214 break;
215 default:
216 break;
219 last = s;
222 void
223 TrayIcon::shortCutFinalize (const QString &result)
225 QClipboard *c = QApplication::clipboard ();
227 c->setText (result, QClipboard::Selection);
228 c->setText (result);
229 refreshShortcuts ();
231 if (clipboardTimeout)
232 clipboardTimer->start (clipboardTimeout * 1000);
234 lingerRemaining = lingerTime;
235 if (lingerTime > 0)
237 startStopTimer (true);
238 if (closeFile && pwm->state () == Pwmd::Opened)
239 pwm->close ();
242 if (lingerRemaining)
243 return;
245 delete pwm;
246 pwm = nullptr;
249 void
250 TrayIcon::slotKnownHostCallback (void *data, const char *host, const char *key,
251 size_t len)
253 gpg_error_t rc = Pwmd::knownHostPrompt (data, host, key, len);
254 emit knownHostRc (rc);
257 void
258 TrayIcon::slotStatusMessage (QString line, void *userData)
260 QString *shortcut = static_cast <QString *> (userData);
261 QStringList l = QString (line).split (" ");
263 if (l.at (0) == "EXPIRE")
265 unsigned t = l.at(1).toUInt ();
266 QDateTime date;
267 date.setSecsSinceEpoch (t);
268 QString title = QString (tr ("The content for the shortcut \"%1\" has expired.")). arg (*shortcut);
269 QString desc = QString (tr ("Use the editor to update the content or to change the expiry time. The element\ncontent has been copied to the clipboard.\n\nExpired at: %1.")).arg(date.toString());
271 if (supportsMessages())
272 QSystemTrayIcon::showMessage(title, desc);
273 else
275 QMessageBox m;
277 m.setText (title);
278 m.setInformativeText (desc);
279 m.setIcon (QMessageBox::Information);
280 m.exec ();
285 void
286 TrayIcon::slotShortCut (QAction * a)
288 EditShortcut data = a->data ().value < EditShortcut > ();
289 if (data.name ().isEmpty ())
290 return;
292 bool reset = false;
293 startStopTimer (false);
294 QSettings cfg ("qpwmc");
295 lingerTime = cfg.value ("linger", 20).toInt ();
296 closeFile = cfg.value ("closeFile", false).toBool ();
297 PwmdRemoteHost lastHostData = currentHostData;
298 QString lastSocket = pwm ? pwm->socket () : QString ();
299 QString lastFile = pwm ? pwm->filename () : QString ();
301 rotateTimer->start (40);
303 bool remote = false;
304 if (PwmdRemoteHost::fillRemoteHost (data.socket (), currentHostData))
306 remote = true;
307 if (currentHostData != lastHostData)
308 reset = true;
310 else
312 if (data.socket () != lastSocket)
313 reset = true;
316 if (reset || !pwm)
318 reset = true;
319 delete pwm;
320 pwm = new Pwmd (data.filename (), "qpwmc", 0, this);
323 if (remote)
325 pwm->setSocket (PwmdRemoteHost::socketUrl (currentHostData));
326 pwm->setConnectParameters (currentHostData.socketArgs ());
328 else
329 pwm->setSocket (data.socket ());
331 pwm->setFilename (data.filename ());
332 pwmd_setopt (pwm->handle (), PWMD_OPTION_SSH_AGENT,
333 currentHostData.sshAgent ());
334 pwmd_setopt (pwm->handle (), PWMD_OPTION_SOCKET_TIMEOUT,
335 currentHostData.socketTimeout ());
336 pwmd_setopt (pwm->handle (), PWMD_OPTION_TLS_VERIFY,
337 currentHostData.tlsVerify ());
338 if (!currentHostData.tlsPriority().isEmpty ())
339 pwmd_setopt (pwm->handle (), PWMD_OPTION_TLS_PRIORITY,
340 currentHostData.tlsPriority ().toUtf8 ().data ());
342 connect (pwm, SIGNAL (statusMessage (QString, void *)), this,
343 SLOT (slotStatusMessage (QString, void *)));
344 connect (pwm, SIGNAL (stateChanged (Pwmd::ConnectionState)), this,
345 SLOT (slotConnectionStateChanged (Pwmd::ConnectionState)));
346 connect (pwm, SIGNAL (commandResult (PwmdCommandQueueItem *, QString, unsigned, bool)), this, SLOT (slotCommandResult (PwmdCommandQueueItem *, QString, unsigned, bool)));
347 connect (pwm, SIGNAL (busy (int, bool)), this, SLOT (slotBusy (int, bool)));
348 connect (pwm, SIGNAL (knownHost (void *, const char *, const char *, size_t)), this, SLOT (slotKnownHostCallback (void *, const char *, const char *, size_t)));
350 /* Trigger the stateChanged() signal. */
351 pwm->reset (true, reset);
353 QString path = data.path ();
354 PwmdInquireData *inq = new PwmdInquireData (pwm->handle (), data.filename ());
355 if (reset)
356 pwm->connect (Pwmd::inquireCallback, inq, true);
358 if (reset || lastFile != data.filename () || pwm->state () != Pwmd::Opened)
359 pwm->open (Pwmd::inquireCallback, inq, true);
361 PwmdInquireData *cinq = new PwmdInquireData (path);
362 if (statusData)
363 delete statusData;
364 statusData = new QString (data.name ());
365 pwm->setStatusMessageData (statusData);
366 pwm->command (new PwmdCommandQueueItem (PwmdCmdIdGetContent, "GET", Pwmd::inquireCallback, cinq));
369 void
370 TrayIcon::slotBusy (int, bool b)
372 if (!b && waitingForClose && !pwm->queued ())
374 waitingForClose = false;
375 delete pwm;
376 pwm = nullptr;
380 void
381 TrayIcon::slotCommandResult (PwmdCommandQueueItem *item,
382 QString result, gpg_error_t rc, bool queued)
384 if (rc)
385 rotateIcon (true);
387 switch (item->id ())
389 case PwmdCmdIdInternalConnect:
390 case PwmdCmdIdInternalOpen:
391 case PwmdCmdIdInternalCloseFile:
392 break;
393 case PwmdCmdIdGetContent:
394 rotateIcon (true);
395 if (!rc)
396 shortCutFinalize (result);
397 break;
398 default:
399 break;
402 if (rc && !item->checkError (rc) && !queued)
403 showMessage (rc, !(item->id () == PwmdCmdIdGetContent));
405 item->setSeen ();
409 void
410 TrayIcon::refreshShortcuts ()
412 QMenu *current;
413 QList<QMenu *> list;
415 shortcutMenu->clear ();
416 shortcutMenu->addAction (tr ("Edit XML"), this, SLOT (slotEditPwmd ()));
417 shortcutMenu->addAction (tr ("Edit shortcuts"), this,
418 SLOT (slotEditShortcuts ()));
419 shortcutMenu->addSeparator ();
420 QSettings cfg ("qpwmc");
421 clipboardTimeout = cfg.value ("clipboardTimeout", 20).toInt ();
422 int size = cfg.beginReadArray ("shortcuts");
424 for (int i = 0; i < size; ++i)
426 current = shortcutMenu;
427 cfg.setArrayIndex (i);
428 QString sub = cfg.value("subMenu").toString();
429 QMenu *m;
431 if (!sub.isEmpty())
433 int n;
435 for (n = 0; n < list.count(); n++) {
436 m = list.at(n);
438 if (m->title() == sub) {
439 current = m;
440 break;
444 if (current == shortcutMenu)
446 current = m = new QMenu(sub, shortcutMenu);
447 list.append(m);
448 shortcutMenu->addMenu(m);
452 EditShortcut data = EditShortcut (cfg.value ("filename").toString (),
453 cfg.value ("name").toString (),
454 cfg.value ("path").toString (),
455 cfg.value ("socket").toString (),
456 cfg.value ("subMenu").toString ());
458 QAction *a = current->addAction (data.name ());
459 a->setData (QVariant::fromValue (data));
462 cfg.endArray ();
464 if (size)
465 shortcutMenu->addSeparator ();
467 shortcutMenu->addAction (tr ("Clear clipboard"), this,
468 SLOT (slotClearClipboardReal ()));
469 shortcutMenu->addAction (tr ("About"), this, SLOT (slotAbout ()));
470 shortcutMenu->addAction (tr ("Quit"), this, SLOT (slotQuit ()));
471 list.clear();
474 void
475 TrayIcon::slotQuit ()
477 QApplication::quit ();
480 void
481 TrayIcon::slotSystemTrayActivated (QSystemTrayIcon::ActivationReason n)
483 if (n == QSystemTrayIcon::DoubleClick)
484 slotEditPwmd ();
487 void
488 TrayIcon::slotAbout ()
490 QMessageBox::about (0, "QPwmc",
491 QString (tr ("QPwmc version %1\nlibpwmd version %2\n\n"
492 "Copyright 2014-2023 Ben Kibbey <bjk@luxsci.net>\n"
493 "https://gitlab.com/bjk/qpwmc")).
494 arg (QPWMC_VERSION, pwmd_version ()));
497 // Don't check that QPwmc owns the content. Forces clearing the clipboard.
498 void
499 TrayIcon::slotClearClipboardReal ()
501 QClipboard *c = QApplication::clipboard();
503 if (c->supportsSelection ())
504 c->setText("", QClipboard::Selection);
505 c->setText("", QClipboard::Clipboard);
508 void
509 TrayIcon::slotClearClipboard ()
511 QClipboard *c = QApplication::clipboard();
513 clipboardTimer->stop();
514 if (c->supportsSelection ())
515 c->clear (QClipboard::Selection);
516 c->clear (QClipboard::Clipboard);
519 void
520 TrayIcon::slotEditShortcuts ()
522 EditShortcutDialog *d = new EditShortcutDialog ();
523 bool b = d->exec ();
525 delete d;
527 if (b)
528 refreshShortcuts ();
531 void
532 TrayIcon::slotEditPwmd ()
534 startStopTimer (false);
535 delete pwm;
536 pwm = nullptr;
538 Pwmd *spwm = new Pwmd (0, 0, 0, this);
539 QString result;
540 spwm->spawnEditor (result, 0, 0, 0, false);
541 delete spwm;