source: calamares/trunk/fuentes/src/modules/keyboard/KeyboardPage.cpp @ 7538

Last change on this file since 7538 was 7538, checked in by kbut, 13 months ago

sync with github

File size: 15.1 KB
Line 
1/* === This file is part of Calamares - <https://github.com/calamares> ===
2 *
3 *   Copyright 2014-2016, Teo Mrnjavac <teo@kde.org>
4 *   Copyright 2017, Adriaan de Groot <groot@kde.org>
5 *
6 *   Portions from the Manjaro Installation Framework
7 *   by Roland Singer <roland@manjaro.org>
8 *   Copyright (C) 2007 Free Software Foundation, Inc.
9 *
10 *   Calamares is free software: you can redistribute it and/or modify
11 *   it under the terms of the GNU General Public License as published by
12 *   the Free Software Foundation, either version 3 of the License, or
13 *   (at your option) any later version.
14 *
15 *   Calamares is distributed in the hope that it will be useful,
16 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
17 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 *   GNU General Public License for more details.
19 *
20 *   You should have received a copy of the GNU General Public License
21 *   along with Calamares. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24#include "KeyboardPage.h"
25
26#include "ui_KeyboardPage.h"
27#include "keyboardwidget/keyboardpreview.h"
28#include "SetKeyboardLayoutJob.h"
29#include "KeyboardLayoutModel.h"
30
31#include "GlobalStorage.h"
32#include "JobQueue.h"
33#include "utils/Logger.h"
34#include "utils/Retranslator.h"
35
36#include <QComboBox>
37#include <QProcess>
38#include <QPushButton>
39
40class LayoutItem : public QListWidgetItem
41{
42public:
43    QString data;
44
45    virtual ~LayoutItem();
46};
47
48LayoutItem::~LayoutItem()
49{
50}
51
52static QPersistentModelIndex
53findLayout( const KeyboardLayoutModel* klm, const QString& currentLayout )
54{
55    QPersistentModelIndex currentLayoutItem;
56
57    for ( int i = 0; i < klm->rowCount(); ++i )
58    {
59        QModelIndex idx = klm->index( i );
60        if ( idx.isValid() &&
61                idx.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString() == currentLayout )
62            currentLayoutItem = idx;
63    }
64
65    return currentLayoutItem;
66}
67
68KeyboardPage::KeyboardPage( QWidget* parent )
69    : QWidget( parent )
70    , ui( new Ui::Page_Keyboard )
71    , m_keyboardPreview( new KeyBoardPreview( this ) )
72    , m_defaultIndex( 0 )
73{
74    ui->setupUi( this );
75
76    // Keyboard Preview
77    ui->KBPreviewLayout->addWidget( m_keyboardPreview );
78
79    m_setxkbmapTimer.setSingleShot( true );
80
81    // Connect signals and slots
82    connect( ui->listVariant, &QListWidget::currentItemChanged,
83             this, &KeyboardPage::onListVariantCurrentItemChanged );
84
85    connect( ui->buttonRestore, &QPushButton::clicked,
86             [this]
87    {
88        ui->comboBoxModel->setCurrentIndex( m_defaultIndex );
89    } );
90
91    connect( ui->comboBoxModel,
92             static_cast< void ( QComboBox::* )( const QString& ) >( &QComboBox::currentIndexChanged ),
93             [this]( const QString& text )
94    {
95        QString model = m_models.value( text, "pc105" );
96
97        // Set Xorg keyboard model
98        QProcess::execute( QLatin1Literal( "setxkbmap" ),
99                           QStringList() << "-model" << model );
100    } );
101
102    CALAMARES_RETRANSLATE( ui->retranslateUi( this ); )
103}
104
105
106KeyboardPage::~KeyboardPage()
107{
108    delete ui;
109}
110
111
112void
113KeyboardPage::init()
114{
115    //### Detect current keyboard layout and variant
116    QString currentLayout;
117    QString currentVariant;
118    QProcess process;
119    process.start( "setxkbmap", QStringList() << "-print" );
120
121    if ( process.waitForFinished() )
122    {
123        const QStringList list = QString( process.readAll() )
124                                 .split( "\n", QString::SkipEmptyParts );
125
126        for ( QString line : list )
127        {
128            line = line.trimmed();
129            if ( !line.startsWith( "xkb_symbols" ) )
130                continue;
131
132            line = line.remove( "}" )
133                   .remove( "{" )
134                   .remove( ";" );
135            line = line.mid( line.indexOf( "\"" ) + 1 );
136
137            QStringList split = line.split( "+", QString::SkipEmptyParts );
138            if ( split.size() >= 2 )
139            {
140                currentLayout = split.at( 1 );
141
142                if ( currentLayout.contains( "(" ) )
143                {
144                    int parenthesisIndex = currentLayout.indexOf( "(" );
145                    currentVariant = currentLayout.mid( parenthesisIndex + 1 )
146                                     .trimmed();
147                    currentVariant.chop( 1 );
148                    currentLayout = currentLayout
149                                    .mid( 0, parenthesisIndex )
150                                    .trimmed();
151                }
152
153                break;
154            }
155        }
156    }
157
158    //### Models
159    m_models = KeyboardGlobal::getKeyboardModels();
160    QMapIterator< QString, QString > mi( m_models );
161
162    ui->comboBoxModel->blockSignals( true );
163
164    while ( mi.hasNext() )
165    {
166        mi.next();
167
168        if ( mi.value() == "pc105" )
169            m_defaultIndex = ui->comboBoxModel->count();
170
171        ui->comboBoxModel->addItem( mi.key() );
172    }
173
174    ui->comboBoxModel->blockSignals( false );
175
176    // Set to default value pc105
177    ui->comboBoxModel->setCurrentIndex( m_defaultIndex );
178
179
180    //### Layouts and Variants
181
182    KeyboardLayoutModel* klm = new KeyboardLayoutModel( this );
183    ui->listLayout->setModel( klm );
184    connect( ui->listLayout->selectionModel(), &QItemSelectionModel::currentChanged,
185             this, &KeyboardPage::onListLayoutCurrentItemChanged );
186
187    // Block signals
188    ui->listLayout->blockSignals( true );
189
190    QPersistentModelIndex currentLayoutItem = findLayout( klm, currentLayout );
191    if ( !currentLayoutItem.isValid() && (
192                ( currentLayout == "latin" )
193                || ( currentLayout == "pc" ) ) )
194    {
195        currentLayout = "us";
196        currentLayoutItem = findLayout( klm, currentLayout );
197    }
198
199    // Set current layout and variant
200    if ( currentLayoutItem.isValid() )
201    {
202        ui->listLayout->setCurrentIndex( currentLayoutItem );
203        updateVariants( currentLayoutItem, currentVariant );
204    }
205
206    // Unblock signals
207    ui->listLayout->blockSignals( false );
208
209    // Default to the first available layout if none was set
210    // Do this after unblocking signals so we get the default variant handling.
211    if ( !currentLayoutItem.isValid() && klm->rowCount() > 0 )
212        ui->listLayout->setCurrentIndex( klm->index( 0 ) );
213}
214
215
216QString
217KeyboardPage::prettyStatus() const
218{
219    QString status;
220    status += tr( "Set keyboard model to %1.<br/>" )
221              .arg( ui->comboBoxModel->currentText() );
222    status += tr( "Set keyboard layout to %1/%2." )
223              .arg( ui->listLayout->currentIndex().data().toString() )
224              .arg( ui->listVariant->currentItem()->text() );
225
226    return status;
227}
228
229
230QList< Calamares::job_ptr >
231KeyboardPage::createJobs( const QString& xOrgConfFileName,
232                          const QString& convertedKeymapPath,
233                          bool writeEtcDefaultKeyboard )
234{
235    QList< Calamares::job_ptr > list;
236    QString selectedModel = m_models.value( ui->comboBoxModel->currentText(),
237                                            "pc105" );
238
239    Calamares::Job* j = new SetKeyboardLayoutJob( selectedModel,
240            m_selectedLayout,
241            m_selectedVariant,
242            xOrgConfFileName,
243            convertedKeymapPath,
244            writeEtcDefaultKeyboard );
245    list.append( Calamares::job_ptr( j ) );
246
247    return list;
248}
249
250
251void
252KeyboardPage::guessLayout( const QStringList& langParts )
253{
254    const KeyboardLayoutModel* klm = dynamic_cast< KeyboardLayoutModel* >( ui->listLayout->model() );
255    bool foundCountryPart = false;
256    for ( auto countryPart = langParts.rbegin(); !foundCountryPart && countryPart != langParts.rend(); ++countryPart )
257    {
258        cDebug() << "   .. looking for locale part" << *countryPart;
259        for ( int i = 0; i < klm->rowCount(); ++i )
260        {
261            QModelIndex idx = klm->index( i );
262            QString name = idx.isValid() ? idx.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString() : QString();
263            if ( idx.isValid() && ( name.compare( *countryPart, Qt::CaseInsensitive ) == 0 ) )
264            {
265                cDebug() << "   .. matched" << name;
266                ui->listLayout->setCurrentIndex( idx );
267                foundCountryPart = true;
268                break;
269            }
270        }
271        if ( foundCountryPart )
272        {
273            ++countryPart;
274            if ( countryPart != langParts.rend() )
275            {
276                cDebug() << "Next level:" << *countryPart;
277                for (int variantnumber = 0; variantnumber < ui->listVariant->count(); ++variantnumber)
278                {
279                    LayoutItem *variantdata = dynamic_cast< LayoutItem* >( ui->listVariant->item( variantnumber ) );
280                    if ( variantdata && (variantdata->data.compare( *countryPart, Qt::CaseInsensitive ) == 0) )
281                    {
282                        ui->listVariant->setCurrentItem( variantdata );
283                        cDebug() << " .. matched variant" << variantdata->data << ' ' << variantdata->text();
284                    }
285                }
286            }
287        }
288    }
289}
290
291
292void
293KeyboardPage::onActivate()
294{
295    /* Guessing a keyboard layout based on the locale means
296     * mapping between language identifiers in <lang>_<country>
297     * format to keyboard mappings, which are <country>_<layout>
298     * format; in addition, some countries have multiple languages,
299     * so fr_BE and nl_BE want different layouts (both Belgian)
300     * and sometimes the language-country name doesn't match the
301     * keyboard-country name at all (e.g. Ellas vs. Greek).
302     *
303     * This is a table of language-to-keyboard mappings. The
304     * language identifier is the key, while the value is
305     * a string that is used instead of the real language
306     * identifier in guessing -- so it should be something
307     * like <layout>_<country>.
308     */
309    static constexpr char arabic[] = "ara";
310    static const auto specialCaseMap = QMap<std::string, std::string>( {
311        /* Most Arab countries map to Arabic keyboard (Default) */
312        { "ar_AE", arabic },
313        { "ar_BH", arabic },
314        { "ar_DZ", arabic },
315        { "ar_EG", arabic },
316        { "ar_IN", arabic },
317        { "ar_IQ", arabic },
318        { "ar_JO", arabic },
319        { "ar_KW", arabic },
320        { "ar_LB", arabic },
321        { "ar_LY", arabic },
322        /* Not Morocco: use layout ma */
323        { "ar_OM", arabic },
324        { "ar_QA", arabic },
325        { "ar_SA", arabic },
326        { "ar_SD", arabic },
327        { "ar_SS", arabic },
328        /* Not Syria: use layout sy */
329        { "ar_TN", arabic },
330        { "ar_YE", arabic },
331        { "ca_ES", "cat_ES" }, /* Catalan */
332        { "as_ES", "ast_ES" }, /* Asturian */
333        { "en_CA", "eng_CA" }, /* Canadian English */
334        { "el_CY", "gr" },     /* Greek in Cyprus */
335        { "el_GR", "gr" },     /* Greek in Greeze */
336        { "ig_NG", "igbo_NG" }, /* Igbo in Nigeria */
337        { "ha_NG", "hausa_NG" } /* Hausa */
338    } );
339
340    ui->listLayout->setFocus();
341
342    // Try to preselect a layout, depending on language and locale
343    Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
344    QString lang = gs->value( "localeConf" ).toMap().value( "LANG" ).toString();
345
346    cDebug() << "Got locale language" << lang;
347    if ( !lang.isEmpty() )
348    {
349        // Chop off .codeset and @modifier
350        int index = lang.indexOf( '.' );
351        if ( index >= 0 )
352            lang.truncate( index );
353        index = lang.indexOf( '@' );
354        if ( index >= 0 )
355            lang.truncate( index );
356
357        lang.replace( '-', '_' );  // Normalize separators
358    }
359    if ( !lang.isEmpty() && specialCaseMap.contains( lang.toStdString() ) )
360    {
361        QLatin1String newLang( specialCaseMap.value( lang.toStdString() ).c_str() );
362        cDebug() << " .. special case language" << lang << '>' << newLang;
363        lang = newLang;
364    }
365    if ( !lang.isEmpty() )
366    {
367        const auto langParts = lang.split( '_', QString::SkipEmptyParts );
368
369        QString country = QLocale::countryToString( QLocale( lang ).country() );
370        cDebug() << " .. extracted country" << country << "::" << langParts;
371
372        guessLayout( langParts );
373    }
374}
375
376
377void
378KeyboardPage::finalize()
379{
380    Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
381    if ( !m_selectedLayout.isEmpty() )
382    {
383        gs->insert( "keyboardLayout", m_selectedLayout );
384        gs->insert( "keyboardVariant", m_selectedVariant ); //empty means default variant
385    }
386
387    //FIXME: also store keyboard model for something?
388}
389
390
391void
392KeyboardPage::updateVariants( const QPersistentModelIndex& currentItem,
393                              QString currentVariant )
394{
395    // Block signals
396    ui->listVariant->blockSignals( true );
397
398    QMap< QString, QString > variants =
399        currentItem.data( KeyboardLayoutModel::KeyboardVariantsRole )
400        .value< QMap< QString, QString > >();
401    QMapIterator< QString, QString > li( variants );
402    LayoutItem* defaultItem = nullptr;
403
404    ui->listVariant->clear();
405
406    while ( li.hasNext() )
407    {
408        li.next();
409
410        LayoutItem* item = new LayoutItem();
411        item->setText( li.key() );
412        item->data = li.value();
413        ui->listVariant->addItem( item );
414
415        // currentVariant defaults to QString(). It is only non-empty during the
416        // initial setup.
417        if ( li.value() == currentVariant )
418            defaultItem = item;
419    }
420
421    // Unblock signals
422    ui->listVariant->blockSignals( false );
423
424    // Set to default value
425    if ( defaultItem )
426        ui->listVariant->setCurrentItem( defaultItem );
427}
428
429
430void
431KeyboardPage::onListLayoutCurrentItemChanged( const QModelIndex& current,
432        const QModelIndex& previous )
433{
434    Q_UNUSED( previous );
435    if ( !current.isValid() )
436        return;
437
438    updateVariants( QPersistentModelIndex( current ) );
439}
440
441/* Returns stringlist with suitable setxkbmap command-line arguments
442 * to set the given @p layout and @p variant.
443 */
444static inline QStringList xkbmap_args( QStringList&& r, const QString& layout, const QString& variant )
445{
446    r << "-layout" << layout;
447    if ( !variant.isEmpty() )
448        r << "-variant" << variant;
449    return r;
450}
451
452void
453KeyboardPage::onListVariantCurrentItemChanged( QListWidgetItem* current, QListWidgetItem* previous )
454{
455    Q_UNUSED( previous );
456
457    QPersistentModelIndex layoutIndex = ui->listLayout->currentIndex();
458    LayoutItem* variantItem = dynamic_cast< LayoutItem* >( current );
459
460    if ( !layoutIndex.isValid() || !variantItem )
461        return;
462
463    QString layout = layoutIndex.data( KeyboardLayoutModel::KeyboardLayoutKeyRole ).toString();
464    QString variant = variantItem->data;
465
466    m_keyboardPreview->setLayout( layout );
467    m_keyboardPreview->setVariant( variant );
468
469    //emit checkReady();
470
471    // Set Xorg keyboard layout
472    if ( m_setxkbmapTimer.isActive() )
473    {
474        m_setxkbmapTimer.stop();
475        m_setxkbmapTimer.disconnect( this );
476    }
477
478    connect( &m_setxkbmapTimer, &QTimer::timeout,
479             this, [=]
480    {
481        QProcess::execute( QLatin1Literal( "setxkbmap" ),
482                           xkbmap_args( QStringList(), layout, variant ) );
483        cDebug() << "xkbmap selection changed to: " << layout << "-" << variant;
484        m_setxkbmapTimer.disconnect( this );
485    } );
486    m_setxkbmapTimer.start( QApplication::keyboardInputInterval() );
487
488    m_selectedLayout = layout;
489    m_selectedVariant = variant;
490}
Note: See TracBrowser for help on using the repository browser.