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.
lxqt-panel-packaging/panel/lxqtpanelapplication.cpp

278 lines
9.9 KiB

/* BEGIN_COMMON_COPYRIGHT_HEADER
* (c)LGPL2+
*
* LXDE-Qt - a lightweight, Qt based, desktop toolset
* http://razor-qt.org
*
* Copyright: 2010-2011 Razor team
* Authors:
* Alexander Sokoloff <sokoloff.a@gmail.com>
*
* This program or library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* END_COMMON_COPYRIGHT_HEADER */
#include "lxqtpanelapplication.h"
#include "lxqtpanelapplication_p.h"
#include "lxqtpanel.h"
#include "config/configpaneldialog.h"
#include <LXQt/Settings>
#include <QtDebug>
#include <QUuid>
#include <QScreen>
#include <QWindow>
#include <QCommandLineParser>
LXQtPanelApplicationPrivate::LXQtPanelApplicationPrivate(LXQtPanelApplication *q)
: mSettings(0),
q_ptr(q)
{
}
ILXQtPanel::Position LXQtPanelApplicationPrivate::computeNewPanelPosition(const LXQtPanel *p, const int screenNum)
{
Q_Q(LXQtPanelApplication);
QVector<bool> screenPositions(4, false); // false means not occupied
for (int i = 0; i < q->mPanels.size(); ++i) {
if (p != q->mPanels.at(i)) {
// We are not the newly added one
if (screenNum == q->mPanels.at(i)->screenNum()) { // Panels on the same screen
int p = static_cast<int> (q->mPanels.at(i)->position());
screenPositions[p] = true; // occupied
}
}
}
int availablePosition = 0;
for (int i = 0; i < 4; ++i) { // Bottom, Top, Left, Right
if (!screenPositions[i]) {
availablePosition = i;
break;
}
}
return static_cast<ILXQtPanel::Position> (availablePosition);
}
LXQtPanelApplication::LXQtPanelApplication(int& argc, char** argv)
: LXQt::Application(argc, argv, true),
d_ptr(new LXQtPanelApplicationPrivate(this))
{
Q_D(LXQtPanelApplication);
QCoreApplication::setApplicationName(QLatin1String("lxqt-panel"));
QCoreApplication::setApplicationVersion(LXQT_VERSION);
QCommandLineParser parser;
parser.setApplicationDescription(QLatin1String("LXQt panel"));
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption configFileOption(QStringList()
<< QLatin1String("c") << QLatin1String("config") << QLatin1String("configfile"),
QCoreApplication::translate("main", "Use alternate configuration file."),
QCoreApplication::translate("main", "Configuration file"));
parser.addOption(configFileOption);
parser.process(*this);
const QString configFile = parser.value(configFileOption);
if (configFile.isEmpty())
d->mSettings = new LXQt::Settings(QLatin1String("panel"), this);
else
d->mSettings = new LXQt::Settings(configFile, QSettings::IniFormat, this);
// This is a workaround for Qt 5 bug #40681.
Q_FOREACH(QScreen* screen, screens())
{
connect(screen, &QScreen::destroyed, this, &LXQtPanelApplication::screenDestroyed);
}
connect(this, &QGuiApplication::screenAdded, this, &LXQtPanelApplication::handleScreenAdded);
connect(this, &QCoreApplication::aboutToQuit, this, &LXQtPanelApplication::cleanup);
QStringList panels = d->mSettings->value("panels").toStringList();
if (panels.isEmpty())
{
panels << "panel1";
}
Q_FOREACH(QString i, panels)
{
addPanel(i);
}
}
LXQtPanelApplication::~LXQtPanelApplication()
{
delete d_ptr;
}
void LXQtPanelApplication::cleanup()
{
qDeleteAll(mPanels);
}
void LXQtPanelApplication::addNewPanel()
{
Q_D(LXQtPanelApplication);
QString name("panel_" + QUuid::createUuid().toString());
LXQtPanel *p = addPanel(name);
int screenNum = p->screenNum();
ILXQtPanel::Position newPanelPosition = d->computeNewPanelPosition(p, screenNum);
p->setPosition(screenNum, newPanelPosition, true);
QStringList panels = d->mSettings->value("panels").toStringList();
panels << name;
d->mSettings->setValue("panels", panels);
// Poupup the configuration dialog to allow user configuration right away
p->showConfigDialog();
}
LXQtPanel* LXQtPanelApplication::addPanel(const QString& name)
{
Q_D(LXQtPanelApplication);
LXQtPanel *panel = new LXQtPanel(name, d->mSettings);
mPanels << panel;
// reemit signals
connect(panel, &LXQtPanel::deletedByUser, this, &LXQtPanelApplication::removePanel);
connect(panel, &LXQtPanel::pluginAdded, this, &LXQtPanelApplication::pluginAdded);
connect(panel, &LXQtPanel::pluginRemoved, this, &LXQtPanelApplication::pluginRemoved);
return panel;
}
void LXQtPanelApplication::handleScreenAdded(QScreen* newScreen)
{
// qDebug() << "LXQtPanelApplication::handleScreenAdded" << newScreen;
connect(newScreen, &QScreen::destroyed, this, &LXQtPanelApplication::screenDestroyed);
}
void LXQtPanelApplication::reloadPanelsAsNeeded()
{
Q_D(LXQtPanelApplication);
// NOTE by PCMan: This is a workaround for Qt 5 bug #40681.
// Here we try to re-create the missing panels which are deleted in
// LXQtPanelApplication::screenDestroyed().
// qDebug() << "LXQtPanelApplication::reloadPanelsAsNeeded()";
QStringList names = d->mSettings->value("panels").toStringList();
Q_FOREACH(const QString& name, names)
{
bool found = false;
Q_FOREACH(LXQtPanel* panel, mPanels)
{
if(panel->name() == name)
{
found = true;
break;
}
}
if(!found)
{
// the panel is found in the config file but does not exist, create it.
qDebug() << "Workaround Qt 5 bug #40681: re-create panel:" << name;
addPanel(name);
}
}
qApp->setQuitOnLastWindowClosed(true);
}
void LXQtPanelApplication::screenDestroyed(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 panel before Qt has a chance to do
// QWindow::setScreen() for it. Later, we reload the panel ourselves. So this can bypassing the Qt bugs.
QScreen* screen = static_cast<QScreen*>(screenObj);
bool reloadNeeded = false;
qApp->setQuitOnLastWindowClosed(false);
Q_FOREACH(LXQtPanel* panel, mPanels)
{
QWindow* panelWindow = panel->windowHandle();
if(panelWindow && panelWindow->screen() == screen)
{
// the screen containing the panel is destroyed
// delete and then re-create the panel ourselves
QString name = panel->name();
panel->saveSettings(false);
delete panel; // delete the panel, so Qt does not have a chance to set a new screen to it.
mPanels.removeAll(panel);
reloadNeeded = true;
qDebug() << "Workaround Qt 5 bug #40681: delete panel:" << name;
}
}
if(reloadNeeded)
QTimer::singleShot(1000, this, SLOT(reloadPanelsAsNeeded()));
else
qApp->setQuitOnLastWindowClosed(true);
}
void LXQtPanelApplication::removePanel(LXQtPanel* panel)
{
Q_D(LXQtPanelApplication);
Q_ASSERT(mPanels.contains(panel));
mPanels.removeAll(panel);
QStringList panels = d->mSettings->value("panels").toStringList();
panels.removeAll(panel->name());
d->mSettings->setValue("panels", panels);
panel->deleteLater();
}
bool LXQtPanelApplication::isPluginSingletonAndRunnig(QString const & pluginId) const
{
for (auto const & panel : mPanels)
if (panel->isPluginSingletonAndRunnig(pluginId))
return true;
return false;
}