|
|
|
/*
|
|
|
|
* <one line to give the library's name and an idea of what it does.>
|
|
|
|
* Copyright (C) 2014 <copyright holder> <email>
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 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
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "keyboardlayoutconfig.h"
|
|
|
|
#include <QProcess>
|
|
|
|
#include <QFile>
|
|
|
|
#include <QHash>
|
|
|
|
#include <QDebug>
|
|
|
|
#include "selectkeyboardlayoutdialog.h"
|
|
|
|
#include <LXQt/Settings>
|
|
|
|
|
|
|
|
KeyboardLayoutConfig::KeyboardLayoutConfig(LXQt::Settings* _settings, QWidget* parent):
|
|
|
|
QWidget(parent),
|
|
|
|
settings(_settings) {
|
|
|
|
ui.setupUi(this);
|
|
|
|
|
|
|
|
loadLists();
|
|
|
|
loadSettings();
|
|
|
|
initControls();
|
|
|
|
|
|
|
|
connect(ui.addLayout, SIGNAL(clicked(bool)), SLOT(onAddLayout()));
|
|
|
|
connect(ui.removeLayout, SIGNAL(clicked(bool)), SLOT(onRemoveLayout()));
|
|
|
|
connect(ui.moveUp, SIGNAL(clicked(bool)), SLOT(onMoveUp()));
|
|
|
|
connect(ui.moveDown, SIGNAL(clicked(bool)), SLOT(onMoveDown()));
|
|
|
|
connect(ui.keyboardModel, SIGNAL(currentIndexChanged(int)), SLOT(accept()));
|
|
|
|
connect(ui.switchKey, SIGNAL(currentIndexChanged(int)), SLOT(accept()));
|
|
|
|
}
|
|
|
|
|
|
|
|
KeyboardLayoutConfig::~KeyboardLayoutConfig() {
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardLayoutConfig::loadSettings() {
|
|
|
|
// load current settings from the output of setxkbmap command
|
|
|
|
QProcess setxkbmap;
|
|
|
|
setxkbmap.start(QLatin1String("setxkbmap -query -verbose 5"));
|
|
|
|
setxkbmap.waitForFinished();
|
|
|
|
if(setxkbmap.exitStatus() == QProcess::NormalExit) {
|
|
|
|
QList<QByteArray> layouts, variants;
|
|
|
|
while(!setxkbmap.atEnd()) {
|
|
|
|
QByteArray line = setxkbmap.readLine();
|
|
|
|
if(line.startsWith("model:")) {
|
|
|
|
keyboardModel_ = QString::fromLatin1(line.mid(6).trimmed());
|
|
|
|
}
|
|
|
|
else if(line.startsWith("layout:")) {
|
|
|
|
layouts = line.mid(7).trimmed().split(',');
|
|
|
|
}
|
|
|
|
else if(line.startsWith("variant:")) {
|
|
|
|
variants = line.mid(8).trimmed().split(',');
|
|
|
|
}
|
|
|
|
else if(line.startsWith("options:")) {
|
|
|
|
QList<QByteArray> options = line.mid(9).trimmed().split(',');
|
|
|
|
Q_FOREACH(QByteArray option, options) {
|
|
|
|
if(option.startsWith("grp:"))
|
|
|
|
switchKey_ = QString::fromLatin1(option);
|
|
|
|
else
|
|
|
|
currentOptions_ << QString::fromLatin1(option);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const int size = layouts.size(), variantsSize = variants.size();
|
|
|
|
for(int i = 0; i < size; ++i) {
|
|
|
|
currentLayouts_.append(QPair<QString, QString>(layouts.at(i), variantsSize > 0 ? variants.at(i) : QString()));
|
|
|
|
}
|
|
|
|
|
|
|
|
setxkbmap.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum ListSection{
|
|
|
|
NoSection,
|
|
|
|
ModelSection,
|
|
|
|
LayoutSection,
|
|
|
|
VariantSection,
|
|
|
|
OptionSection
|
|
|
|
};
|
|
|
|
|
|
|
|
void KeyboardLayoutConfig::loadLists() {
|
|
|
|
// load known lists from xkb data files
|
|
|
|
// FIXME: maybe we should use different files for different OSes?
|
|
|
|
QFile file(QLatin1String("/usr/share/X11/xkb/rules/base.lst"));
|
|
|
|
if(file.open(QIODevice::ReadOnly)) {
|
|
|
|
ListSection section = NoSection;
|
|
|
|
while(!file.atEnd()) {
|
|
|
|
QByteArray line = file.readLine().trimmed();
|
|
|
|
if(section == NoSection) {
|
|
|
|
if(line.startsWith("! model"))
|
|
|
|
section = ModelSection;
|
|
|
|
else if(line.startsWith("! layout"))
|
|
|
|
section = LayoutSection;
|
|
|
|
else if(line.startsWith("! variant"))
|
|
|
|
section = VariantSection;
|
|
|
|
else if(line.startsWith("! option"))
|
|
|
|
section = OptionSection;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if(line.isEmpty()) {
|
|
|
|
section = NoSection;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
int sep = line.indexOf(' ');
|
|
|
|
QString name = QString::fromLatin1(line, sep);
|
|
|
|
while(line[sep] == ' ') // skip spaces
|
|
|
|
++sep;
|
|
|
|
QString description = QString::fromUtf8(line.constData() + sep);
|
|
|
|
|
|
|
|
switch(section) {
|
|
|
|
case ModelSection: {
|
|
|
|
ui.keyboardModel->addItem(description, name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case LayoutSection:
|
|
|
|
knownLayouts_[name] = KeyboardLayoutInfo(description);
|
|
|
|
break;
|
|
|
|
case VariantSection: {
|
|
|
|
// the descriptions of variants are prefixed by their language ids
|
|
|
|
sep = description.indexOf(": ");
|
|
|
|
if(sep >= 0) {
|
|
|
|
QString lang = description.left(sep);
|
|
|
|
QMap<QString, KeyboardLayoutInfo>::iterator it = knownLayouts_.find(lang);
|
|
|
|
if(it != knownLayouts_.end()) {
|
|
|
|
KeyboardLayoutInfo& info = *it;
|
|
|
|
info.variants.append(LayoutVariantInfo(name, description.mid(sep + 2)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case OptionSection:
|
|
|
|
if(line.startsWith("grp:")) { // key used to switch to another layout
|
|
|
|
ui.switchKey->addItem(description, name);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardLayoutConfig::initControls() {
|
|
|
|
QList<QPair<QString, QString> >::iterator it;
|
|
|
|
for(it = currentLayouts_.begin(); it != currentLayouts_.end(); ++it) {
|
|
|
|
QString name = it->first;
|
|
|
|
QString variant = it->second;
|
|
|
|
addLayout(name, variant);
|
|
|
|
}
|
|
|
|
|
|
|
|
int n = ui.keyboardModel->count();
|
|
|
|
int row;
|
|
|
|
for(row = 0; row < n; ++row) {
|
|
|
|
if(ui.keyboardModel->itemData(row, Qt::UserRole).toString() == keyboardModel_) {
|
|
|
|
ui.keyboardModel->setCurrentIndex(row);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
n = ui.switchKey->count();
|
|
|
|
for(row = 0; row < n; ++row) {
|
|
|
|
if(ui.switchKey->itemData(row, Qt::UserRole).toString() == switchKey_) {
|
|
|
|
ui.switchKey->setCurrentIndex(row);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardLayoutConfig::addLayout(QString name, QString variant) {
|
|
|
|
qDebug() << "add" << name << variant;
|
|
|
|
const KeyboardLayoutInfo& info = knownLayouts_.value(name);
|
|
|
|
QTreeWidgetItem* item = new QTreeWidgetItem();
|
|
|
|
item->setData(0, Qt::DisplayRole, info.description);
|
|
|
|
item->setData(0, Qt::UserRole, name);
|
|
|
|
const LayoutVariantInfo* vinfo = info.findVariant(variant);
|
|
|
|
if(vinfo) {
|
|
|
|
item->setData(1, Qt::DisplayRole, vinfo->description);
|
|
|
|
item->setData(1, Qt::UserRole, variant);
|
|
|
|
}
|
|
|
|
ui.layouts->addTopLevelItem(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardLayoutConfig::reset() {
|
|
|
|
ui.layouts->clear();
|
|
|
|
initControls();
|
|
|
|
accept();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardLayoutConfig::accept() {
|
|
|
|
// call setxkbmap to apply the changes
|
|
|
|
QProcess setxkbmap;
|
|
|
|
// clear existing options
|
|
|
|
setxkbmap.start("setxkbmap -option");
|
|
|
|
setxkbmap.waitForFinished();
|
|
|
|
setxkbmap.close();
|
|
|
|
|
|
|
|
QString command = "setxkbmap";
|
|
|
|
// set keyboard model
|
|
|
|
QString model;
|
|
|
|
int cur_model = ui.keyboardModel->currentIndex();
|
|
|
|
if(cur_model >= 0) {
|
|
|
|
model = ui.keyboardModel->itemData(cur_model, Qt::UserRole).toString();
|
|
|
|
command += " -model ";
|
|
|
|
command += model;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set keyboard layout
|
|
|
|
int n = ui.layouts->topLevelItemCount();
|
|
|
|
QString layouts, variants;
|
|
|
|
if(n > 0) {
|
|
|
|
for(int row = 0; row < n; ++row) {
|
|
|
|
QTreeWidgetItem* item = ui.layouts->topLevelItem(row);
|
|
|
|
layouts += item->data(0, Qt::UserRole).toString();
|
|
|
|
variants += item->data(1, Qt::UserRole).toString();
|
|
|
|
if(row < n - 1) { // not the last row
|
|
|
|
layouts += ',';
|
|
|
|
variants += ',';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
command += " -layout ";
|
|
|
|
command += layouts;
|
|
|
|
|
|
|
|
if (variants.indexOf(',') > -1 || !variants.isEmpty()) {
|
|
|
|
command += " -variant ";
|
|
|
|
command += variants;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Q_FOREACH(QString option, currentOptions_) {
|
|
|
|
if (!option.startsWith("grp:")) {
|
|
|
|
command += " -option ";
|
|
|
|
command += option;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString switchKey;
|
|
|
|
int cur_switch_key = ui.switchKey->currentIndex();
|
|
|
|
if(cur_switch_key > 0) { // index 0 is "None"
|
|
|
|
switchKey = ui.switchKey->itemData(cur_switch_key, Qt::UserRole).toString();
|
|
|
|
command += " -option ";
|
|
|
|
command += switchKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << command;
|
|
|
|
|
|
|
|
// execute the command line
|
|
|
|
setxkbmap.start(command);
|
|
|
|
setxkbmap.waitForFinished();
|
|
|
|
|
|
|
|
// save to lxqt-session config file.
|
|
|
|
settings->beginGroup("Keyboard");
|
|
|
|
settings->setValue("layout", layouts);
|
|
|
|
settings->setValue("variant", variants);
|
|
|
|
settings->setValue("model", model);
|
|
|
|
if(switchKey.isEmpty() && currentOptions_ .isEmpty())
|
|
|
|
settings->remove("options");
|
|
|
|
else
|
|
|
|
settings->setValue("options", switchKey.isEmpty() ? currentOptions_ : (currentOptions_ << switchKey));
|
|
|
|
settings->endGroup();
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardLayoutConfig::onAddLayout() {
|
|
|
|
SelectKeyboardLayoutDialog dlg(knownLayouts_, this);
|
|
|
|
if(dlg.exec() == QDialog::Accepted) {
|
|
|
|
addLayout(dlg.selectedLayout(), dlg.selectedVariant());
|
|
|
|
accept();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardLayoutConfig::onRemoveLayout() {
|
|
|
|
if(ui.layouts->topLevelItemCount() > 1) {
|
|
|
|
QTreeWidgetItem* item = ui.layouts->currentItem();
|
|
|
|
if(item) {
|
|
|
|
delete item;
|
|
|
|
accept();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardLayoutConfig::onMoveDown() {
|
|
|
|
QTreeWidgetItem* item = ui.layouts->currentItem();
|
|
|
|
if(!item)
|
|
|
|
return;
|
|
|
|
int pos = ui.layouts->indexOfTopLevelItem(item);
|
|
|
|
if(pos < ui.layouts->topLevelItemCount() - 1) { // not the last item
|
|
|
|
ui.layouts->takeTopLevelItem(pos);
|
|
|
|
ui.layouts->insertTopLevelItem(pos + 1, item);
|
|
|
|
ui.layouts->setCurrentItem(item);
|
|
|
|
accept();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void KeyboardLayoutConfig::onMoveUp() {
|
|
|
|
QTreeWidgetItem* item = ui.layouts->currentItem();
|
|
|
|
if(!item)
|
|
|
|
return;
|
|
|
|
int pos = ui.layouts->indexOfTopLevelItem(item);
|
|
|
|
if(pos > 0) { // not the first item
|
|
|
|
ui.layouts->takeTopLevelItem(pos);
|
|
|
|
ui.layouts->insertTopLevelItem(pos - 1, item);
|
|
|
|
ui.layouts->setCurrentItem(item);
|
|
|
|
accept();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|