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