/* === This file is part of Calamares - === * * Copyright 2014-2016, Teo Mrnjavac * Copyright 2014, Kevin Kofler * * Portions from systemd (localed.c): * Copyright 2011 Lennart Poettering * Copyright 2013 Kay Sievers * (originally under LGPLv2.1+, used under the LGPL to GPL conversion clause) * * 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 #include "JobQueue.h" #include "GlobalStorage.h" #include "utils/Logger.h" #include "utils/CalamaresUtilsSystem.h" #include #include #include #include #include SetKeyboardLayoutJob::SetKeyboardLayoutJob( const QString& model, const QString& layout, const QString& variant, const QString& xOrgConfFileName, const QString& convertedKeymapPath, bool writeEtcDefaultKeyboard ) : Calamares::Job() , m_model( model ) , m_layout( layout ) , m_variant( variant ) , m_xOrgConfFileName( xOrgConfFileName ) , m_convertedKeymapPath( convertedKeymapPath ) , m_writeEtcDefaultKeyboard( writeEtcDefaultKeyboard ) { } QString SetKeyboardLayoutJob::prettyName() const { return tr( "Set keyboard model to %1, layout to %2-%3" ).arg( m_model ) .arg( m_layout ) .arg( m_variant ); } QString SetKeyboardLayoutJob::findConvertedKeymap( const QString& convertedKeymapPath ) const { cDebug() << "Looking for converted keymap in" << convertedKeymapPath; // No search path supplied, assume the distribution does not provide // converted keymaps if ( convertedKeymapPath.isEmpty() ) return QString(); QDir convertedKeymapDir( convertedKeymapPath ); QString name = m_variant.isEmpty() ? m_layout : ( m_layout + '-' + m_variant ); if ( convertedKeymapDir.exists( name + ".map" ) || convertedKeymapDir.exists( name + ".map.gz" ) ) { cDebug() << Logger::SubEntry << "Found converted keymap" << name; return name; } return QString(); } QString SetKeyboardLayoutJob::findLegacyKeymap() const { cDebug() << "Looking for legacy keymap in QRC"; int bestMatching = 0; QString name; QFile file( ":/kbd-model-map" ); file.open( QIODevice::ReadOnly | QIODevice::Text ); QTextStream stream( &file ); while ( !stream.atEnd() ) { QString line = stream.readLine().trimmed(); if ( line.isEmpty() || line.startsWith( '#' ) ) continue; QStringList mapping = line.split( '\t', QString::SkipEmptyParts ); if ( mapping.size() < 5 ) continue; int matching = 0; // Determine how well matching this entry is // We assume here that we have one X11 layout. If the UI changes to // allow more than one layout, this should change too. if ( m_layout == mapping[1] ) // If we got an exact match, this is best matching = 10; // Look for an entry whose first layout matches ours else if ( mapping[1].startsWith( m_layout + ',' ) ) matching = 5; if ( matching > 0 ) { if ( m_model.isEmpty() || m_model == mapping[2] ) matching++; QString mappingVariant = mapping[3]; if ( mappingVariant == "-" ) mappingVariant = QString(); else if ( mappingVariant.startsWith( ',' ) ) mappingVariant.remove( 1, 0 ); if ( m_variant == mappingVariant ) matching++; // We ignore mapping[4], the xkb options, for now. If we ever // allow setting options in the UI, we should match them here. } // The best matching entry so far, then let's save that if ( matching >= qMax( bestMatching, 1 ) ) { cDebug() << Logger::SubEntry << "Found legacy keymap" << mapping[0] << "with score" << matching; if ( matching > bestMatching ) { bestMatching = matching; name = mapping[0]; } } } return name; } bool SetKeyboardLayoutJob::writeVConsoleData( const QString& vconsoleConfPath, const QString& convertedKeymapPath ) const { QString keymap = findConvertedKeymap( convertedKeymapPath ); if ( keymap.isEmpty() ) keymap = findLegacyKeymap(); if ( keymap.isEmpty() ) { cDebug() << "Trying to use X11 layout" << m_layout << "as the virtual console layout"; keymap = m_layout; } QStringList existingLines; // Read in the existing vconsole.conf, if it exists QFile file( vconsoleConfPath ); if ( file.exists() ) { file.open( QIODevice::ReadOnly | QIODevice::Text ); QTextStream stream( &file ); while ( !stream.atEnd() ) existingLines << stream.readLine(); file.close(); if ( stream.status() != QTextStream::Ok ) return false; } // Write out the existing lines and replace the KEYMAP= line file.open( QIODevice::WriteOnly | QIODevice::Text ); QTextStream stream( &file ); bool found = false; foreach ( const QString& existingLine, existingLines ) { if ( existingLine.trimmed().startsWith( "KEYMAP=" ) ) { stream << "KEYMAP=" << keymap << '\n'; found = true; } else stream << existingLine << '\n'; } // Add a KEYMAP= line if there wasn't any if ( !found ) stream << "KEYMAP=" << keymap << '\n'; stream.flush(); file.close(); cDebug() << "Written KEYMAP=" << keymap << "to vconsole.conf"; return ( stream.status() == QTextStream::Ok ); } bool SetKeyboardLayoutJob::writeX11Data( const QString& keyboardConfPath ) const { QFile file( keyboardConfPath ); file.open( QIODevice::WriteOnly | QIODevice::Text ); QTextStream stream( &file ); stream << "# Read and parsed by systemd-localed. It's probably wise not to edit this file\n" "# manually too freely.\n" "Section \"InputClass\"\n" " Identifier \"system-keyboard\"\n" " MatchIsKeyboard \"on\"\n"; if ( !m_layout.isEmpty() ) stream << " Option \"XkbLayout\" \"" << m_layout << "\"\n"; if ( !m_model.isEmpty() ) stream << " Option \"XkbModel\" \"" << m_model << "\"\n"; if ( !m_variant.isEmpty() ) stream << " Option \"XkbVariant\" \"" << m_variant << "\"\n"; stream << "EndSection\n"; stream.flush(); file.close(); cDebug() << "Written XkbLayout" << m_layout << "; XkbModel" << m_model << "; XkbVariant" << m_variant << "to X.org file" << keyboardConfPath; return ( stream.status() == QTextStream::Ok ); } bool SetKeyboardLayoutJob::writeDefaultKeyboardData( const QString& defaultKeyboardPath ) const { QFile file( defaultKeyboardPath ); file.open( QIODevice::WriteOnly | QIODevice::Text ); QTextStream stream( &file ); stream << "# KEYBOARD CONFIGURATION FILE\n\n" "# Consult the keyboard(5) manual page.\n\n"; stream << "XKBMODEL=\"" << m_model << "\"\n"; stream << "XKBLAYOUT=\"" << m_layout << "\"\n"; stream << "XKBVARIANT=\"" << m_variant << "\"\n"; stream << "XKBOPTIONS=\"\"\n\n"; stream << "BACKSPACE=\"guess\"\n"; stream.flush(); file.close(); cDebug() << "Written XKBMODEL" << m_model << "; XKBLAYOUT" << m_layout << "; XKBVARIANT" << m_variant << "to /etc/default/keyboard file" << defaultKeyboardPath; return ( stream.status() == QTextStream::Ok ); } Calamares::JobResult SetKeyboardLayoutJob::exec() { cDebug() << "Executing SetKeyboardLayoutJob"; // Read the location of the destination's / in the host file system from // the global settings Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); QDir destDir( gs->value( "rootMountPoint" ).toString() ); // Get the path to the destination's /etc/vconsole.conf QString vconsoleConfPath = destDir.absoluteFilePath( "etc/vconsole.conf" ); // Get the path to the destination's /etc/X11/xorg.conf.d/00-keyboard.conf QString xorgConfDPath; QString keyboardConfPath; if ( QDir::isAbsolutePath( m_xOrgConfFileName ) ) { keyboardConfPath = m_xOrgConfFileName; while ( keyboardConfPath.startsWith( '/' ) ) keyboardConfPath.remove( 0, 1 ); keyboardConfPath = destDir.absoluteFilePath( keyboardConfPath ); xorgConfDPath = QFileInfo( keyboardConfPath ).path(); } else { xorgConfDPath = destDir.absoluteFilePath( "etc/X11/xorg.conf.d" ); keyboardConfPath = QDir( xorgConfDPath ) .absoluteFilePath( m_xOrgConfFileName ); } destDir.mkpath( xorgConfDPath ); QString defaultKeyboardPath; if ( QDir( destDir.absoluteFilePath( "etc/default" ) ).exists() ) defaultKeyboardPath = destDir.absoluteFilePath( "etc/default/keyboard" ); // Get the path to the destination's path to the converted key mappings QString convertedKeymapPath = m_convertedKeymapPath; if ( !convertedKeymapPath.isEmpty() ) { while ( convertedKeymapPath.startsWith( '/' ) ) convertedKeymapPath.remove( 0, 1 ); convertedKeymapPath = destDir.absoluteFilePath( convertedKeymapPath ); } if ( !writeVConsoleData( vconsoleConfPath, convertedKeymapPath ) ) return Calamares::JobResult::error( tr( "Failed to write keyboard configuration for the virtual console." ), tr( "Failed to write to %1" ).arg( vconsoleConfPath ) ); if ( !writeX11Data( keyboardConfPath ) ) return Calamares::JobResult::error( tr( "Failed to write keyboard configuration for X11." ), tr( "Failed to write to %1" ).arg( keyboardConfPath ) ); if ( !defaultKeyboardPath.isEmpty() && m_writeEtcDefaultKeyboard ) { if ( !writeDefaultKeyboardData( defaultKeyboardPath ) ) return Calamares::JobResult::error( tr( "Failed to write keyboard configuration to existing /etc/default directory." ), tr( "Failed to write to %1" ).arg( keyboardConfPath ) ); } return Calamares::JobResult::ok(); }