Paint is in the middle of the compositor rendering loop. Updating the shadow at that point breaks KWin. Thus we delay the update to the next event cycle. Obviously it would be even better to only update the shadow if it changed, but that might be too expensive.
735 lines
26 KiB
C++
735 lines
26 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"
|
|
// KDecoration2
|
|
#include <KDecoration2/DecoratedClient>
|
|
#include <KDecoration2/DecorationSettings>
|
|
#include <KDecoration2/DecorationShadow>
|
|
// KDE
|
|
#include <KConfigGroup>
|
|
#include <KConfigLoader>
|
|
#include <KConfigDialogManager>
|
|
#include <KDesktopFile>
|
|
#include <KLocalizedTranslator>
|
|
#include <KPluginFactory>
|
|
#include <KSharedConfig>
|
|
#include <KService>
|
|
#include <KServiceTypeTrader>
|
|
// Qt
|
|
#include <QDebug>
|
|
#include <QDirIterator>
|
|
#include <QOffscreenSurface>
|
|
#include <QOpenGLContext>
|
|
#include <QOpenGLFramebufferObject>
|
|
#include <QPainter>
|
|
#include <QQuickItem>
|
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
|
|
#define HAVE_RENDER_CONTROL 1
|
|
#else
|
|
#define HAVE_RENDER_CONTROL 0
|
|
#endif
|
|
|
|
#if HAVE_RENDER_CONTROL
|
|
#include <QQuickRenderControl>
|
|
#endif
|
|
#include <QQuickWindow>
|
|
#include <QQmlComponent>
|
|
#include <QQmlContext>
|
|
#include <QQmlEngine>
|
|
#include <QStandardPaths>
|
|
#include <QTimer>
|
|
#include <QUiLoader>
|
|
#include <QVBoxLayout>
|
|
|
|
K_PLUGIN_FACTORY_WITH_JSON(AuroraeDecoFactory,
|
|
"aurorae.json",
|
|
registerPlugin<Aurorae::Decoration>();
|
|
registerPlugin<Aurorae::ThemeFinder>(QStringLiteral("themes"));
|
|
registerPlugin<Aurorae::ConfigurationModule>(QStringLiteral("kcmodule"));
|
|
)
|
|
|
|
namespace Aurorae
|
|
{
|
|
|
|
class Helper
|
|
{
|
|
public:
|
|
void ref();
|
|
void unref();
|
|
QQmlComponent *component(const QString &theme);
|
|
QQmlContext *rootContext();
|
|
QQmlComponent *svgComponent() {
|
|
return m_svgComponent.data();
|
|
}
|
|
|
|
static Helper &instance();
|
|
private:
|
|
Helper() = default;
|
|
void init();
|
|
QQmlComponent *loadComponent(const QString &themeName);
|
|
int m_refCount = 0;
|
|
QScopedPointer<QQmlEngine> m_engine;
|
|
QHash<QString, QQmlComponent*> m_components;
|
|
QScopedPointer<QQmlComponent> m_svgComponent;
|
|
};
|
|
|
|
Helper &Helper::instance()
|
|
{
|
|
static Helper s_helper;
|
|
return s_helper;
|
|
}
|
|
|
|
void Helper::ref()
|
|
{
|
|
m_refCount++;
|
|
if (m_refCount == 1) {
|
|
m_engine.reset(new QQmlEngine);
|
|
init();
|
|
}
|
|
}
|
|
|
|
void Helper::unref()
|
|
{
|
|
m_refCount--;
|
|
if (m_refCount == 0) {
|
|
// cleanup
|
|
m_svgComponent.reset();
|
|
m_engine.reset();
|
|
m_components.clear();
|
|
}
|
|
}
|
|
|
|
static const QString s_defaultTheme = QStringLiteral("kwin4_decoration_qml_plastik");
|
|
|
|
QQmlComponent *Helper::component(const QString &themeName)
|
|
{
|
|
// maybe it's an SVG theme?
|
|
if (themeName.startsWith(QLatin1Literal("__aurorae__svg__"))) {
|
|
if (m_svgComponent.isNull()) {
|
|
/* 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_svgComponent.reset(new QQmlComponent(m_engine.data()));
|
|
m_svgComponent->loadUrl(QUrl(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kwin/aurorae/aurorae.qml"))));
|
|
}
|
|
return m_svgComponent.data();
|
|
}
|
|
// try finding the QML package
|
|
auto it = m_components.constFind(themeName);
|
|
if (it != m_components.constEnd()) {
|
|
return it.value();
|
|
}
|
|
auto component = loadComponent(themeName);
|
|
if (component) {
|
|
m_components.insert(themeName, component);
|
|
return component;
|
|
}
|
|
// try loading default component
|
|
if (themeName != s_defaultTheme) {
|
|
return loadComponent(s_defaultTheme);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
QQmlComponent *Helper::loadComponent(const QString &themeName)
|
|
{
|
|
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 nullptr;
|
|
}
|
|
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 nullptr;
|
|
}
|
|
// 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());
|
|
}
|
|
QQmlComponent *component = new QQmlComponent(m_engine.data(), m_engine.data());
|
|
component->loadUrl(QUrl::fromLocalFile(file));
|
|
return component;
|
|
}
|
|
|
|
QQmlContext *Helper::rootContext()
|
|
{
|
|
return m_engine->rootContext();
|
|
}
|
|
|
|
void Helper::init()
|
|
{
|
|
// 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");
|
|
|
|
qmlRegisterType<KDecoration2::Decoration>();
|
|
qmlRegisterType<KDecoration2::DecoratedClient>();
|
|
qRegisterMetaType<KDecoration2::BorderSize>();
|
|
}
|
|
|
|
static QString findTheme(const QVariantList &args)
|
|
{
|
|
if (args.isEmpty()) {
|
|
return QString();
|
|
}
|
|
const auto map = args.first().toMap();
|
|
auto it = map.constFind(QStringLiteral("theme"));
|
|
if (it == map.constEnd()) {
|
|
return QString();
|
|
}
|
|
return it.value().toString();
|
|
}
|
|
|
|
Decoration::Decoration(QObject *parent, const QVariantList &args)
|
|
: KDecoration2::Decoration(parent, args)
|
|
, m_item(nullptr)
|
|
, m_borders(nullptr)
|
|
, m_maximizedBorders(nullptr)
|
|
, m_extendedBorders(nullptr)
|
|
, m_padding(nullptr)
|
|
, m_themeName(s_defaultTheme)
|
|
, m_mutex(QMutex::Recursive)
|
|
{
|
|
m_themeName = findTheme(args);
|
|
Helper::instance().ref();
|
|
}
|
|
|
|
Decoration::~Decoration()
|
|
{
|
|
Helper::instance().unref();
|
|
#if HAVE_RENDER_CONTROL
|
|
if (m_context) {
|
|
m_context->makeCurrent(m_offscreenSurface.data());
|
|
|
|
delete m_renderControl;
|
|
delete m_view.data();
|
|
m_fbo.reset();
|
|
delete m_item;
|
|
|
|
m_context->doneCurrent();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Decoration::init()
|
|
{
|
|
KDecoration2::Decoration::init();
|
|
auto s = settings();
|
|
connect(s.data(), &KDecoration2::DecorationSettings::reconfigured, this, &Decoration::configChanged);
|
|
// recreate scene when compositing gets disabled, TODO: remove with rendercontrol
|
|
#if !HAVE_RENDER_CONTROL
|
|
if (!m_recreateNonCompositedConnection) {
|
|
m_recreateNonCompositedConnection = connect(s.data(), &KDecoration2::DecorationSettings::alphaChannelSupportedChanged,
|
|
this, [this](bool alpha) {
|
|
if (!alpha && m_item) {
|
|
m_item->deleteLater();
|
|
m_decorationWindow.reset();
|
|
init();
|
|
}
|
|
});
|
|
}
|
|
#endif
|
|
|
|
QQmlContext *context = new QQmlContext(Helper::instance().rootContext(), this);
|
|
context->setContextProperty(QStringLiteral("decoration"), this);
|
|
context->setContextProperty(QStringLiteral("decorationSettings"), s.data());
|
|
auto component = Helper::instance().component(m_themeName);
|
|
if (!component) {
|
|
return;
|
|
}
|
|
if (component == Helper::instance().svgComponent()) {
|
|
// load SVG theme
|
|
const QString themeName = m_themeName.mid(16);
|
|
KConfig config(QStringLiteral("aurorae/themes/") + themeName + QStringLiteral("/") + themeName + QStringLiteral("rc"),
|
|
KConfig::FullConfig, QStandardPaths::GenericDataLocation);
|
|
// KConfigGroup themeGroup(&conf, themeName);
|
|
AuroraeTheme *theme = new AuroraeTheme(this);
|
|
theme->loadTheme(themeName, config);
|
|
theme->setBorderSize(s->borderSize());
|
|
connect(s.data(), &KDecoration2::DecorationSettings::borderSizeChanged, theme, &AuroraeTheme::setBorderSize);
|
|
// m_theme->setButtonSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("ButtonSize", KDecorationDefines::BorderNormal));
|
|
// m_theme->setTabDragMimeType(tabDragMimeType());
|
|
context->setContextProperty(QStringLiteral("auroraeTheme"), theme);
|
|
}
|
|
m_item = qobject_cast< QQuickItem* >(component->create(context));
|
|
if (!m_item) {
|
|
return;
|
|
}
|
|
m_item->setParent(this);
|
|
|
|
QVariant visualParent = property("visualParent");
|
|
if (visualParent.isValid()) {
|
|
m_item->setParentItem(visualParent.value<QQuickItem*>());
|
|
visualParent.value<QQuickItem*>()->setProperty("drawBackground", false);
|
|
} else {
|
|
#if HAVE_RENDER_CONTROL
|
|
// first create the context
|
|
QSurfaceFormat format;
|
|
format.setDepthBufferSize(16);
|
|
format.setStencilBufferSize(8);
|
|
m_context.reset(new QOpenGLContext);
|
|
m_context->setFormat(format);
|
|
m_context->create();
|
|
// and the offscreen surface
|
|
m_offscreenSurface.reset(new QOffscreenSurface);
|
|
m_offscreenSurface->setFormat(m_context->format());
|
|
m_offscreenSurface->create();
|
|
|
|
m_renderControl = new QQuickRenderControl(this);
|
|
m_view = new QQuickWindow(m_renderControl);
|
|
m_view->setColor(Qt::transparent);
|
|
|
|
// delay rendering a little bit for better performance
|
|
m_updateTimer.reset(new QTimer);
|
|
m_updateTimer->setSingleShot(true);
|
|
m_updateTimer->setInterval(5);
|
|
connect(m_updateTimer.data(), &QTimer::timeout, this,
|
|
[this] {
|
|
if (!m_context->makeCurrent(m_offscreenSurface.data())) {
|
|
return;
|
|
}
|
|
if (m_fbo.isNull() || m_fbo->size() != m_view->size()) {
|
|
m_fbo.reset(new QOpenGLFramebufferObject(m_view->size(), QOpenGLFramebufferObject::CombinedDepthStencil));
|
|
if (!m_fbo->isValid()) {
|
|
qCWarning(AURORAE) << "Creating FBO as render target failed";
|
|
m_fbo.reset();
|
|
return;
|
|
}
|
|
}
|
|
m_view->setRenderTarget(m_fbo.data());
|
|
m_renderControl->polishItems();
|
|
m_renderControl->sync();
|
|
m_renderControl->render();
|
|
|
|
m_view->resetOpenGLState();
|
|
m_buffer = m_fbo->toImage();
|
|
QOpenGLFramebufferObject::bindDefault();
|
|
update();
|
|
}
|
|
);
|
|
auto requestUpdate = [this] {
|
|
if (m_updateTimer->isActive()) {
|
|
return;
|
|
}
|
|
m_updateTimer->start();
|
|
};
|
|
connect(m_renderControl, &QQuickRenderControl::renderRequested, this, requestUpdate);
|
|
connect(m_renderControl, &QQuickRenderControl::sceneChanged, this, requestUpdate);
|
|
|
|
m_item->setParentItem(m_view->contentItem());
|
|
|
|
m_context->makeCurrent(m_offscreenSurface.data());
|
|
m_renderControl->initialize(m_context.data());
|
|
m_context->doneCurrent();
|
|
#else
|
|
// we need a QQuickWindow till we depend on Qt 5.4
|
|
m_decorationWindow.reset(QWindow::fromWinId(client().data()->decorationId()));
|
|
m_view = new QQuickWindow(m_decorationWindow.data());
|
|
m_view->setFlags(Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput);
|
|
m_view->setColor(Qt::transparent);
|
|
connect(m_view.data(), &QQuickWindow::beforeRendering, [this]() {
|
|
if (!settings()->isAlphaChannelSupported()) {
|
|
// directly render to QQuickWindow
|
|
m_fbo.reset();
|
|
return;
|
|
}
|
|
if (m_fbo.isNull() || m_fbo->size() != m_view->size()) {
|
|
m_fbo.reset(new QOpenGLFramebufferObject(m_view->size(), 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.data(), &QQuickWindow::afterRendering, [this] {
|
|
if (!m_fbo) {
|
|
return;
|
|
}
|
|
QMutexLocker locker(&m_mutex);
|
|
m_buffer = m_fbo->toImage();
|
|
});
|
|
connect(s.data(), &KDecoration2::DecorationSettings::alphaChannelSupportedChanged,
|
|
m_view.data(), &QQuickWindow::update);
|
|
connect(m_view.data(), &QQuickWindow::afterRendering, this, [this] { update(); }, Qt::QueuedConnection);
|
|
m_item->setParentItem(m_view->contentItem());
|
|
#endif
|
|
}
|
|
setupBorders(m_item);
|
|
if (m_extendedBorders) {
|
|
auto updateExtendedBorders = [this] {
|
|
setResizeOnlyBorders(*m_extendedBorders);
|
|
};
|
|
updateExtendedBorders();
|
|
connect(m_extendedBorders, &KWin::Borders::leftChanged, this, updateExtendedBorders);
|
|
connect(m_extendedBorders, &KWin::Borders::rightChanged, this, updateExtendedBorders);
|
|
connect(m_extendedBorders, &KWin::Borders::topChanged, this, updateExtendedBorders);
|
|
connect(m_extendedBorders, &KWin::Borders::bottomChanged, this, updateExtendedBorders);
|
|
}
|
|
connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, &Decoration::updateBorders, Qt::QueuedConnection);
|
|
updateBorders();
|
|
if (!m_view.isNull()) {
|
|
#if !HAVE_RENDER_CONTROL
|
|
m_view->setVisible(true);
|
|
#endif
|
|
auto resizeWindow = [this] {
|
|
QRect rect(QPoint(0, 0), size());
|
|
if (m_padding && !client().data()->isMaximized()) {
|
|
rect = rect.adjusted(-m_padding->left(), -m_padding->top(), m_padding->right(), m_padding->bottom());
|
|
}
|
|
m_view->setGeometry(rect);
|
|
#if !HAVE_RENDER_CONTROL
|
|
m_view->lower();
|
|
m_view->update();
|
|
#endif
|
|
};
|
|
connect(this, &Decoration::bordersChanged, this, resizeWindow);
|
|
connect(client().data(), &KDecoration2::DecoratedClient::widthChanged, this, resizeWindow);
|
|
connect(client().data(), &KDecoration2::DecoratedClient::heightChanged, this, resizeWindow);
|
|
connect(client().data(), &KDecoration2::DecoratedClient::maximizedChanged, this, resizeWindow);
|
|
resizeWindow();
|
|
} else {
|
|
// create a dummy shadow for the configuration interface
|
|
if (m_padding) {
|
|
auto s = QSharedPointer<KDecoration2::DecorationShadow>::create();
|
|
s->setPadding(*m_padding);
|
|
s->setInnerShadowRect(QRect(m_padding->left(), m_padding->top(), 1, 1));
|
|
setShadow(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
QVariant Decoration::readConfig(const QString &key, const QVariant &defaultValue)
|
|
{
|
|
KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("auroraerc"));
|
|
return config->group(m_themeName).readEntry(key, defaultValue);
|
|
}
|
|
|
|
void Decoration::setupBorders(QQuickItem *item)
|
|
{
|
|
m_borders = item->findChild<KWin::Borders*>(QStringLiteral("borders"));
|
|
m_maximizedBorders = item->findChild<KWin::Borders*>(QStringLiteral("maximizedBorders"));
|
|
m_extendedBorders = item->findChild<KWin::Borders*>(QStringLiteral("extendedBorders"));
|
|
m_padding = item->findChild<KWin::Borders*>(QStringLiteral("padding"));
|
|
}
|
|
|
|
void Decoration::updateBorders()
|
|
{
|
|
KWin::Borders *b = m_borders;
|
|
if (client().data()->isMaximized() && m_maximizedBorders) {
|
|
b = m_maximizedBorders;
|
|
}
|
|
if (!b) {
|
|
return;
|
|
}
|
|
setBorders(*b);
|
|
}
|
|
|
|
void Decoration::paint(QPainter *painter, const QRect &repaintRegion)
|
|
{
|
|
Q_UNUSED(repaintRegion)
|
|
#if !HAVE_RENDER_CONTROL
|
|
if (!settings()->isAlphaChannelSupported()) {
|
|
return;
|
|
}
|
|
QMutexLocker locker(&m_mutex);
|
|
#endif
|
|
painter->fillRect(rect(), Qt::transparent);
|
|
QRectF r(QPointF(0, 0), m_buffer.size());
|
|
if (m_padding &&
|
|
(m_padding->left() > 0 || m_padding->top() > 0 || m_padding->right() > 0 || m_padding->bottom() > 0) &&
|
|
!client().data()->isMaximized()) {
|
|
r = r.adjusted(m_padding->left(), m_padding->top(), -m_padding->right(), -m_padding->bottom());
|
|
auto s = QSharedPointer<KDecoration2::DecorationShadow>::create();
|
|
s->setShadow(m_buffer);
|
|
s->setPadding(*m_padding);
|
|
s->setInnerShadowRect(QRect(m_padding->left(),
|
|
m_padding->top(),
|
|
m_buffer.width() - m_padding->left() - m_padding->right(),
|
|
m_buffer.height() - m_padding->top() - m_padding->bottom()));
|
|
m_scheduledShadow = s;
|
|
} else {
|
|
m_scheduledShadow = QSharedPointer<KDecoration2::DecorationShadow>();
|
|
}
|
|
QMetaObject::invokeMethod(this, "updateShadow", Qt::QueuedConnection);
|
|
painter->drawImage(rect(), m_buffer, r);
|
|
}
|
|
|
|
void Decoration::updateShadow()
|
|
{
|
|
setShadow(m_scheduledShadow);
|
|
}
|
|
|
|
QMouseEvent Decoration::translatedMouseEvent(QMouseEvent *orig)
|
|
{
|
|
if (!m_padding || client().data()->isMaximized()) {
|
|
orig->setAccepted(false);
|
|
return *orig;
|
|
}
|
|
QMouseEvent event(orig->type(), orig->localPos() + QPointF(m_padding->left(), m_padding->top()), orig->button(), orig->buttons(), orig->modifiers());
|
|
event.setAccepted(false);
|
|
return event;
|
|
}
|
|
|
|
void Decoration::hoverEnterEvent(QHoverEvent *event)
|
|
{
|
|
if (m_view) {
|
|
event->setAccepted(false);
|
|
QCoreApplication::sendEvent(m_view.data(), event);
|
|
}
|
|
KDecoration2::Decoration::hoverEnterEvent(event);
|
|
}
|
|
|
|
void Decoration::hoverLeaveEvent(QHoverEvent *event)
|
|
{
|
|
if (m_view) {
|
|
event->setAccepted(false);
|
|
QCoreApplication::sendEvent(m_view.data(), event);
|
|
}
|
|
KDecoration2::Decoration::hoverLeaveEvent(event);
|
|
}
|
|
|
|
void Decoration::hoverMoveEvent(QHoverEvent *event)
|
|
{
|
|
if (m_view) {
|
|
QMouseEvent mouseEvent(QEvent::MouseMove, event->posF(), Qt::NoButton, Qt::NoButton, Qt::NoModifier);
|
|
QMouseEvent ev = translatedMouseEvent(&mouseEvent);
|
|
QCoreApplication::sendEvent(m_view.data(), &ev);
|
|
event->setAccepted(ev.isAccepted());
|
|
}
|
|
KDecoration2::Decoration::hoverMoveEvent(event);
|
|
}
|
|
|
|
void Decoration::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
if (m_view) {
|
|
QMouseEvent ev = translatedMouseEvent(event);
|
|
QCoreApplication::sendEvent(m_view.data(), &ev);
|
|
event->setAccepted(ev.isAccepted());
|
|
}
|
|
KDecoration2::Decoration::mouseMoveEvent(event);
|
|
}
|
|
|
|
void Decoration::mousePressEvent(QMouseEvent *event)
|
|
{
|
|
if (m_view) {
|
|
QMouseEvent ev = translatedMouseEvent(event);
|
|
QCoreApplication::sendEvent(m_view.data(), &ev);
|
|
event->setAccepted(ev.isAccepted());
|
|
}
|
|
KDecoration2::Decoration::mousePressEvent(event);
|
|
}
|
|
|
|
void Decoration::mouseReleaseEvent(QMouseEvent *event)
|
|
{
|
|
if (m_view) {
|
|
QMouseEvent ev = translatedMouseEvent(event);
|
|
QCoreApplication::sendEvent(m_view.data(), &ev);
|
|
event->setAccepted(ev.isAccepted());
|
|
}
|
|
KDecoration2::Decoration::mouseReleaseEvent(event);
|
|
}
|
|
|
|
void Decoration::installTitleItem(QQuickItem *item)
|
|
{
|
|
auto update = [this, item] {
|
|
QRect rect = item->mapRectToScene(item->childrenRect()).toRect();
|
|
if (rect.isNull()) {
|
|
rect = item->parentItem()->mapRectToScene(QRectF(item->x(), item->y(), item->width(), item->height())).toRect();
|
|
}
|
|
setTitleBar(rect);
|
|
};
|
|
update();
|
|
connect(item, &QQuickItem::widthChanged, this, update);
|
|
connect(item, &QQuickItem::heightChanged, this, update);
|
|
connect(item, &QQuickItem::xChanged, this, update);
|
|
connect(item, &QQuickItem::yChanged, this, update);
|
|
}
|
|
|
|
KDecoration2::DecoratedClient *Decoration::clientPointer() const
|
|
{
|
|
return client().data();
|
|
}
|
|
|
|
ThemeFinder::ThemeFinder(QObject *parent, const QVariantList &args)
|
|
: QObject(parent)
|
|
{
|
|
Q_UNUSED(args)
|
|
init();
|
|
}
|
|
|
|
void ThemeFinder::init()
|
|
{
|
|
findAllQmlThemes();
|
|
findAllSvgThemes();
|
|
}
|
|
|
|
void ThemeFinder::findAllQmlThemes()
|
|
{
|
|
const KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("KWin/Decoration"));
|
|
for (const auto &offer : offers) {
|
|
m_themes.insert(offer->name(), offer->property(QStringLiteral("X-KDE-PluginInfo-Name")).toString());
|
|
}
|
|
}
|
|
|
|
void ThemeFinder::findAllSvgThemes()
|
|
{
|
|
QStringList themes;
|
|
const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("aurorae/themes/"), QStandardPaths::LocateDirectory);
|
|
QStringList themeDirectories;
|
|
for (const QString &dir : dirs) {
|
|
QDir directory = QDir(dir);
|
|
for (const QString &themeDir : directory.entryList(QDir::AllDirs | QDir::NoDotAndDotDot)) {
|
|
themeDirectories << dir + themeDir;
|
|
}
|
|
}
|
|
for (const QString &dir : themeDirectories) {
|
|
for (const QString & file : QDir(dir).entryList(QStringList() << QStringLiteral("metadata.desktop"))) {
|
|
themes.append(dir + '/' + file);
|
|
}
|
|
}
|
|
for (const QString & theme : themes) {
|
|
int themeSepIndex = theme.lastIndexOf('/', -1);
|
|
QString themeRoot = theme.left(themeSepIndex);
|
|
int themeNameSepIndex = themeRoot.lastIndexOf('/', -1);
|
|
QString packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1);
|
|
|
|
KDesktopFile df(theme);
|
|
QString name = df.readName();
|
|
if (name.isEmpty()) {
|
|
name = packageName;
|
|
}
|
|
|
|
m_themes.insert(name, QStringLiteral("__aurorae__svg__") + packageName);
|
|
}
|
|
}
|
|
|
|
static const QString s_configUiPath = QStringLiteral("kwin/decorations/%1/contents/ui/config.ui");
|
|
static const QString s_configXmlPath = QStringLiteral("kwin/decorations/%1/contents/config/main.xml");
|
|
|
|
bool ThemeFinder::hasConfiguration(const QString &theme) const
|
|
{
|
|
if (theme.startsWith(QLatin1String("__aurorae__svg__"))) {
|
|
return false;
|
|
}
|
|
const QString ui = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
|
|
s_configUiPath.arg(theme));
|
|
const QString xml = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
|
|
s_configXmlPath.arg(theme));
|
|
return !(ui.isEmpty() || xml.isEmpty());
|
|
}
|
|
|
|
ConfigurationModule::ConfigurationModule(QWidget *parent, const QVariantList &args)
|
|
: KCModule(parent, args)
|
|
, m_theme(findTheme(args))
|
|
{
|
|
setLayout(new QVBoxLayout(this));
|
|
init();
|
|
}
|
|
|
|
void ConfigurationModule::init()
|
|
{
|
|
const QString ui = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
|
|
s_configUiPath.arg(m_theme));
|
|
const QString xml = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
|
|
s_configXmlPath.arg(m_theme));
|
|
if (ui.isEmpty() || xml.isEmpty()) {
|
|
return;
|
|
}
|
|
KLocalizedTranslator *translator = new KLocalizedTranslator(this);
|
|
QCoreApplication::instance()->installTranslator(translator);
|
|
const KDesktopFile metaData(QStandardPaths::locate(QStandardPaths::GenericDataLocation,
|
|
QStringLiteral("kwin/decorations/%1/metadata.desktop").arg(m_theme)));
|
|
const QString translationDomain = metaData.desktopGroup().readEntry("X-KWin-Config-TranslationDomain", QString());
|
|
if (!translationDomain.isEmpty()) {
|
|
translator->setTranslationDomain(translationDomain);
|
|
}
|
|
// load the KConfigSkeleton
|
|
QFile configFile(xml);
|
|
KSharedConfigPtr auroraeConfig = KSharedConfig::openConfig("auroraerc");
|
|
KConfigGroup configGroup = auroraeConfig->group(m_theme);
|
|
m_skeleton = new KConfigLoader(configGroup, &configFile, this);
|
|
// load the ui file
|
|
QUiLoader *loader = new QUiLoader(this);
|
|
loader->setLanguageChangeEnabled(true);
|
|
QFile uiFile(ui);
|
|
uiFile.open(QFile::ReadOnly);
|
|
QWidget *customConfigForm = loader->load(&uiFile, this);
|
|
translator->addContextToMonitor(customConfigForm->objectName());
|
|
uiFile.close();
|
|
layout()->addWidget(customConfigForm);
|
|
// connect the ui file with the skeleton
|
|
addConfig(m_skeleton, customConfigForm);
|
|
|
|
// send a custom event to the translator to retranslate using our translator
|
|
QEvent le(QEvent::LanguageChange);
|
|
QCoreApplication::sendEvent(customConfigForm, &le);
|
|
}
|
|
|
|
}
|
|
|
|
#include "aurorae.moc"
|