1 /*
2 * <one line to give the library's name and an idea of what it does.>
3 * Copyright (C) 2014 <copyright holder> <email>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 */
20
21 #include "keyboardlayoutconfig.h"
22 #include <QProcess>
23 #include <QFile>
24 #include <QHash>
25 #include <QDebug>
26 #include "selectkeyboardlayoutdialog.h"
27 #include <LXQt/Settings>
28
KeyboardLayoutConfig(LXQt::Settings * _settings,QWidget * parent)29 KeyboardLayoutConfig::KeyboardLayoutConfig(LXQt::Settings* _settings, QWidget* parent):
30 QWidget(parent),
31 settings(_settings),
32 applyConfig_(false) {
33 ui.setupUi(this);
34
35 loadLists();
36 loadSettings();
37 initControls();
38
39 connect(ui.addLayout, &QAbstractButton::clicked, this, &KeyboardLayoutConfig::onAddLayout);
40 connect(ui.removeLayout, &QAbstractButton::clicked, this, &KeyboardLayoutConfig::onRemoveLayout);
41 connect(ui.moveUp, &QAbstractButton::clicked, this, &KeyboardLayoutConfig::onMoveUp);
42 connect(ui.moveDown, &QAbstractButton::clicked, this, &KeyboardLayoutConfig::onMoveDown);
43 connect(ui.keyboardModel, QOverload<int>::of(&QComboBox::activated), [this](int /*index*/) {
44 applyConfig_ = true;
45 Q_EMIT settingsChanged();
46 });
47 connect(ui.switchKey, QOverload<int>::of(&QComboBox::activated), [this](int /*index*/) {
48 applyConfig_ = true;
49 Q_EMIT settingsChanged();
50 });
51 }
52
~KeyboardLayoutConfig()53 KeyboardLayoutConfig::~KeyboardLayoutConfig() {
54 }
55
loadSettings()56 void KeyboardLayoutConfig::loadSettings() {
57 // load current settings from the output of setxkbmap command
58 QProcess setxkbmap;
59 setxkbmap.start(QLatin1String("setxkbmap"), QStringList() << QLatin1String("-query")
60 << QLatin1String("-verbose") << QLatin1String("5"));
61 setxkbmap.waitForFinished();
62 if(setxkbmap.exitStatus() == QProcess::NormalExit) {
63 QList<QByteArray> layouts, variants;
64 while(!setxkbmap.atEnd()) {
65 QByteArray line = setxkbmap.readLine();
66 if(line.startsWith("model:")) {
67 keyboardModel_ = QString::fromLatin1(line.mid(6).trimmed());
68 }
69 else if(line.startsWith("layout:")) {
70 layouts = line.mid(7).trimmed().split(',');
71 }
72 else if(line.startsWith("variant:")) {
73 variants = line.mid(8).trimmed().split(',');
74 }
75 else if(line.startsWith("options:")) {
76 const QList<QByteArray> options = line.mid(9).trimmed().split(',');
77 for(const QByteArray &option : options) {
78 if(option.startsWith("grp:"))
79 switchKey_ = QString::fromLatin1(option);
80 else
81 currentOptions_ << QString::fromLatin1(option);
82 }
83 }
84 }
85
86 const int size = layouts.size(), variantsSize = variants.size();
87 for(int i = 0; i < size; ++i) {
88 currentLayouts_.append(QPair<QString, QString>(QString::fromUtf8(layouts.at(i)), variantsSize > 0 ? QString::fromUtf8(variants.at(i)) : QString()));
89 }
90
91 setxkbmap.close();
92 }
93 }
94
95 enum ListSection{
96 NoSection,
97 ModelSection,
98 LayoutSection,
99 VariantSection,
100 OptionSection
101 };
102
loadLists()103 void KeyboardLayoutConfig::loadLists() {
104 // load known lists from xkb data files
105 // XKBD_BASELIST_PATH is os dependent see keyboardlayoutconfig.h
106 QFile file(QLatin1String(XKBD_BASELIST_PATH));
107 if(file.open(QIODevice::ReadOnly)) {
108 ListSection section = NoSection;
109 while(!file.atEnd()) {
110 QByteArray line = file.readLine().trimmed();
111 if(section == NoSection) {
112 if(line.startsWith("! model"))
113 section = ModelSection;
114 else if(line.startsWith("! layout"))
115 section = LayoutSection;
116 else if(line.startsWith("! variant"))
117 section = VariantSection;
118 else if(line.startsWith("! option"))
119 section = OptionSection;
120 }
121 else {
122 if(line.isEmpty()) {
123 section = NoSection;
124 continue;
125 }
126 int sep = line.indexOf(' ');
127 QString name = QString::fromLatin1(line.constData(), sep);
128 while(line[sep] ==' ') // skip spaces
129 ++sep;
130 QString description = QString::fromUtf8(line.constData() + sep);
131
132 switch(section) {
133 case ModelSection: {
134 ui.keyboardModel->addItem(description, name);
135 break;
136 }
137 case LayoutSection:
138 knownLayouts_[name] = KeyboardLayoutInfo(description);
139 break;
140 case VariantSection: {
141 // the descriptions of variants are prefixed by their language ids
142 sep = description.indexOf(QLatin1String(": "));
143 if(sep >= 0) {
144 QString lang = description.left(sep);
145 QMap<QString, KeyboardLayoutInfo>::iterator it = knownLayouts_.find(lang);
146 if(it != knownLayouts_.end()) {
147 KeyboardLayoutInfo& info = *it;
148 info.variants.append(LayoutVariantInfo(name, description.mid(sep + 2)));
149 }
150 }
151 break;
152 }
153 case OptionSection:
154 if(line.startsWith("grp:")) { // key used to switch to another layout
155 ui.switchKey->addItem(description, name);
156 }
157 break;
158 default:;
159 }
160 }
161 }
162 file.close();
163 }
164 }
165
initControls()166 void KeyboardLayoutConfig::initControls() {
167 QList<QPair<QString, QString> >::iterator it;
168 for(it = currentLayouts_.begin(); it != currentLayouts_.end(); ++it) {
169 QString name = it->first;
170 QString variant = it->second;
171 addLayout(name, variant);
172 }
173
174 int n = ui.keyboardModel->count();
175 int row;
176 for(row = 0; row < n; ++row) {
177 if(ui.keyboardModel->itemData(row, Qt::UserRole).toString() == keyboardModel_) {
178 ui.keyboardModel->setCurrentIndex(row);
179 break;
180 }
181 }
182
183 n = ui.switchKey->count();
184 for(row = 0; row < n; ++row) {
185 if(ui.switchKey->itemData(row, Qt::UserRole).toString() == switchKey_) {
186 ui.switchKey->setCurrentIndex(row);
187 break;
188 }
189 }
190
191 }
192
addLayout(QString name,QString variant)193 void KeyboardLayoutConfig::addLayout(QString name, QString variant) {
194 qDebug() << "add" << name << variant;
195 const KeyboardLayoutInfo& info = knownLayouts_.value(name);
196 QTreeWidgetItem* item = new QTreeWidgetItem();
197 item->setData(0, Qt::DisplayRole, info.description);
198 item->setData(0, Qt::UserRole, name);
199 const LayoutVariantInfo* vinfo = info.findVariant(variant);
200 if(vinfo) {
201 item->setData(1, Qt::DisplayRole, vinfo->description);
202 item->setData(1, Qt::UserRole, variant);
203 }
204 ui.layouts->addTopLevelItem(item);
205 }
206
reset()207 void KeyboardLayoutConfig::reset() {
208 applyConfig_ = true;
209 ui.layouts->clear();
210 initControls();
211 applyConfig();
212 }
213
applyConfig()214 void KeyboardLayoutConfig::applyConfig() {
215 if(!applyConfig_)
216 return;
217 applyConfig_ = false;
218
219 // call setxkbmap to apply the changes
220 QProcess setxkbmap;
221 // clear existing options
222 setxkbmap.start(QStringLiteral("setxkbmap"), QStringList() << QStringLiteral("-option"));
223 setxkbmap.waitForFinished();
224 setxkbmap.close();
225
226 const QString program = QStringLiteral("setxkbmap");
227 QStringList args;
228 // set keyboard model
229 QString model;
230 int cur_model = ui.keyboardModel->currentIndex();
231 if(cur_model >= 0) {
232 model = ui.keyboardModel->itemData(cur_model, Qt::UserRole).toString();
233 args += QLatin1String("-model");
234 args += model;
235 }
236
237 // set keyboard layout
238 int n = ui.layouts->topLevelItemCount();
239 QString layouts, variants;
240 if(n > 0) {
241 for(int row = 0; row < n; ++row) {
242 QTreeWidgetItem* item = ui.layouts->topLevelItem(row);
243 layouts += item->data(0, Qt::UserRole).toString();
244 variants += item->data(1, Qt::UserRole).toString();
245 if(row < n - 1) { // not the last row
246 layouts += QLatin1Char(',');
247 variants += QLatin1Char(',');
248 }
249 }
250 args += QLatin1String("-layout");
251 args += layouts;
252
253 if (variants.indexOf(QLatin1Char(',')) > -1 || !variants.isEmpty()) {
254 args += QLatin1String("-variant");
255 args += variants;
256 }
257 }
258
259 for(const QString& option : qAsConst(currentOptions_)) {
260 if (!option.startsWith(QLatin1String("grp:"))) {
261 args += QLatin1String("-option");
262 args += option;
263 }
264 }
265
266 QString switchKey;
267 int cur_switch_key = ui.switchKey->currentIndex();
268 if(cur_switch_key > 0) { // index 0 is "None"
269 switchKey = ui.switchKey->itemData(cur_switch_key, Qt::UserRole).toString();
270 args += QLatin1String("-option");
271 args += switchKey;
272 }
273
274 qDebug() << program << args;
275
276 // execute the command line
277 setxkbmap.start(program, args);
278 setxkbmap.waitForFinished();
279
280 // save to lxqt-session config file.
281 settings->beginGroup(QStringLiteral("Keyboard"));
282 settings->setValue(QStringLiteral("layout"), layouts);
283 settings->setValue(QStringLiteral("variant"), variants);
284 settings->setValue(QStringLiteral("model"), model);
285 if(switchKey.isEmpty() && currentOptions_ .isEmpty())
286 settings->remove(QStringLiteral("options"));
287 else
288 settings->setValue(QStringLiteral("options"), switchKey.isEmpty() ? currentOptions_ : (currentOptions_ << switchKey));
289 settings->endGroup();
290 }
291
onAddLayout()292 void KeyboardLayoutConfig::onAddLayout() {
293 SelectKeyboardLayoutDialog dlg(knownLayouts_, this);
294 if(dlg.exec() == QDialog::Accepted) {
295 addLayout(dlg.selectedLayout(), dlg.selectedVariant());
296 applyConfig_ = true;
297 Q_EMIT settingsChanged();
298 }
299 }
300
onRemoveLayout()301 void KeyboardLayoutConfig::onRemoveLayout() {
302 if(ui.layouts->topLevelItemCount() > 1) {
303 QTreeWidgetItem* item = ui.layouts->currentItem();
304 if(item) {
305 delete item;
306 applyConfig_ = true;
307 Q_EMIT settingsChanged();
308 }
309 }
310 }
311
onMoveDown()312 void KeyboardLayoutConfig::onMoveDown() {
313 QTreeWidgetItem* item = ui.layouts->currentItem();
314 if(!item)
315 return;
316 int pos = ui.layouts->indexOfTopLevelItem(item);
317 if(pos < ui.layouts->topLevelItemCount() - 1) { // not the last item
318 ui.layouts->takeTopLevelItem(pos);
319 ui.layouts->insertTopLevelItem(pos + 1, item);
320 ui.layouts->setCurrentItem(item);
321 applyConfig_ = true;
322 Q_EMIT settingsChanged();
323 }
324 }
325
onMoveUp()326 void KeyboardLayoutConfig::onMoveUp() {
327 QTreeWidgetItem* item = ui.layouts->currentItem();
328 if(!item)
329 return;
330 int pos = ui.layouts->indexOfTopLevelItem(item);
331 if(pos > 0) { // not the first item
332 ui.layouts->takeTopLevelItem(pos);
333 ui.layouts->insertTopLevelItem(pos - 1, item);
334 ui.layouts->setCurrentItem(item);
335 applyConfig_ = true;
336 Q_EMIT settingsChanged();
337 }
338 }
339
340