/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qtablegenerator.h" #include #include #include #include #include #include #include #include // LC_CTYPE #include // strchr, strncmp, etc. #include // strncasecmp #include // LC_CTYPE TableGenerator::TableGenerator() : m_state(NoErrors), m_systemComposeDir(QString()) { initPossibleLocations(); findComposeFile(); orderComposeTable(); #ifdef DEBUG_GENERATOR printComposeTable(); #endif } void TableGenerator::initPossibleLocations() { // Compose files come as a part of Xlib library. Xlib doesn't provide // a mechanism how to retrieve the location of these files reliably, since it was // never meant for external software to parse compose tables directly. Best we // can do is to hardcode search paths. To add an extra system path use // the QTCOMPOSE environment variable if (qEnvironmentVariableIsSet("QTCOMPOSE")) m_possibleLocations.append(QString::fromLocal8Bit(qgetenv("QTCOMPOSE"))); m_possibleLocations.append(QStringLiteral("/usr/share/X11/locale")); m_possibleLocations.append(QStringLiteral("/usr/local/share/X11/locale")); m_possibleLocations.append(QStringLiteral("/usr/lib/X11/locale")); m_possibleLocations.append(QStringLiteral("/usr/local/lib/X11/locale")); m_possibleLocations.append(QStringLiteral(X11_PREFIX "/share/X11/locale")); m_possibleLocations.append(QStringLiteral(X11_PREFIX "/lib/X11/locale")); } void TableGenerator::findComposeFile() { bool found = false; // check if XCOMPOSEFILE points to a Compose file if (qEnvironmentVariableIsSet("XCOMPOSEFILE")) { QString composeFile(qgetenv("XCOMPOSEFILE")); if (composeFile.endsWith(QLatin1String("Compose"))) found = processFile(composeFile); else qWarning("Qt Warning: XCOMPOSEFILE doesn't point to a valid Compose file"); #ifdef DEBUG_GENERATOR if (found) qDebug() << "Using Compose file from: " << composeFile; #endif } // check if user’s home directory has a file named .XCompose if (!found && cleanState()) { QString composeFile = qgetenv("HOME") + QStringLiteral("/.XCompose"); if (QFile(composeFile).exists()) found = processFile(composeFile); #ifdef DEBUG_GENERATOR if (found) qDebug() << "Using Compose file from: " << composeFile; #endif } // check for the system provided compose files if (!found && cleanState()) { QString table = composeTableForLocale(); if (cleanState()) { if (table.isEmpty()) // no table mappings for the system's locale in the compose.dir m_state = UnsupportedLocale; else found = processFile(systemComposeDir() + QLatin1Char('/') + table); #ifdef DEBUG_GENERATOR if (found) qDebug() << "Using Compose file from: " << systemComposeDir() + QLatin1Char('/') + table; #endif } } if (found && m_composeTable.isEmpty()) m_state = EmptyTable; if (!found) m_state = MissingComposeFile; } QString TableGenerator::composeTableForLocale() { QByteArray loc = locale().toUpper().toUtf8(); QString table = readLocaleMappings(loc); if (table.isEmpty()) table = readLocaleMappings(readLocaleAliases(loc)); return table; } bool TableGenerator::findSystemComposeDir() { bool found = false; for (int i = 0; i < m_possibleLocations.size(); ++i) { QString path = m_possibleLocations.at(i); if (QFile(path + QLatin1String("/compose.dir")).exists()) { m_systemComposeDir = path; found = true; break; } } if (!found) { // should we ask to report this in the qt bug tracker? m_state = UnknownSystemComposeDir; qWarning("Qt Warning: Could not find a location of the system's Compose files. " "Consider setting the QTCOMPOSE environment variable."); } return found; } QString TableGenerator::systemComposeDir() { if (m_systemComposeDir.isNull() && !findSystemComposeDir()) { return QLatin1String("$QTCOMPOSE"); } return m_systemComposeDir; } QString TableGenerator::locale() const { char *name = setlocale(LC_CTYPE, (char *)0); return QLatin1String(name); } QString TableGenerator::readLocaleMappings(const QByteArray &locale) { QString file; if (locale.isEmpty()) return file; QFile mappings(systemComposeDir() + QLatin1String("/compose.dir")); if (mappings.open(QIODevice::ReadOnly)) { const int localeNameLength = locale.size(); const char * const localeData = locale.constData(); char l[1024]; // formating of compose.dir has some inconsistencies while (!mappings.atEnd()) { int read = mappings.readLine(l, sizeof(l)); if (read <= 0) break; char *line = l; if (*line >= 'a' && *line <= 'z') { // file name while (*line && *line != ':' && *line != ' ' && *line != '\t') ++line; if (!*line) continue; const char * const composeFileNameEnd = line; *line = '\0'; ++line; // locale name while (*line && (*line == ' ' || *line == '\t')) ++line; const char * const lc = line; while (*line && *line != ' ' && *line != '\t' && *line != '\n') ++line; *line = '\0'; if (localeNameLength == (line - lc) && !strncasecmp(lc, localeData, line - lc)) { file = QString::fromLocal8Bit(l, composeFileNameEnd - l); break; } } } mappings.close(); } return file; } QByteArray TableGenerator::readLocaleAliases(const QByteArray &locale) { QFile aliases(systemComposeDir() + QLatin1String("/locale.alias")); QByteArray fullLocaleName; if (aliases.open(QIODevice::ReadOnly)) { while (!aliases.atEnd()) { char l[1024]; int read = aliases.readLine(l, sizeof(l)); char *line = l; if (read && ((*line >= 'a' && *line <= 'z') || (*line >= 'A' && *line <= 'Z'))) { const char *alias = line; while (*line && *line != ':' && *line != ' ' && *line != '\t') ++line; if (!*line) continue; *line = 0; if (locale.size() == (line - alias) && !strncasecmp(alias, locale.constData(), line - alias)) { // found a match for alias, read the real locale name ++line; while (*line && (*line == ' ' || *line == '\t')) ++line; const char *fullName = line; while (*line && *line != ' ' && *line != '\t' && *line != '\n') ++line; *line = 0; fullLocaleName = fullName; #ifdef DEBUG_GENERATOR qDebug() << "Alias for: " << alias << "is: " << fullLocaleName; break; #endif } } } aliases.close(); } return fullLocaleName; } bool TableGenerator::processFile(QString composeFileName) { QFile composeFile(composeFileName); if (composeFile.open(QIODevice::ReadOnly)) { parseComposeFile(&composeFile); return true; } qWarning() << QString(QLatin1String("Qt Warning: Compose file: \"%1\" can't be found")) .arg(composeFile.fileName()); return false; } TableGenerator::~TableGenerator() { } QVector TableGenerator::composeTable() const { return m_composeTable; } void TableGenerator::parseComposeFile(QFile *composeFile) { #ifdef DEBUG_GENERATOR qDebug() << "TableGenerator::parseComposeFile: " << composeFile->fileName(); #endif char line[1024]; while (!composeFile->atEnd()) { composeFile->readLine(line, sizeof(line)); if (*line == '<') parseKeySequence(line); else if (!strncmp(line, "include", 7)) parseIncludeInstruction(QString::fromLocal8Bit(line)); } composeFile->close(); } void TableGenerator::parseIncludeInstruction(QString line) { // Parse something that looks like: // include "/usr/share/X11/locale/en_US.UTF-8/Compose" QString quote = QStringLiteral("\""); line.remove(0, line.indexOf(quote) + 1); line.chop(line.length() - line.indexOf(quote)); // expand substitutions if present line.replace(QLatin1String("%H"), QString(qgetenv("HOME"))); line.replace(QLatin1String("%L"), systemComposeDir() + QLatin1Char('/') + composeTableForLocale()); line.replace(QLatin1String("%S"), systemComposeDir()); processFile(line); } ushort TableGenerator::keysymToUtf8(quint32 sym) { QByteArray chars; int bytes; chars.resize(8); bytes = xkb_keysym_to_utf8(sym, chars.data(), chars.size()); if (bytes == -1) qWarning("TableGenerator::keysymToUtf8 - buffer too small"); chars.resize(bytes-1); #ifdef DEBUG_GENERATOR QTextCodec *codec = QTextCodec::codecForLocale(); qDebug() << QString("keysym - 0x%1 : utf8 - %2").arg(QString::number(sym, 16)) .arg(codec->toUnicode(chars)); #endif return QString::fromUtf8(chars).at(0).unicode(); } static inline int fromBase8(const char *s, const char *end) { int result = 0; while (*s && s != end) { if (*s <= '0' && *s >= '7') return 0; result *= 8; result += *s - '0'; ++s; } return result; } static inline int fromBase16(const char *s, const char *end) { int result = 0; while (*s && s != end) { result *= 16; if (*s >= '0' && *s <= '9') result += *s - '0'; else if (*s >= 'a' && *s <= 'f') result += *s - 'a' + 10; else if (*s >= 'A' && *s <= 'F') result += *s - 'A' + 10; else return 0; ++s; } return result; } void TableGenerator::parseKeySequence(char *line) { // we are interested in the lines with the following format: // : "♬" U266c # BEAMED SIXTEENTH NOTE char *keysEnd = strchr(line, ':'); if (!keysEnd) return; QComposeTableElement elem; // find the composed value - strings may be direct text encoded in the locale // for which the compose file is to be used, or an escaped octal or hexadecimal // character code. Octal codes are specified as "\123" and hexadecimal codes as "\0x123a". char *composeValue = strchr(keysEnd, '"'); if (!composeValue) return; ++composeValue; char *composeValueEnd = strchr(composeValue, '"'); if (!composeValueEnd) return; if (*composeValue == '\\' && composeValue[1] >= '0' && composeValue[1] <= '9') { // handle octal and hex code values char detectBase = composeValue[2]; if (detectBase == 'x') { // hexadecimal character code elem.value = keysymToUtf8(fromBase16(composeValue + 3, composeValueEnd)); } else { // octal character code elem.value = keysymToUtf8(fromBase8(composeValue + 1, composeValueEnd)); } } else { // handle direct text encoded in the locale if (*composeValue == '\\') ++composeValue; elem.value = QString::fromLocal8Bit(composeValue).at(0).unicode(); ++composeValue; } #ifdef DEBUG_GENERATOR // find the comment elem.comment = QString::fromLocal8Bit(composeValueEnd + 1).trimmed(); #endif // find the key sequence and convert to X11 keysym char *k = line; const char *kend = keysEnd; for (int i = 0; i < QT_KEYSEQUENCE_MAX_LEN; i++) { // find the next pair of angle brackets and get the contents within while (k < kend && *k != '<') ++k; char *sym = ++k; while (k < kend && *k != '>') ++k; *k = '\0'; if (k < kend) { elem.keys[i] = xkb_keysym_from_name(sym, (xkb_keysym_flags)0); if (elem.keys[i] == XKB_KEY_NoSymbol) { if (!strcmp(sym, "dead_inverted_breve")) elem.keys[i] = XKB_KEY_dead_invertedbreve; else if (!strcmp(sym, "dead_double_grave")) elem.keys[i] = XKB_KEY_dead_doublegrave; #ifdef DEBUG_GENERATOR else qWarning() << QString("Qt Warning - invalid keysym: %1").arg(sym); #endif } } else { elem.keys[i] = 0; } } m_composeTable.append(elem); } void TableGenerator::printComposeTable() const { #ifdef DEBUG_GENERATOR if (m_composeTable.isEmpty()) return; QString output; QComposeTableElement elem; QString comma = QStringLiteral(","); int tableSize = m_composeTable.size(); for (int i = 0; i < tableSize; ++i) { elem = m_composeTable.at(i); output.append(QLatin1String("{ {")); for (int j = 0; j < QT_KEYSEQUENCE_MAX_LEN; j++) { output.append(QString(QLatin1String("0x%1, ")).arg(QString::number(elem.keys[j],16))); } // take care of the trailing comma if (i == tableSize - 1) comma = QStringLiteral(""); output.append(QString(QLatin1String("}, 0x%1, \"\" }%2 // %3 \n")) .arg(QString::number(elem.value,16)) .arg(comma) .arg(elem.comment)); } qDebug() << "output: \n" << output; #endif } void TableGenerator::orderComposeTable() { // Stable-sorting to ensure that the item that appeared before the other in the // original container will still appear first after the sort. This property is // needed to handle the cases when user re-defines already defined key sequence std::stable_sort(m_composeTable.begin(), m_composeTable.end(), Compare()); }