Move getStatus and getDeviceNumber to qcdemu.cpp. It doesn't make sense to keep them...
[qcdemu:qcdemu.git] / qcdemu.cpp
1 /*
2     QCDemu, a Qt4-based frontend for CDemu 1.2+
3     Copyright (C) 2008-2010 Michał "mziab" Ziąbkowski
4
5     QCDemu is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9
10     QCDemu is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with QCDemu.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "qcdemu.h"
20
21 // default constructor
22 QCDemu::QCDemu(QWidget * parent):QWidget(parent)
23 {
24         loadSettings();
25         dbusConnect();
26
27         // create menu and systray icon
28         menu = new QMenu(this);
29         tray = new QSystemTrayIcon(this);
30
31         loadRecent();
32
33         // check if any devices are detected
34         if (getDeviceNumber()==0)
35         {
36                         // if auto-detection fails, restore the old setting and issue an error
37                         if (!guessBus())
38                                 QMessageBox::critical(this, tr("Error"), tr("No CDemu devices detected. "
39                                 "Maybe you forgot to start the CDemu daemon?"));
40         }
41         else if (!daemonIsNewEnough())
42         {
43                 QMessageBox::critical(this, tr("Error"), tr("Your CDemu daemon is too old. "
44                 "Please upgrade to v1.2.0+."));
45         }
46
47         // left click action
48         connect(tray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this,
49                 SLOT(promptMount(QSystemTrayIcon::ActivationReason)));
50
51         // the finishing touches
52         updateActions();
53         tray->setContextMenu(menu);
54         tray->show();
55         bus->connect("net.sf.cdemu.CDEMUD_Daemon", "/CDEMUD_Daemon", "net.sf.cdemu.CDEMUD_Daemon",
56                         "DeviceStatusChanged", this, SLOT(deviceChange(int)));
57         bus->connect("net.sf.cdemu.CDEMUD_Daemon", "/CDEMUD_Daemon", "net.sf.cdemu.CDEMUD_Daemon",
58                         "DaemonStarted", this, SLOT(daemonStarted()));
59         bus->connect("net.sf.cdemu.CDEMUD_Daemon", "/CDEMUD_Daemon", "net.sf.cdemu.CDEMUD_Daemon",
60                         "DaemonStopped", this, SLOT(daemonStopped()));
61 }
62
63 // leaner constructor for handling command line
64 QCDemu::QCDemu(const QStringList &filenames)
65 {
66         loadSettings();
67         loadRecent();
68         dbusConnect();
69
70         // warn if no devices detected
71         device_number=getDeviceNumber();
72
73         if (device_number==0)
74         {
75                 // if auto-detection fails, restore the old setting and issue an error
76                 if (!guessBus())
77                         QMessageBox::critical(this, tr("Error"), tr("No CDemu devices detected. "
78                         "Maybe you forgot to start the CDemu daemon?"));
79         }
80
81         // try to mount given images
82         mount(filenames);
83
84 }
85
86 // initialize DBus connection
87 void QCDemu::dbusConnect()
88 {
89         const QDBusConnection busType = (useSystemBus) ? QDBusConnection::systemBus() : QDBusConnection::sessionBus();
90
91         bus = new QDBusConnection(busType);
92         iface = new QDBusInterface( "net.sf.cdemu.CDEMUD_Daemon", "/CDEMUD_Daemon",
93                                 "net.sf.cdemu.CDEMUD_Daemon", busType );
94         
95         if(!bus->isConnected())
96         {
97                 QMessageBox::critical(this, tr("Error"), tr("Couldn't access DBus. "
98                                         "Please check your system configuration."));
99                 qFatal("Couldn't access DBus. Please check your system configuration.");
100         }
101 }
102
103 // load settings
104 void QCDemu::loadSettings()
105 {
106         QSettings settings("mziab", "qcdemu");
107         useSystemBus=settings.value("useSystemBus", true).toBool();
108         showNotifications=settings.value("showNotifications", true).toBool();
109         lastDir=settings.value("lastDir", ".").toString();
110
111         if (!QFile(lastDir).exists())
112                 lastDir=".";    
113 }
114
115 // load recent images
116 void QCDemu::loadRecent()
117 {
118         QSettings settings("mziab", "qcdemu");
119         recentFiles=settings.value("recentFiles").toStringList();
120
121         // just in case someone tampered with the files
122         while (recentFiles.count()>5)
123                 recentFiles.removeLast();
124 }
125
126 // flush settings on exit
127 QCDemu::~QCDemu()
128 {
129         QSettings settings("mziab", "qcdemu");
130         settings.setValue("useSystemBus", useSystemBus);
131         settings.setValue("showNotifications", showNotifications);
132         if (lastDir!=".")
133                 settings.setValue("lastDir", lastDir);
134
135         settings.setValue("recentFiles", recentFiles);
136
137         // clean up some variables
138         delete bus;
139         delete iface;
140 }
141
142 // run when CDemu is stopped
143 void QCDemu::daemonStopped()
144 {
145         showInfo(tr("CDemu was stopped"));
146         updateActions();
147 }
148
149 // run when CDemu is started
150 void QCDemu::daemonStarted()
151 {
152         showInfo(tr("CDemu has been started"));
153         updateActions();
154 }
155
156
157 // show information baloon in systray
158 void QCDemu::showInfo(const QString &message)
159 {
160         tray->showMessage(tr("QCDemu"), message, QSystemTrayIcon::Information, 5000);
161 }
162
163 // show error baloon in systray
164 void QCDemu::showError(const QString &message)
165 {
166         tray->showMessage(tr("QCDemu"), message, QSystemTrayIcon::Critical, 5000);
167 }
168
169 // run when a device changes status
170 void QCDemu::deviceChange(const int device)
171 {
172         if (showNotifications)
173         {
174                 QString msg = (isMounted(device)) ? QString(tr("Device %1 has been mounted.")).arg(device) :
175                         QString(tr("Device %1 has been unmounted.")).arg(device);
176                 showInfo(msg);
177         }
178
179         updateActions();
180 }
181
182 // mounts the image in the first available device
183 bool QCDemu::mount(const QStringList &filenames)
184 {
185         for (unsigned int i=0; i<device_number; i++)
186         {
187                 if (!isMounted(i))
188                         return mount(i, filenames);
189         }
190
191         QMessageBox::critical(this, tr("Error"), tr("No free CDemu devices detected to mount on."));
192         return(false);
193 }
194
195 // prompts for path and mounts image
196 void QCDemu::promptMount(const int device)
197 {
198         QStringList filenames=getFileNames();
199
200         if (filenames.isEmpty())
201                 return;
202         
203         mount(device, filenames);
204 }
205
206 // prompts for path and mounts images in the first available device (on left click)
207 void QCDemu::promptMount(const QSystemTrayIcon::ActivationReason &reason)
208 {
209         if (reason!=QSystemTrayIcon::Trigger || device_number==0)
210                 return;
211         
212         QStringList filenames=getFileNames();
213
214         if (filenames.isEmpty())
215                 return;
216         
217         mount(filenames);
218 }
219
220 // prompt for image name(s)
221 QStringList QCDemu::getFileNames()
222 {
223         if (!QFile(lastDir).exists())
224                 lastDir=".";
225         
226         QStringList filenames=QFileDialog::getOpenFileNames(this, tr("Select image to mount"), lastDir,
227         tr("All images (*.iso *.img *.wav *.cue *.toc *.b5t *.b6t *.cdi *.nrg *.mds *.xmd *.ccd *.c2d *.cif *.daa);;"
228         "ISO images (*.iso *.img *.wav);;"
229         "CUE images (*.cue);;"
230         "TOC files (*.toc);;"
231         "BlindWrite 5/6 images (*.b5t *.b6t);;"
232         "DiscJuggler images (*.cdi);;"
233         "Nero Burning Rom images (*.nrg);;"
234         "Alcohol 120\% images (*.mds *.xmd);;"
235         "CloneCD images (*.ccd);;"
236         "Easy Media Creator images (*.c2d);;"
237         "Easy CD Creator <= v5.0 images (*.cif);;"
238         "PowerISO direct access archives (*.daa)"));
239         return filenames;
240 }
241
242 // try to mount given images using given device
243 bool QCDemu::mount(const int device, const QStringList &filenames)
244 {
245         // check whether the files are readable
246         foreach(QString file, filenames)
247         {
248                 if (!QFileInfo(file).isReadable())
249                 {
250                         QMessageBox::critical(this, tr("Error"), tr("Couldn't open %1").arg(file));
251                         return false;
252                 }
253         }
254
255         // try to mount
256         QDBusMessage reply = iface->call("DeviceLoad", device, filenames, QMap<QString, QVariant>());
257
258         if (reply.type()!=QDBusMessage::ErrorMessage)
259         {
260                 loadRecent();
261                 
262                 lastDir=QFileInfo(filenames.first()).absolutePath();
263                 if (!recentFiles.contains(filenames.first()))
264                         recentFiles.prepend(filenames.first());
265
266                 while (recentFiles.count()>5)
267                         recentFiles.removeLast();
268
269                 return true;
270         }
271         else
272                 QMessageBox::critical(this, tr("Error"), reply.errorMessage());
273
274         return false;
275 }
276
277 // unmounts given device
278 void QCDemu::unmount(const int device)
279 {
280         QDBusMessage reply = iface->call("DeviceUnload", device);
281
282         if (reply.type()==QDBusMessage::ErrorMessage)
283                 QMessageBox::critical(this, tr("Error"), reply.errorMessage());
284 }
285
286 // run when an action was clicked
287 void QCDemu::clicked()
288 {
289         QAction *action = qobject_cast<QAction *>(sender());
290         if (action)
291         {
292                 unsigned int device=action->data().toInt();
293
294                 if (isMounted(device))
295                         unmount(device);
296                 else
297                         promptMount(device);
298         }
299
300 }
301
302 // run when a recent file entry was clicked
303 void QCDemu::recentClicked()
304 {
305         QAction *action = qobject_cast<QAction *>(sender());
306         if (action)
307         {
308                 QString file=action->data().toString();
309                 if(!mount(QStringList(file)))
310                 {
311                         recentFiles.removeAll(file);
312                         updateActions();
313                 }
314         }
315
316 }
317
318 // checks whether a device is mounted or not
319 bool QCDemu::isMounted(const int device)
320 {
321         QDBusMessage reply = iface->call("DeviceGetStatus", device);
322
323         return(reply.arguments().first().toBool());
324 }
325
326 // query all devices and create actions
327 void QCDemu::updateActions()
328 {
329         // remove all actions in case it's not the first run
330         menu->clear();
331
332         device_number=getDeviceNumber();
333         mountedNumber=0;
334
335         if (device_number==0)
336         {
337                 // add Inactive action
338                 inactive = new QAction(tr("CDemu not running"),this);
339                 menu->addAction(inactive);
340         }
341         else
342         {
343                 for (unsigned int i=0; i<device_number; i++)
344                 {
345                         QVariantList status = getStatus(i);
346                         QString text=QString(tr("Device %1: ")).arg(i);
347
348                         if (status.first().toBool())
349                         {
350                                 QString file = status[1].toString();
351
352                                 // get base filename
353                                 text+=QFileInfo(file).fileName();
354                                 mountedNumber++;
355                         }
356                         else
357                         {
358                                 text+=tr("Not mounted");
359                         }
360
361                         QAction *a = new QAction(text, this);
362                         a->setData(i);
363                         menu->addAction(a);
364                         connect(a, SIGNAL(triggered()), this, SLOT(clicked()));
365                 }
366         }
367
368         // add config
369         menu->addSeparator();
370         config = new QAction(tr("Use system bus"), this);
371         config->setCheckable(true);
372         config->setChecked(useSystemBus);
373         connect(config, SIGNAL(toggled(bool)), this, SLOT(selectBus(bool)));
374         menu->addAction(config);
375
376         notify = new QAction(tr("Show notifications"), this);
377         notify->setCheckable(true);
378         notify->setChecked(showNotifications);
379         connect(notify, SIGNAL(toggled(bool)), this, SLOT(setNotify(bool)));
380         menu->addAction(notify);
381
382         // add recent files
383         if (!recentFiles.isEmpty())
384         {
385                 recent = new QMenu(this);
386                 recent->setTitle(tr("Recent files"));
387                 QAction *clear = new QAction(tr("Purge list"), this);
388                 recent->addAction(clear);
389                 recent->addSeparator();
390                 connect(clear, SIGNAL(triggered()), this, SLOT(recentClear()));
391
392                 for (int i=0; i<recentFiles.count(); i++)
393                 {
394                         QAction *a = new QAction(QFileInfo(recentFiles[i]).fileName(), this);
395                         a->setData(recentFiles[i]);
396                         recent->addAction(a);
397                         connect(a, SIGNAL(triggered()), this, SLOT(recentClicked()));
398                 }
399
400                 menu->addMenu(recent);
401         }
402
403         // add About
404         menu->addSeparator();
405         about = new QAction(tr("About..."), this);
406         about->setCheckable(false);
407         connect(about, SIGNAL(triggered()), this, SLOT(showAbout()));
408         menu->addAction(about);
409
410         // add quit action
411         quit = new QAction(tr("Exit"), this);
412         menu->addAction(quit);
413         connect(quit, SIGNAL(activated()), qApp, SLOT(quit()));
414
415         // set proper icon based on state
416         QString icon_name = (mountedNumber>0) ? "mount" : "unmount";
417         tray->setIcon(QIcon(":/icons/icons/cdrom_"+icon_name+".png"));
418
419         // set proper toolkit
420         QString tooltip_text = (device_number>0) ? QString(tr("%1 available device(s)")).arg(device_number) :
421                 tr("CDemu not running");
422         tray->setToolTip("QCDemu: "+tooltip_text);
423 }
424
425 // change bus to be used
426 void QCDemu::selectBus(const bool systemBus)
427 {
428         useSystemBus=systemBus;
429
430         delete bus;
431         delete iface;
432
433         dbusConnect();
434         updateActions();
435 }
436
437 // auto-detected if the other bus should be used
438 bool QCDemu::guessBus()
439 {
440         useSystemBus = !useSystemBus;
441
442         delete bus;
443         delete iface;
444
445         dbusConnect();
446
447         // clean up on failure
448         if (getDeviceNumber() == 0)
449                 useSystemBus = !useSystemBus;
450
451         return getDeviceNumber();
452 }
453
454
455 // say your name
456 void QCDemu::showAbout()
457 {
458         setWindowIcon(QIcon(":/icons/icons/cdrom_mount.png"));
459         QMessageBox::about(this, tr("About QCDemu"), tr("CDemu v" VERSION " by mziab"));
460 }
461
462 // purge recent file list
463 void QCDemu::recentClear()
464 {
465         recentFiles.clear();
466         updateActions();
467 }
468
469 // check if cdemu daemon is new enough :)
470 bool QCDemu::daemonIsNewEnough()
471 {
472         QDBusMessage reply = iface->call("GetDaemonVersion");
473         if (reply.type()==QDBusMessage::ErrorMessage)
474                 return false;
475         else
476         {
477                 QString minor_ver = reply.arguments().first().toString();
478                 minor_ver = minor_ver.mid(2,1);
479
480                 return (minor_ver.toInt() >= 2);
481         }
482 }
483
484 QVariantList getStatus(const int device)
485 {
486         QDBusMessage reply = iface->call("DeviceGetStatus", device);
487         return(reply.arguments());
488 }
489
490 unsigned int getDeviceNumber()
491 {
492         QDBusMessage reply = iface->call( "GetNumberOfDevices");
493         return (reply.type()!=QDBusMessage::ErrorMessage) ? (reply.arguments().first().toInt()) : 0;
494 }
495