From 0872de7910fad333a5757b24136560c1863f5042 Mon Sep 17 00:00:00 2001 From: Camilo Higuita Date: Wed, 25 Mar 2020 10:08:12 -0500 Subject: [PATCH] [keyboard] Add Config object, split out a keyboardq - Introduce new QML-ified module for the keyboard - To share code, move *keyboard* module to a Config-object --- src/modules/keyboard/Config.cpp | 520 ++++++++++++++++++ src/modules/keyboard/Config.h | 133 +++++ src/modules/keyboard/KeyboardViewStep.cpp | 34 +- src/modules/keyboardq/CMakeLists.txt | 19 + src/modules/keyboardq/KeyboardQmlViewStep.cpp | 133 +++++ src/modules/keyboardq/KeyboardQmlViewStep.h | 71 +++ src/modules/keyboardq/keyboard.qml | 204 +++++++ src/modules/keyboardq/keyboardq.conf | 16 + 8 files changed, 1111 insertions(+), 19 deletions(-) create mode 100644 src/modules/keyboard/Config.cpp create mode 100644 src/modules/keyboard/Config.h create mode 100644 src/modules/keyboardq/CMakeLists.txt create mode 100644 src/modules/keyboardq/KeyboardQmlViewStep.cpp create mode 100644 src/modules/keyboardq/KeyboardQmlViewStep.h create mode 100644 src/modules/keyboardq/keyboard.qml create mode 100644 src/modules/keyboardq/keyboardq.conf diff --git a/src/modules/keyboard/Config.cpp b/src/modules/keyboard/Config.cpp new file mode 100644 index 000000000..f8abb01d4 --- /dev/null +++ b/src/modules/keyboard/Config.cpp @@ -0,0 +1,520 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019-2020, Adriaan de Groot + * + * Calamares 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 3 of the License, or + * (at your option) any later version. + * + * Calamares 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 Calamares. If not, see . + */ + +#include "Config.h" + +#include +#include +#include +#include + +#include "keyboardwidget/keyboardpreview.h" +#include "SetKeyboardLayoutJob.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "utils/Logger.h" +#include "utils/Retranslator.h" + +KeyboardModelsModel::KeyboardModelsModel(QObject* parent) : QAbstractListModel(parent) +{ + detectModels(); +} + +void +KeyboardModelsModel::detectModels() +{ + beginResetModel(); + const auto models = KeyboardGlobal::getKeyboardModels(); + auto index = -1; + for(const auto &key :models.keys()) + { + index++; + m_list << QMap {{"label", key}, {"key", models[key]}}; + if ( models[key] == "pc105" ) + this->setCurrentIndex(index); + + } + endResetModel(); +} + +void +KeyboardModelsModel::refresh() +{ + m_list.clear(); + setCurrentIndex(-1); + detectModels(); +} + +QVariant +KeyboardModelsModel::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + return QVariant(); + const auto item = m_list.at(index.row()); + return role == Qt::DisplayRole ? item["label"] : item["key"]; +} + +int +KeyboardModelsModel::rowCount(const QModelIndex&) const +{ + return m_list.count(); +} + +QHash +KeyboardModelsModel::roleNames() const +{ + return {{Qt::DisplayRole, "label"}, {Qt::UserRole, "key"}}; +} + +int +KeyboardModelsModel::currentIndex() const +{ + return m_currentIndex; +} + +const QMap +KeyboardModelsModel::item(const int &index) const +{ + if(index >= m_list.count() || index < 0) + return QMap(); + + return m_list.at(index); +} + +const QMap +KeyboardVariantsModel::item(const int &index) const +{ + if(index >= m_list.count() || index < 0) + return QMap(); + + return m_list.at(index); +} + +void +KeyboardModelsModel::setCurrentIndex(const int& index) +{ + if(index >= m_list.count() || index < 0) + return; + + m_currentIndex = index; + emit currentIndexChanged(m_currentIndex); +} + +KeyboardVariantsModel::KeyboardVariantsModel(QObject *parent) : QAbstractListModel(parent) +{} + +int +KeyboardVariantsModel::currentIndex() const +{ + return m_currentIndex; +} + +void +KeyboardVariantsModel::setCurrentIndex(const int& index) +{ + if(index >= m_list.count() || index < 0) + return; + + m_currentIndex = index; + emit currentIndexChanged(m_currentIndex); +} + +QVariant +KeyboardVariantsModel::data(const QModelIndex& index, int role) const +{ + if(!index.isValid()) + return QVariant(); + const auto item = m_list.at(index.row()); + return role == Qt::DisplayRole ? item["label"] : item["key"]; +} + +int +KeyboardVariantsModel::rowCount(const QModelIndex&) const +{ + return m_list.count(); +} + +QHash +KeyboardVariantsModel::roleNames() const +{ + return {{Qt::DisplayRole, "label"}, {Qt::UserRole, "key"}}; +} + +void +KeyboardVariantsModel::setVariants(QMap< QString, QString > variants) +{ + m_list.clear(); + beginResetModel(); + for(const auto &key :variants.keys()) + { + const auto item = QMap {{"label", key}, {"key", variants[key]}}; + m_list << item; + } + endResetModel(); +} + +/* Returns stringlist with suitable setxkbmap command-line arguments + * to set the given @p layout and @p variant. + */ +static inline QStringList xkbmap_args( const QString& layout, const QString& variant ) +{ + QStringList r{ "-layout", layout }; + if ( !variant.isEmpty() ) + r << "-variant" << variant; + return r; +} + +Config::Config(QObject *parent) : QObject(parent) +, m_keyboardModelsModel(new KeyboardModelsModel(this)) +, m_keyboardLayoutsModel(new KeyboardLayoutModel(this)) +, m_keyboardVariantsModel(new KeyboardVariantsModel(this)) +{ + m_setxkbmapTimer.setSingleShot( true ); + + // Connect signals and slots + connect( m_keyboardModelsModel, &KeyboardModelsModel::currentIndexChanged, + [&](int index) + { + m_selectedModel = m_keyboardModelsModel->item(index).value("key", "pc105" ); + // Set Xorg keyboard model + QProcess::execute( "setxkbmap", QStringList{ "-model", m_selectedModel } ); + emit prettyStatusChanged(); + } ); + + connect( m_keyboardLayoutsModel, &KeyboardLayoutModel::currentIndexChanged, + [&](int index) + { + m_selectedLayout = m_keyboardLayoutsModel->item(index).first; + updateVariants(QPersistentModelIndex(m_keyboardLayoutsModel->index(index))); + emit prettyStatusChanged(); + } ); + + connect( m_keyboardVariantsModel, &KeyboardVariantsModel::currentIndexChanged, + [&](int index) + { + m_selectedVariant = m_keyboardVariantsModel->item(index)["key"]; + + // Set Xorg keyboard layout + if ( m_setxkbmapTimer.isActive() ) + { + m_setxkbmapTimer.stop(); + m_setxkbmapTimer.disconnect( this ); + } + + connect( &m_setxkbmapTimer, &QTimer::timeout, + this, [=] + { + QProcess::execute( "setxkbmap", xkbmap_args( m_selectedLayout, m_selectedVariant ) ); + cDebug() << "xkbmap selection changed to: " << m_selectedLayout << '-' << m_selectedVariant; + m_setxkbmapTimer.disconnect( this ); + } ); + m_setxkbmapTimer.start( QApplication::keyboardInputInterval() ); + emit prettyStatusChanged(); + } ); +} + +KeyboardModelsModel* +Config::keyboardModels() const +{ + return m_keyboardModelsModel; +} + +KeyboardLayoutModel* +Config::keyboardLayouts() const +{ + return m_keyboardLayoutsModel; +} + +KeyboardVariantsModel* +Config::keyboardVariants() const +{ + return m_keyboardVariantsModel; +} + +static QPersistentModelIndex +findLayout( const KeyboardLayoutModel* klm, const QString& currentLayout ) +{ + QPersistentModelIndex currentLayoutItem; + + for ( int i = 0; i < klm->rowCount(); ++i ) + { + QModelIndex idx = klm->index( i ); + if ( idx.isValid() && + idx.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString() == currentLayout ) + currentLayoutItem = idx; + } + + return currentLayoutItem; +} + +void +Config::init() +{ + //### Detect current keyboard layout and variant + QString currentLayout; + QString currentVariant; + QProcess process; + process.start( "setxkbmap", QStringList() << "-print" ); + + if ( process.waitForFinished() ) + { + const QStringList list = QString( process.readAll() ) + .split( "\n", QString::SkipEmptyParts ); + + for ( QString line : list ) + { + line = line.trimmed(); + if ( !line.startsWith( "xkb_symbols" ) ) + continue; + + line = line.remove( "}" ) + .remove( "{" ) + .remove( ";" ); + line = line.mid( line.indexOf( "\"" ) + 1 ); + + QStringList split = line.split( "+", QString::SkipEmptyParts ); + if ( split.size() >= 2 ) + { + currentLayout = split.at( 1 ); + + if ( currentLayout.contains( "(" ) ) + { + int parenthesisIndex = currentLayout.indexOf( "(" ); + currentVariant = currentLayout.mid( parenthesisIndex + 1 ) + .trimmed(); + currentVariant.chop( 1 ); + currentLayout = currentLayout + .mid( 0, parenthesisIndex ) + .trimmed(); + } + + break; + } + } + } + + //### Layouts and Variants + QPersistentModelIndex currentLayoutItem = findLayout( m_keyboardLayoutsModel, currentLayout ); + if ( !currentLayoutItem.isValid() && ( + ( currentLayout == "latin" ) + || ( currentLayout == "pc" ) ) ) + { + currentLayout = "us"; + currentLayoutItem = findLayout( m_keyboardLayoutsModel, currentLayout ); + } + + // Set current layout and variant + if ( currentLayoutItem.isValid() ) + { + m_keyboardLayoutsModel->setCurrentIndex( currentLayoutItem.row() ); + updateVariants( currentLayoutItem, currentVariant ); + } + + // Default to the first available layout if none was set + // Do this after unblocking signals so we get the default variant handling. + if ( !currentLayoutItem.isValid() && m_keyboardLayoutsModel->rowCount() > 0 ) + m_keyboardLayoutsModel->setCurrentIndex( m_keyboardLayoutsModel->index( 0 ).row() ); +} + +QString +Config::prettyStatus() const +{ + QString status; + status += tr( "Set keyboard model to %1.
" ).arg( m_keyboardModelsModel->item(m_keyboardModelsModel->currentIndex())["label"] ); + + QString layout = m_keyboardLayoutsModel->item(m_keyboardLayoutsModel->currentIndex()).second.description ; + QString variant = m_keyboardVariantsModel->currentIndex() >= 0 ? m_keyboardVariantsModel->item(m_keyboardVariantsModel->currentIndex())["label"] : QString( "" ); + status += tr( "Set keyboard layout to %1/%2." ).arg( layout, variant ); + + return status; +} + +Calamares::JobList +Config::createJobs(const QString& xOrgConfFileName, const QString& convertedKeymapPath, bool writeEtcDefaultKeyboard) +{ + QList< Calamares::job_ptr > list; + + Calamares::Job* j = new SetKeyboardLayoutJob( m_selectedModel, + m_selectedLayout, + m_selectedVariant, + xOrgConfFileName, + convertedKeymapPath, + writeEtcDefaultKeyboard ); + list.append( Calamares::job_ptr( j ) ); + + return list; +} + +void +Config::guessLayout(const QStringList& langParts) +{ + bool foundCountryPart = false; + for ( auto countryPart = langParts.rbegin(); !foundCountryPart && countryPart != langParts.rend(); ++countryPart ) + { + cDebug() << Logger::SubEntry << "looking for locale part" << *countryPart; + for ( int i = 0; i < m_keyboardLayoutsModel->rowCount(); ++i ) + { + QModelIndex idx = m_keyboardLayoutsModel->index( i ); + QString name = idx.isValid() ? idx.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString() : QString(); + if ( idx.isValid() && ( name.compare( *countryPart, Qt::CaseInsensitive ) == 0 ) ) + { + cDebug() << Logger::SubEntry << "matched" << name; + m_keyboardLayoutsModel->setCurrentIndex( i ); + foundCountryPart = true; + break; + } + } + if ( foundCountryPart ) + { + ++countryPart; + if ( countryPart != langParts.rend() ) + { + cDebug() << "Next level:" << *countryPart; + for (int variantnumber = 0; variantnumber < m_keyboardVariantsModel->rowCount(); ++variantnumber) + { + if ( m_keyboardVariantsModel->item(variantnumber)["key"].compare( *countryPart, Qt::CaseInsensitive ) ) + { + m_keyboardVariantsModel->setCurrentIndex( variantnumber ); + cDebug() << Logger::SubEntry << "matched variant" << m_keyboardVariantsModel->item(variantnumber)["key"] << ' ' <item(variantnumber)["key"]; + } + } + } + } + } +} + +void +Config::onActivate() +{ + /* Guessing a keyboard layout based on the locale means + * mapping between language identifiers in _ + * format to keyboard mappings, which are _ + * format; in addition, some countries have multiple languages, + * so fr_BE and nl_BE want different layouts (both Belgian) + * and sometimes the language-country name doesn't match the + * keyboard-country name at all (e.g. Ellas vs. Greek). + * + * This is a table of language-to-keyboard mappings. The + * language identifier is the key, while the value is + * a string that is used instead of the real language + * identifier in guessing -- so it should be something + * like _. + */ + static constexpr char arabic[] = "ara"; + static const auto specialCaseMap = QMap( { + /* Most Arab countries map to Arabic keyboard (Default) */ + { "ar_AE", arabic }, + { "ar_BH", arabic }, + { "ar_DZ", arabic }, + { "ar_EG", arabic }, + { "ar_IN", arabic }, + { "ar_IQ", arabic }, + { "ar_JO", arabic }, + { "ar_KW", arabic }, + { "ar_LB", arabic }, + { "ar_LY", arabic }, + /* Not Morocco: use layout ma */ + { "ar_OM", arabic }, + { "ar_QA", arabic }, + { "ar_SA", arabic }, + { "ar_SD", arabic }, + { "ar_SS", arabic }, + /* Not Syria: use layout sy */ + { "ar_TN", arabic }, + { "ar_YE", arabic }, + { "ca_ES", "cat_ES" }, /* Catalan */ + { "as_ES", "ast_ES" }, /* Asturian */ + { "en_CA", "eng_CA" }, /* Canadian English */ + { "el_CY", "gr" }, /* Greek in Cyprus */ + { "el_GR", "gr" }, /* Greek in Greeze */ + { "ig_NG", "igbo_NG" }, /* Igbo in Nigeria */ + { "ha_NG", "hausa_NG" } /* Hausa */ + } ); + + // Try to preselect a layout, depending on language and locale + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + QString lang = gs->value( "localeConf" ).toMap().value( "LANG" ).toString(); + + cDebug() << "Got locale language" << lang; + if ( !lang.isEmpty() ) + { + // Chop off .codeset and @modifier + int index = lang.indexOf( '.' ); + if ( index >= 0 ) + lang.truncate( index ); + index = lang.indexOf( '@' ); + if ( index >= 0 ) + lang.truncate( index ); + + lang.replace( '-', '_' ); // Normalize separators + } + if ( !lang.isEmpty() ) + { + std::string lang_s = lang.toStdString(); + if ( specialCaseMap.contains( lang_s ) ) + { + QString newLang = QString::fromStdString( specialCaseMap.value( lang_s ) ); + cDebug() << Logger::SubEntry << "special case language" << lang << "becomes" << newLang; + lang = newLang; + } + } + if ( !lang.isEmpty() ) + { + const auto langParts = lang.split( '_', QString::SkipEmptyParts ); + + // Note that this his string is not fit for display purposes! + // It doesn't come from QLocale::nativeCountryName. + QString country = QLocale::countryToString( QLocale( lang ).country() ); + cDebug() << Logger::SubEntry << "extracted country" << country << "::" << langParts; + + guessLayout( langParts ); + } +} + +void +Config::finalize() +{ + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( !m_selectedLayout.isEmpty() ) + { + gs->insert( "keyboardLayout", m_selectedLayout ); + gs->insert( "keyboardVariant", m_selectedVariant ); //empty means default variant + } + + //FIXME: also store keyboard model for something? +} + +void +Config::updateVariants(const QPersistentModelIndex& currentItem, QString currentVariant) +{ + const auto variants = m_keyboardLayoutsModel->item(currentItem.row()).second.variants; + m_keyboardVariantsModel->setVariants(variants); + + auto index = -1; + for(const auto &key : variants.keys()) + { + index++; + if(variants[key] == currentVariant) + { + m_keyboardVariantsModel->setCurrentIndex(index); + return; + } + } +} + diff --git a/src/modules/keyboard/Config.h b/src/modules/keyboard/Config.h new file mode 100644 index 000000000..bb1447cda --- /dev/null +++ b/src/modules/keyboard/Config.h @@ -0,0 +1,133 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019-2020, Adriaan de Groot + * + * Calamares 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 3 of the License, or + * (at your option) any later version. + * + * Calamares 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 Calamares. If not, see . + */ + +#ifndef KEYBOARD_CONFIG_H +#define KEYBOARD_CONFIG_H + +#include +#include +#include +#include +#include "Job.h" +#include +#include "KeyboardLayoutModel.h" + +class KeyboardModelsModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int currentIndex WRITE setCurrentIndex READ currentIndex NOTIFY currentIndexChanged) + +public: + explicit KeyboardModelsModel(QObject *parent = nullptr); + int rowCount(const QModelIndex& = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role) const override; + + void setCurrentIndex(const int &index); + int currentIndex() const; + const QMap item(const int &index) const; + +public slots: + void refresh(); + +protected: + QHash roleNames() const override; + +private: + int m_currentIndex =-1; + QVector> m_list; + void detectModels(); + +signals: + void currentIndexChanged(int index); +}; + +class KeyboardVariantsModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int currentIndex WRITE setCurrentIndex READ currentIndex NOTIFY currentIndexChanged) + +public: + explicit KeyboardVariantsModel(QObject *parent = nullptr); + void setVariants(QMap< QString, QString > variants); + + int rowCount(const QModelIndex& = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role) const override; + + void setCurrentIndex(const int &index); + int currentIndex() const; + + const QMap item(const int &index) const; + +protected: + QHash roleNames() const override; + +private: + int m_currentIndex =-1; + QVector> m_list; + +signals: + void currentIndexChanged(int index); +}; + +class Config : public QObject +{ + Q_OBJECT + Q_PROPERTY(KeyboardModelsModel * keyboardModelsModel READ keyboardModels CONSTANT FINAL) + Q_PROPERTY(KeyboardLayoutModel * keyboardLayoutsModel READ keyboardLayouts CONSTANT FINAL) + Q_PROPERTY(KeyboardVariantsModel * keyboardVariantsModel READ keyboardVariants CONSTANT FINAL) + Q_PROPERTY(QString prettyStatus READ prettyStatus NOTIFY prettyStatusChanged FINAL) + +public: + Config( QObject* parent = nullptr ); + + void init(); + + Calamares::JobList createJobs( const QString& xOrgConfFileName, + const QString& convertedKeymapPath, + bool writeEtcDefaultKeyboard ); + QString prettyStatus() const; + + void onActivate(); + void finalize(); + +private: + void guessLayout( const QStringList& langParts ); + void updateVariants( const QPersistentModelIndex& currentItem, + QString currentVariant = QString() ); + + KeyboardModelsModel *m_keyboardModelsModel; + KeyboardLayoutModel *m_keyboardLayoutsModel; + KeyboardVariantsModel *m_keyboardVariantsModel; + + QString m_selectedLayout; + QString m_selectedModel; + QString m_selectedVariant; + QTimer m_setxkbmapTimer; + +protected: + KeyboardModelsModel * keyboardModels() const; + KeyboardLayoutModel * keyboardLayouts() const; + KeyboardVariantsModel * keyboardVariants() const; + + +signals: + void prettyStatusChanged(); +}; + + +#endif diff --git a/src/modules/keyboard/KeyboardViewStep.cpp b/src/modules/keyboard/KeyboardViewStep.cpp index 472ffbe7e..423d795c0 100644 --- a/src/modules/keyboard/KeyboardViewStep.cpp +++ b/src/modules/keyboard/KeyboardViewStep.cpp @@ -23,6 +23,8 @@ #include "GlobalStorage.h" #include "JobQueue.h" +#include "utils/Variant.h" + CALAMARES_PLUGIN_FACTORY_DEFINITION( KeyboardViewStepFactory, registerPlugin< KeyboardViewStep >(); ) KeyboardViewStep::KeyboardViewStep( QObject* parent ) @@ -121,35 +123,29 @@ KeyboardViewStep::onLeave() void KeyboardViewStep::setConfigurationMap( const QVariantMap& configurationMap ) { - if ( configurationMap.contains( "xOrgConfFileName" ) - && configurationMap.value( "xOrgConfFileName" ).type() == QVariant::String - && !configurationMap.value( "xOrgConfFileName" ).toString().isEmpty() ) + using namespace CalamaresUtils; + + if ( configurationMap.contains( "xOrgConfFileName" ) && + configurationMap.value( "xOrgConfFileName" ).type() == QVariant::String && + !getString( configurationMap, "xOrgConfFileName" ).isEmpty() ) { - m_xOrgConfFileName = configurationMap.value( "xOrgConfFileName" ).toString(); + m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName" ); } else - { m_xOrgConfFileName = "00-keyboard.conf"; - } - if ( configurationMap.contains( "convertedKeymapPath" ) - && configurationMap.value( "convertedKeymapPath" ).type() == QVariant::String - && !configurationMap.value( "convertedKeymapPath" ).toString().isEmpty() ) + if ( configurationMap.contains( "convertedKeymapPath" ) && + configurationMap.value( "convertedKeymapPath" ).type() == QVariant::String && + !getString( configurationMap, "convertedKeymapPath" ).isEmpty() ) { - m_convertedKeymapPath = configurationMap.value( "convertedKeymapPath" ).toString(); + m_convertedKeymapPath = getString( configurationMap, "convertedKeymapPath" ); } else - { m_convertedKeymapPath = QString(); - } - if ( configurationMap.contains( "writeEtcDefaultKeyboard" ) - && configurationMap.value( "writeEtcDefaultKeyboard" ).type() == QVariant::Bool ) - { - m_writeEtcDefaultKeyboard = configurationMap.value( "writeEtcDefaultKeyboard" ).toBool(); - } + if ( configurationMap.contains( "writeEtcDefaultKeyboard" ) && + configurationMap.value( "writeEtcDefaultKeyboard" ).type() == QVariant::Bool ) + m_writeEtcDefaultKeyboard = getBool( configurationMap, "writeEtcDefaultKeyboard", true); else - { m_writeEtcDefaultKeyboard = true; - } } diff --git a/src/modules/keyboardq/CMakeLists.txt b/src/modules/keyboardq/CMakeLists.txt new file mode 100644 index 000000000..f2565fc1c --- /dev/null +++ b/src/modules/keyboardq/CMakeLists.txt @@ -0,0 +1,19 @@ +set( _keyboard ${CMAKE_CURRENT_SOURCE_DIR}/../keyboard ) + +include_directories( ${_keyboard} ) + +calamares_add_plugin( keyboardq + TYPE viewmodule + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + KeyboardQmlViewStep.cpp + ${_keyboard}/Config.cpp + ${_keyboard}/KeyboardLayoutModel.cpp + ${_keyboard}/SetKeyboardLayoutJob.cpp + ${_keyboard}/keyboardwidget/keyboardglobal.cpp + RESOURCES + ${_keyboard}/keyboard.qrc + LINK_PRIVATE_LIBRARIES + calamaresui + SHARED_LIB +) diff --git a/src/modules/keyboardq/KeyboardQmlViewStep.cpp b/src/modules/keyboardq/KeyboardQmlViewStep.cpp new file mode 100644 index 000000000..ba1e4c618 --- /dev/null +++ b/src/modules/keyboardq/KeyboardQmlViewStep.cpp @@ -0,0 +1,133 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2015, Teo Mrnjavac + * Copyright 2020, Camilo Higuita + * + * Calamares 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 3 of the License, or + * (at your option) any later version. + * + * Calamares 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 Calamares. If not, see . + */ + +#include "KeyboardQmlViewStep.h" + +#include "JobQueue.h" +#include "GlobalStorage.h" +#include "utils/Variant.h" + +CALAMARES_PLUGIN_FACTORY_DEFINITION( KeyboardQmlViewStepFactory, registerPlugin(); ) + +KeyboardQmlViewStep::KeyboardQmlViewStep( QObject* parent ) + : Calamares::QmlViewStep(parent ) + , m_config( new Config(this) ) + , m_nextEnabled( false ) + , m_writeEtcDefaultKeyboard( true ) +{ + m_config->init(); + m_nextEnabled = true; + emit nextStatusChanged( m_nextEnabled ); +} + +QString +KeyboardQmlViewStep::prettyName() const +{ + return tr( "Keyboard" ); +} + +QString +KeyboardQmlViewStep::prettyStatus() const +{ + return m_prettyStatus; +} + +bool +KeyboardQmlViewStep::isNextEnabled() const +{ + return m_nextEnabled; +} + +bool +KeyboardQmlViewStep::isBackEnabled() const +{ + return true; +} + +bool +KeyboardQmlViewStep::isAtBeginning() const +{ + return true; +} + +bool +KeyboardQmlViewStep::isAtEnd() const +{ + return true; +} + +Calamares::JobList +KeyboardQmlViewStep::jobs() const +{ + return m_jobs; +} + +void +KeyboardQmlViewStep::onActivate() +{ + m_config->onActivate(); +} + +void +KeyboardQmlViewStep::onLeave() +{ + m_config->finalize(); + m_jobs = m_config->createJobs( m_xOrgConfFileName, + m_convertedKeymapPath, + m_writeEtcDefaultKeyboard ); + m_prettyStatus = m_config->prettyStatus(); +} + +QObject * +KeyboardQmlViewStep::getConfig() +{ + return m_config; +} + +void +KeyboardQmlViewStep::setConfigurationMap( const QVariantMap& configurationMap ) +{ + using namespace CalamaresUtils; + + if ( configurationMap.contains( "xOrgConfFileName" ) && + configurationMap.value( "xOrgConfFileName" ).type() == QVariant::String && + !getString( configurationMap, "xOrgConfFileName" ).isEmpty() ) + { + m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName" ); + } + else + m_xOrgConfFileName = "00-keyboard.conf"; + + if ( configurationMap.contains( "convertedKeymapPath" ) && + configurationMap.value( "convertedKeymapPath" ).type() == QVariant::String && + !getString( configurationMap, "convertedKeymapPath" ).isEmpty() ) + { + m_convertedKeymapPath = getString( configurationMap, "convertedKeymapPath" ); + } + else + m_convertedKeymapPath = QString(); + + if ( configurationMap.contains( "writeEtcDefaultKeyboard" ) && + configurationMap.value( "writeEtcDefaultKeyboard" ).type() == QVariant::Bool ) + m_writeEtcDefaultKeyboard = getBool( configurationMap, "writeEtcDefaultKeyboard", true); + else + m_writeEtcDefaultKeyboard = true; + + Calamares::QmlViewStep::setConfigurationMap( configurationMap ); +} diff --git a/src/modules/keyboardq/KeyboardQmlViewStep.h b/src/modules/keyboardq/KeyboardQmlViewStep.h new file mode 100644 index 000000000..b9e430bc8 --- /dev/null +++ b/src/modules/keyboardq/KeyboardQmlViewStep.h @@ -0,0 +1,71 @@ +/* === This file is part of Calamares - === + * + * Copyright 2014-2015, Teo Mrnjavac + * Copyright 2017, Adriaan de Groot + * + * Calamares 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 3 of the License, or + * (at your option) any later version. + * + * Calamares 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 Calamares. If not, see . + */ + +#ifndef KEYBOARDQMLVIEWSTEP_H +#define KEYBOARDQMLVIEWSTEP_H + +#include "Config.h" + +#include +#include +#include + +#include + +class KeyboardPage; + +class PLUGINDLLEXPORT KeyboardQmlViewStep : public Calamares::QmlViewStep +{ + Q_OBJECT + +public: + explicit KeyboardQmlViewStep( QObject* parent = nullptr ); + + QString prettyName() const override; + QString prettyStatus() const override; + + bool isNextEnabled() const override; + bool isBackEnabled() const override; + + bool isAtBeginning() const override; + bool isAtEnd() const override; + + Calamares::JobList jobs() const override; + + void onActivate() override; + void onLeave() override; + + void setConfigurationMap( const QVariantMap& configurationMap ) override; + QObject* getConfig() override; + +private: + Config * m_config; + bool m_nextEnabled; + QString m_prettyStatus; + + QString m_xOrgConfFileName; + QString m_convertedKeymapPath; + bool m_writeEtcDefaultKeyboard; + + Calamares::JobList m_jobs; +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( KeyboardQmlViewStepFactory ) + +#endif // KEYBOARDQMLVIEWSTEP_H diff --git a/src/modules/keyboardq/keyboard.qml b/src/modules/keyboardq/keyboard.qml new file mode 100644 index 000000000..a093fe31f --- /dev/null +++ b/src/modules/keyboardq/keyboard.qml @@ -0,0 +1,204 @@ +import io.calamares.modules 1.0 as Modules +import io.calamares.ui 1.0 + +import QtQuick 2.10 +import QtQuick.Controls 2.10 +import QtQuick.Layouts 1.3 +import org.kde.kirigami 2.7 as Kirigami + +ResponsiveBase +{ + id: control + Modules.Keyboard //locale handler + { + id: _keyboard + } + + title: stackView.currentItem.title + subtitle: stackView.currentItem.subtitle + + + stackView.initialItem: Item + { + id: _keyboardModelsComponet + + property string title: qsTr("Keyboard Model") + property string subtitle: qsTr("Pick your preferred keyboard model or use the default one based on the detected hardware") + + ListViewTemplate + { + id: _keyboardModelListView + + anchors.centerIn: parent + implicitWidth: Math.min(parent.width, 500) + implicitHeight: Math.min(contentHeight, 500) + currentIndex: model.currentIndex + + header: ToolButton + { + icon.name: "view-refresh" + onClicked: model.refresh() + text: qsTr("Refresh") + } + footer: RowLayout + { + width: parent.width + z: 99999 + + Button + { + Layout.fillWidth: true + text: qsTr("Layouts") + icon.name: "go-previous" + onClicked: control.stackView.push(_keyboardLayoutsComponent) + } + } + + model: _keyboard.Config.keyboardModelsModel + + delegate: ListItemDelegate + { + id: _delegate + label1.text: model.label + onClicked: + { + _keyboardModelListView.model.currentIndex = index + control.stackView.push(_keyboardLayoutsComponent) + } + } + } + + } + + Component + { + id: _keyboardLayoutsComponent + + Item + { + property string title: qsTr("Keyboard Layout") + property string subtitle: _keyboard.Config.prettyStatus + + ListViewTemplate + { + id: _layoutsListView + + anchors.centerIn: parent + + implicitWidth: Math.min(parent.width, 500) + implicitHeight: Math.min(contentHeight, 500) + + currentIndex: model.currentIndex + footer: RowLayout + { + width: parent.width + z: 99999 + + Button + { + Layout.fillWidth: true + icon.name: "go-previous" + text: qsTr("Models") + onClicked: control.stackView.pop() + } + + Button + { + Layout.fillWidth: true + icon.name: "go-next" + text: qsTr("Variants") + onClicked: control.stackView.push(_keyboardVariantsComponent) + } + } + + model: _keyboard.Config.keyboardLayoutsModel + + delegate: ListItemDelegate + { + id: _delegate + label1.text: model.label + onClicked: + { + _layoutsListView.model.currentIndex = index + _layoutsListView.positionViewAtIndex(index, ListView.Center) + control.stackView.push(_keyboardVariantsComponent) + } + } + } + } + + + } + + Component + { + id: _keyboardVariantsComponent + + Item + { + property string title: qsTr("Keyboard Layout") + property string subtitle: _keyboard.Config.prettyStatus + + ListViewTemplate + { + id: _variantsListView + + anchors.centerIn: parent + + implicitWidth: Math.min(parent.width, 500) + implicitHeight: Math.min(contentHeight, 500) + + currentIndex: model.currentIndex + + footerPositioning: ListView.OverlayFooter + + footer: RowLayout + { + z: 99999 + width: parent.width + + Button + { + Layout.fillWidth: true + text: qsTr("Layouts") + icon.name: "go-previous" + onClicked: control.stackView.pop() + } + } + + model: _keyboard.Config.keyboardVariantsModel + + delegate: ListItemDelegate + { + id: _delegate + label1.text: model.label + onClicked: + { + _variantsListView.model.currentIndex = index + _variantsListView.positionViewAtIndex(index, ListView.Center) + } + } + } + } + + } + + + TextField + { + placeholderText: qsTr("Test your keyboard") + Layout.preferredHeight: 60 + Layout.maximumWidth: 500 + Layout.fillWidth: true + Layout.alignment: Qt.AlignCenter + + background: Rectangle + { + color: Kirigami.Theme.backgroundColor + radius: 5 + opacity: 0.3 + } + } + + +} diff --git a/src/modules/keyboardq/keyboardq.conf b/src/modules/keyboardq/keyboardq.conf new file mode 100644 index 000000000..ee97c3939 --- /dev/null +++ b/src/modules/keyboardq/keyboardq.conf @@ -0,0 +1,16 @@ +# NOTE: you must have ckbcomp installed and runnable +# on the live system, for keyboard layout previews. +--- +# The name of the file to write X11 keyboard settings to +# The default value is the name used by upstream systemd-localed. +# Relative paths are assumed to be relative to /etc/X11/xorg.conf.d +xOrgConfFileName: "/etc/X11/xorg.conf.d/00-keyboard.conf" + +# The path to search for keymaps converted from X11 to kbd format +# Leave this empty if the setting does not make sense on your distribution. +convertedKeymapPath: "/lib/kbd/keymaps/xkb" + +# Write keymap configuration to /etc/default/keyboard, usually +# found on Debian-related systems. +# Defaults to true if nothing is set. +#writeEtcDefaultKeyboard: true