/* === 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 "SetKeyboardLayoutJob.h" #include "GlobalStorage.h" #include "JobQueue.h" #include "utils/CalamaresUtilsSystem.h" #include "utils/Logger.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(); }