Files
applet-window-appmenu6/plugin/wm/x11fallbackwindowmanager.cpp
T
Timothy Hutchins 599bc021fb Fixed Wayland and X11 compilation
For X11, switched to using KX11Extras
2024-04-28 19:23:09 -05:00

292 lines
9.7 KiB
C++

/*
* Copyright 2020 Michail Vourlakos <mvourlakos@gmail.com>
*
* This file is part of Latte-Dock
*
* Latte-Dock 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.
*
* Latte-Dock 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 "x11fallbackwindowmanager.h"
#include <config-appmenu.h>
#if HAVE_X11
#include <kx11extras.h>
#include <xcb/xcb.h>
#endif
#include <QGuiApplication>
static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME");
static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH");
#if HAVE_X11
static QHash<QByteArray, xcb_atom_t> s_atoms;
#endif
namespace WM {
X11FallbackWindowManager::X11FallbackWindowManager(QObject *parent)
: AbstractWindowManager(parent)
{
if (!KWindowSystem::isPlatformX11()) {
return;
}
connect(this, &AbstractWindowManager::winIdChanged, this, [this] {
onActiveWindowChanged(m_userWindowId.toUInt());
});
connect(KX11Extras::self(), &KX11Extras::activeWindowChanged,
this, &X11FallbackWindowManager::onActiveWindowChanged);
connect(KX11Extras::self()
, static_cast<void (KX11Extras::*)(WId, NET::Properties, NET::Properties2)>(&KX11Extras::windowChanged)
, this
, &X11FallbackWindowManager::onWindowChanged);
connect(KX11Extras::self()
, static_cast<void (KX11Extras::*)(WId)>(&KX11Extras::windowRemoved)
, this
, &X11FallbackWindowManager::onWindowRemoved);
// Needing a NET::Properties for the updated onWindowChanged function.
// Function was updated to work with KX11Extras onWindowChanged signal.
connect(this, &AbstractWindowManager::screenGeometryChanged, this, [this] {
onWindowChanged(m_currentWindowId.toUInt(), NET::WMState | NET::WMGeometry);
});
connect(this, &AbstractWindowManager::menuAvailableChanged, this, [this] {
onWindowChanged(m_currentWindowId.toUInt(), NET::WMState | NET::WMGeometry);
});
onActiveWindowChanged(KX11Extras::activeWindow());
}
X11FallbackWindowManager::~X11FallbackWindowManager()
{
}
void X11FallbackWindowManager::onWindowChanged(WId id, NET::Properties properties, NET::Properties2 properties2)
{
if (m_currentWindowId == id) {
KWindowInfo info(id, properties, properties2);
filterWindow(info);
}
}
void X11FallbackWindowManager::onWindowRemoved(WId id)
{
if (m_currentWindowId == id) {
setMenuAvailable(false);
setVisible(false);
}
}
void X11FallbackWindowManager::filterWindow(KWindowInfo &info)
{
if (m_currentWindowId == info.win()) {
//! HACK: if the user has enabled screen scaling under X11 environment
//! then the window and screen geometries can not be trusted for comparison
//! before windows coordinates be adjusted properly.
//! BUG: 404500
QPoint windowCenter = info.geometry().center();
if (KWindowSystem::isPlatformX11()) {
windowCenter /= qApp->devicePixelRatio();
}
const bool contained = m_screenGeometry.isNull() || m_screenGeometry.contains(windowCenter);
const bool isActive = m_filterByActive ? info.win() == KX11Extras::activeWindow() : true;
setVisible(isActive && !info.isMinimized() && contained);
}
}
void X11FallbackWindowManager::onActiveWindowChanged(WId id)
{
qApp->removeNativeEventFilter(this);
if (hasUserWindowId() && m_userWindowId!=id) {
//! ignore any other window except the one preferred from plasmoid
return;
}
if (!id) {
setMenuAvailable(false);
emit modelNeedsUpdate();
return;
}
#if HAVE_X11
if (KWindowSystem::isPlatformX11()) {
QNativeInterface::QX11Application *x11App = qApp->nativeInterface<QNativeInterface::QX11Application>();
auto *c = x11App->connection();
auto getWindowPropertyString = [c, this](WId id, const QByteArray & name) -> QByteArray {
QByteArray value;
if (!s_atoms.contains(name))
{
const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData());
QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atomReply(xcb_intern_atom_reply(c, atomCookie, nullptr));
if (atomReply.isNull()) {
return value;
}
s_atoms[name] = atomReply->atom;
if (s_atoms[name] == XCB_ATOM_NONE) {
return value;
}
}
static const long MAX_PROP_SIZE = 10000;
auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE);
QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter> propertyReply(xcb_get_property_reply(c, propertyCookie, nullptr));
if (propertyReply.isNull())
{
return value;
}
if (propertyReply->type == XCB_ATOM_STRING && propertyReply->format == 8 && propertyReply->value_len > 0)
{
const char *data = (const char *) xcb_get_property_value(propertyReply.data());
int len = propertyReply->value_len;
if (data) {
value = QByteArray(data, data[len - 1] ? len : len - 1);
}
}
return value;
};
auto updateMenuFromWindowIfHasMenu = [this, &getWindowPropertyString](WId id) {
const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuServiceNamePropertyName));
const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuObjectPathPropertyName));
if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) {
emit applicationMenuChanged(serviceName, menuObjectPath);
return true;
}
return false;
};
KWindowInfo info(id, NET::WMState | NET::WMWindowType | NET::WMGeometry, NET::WM2TransientFor);
if (info.hasState(NET::SkipTaskbar) ||
info.windowType(NET::UtilityMask) == NET::Utility ||
info.windowType(NET::DesktopMask) == NET::Desktop) {
//! hide when the windows or their transiet(s) do not have a menu
if (filterByActive()) {
KWindowInfo transientInfo = KWindowInfo(info.transientFor(), NET::WMState | NET::WMWindowType | NET::WMGeometry, NET::WM2TransientFor);
while (transientInfo.win()) {
if (transientInfo.win() == m_currentWindowId) {
filterWindow(info);
return;
}
transientInfo = KWindowInfo(transientInfo.transientFor(), NET::WMState | NET::WMWindowType | NET::WMGeometry, NET::WM2TransientFor);
}
}
if (filterByActive()) {
setVisible(false);
}
return;
}
m_currentWindowId = id;
if (!filterChildren()) {
KWindowInfo transientInfo = KWindowInfo(info.transientFor(), NET::WMState | NET::WMWindowType | NET::WMGeometry, NET::WM2TransientFor);
// look at transient windows first
while (transientInfo.win()) {
if (updateMenuFromWindowIfHasMenu(transientInfo.win())) {
filterWindow(info);
return;
}
transientInfo = KWindowInfo(transientInfo.transientFor(), NET::WMState | NET::WMWindowType | NET::WMGeometry, NET::WM2TransientFor);
}
}
if (updateMenuFromWindowIfHasMenu(id)) {
filterWindow(info);
return;
}
// monitor whether an app menu becomes available later
// this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox)
qApp->installNativeEventFilter(this);
m_delayedMenuWindowId = id;
//no menu found, set it to unavailable
setMenuAvailable(false);
emit modelNeedsUpdate();
}
#endif
}
bool X11FallbackWindowManager::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
{
Q_UNUSED(result);
if (!KWindowSystem::isPlatformX11() || eventType != "xcb_generic_event_t") {
return false;
}
#if HAVE_X11
auto e = static_cast<xcb_generic_event_t *>(message);
const uint8_t type = e->response_type & ~0x80;
if (type == XCB_PROPERTY_NOTIFY) {
auto *event = reinterpret_cast<xcb_property_notify_event_t *>(e);
if (event->window == m_delayedMenuWindowId) {
auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName);
auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName);
if (serviceNameAtom != XCB_ATOM_NONE && objectPathAtom != XCB_ATOM_NONE) { // shouldn't happen
if (event->atom == serviceNameAtom || event->atom == objectPathAtom) {
// see if we now have a menu
onActiveWindowChanged(KX11Extras::activeWindow());
}
}
}
}
#else
Q_UNUSED(message);
#endif
return false;
}
}