Merge pull request #2164 from ivan1986/add-keyboard-group

add keyboard layout switch selector
This commit is contained in:
Adriaan de Groot 2023-09-08 21:45:36 +02:00 committed by GitHub
commit 60df29d734
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 270 additions and 26 deletions

View File

@ -30,6 +30,7 @@ This release contains contributions from (alphabetically by first name):
in a Wayland session. (thanks Hector)
- *keyboard* module now writes X11 layout configuration with variants
for all non-ASCII (e.g. us) layouts. (thanks Ivan)
- *keyboard* module now can configure keyboard switch. (thanks Ivan)
# 3.3.0-alpha3 (2023-08-28)

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_KeyboardGroupSwitcherModel( new KeyboardGroupsSwitchersModel( 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 );
xkbChanged();
emit prettyStatusChanged();
} );
connect( m_KeyboardGroupSwitcherModel,
&KeyboardGroupsSwitchersModel::currentIndexChanged,
[ & ]( int index )
{
m_selectedGroup = m_KeyboardGroupSwitcherModel->key( index );
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_KeyboardGroupSwitcherModel, &KeyboardGroupsSwitchersModel::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_KeyboardGroupSwitcherModel->key( m_KeyboardGroupSwitcherModel->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;
}
KeyboardGroupsSwitchersModel*
Config::keyboardGroupsSwitchers() const
{
return m_KeyboardGroupSwitcherModel;
}
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( KeyboardGroupsSwitchersModel* keyboardGroupsSwitchersModel READ keyboardGroupsSwitchers 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
*/
KeyboardGroupsSwitchersModel* keyboardGroupsSwitchers() 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;
KeyboardGroupsSwitchersModel* m_KeyboardGroupSwitcherModel;
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();
}
KeyboardGroupsSwitchersModel::KeyboardGroupsSwitchersModel( 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 groupsSwitcher (xkb id and human-readable)
*
* The list of group switching combinations `getKeyboardGroups()`
* function can be used to update the switching when the two models
* are related.
*/
class KeyboardGroupsSwitchersModel : public XKBListModel
{
Q_OBJECT
public:
explicit KeyboardGroupsSwitchersModel( 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->keyboardGroupsSwitchers();
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->keyboardGroupsSwitchers(),
QOverload< int >::of( &XKBListModel::setCurrentIndex ) );
connect( config->keyboardGroupsSwitchers(),
&KeyboardGroupsSwitchersModel::currentIndexChanged,
ui->groupSelector,
&QComboBox::setCurrentIndex );
CALAMARES_RETRANSLATE_SLOT( &KeyboardPage::retranslate );
}

View File

@ -118,23 +118,59 @@ SPDX-License-Identifier: GPL-3.0-or-later
</layout>
</item>
<item>
<widget class="QLineEdit" name="LE_TestKeyboard">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="topMargin">
<number>0</number>
</property>
<property name="inputMask">
<string/>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Type here to test your keyboard</string>
</property>
</widget>
<item>
<widget class="QLineEdit" name="LE_TestKeyboard">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="inputMask">
<string/>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Type here to test your keyboard</string>
</property>
</widget>
</item>
<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="MinimumExpanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
@ -142,6 +178,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,50 @@ parseKeyboardLayouts( const char* filepath )
return layouts;
}
static KeyboardGlobal::GroupsMap
parseKeyboardGroupsSwitchers( 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;
}
QRegularExpression rx;
rx.setPattern( "^\\s+grp:(\\S+)\\s+(\\w.*)\n$" );
bool optionSectionFound = findSection( fh, "! option" );
// read the file until the end or until we break the loop
while ( optionSectionFound && !fh.atEnd() )
{
QByteArray line = fh.readLine();
// check if we start a new section
if ( line.startsWith( '!' ) )
{
break;
}
// here we are in the option section - find all "grp:" options
// insert into the model map
QRegularExpressionMatch match = rx.match( line );
if ( match.hasMatch() )
{
QString modelDesc = match.captured( 2 );
QString model = match.captured( 1 );
models.insert( modelDesc, model );
}
}
return models;
}
KeyboardGlobal::LayoutsMap
KeyboardGlobal::getKeyboardLayouts()
@ -212,3 +256,9 @@ KeyboardGlobal::getKeyboardModels()
{
return parseKeyboardModels( XKB_FILE );
}
KeyboardGlobal::GroupsMap
KeyboardGlobal::getKeyboardGroups()
{
return parseKeyboardGroupsSwitchers( 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)