mirror of
https://github.com/KDE/latte-dock.git
synced 2025-03-27 02:50:22 +03:00
--This is a HUGE FEATURE and so important for multi-screens users. It is introduced as one single commit because it reimplements plenty of infrastructure changes and it will be easier to identify newly introduced bugs. --Users can now choose for their docks and panels to belong at various screen groups. The first two screen groups introduced are AllScreens and AllSecondayScreens. In the future it might be possible to provide CustomScreensGroup that the user will be able to define specific screens in which a dock or panel should be always present. --Current solution specifies an Original dock or panel and clones/copies itself automatically to other screens. So docks and panels in other screens are just real docks and panels that reference themselves to original docks and panels. --Clones are destroyed during layout startup and are automaticaly recreated. It is suggested to export your layouts through the official Layouts Editor in order to share them because in that case clones are not included in the new generated layout file. If in any case you do not this and you share your layout with any previous versions then your clones will just appear as separate docks and panels that belong to specific screens. --Automatic syncing was introduced in order to keep up-to-date the configuration of Original docks and panels with their referenced Clones. --Automatic syncing currently works for all docks and panels settings, for all normal applets configurations and for all subcontaiments configuration such as systrays. --Automatic syncing does not work for applets inside subcontainments such as Group Plasmoid. In such case it is suggested to configure your applets inside your Group Plasmoid in the original dock or panel and afterwards to trigger a recreation for the relevant clones --Manual recreation of clones is easily possible by just choosing the dock or panel to be OnPrimary or OnSpecificScreen and rechoosing afterwards the AllScreensGroup or AllSecondaryScreensGroup
333 lines
8.8 KiB
C++
333 lines
8.8 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
|
|
|
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
|
*/
|
|
|
|
#include "screenpool.h"
|
|
|
|
// local
|
|
#include <config-latte.h>
|
|
|
|
// Qt
|
|
#include <QDebug>
|
|
#include <QFile>
|
|
#include <QGuiApplication>
|
|
#include <QScreen>
|
|
|
|
// KDE
|
|
#include <KLocalizedString>
|
|
|
|
// X11
|
|
#if HAVE_X11
|
|
#include <QtX11Extras/QX11Info>
|
|
#include <xcb/xcb.h>
|
|
#include <xcb/randr.h>
|
|
#include <xcb/xcb_event.h>
|
|
#endif
|
|
|
|
namespace Latte {
|
|
|
|
const int ScreenPool::FIRSTSCREENID;
|
|
|
|
ScreenPool::ScreenPool(KSharedConfig::Ptr config, QObject *parent)
|
|
: QObject(parent),
|
|
m_configGroup(KConfigGroup(config, QStringLiteral("ScreenConnectors")))
|
|
{
|
|
qApp->installNativeEventFilter(this);
|
|
|
|
m_configSaveTimer.setSingleShot(true);
|
|
connect(&m_configSaveTimer, &QTimer::timeout, this, [this]() {
|
|
m_configGroup.sync();
|
|
});
|
|
|
|
connect(qGuiApp, &QGuiApplication::screenAdded, this, &ScreenPool::onScreenAdded, Qt::UniqueConnection);
|
|
connect(qGuiApp, &QGuiApplication::screenRemoved, this, &ScreenPool::onScreenRemoved, Qt::UniqueConnection);
|
|
}
|
|
|
|
ScreenPool::~ScreenPool()
|
|
{
|
|
m_configGroup.sync();
|
|
}
|
|
|
|
void ScreenPool::load()
|
|
{
|
|
m_lastPrimaryConnector = QString();
|
|
m_screensTable.clear();
|
|
|
|
QScreen *primary = qGuiApp->primaryScreen();
|
|
|
|
if (primary) {
|
|
m_lastPrimaryConnector = primary->name();
|
|
}
|
|
|
|
//restore the known ids to connector mappings
|
|
for (const QString &key : m_configGroup.keyList()) {
|
|
if (key.toInt() <= 0) {
|
|
continue;
|
|
}
|
|
|
|
QString serialized = m_configGroup.readEntry(key, QString());
|
|
|
|
Data::Screen screenRecord(key, serialized);
|
|
//qDebug() << "org.kde.latte ::: " << screenRecord.id << ":" << screenRecord.serialize();
|
|
|
|
if (!key.isEmpty() && !serialized.isEmpty() && !m_screensTable.containsId(key)) {
|
|
m_screensTable << screenRecord;
|
|
qDebug() << "org.kde.latte :: Known Screen - " << screenRecord.id << " : " << screenRecord.name << " : " << screenRecord.geometry;
|
|
}
|
|
}
|
|
|
|
// if there are already connected unknown screens, map those
|
|
// all needs to be populated as soon as possible, otherwise
|
|
// containment->screen() will return an incorrect -1
|
|
// at startup, if it' asked before corona::addOutput()
|
|
// is performed, driving to the creation of a new containment
|
|
for (QScreen *screen : qGuiApp->screens()) {
|
|
onScreenRemoved(screen);
|
|
|
|
if (!m_screensTable.containsName(screen->name())) {
|
|
insertScreenMapping(screen->name());
|
|
} else {
|
|
updateScreenGeometry(screen);
|
|
}
|
|
|
|
onScreenAdded(screen);
|
|
}
|
|
}
|
|
|
|
void ScreenPool::onScreenAdded(const QScreen *screen)
|
|
{
|
|
connect(screen, &QScreen::geometryChanged, this, [&, screen]() {
|
|
updateScreenGeometry(screen);
|
|
});
|
|
}
|
|
|
|
void ScreenPool::onScreenRemoved(const QScreen *screen)
|
|
{
|
|
disconnect(screen, &QScreen::geometryChanged, this, nullptr);
|
|
}
|
|
|
|
void ScreenPool::updateScreenGeometry(const QScreen *screen)
|
|
{
|
|
if (!screen) {
|
|
return;
|
|
}
|
|
|
|
if (m_screensTable.containsName(screen->name())) {
|
|
updateScreenGeometry(id(screen->name()), screen->geometry());
|
|
}
|
|
}
|
|
|
|
void ScreenPool::updateScreenGeometry(const int &screenId, const QRect &screenGeometry)
|
|
{
|
|
QString scrIdStr = QString::number(screenId);
|
|
|
|
if (!m_screensTable.containsId(scrIdStr) || m_screensTable[scrIdStr].geometry == screenGeometry) {
|
|
return;
|
|
}
|
|
|
|
m_screensTable[scrIdStr].geometry = screenGeometry;
|
|
save();
|
|
|
|
emit screenGeometryChanged();
|
|
}
|
|
|
|
|
|
Latte::Data::ScreensTable ScreenPool::screensTable()
|
|
{
|
|
return m_screensTable;
|
|
}
|
|
|
|
void ScreenPool::reload(QString path)
|
|
{
|
|
QFile rcfile(QString(path + "/lattedockrc"));
|
|
|
|
if (rcfile.exists()) {
|
|
qDebug() << "load screen connectors from ::: " << rcfile.fileName();
|
|
KSharedConfigPtr newFile = KSharedConfig::openConfig(rcfile.fileName());
|
|
m_configGroup = KConfigGroup(newFile, QStringLiteral("ScreenConnectors"));
|
|
load();
|
|
}
|
|
}
|
|
|
|
void ScreenPool::removeScreens(const Latte::Data::ScreensTable &obsoleteScreens)
|
|
{
|
|
for (int i=0; i<obsoleteScreens.rowCount(); ++i) {
|
|
if (!m_screensTable.containsId(obsoleteScreens[i].id)) {
|
|
return;
|
|
}
|
|
|
|
m_screensTable.remove(obsoleteScreens[i].id);
|
|
m_configGroup.deleteEntry(obsoleteScreens[i].id);
|
|
}
|
|
}
|
|
|
|
int ScreenPool::primaryScreenId() const
|
|
{
|
|
return id(qGuiApp->primaryScreen()->name());
|
|
}
|
|
|
|
QList<int> ScreenPool::secondaryScreenIds() const
|
|
{
|
|
QList<int> secondaryscreens;
|
|
|
|
for (const auto scr : qGuiApp->screens()) {
|
|
if (scr == qGuiApp->primaryScreen()) {
|
|
continue;
|
|
}
|
|
|
|
secondaryscreens << id(scr->name());
|
|
}
|
|
|
|
return secondaryscreens;
|
|
}
|
|
|
|
void ScreenPool::save()
|
|
{
|
|
QMap<int, QString>::const_iterator i;
|
|
|
|
for (int i=0; i<m_screensTable.rowCount(); ++i) {
|
|
Data::Screen screenRecord = m_screensTable[i];
|
|
if (screenRecord.id.toInt() >= FIRSTSCREENID) {
|
|
m_configGroup.writeEntry(screenRecord.id, screenRecord.serialize());
|
|
}
|
|
}
|
|
|
|
//write to disck every 10 seconds at most
|
|
m_configSaveTimer.start(10000);
|
|
}
|
|
|
|
void ScreenPool::insertScreenMapping(const QString &connector)
|
|
{
|
|
//the ":" check fixes the strange plasma/qt issues when changing layouts
|
|
//there are case that the QScreen instead of the correct screen name
|
|
//returns "0:0", this check prevents from breaking the screens database
|
|
//from garbage ids
|
|
if (m_screensTable.containsName(connector) || connector.startsWith(":")) {
|
|
return;
|
|
}
|
|
|
|
qDebug() << "add connector..." << connector;
|
|
|
|
Data::Screen screenRecord;
|
|
screenRecord.id = QString::number(firstAvailableId());
|
|
screenRecord.name = connector;
|
|
|
|
//! update screen geometry
|
|
for (const auto scr : qGuiApp->screens()) {
|
|
if (scr->name() == connector) {
|
|
screenRecord.geometry = scr->geometry();
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_screensTable << screenRecord;
|
|
save();
|
|
}
|
|
|
|
int ScreenPool::id(const QString &connector) const
|
|
{
|
|
QString screenId = m_screensTable.idForName(connector);
|
|
return screenId.isEmpty() ? NOSCREENID : screenId.toInt();
|
|
}
|
|
|
|
QString ScreenPool::connector(int id) const
|
|
{
|
|
QString idStr = QString::number(id);
|
|
return (m_screensTable.containsId(idStr) ? m_screensTable[idStr].name : QString());
|
|
}
|
|
|
|
int ScreenPool::firstAvailableId() const
|
|
{
|
|
//start counting from 10, first numbers will be used for special cases e.g. primaryScreen, id=0
|
|
int availableId = FIRSTSCREENID;
|
|
|
|
for (int row=0; row<m_screensTable.rowCount(); ++row) {
|
|
if (!m_screensTable.containsId(QString::number(availableId))) {
|
|
return availableId;
|
|
}
|
|
|
|
availableId++;
|
|
}
|
|
|
|
return availableId;
|
|
}
|
|
|
|
bool ScreenPool::hasScreenId(int screenId) const
|
|
{
|
|
return ((screenId>=0) && m_screensTable.containsId(QString::number(screenId)));
|
|
}
|
|
|
|
bool ScreenPool::isScreenActive(int screenId) const
|
|
{
|
|
if (hasScreenId(screenId)) {
|
|
QString scrName = connector(screenId);
|
|
|
|
for (const auto scr : qGuiApp->screens()) {
|
|
if (scr->name() == scrName) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QScreen *ScreenPool::screenForId(int id)
|
|
{
|
|
const auto screens = qGuiApp->screens();
|
|
QScreen *screen{qGuiApp->primaryScreen()};
|
|
|
|
if (hasScreenId(id)) {
|
|
QString scrName = connector(id);
|
|
|
|
for (const auto scr : screens) {
|
|
if (scr->name() == scrName) {
|
|
return scr;
|
|
}
|
|
}
|
|
}
|
|
|
|
return screen;
|
|
}
|
|
|
|
bool ScreenPool::nativeEventFilter(const QByteArray &eventType, void *message, long int *result)
|
|
{
|
|
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 primaryconnector will be the same by then
|
|
if (eventType != "xcb_generic_event_t") {
|
|
return false;
|
|
}
|
|
|
|
xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message);
|
|
|
|
const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev);
|
|
|
|
const xcb_query_extension_reply_t *reply = xcb_get_extension_data(QX11Info::connection(), &xcb_randr_id);
|
|
|
|
if (responseType == reply->first_event + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
|
|
if (qGuiApp->primaryScreen()->name() != m_lastPrimaryConnector) {
|
|
//new screen?
|
|
if (id(qGuiApp->primaryScreen()->name()) < 0) {
|
|
insertScreenMapping(qGuiApp->primaryScreen()->name());
|
|
}
|
|
|
|
m_lastPrimaryConnector = qGuiApp->primaryScreen()->name();
|
|
emit primaryPoolChanged();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
#include "moc_screenpool.cpp"
|