Commit ffa5fc6c authored by Carl Schwan's avatar Carl Schwan 🚴

Port Desktop Session KCM to QML

parent 1a556fbe
# KI18N Translation Domain for this library
add_definitions(-DTRANSLATION_DOMAIN=\"kcmsmserver\")
add_definitions(-DTRANSLATION_DOMAIN=\"kcm_smserver\")
set(kcm_smserver_PART_SRCS kcmsmserver.cpp)
kcmutils_generate_module_data(
kcm_smserver_PART_SRCS
MODULE_DATA_HEADER smserverdata.h
MODULE_DATA_CLASS_NAME SMServerData
SETTINGS_HEADERS smserversettings.h
SETTINGS_CLASSES SMServerSettings
)
qt5_add_dbus_interface(kcm_smserver_PART_SRCS org.freedesktop.login1.Manager.xml login1_manager)
kconfig_add_kcfg_files(kcm_smserver_PART_SRCS smserversettings.kcfgc GENERATE_MOC)
ki18n_wrap_ui(kcm_smserver_PART_SRCS smserverconfigdlg.ui )
add_library(kcm_smserver MODULE ${kcm_smserver_PART_SRCS})
target_link_libraries(kcm_smserver
Qt5::Core
Qt5::DBus
KF5::QuickAddons
KF5::I18n
KF5::ConfigCore
KF5::KCMUtils
PW::KWorkspace)
target_link_libraries(kcm_smserver Qt5::Core Qt5::DBus Qt5::Widgets KF5::I18n KF5::ConfigCore KF5::KCMUtils PW::KWorkspace)
install(TARGETS kcm_smserver DESTINATION ${KDE_INSTALL_PLUGINDIR} )
kcoreaddons_desktop_to_json(kcm_smserver "package/metadata.desktop" SERVICE_TYPES kcmodule.desktop)
add_subdirectory(kconf_update)
########### install files ###############
install( FILES kcmsmserver.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} )
install(TARGETS kcm_smserver DESTINATION ${KDE_INSTALL_PLUGINDIR}/kcms)
install(FILES package/metadata.desktop RENAME kcm_smserver.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR})
kpackage_install_package(package kcm_smserver kcms)
add_subdirectory(kconf_update)
......@@ -6,6 +6,7 @@
* Copyright (c) 2000 Kurt Granroth <granroth@kde.org>
*
* Copyright (c) 2019 Kevin Ottens <kevin.ottens@enioka.com>
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
*
* 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
......@@ -28,140 +29,168 @@
#include <QCheckBox>
#include <QFileInfo>
#include <QVBoxLayout>
#include <kworkspace.h>
#include <QRegExp>
#include <KDesktopFile>
#include <KProcess>
#include <KMessageBox>
#include <QApplication>
#include <KPluginFactory>
#include <QDBusInterface>
#include <QLineEdit>
#include "kcmsmserver.h"
#include "smserversettings.h"
#include "smserverdata.h"
#include "ui_smserverconfigdlg.h"
#include <KPluginFactory>
#include <KAboutData>
#include <KLocalizedString>
#include <sessionmanagement.h>
#include "login1_manager.h"
K_PLUGIN_FACTORY(SMSFactory, registerPlugin<SMServerConfig>(); registerPlugin<SMServerData>();)
K_PLUGIN_CLASS_WITH_JSON(SMServerConfig, "metadata.json")
SMServerConfig::SMServerConfig(QWidget *parent, const QVariantList &args)
: KCModule(parent, args)
, ui(new Ui::SMServerConfigDlg)
, m_data(new SMServerData(this))
SMServerConfig::SMServerConfig(QObject *parent, const QVariantList &args)
: KQuickAddons::ManagedConfigModule(parent, args)
, m_login1Manager(new OrgFreedesktopLogin1ManagerInterface(QStringLiteral("org.freedesktop.login1"),
QStringLiteral("/org/freedesktop/login1"),
QDBusConnection::systemBus(),
this))
{
ui->setupUi(this);
setQuickHelp( i18n("<h1>Session Manager</h1>"
auto settings = new SMServerSettings(this);
qmlRegisterSingletonInstance("org.kde.desktopsession.private", 1, 0, "Settings", settings);
setQuickHelp(i18n("<h1>Session Manager</h1>"
" You can configure the session manager here."
" This includes options such as whether or not the session exit (logout)"
" should be confirmed, whether the session should be restored again when logging in"
" and whether the computer should be automatically shut down after session"
" exit by default."));
ui->firmwareSetupBox->hide();
ui->firmwareSetupMessageWidget->hide();
initFirmwareSetup();
checkFirmwareSetupRequested();
m_restartInSetupScreenInitial = m_restartInSetupScreen;
KAboutData *about = new KAboutData(QStringLiteral("kcm_smserver"), i18n("Desktop Session"),
QStringLiteral("1.0"), i18n("Desktop Session Login and Logout"), KAboutLicense::GPL,
i18n("Copyright © 2000–2020 Desktop Session team"));
about->addAuthor(i18n("Oswald Buddenhagen"), QString(), QStringLiteral("ossi@kde.org"));
about->addAuthor(i18n("Carl Schwan"), QStringLiteral("QML rewrite"),
QStringLiteral("carl@carlschwan.eu"), QStringLiteral("https://carlschwan.eu"));
setAboutData(about);
setButtons(Help | Apply | Default);
addConfig(m_data->settings(), this);
const QString canFirmareSetup = m_login1Manager->CanRebootToFirmwareSetup().value();
if (canFirmareSetup == QLatin1String("yes") || canFirmareSetup == QLatin1String("challenge")) {
m_canFirmareSetup = true;
// now check whether we're UEFI to provide a more descriptive button label
if (QFileInfo(QStringLiteral("/sys/firmware/efi")).isDir()) {
m_isUefi = true;
}
}
}
SMServerConfig::~SMServerConfig() = default;
void SMServerConfig::initFirmwareSetup()
bool SMServerConfig::isUefi() const
{
m_rebootNowAction = new QAction(QIcon::fromTheme(QStringLiteral("system-reboot")), i18n("Restart Now"));
connect(m_rebootNowAction, &QAction::triggered, this, [this] {
auto sm = new SessionManagement(this);
auto doShutdown=[sm]() {
sm->requestReboot();
delete sm;
};
if (sm->state() == SessionManagement::State::Loading) {
connect(sm, &SessionManagement::stateChanged, this, doShutdown);
} else {
doShutdown();
}
});
return m_isUefi;
}
connect(ui->firmwareSetupCheck, &QCheckBox::clicked, this, [this](bool enable) {
ui->firmwareSetupMessageWidget->removeAction(m_rebootNowAction);
ui->firmwareSetupMessageWidget->animatedHide();
QDBusMessage message = QDBusMessage::createMethodCall(m_login1Manager->service(),
m_login1Manager->path(),
m_login1Manager->interface(),
QStringLiteral("SetRebootToFirmwareSetup"));
message.setArguments({enable});
// This cannot be set through a generated DBus interface, so we have to create the message ourself.
message.setInteractiveAuthorizationAllowed(true);
QDBusPendingReply<void> call = m_login1Manager->connection().asyncCall(message);
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this);
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this, enable](QDBusPendingCallWatcher *watcher) {
QDBusPendingReply<void> reply = *watcher;
watcher->deleteLater();
checkFirmwareSetupRequested();
KMessageWidget *message = ui->firmwareSetupMessageWidget;
if (reply.isError()) {
// User likely canceled the PolKit prompt, don't show an error in this case
if (reply.error().type() != QDBusError::AccessDenied) {
message->setMessageType(KMessageWidget::Error);
message->setText(i18n("Failed to request restart to firmware setup: %1", reply.error().message()));
message->animatedShow();
}
return;
}
bool SMServerConfig::restartInSetupScreen() const
{
return m_restartInSetupScreen;
}
if (!enable) {
return;
}
void SMServerConfig::setRestartInSetupScreen(bool restartInSetupScreen)
{
if (m_restartInSetupScreen == restartInSetupScreen) {
return;
}
QDBusMessage message = QDBusMessage::createMethodCall(m_login1Manager->service(),
m_login1Manager->path(),
m_login1Manager->interface(),
QStringLiteral("SetRebootToFirmwareSetup"));
message.setArguments({restartInSetupScreen});
// This cannot be set through a generated DBus interface, so we have to create the message ourself.
message.setInteractiveAuthorizationAllowed(true);
QDBusPendingReply<void> call = m_login1Manager->connection().asyncCall(message);
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(call, this);
connect(callWatcher, &QDBusPendingCallWatcher::finished, this, [this, restartInSetupScreen](QDBusPendingCallWatcher *watcher) {
QDBusPendingReply<void> reply = *watcher;
watcher->deleteLater();
checkFirmwareSetupRequested();
message->setMessageType(KMessageWidget::Information);
if (m_isUefi) {
message->setText(i18n("Next time the computer is restarted, it will enter the UEFI setup screen."));
} else {
message->setText(i18n("Next time the computer is restarted, it will enter the firmware setup screen."));
settingsChanged();
if (reply.isError()) {
// User likely canceled the PolKit prompt, don't show an error in this case
if (reply.error().type() != QDBusError::AccessDenied) {
m_error = reply.error().message();
Q_EMIT errorChanged();
}
message->addAction(m_rebootNowAction);
message->animatedShow();
});
});
return;
} else if (m_error.length() > 0) {
m_error = QString();
Q_EMIT errorChanged();
}
m_restartInSetupScreen = restartInSetupScreen;
Q_EMIT restartInSetupScreenChanged();
const QString canFirmareSetup = m_login1Manager->CanRebootToFirmwareSetup().value();
if (canFirmareSetup == QLatin1String("yes") || canFirmareSetup == QLatin1String("challenge")) {
// now check whether we're UEFI to provide a more descriptive button label
if (QFileInfo(QStringLiteral("/sys/firmware/efi")).isDir()) {
m_isUefi = true;
ui->firmwareSetupBox->setTitle(i18n("UEFI Setup"));
ui->firmwareSetupCheck->setText(i18n("Enter UEFI setup on next restart"));
if (!restartInSetupScreen) {
return;
}
});
}
QString SMServerConfig::error() const
{
return m_error;
}
ui->firmwareSetupBox->setVisible(true);
void SMServerConfig::reboot()
{
auto sm = new SessionManagement(this);
auto doShutdown = [sm]() {
sm->requestReboot();
delete sm;
};
if (sm->state() == SessionManagement::State::Loading) {
connect(sm, &SessionManagement::stateChanged, this, doShutdown);
} else {
doShutdown();
}
}
void SMServerConfig::checkFirmwareSetupRequested()
{
ui->firmwareSetupCheck->setChecked(m_login1Manager->property("RebootToFirmwareSetup").toBool());
m_restartInSetupScreen = m_login1Manager->property("RebootToFirmwareSetup").toBool();
Q_EMIT restartInSetupScreenChanged();
}
bool SMServerConfig::canFirmareSetup() const
{
return m_canFirmareSetup;
}
bool SMServerConfig::isSaveNeeded() const
{
return m_restartInSetupScreen != m_restartInSetupScreenInitial;
}
bool SMServerConfig::isDefaults() const
{
return m_restartInSetupScreen;
}
void SMServerConfig::defaults()
{
ManagedConfigModule::defaults();
setRestartInSetupScreen(false);
}
#include "kcmsmserver.moc"
......@@ -20,40 +20,68 @@
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
*/
#ifndef __kcmsmserver_h__
#define __kcmsmserver_h__
#pragma once
#include <KCModule>
#include <KQuickAddons/ManagedConfigModule>
class QAction;
class SMServerSettings;
class SMServerData;
class SMServerConfigImpl;
class OrgFreedesktopLogin1ManagerInterface;
namespace Ui {
class SMServerConfigDlg;
}
class SMServerConfig : public KCModule
/// KCM handling the desktop session and in particular the login and logout
/// handling.
class SMServerConfig : public KQuickAddons::ManagedConfigModule
{
Q_OBJECT
Q_OBJECT
/// True if we are in an UEFI system.
Q_PROPERTY(bool isUefi READ isUefi CONSTANT)
/// Config telling if we should restart in the setup screen next time we boot.
Q_PROPERTY(bool restartInSetupScreen READ restartInSetupScreen WRITE setRestartInSetupScreen NOTIFY restartInSetupScreenChanged)
/// Error message, empty if there is no error
Q_PROPERTY(QString error READ error NOTIFY errorChanged)
/// Can setup the firmware
Q_PROPERTY(bool canFirmareSetup READ canFirmareSetup CONSTANT)
public:
explicit SMServerConfig( QWidget *parent=nullptr, const QVariantList &list=QVariantList() );
~SMServerConfig();
explicit SMServerConfig(QObject *parent = nullptr, const QVariantList &list = QVariantList());
~SMServerConfig() override;
bool isUefi() const;
bool restartInSetupScreen() const;
void setRestartInSetupScreen(bool isUefi);
QString error() const;
bool canFirmareSetup() const;
/// Tell the computer to reboot.
Q_INVOKABLE void reboot();
void defaults() override;
Q_SIGNALS:
void isUefiChanged();
void restartInSetupScreenChanged();
void errorChanged();
private:
void initFirmwareSetup();
void checkFirmwareSetupRequested();
QScopedPointer<Ui::SMServerConfigDlg> ui;
SMServerData *m_data;
OrgFreedesktopLogin1ManagerInterface *m_login1Manager = nullptr;
QAction *m_rebootNowAction = nullptr;
bool m_isUefi = false;
};
bool isSaveNeeded() const override;
bool isDefaults() const override;
void checkFirmwareSetupRequested();
#endif
OrgFreedesktopLogin1ManagerInterface *m_login1Manager = nullptr;
QAction *m_rebootNowAction = nullptr;
bool m_isUefi = false;
bool m_restartInSetupScreen = false;
bool m_restartInSetupScreenInitial = false;
bool m_canFirmareSetup = false;
QString m_error;
};
/***************************************************************************
* Copyright (C) 2020 Carl Schwan <carl@carlschwan.eu> *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA *
***************************************************************************/
import QtQuick 2.10
import QtQuick.Controls 2.10
import QtQuick.Layouts 1.11
import org.kde.kirigami 2.13 as Kirigami
import QtQuick.Dialogs 1.3
import org.kde.desktopsession.private 1.0
import org.kde.kcm 1.3 as KCM
KCM.SimpleKCM {
id: root
implicitHeight: Kirigami.Units.gridUnit * 28
implicitWidth: Kirigami.Units.gridUnit * 28
header: Kirigami.InlineMessage {
type: kcm.error.length > 0 ? Kirigami.MessageType.Error : Kirigami.MessageType.Information
visible: kcm.restartInSetupScreen || kcm.error.length > 0
text: kcm.error.length > 0
? i18n("Failed to request restart to firmware setup: %1", kcm.error)
: kcm.isUefi ? i18n("Next time the computer is restarted, it will enter the UEFI setup screen.")
: i18n("Next time the computer is restarted, it will enter the firmware setup screen.")
showCloseButton: true
actions: Kirigami.Action {
icon.name: "system-reboot"
onTriggered: kcm.reboot();
text: i18n("Restart Now")
}
}
Kirigami.FormLayout {
CheckBox {
Kirigami.FormData.label: i18n("General:")
text: i18n("Confirm logout")
checked: Settings.confirmLogout
onToggled: Settings.confirmLogout = true
KCM.SettingStateBinding {
configObject: Settings
settingName: "confirmLogout"
}
}
CheckBox {
text: i18n("Offer shutdown options")
checked: Settings.offerShutdown
onToggled: Settings.offerShutdown = true
KCM.SettingStateBinding {
configObject: Settings
settingName: "offerShutdown"
}
}
Item {
Kirigami.FormData.isSection: true
}
ButtonGroup {
buttons: [leaveEnd, leaveRestart, leaveOff]
}
RadioButton {
id: leaveEnd
Kirigami.FormData.label: i18n("Default leave option:")
text: i18n("End current session")
checked: Settings.shutdownType === 0
onToggled: Settings.shutdownType = 0
KCM.SettingStateBinding {
configObject: Settings
settingName: "shutdownType"
}
}
RadioButton {
id: leaveRestart
text: i18n("Restart computer")
checked: Settings.shutdownType === 1
onToggled: Settings.shutdownType = 1
KCM.SettingStateBinding {
configObject: Settings
settingName: "shutdownType"
}
}
RadioButton {
id: leaveOff
text: i18n("Turn off computer")
checked: Settings.shutdownType === 2
onToggled: Settings.shutdownType = 2
KCM.SettingStateBinding {
configObject: Settings
settingName: "shutdownType"
}
}
Item {
Kirigami.FormData.isSection: true
}
ButtonGroup {
buttons: [loginRestore, loginManual, loginEmpty]
}
RadioButton {
id: loginRestore
Kirigami.FormData.label: i18n("When logging in:")
text: i18n("Restore previous saved session")
checked: Settings.loginMode === 0
onToggled: Settings.loginMode = 0
KCM.SettingStateBinding {
configObject: Settings
settingName: "loginMode"
}
}
RadioButton {
id: loginManual
text: i18n("Restore manually saved session")
checked: Settings.loginMode === 1
onToggled: Settings.loginMode = 1
KCM.SettingStateBinding {
configObject: Settings
settingName: "loginMode"
}
}
RadioButton {
id: loginEmpty
text: i18n("Start with an empty session")
checked: Settings.loginMode === 2
onToggled: Settings.loginMode = 2
KCM.SettingStateBinding {
configObject: Settings
settingName: "loginMode"
}
}
Item {
Kirigami.FormData.isSection: true
}
TextField {
id: notRestoredApplications
Kirigami.FormData.label: i18n("Don't restore these applications:")
text: Settings.excludeApps
// onTextEdited instead of onAccepted because otherwise the apply and
// reset buttons won't work, since otherwise in many case no change will
// be sent to the kconfigXt backend.
onTextEdited: Settings.excludeApps = text
ToolTip.text: i18n("Here you can enter a colon or comma separated list of applications that should not be saved in sessions, and therefore will not be started when restoring a session. For example 'xterm:konsole' or 'xterm,konsole'.")
ToolTip.visible: notRestoredApplications.hovered
ToolTip.delay: Kirigami.Units.toolTipDelay
KCM.SettingStateBinding {
configObject: Settings
settingName: "excludeApps"
}
}
Item {
Kirigami.FormData.isSection: true
visible: kcm.canFirmareSetup
}
CheckBox {
id: uefi
text: i18n("Enter firmware setup screen on next restart")
visible: kcm.canFirmareSetup
checked: kcm.restartInSetupScreen
onToggled: kcm.restartInSetupScreen = checked
}
}
}
......@@ -3,13 +3,12 @@ Icon=system-log-out
Type=Service
X-KDE-ServiceTypes=KCModule
X-DocPath=kcontrol/kcmsmserver/index.html
X-Plasma-MainScript=ui/main.qml
X-KDE-FormFactors=desktop
X-KDE-Library=kcm_smserver
X-KDE-ParentApp=kcontrol
X-KDE-System-Settings-Parent-Category=session
X-KDE-Weight=60
Name=Desktop Session
Name[ar]=جلسة سطح المكتب
Name[ast]=Sesión del escritoriu
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SMServerConfigDlg</class>
<widget class="QWidget" name="SMServerConfigDlg">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>477</width>
<height>398</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="topBox">
<property name="title">
<string>General</string>
</property>