kwin/clients/aurorae/src/aurorae.cpp
Martin Gräßlin c40bdc1bb2 [aurorae] Use QMutexLocker in AuroraeClient's dtor
It's possible that the rendering thread is still writing to the
buffer and if we destroy the buffer before it's finished KWin is going
to crash. So let's mutex lock the dtor to ensure that the rendering
thread finishes before we tear down the client.

BUG: 336950
2014-07-07 13:49:10 +02:00

635 lines
22 KiB
C++

/********************************************************************
Copyright (C) 2009, 2010, 2012 Martin Gräßlin <mgraesslin@kde.org>
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, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "aurorae.h"
#include "auroraetheme.h"
#include "config-kwin.h"
// qml imports
#include "decorationoptions.h"
#include <QApplication>
#include <QDebug>
#include <QDirIterator>
#include <QFileInfo>
#include <QMutex>
#include <QOpenGLFramebufferObject>
#include <QPainter>
#include <QQmlComponent>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickWindow>
#include <QStandardPaths>
#include <QWidget>
#include <KConfig>
#include <KConfigGroup>
#include <KSharedConfig>
#include <KPluginInfo>
#include <KServiceTypeTrader>
K_PLUGIN_FACTORY_WITH_JSON(AuroraePluginFactory,
"aurorae.json",
registerPlugin<Aurorae::AuroraeFactory>(QString(), &Aurorae::AuroraeFactory::createInstance);)
K_EXPORT_PLUGIN_VERSION(KWIN_DECORATION_API_VERSION)
namespace Aurorae
{
AuroraeFactory::AuroraeFactory(QObject *parent)
: KDecorationFactory(parent)
, m_theme(new AuroraeTheme(this))
, m_engine(new QQmlEngine(this))
, m_component(new QQmlComponent(m_engine, this))
, m_engineType(AuroraeEngine)
, m_mutex(new QMutex(QMutex::Recursive))
{
init();
connect(options(), &KDecorationOptions::buttonsChanged, this, &AuroraeFactory::buttonsChanged);
connect(options(), &KDecorationOptions::fontsChanged, this, &AuroraeFactory::titleFontChanged);
connect(options(), &KDecorationOptions::configChanged, this, &AuroraeFactory::updateConfiguration);
}
void AuroraeFactory::init()
{
qRegisterMetaType<uint>("Qt::MouseButtons");
// we need to first load our decoration plugin
// once it's loaded we can provide the Borders and access them from C++ side
// so let's try to locate our plugin:
QString pluginPath;
for (const QString &path : m_engine->importPathList()) {
QDirIterator it(path, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
QFileInfo fileInfo = it.fileInfo();
if (!fileInfo.isFile()) {
continue;
}
if (!fileInfo.path().endsWith(QLatin1String("/org/kde/kwin/decoration"))) {
continue;
}
if (fileInfo.fileName() == QLatin1String("libdecorationplugin.so")) {
pluginPath = fileInfo.absoluteFilePath();
break;
}
}
if (!pluginPath.isEmpty()) {
break;
}
}
m_engine->importPlugin(pluginPath, "org.kde.kwin.decoration", nullptr);
qmlRegisterType<KWin::Borders>("org.kde.kwin.decoration", 0, 1, "Borders");
KConfig conf(QStringLiteral("auroraerc"));
KConfigGroup group(&conf, "Engine");
if (!group.hasKey("EngineType") && !group.hasKey("ThemeName")) {
// neither engine type and theme name are configured, use the only available theme
initQML(group);
} else if (group.hasKey("EngineType")) {
const QString engineType = group.readEntry("EngineType", "aurorae").toLower();
if (engineType == QStringLiteral("qml")) {
initQML(group);
} else {
// fallback to classic Aurorae Themes
initAurorae(conf, group);
}
} else {
// fallback to classic Aurorae Themes
initAurorae(conf, group);
}
}
void AuroraeFactory::initAurorae(KConfig &conf, KConfigGroup &group)
{
m_engineType = AuroraeEngine;
const QString themeName = group.readEntry("ThemeName");
if (themeName.isEmpty()) {
// no theme configured, fall back to Plastik QML theme
initQML(group);
return;
}
KConfig config(QStringLiteral("aurorae/themes/") + themeName + QStringLiteral("/") + themeName + QStringLiteral("rc"),
KConfig::FullConfig, QStandardPaths::GenericDataLocation);
KConfigGroup themeGroup(&conf, themeName);
m_theme->loadTheme(themeName, config);
m_theme->setBorderSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("BorderSize", KDecorationDefines::BorderNormal));
m_theme->setButtonSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("ButtonSize", KDecorationDefines::BorderNormal));
m_theme->setTabDragMimeType(tabDragMimeType());
// setup the QML engine
/* use logic from KDeclarative::setupBindings():
"addImportPath adds the path at the beginning, so to honour user's
paths we need to traverse the list in reverse order" */
QStringListIterator paths(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("module/imports"), QStandardPaths::LocateDirectory));
paths.toBack();
while (paths.hasPrevious()) {
m_engine->addImportPath(paths.previous());
}
m_component->loadUrl(QUrl(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/aurorae/aurorae.qml"))));
m_engine->rootContext()->setContextProperty(QStringLiteral("auroraeTheme"), m_theme);
m_themeName = themeName;
}
void AuroraeFactory::initQML(const KConfigGroup &group)
{
// try finding the QML package
const QString themeName = group.readEntry("ThemeName", "kwin4_decoration_qml_plastik");
qCDebug(AURORAE) << "Trying to load QML Decoration " << themeName;
const QString internalname = themeName.toLower();
QString constraint = QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(internalname);
KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("KWin/Decoration"), constraint);
if (offers.isEmpty()) {
qCCritical(AURORAE) << "Couldn't find QML Decoration " << themeName << endl;
// TODO: what to do in error case?
return;
}
KService::Ptr service = offers.first();
const QString pluginName = service->property(QStringLiteral("X-KDE-PluginInfo-Name")).toString();
const QString scriptName = service->property(QStringLiteral("X-Plasma-MainScript")).toString();
const QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral(KWIN_NAME) + QStringLiteral("/decorations/") + pluginName + QStringLiteral("/contents/") + scriptName);
if (file.isNull()) {
qCDebug(AURORAE) << "Could not find script file for " << pluginName;
// TODO: what to do in error case?
return;
}
m_engineType = QMLEngine;
// setup the QML engine
/* use logic from KDeclarative::setupBindings():
"addImportPath adds the path at the beginning, so to honour user's
paths we need to traverse the list in reverse order" */
QStringListIterator paths(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("module/imports"), QStandardPaths::LocateDirectory));
paths.toBack();
while (paths.hasPrevious()) {
m_engine->addImportPath(paths.previous());
}
m_component->loadUrl(QUrl::fromLocalFile(file));
m_themeName = themeName;
}
AuroraeFactory::~AuroraeFactory()
{
s_instance = nullptr;
}
AuroraeFactory *AuroraeFactory::instance()
{
if (!s_instance) {
s_instance = new AuroraeFactory;
}
return s_instance;
}
QObject *AuroraeFactory::createInstance(QWidget*, QObject*, const QList< QVariant >&)
{
return instance();
}
void AuroraeFactory::updateConfiguration()
{
const KConfig conf(QStringLiteral("auroraerc"));
const KConfigGroup group(&conf, "Engine");
const QString themeName = group.readEntry("ThemeName", "example-deco");
const KConfig config(QStringLiteral("aurorae/themes/") + themeName + QStringLiteral("/") + themeName + QStringLiteral("rc"),
KConfig::FullConfig, QStandardPaths::GenericDataLocation);
const KConfigGroup themeGroup(&conf, themeName);
if (themeName != m_themeName) {
m_engine->clearComponentCache();
init();
emit recreateDecorations();
}
if (m_engineType == AuroraeEngine) {
m_theme->setBorderSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("BorderSize", KDecorationDefines::BorderNormal));
m_theme->setButtonSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("ButtonSize", KDecorationDefines::BorderNormal));
} else {
// we don't know how the settings change -> recreate
emit recreateDecorations();
}
emit configChanged();
}
bool AuroraeFactory::supports(Ability ability) const
{
switch (ability) {
case AbilityAnnounceButtons:
case AbilityUsesAlphaChannel:
case AbilityAnnounceAlphaChannel:
case AbilityButtonMenu:
case AbilityButtonSpacer:
case AbilityExtendIntoClientArea:
case AbilityButtonMinimize:
case AbilityButtonMaximize:
case AbilityButtonClose:
case AbilityButtonAboveOthers:
case AbilityButtonBelowOthers:
case AbilityButtonShade:
case AbilityButtonOnAllDesktops:
case AbilityButtonHelp:
case AbilityButtonApplicationMenu:
case AbilityProvidesShadow:
return true; // TODO: correct value from theme
case AbilityTabbing:
return false;
case AbilityUsesBlurBehind:
return true;
default:
return false;
}
}
KDecoration *AuroraeFactory::createDecoration(KDecorationBridge *bridge)
{
AuroraeClient *client = new AuroraeClient(bridge, this);
return client;
}
QList< KDecorationDefines::BorderSize > AuroraeFactory::borderSizes() const
{
return QList< BorderSize >() << BorderTiny << BorderNormal <<
BorderLarge << BorderVeryLarge << BorderHuge <<
BorderVeryHuge << BorderOversized;
}
QQuickItem *AuroraeFactory::createQmlDecoration(Aurorae::AuroraeClient *client)
{
QQmlContext *context = new QQmlContext(m_engine->rootContext(), this);
context->setContextProperty(QStringLiteral("decoration"), client);
return qobject_cast< QQuickItem* >(m_component->create(context));
}
AuroraeFactory *AuroraeFactory::s_instance = nullptr;
/*******************************************************
* Client
*******************************************************/
AuroraeClient::AuroraeClient(KDecorationBridge *bridge, KDecorationFactory *factory)
: KDecoration(bridge, factory)
, m_view(nullptr)
, m_item(AuroraeFactory::instance()->createQmlDecoration(this))
, m_borders(nullptr)
, m_maximizedBorders(nullptr)
, m_extendedBorders(nullptr)
, m_padding(nullptr)
{
connect(AuroraeFactory::instance(), SIGNAL(buttonsChanged()), SIGNAL(buttonsChanged()));
connect(AuroraeFactory::instance(), SIGNAL(configChanged()), SIGNAL(configChanged()));
connect(AuroraeFactory::instance(), SIGNAL(titleFontChanged()), SIGNAL(fontChanged()));
connect(m_item, SIGNAL(alphaChanged()), SLOT(slotAlphaChanged()));
connect(this, SIGNAL(appMenuAvailable()), SIGNAL(appMenuAvailableChanged()));
connect(this, SIGNAL(appMenuUnavailable()), SIGNAL(appMenuAvailableChanged()));
}
AuroraeClient::~AuroraeClient()
{
QMutexLocker locker(AuroraeFactory::instance()->mutex());
}
void AuroraeClient::init()
{
m_view = new QQuickWindow();
m_view->setFlags(initialWFlags());
m_view->setColor(Qt::transparent);
setMainWindow(m_view);
if (compositingActive()) {
connect(m_view, &QQuickWindow::beforeRendering, [this]() {
int left, right, top, bottom;
left = right = top = bottom = 0;
padding(left, right, top, bottom);
if (m_fbo.isNull() || m_fbo->size() != QSize(width() + left + right, height() + top + bottom)) {
m_fbo.reset(new QOpenGLFramebufferObject(QSize(width() + left + right, height() + top + bottom),
QOpenGLFramebufferObject::CombinedDepthStencil));
if (!m_fbo->isValid()) {
qCWarning(AURORAE) << "Creating FBO as render target failed";
m_fbo.reset();
return;
}
}
m_view->setRenderTarget(m_fbo.data());
});
connect(m_view, &QQuickWindow::afterRendering, [this]{
QMutexLocker locker(AuroraeFactory::instance()->mutex());
m_buffer = m_fbo->toImage();
});
connect(m_view, &QQuickWindow::afterRendering, this,
static_cast<void (KDecoration::*)(void)>(&KDecoration::update), Qt::QueuedConnection);
}
if (m_item) {
m_item->setParentItem(m_view->contentItem());
m_item->setParent(m_view);
setupBorders();
}
slotAlphaChanged();
AuroraeFactory::instance()->theme()->setCompositingActive(compositingActive());
}
bool AuroraeClient::eventFilter(QObject *object, QEvent *event)
{
// we need to filter the wheel events on the decoration
// QML does not yet provide a way to accept wheel events, this will change with Qt 5
// TODO: remove in KDE5
// see BUG: 304248
if (object != widget() || event->type() != QEvent::Wheel) {
return KDecoration::eventFilter(object, event);
}
QWheelEvent *wheel = static_cast<QWheelEvent*>(event);
if (mousePosition(wheel->pos()) == PositionCenter) {
titlebarMouseWheelOperation(wheel->delta());
return true;
}
return false;
}
void AuroraeClient::resize(const QSize &s)
{
if (m_item) {
m_item->setWidth(s.width());
m_item->setHeight(s.height());
}
m_view->resize(s);
}
void AuroraeClient::borders(int &left, int &right, int &top, int &bottom) const
{
if (!m_item) {
left = right = top = bottom = 0;
return;
}
KWin::Borders *borders = nullptr;
if (maximizeMode() == MaximizeFull) {
borders = m_maximizedBorders;
} else {
borders = m_borders;
}
sizesFromBorders(borders, left, right, top, bottom);
}
void AuroraeClient::padding(int &left, int &right, int &top, int &bottom) const
{
if (!m_padding) {
left = right = top = bottom = 0;
return;
}
if (maximizeMode() == MaximizeFull) {
left = right = top = bottom = 0;
return;
}
sizesFromBorders(m_padding, left, right, top, bottom);
}
void AuroraeClient::sizesFromBorders(const KWin::Borders *borders, int &left, int &right, int &top, int &bottom) const
{
if (!borders) {
left = right = top = bottom = 0;
return;
}
left = borders->left();
right = borders->right();
top = borders->top();
bottom = borders->bottom();
}
QSize AuroraeClient::minimumSize() const
{
return m_view->minimumSize();
}
KDecorationDefines::Position AuroraeClient::mousePosition(const QPoint &point) const
{
// based on the code from deKorator
int pos = PositionCenter;
if (isShade() || isMaximized()) {
return Position(pos);
}
int borderLeft, borderTop, borderRight, borderBottom;
borders(borderLeft, borderRight, borderTop, borderBottom);
int paddingLeft, paddingTop, paddingRight, paddingBottom;
padding(paddingLeft, paddingRight, paddingTop, paddingBottom);
int titleEdgeLeft, titleEdgeRight, titleEdgeTop, titleEdgeBottom;
AuroraeFactory::instance()->theme()->titleEdges(titleEdgeLeft, titleEdgeTop, titleEdgeRight, titleEdgeBottom, false);
switch (AuroraeFactory::instance()->theme()->decorationPosition()) {
case DecorationTop:
borderTop = titleEdgeTop;
break;
case DecorationLeft:
borderLeft = titleEdgeLeft;
break;
case DecorationRight:
borderRight = titleEdgeRight;
break;
case DecorationBottom:
borderBottom = titleEdgeBottom;
break;
default:
break; // nothing
}
if (point.x() >= (m_view->width() - borderRight - paddingRight)) {
pos |= PositionRight;
} else if (point.x() <= borderLeft + paddingLeft) {
pos |= PositionLeft;
}
if (point.y() >= m_view->height() - borderBottom - paddingBottom) {
pos |= PositionBottom;
} else if (point.y() <= borderTop + paddingTop ) {
pos |= PositionTop;
}
return Position(pos);
}
void AuroraeClient::menuClicked()
{
showWindowMenu(QCursor::pos());
}
void AuroraeClient::appMenuClicked()
{
showApplicationMenu(QCursor::pos());
}
void AuroraeClient::toggleShade()
{
setShade(!isShade());
}
void AuroraeClient::toggleKeepAbove()
{
setKeepAbove(!keepAbove());
}
void AuroraeClient::toggleKeepBelow()
{
setKeepBelow(!keepBelow());
}
void AuroraeClient::titlePressed(int button, int buttons)
{
titlePressed(static_cast<Qt::MouseButton>(button), static_cast<Qt::MouseButtons>(buttons));
}
void AuroraeClient::titlePressed(Qt::MouseButton button, Qt::MouseButtons buttons)
{
const QPoint cursor = QCursor::pos();
QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, cursor - geometry().topLeft() + (m_padding ? QPoint(m_padding->left(), m_padding->top()) : QPoint(0, 0)),
cursor, button, buttons, Qt::NoModifier);
processMousePressEvent(event);
delete event;
event = nullptr;
}
void AuroraeClient::themeChanged()
{
m_item->deleteLater();
m_item = AuroraeFactory::instance()->createQmlDecoration(this);
if (!m_item) {
m_borders = nullptr;
m_extendedBorders = nullptr;
m_maximizedBorders = nullptr;
m_padding = nullptr;
return;
}
m_item->setParentItem(m_view->contentItem());
m_item->setParent(m_view);
setupBorders();
connect(m_item, SIGNAL(alphaChanged()), SLOT(slotAlphaChanged()));
slotAlphaChanged();
}
int AuroraeClient::doubleClickInterval() const
{
return QApplication::doubleClickInterval();
}
void AuroraeClient::closeWindow()
{
QMetaObject::invokeMethod(qobject_cast< KDecoration* >(this), "doCloseWindow", Qt::QueuedConnection);
}
void AuroraeClient::doCloseWindow()
{
KDecoration::closeWindow();
}
void AuroraeClient::maximize(int button)
{
// a maximized window does not need to have a window decoration
// in that case we need to delay handling by one cycle
// BUG: 304870
QMetaObject::invokeMethod(qobject_cast< KDecoration* >(this),
"doMaximzie",
Qt::QueuedConnection,
Q_ARG(int, button));
}
void AuroraeClient::doMaximzie(int button)
{
KDecoration::maximize(static_cast<Qt::MouseButton>(button));
}
void AuroraeClient::titlebarDblClickOperation()
{
// the double click operation can result in a window being maximized
// see maximize
QMetaObject::invokeMethod(qobject_cast< KDecoration* >(this), "doTitlebarDblClickOperation", Qt::QueuedConnection);
}
void AuroraeClient::doTitlebarDblClickOperation()
{
KDecoration::titlebarDblClickOperation();
}
QVariant AuroraeClient::readConfig(const QString &key, const QVariant &defaultValue)
{
KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("auroraerc"));
return config->group(AuroraeFactory::instance()->currentThemeName()).readEntry(key, defaultValue);
}
void AuroraeClient::slotAlphaChanged()
{
if (!m_item) {
setAlphaEnabled(false);
return;
}
QVariant alphaProperty = m_item->property("alpha");
if (alphaProperty.isValid() && alphaProperty.canConvert<bool>()) {
setAlphaEnabled(alphaProperty.toBool());
} else {
// by default all Aurorae themes use the alpha channel
setAlphaEnabled(true);
}
}
QRegion AuroraeClient::region(KDecorationDefines::Region r)
{
if (r != ExtendedBorderRegion) {
return QRegion();
}
if (!m_item) {
return QRegion();
}
if (isMaximized()) {
// empty region for maximized windows
return QRegion();
}
int left, right, top, bottom;
left = right = top = bottom = 0;
sizesFromBorders(m_extendedBorders, left, right, top, bottom);
if (top == 0 && right == 0 && bottom == 0 && left == 0) {
// no extended borders
return QRegion();
}
int paddingLeft, paddingRight, paddingTop, paddingBottom;
paddingLeft = paddingRight = paddingTop = paddingBottom = 0;
padding(paddingLeft, paddingRight, paddingTop, paddingBottom);
QRect rect = this->rect().adjusted(paddingLeft, paddingTop, -paddingRight, -paddingBottom);
rect.translate(-paddingLeft, -paddingTop);
return QRegion(rect.adjusted(-left, -top, right, bottom)).subtract(rect);
}
bool AuroraeClient::animationsSupported() const
{
return compositingActive();
}
void AuroraeClient::render(QPaintDevice *device, const QRegion &sourceRegion)
{
QMutexLocker locker(AuroraeFactory::instance()->mutex());
QPainter painter(device);
painter.setClipRegion(sourceRegion);
painter.drawImage(QPoint(0, 0), m_buffer);
}
void AuroraeClient::setupBorders()
{
if (!m_item) {
return;
}
m_borders = m_item->findChild<KWin::Borders*>(QStringLiteral("borders"));
m_maximizedBorders = m_item->findChild<KWin::Borders*>(QStringLiteral("maximizedBorders"));
m_extendedBorders = m_item->findChild<KWin::Borders*>(QStringLiteral("extendedBorders"));
m_padding = m_item->findChild<KWin::Borders*>(QStringLiteral("padding"));
}
} // namespace Aurorae
#include "aurorae.moc"