2 Copyright (C) 2001, S.R.Haque <srhaque@iee.org>.
3 Copyright (C) 2006, Andriy Rysin <rysin@kde.org>. Derived from an
4 original by Matthias H�zer-Klpfel released under the QPL.
5 This file is part of the KDE project
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
24 KDE Keyboard Tool. Manages XKB keyboard mappings.
26 #include <QDesktopWidget>
30 #include <kglobalaccel.h>
32 #include <kwindowsystem.h>
33 #include <kstandarddirs.h>
37 #include <ktoolinvocation.h>
38 #include <kglobalsettings.h>
39 #include <kactioncollection.h>
40 #include <kapplication.h>
42 #include "x11helper.h"
43 #include "extension.h"
46 #include "xklavier_adaptor.h"
48 #include "kxkbconfig.h"
49 #include "layoutmap.h"
50 #include "kxkbwidget.h"
54 #include "kxkbcore.moc"
58 This is dummy class just to handle x11 events
60 class DummyWidget
: public QWidget
64 DummyWidget(KxkbCore
* kxkb_
):
68 bool x11Event( XEvent
* e
) { return kxkb
->x11EventFilter(e
); }
72 KxkbCore::KxkbCore(int mode
):
75 m_eventsHandled(false),
77 m_layoutOwnerMap(NULL
),
80 actionCollection(NULL
),
85 m_extension
= new XKBExtension();
86 if( !m_extension
->init() ) {
87 kError() << "XKB initialization failed, exiting..." << endl
;
92 X11Helper::registerForNewDeviceEvent(QX11Info::display());
94 m_layoutOwnerMap
= new LayoutMap(m_kxkbConfig
);
100 delete actionCollection
;
104 delete m_layoutOwnerMap
;
107 int KxkbCore::newInstance()
109 if( m_status
== 0 && settingsRead() ) {
112 initSwitchingPolicy();
113 m_layoutOwnerMap
->reset();
124 void KxkbCore::initReactions()
126 if( ! m_eventsHandled
) {
127 m_dummyWidget
= new DummyWidget(this);
128 KApplication::kApplication()->installX11EventFilter(m_dummyWidget
);
130 XKlavierAdaptor::getInstance(QX11Info::display())->startListening();
132 m_eventsHandled
= true;
138 void KxkbCore::cleanup()
140 kDebug() << "cleaning up";
141 if( m_dummyWidget
!= NULL
) {
143 XKlavierAdaptor::getInstance(QX11Info::display())->stopListening();
145 KApplication::kApplication()->removeX11EventFilter(m_dummyWidget
);
146 delete m_dummyWidget
;
147 m_dummyWidget
= NULL
;
148 m_eventsHandled
= false;
153 void KxkbCore::initKDEShortcut()
155 if( m_mode
== KXKB_MAIN
&& !m_kxkbConfig
.m_indicatorOnly
) { // TODO: should component react to kde shortcut?
156 if( actionCollection
== NULL
) {
157 actionCollection
= new KActionCollection( this );
159 #include "kxkbbindings.cpp"
160 connect(a
, SIGNAL(triggered()), this, SLOT(toggled()));
161 connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), this, SLOT(settingsChanged(int)));
163 KAction
* kAction
= static_cast<KAction
*>(actionCollection
->action(0));
164 kDebug() << "kde shortcut" << kAction
->globalShortcut().toString();
171 const KShortcut
* KxkbCore::getKDEShortcut() {
172 if( actionCollection
== NULL
)
174 KAction
* kAction
= static_cast<KAction
*>(actionCollection
->action(0));
178 return &kAction
->globalShortcut();
181 void KxkbCore::stopKDEShortcut()
183 if( actionCollection
!= NULL
) {
184 KAction
* kAction
= static_cast<KAction
*>(actionCollection
->action(0));
185 disconnect(kAction
, SIGNAL(triggered()), this, SLOT(toggled()));
186 disconnect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), this, SLOT(settingsChanged(int)));
187 actionCollection
->clear();
188 delete actionCollection
;
189 actionCollection
= NULL
;
193 void KxkbCore::settingsChanged(int category
)
195 if ( category
== KGlobalSettings::SETTINGS_SHORTCUTS
) {
196 // TODO: can we do it more efficient or recreating action collection is the only way?
202 bool KxkbCore::settingsRead()
204 m_kxkbConfig
.load( KxkbConfig::LOAD_ACTIVE_OPTIONS
);
206 if ( m_kxkbConfig
.m_useKxkb
== false ) {
207 kWarning() << "Kxkb is disabled, exiting...";
212 if( m_rules
== NULL
)
213 m_rules
= new XkbRules(false);
215 if( m_mode
== KXKB_MAIN
&& ! m_kxkbConfig
.m_indicatorOnly
) {
216 m_currentLayout
= m_kxkbConfig
.getDefaultLayout();
220 // for component or indicator we don't need owner map
221 m_kxkbConfig
.m_switchingPolicy
= SWITCH_POLICY_GLOBAL
;
222 updateGroupsFromServer();
225 if( m_kxkbConfig
.m_layouts
.count() == 1 ) {
226 if( m_kxkbConfig
.m_showSingle
== false ) {
227 kWarning() << "Kxkb is hidden for single layout";
236 void KxkbCore::initSwitchingPolicy()
238 disconnect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId
)), this, SLOT(windowChanged(WId
)));
239 disconnect(KWindowSystem::self(), SIGNAL(currentDesktopChanged(int)), this, SLOT(desktopChanged(int)));
241 if( m_kxkbConfig
.m_switchingPolicy
!= SWITCH_POLICY_GLOBAL
242 && m_mode
== KXKB_MAIN
&& !m_kxkbConfig
.m_indicatorOnly
) {
243 QDesktopWidget desktopWidget
;
244 if( desktopWidget
.numScreens() > 1 && desktopWidget
.isVirtualDesktop() == false ) {
245 kWarning() << "With non-virtual desktop only global switching policy supported on non-primary screens" ;
246 //TODO: find out how to handle that
249 if( m_kxkbConfig
.m_switchingPolicy
== SWITCH_POLICY_DESKTOP
) {
250 connect(KWindowSystem::self(), SIGNAL(currentDesktopChanged(int)), this, SLOT(desktopChanged(int)));
253 connect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId
)), this, SLOT(windowChanged(WId
)));
258 void KxkbCore::initLayoutGroups()
261 QStringList variants
;
262 for(int ii
=0; ii
<(int)m_kxkbConfig
.m_layouts
.count(); ii
++) {
263 LayoutUnit
& layoutUnit
= m_kxkbConfig
.m_layouts
[ii
];
264 layouts
<< layoutUnit
.layout
;
265 variants
<< layoutUnit
.variant
;
267 m_extension
->setLayoutGroups(m_kxkbConfig
.m_model
, layouts
, variants
,
268 m_kxkbConfig
.m_options
, m_kxkbConfig
.m_resetOldOptions
);
271 void KxkbCore::setWidget(KxkbWidget
* kxkbWidget
)
274 kError() << "kxkb did not initialize - ignoring set widget" << endl
;
278 if( m_kxkbWidget
!= NULL
) {
279 kDebug() << "destroying old kxkb widget";
280 disconnect(m_kxkbWidget
, SIGNAL(menuTriggered(QAction
*)), this, SLOT(iconMenuTriggered(QAction
*)));
281 disconnect(m_kxkbWidget
, SIGNAL(iconToggled()), this, SLOT(toggled()));
285 m_kxkbWidget
= kxkbWidget
;
286 if( m_kxkbWidget
!= NULL
) {
287 connect(m_kxkbWidget
, SIGNAL(menuTriggered(QAction
*)), this, SLOT(iconMenuTriggered(QAction
*)));
288 connect(m_kxkbWidget
, SIGNAL(iconToggled()), this, SLOT(toggled()));
290 if( m_rules
!= NULL
) // settings already read
295 void KxkbCore::initTray()
297 if( m_kxkbWidget
!= NULL
) {
298 bool visible
= m_kxkbConfig
.m_layouts
.count() > 1 || m_kxkbConfig
.m_showSingle
;
299 kDebug() << "initing tray, visible:" << visible
;
301 m_kxkbWidget
->setShowFlag(m_kxkbConfig
.m_showFlag
);
302 m_kxkbWidget
->initLayoutList(m_kxkbConfig
.m_layouts
, *m_rules
);
303 m_kxkbWidget
->setCurrentLayout(m_kxkbConfig
.m_layouts
[m_currentLayout
]);
304 m_kxkbWidget
->setVisible( visible
);
308 // This function activates the keyboard layout specified by the
309 // configuration members (m_currentLayout)
310 void KxkbCore::layoutApply()
312 setLayout(m_currentLayout
);
316 bool KxkbCore::setLayout(const QString
& layoutPair
)
318 const LayoutUnit
layoutUnitKey(layoutPair
);
319 if( m_kxkbConfig
.m_layouts
.contains(layoutUnitKey
) ) {
320 int ind
= m_kxkbConfig
.m_layouts
.indexOf(layoutUnitKey
);
321 return setLayout( ind
);
327 // Activates the keyboard layout specified by layout
328 bool KxkbCore::setLayout(int layout
)
330 bool res
= m_extension
->setGroup(layout
);
332 updateIndicator(layout
, res
);
337 void KxkbCore::updateIndicator(int layout
, int res
)
339 if( layout
>= GROUP_LIMIT
|| layout
>= m_kxkbConfig
.m_layouts
.count() ) {
343 LayoutUnit
lu( i18n("Group %1", layout
+1), "" );
344 lu
.setDisplayName( QString("%1").arg(layout
+1) );
345 m_kxkbWidget
->setCurrentLayout(lu
);
347 kWarning() << "group is out of my range, seems like old style groups are used";
351 m_error
= ( res
> 0 );
354 m_currentLayout
= layout
;
355 m_layoutOwnerMap
->ownerChanged();
356 m_layoutOwnerMap
->setCurrentLayout(layout
);
360 const LayoutUnit
& lu
= m_kxkbConfig
.m_layouts
[layout
];
363 m_kxkbWidget
->setCurrentLayout(lu
);
365 m_kxkbWidget
->setError(lu
.toPair());
369 void KxkbCore::toggled()
371 if( m_kxkbConfig
.m_layouts
.count() <= 1 )
374 int layout
= m_layoutOwnerMap
->getNextLayout();
378 void KxkbCore::iconMenuTriggered(QAction
* action
)
380 int id
= action
->data().toInt();
382 if( KxkbWidget::START_MENU_ID
<= id
383 && id
< KxkbWidget::START_MENU_ID
+ (int)m_kxkbConfig
.m_layouts
.count() )
385 if( m_kxkbConfig
.m_layouts
.count() <= 1 )
388 int layout
= id
- KxkbWidget::START_MENU_ID
;
389 m_layoutOwnerMap
->setCurrentLayout( layout
);
392 else if (id
== KxkbWidget::CONFIG_MENU_ID
)
395 lst
<< "keyboard_layout";
396 QProcess::startDetached("kcmshell4", lst
);
400 void KxkbCore::desktopChanged(int desktop
)
402 kDebug() << "desktop changed" << desktop
;
406 // TODO: we also have to handle deleted windows
407 void KxkbCore::windowChanged(WId winId
)
409 if( m_kxkbConfig
.m_switchingPolicy
== SWITCH_POLICY_GLOBAL
) { // should not happen actually
410 kDebug() << "windowChanged() signal in GLOBAL switching policy";
414 kDebug() << "active window changed new WinId: " << winId
;
416 if( m_kxkbConfig
.m_switchingPolicy
== SWITCH_POLICY_GLOBAL
417 || winId
!= X11Helper::UNKNOWN_WINDOW_ID
) {
419 m_layoutOwnerMap
->ownerChanged();
420 int layoutState
= m_layoutOwnerMap
->getCurrentLayout();
422 if( layoutState
!= m_currentLayout
) {
423 setLayout(layoutState
);
430 bool KxkbCore::x11EventFilter ( XEvent
* event
)
433 XKlavierAdaptor::getInstance(QX11Info::display())->filterEvents(event
);
436 if( m_extension
->isXkbEvent(event
) ) {
437 // qApp->x11ProcessEvent ( event );
439 if( XKBExtension::isGroupSwitchEvent(event
) ) {
441 int group
= m_extension
->getGroup();
442 if( group
!= m_currentLayout
|| m_error
) {
443 kDebug() << "got event: group changed to " << group
;
444 updateIndicator(group
, 1);
447 else if( XKBExtension::isLayoutSwitchEvent(event
) ) {
448 kDebug() << "got event: layouts changed";
449 updateGroupsFromServer();
452 // kDebug() << "other xkb event: ";// + ((XkbEvent*)event)->any.xkb_type;
456 if( X11Helper::isNewDeviceEvent(event
) ) {
459 // else kDebug() << "other x11 event, type" << event->type;
465 KxkbCore::updateGroupsFromServer()
467 kDebug() << "updating groups from server";
470 XkbConfig xkbConfig
= XKlavierAdaptor::getInstance(QX11Info::display())->getGroupNames();
472 XkbConfig xkbConfig
= X11Helper::getGroupNames(QX11Info::display());
475 int group
= m_extension
->getGroup();
476 kDebug() << " active group" << group
;
478 const QList
<LayoutUnit
>& lus
= xkbConfig
.layouts
;
479 if( lus
.count() > 0 ) {
480 if( lus
!= m_kxkbConfig
.m_layouts
) {
481 if( group
>= m_kxkbConfig
.m_layouts
.count() )
483 m_currentLayout
= group
;
484 m_kxkbConfig
.setConfiguredLayouts(xkbConfig
);
485 m_layoutOwnerMap
->reset();
489 kDebug() << " no change in layouts";
491 updateIndicator(group
, 1);
494 kWarning() << " failed to get layouts from server";
495 if( m_currentLayout
!= group
&& group
< m_kxkbConfig
.m_layouts
.count() ) {
496 kDebug() << " ...tryin to set at least group" << group
;
497 updateIndicator(group
, 1);
500 // kDebug() << "updating layouts from server is not implemented w/out libxklavier";