You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/kxkb/extension.cpp

334 lines
8.8 KiB

#include <string.h>
#include <errno.h>
#include <qstring.h>
#include <qmap.h>
#include <qfile.h>
#include <qdir.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;
p.start(KProcess::Block);
// reload ubuntu hotkey-setup keycode -> keysym maps
KProcess pXmodmap;
pXmodmap << "/usr/bin/xmodmap" << "/usr/share/apps/kxkb/ubuntu.xmodmap";
pXmodmap.start(KProcess::Block);
KProcess pXmodmapHome;
pXmodmapHome << "/usr/bin/xmodmap" << QDir::home().path() + "/.Xmodmap";
pXmodmapHome.start(KProcess::Block);
return p.normalExit() && (p.exitStatus() == 0);
}
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();
// }