From 1ac3459afa8be34749ef44e125b54e1b9e1f69b5 Mon Sep 17 00:00:00 2001 From: Ivan Borzenkov Date: Sat, 8 Jul 2023 22:59:33 +0300 Subject: [PATCH] add keyboard layout switch selector --- src/modules/keyboard/Config.cpp | 42 ++++++++++++-- src/modules/keyboard/Config.h | 8 ++- src/modules/keyboard/KeyboardData_p.cxxtr | 55 +++++++++++++++++++ src/modules/keyboard/KeyboardLayoutModel.cpp | 20 +++++++ src/modules/keyboard/KeyboardLayoutModel.h | 14 +++++ src/modules/keyboard/KeyboardPage.cpp | 16 ++++++ src/modules/keyboard/KeyboardPage.ui | 25 +++++++++ .../keyboardwidget/keyboardglobal.cpp | 48 ++++++++++++++++ .../keyboard/keyboardwidget/keyboardglobal.h | 2 + src/modules/keyboard/layout-extractor.py | 19 ++++++- 10 files changed, 239 insertions(+), 10 deletions(-) diff --git a/src/modules/keyboard/Config.cpp b/src/modules/keyboard/Config.cpp index 2eca1ffb5..143d55d69 100644 --- a/src/modules/keyboard/Config.cpp +++ b/src/modules/keyboard/Config.cpp @@ -160,6 +160,7 @@ Config::Config( QObject* parent ) , m_keyboardModelsModel( new KeyboardModelsModel( this ) ) , m_keyboardLayoutsModel( new KeyboardLayoutModel( this ) ) , m_keyboardVariantsModel( new KeyboardVariantsModel( this ) ) + , m_keyboardGroupsModel( new KeyboardGroupsModel( this ) ) { m_setxkbmapTimer.setSingleShot( true ); @@ -190,25 +191,40 @@ Config::Config( QObject* parent ) emit prettyStatusChanged(); } ); - connect( m_keyboardVariantsModel, &KeyboardVariantsModel::currentIndexChanged, this, &Config::xkbChanged ); + connect( m_keyboardVariantsModel, + &KeyboardVariantsModel::currentIndexChanged, + [ & ]( int index ) + { + m_selectedVariant = m_keyboardVariantsModel->key( index ); + Config::xkbChanged(); + emit prettyStatusChanged(); + } ); + connect( m_keyboardGroupsModel, + &KeyboardGroupsModel::currentIndexChanged, + [ & ]( int index ) + { + m_selectedGroup = m_keyboardGroupsModel->key( index ); + Config::xkbChanged(); + emit prettyStatusChanged(); + } ); // If the user picks something explicitly -- not a consequence of // a guess -- then move to UserSelected state and stay there. connect( m_keyboardModelsModel, &KeyboardModelsModel::currentIndexChanged, this, &Config::selectionChange ); connect( m_keyboardLayoutsModel, &KeyboardLayoutModel::currentIndexChanged, this, &Config::selectionChange ); connect( m_keyboardVariantsModel, &KeyboardVariantsModel::currentIndexChanged, this, &Config::selectionChange ); + connect( m_keyboardGroupsModel, &KeyboardGroupsModel::currentIndexChanged, this, &Config::selectionChange ); m_selectedModel = m_keyboardModelsModel->key( m_keyboardModelsModel->currentIndex() ); m_selectedLayout = m_keyboardLayoutsModel->item( m_keyboardLayoutsModel->currentIndex() ).first; m_selectedVariant = m_keyboardVariantsModel->key( m_keyboardVariantsModel->currentIndex() ); + m_selectedGroup = m_keyboardGroupsModel->key( m_keyboardGroupsModel->currentIndex() ); } void -Config::xkbChanged( int index ) +Config::xkbChanged() { // Set Xorg keyboard layout + variant - m_selectedVariant = m_keyboardVariantsModel->key( index ); - if ( m_setxkbmapTimer.isActive() ) { m_setxkbmapTimer.stop(); @@ -271,8 +287,15 @@ Config::xkbApply() if ( !m_additionalLayoutInfo.additionalLayout.isEmpty() ) { - m_additionalLayoutInfo.groupSwitcher = xkbmap_query_grp_option(); + if ( !m_selectedGroup.isEmpty() ) + { + m_additionalLayoutInfo.groupSwitcher = "grp:" + m_selectedGroup; + } + if ( m_additionalLayoutInfo.groupSwitcher.isEmpty() ) + { + m_additionalLayoutInfo.groupSwitcher = xkbmap_query_grp_option(); + } if ( m_additionalLayoutInfo.groupSwitcher.isEmpty() ) { m_additionalLayoutInfo.groupSwitcher = "grp:alt_shift_toggle"; @@ -315,6 +338,12 @@ Config::keyboardVariants() const return m_keyboardVariantsModel; } +KeyboardGroupsModel* +Config::keyboardGroups() const +{ + return m_keyboardGroupsModel; +} + static QPersistentModelIndex findLayout( const KeyboardLayoutModel* klm, const QString& currentLayout ) { @@ -647,7 +676,8 @@ Config::finalize() if ( !m_additionalLayoutInfo.additionalLayout.isEmpty() ) { gs->insert( "keyboardAdditionalLayout", m_additionalLayoutInfo.additionalLayout ); - gs->insert( "keyboardAdditionalLayout", m_additionalLayoutInfo.additionalVariant ); + gs->insert( "keyboardAdditionalVariant", m_additionalLayoutInfo.additionalVariant ); + gs->insert( "keyboardGroupSwitcher", m_additionalLayoutInfo.groupSwitcher ); gs->insert( "keyboardVConsoleKeymap", m_additionalLayoutInfo.vconsoleKeymap ); } } diff --git a/src/modules/keyboard/Config.h b/src/modules/keyboard/Config.h index 7f72826f5..c03cbcecb 100644 --- a/src/modules/keyboard/Config.h +++ b/src/modules/keyboard/Config.h @@ -27,6 +27,7 @@ class Config : public QObject 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( KeyboardGroupsModel* keyboardGroupsModel READ keyboardGroups CONSTANT FINAL ) Q_PROPERTY( QString prettyStatus READ prettyStatus NOTIFY prettyStatusChanged FINAL ) public: @@ -58,6 +59,9 @@ public: * (dvorak). */ KeyboardVariantsModel* keyboardVariants() const; + /* A group describes a toggle groups of change layouts + */ + KeyboardGroupsModel* keyboardGroups() const; /** @brief Call this to change application language * @@ -87,7 +91,7 @@ private: * xkbChanged() is called when the selection changes, and triggers * a delayed call to xkbApply() which does the actual work. */ - void xkbChanged( int index ); + void xkbChanged(); void xkbApply(); void locale1Apply(); @@ -97,10 +101,12 @@ private: KeyboardModelsModel* m_keyboardModelsModel; KeyboardLayoutModel* m_keyboardLayoutsModel; KeyboardVariantsModel* m_keyboardVariantsModel; + KeyboardGroupsModel* m_keyboardGroupsModel; QString m_selectedLayout; QString m_selectedModel; QString m_selectedVariant; + QString m_selectedGroup; // Layout (and corresponding info) added if current one doesn't support ASCII (e.g. Russian or Japanese) AdditionalLayoutInfo m_additionalLayoutInfo; diff --git a/src/modules/keyboard/KeyboardData_p.cxxtr b/src/modules/keyboard/KeyboardData_p.cxxtr index 39783035a..20675367a 100644 --- a/src/modules/keyboard/KeyboardData_p.cxxtr +++ b/src/modules/keyboard/KeyboardData_p.cxxtr @@ -823,3 +823,58 @@ public: } } +/* This returns a reference to local, which is a terrible idea. + * Good thing it's not meant to be compiled. + */ +class kb_groups : public QObject { +Q_OBJECT +public: + const QStringList& table() + { + return QStringList { + tr("Alt+Caps Lock", "kb_group"), + tr("Alt+Ctrl", "kb_group"), + tr("Alt+Shift", "kb_group"), + tr("Alt+Space", "kb_group"), + tr("Any Win (while pressed)", "kb_group"), + tr("Both Alts together", "kb_group"), + tr("Both Alts together; AltGr alone chooses third level", "kb_group"), + tr("Both Ctrls together", "kb_group"), + tr("Both Shifts together", "kb_group"), + tr("Caps Lock", "kb_group"), + tr("Caps Lock (while pressed), Alt+Caps Lock for the original Caps Lock action", "kb_group"), + tr("Caps Lock to first layout; Shift+Caps Lock to second layout", "kb_group"), + tr("Ctrl+Left Win to first layout; Ctrl+Menu to second layout", "kb_group"), + tr("Ctrl+Shift", "kb_group"), + tr("Ctrl+Space", "kb_group"), + tr("Left Alt", "kb_group"), + tr("Left Alt (while pressed)", "kb_group"), + tr("Left Alt+Left Shift", "kb_group"), + tr("Left Ctrl", "kb_group"), + tr("Left Ctrl to first layout; Right Ctrl to second layout", "kb_group"), + tr("Left Ctrl+Left Shift", "kb_group"), + tr("Left Ctrl+Left Win", "kb_group"), + tr("Left Shift", "kb_group"), + tr("Left Win", "kb_group"), + tr("Left Win (while pressed)", "kb_group"), + tr("Left Win to first layout; Right Win/Menu to second layout", "kb_group"), + tr("Menu", "kb_group"), + tr("Menu (while pressed), Shift+Menu for Menu", "kb_group"), + tr("None", "kb_group"), + tr("Right Alt", "kb_group"), + tr("Right Alt (while pressed)", "kb_group"), + tr("Right Alt+Right Shift", "kb_group"), + tr("Right Ctrl", "kb_group"), + tr("Right Ctrl (while pressed)", "kb_group"), + tr("Right Ctrl+Right Shift", "kb_group"), + tr("Right Shift", "kb_group"), + tr("Right Win", "kb_group"), + tr("Right Win (while pressed)", "kb_group"), + tr("Scroll Lock", "kb_group"), + tr("Shift+Caps Lock", "kb_group"), + tr("Win+Space", "kb_group"), + QString() + }; +} +} + diff --git a/src/modules/keyboard/KeyboardLayoutModel.cpp b/src/modules/keyboard/KeyboardLayoutModel.cpp index ed171a476..a52927e27 100644 --- a/src/modules/keyboard/KeyboardLayoutModel.cpp +++ b/src/modules/keyboard/KeyboardLayoutModel.cpp @@ -272,3 +272,23 @@ KeyboardVariantsModel::setVariants( QMap< QString, QString > variants ) m_currentIndex = -1; endResetModel(); } + +KeyboardGroupsModel::KeyboardGroupsModel( QObject* parent ) + : XKBListModel( parent ) +{ + m_contextname = "kb_groups"; + + // The groups map is from human-readable names (!) to xkb identifier + const auto groups = KeyboardGlobal::getKeyboardGroups(); + m_list.reserve( groups.count() ); + int index = 0; + for ( const auto& key : groups.keys() ) + { + // So here *key* is the key in the map, which is the human-readable thing, + // while the struct fields are xkb-id, and human-readable + m_list << ModelInfo { groups[ key ], key }; + index++; + } + + cDebug() << "Loaded" << m_list.count() << "keyboard groups"; +} diff --git a/src/modules/keyboard/KeyboardLayoutModel.h b/src/modules/keyboard/KeyboardLayoutModel.h index 1fd6a7819..1dc925b33 100644 --- a/src/modules/keyboard/KeyboardLayoutModel.h +++ b/src/modules/keyboard/KeyboardLayoutModel.h @@ -154,6 +154,20 @@ public: void setVariants( QMap< QString, QString > variants ); }; +/** @brief A list of variants (xkb id and human-readable) + * + * The variants that are available depend on the Layout that is used, + * so the `setVariants()` function can be used to update the variants + * when the two models are related. + */ +class KeyboardGroupsModel : public XKBListModel +{ + Q_OBJECT + +public: + explicit KeyboardGroupsModel( QObject* parent = nullptr ); +}; + /** @brief Adjust to changes in application language. */ void retranslateKeyboardModels(); diff --git a/src/modules/keyboard/KeyboardPage.cpp b/src/modules/keyboard/KeyboardPage.cpp index c821c4633..1a4251e53 100644 --- a/src/modules/keyboard/KeyboardPage.cpp +++ b/src/modules/keyboard/KeyboardPage.cpp @@ -68,6 +68,12 @@ KeyboardPage::KeyboardPage( Config* config, QWidget* parent ) ui->variantSelector->setCurrentIndex( model->index( model->currentIndex() ) ); cDebug() << "Variants now total=" << model->rowCount() << "selected=" << model->currentIndex(); } + { + auto* model = config->keyboardGroups(); + ui->groupSelector->setModel( model ); + ui->groupSelector->setCurrentIndex( model->currentIndex() ); + cDebug() << "Groups now total=" << model->rowCount() << "selected=" << model->currentIndex(); + } connect( ui->buttonRestore, &QPushButton::clicked, @@ -107,6 +113,16 @@ KeyboardPage::KeyboardPage( Config* config, QWidget* parent ) ui->variantSelector->setCurrentIndex( m_config->keyboardVariants()->index( index ) ); m_keyboardPreview->setVariant( m_config->keyboardVariants()->key( index ) ); } ); + + connect( ui->groupSelector, + QOverload< int >::of( &QComboBox::currentIndexChanged ), + config->keyboardGroups(), + QOverload< int >::of( &XKBListModel::setCurrentIndex ) ); + connect( config->keyboardGroups(), + &KeyboardGroupsModel::currentIndexChanged, + ui->groupSelector, + &QComboBox::setCurrentIndex ); + CALAMARES_RETRANSLATE_SLOT( &KeyboardPage::retranslate ); } diff --git a/src/modules/keyboard/KeyboardPage.ui b/src/modules/keyboard/KeyboardPage.ui index fa54ed7c8..c3c4a2499 100644 --- a/src/modules/keyboard/KeyboardPage.ui +++ b/src/modules/keyboard/KeyboardPage.ui @@ -117,6 +117,30 @@ SPDX-License-Identifier: GPL-3.0-or-later + + + + 0 + + + + + Keyboard Switch: + + + + + + + + 0 + 0 + + + + + + @@ -142,6 +166,7 @@ SPDX-License-Identifier: GPL-3.0-or-later physicalModelSelector layoutSelector variantSelector + groupSelector LE_TestKeyboard buttonRestore diff --git a/src/modules/keyboard/keyboardwidget/keyboardglobal.cpp b/src/modules/keyboard/keyboardwidget/keyboardglobal.cpp index 5dc6de66e..3f6f2ecf4 100644 --- a/src/modules/keyboard/keyboardwidget/keyboardglobal.cpp +++ b/src/modules/keyboard/keyboardwidget/keyboardglobal.cpp @@ -199,6 +199,48 @@ parseKeyboardLayouts( const char* filepath ) return layouts; } +static KeyboardGlobal::GroupsMap +parseKeyboardGroups( const char* filepath ) +{ + KeyboardGlobal::GroupsMap models; + + QFile fh( filepath ); + fh.open( QIODevice::ReadOnly ); + + if ( !fh.isOpen() ) + { + cDebug() << "X11 Keyboard model definitions not found!"; + return models; + } + + bool modelsFound = findSection( fh, "! option" ); + // read the file until the end or until we break the loop + while ( modelsFound && !fh.atEnd() ) + { + QByteArray line = fh.readLine(); + + // check if we start a new section + if ( line.startsWith( '!' ) ) + { + break; + } + + // here we are in the model section, otherwise we would continue or break + QRegExp rx; + rx.setPattern( "^\\s+grp:(\\S+)\\s+(\\w.*)\n$" ); + + // insert into the model map + if ( rx.indexIn( line ) != -1 ) + { + QString modelDesc = rx.cap( 2 ); + QString model = rx.cap( 1 ); + models.insert( modelDesc, model ); + } + } + + return models; +} + KeyboardGlobal::LayoutsMap KeyboardGlobal::getKeyboardLayouts() @@ -212,3 +254,9 @@ KeyboardGlobal::getKeyboardModels() { return parseKeyboardModels( XKB_FILE ); } + +KeyboardGlobal::GroupsMap +KeyboardGlobal::getKeyboardGroups() +{ + return parseKeyboardGroups( XKB_FILE ); +} diff --git a/src/modules/keyboard/keyboardwidget/keyboardglobal.h b/src/modules/keyboard/keyboardwidget/keyboardglobal.h index 2d60bd768..5166f88bc 100644 --- a/src/modules/keyboard/keyboardwidget/keyboardglobal.h +++ b/src/modules/keyboard/keyboardwidget/keyboardglobal.h @@ -30,9 +30,11 @@ public: using LayoutsMap = QMap< QString, KeyboardInfo >; using ModelsMap = QMap< QString, QString >; + using GroupsMap = QMap< QString, QString >; static LayoutsMap getKeyboardLayouts(); static ModelsMap getKeyboardModels(); + static GroupsMap getKeyboardGroups(); }; #endif // KEYBOARDGLOBAL_H diff --git a/src/modules/keyboard/layout-extractor.py b/src/modules/keyboard/layout-extractor.py index 44b0d6b50..0827c844d 100644 --- a/src/modules/keyboard/layout-extractor.py +++ b/src/modules/keyboard/layout-extractor.py @@ -15,14 +15,15 @@ Prints out a few tables of keyboard model, layout, variant names for use in translations. """ -def scrape_file(file, modelsset, layoutsset, variantsset): +def scrape_file(file, modelsset, layoutsset, variantsset, groupsset): import re # These RE's match what is in keyboardglobal.cpp model_re = re.compile("^\\s+(\\S+)\\s+(\\w.*)\n$") layout_re = re.compile("^\\s+(\\S+)\\s+(\\w.*)\n$") variant_re = re.compile("^\\s+(\\S+)\\s+(\\S+): (\\w.*)\n$") + group_re = re.compile("^\\s+grp:(\\S+)\\s+(\\w.*)\n$") - MODEL, LAYOUT, VARIANT = range(3) + MODEL, LAYOUT, VARIANT, GROUP = range(4) state = None for line in file.readlines(): # Handle changes in section @@ -35,6 +36,9 @@ def scrape_file(file, modelsset, layoutsset, variantsset): elif line.startswith("! variant"): state = VARIANT continue + elif line.startswith("! option"): + state = GROUP + continue elif not line.strip(): state = None # Unchanged from last blank @@ -53,6 +57,12 @@ def scrape_file(file, modelsset, layoutsset, variantsset): v = variant_re.match(line) name = v.groups()[2] variantsset.add(name) + if state == GROUP: + v = group_re.match(line) + if v is None: + continue + name = v.groups()[1] + groupsset.add(name) def write_set(file, label, set): @@ -85,12 +95,15 @@ if __name__ == "__main__": models=set() layouts=set() variants=set() + groups=set() variants.add( "Default" ) + groups.add( "None" ) with open("/usr/local/share/X11/xkb/rules/base.lst", "r") as f: - scrape_file(f, models, layouts, variants) + scrape_file(f, models, layouts, variants, groups) with open("KeyboardData_p.cxxtr", "w") as f: f.write(cpp_header_comment) write_set(f, "kb_models", models) write_set(f, "kb_layouts", layouts) write_set(f, "kb_variants", variants) + write_set(f, "kb_groups", groups)