~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to kcontrol/keyboard/x11_helper.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  Copyright (C) 2010 Andriy Rysin (rysin@kde.org)
 
3
 *
 
4
 *  This program is free software; you can redistribute it and/or modify
 
5
 *  it under the terms of the GNU General Public License as published by
 
6
 *  the Free Software Foundation; either version 2 of the License, or
 
7
 *  (at your option) any later version.
 
8
 *
 
9
 *  This program is distributed in the hope that it will be useful,
 
10
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
 *  GNU General Public License for more details.
 
13
 *
 
14
 *  You should have received a copy of the GNU General Public License
 
15
 *  along with this program; if not, write to the Free Software
 
16
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
17
 */
 
18
 
 
19
#include "x11_helper.h"
 
20
 
 
21
#include <kapplication.h>
 
22
#include <kdebug.h>
 
23
 
 
24
#include <QtGui/QX11Info>
 
25
 
 
26
#include <X11/X.h>
 
27
#include <X11/Xlib.h>
 
28
#include <X11/Xatom.h>
 
29
#include <X11/XKBlib.h>
 
30
#include <X11/extensions/XKBrules.h>
 
31
#include <fixx11h.h>
 
32
 
 
33
 
 
34
// more information about the limit https://bugs.freedesktop.org/show_bug.cgi?id=19501
 
35
int X11Helper::MAX_GROUP_COUNT = 4;
 
36
const char* X11Helper::LEFT_VARIANT_STR = "(";
 
37
const char* X11Helper::RIGHT_VARIANT_STR = ")";
 
38
 
 
39
bool X11Helper::xkbSupported(int* xkbOpcode)
 
40
{
 
41
    // Verify the Xlib has matching XKB extension.
 
42
 
 
43
    int major = XkbMajorVersion;
 
44
    int minor = XkbMinorVersion;
 
45
 
 
46
    if (!XkbLibraryVersion(&major, &minor))
 
47
    {
 
48
        kWarning() << "Xlib XKB extension " << major << '.' << minor <<
 
49
            " != " << XkbMajorVersion << '.' << XkbMinorVersion;
 
50
        return false;
 
51
    }
 
52
 
 
53
    // Verify the X server has matching XKB extension.
 
54
 
 
55
    int opcode_rtrn;
 
56
    int error_rtrn;
 
57
    int xkb_opcode;
 
58
    if( ! XkbQueryExtension(QX11Info::display(), &opcode_rtrn, &xkb_opcode, &error_rtrn, &major, &minor)) {
 
59
        kWarning() << "X server XKB extension " << major << '.' << minor <<
 
60
            " != " << XkbMajorVersion << '.' << XkbMinorVersion;
 
61
        return false;
 
62
    }
 
63
 
 
64
    if( xkbOpcode != NULL ) {
 
65
        *xkbOpcode = xkb_opcode;
 
66
    }
 
67
 
 
68
    return true;
 
69
}
 
70
 
 
71
void X11Helper::switchToNextLayout()
 
72
{
 
73
        int size = getLayoutsList().size();     //TODO: could optimize a bit as we don't need the layouts - just count
 
74
        int group = (X11Helper::getGroup() + 1) % size;
 
75
        X11Helper::setGroup(group);
 
76
}
 
77
 
 
78
void X11Helper::scrollLayouts(int delta)
 
79
{
 
80
        int size = getLayoutsList().size();     //TODO: could optimize a bit as we don't need the layouts - just count
 
81
        int group = X11Helper::getGroup() + delta;
 
82
        group = group < 0 ? size - ((-group) % size) : group % size;
 
83
 
 
84
        X11Helper::setGroup(group);
 
85
}
 
86
 
 
87
QStringList X11Helper::getLayoutsListAsString(const QList<LayoutUnit>& layoutsList)
 
88
{
 
89
        QStringList stringList;
 
90
        foreach(const LayoutUnit& layoutUnit, layoutsList) {
 
91
                stringList << layoutUnit.toString();
 
92
        }
 
93
        return stringList;
 
94
}
 
95
 
 
96
bool X11Helper::setLayout(const LayoutUnit& layout)
 
97
{
 
98
        QList<LayoutUnit> currentLayouts = getLayoutsList();
 
99
        int idx = currentLayouts.indexOf(layout);
 
100
        if( idx == -1 || idx >= X11Helper::MAX_GROUP_COUNT ) {
 
101
                kWarning() << "Layout" << layout.toString() << "is not found in current layout list"
 
102
                                                                << getLayoutsListAsString(currentLayouts);
 
103
                return false;
 
104
        }
 
105
 
 
106
        return X11Helper::setGroup((unsigned int)idx);
 
107
}
 
108
 
 
109
bool X11Helper::setDefaultLayout() {
 
110
        return X11Helper::setGroup(0);
 
111
}
 
112
 
 
113
bool X11Helper::isDefaultLayout() {
 
114
        return X11Helper::getGroup() == 0;
 
115
}
 
116
 
 
117
LayoutUnit X11Helper::getCurrentLayout()
 
118
{
 
119
        QList<LayoutUnit> currentLayouts = getLayoutsList();
 
120
        unsigned int group = X11Helper::getGroup();
 
121
        if( group < (unsigned int)currentLayouts.size() )
 
122
                return currentLayouts[group];
 
123
 
 
124
        kWarning() << "Current group number" << group << "is outside of current layout list" <<
 
125
                                                getLayoutsListAsString(currentLayouts);
 
126
        return LayoutUnit();
 
127
}
 
128
 
 
129
LayoutSet X11Helper::getCurrentLayouts()
 
130
{
 
131
        LayoutSet layoutSet;
 
132
 
 
133
        QList<LayoutUnit> currentLayouts = getLayoutsList();
 
134
        layoutSet.layouts = currentLayouts;
 
135
 
 
136
        unsigned int group = X11Helper::getGroup();
 
137
        if( group < (unsigned int)currentLayouts.size() ) {
 
138
                layoutSet.currentLayout = currentLayouts[group];
 
139
        }
 
140
        else {
 
141
                kWarning() << "Current group number" << group << "is outside of current layout list" << getLayoutsListAsString(currentLayouts);
 
142
                layoutSet.currentLayout = LayoutUnit();
 
143
        }
 
144
 
 
145
        return layoutSet;
 
146
}
 
147
 
 
148
 
 
149
//static QString addNum(const QString& str, int n)
 
150
//{
 
151
//    QString format("%1%2");
 
152
//    if( str.length() >= 3 ) return format.arg(str.left(2)).arg(n);
 
153
//    return format.arg(str).arg(n);
 
154
//}
 
155
 
 
156
QList<LayoutUnit> X11Helper::getLayoutsList()
 
157
{
 
158
        XkbConfig xkbConfig;
 
159
        QList<LayoutUnit> layouts;
 
160
        if( X11Helper::getGroupNames(QX11Info::display(), &xkbConfig, X11Helper::LAYOUTS_ONLY) ) {
 
161
                for(int i=0; i<xkbConfig.layouts.size(); i++) {
 
162
                        QString layout(xkbConfig.layouts[i]);
 
163
                        QString variant;
 
164
                        if( i<xkbConfig.variants.size() && ! xkbConfig.variants[i].isEmpty() ) {
 
165
                                variant = xkbConfig.variants[i];
 
166
                        }
 
167
                        layouts << LayoutUnit(layout, variant);
 
168
                }
 
169
                // if there are layouts with same map name add numbers to display name
 
170
//              for(int i=0; i<layouts.length(); i++) {
 
171
//                      int n=1;
 
172
//                      for(int j=i+1; j<layouts.length(); j++) {
 
173
//                              if( layouts[i].layout == layouts[j].layout && layouts[i].getRawDisplayName().isEmpty() ) {
 
174
//                                      layouts[i].setDisplayName( addNum(layouts[i].layout, 1) );
 
175
//                                      layouts[j].setDisplayName( addNum(layouts[j].layout, ++n) );
 
176
//                                      qDebug() << "Adding" << 1 << "to" << layouts[i].toString();
 
177
//                                      qDebug() << "Adding" << n << "to" << layouts[j].toString();
 
178
//                              }
 
179
//                      }
 
180
//              }
 
181
        }
 
182
        else {
 
183
                kWarning() << "Failed to get layout groups from X server";
 
184
        }
 
185
        return layouts;
 
186
}
 
187
 
 
188
bool X11Helper::setGroup(unsigned int group)
 
189
{
 
190
        return XkbLockGroup(QX11Info::display(), XkbUseCoreKbd, group);
 
191
}
 
192
 
 
193
unsigned int X11Helper::getGroup()
 
194
{
 
195
        XkbStateRec xkbState;
 
196
        XkbGetState( QX11Info::display(), XkbUseCoreKbd, &xkbState );
 
197
        return xkbState.group;
 
198
}
 
199
 
 
200
bool X11Helper::getGroupNames(Display* display, XkbConfig* xkbConfig, FetchType fetchType)
 
201
{
 
202
        static const char* OPTIONS_SEPARATOR = ",";
 
203
 
 
204
        Atom real_prop_type;
 
205
        int fmt;
 
206
        unsigned long nitems, extra_bytes;
 
207
        char *prop_data = NULL;
 
208
        Status ret;
 
209
 
 
210
        Atom rules_atom = XInternAtom(display, _XKB_RF_NAMES_PROP_ATOM, False);
 
211
 
 
212
        /* no such atom! */
 
213
        if (rules_atom == None) {       /* property cannot exist */
 
214
                kWarning() << "Failed to fetch layouts from server:" << "could not find the atom" << _XKB_RF_NAMES_PROP_ATOM;
 
215
                return false;
 
216
        }
 
217
 
 
218
        ret = XGetWindowProperty(display,
 
219
                        DefaultRootWindow(display),
 
220
                        rules_atom, 0L, _XKB_RF_NAMES_PROP_MAXLEN,
 
221
                        False, XA_STRING, &real_prop_type, &fmt,
 
222
                        &nitems, &extra_bytes,
 
223
                        (unsigned char **) (void *) &prop_data);
 
224
 
 
225
        /* property not found! */
 
226
        if (ret != Success) {
 
227
                kWarning() << "Failed to fetch layouts from server:" << "Could not get the property";
 
228
                return false;
 
229
        }
 
230
 
 
231
        /* has to be array of strings */
 
232
        if ((extra_bytes > 0) || (real_prop_type != XA_STRING) || (fmt != 8)) {
 
233
                if (prop_data)
 
234
                        XFree(prop_data);
 
235
                kWarning() << "Failed to fetch layouts from server:" << "Wrong property format";
 
236
                return false;
 
237
        }
 
238
 
 
239
//      qDebug() << "prop_data:" << nitems << prop_data;
 
240
        QStringList names;
 
241
        for(char* p=prop_data; p-prop_data < (long)nitems && p != NULL; p += strlen(p)+1) {
 
242
                names.append( p );
 
243
//              kDebug() << " " << p;
 
244
        }
 
245
 
 
246
        if( names.count() < 4 ) { //{ rules, model, layouts, variants, options }
 
247
                XFree(prop_data);
 
248
                return false;
 
249
        }
 
250
 
 
251
        if( fetchType == ALL || fetchType == LAYOUTS_ONLY ) {
 
252
                QStringList layouts = names[2].split(OPTIONS_SEPARATOR);
 
253
                QStringList variants = names[3].split(OPTIONS_SEPARATOR);
 
254
 
 
255
                for(int ii=0; ii<layouts.count(); ii++) {
 
256
                        xkbConfig->layouts << (layouts[ii] != NULL ? layouts[ii] : "");
 
257
                        xkbConfig->variants << (ii < variants.count() && variants[ii] != NULL ? variants[ii] : "");
 
258
                }
 
259
                kDebug() << "Fetched layout groups from X server:"
 
260
                                << "\tlayouts:" << xkbConfig->layouts
 
261
                                << "\tvariants:" << xkbConfig->variants;
 
262
        }
 
263
 
 
264
        if( fetchType == ALL || fetchType == MODEL_ONLY ) {
 
265
                xkbConfig->keyboardModel = (names[1] != NULL ? names[1] : "");
 
266
                kDebug() << "Fetched keyboard model from X server:" << xkbConfig->keyboardModel;
 
267
        }
 
268
 
 
269
        if( fetchType == ALL ) {
 
270
                if( names.count() >= 5 ) {
 
271
                        QString options = (names[4] != NULL ? names[4] : "");
 
272
                        xkbConfig->options = options.split(OPTIONS_SEPARATOR);
 
273
                        kDebug() << "Fetched xkbOptions from X server:" << options;
 
274
                }
 
275
        }
 
276
 
 
277
        XFree(prop_data);
 
278
        return true;
 
279
}
 
280
 
 
281
XEventNotifier::XEventNotifier(QWidget* parent):
 
282
                QWidget(parent),
 
283
                xkbOpcode(-1)
 
284
{
 
285
        if( KApplication::kApplication() == NULL ) {
 
286
                kWarning() << "Layout Widget won't work properly without KApplication instance";
 
287
        }
 
288
}
 
289
 
 
290
void XEventNotifier::start()
 
291
{
 
292
        if( KApplication::kApplication() != NULL && X11Helper::xkbSupported(&xkbOpcode) ) {
 
293
                registerForXkbEvents(QX11Info::display());
 
294
 
 
295
                // start the event loop
 
296
                KApplication::kApplication()->installX11EventFilter(this);
 
297
        }
 
298
}
 
299
 
 
300
void XEventNotifier::stop()
 
301
{
 
302
        if( KApplication::kApplication() != NULL ) {
 
303
                //TODO: unregister
 
304
        //    XEventNotifier::unregisterForXkbEvents(QX11Info::display());
 
305
 
 
306
                // stop the event loop
 
307
                KApplication::kApplication()->removeX11EventFilter(this);
 
308
        }
 
309
}
 
310
 
 
311
bool XEventNotifier::isXkbEvent(XEvent* event)
 
312
{
 
313
        return event->type == xkbOpcode;
 
314
}
 
315
 
 
316
bool XEventNotifier::processOtherEvents(XEvent* /*event*/)
 
317
{
 
318
        return true;
 
319
}
 
320
 
 
321
bool XEventNotifier::processXkbEvents(XEvent* event)
 
322
{
 
323
        if( XEventNotifier::isGroupSwitchEvent(event) ) {
 
324
                emit(layoutChanged());
 
325
        }
 
326
        else if( XEventNotifier::isLayoutSwitchEvent(event) ) {
 
327
                emit(layoutMapChanged());
 
328
        }
 
329
        return true;
 
330
}
 
331
 
 
332
bool XEventNotifier::x11Event(XEvent * event)
 
333
{
 
334
        //    qApp->x11ProcessEvent ( event );
 
335
        if( isXkbEvent(event) ) {
 
336
                processXkbEvents(event);
 
337
        }
 
338
        else {
 
339
                processOtherEvents(event);
 
340
        }
 
341
        return QWidget::x11Event(event);
 
342
}
 
343
 
 
344
bool XEventNotifier::isGroupSwitchEvent(XEvent* event)
 
345
{
 
346
    XkbEvent *xkbEvent = (XkbEvent*) event;
 
347
#define GROUP_CHANGE_MASK \
 
348
    ( XkbGroupStateMask | XkbGroupBaseMask | XkbGroupLatchMask | XkbGroupLockMask )
 
349
 
 
350
    return xkbEvent->any.xkb_type == XkbStateNotify && xkbEvent->state.changed & GROUP_CHANGE_MASK;
 
351
}
 
352
 
 
353
bool XEventNotifier::isLayoutSwitchEvent(XEvent* event)
 
354
{
 
355
    XkbEvent *xkbEvent = (XkbEvent*) event;
 
356
 
 
357
    return //( (xkbEvent->any.xkb_type == XkbMapNotify) && (xkbEvent->map.changed & XkbKeySymsMask) ) ||
 
358
/*        || ( (xkbEvent->any.xkb_type == XkbNamesNotify) && (xkbEvent->names.changed & XkbGroupNamesMask) || )*/
 
359
           (xkbEvent->any.xkb_type == XkbNewKeyboardNotify);
 
360
}
 
361
 
 
362
int XEventNotifier::registerForXkbEvents(Display* display)
 
363
{
 
364
    int eventMask = XkbNewKeyboardNotifyMask | XkbStateNotifyMask;
 
365
    if( ! XkbSelectEvents(display, XkbUseCoreKbd, eventMask, eventMask) ) {
 
366
        kWarning() << "Couldn't select desired XKB events";
 
367
        return false;
 
368
    }
 
369
    return true;
 
370
}
 
371
 
 
372
 
 
373
static const char* LAYOUT_VARIANT_SEPARATOR_PREFIX = "(";
 
374
static const char* LAYOUT_VARIANT_SEPARATOR_SUFFIX = ")";
 
375
 
 
376
static QString& stripVariantName(QString& variant)
 
377
{
 
378
        if( variant.endsWith(LAYOUT_VARIANT_SEPARATOR_SUFFIX) ) {
 
379
                int suffixLen = strlen(LAYOUT_VARIANT_SEPARATOR_SUFFIX);
 
380
                return variant.remove(variant.length()-suffixLen, suffixLen);
 
381
        }
 
382
        return variant;
 
383
}
 
384
 
 
385
LayoutUnit::LayoutUnit(const QString& fullLayoutName)
 
386
{
 
387
        QStringList lv = fullLayoutName.split(LAYOUT_VARIANT_SEPARATOR_PREFIX);
 
388
        layout = lv[0];
 
389
        variant = lv.size() > 1 ? stripVariantName(lv[1]) : "";
 
390
}
 
391
 
 
392
QString LayoutUnit::toString() const
 
393
{
 
394
        if( variant.isEmpty() )
 
395
                return layout;
 
396
 
 
397
        return layout + LAYOUT_VARIANT_SEPARATOR_PREFIX+variant+LAYOUT_VARIANT_SEPARATOR_SUFFIX;
 
398
}
 
399
 
 
400
const int LayoutUnit::MAX_LABEL_LENGTH = 3;