shift-shell/components/mobileshellstate/panelsettingsdbusobjectmanager.cpp
Devin Lin 839d5e2bff panels: Add support for defining device specific panel tweaks
This adds support for specifying options needed to deal with phone
display panel pecularities (ex. screen curves, notches, punch holes)

This is implemented as settings in ~/.config/plasmamobilerc, which can
set panel heights, paddings, and center spacings to duck display
cutouts. The pixel values are scaling independent, and so are not
affected when the display scaling is changed.

This is then exposed over DBus, so that components from outside of
plasmashell (ex. KWin) can access it easily without needing to connect to
kscreen themselves. Each screen is exposed as a single object.

Currently support is only added in the status bar and the navigation
panel.

Currently all screens have the settings applied. In the future, we may
want to limit this just to the internal screen (?)

---

This also adds a "devices" folder (in `devices/configs`) where per-device configs can be set.

This is installed to `/usr/share/plasma-mobile-device-configs`.
In `plasmamobilerc` (installed to `/etc/xdg/plasmamobilerc`, or
`~/.config/plasmamobilerc`), envmanager will read:

```toml
[Device]
device=oneplus-enchilada
```

for the device config to use and write its settings to
`~/.config/plasma-mobile/plasmamobilerc`.
2025-11-04 23:08:18 -05:00

268 lines
8.3 KiB
C++

// SPDX-FileCopyrightText: 2025 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "panelsettingsdbusobjectmanager.h"
#include "panelsadaptor.h"
#include <kscreen/configmonitor.h>
#include <kscreen/getconfigoperation.h>
#include <kscreen/output.h>
using namespace Qt::Literals::StringLiterals;
const QString CONFIG_FILE = u"plasmamobilerc"_s;
const QString PANELS_CONFIG_GROUP = u"Panels"_s;
// Orientations
const QString TOP_CONFIG_GROUP = u"WhenOnTop"_s;
const QString LEFT_CONFIG_GROUP = u"WhenOnLeft"_s;
const QString RIGHT_CONFIG_GROUP = u"WhenOnRight"_s;
const QString BOTTOM_CONFIG_GROUP = u"WhenOnBottom"_s;
QString mapRotationToTopPosition(KScreen::Output::Rotation rotation)
{
switch (rotation) {
case KScreen::Output::Rotation::Left:
return RIGHT_CONFIG_GROUP;
case KScreen::Output::Rotation::Inverted:
return BOTTOM_CONFIG_GROUP;
case KScreen::Output::Rotation::Right:
return LEFT_CONFIG_GROUP;
default:
return TOP_CONFIG_GROUP;
}
}
QString mapRotationToBottomPosition(KScreen::Output::Rotation rotation)
{
switch (rotation) {
case KScreen::Output::Rotation::Left:
return LEFT_CONFIG_GROUP;
case KScreen::Output::Rotation::Inverted:
return TOP_CONFIG_GROUP;
case KScreen::Output::Rotation::Right:
return RIGHT_CONFIG_GROUP;
default:
return BOTTOM_CONFIG_GROUP;
}
}
PanelSettingsDBusObjectManager::PanelSettingsDBusObjectManager(QObject *parent)
: QObject{parent}
{
}
void PanelSettingsDBusObjectManager::registerObjects()
{
if (m_initialized) {
return;
}
m_initialized = true;
// Fetch kscreen config
connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, [this](auto *op) {
m_kscreenConfig = qobject_cast<KScreen::GetConfigOperation *>(op)->config();
if (!m_kscreenConfig) {
return;
}
KScreen::ConfigMonitor::instance()->addConfig(m_kscreenConfig);
// Listen to all new screens and create a new output
connect(m_kscreenConfig.data(), &KScreen::Config::outputAdded, this, [this](const auto &output) {
if (!output) {
return;
}
auto *obj = new PanelSettingsDBusObject{this};
obj->registerObject(output);
m_dbusObjects.push_back(obj);
});
// Remove the corresponding object when a screen is removed
connect(m_kscreenConfig.data(), &KScreen::Config::outputRemoved, this, [this](const auto outputId) {
for (int i = 0; i < m_dbusObjects.size(); ++i) {
if (m_dbusObjects[i]->outputId() == outputId) {
m_dbusObjects[i]->deleteLater();
m_dbusObjects.remove(i);
break;
}
}
});
// Add all current screens as dbus objects
for (KScreen::OutputPtr output : m_kscreenConfig->outputs()) {
if (!output) {
continue;
}
auto *obj = new PanelSettingsDBusObject{this};
obj->registerObject(output);
m_dbusObjects.push_back(obj);
}
});
}
PanelSettingsDBusObject::PanelSettingsDBusObject(QObject *parent)
: QObject{parent}
, m_config{KSharedConfig::openConfig(CONFIG_FILE)}
{
// Listen to config changes and reload
m_configWatcher = KConfigWatcher::create(m_config);
connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) -> void {
Q_UNUSED(names)
Q_UNUSED(group)
updateFields();
});
}
void PanelSettingsDBusObject::registerObject(KScreen::OutputPtr output)
{
m_output = output;
if (!m_output) {
return;
}
m_outputId = m_output->id();
m_outputName = m_output->name();
// Listen to when the rotation or scale changes to refresh the fields
connect(m_output.data(), &KScreen::Output::rotationChanged, this, &PanelSettingsDBusObject::updateFields);
connect(m_output.data(), &KScreen::Output::scaleChanged, this, &PanelSettingsDBusObject::updateFields);
updateFields();
new PlasmashellMobilePanelsAdaptor{this};
// Register the screen DBus object
QString objectName = m_output->name().replace("-", ""); // DBus doesn't allow dashes
QDBusConnection::sessionBus().registerObject(QStringLiteral("/Mobile/Panels/") + objectName, this);
}
void PanelSettingsDBusObject::updateFields()
{
if (!m_output) {
return;
}
auto group = KConfigGroup{m_config, PANELS_CONFIG_GROUP};
auto topGroup = KConfigGroup{&group, mapRotationToTopPosition(m_output->rotation())};
auto bottomGroup = KConfigGroup{&group, mapRotationToBottomPosition(m_output->rotation())};
// Divide values by the display's scale for scaling independent sizing
setStatusBarHeight(topGroup.readEntry(u"statusBarHeight"_s, -1.0) / m_output->scale());
setStatusBarLeftPadding(topGroup.readEntry(u"statusBarLeftPadding"_s, 0.0) / m_output->scale());
setStatusBarRightPadding(topGroup.readEntry(u"statusBarRightPadding"_s, 0.0) / m_output->scale());
setStatusBarCenterSpacing(topGroup.readEntry(u"statusBarCenterSpacing"_s, 0.0) / m_output->scale());
setNavigationPanelHeight(bottomGroup.readEntry(u"navigationPanelHeight"_s, -1.0) / m_output->scale());
setNavigationPanelLeftPadding(bottomGroup.readEntry(u"navigationPanelLeftPadding"_s, 0.0) / m_output->scale());
setNavigationPanelRightPadding(bottomGroup.readEntry(u"navigationPanelRightPadding"_s, 0.0) / m_output->scale());
}
int PanelSettingsDBusObject::outputId() const
{
return m_outputId;
}
QString PanelSettingsDBusObject::outputName() const
{
return m_outputName;
}
qreal PanelSettingsDBusObject::statusBarHeight() const
{
return m_statusBarHeight;
}
void PanelSettingsDBusObject::setStatusBarHeight(qreal statusBarHeight)
{
if (statusBarHeight == m_statusBarHeight) {
return;
}
m_statusBarHeight = statusBarHeight;
Q_EMIT statusBarHeightChanged();
}
qreal PanelSettingsDBusObject::statusBarLeftPadding() const
{
return m_statusBarLeftPadding;
}
void PanelSettingsDBusObject::setStatusBarLeftPadding(qreal statusBarLeftPadding)
{
if (statusBarLeftPadding == m_statusBarLeftPadding) {
return;
}
m_statusBarLeftPadding = statusBarLeftPadding;
Q_EMIT statusBarLeftPaddingChanged();
}
qreal PanelSettingsDBusObject::statusBarRightPadding() const
{
return m_statusBarRightPadding;
}
void PanelSettingsDBusObject::setStatusBarRightPadding(qreal statusBarRightPadding)
{
if (statusBarRightPadding == m_statusBarRightPadding) {
return;
}
m_statusBarRightPadding = statusBarRightPadding;
Q_EMIT statusBarRightPaddingChanged();
}
qreal PanelSettingsDBusObject::statusBarCenterSpacing() const
{
return m_statusBarCenterSpacing;
}
void PanelSettingsDBusObject::setStatusBarCenterSpacing(qreal statusBarCenterSpacing)
{
if (statusBarCenterSpacing == m_statusBarCenterSpacing) {
return;
}
m_statusBarCenterSpacing = statusBarCenterSpacing;
Q_EMIT statusBarCenterSpacingChanged();
}
qreal PanelSettingsDBusObject::navigationPanelHeight() const
{
return m_navigationPanelHeight;
}
void PanelSettingsDBusObject::setNavigationPanelHeight(qreal navigationPanelHeight)
{
if (navigationPanelHeight == m_navigationPanelHeight) {
return;
}
m_navigationPanelHeight = navigationPanelHeight;
Q_EMIT navigationPanelHeightChanged();
}
qreal PanelSettingsDBusObject::navigationPanelLeftPadding() const
{
return m_navigationPanelLeftPadding;
}
void PanelSettingsDBusObject::setNavigationPanelLeftPadding(qreal navigationPanelLeftPadding)
{
if (navigationPanelLeftPadding == m_navigationPanelLeftPadding) {
return;
}
m_navigationPanelLeftPadding = navigationPanelLeftPadding;
Q_EMIT navigationPanelLeftPaddingChanged();
}
qreal PanelSettingsDBusObject::navigationPanelRightPadding() const
{
return m_navigationPanelRightPadding;
}
void PanelSettingsDBusObject::setNavigationPanelRightPadding(qreal navigationPanelRightPadding)
{
if (navigationPanelRightPadding == m_navigationPanelRightPadding) {
return;
}
m_navigationPanelRightPadding = navigationPanelRightPadding;
Q_EMIT navigationPanelRightPaddingChanged();
}