2 * Copyright (C) 2006 Andriy Rysin (rysin@kde.org)
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.
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.
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.
21 #include <QTextStream>
33 #include <X11/Xatom.h>
34 #define explicit int_explicit // avoid compiler name clash in XKBlib.h
35 #include <X11/XKBlib.h>
37 #include <X11/extensions/XKBrules.h>
40 #include <X11/extensions/XInput.h>
43 #include "kxkbconfig.h"
45 #include "x11helper.h"
46 #include <config-workspace.h>
50 // Compiler will size array automatically.
51 static const char* X11DirList
[] =
56 "/usr/local/share/X11/",
57 "/usr/X11R6/lib/X11/",
58 "/usr/X11R6/lib64/X11/",
59 "/usr/local/X11R6/lib/X11/",
60 "/usr/local/X11R6/lib64/X11/",
63 "/usr/local/lib/X11/",
64 "/usr/local/lib64/X11/",
65 "/usr/pkg/share/X11/",
66 "/usr/pkg/xorg/lib/X11/"
69 // Compiler will size array automatically.
70 static const char* rulesFileList
[] =
77 // Macro will return number of elements in any static array as long as the
78 // array has at least one element.
79 #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
81 static const int X11_DIR_COUNT
= ARRAY_SIZE(X11DirList
);
82 static const int X11_RULES_COUNT
= ARRAY_SIZE(rulesFileList
);
85 X11Helper::findX11Dir()
87 for(int ii
=0; ii
<X11_DIR_COUNT
; ii
++) {
88 const char* xDir
= X11DirList
[ii
];
89 if( xDir
!= NULL
&& QDir(QString(xDir
) + "xkb").exists() ) {
90 // for(int jj=0; jj<X11_RULES_COUNT; jj++) {
96 // if( X11_DIR.isEmpty() ) {
104 X11Helper::findXkbRulesFile(const QString
&x11Dir
, Display
*dpy
)
110 if (XkbRF_GetNamesProp(dpy
, &tmp
, &vd
) && tmp
!= NULL
) {
111 // kDebug() << "namesprop " << tmp ;
112 rulesFile
= x11Dir
+ QString("xkb/rules/%1").arg(tmp
);
113 // kDebug() << "rulesF " << rulesFile ;
117 for(int ii
=0; ii
<X11_RULES_COUNT
; ii
++) {
118 const char* ruleFile
= rulesFileList
[ii
];
119 QString xruleFilePath
= x11Dir
+ ruleFile
;
120 kDebug() << "trying xrules path " << xruleFilePath
;
121 if( QFile(xruleFilePath
).exists() ) {
122 rulesFile
= xruleFilePath
;
132 X11Helper::loadRules(const QString
& file
, bool layoutsOnly
)
134 XkbRF_RulesPtr xkbRules
= XkbRF_Load(QFile::encodeName(file
).data(), (char*)"", true, true);
136 if (xkbRules
== NULL
) {
141 // try to translate layout names by countries in desktop_kdebase
142 // this is poor man's translation as it's good only for layout names and only those which match country names
143 KGlobal::locale()->insertCatalog("desktop_kdebase");
145 RulesInfo
* rulesInfo
= new RulesInfo();
147 for (int i
= 0; i
< xkbRules
->layouts
.num_desc
; ++i
) {
148 QString
layoutName(xkbRules
->layouts
.desc
[i
].name
);
149 rulesInfo
->layouts
.insert( layoutName
, i18nc("Name", xkbRules
->layouts
.desc
[i
].desc
) );
152 if( layoutsOnly
== true ) {
153 XkbRF_Free(xkbRules
, true);
157 // for (int i = 0; i < xkbRules->variants.num_desc; ++i) {
158 // kDebug() << "var:" << xkbRules->variants.desc[i].name << "@" << xkbRules->variants.desc[i].name;
159 // if( xkbRules->variants.desc[i].
160 // rulesInfo->variants.insert(xkbRules->models.desc[i].name, QString( xkbRules->models.desc[i].desc ) );
163 for (int i
= 0; i
< xkbRules
->models
.num_desc
; ++i
)
164 rulesInfo
->models
.insert(xkbRules
->models
.desc
[i
].name
, QString( xkbRules
->models
.desc
[i
].desc
) );
166 for (int i
= 0; i
< xkbRules
->options
.num_desc
; ++i
) {
167 QString optionName
= xkbRules
->options
.desc
[i
].name
;
169 int colonPos
= optionName
.indexOf(':');
170 QString groupName
= optionName
.mid(0, colonPos
);
173 if( colonPos
!= -1 ) {
174 //kDebug() << " option: " << optionName;
176 if( ! rulesInfo
->optionGroups
.contains( groupName
) ) {
177 rulesInfo
->optionGroups
.insert(groupName
, createMissingGroup(groupName
));
178 kDebug() << " added missing option group: " << groupName
;
182 option
.name
= optionName
;
183 option
.description
= xkbRules
->options
.desc
[i
].desc
;
184 option
.group
= &rulesInfo
->optionGroups
[ groupName
];
186 rulesInfo
->options
.insert(optionName
, option
);
189 if( groupName
== "Compose" )
190 groupName
= "compose";
191 if( groupName
== "compat" )
192 groupName
= "numpad";
194 XkbOptionGroup optionGroup
;
195 optionGroup
.name
= groupName
;
196 optionGroup
.description
= xkbRules
->options
.desc
[i
].desc
;
197 optionGroup
.exclusive
= isGroupExclusive( groupName
);
199 //kDebug() << " option group: " << groupName;
200 rulesInfo
->optionGroups
.insert(groupName
, optionGroup
);
204 XkbRF_Free(xkbRules
, true);
211 X11Helper::createMissingGroup(const QString
& groupName
)
213 // workaround for empty 'compose' options group description
214 XkbOptionGroup optionGroup
;
215 optionGroup
.name
= groupName
;
216 // optionGroup.description = "";
217 optionGroup
.exclusive
= isGroupExclusive( groupName
);
223 X11Helper::isGroupExclusive(const QString
& groupName
)
225 if( groupName
== "ctrl" || groupName
== "keypad" || groupName
== "nbsp"
226 || groupName
== "kpdl" || groupName
== "caps" || groupName
== "altwin" )
233 /* pretty simple algorithm - reads the layout file and
234 tries to find "xkb_symbols"
235 also checks whether previous line contains "hidden" to skip it
239 X11Helper::getVariants(const QString
& layout
, const QString
& x11Dir
)
241 QList
<XkbVariant
>* result
= new QList
<XkbVariant
>();
243 QString file
= x11Dir
+ "xkb/symbols/";
244 // workaround for XFree 4.3 new directory for one-group layouts
245 if( QDir(file
+"pc").exists() )
250 // kDebug() << "reading variants from " << file;
253 if (f
.open(QIODevice::ReadOnly
))
260 while ( ts
.status() == QTextStream::Ok
) {
263 QString str
= ts
.readLine();
267 line
= str
.simplified();
269 if (line
[0] == '#' || line
.left(2) == "//" || line
.isEmpty())
272 int pos
= line
.indexOf("xkb_symbols");
276 if( prev_line
.indexOf("hidden") >=0 )
279 pos
= line
.indexOf('"', pos
) + 1;
280 int pos2
= line
.indexOf('"', pos
);
281 if( pos
< 0 || pos2
< 0 )
285 variant
.name
= line
.mid(pos
, pos2
-pos
);
286 variant
.description
= line
.mid(pos
, pos2
-pos
);
287 result
->append(variant
);
288 // kDebug() << "adding variant " << line.mid(pos, pos2-pos);
298 X11Helper::getGroupNames(Display
* dpy
)
302 unsigned long nitems
, extra_bytes
;
303 char *prop_data
= NULL
;
307 Atom rules_atom
= XInternAtom(dpy
, _XKB_RF_NAMES_PROP_ATOM
, False
);
310 if (rules_atom
== None
) { /* property cannot exist */
311 kError() << "Failed to fetch layouts from server:" << "could not find the atom" << _XKB_RF_NAMES_PROP_ATOM
;
315 ret
= XGetWindowProperty(dpy
,
316 QX11Info::appRootWindow(),
317 rules_atom
, 0L, _XKB_RF_NAMES_PROP_MAXLEN
,
318 False
, XA_STRING
, &real_prop_type
, &fmt
,
319 &nitems
, &extra_bytes
,
320 (unsigned char **) (void *) &prop_data
);
322 /* property not found! */
323 if (ret
!= Success
) {
324 kError() << "Failed to fetch layouts from server:" << "Could not get the property";
328 /* has to be array of strings */
329 if ((extra_bytes
> 0) || (real_prop_type
!= XA_STRING
)
333 kError() << "Failed to fetch layouts from server:" << "Wrong property format";
337 kDebug() << "prop_data:" << nitems
<< prop_data
;
339 for(char* p
=prop_data
; p
-prop_data
< (long)nitems
&& p
!= NULL
; p
+= strlen(p
)+1) {
341 kDebug() << " " << p
;
344 if( names
.count() >= 4 ) { //{ rules, model, layouts, variants, options }
345 xkbConfig
.model
= names
[1];
346 // kDebug() << "model:" << xkbConfig.model;
348 QStringList layouts
= names
[2].split(KxkbConfig::OPTIONS_SEPARATOR
);
349 QStringList variants
= names
[3].split(KxkbConfig::OPTIONS_SEPARATOR
);
351 for(int ii
=0; ii
<layouts
.count(); ii
++) {
353 lu
.layout
= layouts
[ii
];
354 lu
.variant
= ii
< variants
.count() ? variants
[ii
] : "";
355 xkbConfig
.layouts
<< lu
;
356 kDebug() << "layout nm:" << lu
.layout
<< "variant:" << lu
.variant
;
359 if( names
.count() >= 5 ) {
360 QString options
= names
[4];
361 xkbConfig
.options
= options
.split(KxkbConfig::OPTIONS_SEPARATOR
);
362 kDebug() << "options:" << options
;
370 #endif /* HAVE_XKLAVIER*/
374 int X11Helper::m_xinputEventType
= -1;
377 extern int _XiGetDevicePresenceNotifyEvent(Display
*);
381 X11Helper::isNewDeviceEvent(XEvent
* event
)
383 if( m_xinputEventType
!= -1 && event
->type
== m_xinputEventType
) {
384 XDevicePresenceNotifyEvent
*xdpne
= (XDevicePresenceNotifyEvent
*) event
;
385 if( xdpne
->devchange
== DeviceEnabled
) {
386 bool keyboard_device
= false;
388 XDeviceInfo
*devices
= XListInputDevices(xdpne
->display
, &ndevices
);
389 if( devices
!= NULL
) {
390 kDebug() << "New device id:" << xdpne
->deviceid
;
391 for(int i
=0; i
<ndevices
; i
++) {
392 kDebug() << "id:" << devices
[i
].id
<< "name:" << devices
[i
].name
<< "used as:" << devices
[i
].use
;
393 if( devices
[i
].id
== xdpne
->deviceid
394 && (devices
[i
].use
== IsXKeyboard
|| devices
[i
].use
== IsXExtensionKeyboard
) ) {
395 keyboard_device
= true;
399 XFreeDeviceList(devices
);
401 return keyboard_device
;
408 X11Helper::registerForNewDeviceEvent(Display
* display
)
413 DevicePresence(display
, xitype
, xiclass
);
414 XSelectExtensionEvent(display
, QX11Info::appRootWindow(), &xiclass
, 1);
415 kDebug() << "Registered for new device events from XInput, class" << xitype
;
416 m_xinputEventType
= xitype
;
421 X11Helper::isNewDeviceEvent(XEvent
* event
)
426 X11Helper::registerForNewDeviceEvent(Display
* display
)
428 kWarn() << "Kxkb is compiled without XInput, xkb configuration will be reset when new keyboard device is plugged in!";
434 const QString
X11Helper::X11_WIN_CLASS_ROOT
= "<root>";
435 const QString
X11Helper::X11_WIN_CLASS_UNKNOWN
= "<unknown>";
438 X11Helper::getWindowClass(Window winId
, Display
* dpy
)
440 unsigned long nitems_ret
, bytes_after_ret
;
441 unsigned char* prop_ret
;
444 Window w
= (Window
)winId
; // suppose WId == Window
447 if( winId
== X11Helper::UNKNOWN_WINDOW_ID
) {
448 kDebug() << "Got window class for " << winId
<< ": '" << X11_WIN_CLASS_ROOT
<< "'";
449 return X11_WIN_CLASS_ROOT
;
452 // kDebug() << "Getting window class for " << winId;
453 if((XGetWindowProperty(dpy
, w
, XA_WM_CLASS
, 0L, 256L, 0, XA_STRING
,
454 &type_ret
, &format_ret
, &nitems_ret
,
455 &bytes_after_ret
, &prop_ret
) == Success
) && (type_ret
!= None
)) {
456 property
= QString::fromLocal8Bit(reinterpret_cast<char*>(prop_ret
));
460 property
= X11_WIN_CLASS_UNKNOWN
;
462 kDebug() << "Got window class for " << winId
<< ": '" << property
<< "'";