1 /***************************************************************************
2 *                                                                         *
3 *   This program is free software; you can redistribute it and/or modify  *
4 *   it under the terms of the GNU General Public License as published by  *
5 *   the Free Software Foundation; either version 3 of the License, or     *
6 *   (at your option) any later version.                                   *
7 *                                                                         *
8 ***************************************************************************/
9 
10 #include "Notification.h"
11 
12 #include <QMenu>
13 #include <QList>
14 #include <QSound>
15 #include <QFile>
16 
17 #include "WulforUtil.h"
18 #include "WulforSettings.h"
19 #include "MainWindow.h"
20 #include "ShellCommandRunner.h"
21 #include "Settings.h"
22 
getBitPos(unsigned eventId)23 static int getBitPos(unsigned eventId){
24     for (unsigned i = 0; i < (sizeof(unsigned)*8); i++){
25         if ((eventId >> i) == 1U)
26             return static_cast<int>(i);
27     }
28 
29     return -1;
30 }
31 
Notification(QObject * parent)32 Notification::Notification(QObject *parent) :
33     QObject(parent), tray(NULL), notify(NULL), suppressSnd(false), suppressTxt(false)
34 {
35     switchModule(static_cast<unsigned>(WIGET(WI_NOTIFY_MODULE)));
36 
37     checkSystemTrayCounter = 0;
38 
39     reloadSounds();
40 
41     enableTray(WBGET(WB_TRAY_ENABLED));
42 
43     connect(MainWindow::getInstance(), SIGNAL(notifyMessage(int,QString,QString)), this, SLOT(showMessage(int,QString,QString)), Qt::QueuedConnection);
44 }
45 
~Notification()46 Notification::~Notification(){
47     enableTray(false);
48     delete notify;
49 }
50 
enableTray(bool enable)51 void Notification::enableTray(bool enable){
52     if (!enable){
53         if (tray)
54             tray->hide();
55 
56         delete tray;
57 
58         tray = NULL;
59 
60 #if defined(Q_WS_MAC)
61         MainWindow::getInstance()->setUnload(false);
62 #else // defined(Q_WS_MAC)
63         MainWindow::getInstance()->setUnload(true);
64 #endif // defined(Q_WS_MAC)
65 
66         //WBSET(WB_TRAY_ENABLED, false);
67     }
68     else {
69         delete tray;
70 
71         tray = NULL;
72 
73         if (!QSystemTrayIcon::isSystemTrayAvailable() && checkSystemTrayCounter < 12){
74             QTimer *timer = new QTimer(this);
75             timer->setSingleShot(true);
76             timer->setInterval(5000);
77 
78             connect(timer, SIGNAL(timeout()), this, SLOT(slotCheckTray()));
79 
80             timer->start();
81 
82             ++checkSystemTrayCounter;
83 
84             return;
85         }
86         else if (!QSystemTrayIcon::isSystemTrayAvailable()){
87             MainWindow::getInstance()->show();
88 
89             return;
90         }
91 
92         checkSystemTrayCounter = 0;
93 
94         tray = new QSystemTrayIcon(this);
95         tray->setIcon(WICON(WulforUtil::eiICON_APPL)
96                     .scaled(22, 22, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
97 
98         QMenu *menu = new QMenu(MainWindow::getInstance());
99         menu->setTitle("EiskaltDC++");
100 
101         QMenu *menuAdditional = new QMenu(tr("Additional"), MainWindow::getInstance());
102         QAction *actSuppressSnd = new QAction(tr("Suppress sound notifications"), menuAdditional);
103         QAction *actSuppressTxt = new QAction(tr("Suppress text notifications"), menuAdditional);
104 
105         actSuppressSnd->setCheckable(true);
106         actSuppressSnd->setChecked(false);
107 
108         actSuppressTxt->setCheckable(true);
109         actSuppressTxt->setChecked(false);
110 
111         menuAdditional->addActions(QList<QAction*>() << actSuppressTxt << actSuppressSnd);
112 
113         QAction *show_hide = new QAction(tr("Show/Hide window"), menu);
114         QAction *setup_speed_lim = new QAction(tr("Setup speed limits"), menu);
115         QAction *close_app = new QAction(tr("Exit"), menu);
116         QAction *sep = new QAction(menu);
117         sep->setSeparator(true);
118 
119         setup_speed_lim->setIcon(WICON(WulforUtil::eiSPEED_LIMIT_ON));
120         show_hide->setIcon(WICON(WulforUtil::eiHIDEWINDOW));
121         close_app->setIcon(WICON(WulforUtil::eiEXIT));
122 
123         connect(show_hide, SIGNAL(triggered()), this, SLOT(slotShowHide()));
124         connect(close_app, SIGNAL(triggered()), this, SLOT(slotExit()));
125         connect(tray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
126                 this, SLOT(slotTrayMenuTriggered(QSystemTrayIcon::ActivationReason)));
127         connect(actSuppressTxt, SIGNAL(triggered()), this, SLOT(slotSuppressTxt()));
128         connect(actSuppressSnd, SIGNAL(triggered()), this, SLOT(slotSuppressSnd()));
129         connect(setup_speed_lim, SIGNAL(triggered()), this, SLOT(slotShowSpeedLimits()));
130 
131         menu->addAction(show_hide);
132         menu->addAction(setup_speed_lim);
133         menu->addMenu(menuAdditional);
134         menu->addActions(QList<QAction*>() << sep << close_app);
135 
136         tray->setContextMenu(menu);
137 
138         tray->show();
139 
140         MainWindow::getInstance()->setUnload(false);
141 
142         //WBSET(WB_TRAY_ENABLED, true);
143     }
144 }
145 
switchModule(int m)146 void Notification::switchModule(int m){
147     Module t = static_cast<Module>(m);
148 
149     delete notify;
150 
151     if (t == QtNotify)
152         notify = new QtNotifyModule();
153 #ifdef DBUS_NOTIFY
154     else
155         notify = new DBusNotifyModule();
156 #else
157     else
158         notify = new QtNotifyModule();
159 #endif
160 }
161 
showMessage(int t,const QString & title,const QString & msg)162 void Notification::showMessage(int t, const QString &title, const QString &msg){
163     // On Mac OS X, the Growl notification system must be installed for this function to display messages.
164     if (WBGET(WB_NOTIFY_ENABLED) && !suppressTxt){
165         do {
166             if (title.isEmpty() || msg.isEmpty())
167                 break;
168 
169             if ((MainWindow::getInstance()->isActiveWindow() && !WBGET(WB_NOTIFY_SHOW_ON_ACTIVE)) ||
170             (!MainWindow::getInstance()->isActiveWindow() && MainWindow::getInstance()->isVisible() && !WBGET(WB_NOTIFY_SHOW_ON_VISIBLE)))
171                 break;
172 
173             if (!(static_cast<unsigned>(WIGET(WI_NOTIFY_EVENTMAP)) & static_cast<unsigned>(t)))
174                 break;
175 
176 #if defined(Q_WS_MAC)
177             qApp->setWindowIcon(WICON(WulforUtil::eiMESSAGE_TRAY_ICON));
178             qApp->alert(MainWindow::getInstance(), 0);
179 #else // defined(Q_WS_MAC)
180             if (tray && t == PM && (!MainWindow::getInstance()->isVisible() || WBGET(WB_NOTIFY_CH_ICON_ALWAYS))){
181                 tray->setIcon(WICON(WulforUtil::eiMESSAGE_TRAY_ICON));
182 
183                 if (MainWindow::getInstance()->isVisible())
184                     QApplication::alert(MainWindow::getInstance(), 0);
185             }
186 #endif // defined(Q_WS_MAC)
187 
188             if (notify)
189                 notify->showMessage(title, msg, tray);
190         } while (0);
191     }
192 
193     if (WBGET(WB_NOTIFY_SND_ENABLED) && !suppressSnd){
194         do {
195             if (!(static_cast<unsigned>(WIGET(WI_NOTIFY_SNDMAP)) & static_cast<unsigned>(t)))
196                 break;
197 
198             int sound_pos = getBitPos(static_cast<unsigned>(t));
199 
200             if (sound_pos >= 0 && sound_pos < sounds.size()){
201                 QString sound = sounds.at(sound_pos);
202 
203                 if (sound.isEmpty() || !QFile::exists(sound))
204                     break;
205 
206                 if (!WBGET(WB_NOTIFY_SND_EXTERNAL))
207                     QSound::play(sound);
208                 else {
209                     QString cmd = WSGET(WS_NOTIFY_SND_CMD);
210 
211                     if (cmd.isEmpty())
212                         break;
213 
214                     ShellCommandRunner *r = new ShellCommandRunner(cmd, QStringList() << sound, this);
215                     connect(r, SIGNAL(finished(bool,QString)), this, SLOT(slotCmdFinished(bool,QString)));
216 
217                     r->start();
218                 }
219             }
220         } while (0);
221     }
222 }
223 
setToolTip(const QString & DSPEED,const QString & USPEED,const QString & DOWN,const QString & UP)224 void Notification::setToolTip(const QString &DSPEED, const QString &USPEED, const QString &DOWN, const QString &UP){
225     if (!WBGET(WB_TRAY_ENABLED) || !tray)
226         return;
227 
228 #if defined(Q_WS_X11)
229     QString out = tr("<b>Speed</b><br/>"
230                      "Download: <font_color=\"green\">%1</font> "
231                      "Upload: <font_color=\"red\">%2</font><br/>"
232                      "<b>Statistics</b><br/>"
233                      "Downloaded: <font_color=\"green\">%3</font> "
234                      "Uploaded: <font_color=\"red\">%4</font>")
235             .arg(DSPEED).arg(USPEED).arg(DOWN).arg(UP);
236 
237     out.replace(" ","&nbsp;");
238     out.replace("_"," ");
239 #else
240     QString out = tr("Speed\n"
241                      "Download: %1 "
242                      "Upload: %2\n"
243                      "Statistics\n"
244                      "Downloaded: %3 "
245                      "Uploaded: %4")
246             .arg(DSPEED).arg(USPEED).arg(DOWN).arg(UP);
247 #endif
248 
249     tray->setToolTip(out);
250 }
251 
reloadSounds()252 void Notification::reloadSounds(){
253     QString encoded = WSGET(WS_NOTIFY_SOUNDS);
254     QString decoded = QByteArray::fromBase64(encoded.toUtf8());
255 
256     sounds = decoded.split("\n");
257 }
258 
slotExit()259 void Notification::slotExit(){
260     if (WBGET(WB_EXIT_CONFIRM))
261         MainWindow::getInstance()->show();
262 
263     MainWindow::getInstance()->setUnload(true);
264     MainWindow::getInstance()->close();
265 }
266 
slotShowHide()267 void Notification::slotShowHide(){
268     MainWindow *MW = MainWindow::getInstance();
269 
270     if (MW->isVisible()){
271 #if defined(Q_WS_WIN)
272         MW->hide();
273 #elif defined(Q_WS_MAC)
274         if (!MW->isActiveWindow()){
275             MW->activateWindow();
276             MW->raise();
277         }
278 #else // Linux, FreeBSD, Haiku, Hurd
279         if (MW->isMinimized())
280             MW->show();
281 
282         if (!MW->isActiveWindow()){
283             MW->activateWindow();
284             MW->raise();
285         }
286         else {
287             MW->hide();
288         }
289 #endif
290     }
291     else{
292         MW->show();
293         MW->raise();
294 #if defined(Q_WS_MAC)
295         MW->redrawToolPanel();
296 #else // defined(Q_WS_MAC)
297         if (tray)
298             MW->redrawToolPanel();
299 #endif // defined(Q_WS_MAC)
300     }
301 }
302 
slotTrayMenuTriggered(QSystemTrayIcon::ActivationReason r)303 void Notification::slotTrayMenuTriggered(QSystemTrayIcon::ActivationReason r){
304     if (r == QSystemTrayIcon::Trigger)
305         slotShowHide();
306 }
307 
slotShowSpeedLimits()308 void Notification::slotShowSpeedLimits(){
309     MainWindow::getInstance()->show();
310     MainWindow::getInstance()->raise();
311 
312     Settings settings;
313     settings.navigate(Settings::Page::Connection, 1);
314 
315     settings.exec();
316 }
317 
slotCmdFinished(bool,QString)318 void Notification::slotCmdFinished(bool, QString){
319     ShellCommandRunner *r = reinterpret_cast<ShellCommandRunner*>(sender());
320 
321     r->exit(0);
322     r->wait(100);
323 
324     if (r->isRunning())
325         r->terminate();
326 
327     delete r;
328 }
329 
slotCheckTray()330 void Notification::slotCheckTray(){
331     QTimer *timer = qobject_cast<QTimer*>(sender());
332 
333     if (!timer)
334         return;
335 
336     enableTray(true);
337 
338     timer->deleteLater();
339 }
340 
slotSuppressTxt()341 void Notification::slotSuppressTxt(){
342     QAction *act = qobject_cast<QAction*>(sender());
343     if (act)
344         setSuppressTxt(act->isChecked());
345 }
346 
slotSuppressSnd()347 void Notification::slotSuppressSnd(){
348     QAction *act = qobject_cast<QAction*>(sender());
349     if (act)
350         setSuppressSnd(act->isChecked());
351 }
352 
resetTrayIcon()353 void Notification::resetTrayIcon(){
354     if (tray)
355         tray->setIcon(WICON(WulforUtil::eiICON_APPL)
356                     .scaled(22, 22, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
357 }
358 
showMessage(const QString & title,const QString & msg,QObject * obj)359 void QtNotifyModule::showMessage(const QString &title, const QString &msg, QObject *obj) {
360     QSystemTrayIcon *tray = reinterpret_cast<QSystemTrayIcon*>(obj);
361 
362     if (tray)
363         tray->showMessage(title, ((msg.length() > 400)? (msg.left(400) + "...") : msg), QSystemTrayIcon::Information, 5000);
364 }
365 
366 #ifdef DBUS_NOTIFY
showMessage(const QString & title,const QString & msg,QObject *)367 void DBusNotifyModule::showMessage(const QString &title, const QString &msg, QObject *) {
368     QDBusInterface iface("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications", QDBusConnection::sessionBus());
369 
370     QVariantList args;
371     args << QString("EiskaltDC++");
372     args << QVariant(QVariant::UInt);
373     args << QVariant(WulforUtil::getInstance()->getIconsPath() + "/" + "icon_appl_big.png");
374     args << QString(title);
375     args << QString(msg);
376     args << QStringList();
377     args << QVariantMap();
378     args << 5000;
379 
380     iface.callWithArgumentList(QDBus::NoBlock, "Notify", args);
381 }
382 #endif
383