not quite so much needs to be delayed to the init() function
[personal-kdebase.git] / workspace / kcontrol / kxkb / x11helper.cpp
blobffa7430aad5425cea5600bf9cdc1a482792f33ec
1 /*
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.
19 #include <QDir>
20 #include <QRegExp>
21 #include <QTextStream>
22 #include <QX11Info>
24 #include <kdebug.h>
26 #ifndef HAVE_XKLAVIER
27 #include <kglobal.h>
28 #include <klocale.h>
29 #endif
32 #include <X11/Xlib.h>
33 #include <X11/Xatom.h>
34 #define explicit int_explicit // avoid compiler name clash in XKBlib.h
35 #include <X11/XKBlib.h>
36 #undef explicit
37 #include <X11/extensions/XKBrules.h>
39 #ifdef HAVE_XINPUT
40 #include <X11/extensions/XInput.h>
41 #endif
43 #include "kxkbconfig.h"
45 #include "x11helper.h"
46 #include <config-workspace.h>
48 #ifndef HAVE_XKLAVIER
50 // Compiler will size array automatically.
51 static const char* X11DirList[] =
53 XLIBDIR,
54 "/etc/X11/",
55 "/usr/share/X11/",
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/",
61 "/usr/lib/X11/",
62 "/usr/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[] =
72 "xkb/rules/base",
73 "xkb/rules/xorg",
74 "xkb/rules/xfree86"
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);
84 const QString
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++) {
92 // }
93 return QString(xDir);
96 // if( X11_DIR.isEmpty() ) {
97 // return;
98 // }
100 return NULL;
103 const QString
104 X11Helper::findXkbRulesFile(const QString &x11Dir, Display *dpy)
106 QString rulesFile;
107 XkbRF_VarDefsRec vd;
108 char *tmp = NULL;
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 ;
115 else {
116 // old way
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;
123 break;
128 return rulesFile;
131 RulesInfo*
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) {
137 // throw Exception
138 return 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);
154 return rulesInfo;
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 ) );
161 // }
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;
181 XkbOption option;
182 option.name = optionName;
183 option.description = xkbRules->options.desc[i].desc;
184 option.group = &rulesInfo->optionGroups[ groupName ];
186 rulesInfo->options.insert(optionName, option);
188 else {
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);
206 return rulesInfo;
210 XkbOptionGroup
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 );
219 return optionGroup;
222 bool
223 X11Helper::isGroupExclusive(const QString& groupName)
225 if( groupName == "ctrl" || groupName == "keypad" || groupName == "nbsp"
226 || groupName == "kpdl" || groupName == "caps" || groupName == "altwin" )
227 return true;
229 return false;
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
238 QList<XkbVariant>*
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() )
246 file += "pc/";
248 file += layout;
250 // kDebug() << "reading variants from " << file;
252 QFile f(file);
253 if (f.open(QIODevice::ReadOnly))
255 QTextStream ts(&f);
257 QString line;
258 QString prev_line;
260 while ( ts.status() == QTextStream::Ok ) {
261 prev_line = line;
263 QString str = ts.readLine();
264 if( str.isNull() )
265 break;
267 line = str.simplified();
269 if (line[0] == '#' || line.left(2) == "//" || line.isEmpty())
270 continue;
272 int pos = line.indexOf("xkb_symbols");
273 if (pos < 0)
274 continue;
276 if( prev_line.indexOf("hidden") >=0 )
277 continue;
279 pos = line.indexOf('"', pos) + 1;
280 int pos2 = line.indexOf('"', pos);
281 if( pos < 0 || pos2 < 0 )
282 continue;
284 XkbVariant variant;
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);
291 f.close();
294 return result;
297 XkbConfig
298 X11Helper::getGroupNames(Display* dpy)
300 Atom real_prop_type;
301 int fmt;
302 unsigned long nitems, extra_bytes;
303 char *prop_data = NULL;
304 Status ret;
305 XkbConfig xkbConfig;
307 Atom rules_atom = XInternAtom(dpy, _XKB_RF_NAMES_PROP_ATOM, False);
309 /* no such atom! */
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;
312 return xkbConfig;
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";
325 return xkbConfig;
328 /* has to be array of strings */
329 if ((extra_bytes > 0) || (real_prop_type != XA_STRING)
330 || (fmt != 8)) {
331 if (prop_data)
332 XFree(prop_data);
333 kError() << "Failed to fetch layouts from server:" << "Wrong property format";
334 return xkbConfig;
337 kDebug() << "prop_data:" << nitems << prop_data;
338 QStringList names;
339 for(char* p=prop_data; p-prop_data < (long)nitems && p != NULL; p += strlen(p)+1) {
340 names.append( p );
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++) {
352 LayoutUnit lu;
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;
366 XFree(prop_data);
367 return xkbConfig;
370 #endif /* HAVE_XKLAVIER*/
372 #ifdef HAVE_XINPUT
374 int X11Helper::m_xinputEventType = -1;
376 extern "C" {
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;
387 int ndevices;
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;
396 break;
399 XFreeDeviceList(devices);
401 return keyboard_device;
404 return false;
408 X11Helper::registerForNewDeviceEvent(Display* display)
410 int xitype;
411 XEventClass xiclass;
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;
417 return xitype;
419 #else
421 X11Helper::isNewDeviceEvent(XEvent* event)
423 return false;
426 X11Helper::registerForNewDeviceEvent(Display* display)
428 kWarn() << "Kxkb is compiled without XInput, xkb configuration will be reset when new keyboard device is plugged in!";
429 return -1;
431 #endif
434 const QString X11Helper::X11_WIN_CLASS_ROOT = "<root>";
435 const QString X11Helper::X11_WIN_CLASS_UNKNOWN = "<unknown>";
437 QString
438 X11Helper::getWindowClass(Window winId, Display* dpy)
440 unsigned long nitems_ret, bytes_after_ret;
441 unsigned char* prop_ret;
442 Atom type_ret;
443 int format_ret;
444 Window w = (Window)winId; // suppose WId == Window
445 QString property;
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));
457 XFree(prop_ret);
459 else {
460 property = X11_WIN_CLASS_UNKNOWN;
462 kDebug() << "Got window class for " << winId << ": '" << property << "'";
464 return property;