feat(settings): add a hot corners page and list the show desktop shortcut

main
reionwong 2 weeks ago
parent e11e9b7e8f
commit 266e9aa388

@ -46,6 +46,7 @@ set(SRCS
src/cursor/cursorthememodel.cpp
src/cursor/cursortheme.cpp
src/cursor/mouse.cpp
src/hotcorners/hotcorners.cpp
src/datetime/time.cpp
src/datetime/timezonemap.cpp
src/touchpad.cpp

@ -31,6 +31,7 @@
#include "cursor/cursorthememodel.h"
#include "cursor/mouse.h"
#include "hotcorners/hotcorners.h"
#include "icontheme/iconthememodel.h"
#include "datetime/time.h"
@ -103,6 +104,7 @@ int Application::run()
qmlRegisterType<Fonts>(uri, 1, 0, "Fonts");
qmlRegisterType<PowerManager>(uri, 1, 0, "PowerManager");
qmlRegisterType<Mouse>(uri, 1, 0, "Mouse");
qmlRegisterType<HotCorners>(uri, 1, 0, "HotCorners");
qmlRegisterType<Time>(uri, 1, 0, "Time");
qmlRegisterType<TimeZoneMap>(uri, 1, 0, "TimeZoneMap");
qmlRegisterType<Touchpad>(uri, 1, 0, "Touchpad");

@ -0,0 +1,99 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "hotcorners.h"
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QDBusReply>
#include <QDBusServiceWatcher>
#include <QDebug>
static constexpr auto service = "com.cutefish.HotCorners1";
static constexpr auto path = "/com/cutefish/HotCorners1";
// The compositor's modifier bits, as in the shortcuts service.
static constexpr int ShiftDown = 1 << 0;
static constexpr int ControlDown = 1 << 1;
static constexpr int AltDown = 1 << 2;
static constexpr int MetaDown = 1 << 3;
HotCorners::HotCorners(QObject *parent)
: QObject(parent)
{
auto *watcher = new QDBusServiceWatcher(QLatin1String(service), QDBusConnection::sessionBus(),
QDBusServiceWatcher::WatchForOwnerChange, this);
connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &HotCorners::refreshInterface);
refreshInterface();
QDBusInterface shortcuts(QStringLiteral("com.cutefish.Shortcuts1"),
QStringLiteral("/com/cutefish/Shortcuts1"),
QStringLiteral("com.cutefish.Shortcuts1"), QDBusConnection::sessionBus());
m_commandKeys = shortcuts.isValid()
&& shortcuts.property("shortcutConvention").toString() == QLatin1String("command");
}
void HotCorners::refreshInterface()
{
delete m_interface;
m_interface = new QDBusInterface(QLatin1String(service), QLatin1String(path),
QLatin1String(service), QDBusConnection::sessionBus(), this);
if (m_interface->isValid())
connect(m_interface, SIGNAL(changed()), this, SIGNAL(changed()));
emit changed();
}
bool HotCorners::available() const
{
return m_interface->isValid();
}
void HotCorners::setCorner(const QString &corner, const QString &action, int modifiers)
{
if (!m_interface->isValid())
return;
auto *watcher = new QDBusPendingCallWatcher(
m_interface->asyncCall(QStringLiteral("setCorner"), corner, action, modifiers), this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *reply) {
const QDBusPendingReply<> result = *reply;
if (result.isError()) {
qWarning() << "Cannot update hot corner" << result.error().message();
emit changed();
}
reply->deleteLater();
});
}
int HotCorners::heldModifiers() const
{
if (!m_interface->isValid())
return 0;
const QDBusReply<int> reply = m_interface->call(QStringLiteral("heldModifiers"));
return reply.isValid() ? reply.value() : 0;
}
QString HotCorners::modifierText(int modifiers) const
{
// Under the Command convention Meta is the Command key, Alt is Option.
QStringList parts;
if (modifiers & ControlDown)
parts << (m_commandKeys ? tr("Control") : tr("Ctrl"));
if (modifiers & AltDown)
parts << (m_commandKeys ? tr("Option") : tr("Alt"));
if (modifiers & ShiftDown)
parts << tr("Shift");
if (modifiers & MetaDown)
parts << (m_commandKeys ? tr("Command") : tr("Meta"));
return parts.join(QStringLiteral(" + "));
}
QString HotCorners::stringProperty(const char *name) const
{
return m_interface->isValid() ? m_interface->property(name).toString() : QString();
}
int HotCorners::intProperty(const char *name) const
{
return m_interface->isValid() ? m_interface->property(name).toInt() : 0;
}

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef HOTCORNERS_H
#define HOTCORNERS_H
#include <QDBusInterface>
#include <QObject>
// The compositor's com.cutefish.HotCorners1: what each screen corner runs.
class HotCorners : public QObject
{
Q_OBJECT
Q_PROPERTY(bool available READ available NOTIFY changed)
Q_PROPERTY(QString topLeft READ topLeft NOTIFY changed)
Q_PROPERTY(int topLeftModifiers READ topLeftModifiers NOTIFY changed)
Q_PROPERTY(QString topRight READ topRight NOTIFY changed)
Q_PROPERTY(int topRightModifiers READ topRightModifiers NOTIFY changed)
Q_PROPERTY(QString bottomLeft READ bottomLeft NOTIFY changed)
Q_PROPERTY(int bottomLeftModifiers READ bottomLeftModifiers NOTIFY changed)
Q_PROPERTY(QString bottomRight READ bottomRight NOTIFY changed)
Q_PROPERTY(int bottomRightModifiers READ bottomRightModifiers NOTIFY changed)
public:
explicit HotCorners(QObject *parent = nullptr);
bool available() const;
QString topLeft() const { return stringProperty("topLeft"); }
int topLeftModifiers() const { return intProperty("topLeftModifiers"); }
QString topRight() const { return stringProperty("topRight"); }
int topRightModifiers() const { return intProperty("topRightModifiers"); }
QString bottomLeft() const { return stringProperty("bottomLeft"); }
int bottomLeftModifiers() const { return intProperty("bottomLeftModifiers"); }
QString bottomRight() const { return stringProperty("bottomRight"); }
int bottomRightModifiers() const { return intProperty("bottomRightModifiers"); }
// `corner` is top-left, top-right, bottom-left or bottom-right; an empty
// action turns it off.
Q_INVOKABLE void setCorner(const QString &corner, const QString &action, int modifiers);
// The modifiers held now, as the compositor sees them.
Q_INVOKABLE int heldModifiers() const;
// "Option + Shift" and the like, in the session's shortcut convention.
Q_INVOKABLE QString modifierText(int modifiers) const;
signals:
void changed();
private:
void refreshInterface();
QString stringProperty(const char *name) const;
int intProperty(const char *name) const;
QDBusInterface *m_interface = nullptr;
bool m_commandKeys = false;
};
#endif // HOTCORNERS_H

@ -119,6 +119,13 @@ ItemPage {
}
}
ListCard {
NavigationItem {
text: qsTr("Hot Corners")
onClicked: rootWindow.pushPage("qrc:/qml/HotCorners/Main.qml")
}
}
ListCard {
ListRow {
interactive: false

@ -0,0 +1,112 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import FishUI as FishUI
import Cutefish.Settings
import "../"
ItemPage {
id: page
headerTitle: qsTr("Hot Corners")
HotCorners {
id: hotCorners
}
// The compositor's action names, "" for none, and what the menu calls them.
readonly property var actions: ["", "toggle-overview", "toggle-show-desktop", "toggle-app-list", "lock-screen"]
readonly property var actionNames: [qsTr("None"), qsTr("Show All Windows"), qsTr("Show Desktop"),
qsTr("Launcher"), qsTr("Lock Screen")]
component CornerRow: ListRow {
id: row
property string corner
property string action
property int modifiers
interactive: false
RowComboBox {
model: page.actionNames
currentIndex: Math.max(0, page.actions.indexOf(row.action))
displayText: row.modifiers !== 0 && currentIndex > 0
? hotCorners.modifierText(row.modifiers) + " – " + currentText
: currentText
// Keys held while choosing are the ones the corner will ask for.
onActivated: (index) => hotCorners.setCorner(row.corner, page.actions[index],
index > 0 ? hotCorners.heldModifiers() : 0)
}
}
Scrollable {
anchors.fill: parent
contentHeight: layout.implicitHeight
ColumnLayout {
id: layout
anchors.fill: parent
spacing: Metrics.sectionSpacing
RoundedItem {
visible: !hotCorners.available
FishUI.Label {
Layout.fillWidth: true
text: qsTr("Hot corners are managed by the Cutefish compositor, which is not running.")
color: FishUI.Theme.disabledTextColor
wrapMode: Text.WordWrap
}
}
ListCard {
visible: hotCorners.available
CornerRow {
text: qsTr("Top left")
corner: "top-left"
action: hotCorners.topLeft
modifiers: hotCorners.topLeftModifiers
}
CornerRow {
text: qsTr("Top right")
corner: "top-right"
action: hotCorners.topRight
modifiers: hotCorners.topRightModifiers
}
CornerRow {
text: qsTr("Bottom left")
corner: "bottom-left"
action: hotCorners.bottomLeft
modifiers: hotCorners.bottomLeftModifiers
}
CornerRow {
text: qsTr("Bottom right")
corner: "bottom-right"
action: hotCorners.bottomRight
modifiers: hotCorners.bottomRightModifiers
}
}
FishUI.Label {
visible: hotCorners.available
Layout.fillWidth: true
Layout.leftMargin: Metrics.cardHorizontalPadding
Layout.rightMargin: Metrics.cardHorizontalPadding
text: qsTr("Move the pointer into a corner of the screen to run its action. Hold modifier keys while choosing an action to make the corner work only while they are held.")
color: FishUI.Theme.disabledTextColor
wrapMode: Text.WordWrap
}
Item {
Layout.fillHeight: true
}
}
}
}

@ -111,6 +111,15 @@ Item {
iconColor: "#5E6AD2",
keywords: qsTr("input source")
},
{
title: qsTr("Hot Corners"),
name: "hotcorners",
page: "qrc:/qml/HotCorners/Main.qml",
parentName: "dock",
iconSource: "dock.svg",
iconColor: "#8585FC",
keywords: qsTr("screen corners, show desktop, show all windows")
},
{
title: qsTr("Keyboard Shortcuts"),
page: "qrc:/qml/Shortcuts/Main.qml",

@ -67,6 +67,7 @@
<file>qml/Battery/BatterySection.qml</file>
<file>qml/Battery/HistoryGraph.qml</file>
<file>qml/Dock/Main.qml</file>
<file>qml/HotCorners/Main.qml</file>
<file>images/sidebar/dark/power.svg</file>
<file>qml/WLAN/Main.qml</file>
<file>qml/Wired/Main.qml</file>

@ -87,6 +87,7 @@ void ShortcutsModel::reload()
{"cycle-forward-mru", windows, tr("Switch to the next window")},
{"cycle-backward-mru", windows, tr("Switch to the previous window")},
{"toggle-overview", windows, tr("Show all windows")},
{"toggle-show-desktop", windows, tr("Show the desktop")},
{"toggle-maximized", windows, tr("Maximize or restore the window")},
{"window-minimize", windows, tr("Minimize the window")},
{"quit-application", windows, tr("Quit the application")},

Loading…
Cancel
Save