2 Copyright (C) 2018-2024 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
25 #include <QMessageBox>
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
*)));
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 ()));
59 lingerTimer
= new QTimer (this);
60 connect (lingerTimer
, SIGNAL (timeout ()), SLOT (slotLinger ()));
68 TrayIcon::~TrayIcon ()
72 delete clipboardTimer
;
78 TrayIcon::startStopTimer (bool start
)
82 setIcon (QIcon (":icons/trayicon-linger.svg"));
83 lingerTimer
->start (1000);
88 setIcon (QIcon (":icons/trayicon.svg"));
93 TrayIcon::slotLinger ()
96 if (pwm
&& lingerRemaining
> 0)
99 startStopTimer (false);
105 TrayIcon::slotRotateIcon ()
111 TrayIcon::rotateIcon (bool reset
)
113 static float rotate
= 1;
114 static float 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."));
127 QPixmap p
= QIcon (":icons/trayicon.svg").pixmap (size
);
130 if (pwm
->state () == Pwmd::Init
|| pwm
->state () == Pwmd::Connecting
)
132 if (rotate
+ mod
< 0.0)
134 else if (rotate
+ mod
> 1)
138 t
.scale (rotate
, rotate
);
142 if (rotate
+ RotateStepDegrees
> 360)
145 rotate
+= RotateStepDegrees
;
149 QPixmap pp
= p
.transformed (t
);
150 setIcon (QIcon (pp
));
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
)
160 if (supportsMessages())
162 QSystemTrayIcon::showMessage(tr ("There was an error while communicating with pwmd."),
163 Pwmd::errorString(rc
, pwm
));
166 Pwmd::showError (rc
, pwm
);
169 if (reset
|| !lingerRemaining
)
171 startStopTimer (false);
172 waitingForClose
= true;
177 TrayIcon::slotConnectionStateChanged (Pwmd::ConnectionState s
)
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);
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..."));
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..."));
213 setToolTip (tr ("Retrieving content..."));
223 TrayIcon::shortCutFinalize (const QString
&result
)
225 QClipboard
*c
= QApplication::clipboard ();
227 c
->setText (result
, QClipboard::Selection
);
231 if (clipboardTimeout
)
232 clipboardTimer
->start (clipboardTimeout
* 1000);
234 lingerRemaining
= lingerTime
;
237 startStopTimer (true);
238 if (closeFile
&& pwm
->state () == Pwmd::Opened
)
250 TrayIcon::slotKnownHostCallback (void *data
, const char *host
, const char *key
,
253 gpg_error_t rc
= Pwmd::knownHostPrompt (data
, host
, key
, len
);
254 emit
knownHostRc (rc
);
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 ();
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
);
278 m
.setInformativeText (desc
);
279 m
.setIcon (QMessageBox::Information
);
286 TrayIcon::slotShortCut (QAction
* a
)
288 EditShortcut data
= a
->data ().value
< EditShortcut
> ();
289 if (data
.name ().isEmpty ())
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);
304 if (PwmdRemoteHost::fillRemoteHost (data
.socket (), currentHostData
))
307 if (currentHostData
!= lastHostData
)
312 if (data
.socket () != lastSocket
)
320 pwm
= new Pwmd (data
.filename (), "qpwmc", 0, this);
325 pwm
->setSocket (PwmdRemoteHost::socketUrl (currentHostData
));
326 pwm
->setConnectParameters (currentHostData
.socketArgs ());
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 ());
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
);
364 statusData
= new QString (data
.name ());
365 pwm
->setStatusMessageData (statusData
);
366 pwm
->command (new PwmdCommandQueueItem (PwmdCmdIdGetContent
, "GET", Pwmd::inquireCallback
, cinq
));
370 TrayIcon::slotBusy (int, bool b
)
372 if (!b
&& waitingForClose
&& !pwm
->queued ())
374 waitingForClose
= false;
381 TrayIcon::slotCommandResult (PwmdCommandQueueItem
*item
,
382 QString result
, gpg_error_t rc
, bool queued
)
389 case PwmdCmdIdInternalConnect
:
390 case PwmdCmdIdInternalOpen
:
391 case PwmdCmdIdInternalCloseFile
:
393 case PwmdCmdIdGetContent
:
396 shortCutFinalize (result
);
402 if (rc
&& !item
->checkError (rc
) && !queued
)
403 showMessage (rc
, !(item
->id () == PwmdCmdIdGetContent
));
410 TrayIcon::refreshShortcuts ()
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();
435 for (n
= 0; n
< list
.count(); n
++) {
438 if (m
->title() == sub
) {
444 if (current
== shortcutMenu
)
446 current
= m
= new QMenu(sub
, shortcutMenu
);
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
));
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 ()));
475 TrayIcon::slotQuit ()
477 QApplication::quit ();
481 TrayIcon::slotSystemTrayActivated (QSystemTrayIcon::ActivationReason n
)
483 if (n
== QSystemTrayIcon::DoubleClick
)
488 TrayIcon::slotAbout ()
490 QMessageBox::about (0, "QPwmc",
491 QString (tr ("QPwmc version %1\nlibpwmd version %2\n\n"
492 "Copyright 2014-2024 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.
499 TrayIcon::slotClearClipboardReal ()
501 QClipboard
*c
= QApplication::clipboard();
503 if (c
->supportsSelection ())
504 c
->setText("", QClipboard::Selection
);
505 c
->setText("", QClipboard::Clipboard
);
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
);
520 TrayIcon::slotEditShortcuts ()
522 EditShortcutDialog
*d
= new EditShortcutDialog ();
532 TrayIcon::slotEditPwmd ()
534 startStopTimer (false);
538 Pwmd
*spwm
= new Pwmd (0, 0, 0, this);
540 spwm
->spawnEditor (result
, 0, 0, 0, false);