diff --git a/src/plasmaquick/CMakeLists.txt b/src/plasmaquick/CMakeLists.txt index cc0dd2113..97784db06 100644 --- a/src/plasmaquick/CMakeLists.txt +++ b/src/plasmaquick/CMakeLists.txt @@ -1,127 +1,131 @@ if(HAVE_X11 AND XCB_XCB_FOUND AND XCB_SHAPE_FOUND) add_definitions(-DHAVE_XCB_SHAPE=1) else() add_definitions(-DHAVE_XCB_SHAPE=0) endif() set(plasmaquick_LIB_SRC appletquickitem.cpp debug_p.cpp dialog.cpp dialogshadows.cpp view.cpp containmentview.cpp configmodel.cpp shellpluginloader.cpp configview.cpp packageurlinterceptor.cpp private/configcategory_p.cpp private/packages.cpp ../declarativeimports/core/framesvgitem.cpp ../declarativeimports/core/units.cpp ) +if(HAVE_KWAYLAND) + set(plasmaquick_LIB_SRC ${plasmaquick_LIB_SRC} waylandintegration.cpp) +endif() + ecm_qt_declare_logging_category(PlasmaQuick_LIB_SRCS HEADER debug_p.h IDENTIFIER LOG_PLASMAQUICK CATEGORY_NAME org.kde.plasmaquick) add_library(KF5PlasmaQuick SHARED ${plasmaquick_LIB_SRC}) add_library(KF5::PlasmaQuick ALIAS KF5PlasmaQuick) target_include_directories(KF5PlasmaQuick PUBLIC "$") target_link_libraries(KF5PlasmaQuick PUBLIC Qt5::Gui Qt5::Quick Qt5::Qml KF5::Plasma KF5::WindowSystem PRIVATE KF5::KIOWidgets KF5::I18n KF5::IconThemes KF5::Service KF5::CoreAddons KF5::XmlGui KF5::Declarative KF5::QuickAddons ) if(HAVE_KWAYLAND) target_link_libraries(KF5PlasmaQuick PRIVATE KF5::WaylandClient ) endif() if(HAVE_X11) target_link_libraries(KF5PlasmaQuick PRIVATE Qt5::X11Extras ${X11_LIBRARIES} XCB::XCB ) if(XCB_SHAPE_FOUND) target_link_libraries(KF5PlasmaQuick PRIVATE XCB::SHAPE) endif() endif() set_target_properties(KF5PlasmaQuick PROPERTIES VERSION ${PLASMA_VERSION_STRING} SOVERSION ${PLASMA_SOVERSION} EXPORT_NAME PlasmaQuick ) install(TARGETS KF5PlasmaQuick EXPORT KF5PlasmaQuickTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) ecm_generate_export_header(KF5PlasmaQuick BASE_NAME PlasmaQuick GROUP_BASE_NAME KF VERSION ${KF5_VERSION} DEPRECATED_BASE_VERSION 0 DEPRECATION_VERSIONS 5.12 5.25 5.36 ) # TODO: add support for EXCLUDE_DEPRECATED_BEFORE_AND_AT to all Plasma libs # needs fixing of undeprecated API being still implemented using own deprecated API set(plasmaquick_LIB_INCLUDES ${CMAKE_CURRENT_BINARY_DIR}/plasmaquick_export.h packageurlinterceptor.h ) ecm_generate_headers(PlasmaQuick_CamelCase_HEADERS HEADER_NAMES AppletQuickItem ContainmentView ConfigView ConfigModel Dialog REQUIRED_HEADERS plasmaquick_LIB_INCLUDES PREFIX PlasmaQuick ) install(FILES ${plasmaquick_LIB_INCLUDES} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/plasmaquick COMPONENT Devel) install(FILES ${PlasmaQuick_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/PlasmaQuick COMPONENT Devel) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5PlasmaQuick") configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5PlasmaQuickConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5PlasmaQuickConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} PATH_VARS KF5_INCLUDE_INSTALL_DIR CMAKE_INSTALL_PREFIX ) ecm_setup_version(${KF5_VERSION} VARIABLE_PREFIX PLASMAQUICK PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5PlasmaQuickConfigVersion.cmake" ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5PlasmaQuickConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5PlasmaQuickConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5PlasmaQuickTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5PlasmaQuickTargets.cmake NAMESPACE KF5:: ) diff --git a/src/plasmaquick/dialog.cpp b/src/plasmaquick/dialog.cpp index 9beef4f72..41a3f0302 100644 --- a/src/plasmaquick/dialog.cpp +++ b/src/plasmaquick/dialog.cpp @@ -1,1451 +1,1452 @@ /*************************************************************************** * Copyright 2011 Marco Martin * * Copyright 2013 Sebastian Kügler * * Copyright 2014 Martin Gräßlin * * Copyright 2014 Vishesh Handa * * * * 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 "dialog.h" #include "config-plasma.h" #include "../declarativeimports/core/framesvgitem.h" #include "dialogshadows_p.h" #include "view.h" #include "configview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_KWAYLAND +#include "waylandintegration_p.h" #include #include #endif #if HAVE_XCB_SHAPE #include #include #endif #if HAVE_X11 #include #endif //Unfortunately QWINDOWSIZE_MAX is not exported #define DIALOGSIZE_MAX ((1<<24)-1) namespace PlasmaQuick { class DialogPrivate { public: DialogPrivate(Dialog *dialog) : q(dialog), location(Plasma::Types::BottomEdge), frameSvgItem(nullptr), hasMask(false), type(Dialog::Normal), hideOnWindowDeactivate(false), outputOnly(false), visible(false), componentComplete(dialog->parent() == nullptr), backgroundHints(Dialog::StandardBackground) { hintsCommitTimer.setSingleShot(true); hintsCommitTimer.setInterval(0); QObject::connect(&hintsCommitTimer, SIGNAL(timeout()), q, SLOT(updateLayoutParameters())); } void updateInputShape(); //SLOTS /** * Sync Borders updates the enabled borders of the frameSvgItem depending * on the geometry of the window. * * \param windowGeometry The window geometry which should be taken into * consideration when activating/deactivating certain borders */ void syncBorders(const QRect& windowGeometry); /** * This function sets the blurBehind, background contrast and shadows. It * does so wrt the frameSvgItem. So make sure the frameSvgItem is the * correct size before calling this function. */ void updateTheme(); void updateVisibility(bool visible); void updateMinimumWidth(); void updateMinimumHeight(); void updateMaximumWidth(); void updateMaximumHeight(); /** * Gets the maximum and minimum size hints for the window based on the contents. it doesn't actually resize anything */ void getSizeHints(QSize &min, QSize &max) const; /** * This function is an optimized version of updateMaximumHeight, * updateMaximumWidth,updateMinimumWidth and updateMinimumHeight. * It should be called when you need to call all 4 of these functions * AND you have called syncToMainItemSize before. */ void updateLayoutParameters(); QRect availableScreenGeometryForPosition(const QPoint& pos) const; /** * This function checks the current position of the dialog and repositions * it so that no part of it is not on the screen */ void repositionIfOffScreen(); void slotMainItemSizeChanged(); void slotWindowPositionChanged(); void syncToMainItemSize(); bool mainItemContainsPosition(const QPointF &point) const; QPointF positionAdjustedForMainItem(const QPointF &point) const; void setupWaylandIntegration(); void applyType(); Dialog *q; Plasma::Types::Location location; Plasma::FrameSvgItem *frameSvgItem; QPointer mainItem; QPointer visualParent; QTimer hintsCommitTimer; #if HAVE_KWAYLAND QPointer shellSurface; #endif QRect cachedGeometry; bool hasMask; Dialog::WindowType type; bool hideOnWindowDeactivate; bool outputOnly; bool visible; Plasma::Theme theme; bool componentComplete; Dialog::BackgroundHints backgroundHints; //Attached Layout property of mainItem, if any QPointer mainItemLayout; }; QRect DialogPrivate::availableScreenGeometryForPosition(const QPoint& pos) const { // FIXME: QWindow::screen() never ever changes if the window is moved across // virtual screens (normal two screens with X), this seems to be intentional // as it's explicitly mentioned in the docs. Until that's changed or some // more proper way of howto get the current QScreen for given QWindow is found, // we simply iterate over the virtual screens and pick the one our QWindow // says it's at. QRect avail; Q_FOREACH (QScreen *screen, QGuiApplication::screens()) { //we check geometry() but then take availableGeometry() //to reliably check in which screen a position is, we need the full //geometry, including areas for panels if (screen->geometry().contains(pos)) { avail = screen->availableGeometry(); break; } } /* * if the heuristic fails (because the topleft of the dialog is offscreen) * use at least our screen() * the screen should be correctly updated now on Qt 5.3+ so should be * more reliable anyways (could be tried to remove the whole Q_FOREACH * at this point) * * important: screen can be a nullptr... see bug 345173 */ if (avail.isEmpty() && q->screen()) { avail = q->screen()->availableGeometry(); } return avail; } void DialogPrivate::syncBorders(const QRect& geom) { QRect avail = availableScreenGeometryForPosition(geom.topLeft()); int borders = Plasma::FrameSvg::AllBorders; //Tooltips always have all the borders // floating windows have all borders if (!q->flags().testFlag(Qt::ToolTip) && location != Plasma::Types::Floating) { if (geom.x() <= avail.x() || location == Plasma::Types::LeftEdge) { borders = borders & ~Plasma::FrameSvg::LeftBorder; } if (geom.y() <= avail.y() || location == Plasma::Types::TopEdge) { borders = borders & ~Plasma::FrameSvg::TopBorder; } if (avail.right() <= geom.x() + geom.width() || location == Plasma::Types::RightEdge) { borders = borders & ~Plasma::FrameSvg::RightBorder; } if (avail.bottom() <= geom.y() + geom.height() || location == Plasma::Types::BottomEdge) { borders = borders & ~Plasma::FrameSvg::BottomBorder; } } if (frameSvgItem->enabledBorders() != (Plasma::FrameSvg::EnabledBorder)borders) { frameSvgItem->setEnabledBorders((Plasma::FrameSvg::EnabledBorder)borders); } } void DialogPrivate::updateTheme() { if (backgroundHints == Dialog::NoBackground) { frameSvgItem->setImagePath(QString()); KWindowEffects::enableBlurBehind(q->winId(), false); KWindowEffects::enableBackgroundContrast(q->winId(), false); q->setMask(QRegion()); DialogShadows::self()->removeWindow(q); } else { if (type == Dialog::Tooltip) { frameSvgItem->setImagePath(QStringLiteral("widgets/tooltip")); } else { frameSvgItem->setImagePath(QStringLiteral("dialogs/background")); } KWindowEffects::enableBlurBehind(q->winId(), theme.blurBehindEnabled(), frameSvgItem->mask()); KWindowEffects::enableBackgroundContrast(q->winId(), theme.backgroundContrastEnabled(), theme.backgroundContrast(), theme.backgroundIntensity(), theme.backgroundSaturation(), frameSvgItem->mask()); if (KWindowSystem::compositingActive()) { if (hasMask) { hasMask = false; q->setMask(QRegion()); } } else { hasMask = true; q->setMask(frameSvgItem->mask()); } if (q->isVisible()) { DialogShadows::self()->addWindow(q, frameSvgItem->enabledBorders()); } } updateInputShape(); } void DialogPrivate::updateVisibility(bool visible) { if (visible) { if (visualParent && visualParent->window()) { q->setTransientParent(visualParent->window()); } if (q->location() == Plasma::Types::FullScreen) { frameSvgItem->setEnabledBorders(Plasma::FrameSvg::NoBorder); // We cache the original size of the item, to retrieve it // when the dialog is switched back from fullscreen. if (q->geometry() != q->screen()->availableGeometry()) { cachedGeometry = q->geometry(); } q->setGeometry(q->screen()->availableGeometry()); } else { if (!cachedGeometry.isNull()) { q->resize(cachedGeometry.size()); slotWindowPositionChanged(); if (visualParent) { q->setPosition(q->popupPosition(visualParent, q->size())); } cachedGeometry = QRect(); } if (mainItem) { syncToMainItemSize(); } if (mainItemLayout) { updateLayoutParameters(); } #if HAVE_KWAYLAND //if is a wayland window that was hidden, we need //to set its position again as there won't be any move event to sync QWindow::position and shellsurface::position if (shellSurface && type != Dialog::OnScreenDisplay) { shellSurface->setPosition(q->position()); } #endif } } if (!q->flags().testFlag(Qt::ToolTip) && type != Dialog::Notification && type != Dialog::CriticalNotification) { KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge; switch (location) { case Plasma::Types::TopEdge: slideLocation = KWindowEffects::TopEdge; break; case Plasma::Types::LeftEdge: slideLocation = KWindowEffects::LeftEdge; break; case Plasma::Types::RightEdge: slideLocation = KWindowEffects::RightEdge; break; case Plasma::Types::BottomEdge: slideLocation = KWindowEffects::BottomEdge; break; //no edge, no slide default: break; } KWindowEffects::slideWindow(q->winId(), slideLocation, -1); } if (visible) { q->raise(); applyType(); } } void DialogPrivate::updateMinimumWidth() { Q_ASSERT(mainItem); Q_ASSERT(mainItemLayout); if (!componentComplete) { return; } q->setMinimumWidth(0); //this is to try to get the internal item resized a tad before, but //the flicker almost always happen anyways, so is *probably* useless //this other kind of flicker is the view not being always focused exactly //on the scene auto margin = frameSvgItem->fixedMargins(); int minimumWidth = mainItemLayout->property("minimumWidth").toInt() + margin->left() + margin->right(); if (q->screen()) { minimumWidth = qMin(q->screen()->availableGeometry().width(), minimumWidth); } q->contentItem()->setWidth(qMax(q->width(), minimumWidth)); q->setWidth(qMax(q->width(), minimumWidth)); hintsCommitTimer.start(); } void DialogPrivate::updateMinimumHeight() { Q_ASSERT(mainItem); Q_ASSERT(mainItemLayout); if (!componentComplete) { return; } q->setMinimumHeight(0); //this is to try to get the internal item resized a tad before, but //the flicker almost always happen anyways, so is *probably* useless //this other kind of flicker is the view not being always focused exactly //on the scene auto margin = frameSvgItem->fixedMargins(); int minimumHeight = mainItemLayout->property("minimumHeight").toInt() + margin->top() + margin->bottom(); if (q->screen()) { minimumHeight = qMin(q->screen()->availableGeometry().height(), minimumHeight); } q->contentItem()->setHeight(qMax(q->height(), minimumHeight)); q->setHeight(qMax(q->height(), minimumHeight)); hintsCommitTimer.start(); } void DialogPrivate::updateMaximumWidth() { Q_ASSERT(mainItem); Q_ASSERT(mainItemLayout); if (!componentComplete) { return; } q->setMaximumWidth(DIALOGSIZE_MAX); auto margin = frameSvgItem->fixedMargins(); int maximumWidth = mainItemLayout->property("maximumWidth").toInt() + margin->left() + margin->right(); if (q->screen()) { maximumWidth = qMin(q->screen()->availableGeometry().width(), maximumWidth); } q->contentItem()->setWidth(qMax(q->width(), maximumWidth)); q->setWidth(qMax(q->width(), maximumWidth)); hintsCommitTimer.start(); } void DialogPrivate::updateMaximumHeight() { Q_ASSERT(mainItem); Q_ASSERT(mainItemLayout); if (!componentComplete) { return; } q->setMaximumHeight(DIALOGSIZE_MAX); auto margin = frameSvgItem->fixedMargins(); int maximumHeight = mainItemLayout->property("maximumHeight").toInt() + margin->top() + margin->bottom(); if (q->screen()) { maximumHeight = qMin(q->screen()->availableGeometry().height(), maximumHeight); } q->contentItem()->setHeight(qMax(q->height(), maximumHeight)); q->setHeight(qMin(q->height(), maximumHeight)); hintsCommitTimer.start(); } void DialogPrivate::getSizeHints(QSize &min, QSize &max) const { if (!componentComplete || !mainItem || !mainItemLayout) { return; } Q_ASSERT(mainItem); Q_ASSERT(mainItemLayout); auto margin = frameSvgItem->fixedMargins(); int minimumHeight = mainItemLayout->property("minimumHeight").toInt(); int maximumHeight = mainItemLayout->property("maximumHeight").toInt(); maximumHeight = maximumHeight ? maximumHeight : DIALOGSIZE_MAX; int minimumWidth = mainItemLayout->property("minimumWidth").toInt(); int maximumWidth = mainItemLayout->property("maximumWidth").toInt(); maximumWidth = maximumWidth ? maximumWidth : DIALOGSIZE_MAX; minimumHeight += margin->top() + margin->bottom(); maximumHeight += margin->top() + margin->bottom(); minimumWidth += margin->left() + margin->right(); maximumWidth += margin->left() + margin->right(); if (q->screen()) { minimumWidth = qMin(q->screen()->availableGeometry().width(), minimumWidth); minimumHeight = qMin(q->screen()->availableGeometry().height(), minimumHeight); maximumWidth = qMin(q->screen()->availableGeometry().width(), maximumWidth); maximumHeight = qMin(q->screen()->availableGeometry().height(), maximumHeight); } min = QSize(minimumWidth, minimumHeight); max = QSize(maximumWidth, maximumHeight); } void DialogPrivate::updateLayoutParameters() { if (!componentComplete || !mainItem || !mainItemLayout) { return; } mainItem->disconnect(q); auto margin = frameSvgItem->fixedMargins(); QSize min; QSize max(DIALOGSIZE_MAX, DIALOGSIZE_MAX); getSizeHints(min, max); const QSize finalSize(qBound(min.width(), q->width(), max.width()), qBound(min.height(), q->height(), max.height())); if (visualParent) { //it's important here that we're using re->size() as size, we don't want to do recursive resizeEvents const QRect geom(q->popupPosition(visualParent, finalSize), finalSize); q->adjustGeometry(geom); } else { q->resize(finalSize); } mainItem->setPosition(QPointF(margin->left(), margin->top())); mainItem->setSize(QSizeF(q->width() - margin->left() - margin->right(), q->height() - margin->top() - margin->bottom())); frameSvgItem->setSize(QSizeF(q->width(), q->height())); repositionIfOffScreen(); updateTheme(); //FIXME: this seems to interfere with the geometry change that //sometimes is still going on, causing flicker (this one is two repositions happening in quick succession). //it may have to be delayed further q->setMinimumSize(min); q->setMaximumSize(max); QObject::connect(mainItem, SIGNAL(widthChanged()), q, SLOT(slotMainItemSizeChanged())); QObject::connect(mainItem, SIGNAL(heightChanged()), q, SLOT(slotMainItemSizeChanged())); } void DialogPrivate::repositionIfOffScreen() { if (!componentComplete) { return; } const QRect avail = availableScreenGeometryForPosition(q->position()); int x = q->x(); int y = q->y(); if (x < avail.left()) { x = avail.left(); } else if (x + q->width() > avail.right()) { x = avail.right() - q->width() + 1; } if (y < avail.top()) { y = avail.top(); } else if (y + q->height() > avail.bottom()) { y = avail.bottom() - q->height() + 1; } q->setX(x); q->setY(y); } void DialogPrivate::updateInputShape() { if (!q->isVisible()) { return; } #if HAVE_XCB_SHAPE if (KWindowSystem::isPlatformX11()) { xcb_connection_t *c = QX11Info::connection(); static bool s_shapeExtensionChecked = false; static bool s_shapeAvailable = false; if (!s_shapeExtensionChecked) { xcb_prefetch_extension_data(c, &xcb_shape_id); const xcb_query_extension_reply_t *extension = xcb_get_extension_data(c, &xcb_shape_id); if (extension->present) { // query version auto cookie = xcb_shape_query_version(c); QScopedPointer version(xcb_shape_query_version_reply(c, cookie, nullptr)); if (!version.isNull()) { s_shapeAvailable = (version->major_version * 0x10 + version->minor_version) >= 0x11; } } s_shapeExtensionChecked = true; } if (!s_shapeAvailable) { return; } if (outputOnly) { // set input shape, so that it doesn't accept any input events xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, q->winId(), 0, 0, 0, nullptr); } else { // delete the shape xcb_shape_mask(c, XCB_SHAPE_SO_INTERSECT, XCB_SHAPE_SK_INPUT, q->winId(), 0, 0, XCB_PIXMAP_NONE); } } #endif } void DialogPrivate::syncToMainItemSize() { Q_ASSERT(mainItem); if (!componentComplete) { return; } if (mainItem->width() <= 0 || mainItem->height() <= 0) { qWarning() << "trying to show an empty dialog"; } updateTheme(); if (visualParent) { // fixedMargins will get all the borders, no matter if they are enabled auto margins = frameSvgItem->fixedMargins(); const QSize fullSize = QSize(mainItem->width(), mainItem->height()) + QSize(margins->left() + margins->right(), margins->top() + margins->bottom()); // We get the popup position with the fullsize as we need the popup // position in order to determine our actual size, as the position // determines which borders will be shown. const QRect geom(q->popupPosition(visualParent, fullSize), fullSize); // We're then moving the window to where we think we would be with all // the borders. This way when syncBorders is called, it has a geometry // to work with. syncBorders(geom); } else { syncBorders(q->geometry()); } QSize s = QSize(mainItem->width(), mainItem->height()) + QSize(frameSvgItem->fixedMargins()->left() + frameSvgItem->fixedMargins()->right(), frameSvgItem->fixedMargins()->top() + frameSvgItem->fixedMargins()->bottom()); QSize min; QSize max(DIALOGSIZE_MAX, DIALOGSIZE_MAX); getSizeHints(min, max); s = QSize(qBound(min.width(), s.width(), max.width()), qBound(min.height(), s.height(), max.height())); q->contentItem()->setSize(s); frameSvgItem->setSize(s); if (visualParent) { const QRect geom(q->popupPosition(visualParent, s), s); if (geom == q->geometry()) { return; } q->adjustGeometry(geom); // The borders will instantly be updated but the geometry might take a // while as sub-classes can reimplement adjustGeometry and animate it. syncBorders(geom); } else { q->resize(s); } mainItem->setPosition(QPointF(frameSvgItem->fixedMargins()->left(), frameSvgItem->fixedMargins()->top())); updateTheme(); } void DialogPrivate::slotWindowPositionChanged() { // Tooltips always have all the borders // floating windows have all borders if (!q->isVisible() || q->flags().testFlag(Qt::ToolTip) || location == Plasma::Types::Floating) { return; } syncBorders(q->geometry()); updateTheme(); if (mainItem) { auto margin = frameSvgItem->fixedMargins(); mainItem->setPosition(QPoint(margin->left(), margin->top())); mainItem->setSize(QSize(q->width() - margin->left() - margin->right(), q->height() - margin->top() - margin->bottom())); } } bool DialogPrivate::mainItemContainsPosition(const QPointF &point) const { if (!mainItem) { return false; } return QRectF(mainItem->mapToScene(QPoint(0,0)), QSizeF(mainItem->width(), mainItem->height())).contains(point); } QPointF DialogPrivate::positionAdjustedForMainItem(const QPointF &point) const { if (!mainItem) { return point; } QRectF itemRect(mainItem->mapToScene(QPoint(0,0)), QSizeF(mainItem->width(), mainItem->height())); return QPointF(qBound(itemRect.left(), point.x(), itemRect.right()), qBound(itemRect.top(), point.y(), itemRect.bottom())); } void DialogPrivate::setupWaylandIntegration() { #if HAVE_KWAYLAND if (shellSurface) { // already setup return; } using namespace KWayland::Client; - PlasmaShell *interface = DialogShadows::self()->waylandPlasmaShellInterface(); + PlasmaShell *interface = WaylandIntegration::self()->waylandPlasmaShell(); if (!interface) { return; } Surface *s = Surface::fromWindow(q); if (!s) { return; } shellSurface = interface->createSurface(s, q); #endif } void DialogPrivate::applyType() { if (type != Dialog::Normal) { /*QXcbWindowFunctions::WmWindowType*/ int wmType = 0; #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { switch (type) { case Dialog::Normal: Q_UNREACHABLE(); break; case Dialog::Dock: wmType = QXcbWindowFunctions::WmWindowType::Dock; break; case Dialog::DialogWindow: wmType = QXcbWindowFunctions::WmWindowType::Dialog; break; case Dialog::PopupMenu: wmType = QXcbWindowFunctions::WmWindowType::PopupMenu; break; case Dialog::Tooltip: wmType = QXcbWindowFunctions::WmWindowType::Tooltip; break; case Dialog::Notification: wmType = QXcbWindowFunctions::WmWindowType::Notification; break; case Dialog::OnScreenDisplay: case Dialog::CriticalNotification: // Not supported by Qt break; } if (wmType) { QXcbWindowFunctions::setWmWindowType(q, static_cast(wmType)); } } #endif if (!wmType) { KWindowSystem::setType(q->winId(), static_cast(type)); } } else { q->setFlags(Qt::FramelessWindowHint | q->flags()); } //an OSD can't be a Dialog, as qt xcb would attempt to set a transient parent for it //see bug 370433 if (type == Dialog::OnScreenDisplay) { q->setFlags((q->flags() & ~Qt::Dialog) | Qt::Window); } if (backgroundHints == Dialog::NoBackground) { frameSvgItem->setImagePath(QString()); } else { if (type == Dialog::Tooltip) { frameSvgItem->setImagePath(QStringLiteral("widgets/tooltip")); } else { frameSvgItem->setImagePath(QStringLiteral("dialogs/background")); } } if (type == Dialog::Dock || type == Dialog::Notification || type == Dialog::OnScreenDisplay || type == Dialog::CriticalNotification) { KWindowSystem::setOnAllDesktops(q->winId(), true); } else { KWindowSystem::setOnAllDesktops(q->winId(), false); } #if HAVE_KWAYLAND if (shellSurface) { shellSurface->setPanelTakesFocus(!q->flags().testFlag(Qt::WindowDoesNotAcceptFocus)); } #endif } Dialog::Dialog(QQuickItem *parent) : QQuickWindow(parent ? parent->window() : nullptr), d(new DialogPrivate(this)) { setClearBeforeRendering(true); setColor(QColor(Qt::transparent)); setFlags(Qt::FramelessWindowHint | Qt::Dialog); connect(this, &QWindow::xChanged, [=]() { d->slotWindowPositionChanged(); }); connect(this, &QWindow::yChanged, [=]() { d->slotWindowPositionChanged(); }); // Given dialogs are skip task bar and don't have a decoration // minimizing them using e.g. "minimize all" should just close them connect(this, &QWindow::windowStateChanged, this, [this](Qt::WindowState newState) { if (newState == Qt::WindowMinimized) { setVisible(false); } }); connect(this, &QWindow::visibleChanged, this, &Dialog::visibleChangedProxy); connect(this, SIGNAL(visibleChanged(bool)), this, SLOT(updateInputShape())); connect(this, SIGNAL(outputOnlyChanged()), this, SLOT(updateInputShape())); //HACK: this property is invoked due to the initialization that gets done to contentItem() in the getter property("data"); //Create the FrameSvg background. d->frameSvgItem = new Plasma::FrameSvgItem(contentItem()); //This is needed as a transition thing for KWayland setProperty("__plasma_frameSvg", QVariant::fromValue(d->frameSvgItem->frameSvg())); connect(&d->theme, SIGNAL(themeChanged()), this, SLOT(updateTheme())); } Dialog::~Dialog() { if (!QCoreApplication::instance()->closingDown()) { DialogShadows::self()->removeWindow(this); } } QQuickItem *Dialog::mainItem() const { return d->mainItem; } void Dialog::setMainItem(QQuickItem *mainItem) { if (d->mainItem != mainItem) { d->hintsCommitTimer.stop(); if (d->mainItem) { disconnect(d->mainItem, nullptr, this, nullptr); d->mainItem->setParentItem(nullptr); } if (d->mainItemLayout) { disconnect(d->mainItemLayout, nullptr, this, nullptr); } d->mainItem = mainItem; if (mainItem) { mainItem->setParentItem(contentItem()); connect(mainItem, SIGNAL(widthChanged()), this, SLOT(slotMainItemSizeChanged())); connect(mainItem, SIGNAL(heightChanged()), this, SLOT(slotMainItemSizeChanged())); d->slotMainItemSizeChanged(); //Extract the representation's Layout, if any QObject *layout = nullptr; setMinimumSize(QSize(0, 0)); setMaximumSize(QSize(DIALOGSIZE_MAX, DIALOGSIZE_MAX)); //Search a child that has the needed Layout properties //HACK: here we are not type safe, but is the only way to access to a pointer of Layout foreach (QObject *child, mainItem->children()) { //find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid() && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid() && child->property("fillWidth").isValid() && child->property("fillHeight").isValid() ) { layout = child; break; } } d->mainItemLayout = layout; if (layout) { //Why queued connections? //we need to be sure that the properties are //already *all* updated when we call the management code connect(layout, SIGNAL(minimumWidthChanged()), this, SLOT(updateMinimumWidth())); connect(layout, SIGNAL(minimumHeightChanged()), this, SLOT(updateMinimumHeight())); connect(layout, SIGNAL(maximumWidthChanged()), this, SLOT(updateMaximumWidth())); connect(layout, SIGNAL(maximumHeightChanged()), this, SLOT(updateMaximumHeight())); d->updateLayoutParameters(); } } //if this is called in Component.onCompleted we have to wait a loop the item is added to a scene emit mainItemChanged(); } } void DialogPrivate::slotMainItemSizeChanged() { syncToMainItemSize(); } QQuickItem *Dialog::visualParent() const { return d->visualParent; } void Dialog::setVisualParent(QQuickItem *visualParent) { if (d->visualParent == visualParent) { return; } d->visualParent = visualParent; emit visualParentChanged(); if (visualParent) { if (visualParent->window()) { setTransientParent(visualParent->window()); } if (d->mainItem) { d->syncToMainItemSize(); } } } QPoint Dialog::popupPosition(QQuickItem *item, const QSize &size) { if (!item) { //If no item was specified try to align at the center of the parent view QQuickItem *parentItem = qobject_cast(parent()); if (parentItem) { QScreen *screen = parentItem->window()->screen(); switch (d->location) { case Plasma::Types::TopEdge: return QPoint(screen->availableGeometry().center().x() - size.width() / 2, screen->availableGeometry().y()); case Plasma::Types::LeftEdge: return QPoint(screen->availableGeometry().x(), screen->availableGeometry().center().y() - size.height() / 2); case Plasma::Types::RightEdge: return QPoint(screen->availableGeometry().right() - size.width(), screen->availableGeometry().center().y() - size.height() / 2); case Plasma::Types::BottomEdge: return QPoint(screen->availableGeometry().center().x() - size.width() / 2, screen->availableGeometry().bottom() - size.height()); //Default center in the screen default: return screen->geometry().center() - QPoint(size.width() / 2, size.height() / 2); } } else { return QPoint(); } } QPointF pos = item->mapToScene(QPointF(0, 0)); if (item->window()) { pos = item->window()->mapToGlobal(pos.toPoint()); } else { return QPoint(); } //if the item is in a dock or in a window that ignores WM we want to position the popups outside of the dock const KWindowInfo winInfo(item->window()->winId(), NET::WMWindowType); const bool outsideParentWindow = ((winInfo.windowType(NET::AllTypesMask) == NET::Dock) || (item->window()->flags() & Qt::X11BypassWindowManagerHint)) && item->window()->mask().isNull(); QRect parentGeometryBounds; if (outsideParentWindow) { parentGeometryBounds = item->window()->geometry(); } else { parentGeometryBounds = item->mapRectToScene(item->boundingRect()).toRect(); if (item->window()) { parentGeometryBounds.moveTopLeft(item->window()->mapToGlobal(parentGeometryBounds.topLeft())); pos = parentGeometryBounds.topLeft(); } } const QPoint topPoint(pos.x() + (item->mapRectToScene(item->boundingRect()).width() - size.width()) / 2, parentGeometryBounds.top() - size.height()); const QPoint bottomPoint(pos.x() + (item->mapRectToScene(item->boundingRect()).width() - size.width()) / 2, parentGeometryBounds.bottom()); const QPoint leftPoint(parentGeometryBounds.left() - size.width(), pos.y() + (item->mapRectToScene(item->boundingRect()).height() - size.height()) / 2); const QPoint rightPoint(parentGeometryBounds.right(), pos.y() + (item->mapRectToScene(item->boundingRect()).height() - size.height()) / 2); QPoint dialogPos; if (d->location == Plasma::Types::TopEdge) { dialogPos = bottomPoint; } else if (d->location == Plasma::Types::LeftEdge) { dialogPos = rightPoint; } else if (d->location == Plasma::Types::RightEdge) { dialogPos = leftPoint; } else { // Types::BottomEdge dialogPos = topPoint; } //find the correct screen for the item //we do not rely on item->window()->screen() because //QWindow::screen() is always only the screen where the window gets first created //not actually the current window. See QWindow::screen() documentation QRect avail = item->window()->screen()->availableGeometry(); if (outsideParentWindow && d->frameSvgItem->enabledBorders() != Plasma::FrameSvg::AllBorders) { //make the panel look it's inside the panel, in order to not make it look cut switch (d->location) { case Plasma::Types::LeftEdge: case Plasma::Types::RightEdge: avail.setTop(qMax(avail.top(), parentGeometryBounds.top())); avail.setBottom(qMin(avail.bottom(), parentGeometryBounds.bottom())); break; default: avail.setLeft(qMax(avail.left(), parentGeometryBounds.left())); avail.setRight(qMin(avail.right(), parentGeometryBounds.right())); break; } } if (dialogPos.x() < avail.left()) { // popup hits lhs if (d->location != Plasma::Types::LeftEdge || d->location == Plasma::Types::RightEdge) { // move it dialogPos.setX(avail.left()); } else { // swap edge dialogPos.setX(rightPoint.x()); } } if (dialogPos.x() + size.width() > avail.right()) { // popup hits rhs if (d->location == Plasma::Types::TopEdge || d->location == Plasma::Types::BottomEdge) { dialogPos.setX(qMax(avail.left(), (avail.right() - size.width() + 1))); } else { dialogPos.setX(leftPoint.x()); } } if (dialogPos.y() < avail.top()) { // hitting top if (d->location == Plasma::Types::LeftEdge || d->location == Plasma::Types::RightEdge) { dialogPos.setY(avail.top()); } else { dialogPos.setY(bottomPoint.y()); } } if (dialogPos.y() + size.height() > avail.bottom()) { // hitting bottom if (d->location == Plasma::Types::TopEdge || d->location == Plasma::Types::BottomEdge) { dialogPos.setY(topPoint.y()); } else { dialogPos.setY(qMax(avail.top(), (avail.bottom() - size.height() + 1))); } } return dialogPos; } Plasma::Types::Location Dialog::location() const { return d->location; } void Dialog::setLocation(Plasma::Types::Location location) { if (d->location == location) { return; } d->location = location; emit locationChanged(); if (d->mainItem) { d->syncToMainItemSize(); } } QObject *Dialog::margins() const { return d->frameSvgItem->fixedMargins(); } void Dialog::setFramelessFlags(Qt::WindowFlags flags) { setFlags(Qt::FramelessWindowHint | flags); d->applyType(); emit flagsChanged(); } void Dialog::adjustGeometry(const QRect &geom) { setGeometry(geom); } void Dialog::resizeEvent(QResizeEvent* re) { QQuickWindow::resizeEvent(re); //it's a spontaneous event generated in qguiapplication.cpp QGuiApplicationPrivate::processWindowScreenChangedEvent //QWindowSystemInterfacePrivate::GeometryChangeEvent gce(window, QHighDpi::fromNativePixels(window->handle()->geometry(), window), QRect()); //This happens before the first show event when there is more than one screen, //right after the window has been created, the window is still 0x0, //but the resize event gets delivered with 0x0 again and executed with all the bad side effects //this seems to happen for every window when there are multiple screens, so something we have probably to watch out for in the future if (re->size().isEmpty() || re->size() == re->oldSize()) { return; } //A dialog can be resized even if no mainItem has ever been set if (!d->mainItem) { return; } d->mainItem->disconnect(this); d->frameSvgItem->setSize(QSizeF(re->size().width(), re->size().height())); auto margin = d->frameSvgItem->fixedMargins(); d->mainItem->setPosition(QPointF(margin->left(), margin->top())); d->mainItem->setSize(QSize(re->size().width() - margin->left() - margin->right(), re->size().height() - margin->top() - margin->bottom())); QObject::connect(d->mainItem, SIGNAL(widthChanged()), this, SLOT(slotMainItemSizeChanged())); QObject::connect(d->mainItem, SIGNAL(heightChanged()), this, SLOT(slotMainItemSizeChanged())); } void Dialog::setType(WindowType type) { if (type == d->type) { return; } d->type = type; d->applyType(); emit typeChanged(); } Dialog::WindowType Dialog::type() const { return d->type; } void Dialog::focusInEvent(QFocusEvent *ev) { QQuickWindow::focusInEvent(ev); } void Dialog::focusOutEvent(QFocusEvent *ev) { if (d->hideOnWindowDeactivate) { bool parentHasFocus = false; QWindow *parentWindow = transientParent(); while (parentWindow) { if (parentWindow->isActive() && !(parentWindow->flags() & Qt::WindowDoesNotAcceptFocus)) { parentHasFocus = true; break; } parentWindow = parentWindow->transientParent(); } const QWindow *focusWindow = QGuiApplication::focusWindow(); bool childHasFocus = focusWindow && ((focusWindow->isActive() && isAncestorOf(focusWindow)) || focusWindow->type() & Qt::Popup); const bool viewClicked = qobject_cast(focusWindow) || qobject_cast(focusWindow) || qobject_cast(focusWindow); if (viewClicked || (!parentHasFocus && !childHasFocus)) { setVisible(false); emit windowDeactivated(); } } QQuickWindow::focusOutEvent(ev); } void Dialog::showEvent(QShowEvent *event) { if (d->backgroundHints != Dialog::NoBackground) { DialogShadows::self()->addWindow(this, d->frameSvgItem->enabledBorders()); } KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); QQuickWindow::showEvent(event); } bool Dialog::event(QEvent *event) { if (event->type() == QEvent::Expose) { auto ee = static_cast(event); if (!KWindowSystem::isPlatformWayland() || ee->region().isNull()) { return QQuickWindow::event(event); } /* * expose event is the only place where to correctly * register our wayland extensions, as showevent is a bit too * soon and the platform window isn't shown yet * create a shell surface every time the window gets exposed * (only the first expose event, guarded by shelldurface existence) * and tear it down when the window gets hidden * see https://phabricator.kde.org/T6064 */ #if HAVE_KWAYLAND //sometimes non null regions arrive even for non visible windows //for which surface creation would fail if (!d->shellSurface && isVisible()) { KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); d->setupWaylandIntegration(); d->updateVisibility(true); d->updateTheme(); } #endif } else if (event->type() == QEvent::PlatformSurface) { const QPlatformSurfaceEvent *pSEvent = static_cast(event); if (pSEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager); } } else if (event->type() == QEvent::Show) { d->updateVisibility(true); } else if (event->type() == QEvent::Hide) { d->updateVisibility(false); #if HAVE_KWAYLAND delete d->shellSurface; d->shellSurface = nullptr; #endif } else if (event->type() == QEvent::Move) { #if HAVE_KWAYLAND if (d->shellSurface) { QMoveEvent *me = static_cast(event); d->shellSurface->setPosition(me->pos()); } #endif } /*Fitt's law: if the containment has margins, and the mouse cursor clicked * on the mouse edge, forward the click in the containment boundaries */ if (d->mainItem && !d->mainItem->size().isEmpty()) { switch (event->type()) { case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { QMouseEvent *me = static_cast(event); //don't mess with position if the cursor is actually outside the view: //somebody is doing a click and drag that must not break when the cursor i outside if (geometry().contains(me->screenPos().toPoint()) && !d->mainItemContainsPosition(me->windowPos())) { QMouseEvent me2(me->type(), d->positionAdjustedForMainItem(me->windowPos()), d->positionAdjustedForMainItem(me->windowPos()), d->positionAdjustedForMainItem(me->windowPos()) + position(), me->button(), me->buttons(), me->modifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &me2); } return true; } break; } case QEvent::Wheel: { QWheelEvent *we = static_cast(event); #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) const QPoint pos = we->pos(); #else const QPoint pos = we->position().toPoint(); #endif if (!d->mainItemContainsPosition(pos)) { #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) QWheelEvent we2(d->positionAdjustedForMainItem(pos), d->positionAdjustedForMainItem(pos) + position(), we->pixelDelta(), we->angleDelta(), we->angleDelta().y(), we->orientation(), we->buttons(), we->modifiers(), we->phase()); #else QWheelEvent we2(d->positionAdjustedForMainItem(pos), d->positionAdjustedForMainItem(pos) + position(), we->pixelDelta(), we->angleDelta(), we->buttons(), we->modifiers(), we->phase(), false /*inverted*/); #endif if (isVisible()) { QCoreApplication::sendEvent(this, &we2); } return true; } break; } case QEvent::DragEnter: { QDragEnterEvent *de = static_cast(event); if (!d->mainItemContainsPosition(de->pos())) { QDragEnterEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &de2); } return true; } break; } //DragLeave just works case QEvent::DragLeave: break; case QEvent::DragMove: { QDragMoveEvent *de = static_cast(event); if (!d->mainItemContainsPosition(de->pos())) { QDragMoveEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &de2); } return true; } break; } case QEvent::Drop: { QDropEvent *de = static_cast(event); if (!d->mainItemContainsPosition(de->pos())) { QDropEvent de2(d->positionAdjustedForMainItem(de->pos()).toPoint(), de->possibleActions(), de->mimeData(), de->mouseButtons(), de->keyboardModifiers()); if (isVisible()) { QCoreApplication::sendEvent(this, &de2); } return true; } break; } default: break; } } return QQuickWindow::event(event); } void Dialog::hideEvent(QHideEvent *event) { QQuickWindow::hideEvent(event); } void Dialog::classBegin() { d->componentComplete = false; } void Dialog::componentComplete() { d->componentComplete = true; QQuickWindow::setVisible(d->visible); d->updateTheme(); } bool Dialog::hideOnWindowDeactivate() const { return d->hideOnWindowDeactivate; } void Dialog::setHideOnWindowDeactivate(bool hide) { if (d->hideOnWindowDeactivate == hide) { return; } d->hideOnWindowDeactivate = hide; emit hideOnWindowDeactivateChanged(); } bool Dialog::isOutputOnly() const { return d->outputOnly; } void Dialog::setOutputOnly(bool outputOnly) { if (d->outputOnly == outputOnly) { return; } d->outputOnly = outputOnly; emit outputOnlyChanged(); } void Dialog::setVisible(bool visible) { //only update real visibility when we have finished component completion //and all flags have been set d->visible = visible; if (d->componentComplete) { if (visible && d->visualParent) { setPosition(popupPosition(d->visualParent, size())); } // Bug 381242: Qt remembers minimize state and re-applies it when showing setWindowStates(windowStates() & ~Qt::WindowMinimized); QQuickWindow::setVisible(visible); //signal will be emitted and proxied from the QQuickWindow code } else { emit visibleChangedProxy(); } } bool Dialog::isVisible() const { if (d->componentComplete) { return QQuickWindow::isVisible(); } return d->visible; } Dialog::BackgroundHints Dialog::backgroundHints() const { return d->backgroundHints; } void Dialog::setBackgroundHints(Dialog::BackgroundHints hints) { if (d->backgroundHints == hints) { return; } d->backgroundHints = hints; d->updateTheme(); emit backgroundHintsChanged(); } } #include "moc_dialog.cpp" diff --git a/src/plasmaquick/dialogshadows.cpp b/src/plasmaquick/dialogshadows.cpp index 581e200c6..8c7e27e27 100644 --- a/src/plasmaquick/dialogshadows.cpp +++ b/src/plasmaquick/dialogshadows.cpp @@ -1,746 +1,695 @@ /* * Copyright 2011 by Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2, * 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 Library 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 "dialogshadows_p.h" #include #include #include #include #if HAVE_X11 #include #include #include #include #include #endif #if HAVE_KWAYLAND -#include -#include +#include "waylandintegration_p.h" #include #include #include -#include #endif #include class DialogShadows::Private { public: Private(DialogShadows *shadows) : q(shadows) #if HAVE_X11 , _connection(nullptr) , _gc(0x0) , m_isX11(KWindowSystem::isPlatformX11()) #endif { - setupWaylandIntegration(); } ~Private() { // Do not call clearPixmaps() from here: it creates new QPixmap(), // which causes a crash when application is stopping. freeX11Pixmaps(); } void freeX11Pixmaps(); void freeWaylandBuffers(); void clearPixmaps(); void setupPixmaps(); Qt::HANDLE createPixmap(const QPixmap &source); void initPixmap(const QString &element); QPixmap initEmptyPixmap(const QSize &size); void updateShadow(const QWindow *window, Plasma::FrameSvg::EnabledBorders); void updateShadowX11(const QWindow *window, Plasma::FrameSvg::EnabledBorders); void updateShadowWayland(const QWindow *window, Plasma::FrameSvg::EnabledBorders); void clearShadow(const QWindow *window); void clearShadowX11(const QWindow *window); void clearShadowWayland(const QWindow *window); void updateShadows(); void windowDestroyed(QObject *deletedObject); void setupData(Plasma::FrameSvg::EnabledBorders enabledBorders); - void setupWaylandIntegration(); - DialogShadows *q; QList m_shadowPixmaps; QPixmap m_emptyCornerPix; QPixmap m_emptyCornerLeftPix; QPixmap m_emptyCornerTopPix; QPixmap m_emptyCornerRightPix; QPixmap m_emptyCornerBottomPix; QPixmap m_emptyVerticalPix; QPixmap m_emptyHorizontalPix; #if HAVE_X11 //! xcb connection xcb_connection_t *_connection; //! graphical context xcb_gcontext_t _gc; bool m_isX11; #endif #if HAVE_KWAYLAND struct Wayland { - KWayland::Client::ShadowManager *manager = nullptr; - KWayland::Client::ShmPool *shmPool = nullptr; - KWayland::Client::PlasmaShell *plasmaShell = nullptr; - QList shadowBuffers; }; Wayland m_wayland; #endif QHash > data; QHash m_windows; }; class DialogShadowsSingleton { public: DialogShadowsSingleton() { } DialogShadows self; }; Q_GLOBAL_STATIC(DialogShadowsSingleton, privateDialogShadowsSelf) DialogShadows::DialogShadows(QObject *parent, const QString &prefix) : Plasma::Svg(parent), d(new Private(this)) { setImagePath(prefix); connect(this, SIGNAL(repaintNeeded()), this, SLOT(updateShadows())); } DialogShadows::~DialogShadows() { delete d; } DialogShadows *DialogShadows::self() { return &privateDialogShadowsSelf->self; } void DialogShadows::addWindow(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { if (!window) { return; } d->m_windows[window] = enabledBorders; d->updateShadow(window, enabledBorders); connect(window, SIGNAL(destroyed(QObject*)), this, SLOT(windowDestroyed(QObject*)), Qt::UniqueConnection); } void DialogShadows::removeWindow(const QWindow *window) { if (!d->m_windows.contains(window)) { return; } d->m_windows.remove(window); disconnect(window, nullptr, this, nullptr); d->clearShadow(window); if (d->m_windows.isEmpty()) { d->clearPixmaps(); } } void DialogShadows::setEnabledBorders(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { if (!window || !d->m_windows.contains(window)) { return; } d->updateShadow(window, enabledBorders); } void DialogShadows::Private::windowDestroyed(QObject *deletedObject) { m_windows.remove(static_cast(deletedObject)); if (m_windows.isEmpty()) { clearPixmaps(); } } void DialogShadows::Private::updateShadows() { setupPixmaps(); QHash::const_iterator i; for (i = m_windows.constBegin(); i != m_windows.constEnd(); ++i) { updateShadow(i.key(), i.value()); } } Qt::HANDLE DialogShadows::Private::createPixmap(const QPixmap &source) { // do nothing for invalid pixmaps if (source.isNull()) { return nullptr; } /* in some cases, pixmap handle is invalid. This is the case notably when Qt uses to RasterEngine. In this case, we create an X11 Pixmap explicitly and draw the source pixmap on it. */ #if HAVE_X11 if (!m_isX11) { return nullptr; } // check connection if (!_connection) { _connection = QX11Info::connection(); } const int width(source.width()); const int height(source.height()); // create X11 pixmap Pixmap pixmap = XCreatePixmap(QX11Info::display(), QX11Info::appRootWindow(), width, height, 32); // check gc if (!_gc) { _gc = xcb_generate_id(_connection); xcb_create_gc(_connection, _gc, pixmap, 0, nullptr); } // // create explicitly shared QPixmap from it // QPixmap dest( QPixmap::fromX11Pixmap( pixmap, QPixmap::ExplicitlyShared ) ); // // // create surface for pixmap // { // QPainter painter( &dest ); // painter.setCompositionMode( QPainter::CompositionMode_Source ); // painter.drawPixmap( 0, 0, source ); // } // // // return pixmap; QImage image(source.toImage()); xcb_put_image( _connection, XCB_IMAGE_FORMAT_Z_PIXMAP, pixmap, _gc, image.width(), image.height(), 0, 0, 0, 32, image.sizeInBytes(), image.constBits()); return (Qt::HANDLE)pixmap; #else return nullptr; #endif } void DialogShadows::Private::initPixmap(const QString &element) { m_shadowPixmaps << q->pixmap(element); } QPixmap DialogShadows::Private::initEmptyPixmap(const QSize &size) { #if HAVE_X11 if (!m_isX11) { return QPixmap(); } QPixmap tempEmptyPix(size); if (!size.isEmpty()) { tempEmptyPix.fill(Qt::transparent); } return tempEmptyPix; #else Q_UNUSED(size) return QPixmap(); #endif } void DialogShadows::Private::setupPixmaps() { clearPixmaps(); initPixmap(QStringLiteral("shadow-top")); initPixmap(QStringLiteral("shadow-topright")); initPixmap(QStringLiteral("shadow-right")); initPixmap(QStringLiteral("shadow-bottomright")); initPixmap(QStringLiteral("shadow-bottom")); initPixmap(QStringLiteral("shadow-bottomleft")); initPixmap(QStringLiteral("shadow-left")); initPixmap(QStringLiteral("shadow-topleft")); m_emptyCornerPix = initEmptyPixmap(QSize(1, 1)); m_emptyCornerLeftPix = initEmptyPixmap(QSize(q->elementSize(QStringLiteral("shadow-topleft")).width(), 1)); m_emptyCornerTopPix = initEmptyPixmap(QSize(1, q->elementSize(QStringLiteral("shadow-topleft")).height())); m_emptyCornerRightPix = initEmptyPixmap(QSize(q->elementSize(QStringLiteral("shadow-bottomright")).width(), 1)); m_emptyCornerBottomPix = initEmptyPixmap(QSize(1, q->elementSize(QStringLiteral("shadow-bottomright")).height())); m_emptyVerticalPix = initEmptyPixmap(QSize(1, q->elementSize(QStringLiteral("shadow-left")).height())); m_emptyHorizontalPix = initEmptyPixmap(QSize(q->elementSize(QStringLiteral("shadow-top")).width(), 1)); #if HAVE_KWAYLAND - if (m_wayland.shmPool) { + if (KWayland::Client::ShmPool *shmPool = WaylandIntegration::self()->waylandShmPool()) { for (auto it = m_shadowPixmaps.constBegin(); it != m_shadowPixmaps.constEnd(); ++it) { - m_wayland.shadowBuffers << m_wayland.shmPool->createBuffer(it->toImage()); + m_wayland.shadowBuffers << shmPool->createBuffer(it->toImage()); } } #endif } void DialogShadows::Private::setupData(Plasma::FrameSvg::EnabledBorders enabledBorders) { #if HAVE_X11 if (!m_isX11) { return; } //shadow-top if (enabledBorders & Plasma::FrameSvg::TopBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[0])); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyHorizontalPix)); } //shadow-topright if (enabledBorders & Plasma::FrameSvg::TopBorder && enabledBorders & Plasma::FrameSvg::RightBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[1])); } else if (enabledBorders & Plasma::FrameSvg::TopBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerTopPix)); } else if (enabledBorders & Plasma::FrameSvg::RightBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerRightPix)); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); } //shadow-right if (enabledBorders & Plasma::FrameSvg::RightBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[2])); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyVerticalPix)); } //shadow-bottomright if (enabledBorders & Plasma::FrameSvg::BottomBorder && enabledBorders & Plasma::FrameSvg::RightBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[3])); } else if (enabledBorders & Plasma::FrameSvg::BottomBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerBottomPix)); } else if (enabledBorders & Plasma::FrameSvg::RightBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerRightPix)); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); } //shadow-bottom if (enabledBorders & Plasma::FrameSvg::BottomBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[4])); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyHorizontalPix)); } //shadow-bottomleft if (enabledBorders & Plasma::FrameSvg::BottomBorder && enabledBorders & Plasma::FrameSvg::LeftBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[5])); } else if (enabledBorders & Plasma::FrameSvg::BottomBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerBottomPix)); } else if (enabledBorders & Plasma::FrameSvg::LeftBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerLeftPix)); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); } //shadow-left if (enabledBorders & Plasma::FrameSvg::LeftBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[6])); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyVerticalPix)); } //shadow-topleft if (enabledBorders & Plasma::FrameSvg::TopBorder && enabledBorders & Plasma::FrameSvg::LeftBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_shadowPixmaps[7])); } else if (enabledBorders & Plasma::FrameSvg::TopBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerTopPix)); } else if (enabledBorders & Plasma::FrameSvg::LeftBorder) { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerLeftPix)); } else { data[enabledBorders] << reinterpret_cast(createPixmap(m_emptyCornerPix)); } #endif int left, top, right, bottom = 0; QSize marginHint; if (enabledBorders & Plasma::FrameSvg::TopBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-top-margin")); if (marginHint.isValid()) { top = marginHint.height(); } else { top = m_shadowPixmaps[0].height(); // top } } else { top = 1; } if (enabledBorders & Plasma::FrameSvg::RightBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-right-margin")); if (marginHint.isValid()) { right = marginHint.width(); } else { right = m_shadowPixmaps[2].width(); // right } } else { right = 1; } if (enabledBorders & Plasma::FrameSvg::BottomBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-bottom-margin")); if (marginHint.isValid()) { bottom = marginHint.height(); } else { bottom = m_shadowPixmaps[4].height(); // bottom } } else { bottom = 1; } if (enabledBorders & Plasma::FrameSvg::LeftBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-left-margin")); if (marginHint.isValid()) { left = marginHint.width(); } else { left = m_shadowPixmaps[6].width(); // left } } else { left = 1; } data[enabledBorders] << top << right << bottom << left; } void DialogShadows::Private::freeX11Pixmaps() { #if HAVE_X11 if (!m_isX11) { return; } auto *display = QX11Info::display(); if (!display) { return; } foreach (const QPixmap &pixmap, m_shadowPixmaps) { if (!pixmap.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(pixmap))); } } if (!m_emptyCornerPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerPix))); } if (!m_emptyCornerBottomPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerBottomPix))); } if (!m_emptyCornerLeftPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerLeftPix))); } if (!m_emptyCornerRightPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerRightPix))); } if (!m_emptyCornerTopPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyCornerTopPix))); } if (!m_emptyVerticalPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyVerticalPix))); } if (!m_emptyHorizontalPix.isNull()) { XFreePixmap(display, reinterpret_cast(createPixmap(m_emptyHorizontalPix))); } #endif } void DialogShadows::Private::clearPixmaps() { #if HAVE_X11 freeX11Pixmaps(); m_emptyCornerPix = QPixmap(); m_emptyCornerBottomPix = QPixmap(); m_emptyCornerLeftPix = QPixmap(); m_emptyCornerRightPix = QPixmap(); m_emptyCornerTopPix = QPixmap(); m_emptyVerticalPix = QPixmap(); m_emptyHorizontalPix = QPixmap(); #endif freeWaylandBuffers(); m_shadowPixmaps.clear(); data.clear(); } void DialogShadows::Private::freeWaylandBuffers() { #if HAVE_KWAYLAND m_wayland.shadowBuffers.clear(); #endif } void DialogShadows::Private::updateShadow(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { #if HAVE_X11 if (m_isX11) { updateShadowX11(window, enabledBorders); } #endif #if HAVE_KWAYLAND - if (m_wayland.manager) { + if (WaylandIntegration::self()->waylandShadowManager()) { updateShadowWayland(window, enabledBorders); } #endif } void DialogShadows::Private::updateShadowX11(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { #if HAVE_X11 if (m_shadowPixmaps.isEmpty()) { setupPixmaps(); } if (!data.contains(enabledBorders)) { setupData(enabledBorders); } Display *dpy = QX11Info::display(); Atom atom = XInternAtom(dpy, "_KDE_NET_WM_SHADOW", False); //qDebug() << "going to set the shadow of" << window->winId() << "to" << data; XChangeProperty(dpy, window->winId(), atom, XA_CARDINAL, 32, PropModeReplace, reinterpret_cast(data[enabledBorders].constData()), data[enabledBorders].size()); #endif } void DialogShadows::Private::updateShadowWayland(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders) { #if HAVE_KWAYLAND - if (!m_wayland.shmPool) { + if (!WaylandIntegration::self()->waylandShmPool()) { return; } if (m_wayland.shadowBuffers.isEmpty()) { setupPixmaps(); } // TODO: check whether the surface already has a shadow KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(const_cast(window)); if (!surface) { return; } - auto shadow = m_wayland.manager->createShadow(surface, surface); + KWayland::Client::ShadowManager *manager = WaylandIntegration::self()->waylandShadowManager(); + auto shadow = manager->createShadow(surface, surface); //shadow-top if (enabledBorders & Plasma::FrameSvg::TopBorder) { shadow->attachTop(m_wayland.shadowBuffers.at(0)); } //shadow-topright if (enabledBorders & Plasma::FrameSvg::TopBorder && enabledBorders & Plasma::FrameSvg::RightBorder) { shadow->attachTopRight(m_wayland.shadowBuffers.at(1)); } //shadow-right if (enabledBorders & Plasma::FrameSvg::RightBorder) { shadow->attachRight(m_wayland.shadowBuffers.at(2)); } //shadow-bottomright if (enabledBorders & Plasma::FrameSvg::BottomBorder && enabledBorders & Plasma::FrameSvg::RightBorder) { shadow->attachBottomRight(m_wayland.shadowBuffers.at(3)); } //shadow-bottom if (enabledBorders & Plasma::FrameSvg::BottomBorder) { shadow->attachBottom(m_wayland.shadowBuffers.at(4)); } //shadow-bottomleft if (enabledBorders & Plasma::FrameSvg::BottomBorder && enabledBorders & Plasma::FrameSvg::LeftBorder) { shadow->attachBottomLeft(m_wayland.shadowBuffers.at(5)); } //shadow-left if (enabledBorders & Plasma::FrameSvg::LeftBorder) { shadow->attachLeft(m_wayland.shadowBuffers.at(6)); } //shadow-topleft if (enabledBorders & Plasma::FrameSvg::TopBorder && enabledBorders & Plasma::FrameSvg::LeftBorder) { shadow->attachTopLeft(m_wayland.shadowBuffers.at(7)); } QSize marginHint; QMarginsF margins; if (enabledBorders & Plasma::FrameSvg::TopBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-top-margin")); if (marginHint.isValid()) { margins.setTop(marginHint.height()); } else { margins.setTop(m_shadowPixmaps[0].height()); } } if (enabledBorders & Plasma::FrameSvg::RightBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-right-margin")); if (marginHint.isValid()) { margins.setRight(marginHint.width()); } else { margins.setRight(m_shadowPixmaps[2].width()); } } if (enabledBorders & Plasma::FrameSvg::BottomBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-bottom-margin")); if (marginHint.isValid()) { margins.setBottom(marginHint.height()); } else { margins.setBottom(m_shadowPixmaps[4].height()); } } if (enabledBorders & Plasma::FrameSvg::LeftBorder) { marginHint = q->elementSize(QStringLiteral("shadow-hint-left-margin")); if (marginHint.isValid()) { margins.setLeft(marginHint.width()); } else { margins.setLeft(m_shadowPixmaps[6].width()); } } shadow->setOffsets(margins); shadow->commit(); surface->commit(KWayland::Client::Surface::CommitFlag::None); #endif } void DialogShadows::Private::clearShadow(const QWindow *window) { if (!static_cast(window)->surfaceHandle()) { qWarning() << "Cannot clear shadow from window without native surface!"; return; } #if HAVE_X11 if (m_isX11) { clearShadowX11(window); } #endif #if HAVE_KWAYLAND - if (m_wayland.manager) { + if (WaylandIntegration::self()->waylandShadowManager()) { clearShadowWayland(window); } #endif } void DialogShadows::Private::clearShadowX11(const QWindow* window) { #if HAVE_X11 Display *dpy = QX11Info::display(); Atom atom = XInternAtom(dpy, "_KDE_NET_WM_SHADOW", False); XDeleteProperty(dpy, window->winId(), atom); #endif } void DialogShadows::Private::clearShadowWayland(const QWindow *window) { #if HAVE_KWAYLAND KWayland::Client::Surface *surface = KWayland::Client::Surface::fromWindow(const_cast(window)); if (!surface) { return; } - m_wayland.manager->removeShadow(surface); + KWayland::Client::ShadowManager *manager = WaylandIntegration::self()->waylandShadowManager(); + manager->removeShadow(surface); surface->commit(KWayland::Client::Surface::CommitFlag::None); #endif } bool DialogShadows::enabled() const { return hasElement(QStringLiteral("shadow-left")); } -KWayland::Client::PlasmaShell *DialogShadows::waylandPlasmaShellInterface() const -{ -#if HAVE_KWAYLAND - return d->m_wayland.plasmaShell; -#else - return nullptr; -#endif -} - -void DialogShadows::Private::setupWaylandIntegration() -{ -#if HAVE_KWAYLAND - if (!KWindowSystem::isPlatformWayland()) { - return; - } - using namespace KWayland::Client; - ConnectionThread *connection = ConnectionThread::fromApplication(q); - if (!connection) { - return; - } - Registry *registry = new Registry(q); - registry->create(connection); - connect(registry, &Registry::shadowAnnounced, q, - [this, registry] (quint32 name, quint32 version) { - m_wayland.manager = registry->createShadowManager(name, version, q); - updateShadows(); - }, Qt::QueuedConnection - ); - connect(registry, &Registry::shmAnnounced, q, - [this, registry] (quint32 name, quint32 version) { - m_wayland.shmPool = registry->createShmPool(name, version, q); - updateShadows(); - }, Qt::QueuedConnection - ); - connect(registry, &Registry::plasmaShellAnnounced, q, - [this, registry] (quint32 name, quint32 version) { - m_wayland.plasmaShell = registry->createPlasmaShell(name, version, q); - } - ); - registry->setup(); - connection->roundtrip(); -#endif -} - #include "moc_dialogshadows_p.cpp" diff --git a/src/plasmaquick/dialogshadows_p.h b/src/plasmaquick/dialogshadows_p.h index c75c0bb24..2e106b56c 100644 --- a/src/plasmaquick/dialogshadows_p.h +++ b/src/plasmaquick/dialogshadows_p.h @@ -1,63 +1,53 @@ /* * Copyright 2011 by Aaron Seigo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License version 2, * 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 Library 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. */ #ifndef PLASMA_DIALOGSHADOWS_H #define PLASMA_DIALOGSHADOWS_H #include #include "plasma/framesvg.h" #include "plasma/svg.h" -namespace KWayland -{ - namespace Client - { - class PlasmaShell; - } -} - class DialogShadows : public Plasma::Svg { Q_OBJECT public: explicit DialogShadows(QObject *parent = nullptr, const QString &prefix = QStringLiteral("dialogs/background")); ~DialogShadows(); static DialogShadows *self(); void addWindow(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders = Plasma::FrameSvg::AllBorders); void removeWindow(const QWindow *window); void setEnabledBorders(const QWindow *window, Plasma::FrameSvg::EnabledBorders enabledBorders = Plasma::FrameSvg::AllBorders); bool enabled() const; - KWayland::Client::PlasmaShell *waylandPlasmaShellInterface() const; - private: class Private; Private *const d; Q_PRIVATE_SLOT(d, void updateShadows()) Q_PRIVATE_SLOT(d, void windowDestroyed(QObject *deletedObject)) }; #endif diff --git a/src/plasmaquick/waylandintegration.cpp b/src/plasmaquick/waylandintegration.cpp new file mode 100644 index 000000000..5bface56e --- /dev/null +++ b/src/plasmaquick/waylandintegration.cpp @@ -0,0 +1,132 @@ +/* + * Copyright 2020 Vlad Zahorodnii + * + * Based on WaylandIntegration from kwayland-integration + * + * Copyright 2014 Martin Gräßlin + * Copyright 2015 Marco Martin + * + * 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 "waylandintegration_p.h" +#include "debug_p.h" + +#include +#include +#include +#include +#include + +#include + +class WaylandIntegrationSingleton +{ +public: + WaylandIntegration self; +}; + +Q_GLOBAL_STATIC(WaylandIntegrationSingleton, privateWaylandIntegrationSelf) + +WaylandIntegration::WaylandIntegration(QObject *parent) + : QObject(parent) +{ + setupKWaylandIntegration(); +} + +WaylandIntegration::~WaylandIntegration() +{ +} + +KWayland::Client::PlasmaShell *WaylandIntegration::waylandPlasmaShell() +{ + if (!m_waylandPlasmaShell && m_registry) { + const KWayland::Client::Registry::AnnouncedInterface interface = + m_registry->interface(KWayland::Client::Registry::Interface::PlasmaShell); + + if (interface.name == 0) { + qCWarning(LOG_PLASMAQUICK) << "The compositor does not support the plasma shell protocol"; + return nullptr; + } + + m_waylandPlasmaShell = m_registry->createPlasmaShell(interface.name, interface.version, qApp); + + connect(m_waylandPlasmaShell, &KWayland::Client::PlasmaShell::removed, this, [this]() { + m_waylandPlasmaShell->deleteLater(); + }); + } + + return m_waylandPlasmaShell; +} + +KWayland::Client::ShadowManager *WaylandIntegration::waylandShadowManager() +{ + if (!m_waylandShadowManager && m_registry) { + const KWayland::Client::Registry::AnnouncedInterface interface = + m_registry->interface(KWayland::Client::Registry::Interface::Shadow); + + if (interface.name == 0) { + qCWarning(LOG_PLASMAQUICK) << "The compositor does not support the shadow protocol"; + return nullptr; + } + + m_waylandShadowManager = m_registry->createShadowManager(interface.name, interface.version, qApp); + + connect(m_waylandShadowManager, &KWayland::Client::ShadowManager::removed, this, [this]() { + m_waylandShadowManager->deleteLater(); + }); + } + + return m_waylandShadowManager; +} + +KWayland::Client::ShmPool *WaylandIntegration::waylandShmPool() +{ + if (!m_waylandShmPool && m_registry) { + const KWayland::Client::Registry::AnnouncedInterface interface = + m_registry->interface(KWayland::Client::Registry::Interface::Shm); + + if (interface.name == 0) { + return nullptr; + } + + m_waylandShmPool = m_registry->createShmPool(interface.name, interface.version, qApp); + + connect(m_waylandShmPool, &KWayland::Client::ShmPool::removed, this, [this]() { + m_waylandShmPool->deleteLater(); + }); + } + + return m_waylandShmPool; +} + +WaylandIntegration *WaylandIntegration::self() +{ + return &privateWaylandIntegrationSelf()->self; +} + +void WaylandIntegration::setupKWaylandIntegration() +{ + KWayland::Client::ConnectionThread *connection = KWayland::Client::ConnectionThread::fromApplication(this); + if (!connection) { + return; + } + + m_registry = new KWayland::Client::Registry(qApp); + m_registry->create(connection); + m_registry->setup(); + + connection->roundtrip(); +} diff --git a/src/plasmaquick/waylandintegration_p.h b/src/plasmaquick/waylandintegration_p.h new file mode 100644 index 000000000..24422f001 --- /dev/null +++ b/src/plasmaquick/waylandintegration_p.h @@ -0,0 +1,61 @@ +/* + * Copyright 2020 Vlad Zahorodnii + * + * 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. + */ + +#ifndef WAYLANDINTEGRATION_P_H +#define WAYLANDINTEGRATION_P_H + +#include +#include + +namespace KWayland +{ +namespace Client +{ +class PlasmaShell; +class Registry; +class ShadowManager; +class ShmPool; +} +} + +class WaylandIntegration : public QObject +{ + Q_OBJECT + +public: + explicit WaylandIntegration(QObject *parent = nullptr); + ~WaylandIntegration() override; + + KWayland::Client::PlasmaShell *waylandPlasmaShell(); + KWayland::Client::ShadowManager *waylandShadowManager(); + KWayland::Client::ShmPool *waylandShmPool(); + + static WaylandIntegration *self(); + +private: + void setupKWaylandIntegration(); + + QPointer m_registry; + QPointer m_waylandPlasmaShell; + QPointer m_waylandShadowManager; + QPointer m_waylandShmPool; + + Q_DISABLE_COPY(WaylandIntegration) +}; + +#endif // WAYLANDINTEGRATION_P_H