diff options
Diffstat (limited to 'kxkb/extension.cpp')
-rw-r--r-- | kxkb/extension.cpp | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/kxkb/extension.cpp b/kxkb/extension.cpp new file mode 100644 index 000000000..f4eee125c --- /dev/null +++ b/kxkb/extension.cpp @@ -0,0 +1,326 @@ +#include <string.h> +#include <errno.h> + +#include <qstring.h> +#include <qmap.h> +#include <qfile.h> + +#include <kdebug.h> +#include <kstandarddirs.h> +#include <kprocess.h> + +#include <X11/Xatom.h> +#include <X11/Xos.h> +#include <X11/Xlib.h> +#include <X11/XKBlib.h> +#include <X11/extensions/XKBfile.h> +#include <X11/extensions/XKBgeom.h> +#include <X11/extensions/XKM.h> + +#include "extension.h" + + +QMap<QString, FILE*> XKBExtension::fileCache; //TODO: move to class? + + +static QString getLayoutKey(const QString& layout, const QString& variant) +{ + return layout + "." + variant; +} + +QString XKBExtension::getPrecompiledLayoutFilename(const QString& layoutKey) +{ + QString compiledLayoutFileName = m_tempDir + layoutKey + ".xkm"; + return compiledLayoutFileName; +} + +XKBExtension::XKBExtension(Display *d) +{ + if ( d == NULL ) + d = qt_xdisplay(); + m_dpy = d; + +// QStringList dirs = KGlobal::dirs()->findDirs ( "tmp", "" ); +// m_tempDir = dirs.count() == 0 ? "/tmp/" : dirs[0]; + m_tempDir = locateLocal("tmp", ""); +} + +bool XKBExtension::init() +{ + // Verify the Xlib has matching XKB extension. + + int major = XkbMajorVersion; + int minor = XkbMinorVersion; + + if (!XkbLibraryVersion(&major, &minor)) + { + kdError() << "Xlib XKB extension " << major << '.' << minor << + " != " << XkbMajorVersion << '.' << XkbMinorVersion << endl; + return false; + } + + // Verify the X server has matching XKB extension. + + int opcode_rtrn; + int error_rtrn; + int xkb_opcode; + if (!XkbQueryExtension(m_dpy, &opcode_rtrn, &xkb_opcode, &error_rtrn, + &major, &minor)) + { + kdError() << "X server XKB extension " << major << '.' << minor << + " != " << XkbMajorVersion << '.' << XkbMinorVersion << endl; + return false; + } + + // Do it, or face horrible memory corrupting bugs + ::XkbInitAtoms(NULL); + + return true; +} + +void XKBExtension::reset() +{ + for(QMap<QString, FILE*>::ConstIterator it = fileCache.begin(); it != fileCache.end(); it++) { + fclose(*it); +// remove( QFile::encodeName(getPrecompiledLayoutFileName(*it)) ); + } + fileCache.clear(); +} + +XKBExtension::~XKBExtension() +{ +/* if( m_compiledLayoutFileNames.isEmpty() == false ) + deletePrecompiledLayouts();*/ +} + +bool XKBExtension::setXkbOptions(const QString& options, bool resetOld) +{ + if (options.isEmpty()) + return true; + + QString exe = KGlobal::dirs()->findExe("setxkbmap"); + if (exe.isEmpty()) + return false; + + KProcess p; + p << exe; + if( resetOld ) + p << "-option"; + p << "-option" << options; + + p.start(KProcess::Block); + + return p.normalExit() && (p.exitStatus() == 0); +} + +bool XKBExtension::setLayout(const QString& model, + const QString& layout, const QString& variant, + const QString& includeGroup, bool useCompiledLayouts) +{ + if( useCompiledLayouts == false ) { + return setLayoutInternal( model, layout, variant, includeGroup ); + } + + const QString layoutKey = getLayoutKey(layout, variant); + + bool res; + if( fileCache.contains(layoutKey) ) { + res = setCompiledLayout( layoutKey ); + kdDebug() << "setCompiledLayout " << layoutKey << ": " << res << endl; + + if( res ) + return res; + } +// else { + res = setLayoutInternal( model, layout, variant, includeGroup ); + kdDebug() << "setRawLayout " << layoutKey << ": " << res << endl; + if( res ) + compileCurrentLayout( layoutKey ); + +// } + return res; +} + +// private +bool XKBExtension::setLayoutInternal(const QString& model, + const QString& layout, const QString& variant, + const QString& includeGroup) +{ + if ( layout.isEmpty() ) + return false; + + QString exe = KGlobal::dirs()->findExe("setxkbmap"); + if( exe.isEmpty() ) { + kdError() << "Can't find setxkbmap" << endl; + return false; + } + + QString fullLayout = layout; + QString fullVariant = variant; + if( includeGroup.isEmpty() == false ) { + fullLayout = includeGroup; + fullLayout += ","; + fullLayout += layout; + +// fullVariant = baseVar; + fullVariant = ","; + fullVariant += variant; + } + + KProcess p; + p << exe; +// p << "-rules" << rule; + if( model.isEmpty() == false ) + p << "-model" << model; + p << "-layout" << fullLayout; + if( !fullVariant.isNull() && !fullVariant.isEmpty() ) + p << "-variant" << fullVariant; + + if (p.start(KProcess::Block) && p.normalExit() && (p.exitStatus() == 0)) { + return true; //setGroup( group ); + } + else { + return false; + } +} + +bool XKBExtension::setGroup(unsigned int group) +{ + kdDebug() << "Setting group " << group << endl; + return XkbLockGroup( m_dpy, XkbUseCoreKbd, group ); +} + +unsigned int XKBExtension::getGroup() const +{ + XkbStateRec xkbState; + XkbGetState( m_dpy, XkbUseCoreKbd, &xkbState ); + return xkbState.group; +} + +/** + * @brief Gets the current layout in its binary compiled form + * and write it to the file specified by 'fileName' + * @param[in] fileName file to store compiled layout to + * @return true if no problem, false otherwise + */ +bool XKBExtension::compileCurrentLayout(const QString &layoutKey) +{ + XkbFileInfo result; + memset(&result, 0, sizeof(result)); + result.type = XkmKeymapFile; + XkbReadFromServer(m_dpy, XkbAllMapComponentsMask, XkbAllMapComponentsMask, &result); + + const QString fileName = getPrecompiledLayoutFilename(layoutKey); + + kdDebug() << "compiling layout " << this << " cache size: " << fileCache.count() << endl; + if( fileCache.contains(layoutKey) ) { + kdDebug() << "trashing old compiled layout for " << fileName << endl; + if( fileCache[ layoutKey ] != NULL ) + fclose( fileCache[ layoutKey ] ); // recompiling - trash the old file + fileCache.remove(fileName); + } + + FILE *output = fopen(QFile::encodeName(fileName), "w"); + + if ( output == NULL ) + { + kdWarning() << "Could not open " << fileName << " to precompile: " << strerror(errno) << endl; + XkbFreeKeyboard(result.xkb, XkbAllControlsMask, True); + return false; + } + + if( !XkbWriteXKMFile(output, &result) ) { + kdWarning() << "Could not write compiled layout to " << fileName << endl; + fclose(output); + return false; + } + + fclose(output); // TODO: can we change mode w/out reopening? + FILE *input = fopen(QFile::encodeName(fileName), "r"); + fileCache[ layoutKey ] = input; + + XkbFreeKeyboard(result.xkb, XkbAllControlsMask, True); + return true; +} + +/** + * @brief takes layout from its compiled binary snapshot in file + * and sets it as current + * TODO: cache layout in memory rather than in file + */ +bool XKBExtension::setCompiledLayout(const QString &layoutKey) +{ + FILE *input = NULL; + + if( fileCache.contains(layoutKey) ) { + input = fileCache[ layoutKey ]; + } + + if( input == NULL ) { + kdWarning() << "setCompiledLayout trying to reopen xkb file" << endl; // should never happen + const QString fileName = getPrecompiledLayoutFilename(layoutKey); + input = fopen(QFile::encodeName(fileName), "r"); + + // FILE *input = fopen(QFile::encodeName(fileName), "r"); + if ( input == NULL ) { + kdDebug() << "Unable to open " << fileName << ": " << strerror(errno) << endl; + fileCache.remove(layoutKey); + return false; + } + } + else { + rewind(input); + } + + XkbFileInfo result; + memset(&result, 0, sizeof(result)); + if ((result.xkb = XkbAllocKeyboard())==NULL) { + kdWarning() << "Unable to allocate memory for keyboard description" << endl; +// fclose(input); +// fileCache.remove(layoutKey); + return false; + } + + unsigned retVal = XkmReadFile(input, 0, XkmKeymapLegal, &result); + if (retVal == XkmKeymapLegal) + { + // this means reading the Xkm didn't manage to read any section + kdWarning() << "Unable to load map from file" << endl; + XkbFreeKeyboard(result.xkb, XkbAllControlsMask, True); + fclose(input); + fileCache.remove(layoutKey); + return false; + } + + // fclose(input); // don't close - goes in cache + + if (XkbChangeKbdDisplay(m_dpy, &result) == Success) + { + if (!XkbWriteToServer(&result)) + { + kdWarning() << "Unable to write the keyboard layout to X display" << endl; + XkbFreeKeyboard(result.xkb, XkbAllControlsMask, True); + return false; + } + } + else + { + kdWarning() << "Unable prepare the keyboard layout for X display" << endl; + } + + XkbFreeKeyboard(result.xkb, XkbAllControlsMask, True); + return true; +} + + +// Deletes the precompiled layouts stored in temporary files +// void XKBExtension::deletePrecompiledLayouts() +// { +// QMapConstIterator<LayoutUnit, QString> it, end; +// end = m_compiledLayoutFileNames.end(); +// for (it = m_compiledLayoutFileNames.begin(); it != end; ++it) +// { +// unlink(QFile::encodeName(it.data())); +// } +// m_compiledLayoutFileNames.clear(); +// } |