You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pcmanfm-qt-packaging/pcmanfm/application.cpp

841 lines
29 KiB

/*
Copyright (C) 2013 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "application.h"
#include "mainwindow.h"
#include "desktopwindow.h"
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDir>
#include <QDesktopWidget>
#include <QVector>
#include <QLocale>
#include <QLibraryInfo>
#include <QPixmapCache>
#include <QFile>
#include <QMessageBox>
#include <QCommandLineParser>
#include <QSocketNotifier>
#include <gio/gio.h>
#include <sys/socket.h>
#include "applicationadaptor.h"
#include "preferencesdialog.h"
#include "desktoppreferencesdialog.h"
#include "mountoperation.h"
#include "autorundialog.h"
#include "launcher.h"
#include "filesearchdialog.h"
#include <QScreen>
#include <QWindow>
#include <X11/Xlib.h>
#include "xdgdir.h"
#include <QFileSystemWatcher>
using namespace PCManFM;
static const char* serviceName = "org.pcmanfm.PCManFM";
static const char* ifaceName = "org.pcmanfm.Application";
int ProxyStyle::styleHint(StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const {
Application* app = static_cast<Application*>(qApp);
if(hint == QStyle::SH_ItemView_ActivateItemOnSingleClick)
return app->settings().singleClick();
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
Application::Application(int& argc, char** argv):
QApplication(argc, argv),
libFm_(),
settings_(),
profileName_("default"),
daemonMode_(false),
desktopWindows_(),
enableDesktopManager_(false),
preferencesDialog_(),
volumeMonitor_(NULL),
userDirsWatcher_(NULL),
lxqtRunning_(false),
editBookmarksialog_() {
argc_ = argc;
argv_ = argv;
// QDBusConnection::sessionBus().registerObject("/org/pcmanfm/Application", this);
QDBusConnection dbus = QDBusConnection::sessionBus();
if(dbus.registerService(serviceName)) {
// we successfully registered the service
isPrimaryInstance = true;
setStyle(new ProxyStyle());
desktop()->installEventFilter(this);
new ApplicationAdaptor(this);
dbus.registerObject("/Application", this);
connect(this, &Application::aboutToQuit, this, &Application::onAboutToQuit);
// aboutToQuit() is not signalled on SIGTERM, install signal handler
installSigtermHandler();
settings_.load(profileName_);
// decrease the cache size to reduce memory usage
QPixmapCache::setCacheLimit(2048);
if(settings_.useFallbackIconTheme()) {
QIcon::setThemeName(settings_.fallbackIconThemeName());
Fm::IconTheme::checkChanged();
}
// Check if LXQt Session is running. LXQt has it's own Desktop Folder
// editor. We just hide our editor when LXQt is running.
QDBusInterface* lxqtSessionIface = new QDBusInterface(
QStringLiteral("org.lxqt.session"),
QStringLiteral("/LXQtSession"));
if (lxqtSessionIface) {
if (lxqtSessionIface->isValid()) {
lxqtRunning_ = true;
userDesktopFolder_ = XdgDir::readDesktopDir();
initWatch();
}
delete lxqtSessionIface;
lxqtSessionIface = 0;
}
}
else {
// an service of the same name is already registered.
// we're not the first instance
isPrimaryInstance = false;
}
}
Application::~Application() {
desktop()->removeEventFilter(this);
if(volumeMonitor_) {
g_signal_handlers_disconnect_by_func(volumeMonitor_, gpointer(onVolumeAdded), this);
g_object_unref(volumeMonitor_);
}
// if(enableDesktopManager_)
// removeNativeEventFilter(this);
}
void Application::initWatch()
{
QFile file_ (QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/user-dirs.dirs"));
if(! file_.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << Q_FUNC_INFO << "Could not read: " << userDirsFile_;
userDirsFile_ = QString();
} else {
userDirsFile_ = file_.fileName();
}
userDirsWatcher_ = new QFileSystemWatcher(this);
userDirsWatcher_->addPath(userDirsFile_);
connect(userDirsWatcher_, &QFileSystemWatcher::fileChanged, this, &Application::onUserDirsChanged);
}
bool Application::parseCommandLineArgs() {
bool keepRunning = false;
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption profileOption(QStringList() << "p" << "profile", tr("Name of configuration profile"), tr("PROFILE"));
parser.addOption(profileOption);
QCommandLineOption daemonOption(QStringList() << "d" << "daemon-mode", tr("Run PCManFM as a daemon"));
parser.addOption(daemonOption);
QCommandLineOption quitOption(QStringList() << "q" << "quit", tr("Quit PCManFM"));
parser.addOption(quitOption);
QCommandLineOption desktopOption("desktop", tr("Launch desktop manager"));
parser.addOption(desktopOption);
QCommandLineOption desktopOffOption("desktop-off", tr("Turn off desktop manager if it's running"));
parser.addOption(desktopOffOption);
QCommandLineOption desktopPrefOption("desktop-pref", tr("Open desktop preference dialog on the page with the specified name"), tr("NAME"));
parser.addOption(desktopPrefOption);
QCommandLineOption newWindowOption(QStringList() << "n" << "new-window", tr("Open new window"));
parser.addOption(newWindowOption);
QCommandLineOption findFilesOption(QStringList() << "f" << "find-files", tr("Open Find Files utility"));
parser.addOption(findFilesOption);
QCommandLineOption setWallpaperOption(QStringList() << "w" << "set-wallpaper", tr("Set desktop wallpaper from image FILE"), tr("FILE"));
parser.addOption(setWallpaperOption);
// don't translate list of modes in description, please
QCommandLineOption wallpaperModeOption("wallpaper-mode", tr("Set mode of desktop wallpaper. MODE=(color|stretch|fit|center|tile)"), tr("MODE"));
parser.addOption(wallpaperModeOption);
QCommandLineOption showPrefOption("show-pref", tr("Open Preferences dialog on the page with the specified name"), tr("NAME"));
parser.addOption(showPrefOption);
parser.addPositionalArgument("files", tr("Files or directories to open"), tr("[FILE1, FILE2,...]"));
parser.process(arguments());
if(isPrimaryInstance) {
qDebug("isPrimaryInstance");
if(parser.isSet(daemonOption))
daemonMode_ = true;
if(parser.isSet(profileOption))
profileName_ = parser.value(profileOption);
// load settings
settings_.load(profileName_);
// desktop icon management
if(parser.isSet(desktopOption)) {
desktopManager(true);
keepRunning = true;
}
else if(parser.isSet(desktopOffOption))
desktopManager(false);
if(parser.isSet(desktopPrefOption)) { // desktop preference dialog
desktopPrefrences(parser.value(desktopPrefOption));
keepRunning = true;
}
else if(parser.isSet(findFilesOption)) { // file searching utility
findFiles(parser.positionalArguments());
keepRunning = true;
}
else if(parser.isSet(showPrefOption)) { // preferences dialog
preferences(parser.value(showPrefOption));
keepRunning = true;
}
else if(parser.isSet(setWallpaperOption) || parser.isSet(wallpaperModeOption)) // set wall paper
setWallpaper(parser.value(setWallpaperOption), parser.value(wallpaperModeOption));
else {
if(!parser.isSet(desktopOption) && !parser.isSet(desktopOffOption)) {
QStringList paths = parser.positionalArguments();
if(paths.isEmpty()) {
// if no path is specified and we're using daemon mode,
// don't open current working directory
if(!daemonMode_)
paths.push_back(QDir::currentPath());
}
if(!paths.isEmpty())
launchFiles(QDir::currentPath(), paths, parser.isSet(newWindowOption));
keepRunning = true;
}
}
}
else {
QDBusConnection dbus = QDBusConnection::sessionBus();
QDBusInterface iface(serviceName, "/Application", ifaceName, dbus, this);
if(parser.isSet(quitOption)) {
iface.call("quit");
return false;
}
if(parser.isSet(desktopOption))
iface.call("desktopManager", true);
else if(parser.isSet(desktopOffOption))
iface.call("desktopManager", false);
if(parser.isSet(desktopPrefOption)) { // desktop preference dialog
iface.call("desktopPrefrences", parser.value(desktopPrefOption));
}
else if(parser.isSet(findFilesOption)) { // file searching utility
iface.call("findFiles", parser.positionalArguments());
}
else if(parser.isSet(showPrefOption)) { // preferences dialog
iface.call("preferences", parser.value(showPrefOption));
}
else if(parser.isSet(setWallpaperOption) || parser.isSet(wallpaperModeOption)) { // set wall paper
iface.call("setWallpaper", parser.value(setWallpaperOption), parser.value(wallpaperModeOption));
}
else {
if(!parser.isSet(desktopOption) && !parser.isSet(desktopOffOption)) {
QStringList paths = parser.positionalArguments();
if(paths.isEmpty()) {
paths.push_back(QDir::currentPath());
}
iface.call("launchFiles", QDir::currentPath(), paths, parser.isSet(newWindowOption));
}
}
}
return keepRunning;
}
void Application::init() {
// install the translations built-into Qt itself
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
installTranslator(&qtTranslator);
// install libfm-qt translator
installTranslator(libFm_.translator());
// install our own tranlations
translator.load("pcmanfm-qt_" + QLocale::system().name(), PCMANFM_DATA_DIR "/translations");
installTranslator(&translator);
}
int Application::exec() {
if(!parseCommandLineArgs())
return 0;
if(daemonMode_) // keep running even when there is no window opened.
setQuitOnLastWindowClosed(false);
volumeMonitor_ = g_volume_monitor_get();
// delay the volume manager a little because in newer versions of glib/gio there's a problem.
// when the first volume monitor object is created, it discovers volumes asynchonously.
// g_volume_monitor_get() immediately returns while the monitor is still discovering devices.
// So initially g_volume_monitor_get_volumes() returns nothing, but shortly after that
// we get volume-added signals for all of the volumes. This is not what we want.
// So, we wait for 3 seconds here to let it finish device discovery.
QTimer::singleShot(3000, this, SLOT(initVolumeManager()));
return QCoreApplication::exec();
}
void Application::onUserDirsChanged()
{
qDebug() << Q_FUNC_INFO;
bool file_deleted = !userDirsWatcher_->files().contains(userDirsFile_);
if(file_deleted) {
// if our config file is already deleted, reinstall a new watcher
userDirsWatcher_->addPath(userDirsFile_);
}
const QString d = XdgDir::readDesktopDir();
if (d != userDesktopFolder_) {
userDesktopFolder_ = d;
const QDir dir(d);
if (dir.exists()) {
const int N = desktopWindows_.size();
for(int i = 0; i < N; ++i) {
desktopWindows_.at(i)->setDesktopFolder();
}
} else {
qWarning("Application::onUserDirsChanged: %s doesn't exist",
qUtf8Printable(userDesktopFolder_));
}
}
}
void Application::onAboutToQuit() {
qDebug("aboutToQuit");
settings_.save();
}
bool Application::eventFilter(QObject* watched, QEvent* event) {
if(watched == desktop()) {
switch(event->type()) {
case QEvent::StyleChange:
case QEvent::ThemeChange:
setStyle(new ProxyStyle());
};
}
return QObject::eventFilter(watched, event);
}
void Application::onLastWindowClosed() {
}
void Application::onSaveStateRequest(QSessionManager& manager) {
}
void Application::desktopManager(bool enabled) {
// TODO: turn on or turn off desktpo management (desktop icons & wallpaper)
qDebug("desktopManager: %d", enabled);
QDesktopWidget* desktopWidget = desktop();
if(enabled) {
if(!enableDesktopManager_) {
// installNativeEventFilter(this);
Q_FOREACH(QScreen* screen, screens()) {
connect(screen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
connect(screen, &QObject::destroyed, this, &Application::onScreenDestroyed);
}
connect(this, &QApplication::screenAdded, this, &Application::onScreenAdded);
connect(desktopWidget, &QDesktopWidget::resized, this, &Application::onScreenResized);
connect(desktopWidget, &QDesktopWidget::screenCountChanged, this, &Application::onScreenCountChanged);
// NOTE: there are two modes
// When virtual desktop is used (all screens are combined to form a large virtual desktop),
// we only create one DesktopWindow. Otherwise, we create one for each screen.
if(desktopWidget->isVirtualDesktop()) {
DesktopWindow* window = createDesktopWindow(-1);
desktopWindows_.push_back(window);
}
else {
int n = desktopWidget->numScreens();
desktopWindows_.reserve(n);
for(int i = 0; i < n; ++i) {
DesktopWindow* window = createDesktopWindow(i);
desktopWindows_.push_back(window);
}
}
}
}
else {
if(enableDesktopManager_) {
disconnect(desktopWidget, &QDesktopWidget::resized, this, &Application::onScreenResized);
disconnect(desktopWidget, &QDesktopWidget::screenCountChanged, this, &Application::onScreenCountChanged);
int n = desktopWindows_.size();
for(int i = 0; i < n; ++i) {
DesktopWindow* window = desktopWindows_.at(i);
delete window;
}
desktopWindows_.clear();
Q_FOREACH(QScreen* screen, screens()) {
disconnect(screen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
disconnect(screen, &QObject::destroyed, this, &Application::onScreenDestroyed);
}
disconnect(this, &QApplication::screenAdded, this, &Application::onScreenAdded);
// removeNativeEventFilter(this);
}
}
enableDesktopManager_ = enabled;
}
void Application::desktopPrefrences(QString page) {
// show desktop preference window
if(!desktopPreferencesDialog_) {
desktopPreferencesDialog_ = new DesktopPreferencesDialog();
// Should be used only one time
desktopPreferencesDialog_->setEditDesktopFolder(!lxqtRunning_);
}
desktopPreferencesDialog_.data()->selectPage(page);
desktopPreferencesDialog_.data()->show();
desktopPreferencesDialog_.data()->raise();
desktopPreferencesDialog_.data()->activateWindow();
}
void Application::onFindFileAccepted() {
Fm::FileSearchDialog* dlg = static_cast<Fm::FileSearchDialog*>(sender());
Fm::Path uri = dlg->searchUri();
// FIXME: we should be able to open it in an existing window
FmPathList* paths = fm_path_list_new();
fm_path_list_push_tail(paths, uri.data());
Launcher(NULL).launchPaths(NULL, paths);
fm_path_list_unref(paths);
}
void Application::findFiles(QStringList paths) {
// launch file searching utility.
Fm::FileSearchDialog* dlg = new Fm::FileSearchDialog(paths);
connect(dlg, &QDialog::accepted, this, &Application::onFindFileAccepted);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->show();
}
void Application::launchFiles(QString cwd, QStringList paths, bool inNewWindow) {
FmPathList* pathList = fm_path_list_new();
FmPath* cwd_path = NULL;
QStringList::iterator it;
Q_FOREACH(const QString& it, paths) {
QByteArray pathName = it.toLocal8Bit();
FmPath* path = NULL;
if(pathName[0] == '/') // absolute path
path = fm_path_new_for_path(pathName.constData());
else if(pathName.contains(":/")) // URI
path = fm_path_new_for_uri(pathName.constData());
else if(pathName == "~") // special case for home dir
path = fm_path_ref(fm_path_get_home());
else // basename
{
if(Q_UNLIKELY(!cwd_path))
cwd_path = fm_path_new_for_str(cwd.toLocal8Bit().constData());
path = fm_path_new_relative(cwd_path, pathName.constData());
}
fm_path_list_push_tail(pathList, path);
fm_path_unref(path);
}
if(cwd_path)
fm_path_unref(cwd_path);
Launcher(NULL).launchPaths(NULL, pathList);
fm_path_list_unref(pathList);
}
void Application::openFolders(FmFileInfoList* files) {
Launcher(NULL).launchFiles(NULL, files);
}
void Application::openFolderInTerminal(FmPath* path) {
if(!settings_.terminal().isEmpty()) {
char* cwd_str;
if(fm_path_is_native(path))
cwd_str = fm_path_to_str(path);
else { // gio will map remote filesystems to local FUSE-mounted paths here.
GFile* gf = fm_path_to_gfile(path);
cwd_str = g_file_get_path(gf);
g_object_unref(gf);
}
GError* err = NULL;
if(!fm_terminal_launch(cwd_str, &err)) {
QMessageBox::critical(NULL, tr("Error"), QString::fromUtf8(err->message));
g_error_free(err);
}
g_free(cwd_str);
}
else {
// show an error message and ask the user to set the command
QMessageBox::critical(NULL, tr("Error"), tr("Terminal emulator is not set."));
preferences("advanced");
}
}
void Application::preferences(QString page) {
// open preference dialog
if(!preferencesDialog_) {
preferencesDialog_ = new PreferencesDialog(page);
}
else {
// TODO: set page
}
preferencesDialog_.data()->show();
preferencesDialog_.data()->raise();
preferencesDialog_.data()->activateWindow();
}
void Application::setWallpaper(QString path, QString modeString) {
static const char* valid_wallpaper_modes[] = {"color", "stretch", "fit", "center", "tile"};
DesktopWindow::WallpaperMode mode = settings_.wallpaperMode();
bool changed = false;
if(!path.isEmpty() && path != settings_.wallpaper()) {
if(QFile(path).exists()) {
settings_.setWallpaper(path);
changed = true;
}
}
// convert mode string to value
for(int i = 0; i < G_N_ELEMENTS(valid_wallpaper_modes); ++i) {
if(modeString == valid_wallpaper_modes[i]) {
mode = (DesktopWindow::WallpaperMode)i;
if(mode != settings_.wallpaperMode())
changed = true;
break;
}
}
// FIXME: support different wallpapers on different screen.
// update wallpaper
if(changed) {
if(enableDesktopManager_) {
Q_FOREACH(DesktopWindow * desktopWindow, desktopWindows_) {
if(!path.isEmpty())
desktopWindow->setWallpaperFile(path);
if(mode != settings_.wallpaperMode())
desktopWindow->setWallpaperMode(mode);
desktopWindow->updateWallpaper();
}
settings_.save(); // save the settings to the config file
}
}
}
void Application::onScreenResized(int num) {
if(desktop()->isVirtualDesktop()) {
// in virtual desktop mode, we only have one desktop window. that is the first one.
DesktopWindow* window = desktopWindows_.at(0);
window->setGeometry(desktop()->geometry());
}
else {
DesktopWindow* window = desktopWindows_.at(num);
QRect rect = desktop()->screenGeometry(num);
window->setGeometry(rect);
}
}
DesktopWindow* Application::createDesktopWindow(int screenNum) {
DesktopWindow* window = new DesktopWindow(screenNum);
if(screenNum == -1) { // one large virtual desktop only
QRect rect = desktop()->geometry();
window->setGeometry(rect);
}
else {
QRect rect = desktop()->screenGeometry(screenNum);
window->setGeometry(rect);
}
window->updateFromSettings(settings_);
window->show();
return window;
}
void Application::onScreenCountChanged(int newCount) {
QDesktopWidget* desktopWidget = desktop();
bool oldVirtual = (desktopWindows_.size() == 1 && desktopWindows_.at(0)->screenNum() == -1);
bool isVirtual = desktopWidget->isVirtualDesktop();
if(oldVirtual && isVirtual) {
// if we are using virtual desktop mode previously, and the new mode is sitll virtual
// no further change is needed, only do relayout.
desktopWindows_.at(0)->queueRelayout();
return;
}
// we used non-virtual mode originally, but now we're switched to virtual mode
if(isVirtual)
newCount = 1; // we only want one desktop window for all screens in virtual mode
if(newCount > desktopWindows_.size()) {
// add more desktop windows
for(int i = desktopWindows_.size(); i < newCount; ++i) {
DesktopWindow* desktop = createDesktopWindow(i);
desktopWindows_.push_back(desktop);
}
}
else if(newCount < desktopWindows_.size()) {
// delete excessive desktop windows
for(int i = newCount; i < desktopWindows_.size(); ++i) {
DesktopWindow* desktop = desktopWindows_.at(i);
delete desktop;
}
desktopWindows_.resize(newCount);
}
if(newCount == 1) { // now only 1 screen is in use
DesktopWindow* desktop = desktopWindows_.at(0);
if(isVirtual)
desktop->setScreenNum(-1);
else // non-virtual mode, and we only have 1 screen
desktop->setScreenNum(0);
desktop->updateWallpaper();
}
}
// called when Settings is changed to update UI
void Application::updateFromSettings() {
// if(iconTheme.isEmpty())
// Fm::IconTheme::setThemeName(settings_.fallbackIconThemeName());
// update main windows and desktop windows
QWidgetList windows = this->topLevelWidgets();
QWidgetList::iterator it;
for(it = windows.begin(); it != windows.end(); ++it) {
QWidget* window = *it;
if(window->inherits("PCManFM::MainWindow")) {
MainWindow* mainWindow = static_cast<MainWindow*>(window);
mainWindow->updateFromSettings(settings_);
}
}
if(desktopManagerEnabled())
updateDesktopsFromSettings();
}
void Application::updateDesktopsFromSettings() {
QVector<DesktopWindow*>::iterator it;
for(it = desktopWindows_.begin(); it != desktopWindows_.end(); ++it) {
DesktopWindow* desktopWindow = static_cast<DesktopWindow*>(*it);
desktopWindow->updateFromSettings(settings_);
}
}
void Application::editBookmarks() {
if(!editBookmarksialog_) {
FmBookmarks* bookmarks = fm_bookmarks_dup();
editBookmarksialog_ = new Fm::EditBookmarksDialog(bookmarks);
g_object_unref(bookmarks);
}
editBookmarksialog_.data()->show();
}
void Application::initVolumeManager() {
g_signal_connect(volumeMonitor_, "volume-added", G_CALLBACK(onVolumeAdded), this);
if(settings_.mountOnStartup()) {
/* try to automount all volumes */
GList* vols = g_volume_monitor_get_volumes(volumeMonitor_);
for(GList* l = vols; l; l = l->next) {
GVolume* volume = G_VOLUME(l->data);
if(g_volume_should_automount(volume))
autoMountVolume(volume, false);
g_object_unref(volume);
}
g_list_free(vols);
}
}
bool Application::autoMountVolume(GVolume* volume, bool interactive) {
if(!g_volume_should_automount(volume) || !g_volume_can_mount(volume))
return FALSE;
GMount* mount = g_volume_get_mount(volume);
if(!mount) { // not mounted, automount is needed
// try automount
Fm::MountOperation* op = new Fm::MountOperation(interactive);
op->mount(volume);
if(!op->wait())
return false;
if(!interactive)
return true;
mount = g_volume_get_mount(volume);
}
if(mount) {
if(interactive && settings_.autoRun()) { // show autorun dialog
AutoRunDialog* dlg = new AutoRunDialog(volume, mount);
dlg->show();
}
g_object_unref(mount);
}
return true;
}
// static
void Application::onVolumeAdded(GVolumeMonitor* monitor, GVolume* volume, Application* pThis) {
if(pThis->settings_.mountRemovable())
pThis->autoMountVolume(volume, true);
}
#if 0
bool Application::nativeEventFilter(const QByteArray & eventType, void * message, long * result) {
if(eventType == "xcb_generic_event_t") { // XCB event
// filter all native X11 events (xcb)
xcb_generic_event_t* generic_event = reinterpret_cast<xcb_generic_event_t*>(message);
// qDebug("XCB event: %d", generic_event->response_type & ~0x80);
Q_FOREACH(DesktopWindow * window, desktopWindows_) {
}
}
return false;
}
#endif
void Application::onScreenAdded(QScreen* newScreen) {
if(enableDesktopManager_) {
connect(newScreen, &QScreen::virtualGeometryChanged, this, &Application::onVirtualGeometryChanged);
connect(newScreen, &QObject::destroyed, this, &Application::onScreenDestroyed);
}
}
void Application::onScreenDestroyed(QObject* screenObj) {
// NOTE by PCMan: This is a workaround for Qt 5 bug #40681.
// With this very dirty workaround, we can fix lxde/lxde-qt bug #204, #205, and #206.
// Qt 5 has two new regression bugs which breaks lxqt-panel in a multihead environment.
// #40681: Regression bug: QWidget::winId() returns old value and QEvent::WinIdChange event is not emitted sometimes. (multihead setup)
// #40791: Regression: QPlatformWindow, QWindow, and QWidget::winId() are out of sync.
// Explanations for the workaround:
// Internally, Qt mantains a list of QScreens and update it when XRandR configuration changes.
// When the user turn off an monitor with xrandr --output <xxx> --off, this will destroy the QScreen
// object which represent the output. If the QScreen being destroyed contains our panel widget,
// Qt will call QWindow::setScreen(0) on the internal windowHandle() of our panel widget to move it
// to the primary screen. However, moving a window to a different screen is more than just changing
// its position. With XRandR, all screens are actually part of the same virtual desktop. However,
// this is not the case in other setups, such as Xinerama and moving a window to another screen is
// not possible unless you destroy the widget and create it again for a new screen.
// Therefore, Qt destroy the widget and re-create it when moving our panel to a new screen.
// Unfortunately, destroying the window also destroy the child windows embedded into it,
// using XEMBED such as the tray icons. (#206)
// Second, when the window is re-created, the winId of the QWidget is changed, but Qt failed to
// generate QEvent::WinIdChange event so we have no way to know that. We have to set
// some X11 window properties using the native winId() to make it a dock, but this stop working
// because we cannot get the correct winId(), so this causes #204 and #205.
//
// The workaround is very simple. Just completely destroy the window before Qt has a chance to do
// QWindow::setScreen() for it. Later, we recreate the window ourselves. So this can bypassing the Qt bugs.
if(enableDesktopManager_) {
bool reloadNeeded = false;
// FIXME: add workarounds for Qt5 bug #40681 and #40791 here.
Q_FOREACH(DesktopWindow* desktop, desktopWindows_) {
if(desktop->windowHandle()->screen() == screenObj) {
desktop->destroy(); // destroy the underlying native window
reloadNeeded = true;
}
}
if(reloadNeeded)
QTimer::singleShot(0, this, SLOT(reloadDesktopsAsNeeded()));
}
}
void Application::reloadDesktopsAsNeeded() {
if(enableDesktopManager_) {
// workarounds for Qt5 bug #40681 and #40791 here.
Q_FOREACH(DesktopWindow* desktop, desktopWindows_) {
if(!desktop->windowHandle()) {
desktop->create(); // re-create the underlying native window
desktop->queueRelayout();
desktop->show();
}
}
}
}
// This slot is for Qt 5 onlt, but the stupid Qt moc cannot do conditional compilation
// so we have to define it for Qt 4 as well.
void Application::onVirtualGeometryChanged(const QRect& rect) {
// NOTE: the following is a workaround for Qt bug 32567.
// https://bugreports.qt-project.org/browse/QTBUG-32567
// Though the status of the bug report is closed, it's not yet fixed for X11.
// In theory, QDesktopWidget should emit "workAreaResized()" signal when the work area
// of any screen is changed, but in fact it does not do it.
// However, QScreen provided since Qt5 does not have the bug and
// virtualGeometryChanged() is emitted correctly when the workAreas changed.
// So we use it in Qt5.
if(enableDesktopManager_) {
// qDebug() << "onVirtualGeometryChanged";
Q_FOREACH(DesktopWindow* desktop, desktopWindows_) {
desktop->queueRelayout();
}
}
}
static int sigterm_fd[2];
static void sigtermHandler(int) {
char c = 1;
::write(sigterm_fd[0], &c, sizeof(c));
}
void Application::installSigtermHandler() {
if(::socketpair(AF_UNIX, SOCK_STREAM, 0, sigterm_fd) == 0) {
QSocketNotifier* notifier = new QSocketNotifier(sigterm_fd[1], QSocketNotifier::Read, this);
connect(notifier, &QSocketNotifier::activated, this, &Application::onSigtermNotified);
struct sigaction action;
action.sa_handler = sigtermHandler;
::sigemptyset(&action.sa_mask);
action.sa_flags |= SA_RESTART;
if(::sigaction(SIGTERM, &action, 0) != 0) {
qWarning("Couldn't install SIGTERM handler");
}
} else {
qWarning("Couldn't create SIGTERM socketpair");
}
}
void Application::onSigtermNotified() {
if (QSocketNotifier* notifier = qobject_cast<QSocketNotifier*>(sender())) {
notifier->setEnabled(false);
char c;
::read(sigterm_fd[1], &c, sizeof(c));
quit();
notifier->setEnabled(true);
}
}