1 /*
2 
3     Copyright (C) 2013  Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
4 
5     This program 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 2 of the License, or
8     (at your option) any later version.
9 
10     This program 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 along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 
20 
21 #include "application.h"
22 #include "mainwindow.h"
23 #include "desktopwindow.h"
24 #include <QDBusConnection>
25 #include <QDBusInterface>
26 #include <QDir>
27 #include <QVector>
28 #include <QLocale>
29 #include <QLibraryInfo>
30 #include <QPixmapCache>
31 #include <QFile>
32 #include <QMessageBox>
33 #include <QCommandLineParser>
34 #include <QSocketNotifier>
35 #include <QScreen>
36 #include <QWindow>
37 #include <QFileSystemWatcher>
38 
39 #include <gio/gio.h>
40 #include <sys/socket.h>
41 
42 #include <libfm-qt/mountoperation.h>
43 #include <libfm-qt/filesearchdialog.h>
44 #include <libfm-qt/core/terminal.h>
45 #include <libfm-qt/core/bookmarks.h>
46 #include <libfm-qt/core/folderconfig.h>
47 
48 #include "applicationadaptor.h"
49 #include "preferencesdialog.h"
50 #include "desktoppreferencesdialog.h"
51 #include "autorundialog.h"
52 #include "launcher.h"
53 #include "xdgdir.h"
54 #include "connectserverdialog.h"
55 
56 #include <X11/Xlib.h>
57 
58 
59 namespace PCManFM {
60 
61 static const char* serviceName = "org.pcmanfm.PCManFM";
62 static const char* ifaceName = "org.pcmanfm.Application";
63 
styleHint(StyleHint hint,const QStyleOption * option,const QWidget * widget,QStyleHintReturn * returnData) const64 int ProxyStyle::styleHint(StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const {
65     Application* app = static_cast<Application*>(qApp);
66     if(hint == QStyle::SH_ItemView_ActivateItemOnSingleClick) {
67         if (app->settings().singleClick()) {
68             return true;
69         }
70         // follow the style
71         return QCommonStyle::styleHint(hint,option,widget,returnData);
72     }
73     return QProxyStyle::styleHint(hint, option, widget, returnData);
74 }
75 
Application(int & argc,char ** argv)76 Application::Application(int& argc, char** argv):
77     QApplication(argc, argv),
78     libFm_(),
79     settings_(),
80     profileName_(QStringLiteral("default")),
81     daemonMode_(false),
82     enableDesktopManager_(false),
83     desktopWindows_(),
84     preferencesDialog_(),
85     editBookmarksialog_(),
86     volumeMonitor_(nullptr),
87     userDirsWatcher_(nullptr),
88     lxqtRunning_(false) {
89 
90     // mainly used to disable tab DND outside X11 because Wayland has problem with QDrag
91     isX11_ = QGuiApplication::platformName() == QStringLiteral("xcb");
92 
93     argc_ = argc;
94     argv_ = argv;
95 
96     setApplicationVersion(QStringLiteral(PCMANFM_QT_VERSION));
97 
98     // QDBusConnection::sessionBus().registerObject("/org/pcmanfm/Application", this);
99     QDBusConnection dbus = QDBusConnection::sessionBus();
100     if(dbus.registerService(QLatin1String(serviceName))) {
101         // we successfully registered the service
102         isPrimaryInstance = true;
103         setStyle(new ProxyStyle());
104         //desktop()->installEventFilter(this);
105 
106         new ApplicationAdaptor(this);
107         dbus.registerObject(QStringLiteral("/Application"), this);
108 
109         connect(this, &Application::aboutToQuit, this, &Application::onAboutToQuit);
110         // aboutToQuit() is not signalled on SIGTERM, install signal handler
111         installSigtermHandler();
112 
113         // Check if LXQt Session is running. LXQt has it's own Desktop Folder
114         // editor. We just hide our editor when LXQt is running.
115         QDBusInterface* lxqtSessionIface = new QDBusInterface(
116             QStringLiteral("org.lxqt.session"),
117             QStringLiteral("/LXQtSession"));
118         if(lxqtSessionIface) {
119             if(lxqtSessionIface->isValid()) {
120                 lxqtRunning_ = true;
121                 userDesktopFolder_ = XdgDir::readDesktopDir();
122                 initWatch();
123             }
124             delete lxqtSessionIface;
125             lxqtSessionIface = nullptr;
126         }
127     }
128     else {
129         // an service of the same name is already registered.
130         // we're not the first instance
131         isPrimaryInstance = false;
132     }
133 }
134 
~Application()135 Application::~Application() {
136     //desktop()->removeEventFilter(this);
137 
138     if(volumeMonitor_) {
139         g_signal_handlers_disconnect_by_func(volumeMonitor_, gpointer(onVolumeAdded), this);
140         g_object_unref(volumeMonitor_);
141     }
142 
143     // if(enableDesktopManager_)
144     //   removeNativeEventFilter(this);
145 }
146 
initWatch()147 void Application::initWatch() {
148     QFile file_(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/user-dirs.dirs"));
149     if(! file_.open(QIODevice::ReadOnly | QIODevice::Text)) {
150         qDebug() << Q_FUNC_INFO << "Could not read: " << userDirsFile_;
151         userDirsFile_ = QString();
152     }
153     else {
154         userDirsFile_ = file_.fileName();
155     }
156 
157     userDirsWatcher_ = new QFileSystemWatcher(this);
158     userDirsWatcher_->addPath(userDirsFile_);
159     connect(userDirsWatcher_, &QFileSystemWatcher::fileChanged, this, &Application::onUserDirsChanged);
160 }
161 
parseCommandLineArgs()162 bool Application::parseCommandLineArgs() {
163     bool keepRunning = false;
164     QCommandLineParser parser;
165     parser.addHelpOption();
166     parser.addVersionOption();
167 
168     QCommandLineOption profileOption(QStringList() << QStringLiteral("p") << QStringLiteral("profile"), tr("Name of configuration profile"), tr("PROFILE"));
169     parser.addOption(profileOption);
170 
171     QCommandLineOption daemonOption(QStringList() << QStringLiteral("d") << QStringLiteral("daemon-mode"), tr("Run PCManFM-Qt as a daemon"));
172     parser.addOption(daemonOption);
173 
174     QCommandLineOption quitOption(QStringList() << QStringLiteral("q") << QStringLiteral("quit"), tr("Quit PCManFM-Qt"));
175     parser.addOption(quitOption);
176 
177     QCommandLineOption desktopOption(QStringLiteral("desktop"), tr("Launch desktop manager"));
178     parser.addOption(desktopOption);
179 
180     QCommandLineOption desktopOffOption(QStringLiteral("desktop-off"), tr("Turn off desktop manager if it's running"));
181     parser.addOption(desktopOffOption);
182 
183     QCommandLineOption desktopPrefOption(QStringLiteral("desktop-pref"), tr("Open desktop preference dialog on the page with the specified name"), tr("NAME"));
184     parser.addOption(desktopPrefOption);
185 
186     QCommandLineOption newWindowOption(QStringList() << QStringLiteral("n") << QStringLiteral("new-window"), tr("Open new window"));
187     parser.addOption(newWindowOption);
188 
189     QCommandLineOption findFilesOption(QStringList() << QStringLiteral("f") << QStringLiteral("find-files"), tr("Open Find Files utility"));
190     parser.addOption(findFilesOption);
191 
192     QCommandLineOption setWallpaperOption(QStringList() << QStringLiteral("w") << QStringLiteral("set-wallpaper"), tr("Set desktop wallpaper from image FILE"), tr("FILE"));
193     parser.addOption(setWallpaperOption);
194 
195     QCommandLineOption wallpaperModeOption(QStringLiteral("wallpaper-mode"), tr("Set mode of desktop wallpaper. MODE=(%1)").arg(QStringLiteral("color|stretch|fit|center|tile|zoom")), tr("MODE"));
196     parser.addOption(wallpaperModeOption);
197 
198     QCommandLineOption showPrefOption(QStringLiteral("show-pref"), tr("Open Preferences dialog on the page with the specified name"), tr("NAME"));
199     parser.addOption(showPrefOption);
200 
201     parser.addPositionalArgument(QStringLiteral("files"), tr("Files or directories to open"), tr("[FILE1, FILE2,...]"));
202 
203     parser.process(arguments());
204 
205     if(isPrimaryInstance) {
206         qDebug("isPrimaryInstance");
207 
208         if(parser.isSet(daemonOption)) {
209             daemonMode_ = true;
210         }
211         if(parser.isSet(profileOption)) {
212             profileName_ = parser.value(profileOption);
213         }
214 
215         // load app config
216         settings_.load(profileName_);
217 
218         // init per-folder config
219         QString perFolderConfigFile = settings_.profileDir(profileName_) + QStringLiteral("/dir-settings.conf");
220         Fm::FolderConfig::init(perFolderConfigFile.toLocal8Bit().constData());
221 
222         // decrease the cache size to reduce memory usage
223         QPixmapCache::setCacheLimit(2048);
224 
225         if(settings_.useFallbackIconTheme()) {
226             QIcon::setThemeName(settings_.fallbackIconThemeName());
227         }
228 
229         // desktop icon management
230         if(parser.isSet(desktopOption)) {
231             desktopManager(true);
232             keepRunning = true;
233         }
234         else if(parser.isSet(desktopOffOption)) {
235             desktopManager(false);
236         }
237 
238         if(parser.isSet(desktopPrefOption)) { // desktop preference dialog
239             desktopPrefrences(parser.value(desktopPrefOption));
240             keepRunning = true;
241         }
242         else if(parser.isSet(findFilesOption)) { // file searching utility
243             findFiles(parser.positionalArguments());
244             keepRunning = true;
245         }
246         else if(parser.isSet(showPrefOption)) { // preferences dialog
247             preferences(parser.value(showPrefOption));
248             keepRunning = true;
249         }
250         else if(parser.isSet(setWallpaperOption) || parser.isSet(wallpaperModeOption)) { // set wall paper
251             setWallpaper(parser.value(setWallpaperOption), parser.value(wallpaperModeOption));
252         }
253         else {
254             if(!parser.isSet(desktopOption) && !parser.isSet(desktopOffOption)) {
255                 bool reopenLastTabs = false;
256                 QStringList paths = parser.positionalArguments();
257                 if(paths.isEmpty()) {
258                     // if no path is specified and we're using daemon mode,
259                     // don't open current working directory
260                     if(!daemonMode_) {
261                         reopenLastTabs = true; // open last tab paths only if no path is specified
262                         paths.push_back(QDir::currentPath());
263                     }
264                 }
265                 if(!paths.isEmpty()) {
266                     launchFiles(QDir::currentPath(), paths, parser.isSet(newWindowOption), reopenLastTabs);
267                 }
268                 keepRunning = true;
269             }
270         }
271     }
272     else {
273         QDBusConnection dbus = QDBusConnection::sessionBus();
274         QDBusInterface iface(QLatin1String(serviceName), QStringLiteral("/Application"), QLatin1String(ifaceName), dbus, this);
275         if(parser.isSet(quitOption)) {
276             iface.call(QStringLiteral("quit"));
277             return false;
278         }
279 
280         if(parser.isSet(desktopOption)) {
281             iface.call(QStringLiteral("desktopManager"), true);
282         }
283         else if(parser.isSet(desktopOffOption)) {
284             iface.call(QStringLiteral("desktopManager"), false);
285         }
286 
287         if(parser.isSet(desktopPrefOption)) { // desktop preference dialog
288             iface.call(QStringLiteral("desktopPrefrences"), parser.value(desktopPrefOption));
289         }
290         else if(parser.isSet(findFilesOption)) { // file searching utility
291             iface.call(QStringLiteral("findFiles"), parser.positionalArguments());
292         }
293         else if(parser.isSet(showPrefOption)) { // preferences dialog
294             iface.call(QStringLiteral("preferences"), parser.value(showPrefOption));
295         }
296         else if(parser.isSet(setWallpaperOption) || parser.isSet(wallpaperModeOption)) { // set wall paper
297             iface.call(QStringLiteral("setWallpaper"), parser.value(setWallpaperOption), parser.value(wallpaperModeOption));
298         }
299         else {
300             if(!parser.isSet(desktopOption) && !parser.isSet(desktopOffOption)) {
301                 bool reopenLastTabs = false;
302                 QStringList paths = parser.positionalArguments();
303                 if(paths.isEmpty()) {
304                     reopenLastTabs = true; // open last tab paths only if no path is specified
305                     paths.push_back(QDir::currentPath());
306                 }
307                 iface.call(QStringLiteral("launchFiles"), QDir::currentPath(), paths, parser.isSet(newWindowOption), reopenLastTabs);
308             }
309         }
310     }
311     return keepRunning;
312 }
313 
init()314 void Application::init() {
315 
316     // install the translations built-into Qt itself
317     qtTranslator.load(QStringLiteral("qt_") + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
318     installTranslator(&qtTranslator);
319 
320     // install libfm-qt translator
321     installTranslator(libFm_.translator());
322 
323     // install our own tranlations
324     translator.load(QStringLiteral("pcmanfm-qt_") + QLocale::system().name(), QStringLiteral(PCMANFM_DATA_DIR) + QStringLiteral("/translations"));
325     installTranslator(&translator);
326 }
327 
exec()328 int Application::exec() {
329 
330     if(!parseCommandLineArgs()) {
331         return 0;
332     }
333 
334     if(daemonMode_) { // keep running even when there is no window opened.
335         setQuitOnLastWindowClosed(false);
336     }
337 
338     volumeMonitor_ = g_volume_monitor_get();
339     // delay the volume manager a little because in newer versions of glib/gio there's a problem.
340     // when the first volume monitor object is created, it discovers volumes asynchonously.
341     // g_volume_monitor_get() immediately returns while the monitor is still discovering devices.
342     // So initially g_volume_monitor_get_volumes() returns nothing, but shortly after that
343     // we get volume-added signals for all of the volumes. This is not what we want.
344     // So, we wait for 3 seconds here to let it finish device discovery.
345     QTimer::singleShot(3000, this, &Application::initVolumeManager);
346 
347     return QCoreApplication::exec();
348 }
349 
350 
onUserDirsChanged()351 void Application::onUserDirsChanged() {
352     qDebug() << Q_FUNC_INFO;
353     bool file_deleted = !userDirsWatcher_->files().contains(userDirsFile_);
354     if(file_deleted) {
355         // if our config file is already deleted, reinstall a new watcher
356         userDirsWatcher_->addPath(userDirsFile_);
357     }
358 
359     const QString d = XdgDir::readDesktopDir();
360     if(d != userDesktopFolder_) {
361         userDesktopFolder_ = d;
362         const QDir dir(d);
363         if(dir.exists()) {
364             const int N = desktopWindows_.size();
365             for(int i = 0; i < N; ++i) {
366                 desktopWindows_.at(i)->setDesktopFolder();
367             }
368         }
369         else {
370             qWarning("Application::onUserDirsChanged: %s doesn't exist",
371                      userDesktopFolder_.toUtf8().constData());
372         }
373     }
374 }
375 
onAboutToQuit()376 void Application::onAboutToQuit() {
377     qDebug("aboutToQuit");
378     // save custom positions of desktop items
379     if(!desktopWindows_.isEmpty()) {
380         desktopWindows_.first()->saveItemPositions();
381     }
382 
383     settings_.save();
384 }
385 
cleanPerFolderConfig()386 void Application::cleanPerFolderConfig() {
387     // first save the perfolder config cache to have the list of all custom folders
388     Fm::FolderConfig::saveCache();
389     // then remove non-existent native folders from the list of custom folders
390     QByteArray perFolderConfig = (settings_.profileDir(profileName_) + QStringLiteral("/dir-settings.conf"))
391                                  .toLocal8Bit();
392     GKeyFile* kf = g_key_file_new();
393     if(g_key_file_load_from_file(kf, perFolderConfig.constData(), G_KEY_FILE_NONE, nullptr)) {
394         bool removed(false);
395         gchar **groups = g_key_file_get_groups(kf, nullptr);
396         for(int i = 0; groups[i] != nullptr; i++) {
397             const gchar *g = groups[i];
398             if(Fm::FilePath::fromPathStr(g).isNative() && !QDir(QString::fromUtf8(g)).exists()) {
399                 g_key_file_remove_group(kf, g, nullptr);
400                 removed = true;
401             }
402         }
403         g_strfreev(groups);
404         if(removed) {
405             g_key_file_save_to_file(kf, perFolderConfig.constData(), nullptr);
406         }
407     }
408     g_key_file_free(kf);
409 }
410 
411 /*bool Application::eventFilter(QObject* watched, QEvent* event) {
412     if(watched == desktop()) {
413         if(event->type() == QEvent::StyleChange ||
414                 event->type() == QEvent::ThemeChange) {
415             setStyle(new ProxyStyle());
416         }
417     }
418     return QObject::eventFilter(watched, event);
419 }*/
420 
onLastWindowClosed()421 void Application::onLastWindowClosed() {
422 
423 }
424 
onSaveStateRequest(QSessionManager &)425 void Application::onSaveStateRequest(QSessionManager& /*manager*/) {
426 
427 }
428 
desktopManager(bool enabled)429 void Application::desktopManager(bool enabled) {
430     // TODO: turn on or turn off desktpo management (desktop icons & wallpaper)
431     //qDebug("desktopManager: %d", enabled);
432     if(enabled) {
433         if(!enableDesktopManager_) {
434             // installNativeEventFilter(this);
435             const auto allScreens = screens();
436             for(QScreen* screen : allScreens) {
437                 connect(screen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
438                 connect(screen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
439                 connect(screen, &QObject::destroyed, this, &Application::onScreenDestroyed);
440             }
441             connect(this, &QApplication::screenAdded, this, &Application::onScreenAdded);
442             connect(this, &QApplication::screenRemoved, this, &Application::onScreenRemoved);
443 
444             // NOTE: there are two modes
445             // When virtual desktop is used (all screens are combined to form a large virtual desktop),
446             // we only create one DesktopWindow. Otherwise, we create one for each screen.
447             if(primaryScreen() && primaryScreen()->virtualSiblings().size() > 1) {
448                 DesktopWindow* window = createDesktopWindow(-1);
449                 desktopWindows_.push_back(window);
450             }
451             else {
452                 int n = qMax(allScreens.size(), 1);
453                 desktopWindows_.reserve(n);
454                 for(int i = 0; i < n; ++i) {
455                     DesktopWindow* window = createDesktopWindow(i);
456                     desktopWindows_.push_back(window);
457                 }
458             }
459         }
460     }
461     else {
462         if(enableDesktopManager_) {
463             int n = desktopWindows_.size();
464             for(int i = 0; i < n; ++i) {
465                 DesktopWindow* window = desktopWindows_.at(i);
466                 delete window;
467             }
468             desktopWindows_.clear();
469             const auto allScreens = screens();
470             for(QScreen* screen : allScreens) {
471                 disconnect(screen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
472                 disconnect(screen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
473                 disconnect(screen, &QObject::destroyed, this, &Application::onScreenDestroyed);
474             }
475             disconnect(this, &QApplication::screenAdded, this, &Application::onScreenAdded);
476             disconnect(this, &QApplication::screenRemoved, this, &Application::onScreenRemoved);
477             // removeNativeEventFilter(this);
478         }
479     }
480     enableDesktopManager_ = enabled;
481 }
482 
desktopPrefrences(QString page)483 void Application::desktopPrefrences(QString page) {
484     // show desktop preference window
485     if(!desktopPreferencesDialog_) {
486         desktopPreferencesDialog_ = new DesktopPreferencesDialog();
487 
488         // Should be used only one time
489         desktopPreferencesDialog_->setEditDesktopFolder(!lxqtRunning_);
490     }
491     desktopPreferencesDialog_.data()->selectPage(page);
492     desktopPreferencesDialog_.data()->show();
493     desktopPreferencesDialog_.data()->raise();
494     desktopPreferencesDialog_.data()->activateWindow();
495 }
496 
onFindFileAccepted()497 void Application::onFindFileAccepted() {
498     Fm::FileSearchDialog* dlg = static_cast<Fm::FileSearchDialog*>(sender());
499     // get search settings
500     settings_.setSearchNameCaseInsensitive(dlg->nameCaseInsensitive());
501     settings_.setsearchContentCaseInsensitive(dlg->contentCaseInsensitive());
502     settings_.setSearchNameRegexp(dlg->nameRegexp());
503     settings_.setSearchContentRegexp(dlg->contentRegexp());
504     settings_.setSearchRecursive(dlg->recursive());
505     settings_.setSearchhHidden(dlg->searchhHidden());
506 
507     Fm::FilePathList paths;
508     paths.emplace_back(dlg->searchUri());
509     MainWindow* window = MainWindow::lastActive();
510     Launcher(window).launchPaths(nullptr, paths);
511 }
512 
onConnectToServerAccepted()513 void Application::onConnectToServerAccepted() {
514     ConnectServerDialog* dlg = static_cast<ConnectServerDialog*>(sender());
515     QString uri = dlg->uriText();
516     Fm::FilePathList paths;
517     paths.push_back(Fm::FilePath::fromUri(uri.toUtf8().constData()));
518     MainWindow* window = MainWindow::lastActive();
519     Launcher(window).launchPaths(nullptr, paths);
520 }
521 
findFiles(QStringList paths)522 void Application::findFiles(QStringList paths) {
523     // launch file searching utility.
524     Fm::FileSearchDialog* dlg = new Fm::FileSearchDialog(paths);
525     connect(dlg, &QDialog::accepted, this, &Application::onFindFileAccepted);
526     dlg->setAttribute(Qt::WA_DeleteOnClose);
527     // set search settings
528     dlg->setNameCaseInsensitive(settings_.searchNameCaseInsensitive());
529     dlg->setContentCaseInsensitive(settings_.searchContentCaseInsensitive());
530     dlg->setNameRegexp(settings_.searchNameRegexp());
531     dlg->setContentRegexp(settings_.searchContentRegexp());
532     dlg->setRecursive(settings_.searchRecursive());
533     dlg->setSearchhHidden(settings_.searchhHidden());
534 
535     dlg->show();
536 }
537 
connectToServer()538 void Application::connectToServer() {
539     ConnectServerDialog* dlg = new ConnectServerDialog();
540     connect(dlg, &QDialog::accepted, this, &Application::onConnectToServerAccepted);
541     dlg->setAttribute(Qt::WA_DeleteOnClose);
542     dlg->show();
543 }
544 
launchFiles(QString cwd,QStringList paths,bool inNewWindow,bool reopenLastTabs)545 void Application::launchFiles(QString cwd, QStringList paths, bool inNewWindow, bool reopenLastTabs) {
546     Fm::FilePathList pathList;
547     Fm::FilePath cwd_path;
548 
549     reopenLastTabs = reopenLastTabs && settings_.reopenLastTabs() && !settings_.tabPaths().isEmpty();
550     if(reopenLastTabs) {
551         paths = settings_.tabPaths();
552         paths.removeDuplicates();
553         // forget tab paths with next windows until the last one is closed
554         settings_.setTabPaths(QStringList());
555     }
556 
557     for(const QString& it : qAsConst(paths)) {
558         QByteArray pathName = it.toLocal8Bit();
559         Fm::FilePath path;
560         if(pathName == "~") { // special case for home dir
561             path = Fm::FilePath::homeDir();
562         }
563         if(pathName[0] == '/') { // absolute path
564             path = Fm::FilePath::fromLocalPath(pathName.constData());
565         }
566         else if(pathName.contains(":/")) { // URI
567             path = Fm::FilePath::fromUri(pathName.constData());
568         }
569         else { // basename
570             if(Q_UNLIKELY(!cwd_path)) {
571                 cwd_path = Fm::FilePath::fromLocalPath(cwd.toLocal8Bit().constData());
572             }
573             path = cwd_path.relativePath(pathName.constData());
574         }
575        pathList.push_back(std::move(path));
576     }
577 
578     if(!inNewWindow && settings_.singleWindowMode()) {
579         MainWindow* window = MainWindow::lastActive();
580         // if there is no last active window, find the last created window
581         if(window == nullptr) {
582             QWidgetList windows = topLevelWidgets();
583             for(int i = 0; i < windows.size(); ++i) {
584                 auto win = windows.at(windows.size() - 1 - i);
585                 if(win->inherits("PCManFM::MainWindow")) {
586                     window = static_cast<MainWindow*>(win);
587                     break;
588                 }
589             }
590         }
591         auto launcher = Launcher(window);
592         launcher.openInNewTab();
593         launcher.launchPaths(nullptr, pathList);
594     }
595     else {
596         Launcher(nullptr).launchPaths(nullptr, pathList);
597     }
598 
599     // if none of the last tabs can be opened and there is no main window yet,
600     // open the current directory
601     if(reopenLastTabs) {
602         bool hasWindow = false;
603         const QWidgetList windows = topLevelWidgets();
604         for(const auto& win : windows) {
605             if(win->inherits("PCManFM::MainWindow")) {
606                 hasWindow = true;
607                 break;
608             }
609         }
610         if(!hasWindow) {
611             paths.clear();
612             paths.push_back(QDir::currentPath());
613             launchFiles(QDir::currentPath(), paths, inNewWindow, false);
614         }
615     }
616 }
617 
openFolders(Fm::FileInfoList files)618 void Application::openFolders(Fm::FileInfoList files) {
619     Launcher(nullptr).launchFiles(nullptr, std::move(files));
620 }
621 
openFolderInTerminal(Fm::FilePath path)622 void Application::openFolderInTerminal(Fm::FilePath path) {
623     if(!settings_.terminal().isEmpty()) {
624         Fm::GErrorPtr err;
625         auto terminalName = settings_.terminal().toUtf8();
626         if(!Fm::launchTerminal(terminalName.constData(), path, err)) {
627             QMessageBox::critical(nullptr, tr("Error"), err.message());
628         }
629     }
630     else {
631         // show an error message and ask the user to set the command
632         QMessageBox::critical(nullptr, tr("Error"), tr("Terminal emulator is not set."));
633         preferences(QStringLiteral("advanced"));
634     }
635 }
636 
preferences(QString page)637 void Application::preferences(QString page) {
638     // open preference dialog
639     if(!preferencesDialog_) {
640         preferencesDialog_ = new PreferencesDialog(page);
641     }
642     else {
643         preferencesDialog_.data()->selectPage(page);
644     }
645     preferencesDialog_.data()->show();
646     preferencesDialog_.data()->raise();
647     preferencesDialog_.data()->activateWindow();
648 }
649 
setWallpaper(QString path,QString modeString)650 void Application::setWallpaper(QString path, QString modeString) {
651     static const char* valid_wallpaper_modes[] = {"color", "stretch", "fit", "center", "tile"};
652     DesktopWindow::WallpaperMode mode = settings_.wallpaperMode();
653     bool changed = false;
654 
655     if(!path.isEmpty() && path != settings_.wallpaper()) {
656         if(QFile(path).exists()) {
657             settings_.setWallpaper(path);
658             changed = true;
659         }
660     }
661     // convert mode string to value
662     for(std::size_t i = 0; i < G_N_ELEMENTS(valid_wallpaper_modes); ++i) {
663         if(modeString == QLatin1String(valid_wallpaper_modes[i])) {
664             // We don't take safety checks because valid_wallpaper_modes[] is
665             // defined in this function and we can clearly see that it does not
666             // overflow.
667             mode = static_cast<DesktopWindow::WallpaperMode>(i);
668             if(mode != settings_.wallpaperMode()) {
669                 changed = true;
670             }
671             break;
672         }
673     }
674     // FIXME: support different wallpapers on different screen.
675     // update wallpaper
676     if(changed) {
677         if(enableDesktopManager_) {
678             for(DesktopWindow* desktopWin :  qAsConst(desktopWindows_)) {
679                 if(!path.isEmpty()) {
680                     desktopWin->setWallpaperFile(path);
681                 }
682                 if(mode != settings_.wallpaperMode()) {
683                     desktopWin->setWallpaperMode(mode);
684                 }
685                 desktopWin->updateWallpaper();
686                 desktopWin->update();
687             }
688             settings_.save(); // save the settings to the config file
689         }
690     }
691 }
692 
createDesktopWindow(int screenNum)693 DesktopWindow* Application::createDesktopWindow(int screenNum) {
694     DesktopWindow* window = new DesktopWindow(screenNum);
695 
696     if(screenNum == -1) { // one large virtual desktop only
697         QRect rect = primaryScreen()->virtualGeometry();
698         window->setGeometry(rect);
699     }
700     else {
701         QRect rect;
702         if(auto screen = window->getDesktopScreen()) {
703             rect = screen->geometry();
704         }
705         window->setGeometry(rect);
706     }
707 
708     window->updateFromSettings(settings_);
709     window->show();
710     return window;
711 }
712 
713 // called when Settings is changed to update UI
updateFromSettings()714 void Application::updateFromSettings() {
715     // if(iconTheme.isEmpty())
716     //  Fm::IconTheme::setThemeName(settings_.fallbackIconThemeName());
717 
718     // update main windows and desktop windows
719     QWidgetList windows = this->topLevelWidgets();
720     QWidgetList::iterator it;
721     for(it = windows.begin(); it != windows.end(); ++it) {
722         QWidget* window = *it;
723         if(window->inherits("PCManFM::MainWindow")) {
724             MainWindow* mainWindow = static_cast<MainWindow*>(window);
725             mainWindow->updateFromSettings(settings_);
726         }
727     }
728     if(desktopManagerEnabled()) {
729         updateDesktopsFromSettings();
730     }
731 }
732 
updateDesktopsFromSettings(bool changeSlide)733 void Application::updateDesktopsFromSettings(bool changeSlide) {
734     QVector<DesktopWindow*>::iterator it;
735     for(it = desktopWindows_.begin(); it != desktopWindows_.end(); ++it) {
736         DesktopWindow* desktopWin = static_cast<DesktopWindow*>(*it);
737         desktopWin->updateFromSettings(settings_, changeSlide);
738     }
739 }
740 
editBookmarks()741 void Application::editBookmarks() {
742     if(!editBookmarksialog_) {
743         editBookmarksialog_ = new Fm::EditBookmarksDialog(Fm::Bookmarks::globalInstance());
744     }
745     editBookmarksialog_.data()->show();
746 }
747 
initVolumeManager()748 void Application::initVolumeManager() {
749 
750     g_signal_connect(volumeMonitor_, "volume-added", G_CALLBACK(onVolumeAdded), this);
751 
752     if(settings_.mountOnStartup()) {
753         /* try to automount all volumes */
754         GList* vols = g_volume_monitor_get_volumes(volumeMonitor_);
755         for(GList* l = vols; l; l = l->next) {
756             GVolume* volume = G_VOLUME(l->data);
757             if(g_volume_should_automount(volume)) {
758                 autoMountVolume(volume, false);
759             }
760             g_object_unref(volume);
761         }
762         g_list_free(vols);
763     }
764 }
765 
autoMountVolume(GVolume * volume,bool interactive)766 bool Application::autoMountVolume(GVolume* volume, bool interactive) {
767     if(!g_volume_should_automount(volume) || !g_volume_can_mount(volume)) {
768         return FALSE;
769     }
770 
771     GMount* mount = g_volume_get_mount(volume);
772     if(!mount) { // not mounted, automount is needed
773         // try automount
774         Fm::MountOperation* op = new Fm::MountOperation(interactive);
775         op->mount(volume);
776         if(!op->wait()) {
777             return false;
778         }
779         if(!interactive) {
780             return true;
781         }
782         mount = g_volume_get_mount(volume);
783     }
784 
785     if(mount) {
786         if(interactive && settings_.autoRun()) { // show autorun dialog
787             AutoRunDialog* dlg = new AutoRunDialog(volume, mount);
788             dlg->show();
789         }
790         g_object_unref(mount);
791     }
792     return true;
793 }
794 
795 // static
onVolumeAdded(GVolumeMonitor *,GVolume * volume,Application * pThis)796 void Application::onVolumeAdded(GVolumeMonitor* /*monitor*/, GVolume* volume, Application* pThis) {
797     if(pThis->settings_.mountRemovable()) {
798         pThis->autoMountVolume(volume, true);
799     }
800 }
801 
802 #if 0
803 bool Application::nativeEventFilter(const QByteArray& eventType, void* message, long* result) {
804     if(eventType == "xcb_generic_event_t") { // XCB event
805         // filter all native X11 events (xcb)
806         xcb_generic_event_t* generic_event = reinterpret_cast<xcb_generic_event_t*>(message);
807         // qDebug("XCB event: %d", generic_event->response_type & ~0x80);
808         Q_FOREACH(DesktopWindow* window, desktopWindows_) {
809         }
810     }
811     return false;
812 }
813 #endif
814 
onScreenAdded(QScreen * newScreen)815 void Application::onScreenAdded(QScreen* newScreen) {
816     if(enableDesktopManager_) {
817         connect(newScreen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
818         connect(newScreen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
819         connect(newScreen, &QObject::destroyed, this, &Application::onScreenDestroyed);
820         const auto siblings = primaryScreen()->virtualSiblings();
821         if(siblings.contains(newScreen)) { // the primary screen is changed
822             if(desktopWindows_.size() == 1) {
823                 desktopWindows_.at(0)->setGeometry(newScreen->virtualGeometry());
824                 if(siblings.size() > 1) { // a virtual desktop is created
825                     desktopWindows_.at(0)->setScreenNum(-1);
826                 }
827             }
828             else if(desktopWindows_.isEmpty()) { // for the sake of certainty
829                 DesktopWindow* window = createDesktopWindow(desktopWindows_.size());
830                 desktopWindows_.push_back(window);
831             }
832         }
833         else { // a separate screen is added
834             DesktopWindow* window = createDesktopWindow(desktopWindows_.size());
835             desktopWindows_.push_back(window);
836         }
837     }
838 }
839 
onScreenRemoved(QScreen * oldScreen)840 void Application::onScreenRemoved(QScreen* oldScreen) {
841     if(enableDesktopManager_){
842         disconnect(oldScreen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
843         disconnect(oldScreen, &QScreen::availableGeometryChanged, this, &Application::onAvailableGeometryChanged);
844         disconnect(oldScreen, &QObject::destroyed, this, &Application::onScreenDestroyed);
845         if(desktopWindows_.isEmpty()) {
846             return;
847         }
848         if(desktopWindows_.size() == 1) { // a single desktop is changed
849             if(primaryScreen() != nullptr) {
850                 desktopWindows_.at(0)->setGeometry(primaryScreen()->virtualGeometry());
851                 if(primaryScreen()->virtualSiblings().size() == 1) {
852                     desktopWindows_.at(0)->setScreenNum(0); // there is no virtual desktop anymore
853                 }
854             }
855             else if (screens().isEmpty()) { // for the sake of certainty
856                 desktopWindows_.at(0)->setScreenNum(0);
857             }
858         }
859         else { // a separate desktop is removed
860             int n = desktopWindows_.size();
861             for(int i = 0; i < n; ++i) {
862                 DesktopWindow* window = desktopWindows_.at(i);
863                 if(window->getDesktopScreen() == oldScreen) {
864                     desktopWindows_.remove(i);
865                     delete window;
866                     break;
867                 }
868             }
869         }
870     }
871 }
872 
onScreenDestroyed(QObject * screenObj)873 void Application::onScreenDestroyed(QObject* screenObj) {
874     // NOTE by PCMan: This is a workaround for Qt 5 bug #40681.
875     // With this very dirty workaround, we can fix lxqt/lxqt bug #204, #205, and #206.
876     // Qt 5 has two new regression bugs which breaks lxqt-panel in a multihead environment.
877     // #40681: Regression bug: QWidget::winId() returns old value and QEvent::WinIdChange event is not emitted sometimes. (multihead setup)
878     // #40791: Regression: QPlatformWindow, QWindow, and QWidget::winId() are out of sync.
879     // Explanations for the workaround:
880     // Internally, Qt mantains a list of QScreens and update it when XRandR configuration changes.
881     // When the user turn off an monitor with xrandr --output <xxx> --off, this will destroy the QScreen
882     // object which represent the output. If the QScreen being destroyed contains our panel widget,
883     // Qt will call QWindow::setScreen(0) on the internal windowHandle() of our panel widget to move it
884     // to the primary screen. However, moving a window to a different screen is more than just changing
885     // its position. With XRandR, all screens are actually part of the same virtual desktop. However,
886     // this is not the case in other setups, such as Xinerama and moving a window to another screen is
887     // not possible unless you destroy the widget and create it again for a new screen.
888     // Therefore, Qt destroy the widget and re-create it when moving our panel to a new screen.
889     // Unfortunately, destroying the window also destroy the child windows embedded into it,
890     // using XEMBED such as the tray icons. (#206)
891     // Second, when the window is re-created, the winId of the QWidget is changed, but Qt failed to
892     // generate QEvent::WinIdChange event so we have no way to know that. We have to set
893     // some X11 window properties using the native winId() to make it a dock, but this stop working
894     // because we cannot get the correct winId(), so this causes #204 and #205.
895     //
896     // The workaround is very simple. Just completely destroy the window before Qt has a chance to do
897     // QWindow::setScreen() for it. Later, we recreate the window ourselves. So this can bypassing the Qt bugs.
898     if(enableDesktopManager_) {
899         bool reloadNeeded = false;
900         // FIXME: add workarounds for Qt5 bug #40681 and #40791 here.
901         for(DesktopWindow* desktopWin :  qAsConst(desktopWindows_)) {
902             if(desktopWin->windowHandle()->screen() == screenObj) {
903                 desktopWin->destroy(); // destroy the underlying native window
904                 reloadNeeded = true;
905             }
906         }
907         if(reloadNeeded) {
908             QTimer::singleShot(0, this, &Application::reloadDesktopsAsNeeded);
909         }
910     }
911 }
912 
reloadDesktopsAsNeeded()913 void Application::reloadDesktopsAsNeeded() {
914     if(enableDesktopManager_) {
915         // workarounds for Qt5 bug #40681 and #40791 here.
916         for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
917             if(!desktopWin->windowHandle()) {
918                 desktopWin->create(); // re-create the underlying native window
919                 desktopWin->queueRelayout();
920                 desktopWin->show();
921             }
922         }
923     }
924 }
925 
onVirtualGeometryChanged(const QRect &)926 void Application::onVirtualGeometryChanged(const QRect& /*rect*/) {
927     // update desktop geometries
928     if(enableDesktopManager_) {
929         for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
930             auto desktopScreen = desktopWin->getDesktopScreen();
931             if(desktopScreen) {
932                 desktopWin->setGeometry(desktopScreen->virtualGeometry());
933             }
934         }
935     }
936 }
937 
onAvailableGeometryChanged(const QRect &)938 void Application::onAvailableGeometryChanged(const QRect& /*rect*/) {
939     // update desktop layouts
940     if(enableDesktopManager_) {
941         for(DesktopWindow* desktopWin : qAsConst(desktopWindows_)) {
942             desktopWin->queueRelayout();
943         }
944     }
945 }
946 
947 
948 static int sigterm_fd[2];
949 
sigtermHandler(int)950 static void sigtermHandler(int) {
951     char c = 1;
952     ::write(sigterm_fd[0], &c, sizeof(c));
953 }
954 
installSigtermHandler()955 void Application::installSigtermHandler() {
956     if(::socketpair(AF_UNIX, SOCK_STREAM, 0, sigterm_fd) == 0) {
957         QSocketNotifier* notifier = new QSocketNotifier(sigterm_fd[1], QSocketNotifier::Read, this);
958         connect(notifier, &QSocketNotifier::activated, this, &Application::onSigtermNotified);
959 
960         struct sigaction action;
961         action.sa_handler = sigtermHandler;
962         ::sigemptyset(&action.sa_mask);
963         action.sa_flags = SA_RESTART;
964         if(::sigaction(SIGTERM, &action, nullptr) != 0) {
965             qWarning("Couldn't install SIGTERM handler");
966         }
967     }
968     else {
969         qWarning("Couldn't create SIGTERM socketpair");
970     }
971 }
972 
onSigtermNotified()973 void Application::onSigtermNotified() {
974     if(QSocketNotifier* notifier = qobject_cast<QSocketNotifier*>(sender())) {
975         notifier->setEnabled(false);
976         char c;
977         ::read(sigterm_fd[1], &c, sizeof(c));
978         // close all windows cleanly; otherwise, we might get this warning:
979         // "QBasicTimer::start: QBasicTimer can only be used with threads started with QThread"
980         const auto windows = topLevelWidgets();
981         for(const auto& win : windows) {
982             if(win->inherits("PCManFM::MainWindow")) {
983                 MainWindow* mainWindow = static_cast<MainWindow*>(win);
984                 mainWindow->close();
985             }
986         }
987         quit();
988         notifier->setEnabled(true);
989     }
990 }
991 
992 } // namespace PCManFM
993