#include "panel_window.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../core/generation.hpp" #include "../core/panelinterface.hpp" #include "../core/proxywindow.hpp" #include "util.hpp" class XPanelStack { public: static XPanelStack* instance() { static XPanelStack* stack = nullptr; // NOLINT if (stack == nullptr) { stack = new XPanelStack(); } return stack; } [[nodiscard]] const QList& panels(XPanelWindow* panel) { return this->mPanels[EngineGeneration::findObjectGeneration(panel)]; } void addPanel(XPanelWindow* panel) { auto& panels = this->mPanels[EngineGeneration::findObjectGeneration(panel)]; if (!panels.contains(panel)) { panels.push_back(panel); } } void removePanel(XPanelWindow* panel) { auto& panels = this->mPanels[EngineGeneration::findObjectGeneration(panel)]; if (panels.removeOne(panel)) { if (panels.isEmpty()) { this->mPanels.erase(EngineGeneration::findObjectGeneration(panel)); } // from the bottom up, update all panels for (auto* panel: panels) { panel->updateDimensions(); } } } private: std::map> mPanels; }; bool XPanelEventFilter::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::PlatformSurface) { auto* surfaceEvent = static_cast(event); // NOLINT if (surfaceEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) { emit this->surfaceCreated(); } } return this->QObject::eventFilter(watched, event); } XPanelWindow::XPanelWindow(QObject* parent): ProxyWindowBase(parent) { QObject::connect( &this->eventFilter, &XPanelEventFilter::surfaceCreated, this, &XPanelWindow::xInit ); } XPanelWindow::~XPanelWindow() { XPanelStack::instance()->removePanel(this); } void XPanelWindow::connectWindow() { this->ProxyWindowBase::connectWindow(); this->window->installEventFilter(&this->eventFilter); this->connectScreen(); // clang-format off QObject::connect(this->window, &QQuickWindow::screenChanged, this, &XPanelWindow::connectScreen); QObject::connect(this->window, &QQuickWindow::visibleChanged, this, &XPanelWindow::updatePanelStack); // clang-format on // qt overwrites _NET_WM_STATE, so we have to use the qt api // QXcbWindow::WindowType::Dock in qplatformwindow_p.h // see QXcbWindow::setWindowFlags in qxcbwindow.cpp this->window->setProperty("_q_xcb_wm_window_type", 0x000004); // at least one flag needs to change for the above property to apply this->window->setFlag(Qt::FramelessWindowHint); this->updateAboveWindows(); this->updateFocusable(); if (this->window->handle() != nullptr) { this->xInit(); this->updatePanelStack(); } } void XPanelWindow::setWidth(qint32 width) { this->mWidth = width; // only update the actual size if not blocked by anchors if (!this->mAnchors.horizontalConstraint()) { this->ProxyWindowBase::setWidth(width); this->updateDimensions(); } } void XPanelWindow::setHeight(qint32 height) { this->mHeight = height; // only update the actual size if not blocked by anchors if (!this->mAnchors.verticalConstraint()) { this->ProxyWindowBase::setHeight(height); this->updateDimensions(); } } Anchors XPanelWindow::anchors() const { return this->mAnchors; } void XPanelWindow::setAnchors(Anchors anchors) { if (this->mAnchors == anchors) return; this->mAnchors = anchors; this->updateDimensions(); emit this->anchorsChanged(); } qint32 XPanelWindow::exclusiveZone() const { return this->mExclusiveZone; } void XPanelWindow::setExclusiveZone(qint32 exclusiveZone) { if (this->mExclusiveZone == exclusiveZone) return; this->mExclusiveZone = exclusiveZone; const bool wasNormal = this->mExclusionMode == ExclusionMode::Normal; this->setExclusionMode(ExclusionMode::Normal); if (wasNormal) this->updateStrut(); emit this->exclusiveZoneChanged(); } ExclusionMode::Enum XPanelWindow::exclusionMode() const { return this->mExclusionMode; } void XPanelWindow::setExclusionMode(ExclusionMode::Enum exclusionMode) { if (this->mExclusionMode == exclusionMode) return; this->mExclusionMode = exclusionMode; this->updateStrut(); emit this->exclusionModeChanged(); } Margins XPanelWindow::margins() const { return this->mMargins; } void XPanelWindow::setMargins(Margins margins) { if (this->mMargins == margins) return; this->mMargins = margins; this->updateDimensions(); emit this->marginsChanged(); } bool XPanelWindow::aboveWindows() const { return this->mAboveWindows; } void XPanelWindow::setAboveWindows(bool aboveWindows) { if (this->mAboveWindows == aboveWindows) return; this->mAboveWindows = aboveWindows; this->updateAboveWindows(); emit this->aboveWindowsChanged(); } bool XPanelWindow::focusable() const { return this->mFocusable; } void XPanelWindow::setFocusable(bool focusable) { if (this->mFocusable == focusable) return; this->mFocusable = focusable; this->updateFocusable(); emit this->focusableChanged(); } void XPanelWindow::xInit() { if (this->window == nullptr || this->window->handle() == nullptr) return; this->updateDimensions(); auto* conn = x11Connection(); // Stick to every workspace auto desktop = 0xffffffff; xcb_change_property( conn, XCB_PROP_MODE_REPLACE, this->window->winId(), XAtom::_NET_WM_DESKTOP.atom(), XCB_ATOM_CARDINAL, 32, 1, &desktop ); } void XPanelWindow::connectScreen() { if (this->mTrackedScreen != nullptr) { QObject::disconnect(this->mTrackedScreen, nullptr, this, nullptr); } this->mTrackedScreen = this->window->screen(); if (this->mTrackedScreen != nullptr) { QObject::connect( this->mTrackedScreen, &QScreen::geometryChanged, this, &XPanelWindow::updateDimensions ); } } void XPanelWindow::updateDimensions() { if (this->window == nullptr || this->window->handle() == nullptr) return; auto screenGeometry = this->window->screen()->virtualGeometry(); if (this->mExclusionMode != ExclusionMode::Ignore) { for (auto* panel: XPanelStack::instance()->panels(this)) { // we only care about windows below us if (panel == this) break; int side = -1; quint32 exclusiveZone = 0; panel->getExclusion(side, exclusiveZone); if (exclusiveZone == 0) continue; auto zone = static_cast(exclusiveZone); screenGeometry.adjust( side == 0 ? zone : 0, side == 2 ? zone : 0, side == 1 ? -zone : 0, side == 3 ? -zone : 0 ); } } auto geometry = QRect(); if (this->mAnchors.horizontalConstraint()) { geometry.setX(screenGeometry.x() + this->mMargins.mLeft); geometry.setWidth(screenGeometry.width() - this->mMargins.mLeft - this->mMargins.mRight); } else { if (this->mAnchors.mLeft) { geometry.setX(screenGeometry.x() + this->mMargins.mLeft); } else if (this->mAnchors.mRight) { geometry.setX( screenGeometry.x() + screenGeometry.width() - this->mWidth - this->mMargins.mRight ); } else { geometry.setX(screenGeometry.x() + screenGeometry.width() / 2 - this->mWidth / 2); } geometry.setWidth(this->mWidth); } if (this->mAnchors.verticalConstraint()) { geometry.setY(screenGeometry.y() + this->mMargins.mTop); geometry.setHeight(screenGeometry.height() - this->mMargins.mTop - this->mMargins.mBottom); } else { if (this->mAnchors.mTop) { geometry.setY(screenGeometry.y() + this->mMargins.mTop); } else if (this->mAnchors.mBottom) { geometry.setY( screenGeometry.y() + screenGeometry.height() - this->mHeight - this->mMargins.mBottom ); } else { geometry.setY(screenGeometry.y() + screenGeometry.height() / 2 - this->mHeight / 2); } geometry.setHeight(this->mHeight); } this->window->setGeometry(geometry); this->updateStrut(); } void XPanelWindow::updatePanelStack() { if (this->window->isVisible()) { XPanelStack::instance()->addPanel(this); } else { XPanelStack::instance()->removePanel(this); } } void XPanelWindow::getExclusion(int& side, quint32& exclusiveZone) { if (this->mExclusionMode == ExclusionMode::Ignore) return; auto& anchors = this->mAnchors; if (anchors.mLeft || anchors.mRight || anchors.mTop || anchors.mBottom) { if (!anchors.horizontalConstraint() && (anchors.verticalConstraint() || (!anchors.mTop && !anchors.mBottom))) { side = anchors.mLeft ? 0 : anchors.mRight ? 1 : -1; } else if (!anchors.verticalConstraint() && (anchors.horizontalConstraint() || (!anchors.mLeft && !anchors.mRight))) { side = anchors.mTop ? 2 : anchors.mBottom ? 3 : -1; } } if (side == -1) return; auto autoExclude = this->mExclusionMode == ExclusionMode::Auto; if (autoExclude) { if (side == 0 || side == 1) { exclusiveZone = this->mWidth + (side == 0 ? this->mMargins.mLeft : this->mMargins.mRight); } else { exclusiveZone = this->mHeight + (side == 2 ? this->mMargins.mTop : this->mMargins.mBottom); } } else { exclusiveZone = this->mExclusiveZone; } } void XPanelWindow::updateStrut() { if (this->window == nullptr || this->window->handle() == nullptr) return; auto* conn = x11Connection(); int side = -1; quint32 exclusiveZone = 0; this->getExclusion(side, exclusiveZone); if (side == -1 || this->mExclusionMode == ExclusionMode::Ignore) { xcb_delete_property(conn, this->window->winId(), XAtom::_NET_WM_STRUT.atom()); xcb_delete_property(conn, this->window->winId(), XAtom::_NET_WM_STRUT_PARTIAL.atom()); return; } auto data = std::array(); data[side] = exclusiveZone; // https://specifications.freedesktop.org/wm-spec/wm-spec-latest.html#idm45573693101552 // assuming "specified in root window coordinates" means relative to the window geometry // in which case only the end position should be set, to the opposite extent. data[side * 2 + 5] = side == 0 || side == 1 ? this->window->height() : this->window->width(); xcb_change_property( conn, XCB_PROP_MODE_REPLACE, this->window->winId(), XAtom::_NET_WM_STRUT.atom(), XCB_ATOM_CARDINAL, 32, 4, data.data() ); xcb_change_property( conn, XCB_PROP_MODE_REPLACE, this->window->winId(), XAtom::_NET_WM_STRUT_PARTIAL.atom(), XCB_ATOM_CARDINAL, 32, 12, data.data() ); } void XPanelWindow::updateAboveWindows() { if (this->window == nullptr) return; this->window->setFlag(Qt::WindowStaysOnBottomHint, !this->mAboveWindows); this->window->setFlag(Qt::WindowStaysOnTopHint, this->mAboveWindows); } void XPanelWindow::updateFocusable() { if (this->window == nullptr) return; this->window->setFlag(Qt::WindowDoesNotAcceptFocus, !this->mFocusable); } // XPanelInterface XPanelInterface::XPanelInterface(QObject* parent) : PanelWindowInterface(parent) , panel(new XPanelWindow(this)) { // clang-format off QObject::connect(this->panel, &ProxyWindowBase::windowConnected, this, &XPanelInterface::windowConnected); QObject::connect(this->panel, &ProxyWindowBase::visibleChanged, this, &XPanelInterface::visibleChanged); QObject::connect(this->panel, &ProxyWindowBase::backerVisibilityChanged, this, &XPanelInterface::backingWindowVisibleChanged); QObject::connect(this->panel, &ProxyWindowBase::heightChanged, this, &XPanelInterface::heightChanged); QObject::connect(this->panel, &ProxyWindowBase::widthChanged, this, &XPanelInterface::widthChanged); QObject::connect(this->panel, &ProxyWindowBase::screenChanged, this, &XPanelInterface::screenChanged); QObject::connect(this->panel, &ProxyWindowBase::windowTransformChanged, this, &XPanelInterface::windowTransformChanged); QObject::connect(this->panel, &ProxyWindowBase::colorChanged, this, &XPanelInterface::colorChanged); QObject::connect(this->panel, &ProxyWindowBase::maskChanged, this, &XPanelInterface::maskChanged); // panel specific QObject::connect(this->panel, &XPanelWindow::anchorsChanged, this, &XPanelInterface::anchorsChanged); QObject::connect(this->panel, &XPanelWindow::marginsChanged, this, &XPanelInterface::marginsChanged); QObject::connect(this->panel, &XPanelWindow::exclusiveZoneChanged, this, &XPanelInterface::exclusiveZoneChanged); QObject::connect(this->panel, &XPanelWindow::exclusionModeChanged, this, &XPanelInterface::exclusionModeChanged); QObject::connect(this->panel, &XPanelWindow::aboveWindowsChanged, this, &XPanelInterface::aboveWindowsChanged); QObject::connect(this->panel, &XPanelWindow::focusableChanged, this, &XPanelInterface::focusableChanged); // clang-format on } void XPanelInterface::onReload(QObject* oldInstance) { QQmlEngine::setContextForObject(this->panel, QQmlEngine::contextForObject(this)); auto* old = qobject_cast(oldInstance); this->panel->reload(old != nullptr ? old->panel : nullptr); } QQmlListProperty XPanelInterface::data() { return this->panel->data(); } ProxyWindowBase* XPanelInterface::proxyWindow() const { return this->panel; } QQuickItem* XPanelInterface::contentItem() const { return this->panel->contentItem(); } bool XPanelInterface::isBackingWindowVisible() const { return this->panel->isVisibleDirect(); } // NOLINTBEGIN #define proxyPair(type, get, set) \ type XPanelInterface::get() const { return this->panel->get(); } \ void XPanelInterface::set(type value) { this->panel->set(value); } proxyPair(bool, isVisible, setVisible); proxyPair(qint32, width, setWidth); proxyPair(qint32, height, setHeight); proxyPair(QuickshellScreenInfo*, screen, setScreen); proxyPair(QColor, color, setColor); proxyPair(PendingRegion*, mask, setMask); // panel specific proxyPair(Anchors, anchors, setAnchors); proxyPair(Margins, margins, setMargins); proxyPair(qint32, exclusiveZone, setExclusiveZone); proxyPair(ExclusionMode::Enum, exclusionMode, setExclusionMode); proxyPair(bool, focusable, setFocusable); proxyPair(bool, aboveWindows, setAboveWindows); #undef proxyPair // NOLINTEND