1 /*
2     SPDX-FileCopyrightText: 2018 Roman Gilg <subdiff@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "libinput_config.h"
8 #include "../configcontainer.h"
9 
10 #include <KAboutData>
11 #include <KLocalizedString>
12 #include <KMessageWidget>
13 #include <kdeclarative/kdeclarative.h>
14 
15 #include <QMetaObject>
16 #include <QQmlContext>
17 #include <QQmlProperty>
18 #include <QQuickItem>
19 #include <QQuickWidget>
20 #include <QVBoxLayout>
21 
22 #include "inputbackend.h"
23 
getDeviceList(InputBackend * backend)24 static QVariant getDeviceList(InputBackend *backend)
25 {
26     return QVariant::fromValue(backend->getDevices().toList());
27 }
28 
LibinputConfig(ConfigContainer * parent,InputBackend * backend)29 LibinputConfig::LibinputConfig(ConfigContainer *parent, InputBackend *backend)
30     : ConfigPlugin(parent)
31 {
32     m_backend = backend;
33 
34     KAboutData *data = new KAboutData(QStringLiteral("kcmmouse"),
35                                       i18n("Pointer device KCM"),
36                                       QStringLiteral("1.0"),
37                                       i18n("System Settings module for managing mice and trackballs."),
38                                       KAboutLicense::GPL_V2,
39                                       i18n("Copyright 2018 Roman Gilg"),
40                                       QString());
41 
42     data->addAuthor(i18n("Roman Gilg"), i18n("Developer"), QStringLiteral("subdiff@gmail.com"));
43 
44     m_parent->setAboutData(data);
45 
46     m_initError = !m_backend->errorString().isNull();
47 
48     m_view = new QQuickWidget(this);
49 
50     m_errorMessage = new KMessageWidget(this);
51     m_errorMessage->setCloseButtonVisible(false);
52     m_errorMessage->setWordWrap(true);
53     m_errorMessage->setVisible(false);
54 
55     QVBoxLayout *layout = new QVBoxLayout(parent);
56 
57     layout->addWidget(m_errorMessage);
58     layout->addWidget(m_view);
59     parent->setLayout(layout);
60 
61     m_view->setResizeMode(QQuickWidget::SizeRootObjectToView);
62     m_view->setClearColor(Qt::transparent);
63     m_view->setAttribute(Qt::WA_AlwaysStackOnTop);
64 
65     m_view->rootContext()->setContextProperty("backend", m_backend);
66     m_view->rootContext()->setContextProperty("deviceModel", getDeviceList(m_backend));
67 
68     KDeclarative::KDeclarative kdeclarative;
69     kdeclarative.setupEngine(m_view->engine()); // This is a new engine
70     kdeclarative.setDeclarativeEngine(m_view->engine());
71     kdeclarative.setupContext();
72 
73     if (m_backend->mode() == InputBackendMode::XLibinput) {
74         m_view->setSource(QUrl("qrc:/libinput/main_deviceless.qml"));
75     } else {
76         m_view->setSource(QUrl("qrc:/libinput/main.qml"));
77     }
78 
79     if (m_initError) {
80         m_errorMessage->setMessageType(KMessageWidget::Error);
81         m_errorMessage->setText(m_backend->errorString());
82         QMetaObject::invokeMethod(m_errorMessage, "animatedShow", Qt::QueuedConnection);
83     } else {
84         connect(m_backend, SIGNAL(deviceAdded(bool)), this, SLOT(onDeviceAdded(bool)));
85         connect(m_backend, SIGNAL(deviceRemoved(int)), this, SLOT(onDeviceRemoved(int)));
86         connect(m_view->rootObject(), SIGNAL(changeSignal()), this, SLOT(onChange()));
87     }
88 
89     m_view->show();
90 }
91 
sizeHint() const92 QSize LibinputConfig::sizeHint() const
93 {
94     return QQmlProperty::read(m_view->rootObject(), "sizeHint").toSize();
95 }
96 
minimumSizeHint() const97 QSize LibinputConfig::minimumSizeHint() const
98 {
99     return QQmlProperty::read(m_view->rootObject(), "minimumSizeHint").toSize();
100 }
101 
load()102 void LibinputConfig::load()
103 {
104     // in case of critical init error in backend, don't try
105     if (m_initError) {
106         return;
107     }
108 
109     if (!m_backend->getConfig()) {
110         m_errorMessage->setMessageType(KMessageWidget::Error);
111         m_errorMessage->setText(i18n("Error while loading values. See logs for more information. Please restart this configuration module."));
112         m_errorMessage->animatedShow();
113     } else {
114         if (!m_backend->deviceCount()) {
115             m_errorMessage->setMessageType(KMessageWidget::Information);
116             m_errorMessage->setText(i18n("No pointer device found. Connect now."));
117             m_errorMessage->animatedShow();
118         }
119     }
120     QMetaObject::invokeMethod(m_view->rootObject(), "syncValuesFromBackend");
121 }
122 
save()123 void LibinputConfig::save()
124 {
125     if (!m_backend->applyConfig()) {
126         m_errorMessage->setMessageType(KMessageWidget::Error);
127         m_errorMessage->setText(i18n("Not able to save all changes. See logs for more information. Please restart this configuration module and try again."));
128         m_errorMessage->animatedShow();
129     } else {
130         hideErrorMessage();
131     }
132     // load newly written values
133     load();
134     // in case of error, config still in changed state
135     Q_EMIT m_parent->changed(m_backend->isChangedConfig());
136 }
137 
defaults()138 void LibinputConfig::defaults()
139 {
140     // in case of critical init error in backend, don't try
141     if (m_initError) {
142         return;
143     }
144 
145     if (!m_backend->getDefaultConfig()) {
146         m_errorMessage->setMessageType(KMessageWidget::Error);
147         m_errorMessage->setText(i18n("Error while loading default values. Failed to set some options to their default values."));
148         m_errorMessage->animatedShow();
149     }
150     QMetaObject::invokeMethod(m_view->rootObject(), "syncValuesFromBackend");
151     Q_EMIT m_parent->changed(m_backend->isChangedConfig());
152 }
153 
onChange()154 void LibinputConfig::onChange()
155 {
156     if (!m_backend->deviceCount()) {
157         return;
158     }
159     hideErrorMessage();
160     Q_EMIT m_parent->changed(m_backend->isChangedConfig());
161 }
162 
onDeviceAdded(bool success)163 void LibinputConfig::onDeviceAdded(bool success)
164 {
165     QQuickItem *rootObj = m_view->rootObject();
166 
167     if (!success) {
168         m_errorMessage->setMessageType(KMessageWidget::Error);
169         m_errorMessage->setText(i18n("Error while adding newly connected device. Please reconnect it and restart this configuration module."));
170     }
171 
172     int activeIndex;
173     if (m_backend->deviceCount() == 1) {
174         // if no pointer device was connected previously, show the new device and hide the no-device-message
175         activeIndex = 0;
176         hideErrorMessage();
177     } else {
178         activeIndex = QQmlProperty::read(rootObj, "deviceIndex").toInt();
179     }
180     m_view->rootContext()->setContextProperty("deviceModel", getDeviceList(m_backend));
181     QMetaObject::invokeMethod(rootObj, "resetModel", Q_ARG(QVariant, activeIndex));
182     QMetaObject::invokeMethod(rootObj, "syncValuesFromBackend");
183 }
184 
onDeviceRemoved(int index)185 void LibinputConfig::onDeviceRemoved(int index)
186 {
187     QQuickItem *rootObj = m_view->rootObject();
188 
189     int activeIndex = QQmlProperty::read(rootObj, "deviceIndex").toInt();
190     if (activeIndex == index) {
191         m_errorMessage->setMessageType(KMessageWidget::Information);
192         if (m_backend->deviceCount()) {
193             m_errorMessage->setText(i18n("Pointer device disconnected. Closed its setting dialog."));
194         } else {
195             m_errorMessage->setText(i18n("Pointer device disconnected. No other devices found."));
196         }
197         m_errorMessage->animatedShow();
198         activeIndex = 0;
199     } else {
200         if (index < activeIndex) {
201             activeIndex--;
202         }
203     }
204     m_view->rootContext()->setContextProperty("deviceModel", getDeviceList(m_backend));
205     QMetaObject::invokeMethod(m_view->rootObject(), "resetModel", Q_ARG(QVariant, activeIndex));
206     QMetaObject::invokeMethod(rootObj, "syncValuesFromBackend");
207 
208     Q_EMIT m_parent->changed(m_backend->isChangedConfig());
209 }
210 
hideErrorMessage()211 void LibinputConfig::hideErrorMessage()
212 {
213     if (m_errorMessage->isVisible()) {
214         m_errorMessage->animatedHide();
215     }
216 }
217