/* Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. Derived from an original by Matthias H�zer-Klpfel released under the QPL. This file is part of the KDE project This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. DESCRIPTION KDE Keyboard Tool. Manages XKB keyboard mappings. */ #include <unistd.h> #include <stdlib.h> #include <assert.h> #include <tqregexp.h> #include <tqfile.h> #include <tqstringlist.h> #include <tqimage.h> #include <kaboutdata.h> #include <kcmdlineargs.h> #include <kglobal.h> #include <kglobalaccel.h> #include <klocale.h> #include <kprocess.h> #include <twinmodule.h> #include <twin.h> #include <ktempfile.h> #include <kstandarddirs.h> #include <kipc.h> #include <kaction.h> #include <kpopupmenu.h> #include <kdebug.h> #include <kconfig.h> #include "x11helper.h" #include "kxkb.h" #include "extension.h" #include "rules.h" #include "kxkbconfig.h" #include "layoutmap.h" #include "kxkb.moc" KXKBApp::KXKBApp(bool allowStyles, bool GUIenabled) : KUniqueApplication(allowStyles, GUIenabled), m_prevWinId(X11Helper::UNKNOWN_WINDOW_ID), m_rules(NULL), m_tray(NULL), kWinModule(NULL), m_forceSetXKBMap( false ) { m_extension = new XKBExtension(); if( !m_extension->init() ) { kdDebug() << "xkb initialization failed, exiting..." << endl; ::exit(1); } // keep in sync with kcmlayout.cpp keys = new KGlobalAccel(TQT_TQOBJECT(this)); #include "kxkbbindings.cpp" keys->updateConnections(); m_layoutOwnerMap = new LayoutMap(kxkbConfig); connect( this, TQT_SIGNAL(settingsChanged(int)), TQT_SLOT(slotSettingsChanged(int)) ); addKipcEventMask( KIPC::SettingsChanged ); } KXKBApp::~KXKBApp() { // deletePrecompiledLayouts(); delete keys; delete m_tray; delete m_rules; delete m_extension; delete m_layoutOwnerMap; delete kWinModule; } int KXKBApp::newInstance() { m_extension->reset(); if( settingsRead() ) layoutApply(); return 0; } bool KXKBApp::settingsRead() { kxkbConfig.load( KxkbConfig::LOAD_ACTIVE_OPTIONS ); if( kxkbConfig.m_enableXkbOptions ) { kdDebug() << "Setting XKB options " << kxkbConfig.m_options << endl; if( !m_extension->setXkbOptions(kxkbConfig.m_options, kxkbConfig.m_resetOldOptions) ) { kdDebug() << "Setting XKB options failed!" << endl; } } if ( kxkbConfig.m_useKxkb == false ) { kapp->quit(); return false; } m_prevWinId = X11Helper::UNKNOWN_WINDOW_ID; if( kxkbConfig.m_switchingPolicy == SWITCH_POLICY_GLOBAL ) { delete kWinModule; kWinModule = NULL; } else { TQDesktopWidget desktopWidget; if( desktopWidget.numScreens() > 1 && desktopWidget.isVirtualDesktop() == false ) { kdWarning() << "With non-virtual desktop only global switching policy supported on non-primary screens" << endl; //TODO: find out how to handle that } if( kWinModule == NULL ) { kWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP); connect(kWinModule, TQT_SIGNAL(activeWindowChanged(WId)), TQT_SLOT(windowChanged(WId))); } m_prevWinId = kWinModule->activeWindow(); kdDebug() << "Active window " << m_prevWinId << endl; } m_layoutOwnerMap->reset(); m_layoutOwnerMap->setCurrentWindow( m_prevWinId ); if( m_rules == NULL ) m_rules = new XkbRules(false); for(int ii=0; ii<(int)kxkbConfig.m_layouts.count(); ii++) { LayoutUnit& layoutUnit = kxkbConfig.m_layouts[ii]; layoutUnit.defaultGroup = m_rules->getDefaultGroup(layoutUnit.layout, layoutUnit.includeGroup); kdDebug() << "default group for " << layoutUnit.toPair() << " is " << layoutUnit.defaultGroup << endl; } m_currentLayout = kxkbConfig.getDefaultLayout(); if( kxkbConfig.m_layouts.count() == 1 ) { TQString layoutName = m_currentLayout.layout; TQString variantName = m_currentLayout.variant; TQString includeName = m_currentLayout.includeGroup; int group = m_currentLayout.defaultGroup; if( !m_extension->setLayout(kxkbConfig.m_model, layoutName, variantName, includeName, false) || !m_extension->setGroup( group ) ) { kdDebug() << "Error switching to single layout " << m_currentLayout.toPair() << endl; // TODO: alert user } if( kxkbConfig.m_showSingle == false ) { kapp->quit(); return false; } } else { // initPrecompiledLayouts(); } initTray(); KGlobal::config()->reparseConfiguration(); // kcontrol modified kdeglobals keys->readSettings(); keys->updateConnections(); return true; } void KXKBApp::initTray() { if( !m_tray ) { KSystemTray* sysTray = new KxkbSystemTray(); KPopupMenu* popupMenu = sysTray->contextMenu(); // popupMenu->insertTitle( kapp->miniIcon(), kapp->caption() ); m_tray = new KxkbLabelController(sysTray, popupMenu); connect(popupMenu, TQT_SIGNAL(activated(int)), this, TQT_SLOT(menuActivated(int))); connect(sysTray, TQT_SIGNAL(toggled()), this, TQT_SLOT(toggled())); } m_tray->setShowFlag(kxkbConfig.m_showFlag); m_tray->initLayoutList(kxkbConfig.m_layouts, *m_rules); m_tray->setCurrentLayout(m_currentLayout); m_tray->show(); } // This function activates the keyboard layout specified by the // configuration members (m_currentLayout) void KXKBApp::layoutApply() { setLayout(m_currentLayout); } // kdcop bool KXKBApp::setLayout(const TQString& layoutPair) { const LayoutUnit layoutUnitKey(layoutPair); if( kxkbConfig.m_layouts.contains(layoutUnitKey) ) { return setLayout( *kxkbConfig.m_layouts.find(layoutUnitKey) ); } return false; } // Activates the keyboard layout specified by 'layoutUnit' bool KXKBApp::setLayout(const LayoutUnit& layoutUnit, int group) { bool res = false; if( group == -1 ) group = layoutUnit.defaultGroup; res = m_extension->setLayout(kxkbConfig.m_model, layoutUnit.layout, layoutUnit.variant, layoutUnit.includeGroup); if( res ) m_extension->setGroup(group); // not checking for ret - not important if( res ) m_currentLayout = layoutUnit; if (m_tray) { if( res ) m_tray->setCurrentLayout(layoutUnit); else m_tray->setError(layoutUnit.toPair()); } return res; } void KXKBApp::toggled() { const LayoutUnit& layout = m_layoutOwnerMap->getNextLayout().layoutUnit; setLayout(layout); } void KXKBApp::menuActivated(int id) { if( KxkbLabelController::START_MENU_ID <= id && id < KxkbLabelController::START_MENU_ID + (int)kxkbConfig.m_layouts.count() ) { const LayoutUnit& layout = kxkbConfig.m_layouts[id - KxkbLabelController::START_MENU_ID]; m_layoutOwnerMap->setCurrentLayout( layout ); setLayout( layout ); } else if (id == KxkbLabelController::CONFIG_MENU_ID) { KProcess p; p << "kcmshell" << "keyboard_layout"; p.start(KProcess::DontCare); } else if (id == KxkbLabelController::HELP_MENU_ID) { KApplication::kApplication()->invokeHelp(0, "kxkb"); } else { quit(); } } // TODO: we also have to handle deleted windows void KXKBApp::windowChanged(WId winId) { // kdDebug() << "window switch" << endl; if( kxkbConfig.m_switchingPolicy == SWITCH_POLICY_GLOBAL ) { // should not happen actually kdDebug() << "windowChanged() signal in GLOBAL switching policy" << endl; return; } int group = m_extension->getGroup(); kdDebug() << "old WinId: " << m_prevWinId << ", new WinId: " << winId << endl; if( m_prevWinId != X11Helper::UNKNOWN_WINDOW_ID ) { // saving layout/group from previous window // kdDebug() << "storing " << m_currentLayout.toPair() << ":" << group << " for " << m_prevWinId << endl; // m_layoutOwnerMap->setCurrentWindow(m_prevWinId); m_layoutOwnerMap->setCurrentLayout(m_currentLayout); m_layoutOwnerMap->setCurrentGroup(group); } m_prevWinId = winId; if( winId != X11Helper::UNKNOWN_WINDOW_ID ) { m_layoutOwnerMap->setCurrentWindow(winId); const LayoutState& layoutState = m_layoutOwnerMap->getCurrentLayout(); if( layoutState.layoutUnit != m_currentLayout ) { kdDebug() << "switching to " << layoutState.layoutUnit.toPair() << ":" << group << " for " << winId << endl; setLayout( layoutState.layoutUnit, layoutState.group ); } else if( layoutState.group != group ) { // we need to change only the group m_extension->setGroup(layoutState.group); } } } void KXKBApp::slotSettingsChanged(int category) { if ( category != KApplication::SETTINGS_SHORTCUTS) return; KGlobal::config()->reparseConfiguration(); // kcontrol modified kdeglobals keys->readSettings(); keys->updateConnections(); } /* Viki (onscreen keyboard) has problems determining some modifiers states when kxkb uses precompiled layouts instead of setxkbmap. Probably a bug in the xkb functions used for the precompiled layouts *shrug*. */ void KXKBApp::forceSetXKBMap( bool set ) { if( m_forceSetXKBMap == set ) return; m_forceSetXKBMap = set; layoutApply(); } /*Precompiles the keyboard layouts for faster activation later. This is done by loading each one of them and then dumping the compiled map from the X server into our local buffer.*/ // void KXKBApp::initPrecompiledLayouts() // { // TQStringList dirs = KGlobal::dirs()->findDirs ( "tmp", "" ); // TQString tempDir = dirs.count() == 0 ? "/tmp/" : dirs[0]; // // TQValueList<LayoutUnit>::ConstIterator end = kxkbConfig.m_layouts.end(); // // for (TQValueList<LayoutUnit>::ConstIterator it = kxkbConfig.m_layouts.begin(); it != end; ++it) // { // LayoutUnit layoutUnit(*it); // // const char* baseGr = m_includes[layout]; // // int group = m_rules->getGroup(layout, baseGr); // // if( m_extension->setLayout(m_model, layout, m_variants[layout], group, baseGr) ) { // TQString compiledLayoutFileName = tempDir + layoutUnit.layout + "." + layoutUnit.variant + ".xkm"; // // if( m_extension->getCompiledLayout(compiledLayoutFileName) ) // m_compiledLayoutFileNames[layoutUnit.toPair()] = compiledLayoutFileName; // // } // // else { // // kdDebug() << "Error precompiling layout " << layout << endl; // // } // } // } const char * DESCRIPTION = I18N_NOOP("A utility to switch keyboard maps"); extern "C" KDE_EXPORT int kdemain(int argc, char *argv[]) { KAboutData about("kxkb", I18N_NOOP("TDE Keyboard Tool"), "1.0", DESCRIPTION, KAboutData::License_LGPL, "Copyright (C) 2001, S.R.Haque\n(C) 2002-2003, 2006 Andriy Rysin"); TDECmdLineArgs::init(argc, argv, &about); KXKBApp::addCmdLineOptions(); if (!KXKBApp::start()) return 0; KXKBApp app; app.disableSessionManagement(); app.exec(); return 0; }