File indexing completed on 2025-01-26 05:06:35

0001 /*
0002     SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "libinput_config.h"
0008 #include "../configcontainer.h"
0009 
0010 #include <KAboutData>
0011 #include <KLocalizedContext>
0012 #include <KLocalizedString>
0013 #include <KMessageWidget>
0014 
0015 #include <QMetaObject>
0016 #include <QQmlContext>
0017 #include <QQmlEngine>
0018 #include <QQmlProperty>
0019 #include <QQuickItem>
0020 #include <QQuickWidget>
0021 #include <QVBoxLayout>
0022 
0023 #include "inputbackend.h"
0024 
0025 static QVariant getDeviceList(InputBackend *backend)
0026 {
0027     return QVariant::fromValue(backend->getDevices().toList());
0028 }
0029 
0030 LibinputConfig::LibinputConfig(ConfigContainer *parent, InputBackend *backend)
0031     : ConfigPlugin(parent)
0032 {
0033     m_backend = backend;
0034 
0035     m_initError = !m_backend->errorString().isNull();
0036 
0037     m_view = new QQuickWidget(this);
0038 
0039     m_errorMessage = new KMessageWidget(this);
0040     m_errorMessage->setCloseButtonVisible(false);
0041     m_errorMessage->setWordWrap(true);
0042     m_errorMessage->setVisible(false);
0043 
0044     QVBoxLayout *layout = new QVBoxLayout(parent->widget());
0045 
0046     layout->addWidget(m_errorMessage);
0047     layout->addWidget(m_view);
0048     parent->widget()->setLayout(layout);
0049 
0050     m_view->setResizeMode(QQuickWidget::SizeRootObjectToView);
0051     m_view->setClearColor(Qt::transparent);
0052     m_view->setAttribute(Qt::WA_AlwaysStackOnTop);
0053 
0054     m_view->rootContext()->setContextProperty("backend", m_backend);
0055     m_view->rootContext()->setContextProperty("deviceModel", getDeviceList(m_backend));
0056 
0057     QObject::connect(m_view, &QQuickWidget::statusChanged, [&](QQuickWidget::Status status) {
0058         if (status == QQuickWidget::Ready) {
0059             connect(m_view->rootObject(), SIGNAL(changeSignal()), this, SLOT(onChange()));
0060         }
0061     });
0062 
0063     m_view->engine()->rootContext()->setContextObject(new KLocalizedContext(m_view->engine()));
0064 
0065 #if BUILD_KCM_MOUSE_X11
0066     bool deviceless = m_backend->mode() == InputBackendMode::XLibinput;
0067 #else
0068     bool deviceless = false;
0069 #endif
0070     if (deviceless) {
0071         m_view->setSource(QUrl(QStringLiteral("qrc:/libinput/main_deviceless.qml")));
0072     } else {
0073         m_view->setSource(QUrl(QStringLiteral("qrc:/libinput/main.qml")));
0074     }
0075 
0076     if (m_initError) {
0077         m_errorMessage->setMessageType(KMessageWidget::Error);
0078         m_errorMessage->setText(m_backend->errorString());
0079         QMetaObject::invokeMethod(m_errorMessage, "animatedShow", Qt::QueuedConnection);
0080     } else {
0081         connect(m_backend, SIGNAL(deviceAdded(bool)), this, SLOT(onDeviceAdded(bool)));
0082         connect(m_backend, SIGNAL(deviceRemoved(int)), this, SLOT(onDeviceRemoved(int)));
0083     }
0084 
0085     // Just set it to a reasonable default size
0086     // This only matters for kcmshell and most of the time we have the KCM in systemsettings open
0087     m_view->resize(QSize(300, 300));
0088     m_view->show();
0089 }
0090 
0091 void LibinputConfig::load()
0092 {
0093     // in case of critical init error in backend, don't try
0094     if (m_initError) {
0095         return;
0096     }
0097 
0098     if (!m_backend->getConfig()) {
0099         m_errorMessage->setMessageType(KMessageWidget::Error);
0100         m_errorMessage->setText(i18n("Error while loading values. See logs for more information. Please restart this configuration module."));
0101         m_errorMessage->animatedShow();
0102     } else {
0103         if (!m_backend->deviceCount()) {
0104             m_errorMessage->setMessageType(KMessageWidget::Information);
0105             m_errorMessage->setText(i18n("No pointer device found. Connect now."));
0106             m_errorMessage->animatedShow();
0107         }
0108     }
0109     QMetaObject::invokeMethod(m_view->rootObject(), "syncValuesFromBackend");
0110 }
0111 
0112 void LibinputConfig::save()
0113 {
0114     if (!m_backend->applyConfig()) {
0115         m_errorMessage->setMessageType(KMessageWidget::Error);
0116         m_errorMessage->setText(i18n("Not able to save all changes. See logs for more information. Please restart this configuration module and try again."));
0117         m_errorMessage->animatedShow();
0118     } else {
0119         hideErrorMessage();
0120     }
0121     // load newly written values
0122     load();
0123     // in case of error, config still in changed state
0124     m_parent->setNeedsSave(m_backend->isChangedConfig());
0125 }
0126 
0127 void LibinputConfig::defaults()
0128 {
0129     // in case of critical init error in backend, don't try
0130     if (m_initError) {
0131         return;
0132     }
0133 
0134     if (!m_backend->getDefaultConfig()) {
0135         m_errorMessage->setMessageType(KMessageWidget::Error);
0136         m_errorMessage->setText(i18n("Error while loading default values. Failed to set some options to their default values."));
0137         m_errorMessage->animatedShow();
0138     }
0139     QMetaObject::invokeMethod(m_view->rootObject(), "syncValuesFromBackend");
0140     m_parent->setNeedsSave(m_backend->isChangedConfig());
0141 }
0142 
0143 void LibinputConfig::onChange()
0144 {
0145     if (!m_backend->deviceCount()) {
0146         return;
0147     }
0148     hideErrorMessage();
0149     m_parent->setNeedsSave(m_backend->isChangedConfig());
0150 }
0151 
0152 void LibinputConfig::onDeviceAdded(bool success)
0153 {
0154     QQuickItem *rootObj = m_view->rootObject();
0155 
0156     if (!success) {
0157         m_errorMessage->setMessageType(KMessageWidget::Error);
0158         m_errorMessage->setText(i18n("Error while adding newly connected device. Please reconnect it and restart this configuration module."));
0159     }
0160 
0161     int activeIndex;
0162     if (m_backend->deviceCount() == 1) {
0163         // if no pointer device was connected previously, show the new device and hide the no-device-message
0164         activeIndex = 0;
0165         hideErrorMessage();
0166     } else {
0167         activeIndex = QQmlProperty::read(rootObj, "deviceIndex").toInt();
0168     }
0169     m_view->rootContext()->setContextProperty("deviceModel", getDeviceList(m_backend));
0170     QMetaObject::invokeMethod(rootObj, "resetModel", Q_ARG(QVariant, activeIndex));
0171     QMetaObject::invokeMethod(rootObj, "syncValuesFromBackend");
0172 }
0173 
0174 void LibinputConfig::onDeviceRemoved(int index)
0175 {
0176     QQuickItem *rootObj = m_view->rootObject();
0177 
0178     int activeIndex = QQmlProperty::read(rootObj, "deviceIndex").toInt();
0179     if (activeIndex == index) {
0180         m_errorMessage->setMessageType(KMessageWidget::Information);
0181         if (m_backend->deviceCount()) {
0182             m_errorMessage->setText(i18n("Pointer device disconnected. Closed its setting dialog."));
0183         } else {
0184             m_errorMessage->setText(i18n("Pointer device disconnected. No other devices found."));
0185         }
0186         m_errorMessage->animatedShow();
0187         activeIndex = 0;
0188     } else {
0189         if (index < activeIndex) {
0190             activeIndex--;
0191         }
0192     }
0193     m_view->rootContext()->setContextProperty("deviceModel", getDeviceList(m_backend));
0194     QMetaObject::invokeMethod(m_view->rootObject(), "resetModel", Q_ARG(QVariant, activeIndex));
0195     QMetaObject::invokeMethod(rootObj, "syncValuesFromBackend");
0196 
0197     m_parent->setNeedsSave(m_backend->isChangedConfig());
0198 }
0199 
0200 void LibinputConfig::hideErrorMessage()
0201 {
0202     if (m_errorMessage->isVisible()) {
0203         m_errorMessage->animatedHide();
0204     }
0205 }