You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
latte-dock/app/primaryoutputwatcher.cpp

163 lines
5.4 KiB
C++

/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "primaryoutputwatcher.h"
#include <KWindowSystem>
#include <QDebug>
#include <QGuiApplication>
#include <QScreen>
#include "qwayland-kde-primary-output-v1.h"
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/registry.h>
#include <config-latte.h>
#if HAVE_X11
#include <QTimer> //Used only in x11 case
#include <private/qtx11extras_p.h>
#include <xcb/randr.h>
#include <xcb/xcb.h>
#include <xcb/xcb_event.h>
#endif
class WaylandPrimaryOutput : public QObject, public QtWayland::kde_primary_output_v1
{
Q_OBJECT
public:
WaylandPrimaryOutput(struct ::wl_registry *registry, int id, int version, QObject *parent)
: QObject(parent)
, QtWayland::kde_primary_output_v1(registry, id, version)
{
}
void kde_primary_output_v1_primary_output(const QString &outputName) override
{
Q_EMIT primaryOutputChanged(outputName);
}
Q_SIGNALS:
void primaryOutputChanged(const QString &outputName);
};
PrimaryOutputWatcher::PrimaryOutputWatcher(QObject *parent)
: QObject(parent)
{
#if HAVE_X11
if (KWindowSystem::isPlatformX11()) {
m_primaryOutputName = qGuiApp->primaryScreen()->name();
qGuiApp->installNativeEventFilter(this);
const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id);
m_xrandrExtensionOffset = reply->first_event;
setPrimaryOutputName(qGuiApp->primaryScreen()->name());
connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, [this](QScreen *newPrimary) {
setPrimaryOutputName(newPrimary->name());
});
}
#endif
if (KWindowSystem::isPlatformWayland()) {
setupRegistry();
}
}
void PrimaryOutputWatcher::setPrimaryOutputName(const QString &newOutputName)
{
if (newOutputName != m_primaryOutputName) {
const QString oldOutputName = m_primaryOutputName;
m_primaryOutputName = newOutputName;
Q_EMIT primaryOutputNameChanged(oldOutputName, newOutputName);
}
}
void PrimaryOutputWatcher::setupRegistry()
{
auto m_connection = KWayland::Client::ConnectionThread::fromApplication(this);
if (!m_connection) {
return;
}
// Asking for primaryOutputName() before this happened, will return qGuiApp->primaryScreen()->name() anyways, so set it so the primaryOutputNameChange will
// have parameters that are coherent
m_primaryOutputName = qGuiApp->primaryScreen()->name();
m_registry = new KWayland::Client::Registry(this);
connect(m_registry, &KWayland::Client::Registry::interfaceAnnounced, this, [this](const QByteArray &interface, quint32 name, quint32 version) {
if (interface == WaylandPrimaryOutput::interface()->name) {
auto m_outputManagement = new WaylandPrimaryOutput(m_registry->registry(), name, version, this);
connect(m_outputManagement, &WaylandPrimaryOutput::primaryOutputChanged, this, [this](const QString &outputName) {
m_primaryOutputWayland = outputName;
// Only set the outputName when there's a QScreen attached to it
if (screenForName(outputName)) {
setPrimaryOutputName(outputName);
}
});
}
});
// In case the outputName was received before Qt reported the screen
connect(qGuiApp, &QGuiApplication::screenAdded, this, [this](QScreen *screen) {
if (screen->name() == m_primaryOutputWayland) {
setPrimaryOutputName(m_primaryOutputWayland);
}
});
m_registry->create(m_connection);
m_registry->setup();
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
bool PrimaryOutputWatcher::nativeEventFilter(const QByteArray &eventType, void *message, long int *result)
#else
bool PrimaryOutputWatcher::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
#endif
{
Q_UNUSED(result);
#if HAVE_X11
// a particular edge case: when we switch the only enabled screen
// we don't have any signal about it, the primary screen changes but we have the same old QScreen* getting recycled
// see https://bugs.kde.org/show_bug.cgi?id=373880
// if this slot will be invoked many times, their//second time on will do nothing as name and primaryOutputName will be the same by then
if (eventType[0] != 'x') {
return false;
}
xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message);
const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev);
if (responseType == m_xrandrExtensionOffset + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
QTimer::singleShot(0, this, [this]() {
setPrimaryOutputName(qGuiApp->primaryScreen()->name());
});
}
#endif
return false;
}
QScreen *PrimaryOutputWatcher::screenForName(const QString &outputName) const
{
const auto screens = qGuiApp->screens();
for (auto screen : screens) {
if (screen->name() == outputName) {
return screen;
}
}
return nullptr;
}
QScreen *PrimaryOutputWatcher::primaryScreen() const
{
auto screen = screenForName(m_primaryOutputName);
if (!screen) {
qDebug() << "PrimaryOutputWatcher: Could not find primary screen:" << m_primaryOutputName;
return qGuiApp->primaryScreen();
}
return screen;
}
#include "primaryoutputwatcher.moc"