add keyboard layout switch selector

This commit is contained in:
Ivan Borzenkov 2023-07-08 22:59:33 +03:00
parent 1b235e56a3
commit 1ac3459afa
10 changed files with 239 additions and 10 deletions

View File

@ -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 );
}
}

View File

@ -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;

View File

@ -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()
};
}
}

View File

@ -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";
}

View File

@ -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();

View File

@ -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 );
}

View File

@ -117,6 +117,30 @@ SPDX-License-Identifier: GPL-3.0-or-later
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Keyboard Switch:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="groupSelector">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLineEdit" name="LE_TestKeyboard">
<property name="font">
@ -142,6 +166,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<tabstop>physicalModelSelector</tabstop>
<tabstop>layoutSelector</tabstop>
<tabstop>variantSelector</tabstop>
<tabstop>groupSelector</tabstop>
<tabstop>LE_TestKeyboard</tabstop>
<tabstop>buttonRestore</tabstop>
</tabstops>

View File

@ -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 );
}

View File

@ -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

View File

@ -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)