[kcmkwin/desktop] KCM using new virtual desktops DBus interface

Summary:
A rewrite of the Virtual Desktops KCM using the new DBus
API.

Depends on D13887.

Reviewers: mart, davidedmundson, ltoscano, zzag

Subscribers: davidedmundson, broulik, plasma-devel, kwin

Tags: #kwin

Maniphest Tasks: T4457

Differential Revision: https://phabricator.kde.org/D14542
icc-effect-5.17.5
Eike Hein 2018-11-30 00:57:35 +09:00
parent 9993c6d674
commit cee5ea7819
14 changed files with 1447 additions and 1306 deletions

View File

@ -1,33 +1,27 @@
include(ECMQMLModules)
ecm_find_qmlmodule(org.kde.plasma.core 2.0)
# KI18N Translation Domain for this library.
add_definitions(-DTRANSLATION_DOMAIN=\"kcm_kwin_virtualdesktops\")
########### next target ###############
# KI18N Translation Domain for this library
add_definitions(-DTRANSLATION_DOMAIN=\"kcm_kwindesktop\")
include_directories(${KWIN_SOURCE_DIR}/effects)
set(kcm_kwin_virtualdesktops_PART_SRCS virtualdesktops.cpp desktopsmodel.cpp ../../virtualdesktopsdbustypes.cpp)
set(kcm_kwindesktop_PART_SRCS main.cpp desktopnameswidget.cpp)
ki18n_wrap_ui(kcm_kwindesktop_PART_SRCS main.ui)
qt5_add_dbus_interface( kcm_kwindesktop_PART_SRCS
${KWIN_SOURCE_DIR}/org.kde.kwin.Effects.xml kwin_effects_interface)
add_library(kcm_kwin_virtualdesktops MODULE ${kcm_kwin_virtualdesktops_PART_SRCS})
add_library(kcm_kwindesktop MODULE ${kcm_kwindesktop_PART_SRCS})
target_link_libraries(kcm_kwindesktop
Qt5::X11Extras
KF5::KCMUtils
KF5::Completion
KF5::GlobalAccel
target_link_libraries(kcm_kwin_virtualdesktops
Qt5::DBus
KF5::I18n
KF5::Package
KF5::WindowSystem
KF5::XmlGui
${X11_LIBRARIES}
kwin4_effect_builtins
KF5::KCMUtils
KF5::QuickAddons
)
install(TARGETS kcm_kwindesktop DESTINATION ${PLUGIN_INSTALL_DIR} )
kcoreaddons_desktop_to_json(kcm_kwin_virtualdesktops "kcm_kwin_virtualdesktops.desktop")
########### install files ###############
install( FILES desktop.desktop DESTINATION ${SERVICES_INSTALL_DIR} )
install(TARGETS kcm_kwin_virtualdesktops DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
install(FILES kcm_kwin_virtualdesktops.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
kpackage_install_package(package kcm_kwin_virtualdesktops kcms)

View File

@ -1,4 +1,2 @@
#! /usr/bin/env bash
$EXTRACTRC `find . -name \*.ui` >> rc.cpp || exit 11
$XGETTEXT *.cpp -o $podir/kcm_kwindesktop.pot
rm -f rc.cpp
$XGETTEXT `find . -name \*.cpp -o -name \*.qml` -o $podir/kcm_kwin_virtualdesktops.pot

View File

@ -1,124 +0,0 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2009 Martin Gräßlin <mgraesslin@kde.org>
This program 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.
This program 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 "desktopnameswidget.h"
#include "main.h"
#include <QLabel>
#include <QGridLayout>
#include <KLocalizedString>
#include <KLineEdit>
namespace KWin
{
DesktopNamesWidget::DesktopNamesWidget(QWidget *parent)
: QWidget(parent)
, m_maxDesktops(0)
, m_desktopConfig(0)
{
m_namesLayout = new QGridLayout;
m_namesLayout->setMargin(0);
setLayout(m_namesLayout);
}
DesktopNamesWidget::~DesktopNamesWidget()
{
}
void DesktopNamesWidget::numberChanged(int number)
{
if ((number < 1) || (number > m_maxDesktops))
return;
if (m_nameInputs.size() != number) {
if (number < m_nameInputs.size()) {
// remove widgets
while (number != m_nameInputs.size()) {
KLineEdit* edit = m_nameInputs.last();
m_nameInputs.removeLast();
delete edit;
QLabel* label = m_nameLabels.last();
m_nameLabels.removeLast();
delete label;
}
} else {
// add widgets
while (number != m_nameInputs.size()) {
int desktop = m_nameInputs.size();
QLabel* label = new QLabel(i18n("Desktop %1:", desktop + 1), this);
KLineEdit* edit = new KLineEdit(this);
label->setWhatsThis(i18n("Here you can enter the name for desktop %1", desktop + 1));
edit->setWhatsThis(i18n("Here you can enter the name for desktop %1", desktop + 1));
m_namesLayout->addWidget(label, desktop % 10, 0 + 2 *(desktop >= 10), 1, 1);
m_namesLayout->addWidget(edit, desktop % 10, 1 + 2 *(desktop >= 10), 1, 1);
m_nameInputs << edit;
m_nameLabels << label;
setDefaultName(desktop + 1);
if (desktop > 1) {
setTabOrder(m_nameInputs[desktop - 1], m_nameInputs[desktop]);
}
connect(edit, SIGNAL(textChanged(QString)), SIGNAL(changed()));
}
}
}
}
QString DesktopNamesWidget::name(int desktop)
{
if ((desktop < 1) || (desktop > m_maxDesktops) || (desktop > m_nameInputs.size()))
return QString();
return m_nameInputs[ desktop -1 ]->text();
}
void DesktopNamesWidget::setName(int desktop, QString desktopName)
{
if ((desktop < 1) || (desktop > m_maxDesktops) || (desktop > m_nameInputs.size()))
return;
m_nameInputs[ desktop-1 ]->setText(desktopName);
}
void DesktopNamesWidget::setDefaultName(int desktop)
{
if ((desktop < 1) || (desktop > m_maxDesktops))
return;
QString name = m_desktopConfig->cachedDesktopName(desktop);
if (name.isEmpty())
name = i18n("Desktop %1", desktop);
m_nameInputs[ desktop -1 ]->setText(name);
}
void DesktopNamesWidget::setMaxDesktops(int maxDesktops)
{
m_maxDesktops = maxDesktops;
}
void DesktopNamesWidget::setDesktopConfig(KWinDesktopConfig* desktopConfig)
{
m_desktopConfig = desktopConfig;
}
} // namespace

View File

@ -1,63 +0,0 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2009 Martin Gräßlin <mgraesslin@kde.org>
This program 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.
This program 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/>.
*********************************************************************/
#ifndef DESKTOPNAMESWIDGET_H
#define DESKTOPNAMESWIDGET_H
#include <QWidget>
#include <QList>
class KLineEdit;
class QLabel;
class QGridLayout;
namespace KWin
{
class KWinDesktopConfig;
class DesktopNamesWidget : public QWidget
{
Q_OBJECT
public:
explicit DesktopNamesWidget(QWidget *parent);
~DesktopNamesWidget();
QString name(int desktop);
void setName(int desktop, QString desktopName);
void setDefaultName(int desktop);
void setMaxDesktops(int maxDesktops);
void setDesktopConfig(KWinDesktopConfig *desktopConfig);
Q_SIGNALS:
void changed();
public Q_SLOTS:
void numberChanged(int number);
private:
QList< QLabel* > m_nameLabels;
QList< KLineEdit* > m_nameInputs;
QGridLayout* m_namesLayout;
int m_maxDesktops;
KWinDesktopConfig *m_desktopConfig;
};
} // namespace
#endif // DESKTOPNAMESWIDGET_H

View File

@ -0,0 +1,637 @@
/*
* Copyright (C) 2018 Eike Hein <hein@kde.org>
* Copyright (C) 2018 Marco Martin <mart@kde.org>
*
* This program 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.
*
* This program 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 "desktopsmodel.h"
#include <cmath>
#include <KLocalizedString>
#include <QDBusArgument>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusMetaType>
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QDBusServiceWatcher>
#include <QDBusVariant>
#include <QMetaEnum>
#include <QUuid>
namespace KWin
{
static const QString s_serviceName(QStringLiteral("org.kde.KWin"));
static const QString s_virtualDesktopsInterface(QStringLiteral("org.kde.KWin.VirtualDesktopManager"));
static const QString s_virtDesktopsPath(QStringLiteral("/VirtualDesktopManager"));
static const QString s_fdoPropertiesInterface(QStringLiteral("org.freedesktop.DBus.Properties"));
DesktopsModel::DesktopsModel(QObject *parent)
: QAbstractListModel(parent)
, m_userModified(false)
, m_serverModified(false)
, m_serverSideRows(-1)
, m_rows(-1)
, m_synchronizing(false)
{
qDBusRegisterMetaType<KWin::DBusDesktopDataStruct>();
qDBusRegisterMetaType<KWin::DBusDesktopDataVector>();
m_serviceWatcher = new QDBusServiceWatcher(s_serviceName,
QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange);
QObject::connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered,
this, [this]() { reset(); });
QObject::connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered,
this, [this]() {
QDBusConnection::sessionBus().disconnect(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("desktopCreated"),
this,
SLOT(desktopCreated(QString,KWin::DBusDesktopDataStruct)));
QDBusConnection::sessionBus().disconnect(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("desktopRemoved"),
this,
SLOT(desktopRemoved(QString)));
QDBusConnection::sessionBus().disconnect(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("desktopDataChanged"),
this,
SLOT(desktopDataChanged(QString,KWin::DBusDesktopDataStruct)));
QDBusConnection::sessionBus().disconnect(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("rowsChanged"),
this,
SLOT(desktopRowsChanged(uint)));
}
);
reset();
}
DesktopsModel::~DesktopsModel()
{
}
QHash<int, QByteArray> DesktopsModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles"));
for (int i = 0; i < e.keyCount(); ++i) {
roles.insert(e.value(i), e.key(i));
}
return roles;
}
QVariant DesktopsModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() < 0 || index.row() > (m_desktops.count() - 1)) {
return QVariant();
}
if (role == Qt::DisplayRole) {
return m_names.value(m_desktops.at(index.row()));
} else if (role == Id) {
return m_desktops.at(index.row());
} else if (role == DesktopRow) {
const int rows = std::max(m_rows, 1);
const int perRow = std::ceil((qreal)m_desktops.count() / (qreal)rows);
return (index.row() / perRow) + 1;
}
return QVariant();
}
int DesktopsModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_desktops.count();
}
bool DesktopsModel::ready() const
{
return !m_desktops.isEmpty();
}
QString DesktopsModel::error() const
{
return m_error;
}
bool DesktopsModel::userModified() const
{
return m_userModified;
}
bool DesktopsModel::serverModified() const
{
return m_serverModified;
}
int DesktopsModel::rows() const
{
return m_rows;
}
void DesktopsModel::setRows(int rows)
{
if (!ready()) {
return;
}
if (m_rows != rows) {
m_rows = rows;
emit rowsChanged();
emit dataChanged(index(0, 0), index(m_desktops.count() - 1, 0), QVector<int>{DesktopRow});
updateModifiedState();
}
}
void DesktopsModel::createDesktop(const QString &name)
{
if (!ready()) {
return;
}
beginInsertRows(QModelIndex(), m_desktops.count(), m_desktops.count());
const QString &dummyId = QUuid::createUuid().toString(QUuid::WithoutBraces);
m_desktops.append(dummyId);
m_names[dummyId] = name;
endInsertRows();
updateModifiedState();
}
void DesktopsModel::removeDesktop(const QString &id)
{
if (!ready() || !m_desktops.contains(id)) {
return;
}
const int desktopIndex = m_desktops.indexOf(id);
beginRemoveRows(QModelIndex(), desktopIndex, desktopIndex);
m_desktops.removeAt(desktopIndex);
m_names.remove(id);
endRemoveRows();
updateModifiedState();
}
void DesktopsModel::setDesktopName(const QString &id, const QString &name)
{
if (!ready() || !m_desktops.contains(id)) {
return;
}
m_names[id] = name;
const QModelIndex &idx = index(m_desktops.indexOf(id), 0);
dataChanged(idx, idx, QVector<int>{Qt::DisplayRole});
updateModifiedState();
}
void DesktopsModel::syncWithServer()
{
m_synchronizing = true;
auto callFinished = [this](QDBusPendingCallWatcher *call) {
QDBusPendingReply<void> reply = *call;
if (reply.isError()) {
handleCallError();
}
call->deleteLater();
};
if (m_desktops.count() > m_serverSideDesktops.count()) {
auto call = QDBusMessage::createMethodCall(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("createDesktop"));
const int newIndex = m_serverSideDesktops.count();
call.setArguments({(uint)newIndex, m_names.value(m_desktops.at(newIndex))});
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call);
const auto *watcher = new QDBusPendingCallWatcher(pending, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished);
return; // The change-handling slot will call syncWithServer() again,
// until everything is in sync.
}
if (m_desktops.count() < m_serverSideDesktops.count()) {
QStringListIterator i(m_serverSideDesktops);
i.toBack();
while (i.hasPrevious()) {
const QString &previous = i.previous();
if (!m_desktops.contains(previous)) {
auto call = QDBusMessage::createMethodCall(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("removeDesktop"));
call.setArguments({previous});
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call);
const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished);
return; // The change-handling slot will call syncWithServer() again,
// until everything is in sync.
}
}
}
// Sync ids. Replace dummy ids in the process.
for (int i = 0; i < m_serverSideDesktops.count(); ++i) {
const QString oldId = m_desktops.at(i);
const QString &newId = m_serverSideDesktops.at(i);
m_desktops[i] = newId;
m_names[newId] = m_names.take(oldId);
}
emit dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector<int>{Qt::DisplayRole});
// Sync names.
if (m_names != m_serverSideNames) {
QHashIterator<QString, QString> i(m_names);
while (i.hasNext()) {
i.next();
if (i.value() != m_serverSideNames.value(i.key())) {
auto call = QDBusMessage::createMethodCall(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("setDesktopName"));
call.setArguments({i.key(), i.value()});
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call);
const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished);
break;
}
}
return; // The change-handling slot will call syncWithServer() again,
// until everything is in sync..
}
// Sync rows.
if (m_rows != m_serverSideRows) {
auto call = QDBusMessage::createMethodCall(
s_serviceName,
s_virtDesktopsPath,
s_fdoPropertiesInterface,
QStringLiteral("Set"));
call.setArguments({s_virtualDesktopsInterface,
QStringLiteral("rows"), QVariant::fromValue(QDBusVariant(QVariant((uint)m_rows)))});
QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call);
const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished);
}
}
void DesktopsModel::reset()
{
m_synchronizing = false; // Sanity.
auto getAllAndConnectCall = QDBusMessage::createMethodCall(
s_serviceName,
s_virtDesktopsPath,
s_fdoPropertiesInterface,
QStringLiteral("GetAll"));
getAllAndConnectCall.setArguments({s_virtualDesktopsInterface});
QDBusConnection::sessionBus().callWithCallback(
getAllAndConnectCall,
this,
SLOT(getAllAndConnect(QDBusMessage)),
SLOT(handleCallError()));
}
void DesktopsModel::getAllAndConnect(const QDBusMessage &msg)
{
const QVariantMap &data = qdbus_cast<QVariantMap>(msg.arguments().at(0).value<QDBusArgument>());
const KWin::DBusDesktopDataVector &desktops = qdbus_cast<KWin::DBusDesktopDataVector>(
data.value(QStringLiteral("desktops")).value<QDBusArgument>()
);
const int newServerSideRows = data.value(QStringLiteral("rows")).toUInt();
QStringList newServerSideDesktops;
QHash<QString,QString> newServerSideNames;
for (const KWin::DBusDesktopDataStruct &d : desktops) {
newServerSideDesktops.append(d.id);
newServerSideNames[d.id] = d.name;
}
// If the server-side state changed during a KWin restart, and the
// user had made notifications, the model should notify about the
// change.
if (m_serverSideDesktops != newServerSideDesktops
|| m_serverSideNames != newServerSideNames
|| m_serverSideRows != newServerSideRows) {
if (!m_serverSideDesktops.isEmpty() || m_userModified) {
m_serverModified = true;
emit serverModifiedChanged();
}
m_serverSideDesktops = newServerSideDesktops;
m_serverSideNames = newServerSideNames;
m_serverSideRows = newServerSideRows;
}
// For the case KWin restarts while the KCM was open: If the user had
// made no modifications, just reset to the server data. E.g. perhaps
// the user intentionally nuked the KWin config while it was down, so
// we should follow.
if (!m_userModified || m_desktops.empty()) {
beginResetModel();
m_desktops = m_serverSideDesktops;
m_names = m_serverSideNames;
m_rows = m_serverSideRows;
endResetModel();
}
emit readyChanged();
auto handleConnectionError = [this]() {
m_error = i18n("There was an error connecting to the compositor.");
emit errorChanged();
};
bool connected = QDBusConnection::sessionBus().connect(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("desktopCreated"),
this,
SLOT(desktopCreated(QString,KWin::DBusDesktopDataStruct)));
if (!connected) {
handleConnectionError();
return;
}
connected = QDBusConnection::sessionBus().connect(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("desktopRemoved"),
this,
SLOT(desktopRemoved(QString)));
if (!connected) {
handleConnectionError();
return;
}
connected = QDBusConnection::sessionBus().connect(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("desktopDataChanged"),
this,
SLOT(desktopDataChanged(QString,KWin::DBusDesktopDataStruct)));
if (!connected) {
handleConnectionError();
return;
}
connected = QDBusConnection::sessionBus().connect(
s_serviceName,
s_virtDesktopsPath,
s_virtualDesktopsInterface,
QStringLiteral("rowsChanged"),
this,
SLOT(desktopRowsChanged(uint)));
if (!connected) {
handleConnectionError();
return;
}
}
void DesktopsModel::desktopCreated(const QString &id, const KWin::DBusDesktopDataStruct &data)
{
m_serverSideDesktops.insert(data.position, id);
m_serverSideNames[data.id] = data.name;
// If the user didn't make any changes, we can just stay in sync.
if (!m_userModified) {
beginInsertRows(QModelIndex(), data.position, data.position);
m_desktops = m_serverSideDesktops;
m_names = m_serverSideNames;
endInsertRows();
} else {
// Remove dummy data.
const QString dummyId = m_desktops.at(data.position);
m_desktops[data.position] = id;
m_names.remove(dummyId);
m_names[id] = data.name;
const QModelIndex &idx = index(data.position, 0);
emit dataChanged(idx, idx, QVector<int>{Id});
updateModifiedState(/* server */ true);
}
}
void DesktopsModel::desktopRemoved(const QString &id)
{
const int desktopIndex = m_serverSideDesktops.indexOf(id);
m_serverSideDesktops.removeAt(desktopIndex);
m_serverSideNames.remove(id);
// If the user didn't make any changes, we can just stay in sync.
if (!m_userModified) {
beginRemoveRows(QModelIndex(), desktopIndex, desktopIndex);
m_desktops = m_serverSideDesktops;
m_names = m_serverSideNames;
endRemoveRows();
} else {
updateModifiedState(/* server */ true);
}
}
void DesktopsModel::desktopDataChanged(const QString &id, const KWin::DBusDesktopDataStruct &data)
{
const int desktopIndex = m_serverSideDesktops.indexOf(id);
m_serverSideDesktops[desktopIndex] = id;
m_serverSideNames[id] = data.name;
// If the user didn't make any changes, we can just stay in sync.
if (!m_userModified) {
m_desktops = m_serverSideDesktops;
m_names = m_serverSideNames;
const QModelIndex &idx = index(desktopIndex, 0);
dataChanged(idx, idx, QVector<int>{Qt::DisplayRole});
} else {
updateModifiedState(/* server */ true);
}
}
void DesktopsModel::desktopRowsChanged(uint rows)
{
// Unfortunately we sometimes get this signal from the server with an unchanged value.
if ((int)rows == m_serverSideRows) {
return;
}
m_serverSideRows = rows;
// If the user didn't make any changes, we can just stay in sync.
if (!m_userModified) {
m_rows = m_serverSideRows;
emit rowsChanged();
emit dataChanged(index(0, 0), index(m_desktops.count() - 1, 0), QVector<int>{DesktopRow});
} else {
updateModifiedState(/* server */ true);
}
}
void DesktopsModel::updateModifiedState(bool server)
{
// Count is the same but contents are not: The user may have
// removed and created new desktops in the UI, but there were
// no changes to send to the server because number and names
// have remained the same. In that case we can just clean
// that up here.
if (m_desktops.count() == m_serverSideDesktops.count()
&& m_desktops != m_serverSideDesktops) {
for (int i = 0; i < m_serverSideDesktops.count(); ++i) {
const QString oldId = m_desktops.at(i);
const QString &newId = m_serverSideDesktops.at(i);
m_desktops[i] = newId;
m_names[newId] = m_names.take(oldId);
}
emit dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector<int>{Qt::DisplayRole});
}
if (m_desktops == m_serverSideDesktops
&& m_names == m_serverSideNames
&& m_rows == m_serverSideRows) {
m_userModified = false;
emit userModifiedChanged();
m_serverModified = false;
emit serverModifiedChanged();
m_synchronizing = false;
} else {
if (m_synchronizing) {
m_serverModified = false;
emit serverModifiedChanged();
syncWithServer();
} else if (server) {
m_serverModified = true;
emit serverModifiedChanged();
} else {
m_userModified = true;
emit userModifiedChanged();
}
}
}
void DesktopsModel::handleCallError()
{
if (m_synchronizing) {
m_synchronizing = false;
m_serverModified = false;
emit serverModifiedChanged();
m_error = i18n("There was an error saving the settings to the compositor.");
emit errorChanged();
} else {
m_error = i18n("There was an error requesting information from the compositor.");
emit errorChanged();
}
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright (C) 2018 Eike Hein <hein@kde.org>
* Copyright (C) 2018 Marco Martin <mart@kde.org>
*
* This program 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.
*
* This program 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/>.
*/
#ifndef DESKTOPSMODEL_H
#define DESKTOPSMODEL_H
#include <QAbstractListModel>
#include "../virtualdesktopsdbustypes.h"
class QDBusArgument;
class QDBusMessage;
class QDBusServiceWatcher;
namespace KWin
{
/**
* @short An item model around KWin's D-Bus API for virtual desktops.
*
* The model initially gets the state from KWin and populates.
*
* As long as the user makes no changes, KWin-side changes are directly
* exposed in the model.
*
* If the user makes changes (see the `userModified` property), it stops
* exposing KWin-side changes live, but it keeps track of the KWin-side
* changes, so it can figure out and apply the delta when `syncWithServer`
* is called.
*
* When KWin-side changes happen while the model is user-modified, the
* model signals this via the `serverModified` property. A call to
* `syncWithServer` will overwrite the KWin-side changes.
*
* After synchronization, the model tracks Kwin-side changes again,
* until the user makes further changes.
*
* @author Eike Hein <hein@kde.org>
**/
class DesktopsModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
Q_PROPERTY(QString error READ error NOTIFY errorChanged)
Q_PROPERTY(bool userModified READ userModified NOTIFY userModifiedChanged)
Q_PROPERTY(bool serverModified READ serverModified NOTIFY serverModifiedChanged)
Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged)
public:
enum AdditionalRoles {
Id = Qt::UserRole + 1,
DesktopRow
};
Q_ENUM(AdditionalRoles)
explicit DesktopsModel(QObject *parent = nullptr);
~DesktopsModel() override;
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = {}) const override;
bool ready() const;
QString error() const;
bool userModified() const;
bool serverModified() const;
int rows() const;
void setRows(int rows);
Q_INVOKABLE void createDesktop(const QString &name);
Q_INVOKABLE void removeDesktop(const QString &id);
Q_INVOKABLE void setDesktopName(const QString &id, const QString &name);
Q_INVOKABLE void syncWithServer();
Q_SIGNALS:
void readyChanged() const;
void errorChanged() const;
void userModifiedChanged() const;
void serverModifiedChanged() const;
void rowsChanged() const;
protected Q_SLOTS:
void reset();
void getAllAndConnect(const QDBusMessage &msg);
void desktopCreated(const QString &id, const KWin::DBusDesktopDataStruct &data);
void desktopRemoved(const QString &id);
void desktopDataChanged(const QString &id, const KWin::DBusDesktopDataStruct &data);
void desktopRowsChanged(uint rows);
void updateModifiedState(bool server = false);
void handleCallError();
private:
QDBusServiceWatcher *m_serviceWatcher;
QString m_error;
bool m_userModified;
bool m_serverModified;
QStringList m_serverSideDesktops;
QHash<QString,QString> m_serverSideNames;
int m_serverSideRows;
QStringList m_desktops;
QHash<QString,QString> m_names;
int m_rows;
bool m_synchronizing;
};
}
#endif

View File

@ -1,11 +1,11 @@
[Desktop Entry]
Exec=kcmshell5 kcm_kwin_virtualdesktops
Icon=preferences-desktop
Type=Service
X-KDE-ServiceTypes=KCModule
X-DocPath=kcontrol/desktop/index.html
Icon=preferences-desktop
Exec=kcmshell5 desktop
X-DocPath=kcontrol/kwin_virtualdesktops/index.html
X-KDE-Library=kcm_kwindesktop
X-KDE-Library=kcm_kwin_virtualdesktops
X-KDE-ParentApp=kcontrol
X-KDE-System-Settings-Parent-Category=desktopbehavior
@ -143,7 +143,7 @@ X-KDE-Keywords[nds]=Schriefdisch,Schriefdischen,virtuell,mehr,Schriefdisch-Över
X-KDE-Keywords[nl]=bureaublad,bureaubladen,aantal,virtueel bureaublad,meervoudige bureaubladen,pager,pager-widget,pager-applet,pagerinstellingen
X-KDE-Keywords[nn]=skrivebord,mengd,tal,virtuelt skrivebord,fleire skrivebord,vekslar,vekslarelement,vekslarelement,vekslerinnstillinger,vekslaroppsett
X-KDE-Keywords[pa]=ਡੈਸਕਟਾਪ,ਗਿਣਤੀ,ਨੰਬਰ,ਅੰਕ,ਵਰਚੁਅਲ ਡੈਸਕਟਾਪ,ਕਈ ਡੈਸਕਟਾਪ,ਪੇਜ਼ਰ,ਪੇਜ਼ਰ ਵਿਜੈਟ,ਪੇਜ਼ਰ ਐਪਲਿਟ,ਪੇਜ਼ਰ ਸੈਟਿੰਗਾਂ
X-KDE-Keywords[pl]=pulpit,pulpity,liczba,pulpity wirtualne,wiele pulpitów
X-KDE-Keywords[pl]=pulpit,pulpity,liczba,pulpity wirtualne,wiele pulpitów
X-KDE-Keywords[pt]=ecrã,ecrãs,número,ecrã virtual,múltiplos ecrãs,paginador,elemento paginador,'applet' do paginador,configuração do paginador
X-KDE-Keywords[pt_BR]=área de trabalho,áreas de trabalho,desktop,desktops,número,área de trabalho virtual,múltiplas áreas de trabalho,paginador,elemento paginador,miniaplicativo do paginador,configurações do paginador
X-KDE-Keywords[ru]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,рабочий стол,рабочие столы,число,виртуальный рабочий стол,несколько рабочих столов,переключатель,переключение,виджет переключения,аплет переключения,параметры переключения,настройки переключения
@ -159,3 +159,6 @@ X-KDE-Keywords[uk]=desktop,desktops,number,virtual desktop,multiple desktops,pag
X-KDE-Keywords[x-test]=xxdesktopxx,xxdesktopsxx,xxnumberxx,xxvirtual desktopxx,xxmultiple desktopsxx,xxpagerxx,xxpager widgetxx,xxpager appletxx,xxpager settingsxx
X-KDE-Keywords[zh_CN]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings,桌面,虚拟桌面,多桌面,分页,分页器,分页器组件,分页器设置
X-KDE-Keywords[zh_TW]=desktop,desktops,number,virtual desktop,multiple desktops,pager,pager widget,pager applet,pager settings
Categories=Qt;KDE;X-KDE-settings-translations;

View File

@ -1,671 +0,0 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2009 Martin Gräßlin <mgraesslin@kde.org>
This program 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.
This program 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 "main.h"
#include <effect_builtins.h>
#include <config-kwin.h>
#include <kwin_effects_interface.h>
#include <QDBusMessage>
#include <QDBusConnection>
#include <QDBusInterface>
#include <KAboutData>
#include <KAboutApplicationDialog>
#include <KActionCollection>
#include <KCModuleProxy>
#include <KGlobalAccel>
#include <KLocalizedString>
#include <KPackage/Package>
#include <KPackage/PackageLoader>
#include <KPluginInfo>
#include <KPluginFactory>
#include <KPluginTrader>
#include <KConfigGroup>
#include <KService>
#include <KServiceTypeTrader>
#include <KShortcutsEditor>
#include <QX11Info>
#include <X11/Xlib.h>
#include <fixx11h.h>
#include <netwm.h>
#include <QDialogButtonBox>
K_PLUGIN_FACTORY(KWinDesktopConfigFactory, registerPlugin<KWin::KWinDesktopConfig>();)
namespace KWin
{
KWinDesktopConfigForm::KWinDesktopConfigForm(QWidget* parent)
: QWidget(parent)
{
setupUi(this);
}
KWinDesktopConfig::KWinDesktopConfig(QWidget* parent, const QVariantList& args)
: KCModule(KAboutData::pluginData(QStringLiteral("kcm_kwindesktop")), parent, args)
, m_config(KSharedConfig::openConfig("kwinrc"))
, m_actionCollection(nullptr)
, m_switchDesktopCollection(nullptr)
{
init();
}
void KWinDesktopConfig::init()
{
m_ui = new KWinDesktopConfigForm(this);
// TODO: there has to be a way to add the shortcuts editor to the ui file
m_editor = new KShortcutsEditor(m_ui, KShortcutsEditor::GlobalAction);
m_ui->editorFrame->setLayout(new QVBoxLayout());
m_ui->editorFrame->layout()->setMargin(0);
m_ui->editorFrame->layout()->addWidget(m_editor);
m_ui->desktopNames->setDesktopConfig(this);
m_ui->desktopNames->setMaxDesktops(maxDesktops);
m_ui->desktopNames->numberChanged(defaultDesktops);
QVBoxLayout* layout = new QVBoxLayout(this);
layout->addWidget(m_ui);
setQuickHelp(i18n("<h1>Multiple Desktops</h1>In this module, you can configure how many virtual desktops you want and how these should be labeled."));
// Shortcut config. The shortcut belongs to the component "kwin"!
m_actionCollection = new KActionCollection(this, QStringLiteral("kwin"));
m_actionCollection->setComponentDisplayName(i18n("KWin"));
m_actionCollection->setConfigGroup("Desktop Switching");
m_actionCollection->setConfigGlobal(true);
m_switchDesktopCollection = new KActionCollection(this, QStringLiteral("kwin"));
m_switchDesktopCollection->setComponentDisplayName(i18n("KWin"));
m_switchDesktopCollection->setConfigGroup("Desktop Switching");
m_switchDesktopCollection->setConfigGlobal(true);
// actions for switch desktop collection - other action is filled dynamically
addAction("Switch to Next Desktop", i18n("Switch to Next Desktop"));
addAction("Switch to Previous Desktop", i18n("Switch to Previous Desktop"));
addAction("Switch One Desktop to the Right", i18n("Switch One Desktop to the Right"));
addAction("Switch One Desktop to the Left", i18n("Switch One Desktop to the Left"));
addAction("Switch One Desktop Up", i18n("Switch One Desktop Up"));
addAction("Switch One Desktop Down", i18n("Switch One Desktop Down"));
addAction("Walk Through Desktops", i18n("Walk Through Desktops"));
addAction("Walk Through Desktops (Reverse)", i18n("Walk Through Desktops (Reverse)"));
addAction("Walk Through Desktop List", i18n("Walk Through Desktop List"));
addAction("Walk Through Desktop List (Reverse)", i18n("Walk Through Desktop List (Reverse)"));
m_editor->addCollection(m_switchDesktopCollection, i18n("Desktop Switching"));
// get number of desktops
int n = 1;
if (QX11Info::isPlatformX11()) {
NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames);
n = info.numberOfDesktops();
}
auto addSwitchTo = [this](int i, const QKeySequence &sequence) {
QAction* a = m_actionCollection->addAction(QString("Switch to Desktop %1").arg(i));
a->setProperty("isConfigurationAction", true);
a->setText(i18n("Switch to Desktop %1", i));
KGlobalAccel::setGlobalShortcut(a, sequence);
};
if (n >= 2) {
addSwitchTo(1, Qt::CTRL + Qt::Key_F1);
addSwitchTo(2, Qt::CTRL + Qt::Key_F2);
}
if (n >= 3) {
addSwitchTo(3, Qt::CTRL + Qt::Key_F3);
}
if (n >= 4) {
addSwitchTo(4, Qt::CTRL + Qt::Key_F4);
}
for (int i = 5; i <= n; ++i) {
addSwitchTo(i, QKeySequence());
}
// This should be after the "Switch to Desktop %1" loop. It HAS to be
// there after numberSpinBox is connected to slotChangeShortcuts. We would
// overwrite the users settings if not,
m_ui->numberSpinBox->setValue(n);
m_editor->addCollection(m_actionCollection, i18n("Desktop Switching"));
// search the effect names
// TODO: way to recognize if a effect is not found
KServiceTypeTrader* trader = KServiceTypeTrader::self();
QString fadedesktop;
KService::List services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_fadedesktop'");
if (!services.isEmpty())
fadedesktop = services.first()->name();
m_ui->effectComboBox->addItem(i18n("No Animation"));
m_ui->effectComboBox->addItem(BuiltInEffects::effectData(BuiltInEffect::Slide).displayName);
m_ui->effectComboBox->addItem(BuiltInEffects::effectData(BuiltInEffect::CubeSlide).displayName);
m_ui->effectComboBox->addItem(fadedesktop);
// effect config and info button
m_ui->effectInfoButton->setIcon(QIcon::fromTheme("dialog-information"));
m_ui->effectConfigButton->setIcon(QIcon::fromTheme("configure"));
connect(m_ui->rowsSpinBox, SIGNAL(valueChanged(int)), SLOT(changed()));
connect(m_ui->numberSpinBox, SIGNAL(valueChanged(int)), SLOT(changed()));
connect(m_ui->numberSpinBox, SIGNAL(valueChanged(int)), SLOT(slotChangeShortcuts(int)));
connect(m_ui->desktopNames, SIGNAL(changed()), SLOT(changed()));
connect(m_ui->popupInfoCheckBox, SIGNAL(toggled(bool)), SLOT(changed()));
connect(m_ui->popupHideSpinBox, SIGNAL(valueChanged(int)), SLOT(changed()));
connect(m_ui->desktopLayoutIndicatorCheckBox, SIGNAL(stateChanged(int)), SLOT(changed()));
connect(m_ui->wrapAroundBox, SIGNAL(stateChanged(int)), SLOT(changed()));
connect(m_editor, SIGNAL(keyChange()), SLOT(changed()));
connect(m_ui->allShortcutsCheckBox, SIGNAL(stateChanged(int)), SLOT(slotShowAllShortcuts()));
connect(m_ui->effectComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changed()));
connect(m_ui->effectComboBox, SIGNAL(currentIndexChanged(int)), SLOT(slotEffectSelectionChanged(int)));
connect(m_ui->effectInfoButton, SIGNAL(clicked()), SLOT(slotAboutEffectClicked()));
connect(m_ui->effectConfigButton, SIGNAL(clicked()), SLOT(slotConfigureEffectClicked()));
// Begin check for immutable - taken from old desktops kcm
int kwin_screen_number = QX11Info::appScreen();
m_config = KSharedConfig::openConfig("kwinrc");
QByteArray groupname;
if (kwin_screen_number == 0)
groupname = "Desktops";
else
groupname = "Desktops-screen-" + QByteArray::number(kwin_screen_number);
if (m_config->isGroupImmutable(groupname)) {
m_ui->nameGroup->setEnabled(false);
//number of desktops widgets
m_ui->numberLabel->setEnabled(false);
m_ui->numberSpinBox->setEnabled(false);
m_ui->rowsSpinBox->setEnabled(false);
} else {
KConfigGroup cfgGroup(m_config.data(), groupname.constData());
if (cfgGroup.isEntryImmutable("Number")) {
//number of desktops widgets
m_ui->numberLabel->setEnabled(false);
m_ui->numberSpinBox->setEnabled(false);
m_ui->rowsSpinBox->setEnabled(false);
}
}
// End check for immutable
}
KWinDesktopConfig::~KWinDesktopConfig()
{
undo();
}
void KWinDesktopConfig::addAction(const QString &name, const QString &label)
{
QAction* a = m_switchDesktopCollection->addAction(name);
a->setProperty("isConfigurationAction", true);
a->setText(label);
KGlobalAccel::setGlobalShortcut(a, QKeySequence());
}
void KWinDesktopConfig::defaults()
{
// TODO: plasma stuff
m_ui->numberSpinBox->setValue(defaultDesktops);
m_ui->desktopNames->numberChanged(defaultDesktops);
for (int i = 1; i <= maxDesktops; i++) {
m_desktopNames[i-1] = i18n("Desktop %1", i);
if (i <= defaultDesktops)
m_ui->desktopNames->setDefaultName(i);
}
// popup info
m_ui->popupInfoCheckBox->setChecked(false);
m_ui->popupHideSpinBox->setValue(1000);
m_ui->desktopLayoutIndicatorCheckBox->setChecked(true);
m_ui->effectComboBox->setCurrentIndex(1);
m_ui->wrapAroundBox->setChecked(true);
m_ui->rowsSpinBox->setValue(2);
m_editor->allDefault();
emit changed(true);
}
void KWinDesktopConfig::load()
{
// This method is called on reset(). So undo all changes.
undo();
if (QX11Info::isPlatformX11()) {
// get number of desktops
NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout);
for (int i = 1; i <= maxDesktops; i++) {
QString name = QString::fromUtf8(info.desktopName(i));
m_desktopNames << name;
m_ui->desktopNames->setName(i, name);
}
m_ui->rowsSpinBox->setValue(info.desktopLayoutColumnsRows().height());
} else {
// TODO: proper implementation
m_ui->rowsSpinBox->setValue(1);
}
// Popup info
KConfigGroup effectconfig(m_config, "Plugins");
KConfigGroup popupInfo(m_config, "Script-desktopchangeosd");
m_ui->popupInfoCheckBox->setChecked(effectconfig.readEntry("desktopchangeosdEnabled", false));
m_ui->popupHideSpinBox->setValue(popupInfo.readEntry("PopupHideDelay", 1000));
m_ui->desktopLayoutIndicatorCheckBox->setChecked(!popupInfo.readEntry("TextOnly", false));
// Wrap Around on screen edge
KConfigGroup windowConfig(m_config, "Windows");
m_ui->wrapAroundBox->setChecked(windowConfig.readEntry<bool>("RollOverDesktops", true));
// Effect for desktop switching
// Set current option to "none" if no plugin is activated.
m_ui->effectComboBox->setCurrentIndex(0);
auto enableBuiltInEffect = [&effectconfig,this](BuiltInEffect effect, int index) {
const QString key = BuiltInEffects::nameForEffect(effect) + QStringLiteral("Enabled");
if (effectconfig.readEntry(key, BuiltInEffects::enabledByDefault(effect))) {
m_ui->effectComboBox->setCurrentIndex(index);
}
};
enableBuiltInEffect(BuiltInEffect::Slide, 1);
enableBuiltInEffect(BuiltInEffect::CubeSlide, 2);
if (effectEnabled("fadedesktop", effectconfig))
m_ui->effectComboBox->setCurrentIndex(3);
slotEffectSelectionChanged(m_ui->effectComboBox->currentIndex());
// TODO: plasma stuff
emit changed(false);
}
void KWinDesktopConfig::save()
{
// TODO: plasma stuff
const int numberDesktops = m_ui->numberSpinBox->value();
int rows = m_ui->rowsSpinBox->value();
rows = qBound(1, rows, numberDesktops);
// avoid weird cases like having 3 rows for 4 desktops, where the last row is unused
int columns = numberDesktops / rows;
if (numberDesktops % rows > 0) {
columns++;
}
if (QX11Info::isPlatformX11()) {
NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout);
// set desktop names
for (int i = 1; i <= maxDesktops; i++) {
QString desktopName = m_desktopNames[ i -1 ];
if (i <= m_ui->numberSpinBox->value())
desktopName = m_ui->desktopNames->name(i);
info.setDesktopName(i, desktopName.toUtf8());
info.activate();
}
// set number of desktops
info.setNumberOfDesktops(numberDesktops);
info.activate();
info.setDesktopLayout(NET::OrientationHorizontal, columns, rows, NET::DesktopLayoutCornerTopLeft);
info.activate();
XSync(QX11Info::display(), false);
}
// save the desktops
QString groupname;
const int screenNumber = QX11Info::appScreen();
if (screenNumber == 0)
groupname = "Desktops";
else
groupname.sprintf("Desktops-screen-%d", screenNumber);
KConfigGroup group(m_config, groupname);
group.writeEntry("Rows", rows);
// Popup info
KConfigGroup effectconfig(m_config, "Plugins");
KConfigGroup popupInfo(m_config, "Script-desktopchangeosd");
effectconfig.writeEntry("desktopchangeosdEnabled", m_ui->popupInfoCheckBox->isChecked());
popupInfo.writeEntry("PopupHideDelay", m_ui->popupHideSpinBox->value());
popupInfo.writeEntry("TextOnly", !m_ui->desktopLayoutIndicatorCheckBox->isChecked());
// Wrap Around on screen edge
KConfigGroup windowConfig(m_config, "Windows");
windowConfig.writeEntry("RollOverDesktops", m_ui->wrapAroundBox->isChecked());
// Effect desktop switching
int desktopSwitcher = m_ui->effectComboBox->currentIndex();
bool slideEnabled = false;
bool cubeSlideEnabled = false;
bool fadeEnabled = false;
switch(desktopSwitcher) {
case 1:
// slide
slideEnabled = true;
break;
case 2:
// cube
cubeSlideEnabled = true;
break;
case 3:
// fadedesktop
fadeEnabled = true;
break;
}
effectconfig.writeEntry("slideEnabled", slideEnabled);
effectconfig.writeEntry("cubeslideEnabled", cubeSlideEnabled);
effectconfig.writeEntry("kwin4_effect_fadedesktopEnabled", fadeEnabled);
m_editor->save();
m_config->sync();
// Send signal to all kwin instances
QDBusMessage message = QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig");
QDBusConnection::sessionBus().send(message);
// and reconfigure the effects
OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"),
QStringLiteral("/Effects"),
QDBusConnection::sessionBus());
if (slideEnabled) {
interface.loadEffect(BuiltInEffects::nameForEffect(BuiltInEffect::Slide));
} else {
interface.unloadEffect(BuiltInEffects::nameForEffect(BuiltInEffect::Slide));
}
if (cubeSlideEnabled) {
interface.loadEffect(BuiltInEffects::nameForEffect(BuiltInEffect::CubeSlide));
} else {
interface.unloadEffect(BuiltInEffects::nameForEffect(BuiltInEffect::CubeSlide));
}
if (fadeEnabled) {
interface.loadEffect(QStringLiteral("kwin4_effect_fadedesktop"));
} else {
interface.unloadEffect(QStringLiteral("kwin4_effect_fadedesktop"));
}
emit changed(false);
}
void KWinDesktopConfig::undo()
{
// The global shortcuts editor makes changes active immediately. In case
// of undo we have to undo them manually
m_editor->undoChanges();
}
QString KWinDesktopConfig::cachedDesktopName(int desktop)
{
if (desktop > m_desktopNames.size())
return QString();
return m_desktopNames[ desktop -1 ];
}
QString KWinDesktopConfig::extrapolatedShortcut(int desktop) const
{
if (!desktop || desktop > m_actionCollection->count())
return QString();
if (desktop == 1)
return QString("Ctrl+F1");
QAction *beforeAction = m_actionCollection->actions().at(qMin(9, desktop - 2));
auto shortcuts = KGlobalAccel::self()->shortcut(beforeAction);
if (shortcuts.isEmpty()) {
shortcuts = KGlobalAccel::self()->defaultShortcut(beforeAction);
}
QString before;
if (!shortcuts.isEmpty()) {
before = shortcuts.first().toString(QKeySequence::PortableText);
}
QString seq;
if (before.contains(QRegExp("F[0-9]{1,2}"))) {
if (desktop < 13) // 10?
seq = QString("F%1").arg(desktop);
else if (!before.contains("Shift"))
seq = "Shift+" + QString("F%1").arg(desktop - 10);
} else if (before.contains(QRegExp("[0-9]"))) {
if (desktop == 10)
seq = '0';
else if (desktop > 10) {
if (!before.contains("Shift"))
seq = "Shift+" + QString::number(desktop == 20 ? 0 : (desktop - 10));
} else
seq = QString::number(desktop);
}
if (!seq.isEmpty()) {
if (before.contains("Ctrl"))
seq.prepend("Ctrl+");
if (before.contains("Alt"))
seq.prepend("Alt+");
if (before.contains("Shift"))
seq.prepend("Shift+");
if (before.contains("Meta"))
seq.prepend("Meta+");
}
return seq;
}
void KWinDesktopConfig::slotChangeShortcuts(int number)
{
if ((number < 1) || (number > maxDesktops))
return;
if (m_ui->allShortcutsCheckBox->isChecked())
number = maxDesktops;
while (number != m_actionCollection->count()) {
if (number < m_actionCollection->count()) {
// Remove the action from the action collection. The action itself
// will still exist because that's the way kwin currently works.
// No need to remove/forget it. See kwinbindings.
QAction *a = m_actionCollection->takeAction(m_actionCollection->actions().last());
// Remove any associated global shortcut. Set it to ""
KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>(), KGlobalAccel::NoAutoloading);
m_ui->messageLabel->hide();
delete a;
} else {
// add desktop
int desktop = m_actionCollection->count() + 1;
QAction* action = m_actionCollection->addAction(QString("Switch to Desktop %1").arg(desktop));
action->setProperty("isConfigurationAction", true);
action->setText(i18n("Switch to Desktop %1", desktop));
KGlobalAccel::self()->setShortcut(action, QList<QKeySequence>());
QString shortcutString = extrapolatedShortcut(desktop);
if (shortcutString.isEmpty()) {
m_ui->messageLabel->setText(i18n("No suitable Shortcut for Desktop %1 found", desktop));
m_ui->messageLabel->show();
} else {
QKeySequence shortcut(shortcutString);
if (!shortcut.isEmpty() && KGlobalAccel::self()->isGlobalShortcutAvailable(shortcut)) {
KGlobalAccel::self()->setShortcut(action, QList<QKeySequence>() << shortcut, KGlobalAccel::NoAutoloading);
m_ui->messageLabel->setText(i18n("Assigned global Shortcut \"%1\" to Desktop %2", shortcutString, desktop));
m_ui->messageLabel->show();
} else {
m_ui->messageLabel->setText(i18n("Shortcut conflict: Could not set Shortcut %1 for Desktop %2", shortcutString, desktop));
m_ui->messageLabel->show();
}
}
}
}
m_editor->clearCollections();
m_editor->addCollection(m_switchDesktopCollection, i18n("Desktop Switching"));
m_editor->addCollection(m_actionCollection, i18n("Desktop Switching"));
}
void KWinDesktopConfig::slotShowAllShortcuts()
{
slotChangeShortcuts(m_ui->numberSpinBox->value());
}
void KWinDesktopConfig::slotEffectSelectionChanged(int index)
{
bool enabled = false;
if (index != 0)
enabled = true;
m_ui->effectInfoButton->setEnabled(enabled);
switch (index) {
case 1: // Slide
case 2: // Cube Slide
enabled = true;
break;
default:
enabled = false;
break;
}
m_ui->effectConfigButton->setEnabled(enabled);
}
bool KWinDesktopConfig::effectEnabled(const QString& effect, const KConfigGroup& cfg) const
{
KService::List services = KServiceTypeTrader::self()->query(
"KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_" + effect + '\'');
if (services.isEmpty())
return false;
QVariant v = services.first()->property("X-KDE-PluginInfo-EnabledByDefault");
return cfg.readEntry("kwin4_effect_" + effect + "Enabled", v.toBool());
}
void KWinDesktopConfig::slotAboutEffectClicked()
{
QString effect;
bool fromKService = false;
BuiltInEffect builtIn = BuiltInEffect::Invalid;
switch(m_ui->effectComboBox->currentIndex()) {
case 1:
builtIn = BuiltInEffect::Slide;
break;
case 2:
builtIn = BuiltInEffect::CubeSlide;
break;
case 3:
effect = "fadedesktop";
fromKService = true;
break;
default:
return;
}
auto showDialog = [this](const KAboutData &aboutData) {
QPointer<KAboutApplicationDialog> aboutPlugin = new KAboutApplicationDialog(aboutData, this);
aboutPlugin->exec();
delete aboutPlugin;
};
if (fromKService) {
const QString pluginId = QStringLiteral("kwin4_effect_%1").arg(effect);
const auto effectsMetaData = KPackage::PackageLoader::self()->findPackages(
QStringLiteral("KWin/Effect"),
QStringLiteral("kwin/effects/"),
[&pluginId](const KPluginMetaData &meta) {
return meta.pluginId() == pluginId;
});
if (effectsMetaData.isEmpty()) {
return;
}
KPluginInfo pluginInfo(effectsMetaData.first());
const QString name = pluginInfo.name();
const QString comment = pluginInfo.comment();
const QString author = pluginInfo.author();
const QString email = pluginInfo.email();
const QString website = pluginInfo.website();
const QString version = pluginInfo.version();
const QString license = pluginInfo.license();
const QString icon = pluginInfo.icon();
KAboutData aboutData(name, name, version, comment, KAboutLicense::byKeyword(license).key(), QString(), QString(), website.toLatin1());
aboutData.setProgramLogo(icon);
const QStringList authors = author.split(',');
const QStringList emails = email.split(',');
int i = 0;
if (authors.count() == emails.count()) {
foreach (const QString & author, authors) {
if (!author.isEmpty()) {
aboutData.addAuthor(i18n(author.toUtf8()), QString(), emails[i]);
}
i++;
}
}
showDialog(aboutData);
} else {
const BuiltInEffects::EffectData &data = BuiltInEffects::effectData(builtIn);
KAboutData aboutData(data.name,
data.displayName,
QStringLiteral(KWIN_VERSION_STRING),
data.comment,
KAboutLicense::GPL_V2);
aboutData.setProgramLogo(QIcon::fromTheme(QStringLiteral("preferences-system-windows")));
aboutData.addAuthor(i18n("KWin development team"));
showDialog(aboutData);
}
}
void KWinDesktopConfig::slotConfigureEffectClicked()
{
QString effect;
switch(m_ui->effectComboBox->currentIndex()) {
case 1:
effect = BuiltInEffects::nameForEffect(BuiltInEffect::Slide);
break;
case 2:
effect = BuiltInEffects::nameForEffect(BuiltInEffect::CubeSlide);
break;
default:
return;
}
QPointer<QDialog> configDialog = new QDialog(this);
KCModule *kcm = KPluginTrader::createInstanceFromQuery<KCModule>(QStringLiteral("kwin/effects/configs/"), QString(),
QStringLiteral("'%1' in [X-KDE-ParentComponents]").arg(effect),
configDialog);
if (!kcm) {
delete configDialog;
return;
}
configDialog->setWindowTitle(m_ui->effectComboBox->currentText());
configDialog->setLayout(new QVBoxLayout);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::RestoreDefaults, configDialog);
connect(buttons, SIGNAL(accepted()), configDialog, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), configDialog, SLOT(reject()));
connect(buttons->button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked(bool)), kcm, SLOT(defaults()));
QWidget *showWidget = new QWidget(configDialog);
QVBoxLayout *layout = new QVBoxLayout;
showWidget->setLayout(layout);
layout->addWidget(kcm);
configDialog->layout()->addWidget(showWidget);
configDialog->layout()->addWidget(buttons);
if (configDialog->exec() == QDialog::Accepted) {
kcm->save();
} else {
kcm->load();
}
delete configDialog;
}
} // namespace
#include "main.moc"

View File

@ -1,92 +0,0 @@
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2009 Martin Gräßlin <mgraesslin@kde.org>
This program 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.
This program 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/>.
*********************************************************************/
#ifndef __MAIN_H__
#define __MAIN_H__
#include <kcmodule.h>
#include <ksharedconfig.h>
#include "ui_main.h"
class KActionCollection;
class KConfigGroup;
class KShortcutsEditor;
namespace KWin
{
// if you change this, update also the number of keyboard shortcuts in kwin/kwinbindings.cpp
static const int maxDesktops = 20;
static const int defaultDesktops = 4;
class KWinDesktopConfigForm : public QWidget, public Ui::KWinDesktopConfigForm
{
Q_OBJECT
public:
explicit KWinDesktopConfigForm(QWidget* parent);
};
class KWinDesktopConfig : public KCModule
{
Q_OBJECT
public:
explicit KWinDesktopConfig(QWidget* parent, const QVariantList& args);
~KWinDesktopConfig();
QString cachedDesktopName(int desktop);
// undo all changes
void undo();
public Q_SLOTS:
virtual void save();
virtual void load();
virtual void defaults();
private Q_SLOTS:
void slotChangeShortcuts(int number);
void slotShowAllShortcuts();
void slotEffectSelectionChanged(int index);
void slotAboutEffectClicked();
void slotConfigureEffectClicked();
private:
void init();
void addAction(const QString &name, const QString &label);
bool effectEnabled(const QString& effect, const KConfigGroup& cfg) const;
QString extrapolatedShortcut(int desktop) const;
private:
KWinDesktopConfigForm* m_ui;
KSharedConfigPtr m_config;
// cache for desktop names given by NETRootInfo
// needed as the widget only stores the names for actual number of desktops
QStringList m_desktopNames;
// Collection for switching desktops like ctrl+f1
KActionCollection* m_actionCollection;
// Collection for next, previous, up, down desktop
KActionCollection* m_switchDesktopCollection;
KShortcutsEditor* m_editor;
};
} // namespace
#endif

View File

@ -1,326 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KWinDesktopConfigForm</class>
<widget class="QWidget" name="KWinDesktopConfigForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>572</width>
<height>310</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="desktop">
<attribute name="title">
<string>Desktops</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Layout</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="numberLabel">
<property name="whatsThis">
<string>Here you can set how many virtual desktops you want on your KDE desktop.</string>
</property>
<property name="text">
<string>&amp;Number of desktops:</string>
</property>
<property name="buddy">
<cstring>numberSpinBox</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="numberSpinBox">
<property name="whatsThis">
<string>Here you can set how many virtual desktops you want on your KDE desktop.</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>20</number>
</property>
<property name="value">
<number>4</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>N&amp;umber of rows:</string>
</property>
<property name="buddy">
<cstring>rowsSpinBox</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="rowsSpinBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>20</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="nameGroup">
<property name="title">
<string>Desktop Names</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="KWin::DesktopNamesWidget" name="desktopNames" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="messageLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="switching">
<attribute name="title">
<string>Switching</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="wrapAroundBox">
<property name="whatsThis">
<string>Enable this option if you want keyboard or active desktop border navigation beyond the edge of a desktop to take you to the opposite edge of the new desktop.</string>
</property>
<property name="text">
<string>Desktop navigation wraps around</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Desktop Effect Animation</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Animation:</string>
</property>
<property name="buddy">
<cstring>effectComboBox</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QComboBox" name="effectComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="effectConfigButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="effectInfoButton"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="popupInfoCheckBox">
<property name="title">
<string>Desktop Switch On-Screen Display</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="hideLabel">
<property name="text">
<string>Duration:</string>
</property>
<property name="buddy">
<cstring>popupHideSpinBox</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="popupHideSpinBox">
<property name="suffix">
<string> msec</string>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="singleStep">
<number>50</number>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="desktopLayoutIndicatorCheckBox">
<property name="toolTip">
<string>Enabling this option will show a small preview of the desktop layout indicating the selected desktop.</string>
</property>
<property name="text">
<string>Show desktop layout indicators</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="shortcutsGroupBox">
<property name="title">
<string>Shortcuts</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QFrame" name="editorFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="allShortcutsCheckBox">
<property name="text">
<string>Show shortcuts for all possible desktops</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KWin::DesktopNamesWidget</class>
<extends>QWidget</extends>
<header>desktopnameswidget.h</header>
<container>1</container>
<slots>
<slot>numberChanged(int)</slot>
</slots>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>numberSpinBox</sender>
<signal>valueChanged(int)</signal>
<receiver>desktopNames</receiver>
<slot>numberChanged(int)</slot>
<hints>
<hint type="sourcelabel">
<x>327</x>
<y>144</y>
</hint>
<hint type="destinationlabel">
<x>326</x>
<y>209</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,253 @@
/*
* Copyright (C) 2018 Eike Hein <hein@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
import QtQuick 2.1
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.4 as QtControls
import org.kde.kirigami 2.5 as Kirigami
import org.kde.plasma.core 2.1 as PlasmaCore
import org.kde.kcm 1.2
ScrollViewKCM {
id: root
ConfigModule.quickHelp: i18n("Virtual Desktops")
Connections {
target: kcm.desktopsModel
onReadyChanged: {
rowsSpinBox.value = kcm.desktopsModel.rows;
}
onRowsChanged: {
rowsSpinBox.value = kcm.desktopsModel.rows;
}
}
Component {
id: desktopsListItemComponent
Kirigami.SwipeListItem {
id: listItem
contentItem: RowLayout {
QtControls.TextField {
id: nameField
background: null
leftPadding: Kirigami.Units.largeSpacing
topPadding: 0
bottomPadding: 0
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
text: model.display
readOnly: true
onEditingFinished: {
readOnly = true;
Qt.callLater(kcm.desktopsModel.setDesktopName, model.Id, text);
}
}
}
actions: [
Kirigami.Action {
enabled: !model.IsMissing
iconName: "edit-rename"
tooltip: i18nc("@info:tooltip", "Rename")
onTriggered: {
nameField.readOnly = false;
nameField.selectAll();
nameField.forceActiveFocus();
}
},
Kirigami.Action {
enabled: !model.IsMissing
iconName: "list-remove"
tooltip: i18nc("@info:tooltip", "Remove")
onTriggered: kcm.desktopsModel.removeDesktop(model.Id)
}]
}
}
header: ColumnLayout {
id: messagesLayout
spacing: Kirigami.Units.largeSpacing
Kirigami.InlineMessage {
Layout.fillWidth: true
type: Kirigami.MessageType.Error
text: kcm.desktopsModel.error
visible: kcm.desktopsModel.error != ""
}
Kirigami.InlineMessage {
Layout.fillWidth: true
type: Kirigami.MessageType.Information
text: i18n("Virtual desktops have been changed outside this settings application. Saving now will overwrite the changes.")
visible: kcm.desktopsModel.serverModified
}
RowLayout {
QtControls.Label {
text: i18n("Rows:")
}
QtControls.SpinBox {
id: rowsSpinBox
from: 1
to: 20
onValueModified: kcm.desktopsModel.rows = value
}
Item { // Spacer
Layout.fillWidth: true
}
QtControls.Button {
Layout.alignment: Qt.AlignRight
text: i18nc("@action:button", "Add")
icon.name: "list-add"
onClicked: kcm.desktopsModel.createDesktop(i18n("New Desktop"))
}
}
}
view: ListView {
id: desktopsList
model: kcm.desktopsModel.ready ? kcm.desktopsModel : null
section.property: "DesktopRow"
section.delegate: Kirigami.AbstractListItem {
width: desktopsList.width
backgroundColor: Kirigami.Theme.backgroundColor
hoverEnabled: false
supportsMouseEvents: false
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
QtControls.Label {
text: i18n("Row %1", section)
}
}
delegate: Kirigami.DelegateRecycler {
width: desktopsList.width
sourceComponent: desktopsListItemComponent
}
}
footer: ColumnLayout {
Kirigami.FormLayout {
anchors.horizontalCenter: parent.horizontalCenter
Connections {
target: kcm
onNavWrapsChanged: navWraps.checked = kcm.navWraps
onOsdEnabledChanged: osdEnabled.checked = kcm.osdEnabled
onOsdDurationChanged: osdDuration.value = kcm.osdDuration
onOsdTextOnlyChanged: osdTextOnly.checked = !kcm.osdTextOnly
}
QtControls.CheckBox {
id: navWraps
Kirigami.FormData.label: i18n("Options:")
text: i18n("Navigation wraps around")
checked: kcm.navWraps
onCheckedChanged: kcm.navWraps = checked
}
RowLayout {
Layout.fillWidth: true
QtControls.CheckBox {
id: osdEnabled
text: i18n("Show on-screen display when switching:")
checked: kcm.osdEnabled
onCheckedChanged: kcm.osdEnabled = checked
}
QtControls.SpinBox {
id: osdDuration
from: 0
to: 10000
stepSize: 100
textFromValue: function(value, locale) { return i18n("%1 ms", value)}
value: kcm.osdDuration
onValueChanged: kcm.osdDuration = value
}
}
RowLayout {
Layout.fillWidth: true
Item {
width: units.largeSpacing
}
QtControls.CheckBox {
id: osdTextOnly
enabled: osdEnabled.checked
text: i18n("Show desktop layout indicators")
checked: kcm.osdTextOnly
onCheckedChanged: kcm.osdTextOnly = !checked
}
}
}
}
}

View File

@ -0,0 +1,116 @@
[Desktop Entry]
Name=Virtual Desktops
Name[ar]=أسطح المكتب الافتراضية
Name[bg]=Виртуални работни плотове
Name[bs]=Virtuelne površi
Name[ca]=Escriptoris virtuals
Name[ca@valencia]=Escriptoris virtuals
Name[cs]=Virtuální plochy
Name[da]=Virtuelle skriveborde
Name[de]=Virtuelle Arbeitsflächen
Name[el]=Εικονικές επιφάνειες εργασίες
Name[en_GB]=Virtual Desktops
Name[es]=Escritorios virtuales
Name[et]=Virtuaalsed töölauad
Name[eu]=Alegiazko mahaigaina
Name[fi]=Virtuaalityöpöydät
Name[fr]=Bureaux virtuels
Name[ga]=Deasca Fíorúla
Name[gl]=Escritorios virtuais
Name[gu]=વર્ચ્યુઅલ ડેસ્કટોપો
Name[he]=שולחנות עבודה וירטואליים
Name[hi]=आभासी डेस्कटॉप
Name[hr]=Virtualne radne površine
Name[hu]=Virtuális asztalok
Name[ia]=Scriptorios virtual
Name[id]=Desktop Virtual
Name[is]=Sýndarskjáborð
Name[it]=Desktop virtuali
Name[ja]=仮想デスクトップ
Name[kk]=Виртуалды Үстелдер
Name[km]=ផ្ទៃតុ​និម្មិត
Name[kn]=ವಾಸ್ತವಪ್ರಾಯ ಗಣಕತೆರೆಗಳು
Name[ko]=가상 데스크톱
Name[lt]=Virtualūs darbalaukiai
Name[lv]=Virtuālās darbvirsmas
Name[mr]=आभासी डेस्कटॉप
Name[nb]=Virtuelle skrivebord
Name[nds]=Mehr Schriefdischen
Name[nl]=Virtuele bureaubladen
Name[nn]=Virtuelle skrivebord
Name[pa]=ਵਰਚੁਅਲ ਡੈਸਕਟਾਪ
Name[pl]=Pulpity wirtualne
Name[pt]=Ecrãs Virtuais
Name[pt_BR]=Áreas de trabalho virtuais
Name[ro]=Birouri virtuale
Name[ru]=Рабочие столы
Name[si]=අත්ථ්‍ය වැඩතල
Name[sk]=Virtuálne pracovné plochy
Name[sl]=Navidezna namizja
Name[sr]=Виртуелне површи
Name[sr@ijekavian]=Виртуелне површи
Name[sr@ijekavianlatin]=Virtuelne površi
Name[sr@latin]=Virtuelne površi
Name[sv]=Virtuella skrivbord
Name[tg]=Мизҳои кории виртуалӣ
Name[th]=พื้นที่ทำงานเสมือน
Name[tr]=Sanal Masaüstleri
Name[ug]=مەۋھۇم ئۈستەلئۈستى
Name[uk]=Віртуальні стільниці
Name[wa]=Forveyous scribannes
Name[x-test]=xxVirtual Desktopsxx
Name[zh_CN]=虚拟桌面
Name[zh_TW]=虛擬桌面
Comment=Navigation, Number and Layout of Virtual Desktops
Comment[bs]=Navigacija, broj i izgled virtualnih desktopa
Comment[ca]=Navegació, nombre i disposició dels escriptoris virtuals
Comment[ca@valencia]=Navegació, nombre i disposició dels escriptoris virtuals
Comment[cs]=Navigace, počet a rozvržení virtuálních ploch
Comment[da]=Navigation, antal og layout af virtuelle skriveborde
Comment[de]=Navigation, Anzahl und Layout virtueller Arbeitsflächen
Comment[el]=Περιήγηση, αριθμός και διάταξη εικονικών επιφανειών εργασίας
Comment[en_GB]=Navigation, Number and Layout of Virtual Desktops
Comment[es]=Navegación, número y disposición de los escritorios virtuales
Comment[et]=Virtuaalsete töölaudade vahel liikumine, nende arv ja paigutus
Comment[eu]=Nabigazioa, alegiazko mahaigainen kopurua eta antolamendua
Comment[fi]=Virtuaalityöpöytien vaihtaminen, määrä ja asettelu
Comment[fr]=Navigation, nombre et disposition des bureaux virtuels
Comment[gl]=Navegación, cantidade e disposición dos escritorios virtuais
Comment[he]=ניווט, פריסה ומספר שולחנות עבודה וירטואלים
Comment[hu]=Navigáció, a virtuális asztalok száma és elrendezése
Comment[id]=Navigasi, Jumlah dan Tata Letak Desktop Virtual
Comment[it]=Navigazione, numero e disposizione dei desktop virtuali
Comment[ko]=가상 데스크톱 탐색, 개수, 레이아웃
Comment[lt]=Naršymas, Skaičius ir išdėstymas virtualių darbalaukių
Comment[nb]=Navigering, antall og utlegg av virtuelle skrivebord
Comment[nds]=Tall, Anornen un dat Anstüern vun de virtuellen Schriefdischen fastleggen
Comment[nl]=Navigatie door, aantal en indeling van virtuele bureaubladen
Comment[nn]=Navigering, nummer og vising av virtuelle skrivebord
Comment[pa]=ਵਰਚੁਅਲ ਡੈਸਕਟਾਪਾਂ ਲਈ ਨੇਵੀਗੇਸ਼ਨ, ਗਿਣਤੀ ਅਤੇ ਢਾਂਚਾ
Comment[pl]=Poruszanie się, liczba i układ wirtualnych pulpitów
Comment[pt]=Navegação, Número e Disposição dos Ecrãs Virtuais
Comment[pt_BR]=Navegação, quantidade e layout das áreas de trabalho virtuais
Comment[ru]=Число, расположение и способ переключения рабочих столов
Comment[sk]=Navigácia, počet a rozloženie virtuálnych plôch
Comment[sl]=Krmarjenje med, število in razporeditev navideznih namizij
Comment[sr]=Кретање, број и распоред виртуелних површи
Comment[sr@ijekavian]=Кретање, број и распоред виртуелних површи
Comment[sr@ijekavianlatin]=Kretanje, broj i raspored virtuelnih površi
Comment[sr@latin]=Kretanje, broj i raspored virtuelnih površi
Comment[sv]=Navigering, antal och layout av virtuella skrivbord
Comment[tr]=Gezinti, Sanal Masaüstlerinin Sayısı ve Yerleşimi
Comment[uk]=Навігація, кількість та компонування віртуальних стільниць
Comment[vi]=Số lượng, bố trí và điều hướng của màn hình ảo
Comment[x-test]=xxNavigation, Number and Layout of Virtual Desktopsxx
Comment[zh_CN]=虚拟桌面的切换,数量和布局
Comment[zh_TW]=虛擬桌面的導覽、數字與佈局
Icon=preferences-desktop
Type=Service
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-Name=kcm_kwin_virtualdesktops
X-KDE-ServiceTypes=Plasma/Generic
X-Plasma-API=declarativeappletscript
X-Plasma-MainScript=ui/main.qml

View File

@ -0,0 +1,205 @@
/*
* Copyright (C) 2018 Eike Hein <hein@kde.org>
*
* This program 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.
*
* This program 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 "virtualdesktops.h"
#include "desktopsmodel.h"
#include <KAboutData>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KPluginFactory>
K_PLUGIN_FACTORY_WITH_JSON(VirtualDesktopsFactory, "kcm_kwin_virtualdesktops.json", registerPlugin<KWin::VirtualDesktops>();)
namespace KWin
{
VirtualDesktops::VirtualDesktops(QObject *parent, const QVariantList &args)
: KQuickAddons::ConfigModule(parent, args)
, m_kwinConfig(KSharedConfig::openConfig("kwinrc"))
, m_desktopsModel(new KWin::DesktopsModel(this))
, m_navWraps(true)
, m_osdEnabled(false)
, m_osdDuration(1000)
, m_osdTextOnly(false)
{
KAboutData *about = new KAboutData(QStringLiteral("kcm_kwin_virtualdesktops"),
i18n("Configure Virtual Desktops"),
QStringLiteral("2.0"), QString(), KAboutLicense::GPL);
setAboutData(about);
setButtons(Apply | Default);
QObject::connect(m_desktopsModel, &KWin::DesktopsModel::userModifiedChanged,
this, &VirtualDesktops::updateNeedsSave);
}
VirtualDesktops::~VirtualDesktops()
{
}
QAbstractItemModel *VirtualDesktops::desktopsModel() const
{
return m_desktopsModel;
}
bool VirtualDesktops::navWraps() const
{
return m_navWraps;
}
void VirtualDesktops::setNavWraps(bool wraps)
{
if (m_navWraps != wraps) {
m_navWraps = wraps;
emit navWrapsChanged();
updateNeedsSave();
}
}
bool VirtualDesktops::osdEnabled() const
{
return m_osdEnabled;
}
void VirtualDesktops::setOsdEnabled(bool enabled)
{
if (m_osdEnabled != enabled) {
m_osdEnabled = enabled;
emit osdEnabledChanged();
updateNeedsSave();
}
}
int VirtualDesktops::osdDuration() const
{
return m_osdDuration;
}
void VirtualDesktops::setOsdDuration(int duration)
{
if (m_osdDuration != duration) {
m_osdDuration = duration;
emit osdDurationChanged();
updateNeedsSave();
}
}
int VirtualDesktops::osdTextOnly() const
{
return m_osdTextOnly;
}
void VirtualDesktops::setOsdTextOnly(bool textOnly)
{
if (m_osdTextOnly != textOnly) {
m_osdTextOnly = textOnly;
emit osdTextOnlyChanged();
updateNeedsSave();
}
}
void VirtualDesktops::load()
{
KConfigGroup navConfig(m_kwinConfig, "Windows");
setNavWraps(navConfig.readEntry<bool>("RollOverDesktops", true));
KConfigGroup osdConfig(m_kwinConfig, "Plugins");
setOsdEnabled(osdConfig.readEntry("desktopchangeosdEnabled", false));
KConfigGroup osdSettings(m_kwinConfig, "Script-desktopchangeosd");
setOsdDuration(osdSettings.readEntry("PopupHideDelay", 1000));
setOsdTextOnly(osdSettings.readEntry("TextOnly", false));
}
void VirtualDesktops::save()
{
m_desktopsModel->syncWithServer();
KConfigGroup navConfig(m_kwinConfig, "Windows");
navConfig.writeEntry("RollOverDesktops", m_navWraps);
KConfigGroup osdConfig(m_kwinConfig, "Plugins");
osdConfig.writeEntry("desktopchangeosdEnabled", m_osdEnabled);
KConfigGroup osdSettings(m_kwinConfig, "Script-desktopchangeosd");
osdSettings.writeEntry("PopupHideDelay", m_osdDuration);
osdSettings.writeEntry("TextOnly", m_osdTextOnly);
m_kwinConfig->sync();
QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"),
QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"));
QDBusConnection::sessionBus().send(message);
setNeedsSave(false);
}
void VirtualDesktops::defaults()
{
m_desktopsModel->setRows(1);
setNavWraps(true);
setOsdEnabled(false);
setOsdDuration(1000);
setOsdTextOnly(false);
}
void VirtualDesktops::updateNeedsSave()
{
bool needsSave = false;
if (m_desktopsModel->userModified()) {
needsSave = true;
}
KConfigGroup navConfig(m_kwinConfig, "Windows");
if (m_navWraps != navConfig.readEntry<bool>("RollOverDesktops", true)) {
needsSave = true;
}
KConfigGroup osdConfig(m_kwinConfig, "Plugins");
if (m_osdEnabled != osdConfig.readEntry("desktopchangeosdEnabled", false)) {
needsSave = true;
}
KConfigGroup osdSettings(m_kwinConfig, "Script-desktopchangeosd");
if (m_osdDuration != osdSettings.readEntry("PopupHideDelay", 1000)) {
needsSave = true;
}
if (m_osdTextOnly != osdSettings.readEntry("TextOnly", false)) {
needsSave = true;
}
setNeedsSave(needsSave);
}
}
#include "virtualdesktops.moc"

View File

@ -0,0 +1,82 @@
/*
* Copyright (C) 2018 Eike Hein <hein@kde.org>
*
* This program 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.
*
* This program 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/>.
*/
#ifndef VIRTUALDESKTOPS_H
#define VIRTUALDESKTOPS_H
#include <KQuickAddons/ConfigModule>
#include <KSharedConfig>
namespace KWin
{
class DesktopsModel;
class VirtualDesktops : public KQuickAddons::ConfigModule
{
Q_OBJECT
Q_PROPERTY(QAbstractItemModel* desktopsModel READ desktopsModel CONSTANT)
Q_PROPERTY(bool navWraps READ navWraps WRITE setNavWraps NOTIFY navWrapsChanged)
Q_PROPERTY(bool osdEnabled READ osdEnabled WRITE setOsdEnabled NOTIFY osdEnabledChanged)
Q_PROPERTY(int osdDuration READ osdDuration WRITE setOsdDuration NOTIFY osdDurationChanged)
Q_PROPERTY(bool osdTextOnly READ osdTextOnly WRITE setOsdTextOnly NOTIFY osdTextOnlyChanged)
public:
explicit VirtualDesktops(QObject *parent = nullptr, const QVariantList &list = QVariantList());
~VirtualDesktops() override;
QAbstractItemModel *desktopsModel() const;
bool navWraps() const;
void setNavWraps(bool wraps);
bool osdEnabled() const;
void setOsdEnabled(bool enabled);
int osdDuration() const;
void setOsdDuration(int duration);
int osdTextOnly() const;
void setOsdTextOnly(bool textOnly);
Q_SIGNALS:
void navWrapsChanged() const;
void osdEnabledChanged() const;
void osdDurationChanged() const;
void osdTextOnlyChanged() const;
public Q_SLOTS:
void load() override;
void save() override;
void defaults() override;
private Q_SLOTS:
void updateNeedsSave();
private:
KSharedConfigPtr m_kwinConfig;
DesktopsModel *m_desktopsModel;
bool m_navWraps;
bool m_osdEnabled;
int m_osdDuration;
bool m_osdTextOnly;
};
}
#endif