diff --git a/src/modules/keyboard/CMakeLists.txt b/src/modules/keyboard/CMakeLists.txt index a2e09dc41..10868a269 100644 --- a/src/modules/keyboard/CMakeLists.txt +++ b/src/modules/keyboard/CMakeLists.txt @@ -3,6 +3,9 @@ # SPDX-FileCopyrightText: 2020 Adriaan de Groot # SPDX-License-Identifier: BSD-2-Clause # + +find_package(Qt5 ${QT_VERSION} CONFIG REQUIRED Core DBus) + calamares_add_plugin(keyboard TYPE viewmodule EXPORT_MACRO PLUGINDLLEXPORT_PRO @@ -19,6 +22,8 @@ calamares_add_plugin(keyboard RESOURCES keyboard.qrc SHARED_LIB + LINK_LIBRARIES + Qt5::DBus ) calamares_add_test(keyboardtest SOURCES Tests.cpp SetKeyboardLayoutJob.cpp RESOURCES keyboard.qrc) diff --git a/src/modules/keyboard/Config.cpp b/src/modules/keyboard/Config.cpp index e7ffc6555..f8d9f8d01 100644 --- a/src/modules/keyboard/Config.cpp +++ b/src/modules/keyboard/Config.cpp @@ -23,9 +23,14 @@ #include "utils/Variant.h" #include +#include #include #include +#include +#include +#include + /* Returns stringlist with suitable setxkbmap command-line arguments * to set the given @p model. */ @@ -163,7 +168,14 @@ Config::Config( QObject* parent ) { // Set Xorg keyboard model m_selectedModel = m_keyboardModelsModel->key( index ); - QProcess::execute( "setxkbmap", xkbmap_model_args( m_selectedModel ) ); + if ( m_useLocale1 ) + { + locale1Apply(); + } + else + { + QProcess::execute( "setxkbmap", xkbmap_model_args( m_selectedModel ) ); + } emit prettyStatusChanged(); } ); @@ -201,12 +213,55 @@ Config::xkbChanged( int index ) m_setxkbmapTimer.disconnect( this ); } - connect( &m_setxkbmapTimer, &QTimer::timeout, this, &Config::xkbApply ); + if ( m_useLocale1 ) + { + connect( &m_setxkbmapTimer, &QTimer::timeout, this, &Config::locale1Apply ); + } + else + { + connect( &m_setxkbmapTimer, &QTimer::timeout, this, &Config::xkbApply ); + } m_setxkbmapTimer.start( QApplication::keyboardInputInterval() ); emit prettyStatusChanged(); } +void +Config::locale1Apply() +{ + m_additionalLayoutInfo = getAdditionalLayoutInfo( m_selectedLayout ); + + QString layout = m_selectedLayout; + QString variant = m_selectedVariant; + QString option; + + if ( !m_additionalLayoutInfo.additionalLayout.isEmpty() ) + { + layout = m_additionalLayoutInfo.additionalLayout + "," + layout; + variant = m_additionalLayoutInfo.additionalVariant + "," + layout; + option = m_additionalLayoutInfo.groupSwitcher; + } + + QDBusInterface locale1( "org.freedesktop.locale1", + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + QDBusConnection::systemBus() ); + if ( !locale1.isValid() ) + { + cWarning() << "Interface" << locale1.interface() << "is not valid."; + return; + } + + // Using convert=true, this also updates the VConsole config + { + QDBusReply< void > r = locale1.call( "SetX11Keyboard", layout, m_selectedModel, variant, option, true, false ); + if ( !r.isValid() ) + { + cWarning() << "Could not set keyboard config through org.freedesktop.locale1.X11Keyboard." << r.error(); + } + } +} + void Config::xkbApply() { @@ -276,21 +331,10 @@ findLayout( const KeyboardLayoutModel* klm, const QString& currentLayout ) } void -Config::detectCurrentKeyboardLayout() +Config::getCurrentKeyboardLayoutXkb( QString& currentLayout, QString& currentVariant, QString& currentModel ) { - if ( m_state != State::Initial ) - { - return; - } - cScopedAssignment returnToIntial( &m_state, State::Initial ); - m_state = State::Guessing; - - //### 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", SplitSkipEmptyParts ); @@ -299,10 +343,13 @@ Config::detectCurrentKeyboardLayout() // xkb_symbols { include "pc+latin+ru:2+inet(evdev)+group(alt_shift_toggle)+ctrl(swapcaps)" }; for ( const auto& line : list ) { - if ( !line.trimmed().startsWith( "xkb_symbols" ) ) + bool symbols = false; + if ( line.trimmed().startsWith( "xkb_symbols" ) ) { - continue; + symbols = true; } + else if ( !line.trimmed().startsWith( "xkb_geometry" ) ) + continue; int firstQuote = line.indexOf( '"' ); int lastQuote = line.lastIndexOf( '"' ); @@ -314,7 +361,7 @@ Config::detectCurrentKeyboardLayout() QStringList split = line.mid( firstQuote + 1, lastQuote - firstQuote ).split( "+", SplitSkipEmptyParts ); cDebug() << split; - if ( split.size() >= 2 ) + if ( symbols && split.size() >= 2 ) { currentLayout = split.at( 1 ); @@ -328,8 +375,61 @@ Config::detectCurrentKeyboardLayout() break; } + else if ( !symbols && split.size() >= 1 ) + { + currentModel = split.at( 0 ); + if ( currentModel.contains( "(" ) ) + { + int parenthesisIndex = currentLayout.indexOf( "(" ); + currentModel = currentModel.mid( parenthesisIndex + 1 ).trimmed(); + currentModel.chop( 1 ); + } + } } } +} + +void +Config::getCurrentKeyboardLayoutLocale1( QString& currentLayout, QString& currentVariant, QString& currentModel ) +{ + QDBusInterface locale1( "org.freedesktop.locale1", + "/org/freedesktop/locale1", + "org.freedesktop.locale1", + QDBusConnection::systemBus() ); + if ( !locale1.isValid() ) + { + cWarning() << "Interface" << locale1.interface() << "is not valid."; + return; + } + + currentLayout = locale1.property( "X11Layout" ).toString().split( "," ).last(); + currentVariant = locale1.property( "X11Variant" ).toString().split( "," ).last(); + currentModel = locale1.property( "X11Model" ).toString(); +} + +void +Config::detectCurrentKeyboardLayout() +{ + if ( m_state != State::Initial ) + { + return; + } + cScopedAssignment returnToIntial( &m_state, State::Initial ); + m_state = State::Guessing; + + //### Detect current keyboard layout, variant, and model + QString currentLayout; + QString currentVariant; + QString currentModel; + + if ( m_useLocale1 ) + { + getCurrentKeyboardLayoutLocale1( currentLayout, currentVariant, currentModel ); + } + else + { + getCurrentKeyboardLayoutXkb( currentLayout, currentVariant, currentModel ); + } //### Layouts and Variants QPersistentModelIndex currentLayoutItem = findLayout( m_keyboardLayoutsModel, currentLayout ); @@ -352,6 +452,17 @@ Config::detectCurrentKeyboardLayout() { m_keyboardLayoutsModel->setCurrentIndex( m_keyboardLayoutsModel->index( 0 ).row() ); } + + //### Keyboard model + for ( int i = 0; i < m_keyboardModelsModel->rowCount(); ++i ) + { + QModelIndex idx = m_keyboardModelsModel->index( i ); + if ( idx.isValid() && idx.data( XKBListModel::KeyRole ).toString() == currentModel ) + { + m_keyboardModelsModel->setCurrentIndex( idx.row() ); + break; + } + } } QString @@ -381,7 +492,8 @@ Config::createJobs() m_additionalLayoutInfo, m_xOrgConfFileName, m_convertedKeymapPath, - m_writeEtcDefaultKeyboard ); + m_writeEtcDefaultKeyboard, + m_useLocale1 ); list.append( Calamares::job_ptr( j ) ); return list; @@ -430,7 +542,7 @@ guessLayout( const QStringList& langParts, KeyboardLayoutModel* layouts, Keyboar void Config::guessLocaleKeyboardLayout() { - if ( m_state != State::Initial ) + if ( m_state != State::Initial || !m_guessLayout ) { return; } @@ -561,6 +673,7 @@ void Config::setConfigurationMap( const QVariantMap& configurationMap ) { using namespace CalamaresUtils; + bool isX11 = QGuiApplication::platformName() == "xcb"; const auto xorgConfDefault = QStringLiteral( "00-keyboard.conf" ); m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName", xorgConfDefault ); @@ -570,6 +683,8 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) } m_convertedKeymapPath = getString( configurationMap, "convertedKeymapPath" ); m_writeEtcDefaultKeyboard = getBool( configurationMap, "writeEtcDefaultKeyboard", true ); + m_useLocale1 = getBool( configurationMap, "useLocale1", !isX11 ); + m_guessLayout = getBool( configurationMap, "guessLayout", true ); } void diff --git a/src/modules/keyboard/Config.h b/src/modules/keyboard/Config.h index 436ead4b5..b753edf34 100644 --- a/src/modules/keyboard/Config.h +++ b/src/modules/keyboard/Config.h @@ -89,6 +89,10 @@ private: */ void xkbChanged( int index ); void xkbApply(); + void locale1Apply(); + + void getCurrentKeyboardLayoutXkb( QString& currentLayout, QString& currentVariant, QString& currentModel ); + void getCurrentKeyboardLayoutLocale1( QString& currentLayout, QString& currentVariant, QString& currentModel ); KeyboardModelsModel* m_keyboardModelsModel; KeyboardLayoutModel* m_keyboardLayoutsModel; @@ -107,6 +111,8 @@ private: QString m_xOrgConfFileName; QString m_convertedKeymapPath; bool m_writeEtcDefaultKeyboard = true; + bool m_useLocale1; + bool m_guessLayout; // The state determines whether we guess settings or preserve them: // - Initial -> Guessing diff --git a/src/modules/keyboard/KeyboardLayoutModel.h b/src/modules/keyboard/KeyboardLayoutModel.h index c2306c001..1fd6a7819 100644 --- a/src/modules/keyboard/KeyboardLayoutModel.h +++ b/src/modules/keyboard/KeyboardLayoutModel.h @@ -86,6 +86,7 @@ public: /// @brief Set the index back to PC105 (the default physical model) void setCurrentIndex() { XKBListModel::setCurrentIndex( m_defaultPC105 ); } + using XKBListModel::setCurrentIndex; private: int m_defaultPC105 = -1; ///< The index of pc105, if there is one diff --git a/src/modules/keyboard/SetKeyboardLayoutJob.cpp b/src/modules/keyboard/SetKeyboardLayoutJob.cpp index c80d84e7d..e40667295 100644 --- a/src/modules/keyboard/SetKeyboardLayoutJob.cpp +++ b/src/modules/keyboard/SetKeyboardLayoutJob.cpp @@ -36,7 +36,8 @@ SetKeyboardLayoutJob::SetKeyboardLayoutJob( const QString& model, const AdditionalLayoutInfo& additionalLayoutInfo, const QString& xOrgConfFileName, const QString& convertedKeymapPath, - bool writeEtcDefaultKeyboard ) + bool writeEtcDefaultKeyboard, + bool skipIfNoRoot ) : Calamares::Job() , m_model( model ) , m_layout( layout ) @@ -45,6 +46,7 @@ SetKeyboardLayoutJob::SetKeyboardLayoutJob( const QString& model, , m_xOrgConfFileName( xOrgConfFileName ) , m_convertedKeymapPath( convertedKeymapPath ) , m_writeEtcDefaultKeyboard( writeEtcDefaultKeyboard ) + , m_skipIfNoRoot( skipIfNoRoot ) { } @@ -348,6 +350,9 @@ SetKeyboardLayoutJob::exec() Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); QDir destDir( gs->value( "rootMountPoint" ).toString() ); + // Skip this if we are using locale1 and we are configuring the local system, + // since the service will have already updated these configs for us. + if ( !( m_skipIfNoRoot && ( destDir.isEmpty() || destDir.isRoot() ) ) ) { // Get the path to the destination's /etc/vconsole.conf QString vconsoleConfPath = destDir.absoluteFilePath( "etc/vconsole.conf" ); @@ -368,9 +373,7 @@ SetKeyboardLayoutJob::exec() return Calamares::JobResult::error( tr( "Failed to write keyboard configuration for the virtual console." ), tr( "Failed to write to %1" ).arg( vconsoleConfPath ) ); } - } - { // Get the path to the destination's /etc/X11/xorg.conf.d/00-keyboard.conf QString xorgConfDPath; QString keyboardConfPath; diff --git a/src/modules/keyboard/SetKeyboardLayoutJob.h b/src/modules/keyboard/SetKeyboardLayoutJob.h index 15fadfb52..87aa8ff73 100644 --- a/src/modules/keyboard/SetKeyboardLayoutJob.h +++ b/src/modules/keyboard/SetKeyboardLayoutJob.h @@ -25,7 +25,8 @@ public: const AdditionalLayoutInfo& additionaLayoutInfo, const QString& xOrgConfFileName, const QString& convertedKeymapPath, - bool writeEtcDefaultKeyboard ); + bool writeEtcDefaultKeyboard, + bool skipIfNoRoot ); QString prettyName() const override; Calamares::JobResult exec() override; @@ -44,6 +45,7 @@ private: QString m_xOrgConfFileName; QString m_convertedKeymapPath; const bool m_writeEtcDefaultKeyboard; + const bool m_skipIfNoRoot; }; #endif /* SETKEYBOARDLAYOUTJOB_H */ diff --git a/src/modules/keyboard/keyboard.conf b/src/modules/keyboard/keyboard.conf index 3b2f3a312..2a8e85149 100644 --- a/src/modules/keyboard/keyboard.conf +++ b/src/modules/keyboard/keyboard.conf @@ -21,3 +21,13 @@ convertedKeymapPath: "/lib/kbd/keymaps/xkb" # found on Debian-related systems. # Defaults to true if nothing is set. #writeEtcDefaultKeyboard: true + +# Use the Locale1 service instead of directly managing configuration files. +# This is the modern mechanism for configuring the systemwide keyboard layout, +# and works on Wayland compositors to set the current layout. +# Defaults to false on X11 and true otherwise. +#useLocale1: true + +# Guess the default layout from the user locale. If false, keeps the current +# OS keyboard layout as the default (useful if the layout is pre-configured). +#guessLayout: true diff --git a/src/modules/keyboardq/CMakeLists.txt b/src/modules/keyboardq/CMakeLists.txt index 9c7922d86..afd8d4aad 100644 --- a/src/modules/keyboardq/CMakeLists.txt +++ b/src/modules/keyboardq/CMakeLists.txt @@ -8,6 +8,8 @@ if(NOT WITH_QML) return() endif() +find_package(Qt5 ${QT_VERSION} CONFIG REQUIRED Core DBus) + set(_keyboard ${CMAKE_CURRENT_SOURCE_DIR}/../keyboard) include_directories(${_keyboard}) @@ -24,4 +26,6 @@ calamares_add_plugin(keyboardq RESOURCES keyboardq.qrc SHARED_LIB + LINK_LIBRARIES + Qt5::DBus )