SPDX-FileCopyrightText: 2010 Andriy Rysin <rysin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "xkb_rules.h"
#include "config-workspace.h"
#include "debug.h"
#include <KLocalizedString>
#include <QtConcurrentFilter>
#include <xkbcommon/xkbregistry.h>
static QString translate_xml_item(const QString &itemText)
{
if (itemText.isEmpty()) {
return itemText;
}
QString msgid(itemText);
return i18nd("xkeyboard-config", msgid.replace(QLatin1String("<"), QLatin1String("<")).replace(QLatin1String(">"), QLatin1String(">")).toUtf8())
.replace(QLatin1String("<"), QLatin1String("<"))
.replace(QLatin1String(">"), QLatin1String(">"));
}
template<typename T>
static QString translate_description(T item)
{
return item.description.isEmpty() ? item.name : translate_xml_item(item.description);
}
template<typename T>
static bool notEmpty(const T &item)
{
return !item.name.isEmpty();
}
template<class T>
void removeEmptyItems(QList<T> &list)
{
QMutableListIterator<T> it(list);
while (it.hasNext()) {
T t = it.next();
if (!notEmpty(t)) {
it.remove();
}
}
}
static void postProcess(Rules &rules)
{
removeEmptyItems(rules.layoutInfos);
removeEmptyItems(rules.modelInfos);
removeEmptyItems(rules.optionGroupInfos);
for (ModelInfo &modelInfo : rules.modelInfos) {
modelInfo.vendor = translate_xml_item(modelInfo.vendor);
modelInfo.description = translate_description(modelInfo);
}
for (LayoutInfo &layoutInfo : rules.layoutInfos) {
layoutInfo.description = translate_description(layoutInfo);
removeEmptyItems(layoutInfo.variantInfos);
for (VariantInfo &variantInfo : layoutInfo.variantInfos) {
variantInfo.description = translate_description(variantInfo);
}
}
for (OptionGroupInfo &optionGroupInfo : rules.optionGroupInfos) {
optionGroupInfo.description = translate_description(optionGroupInfo);
removeEmptyItems(optionGroupInfo.optionInfos);
for (OptionInfo &optionInfo : optionGroupInfo.optionInfos) {
optionInfo.description = translate_description(optionInfo);
}
}
}
const char Rules::XKB_OPTION_GROUP_SEPARATOR = ':';
static void rxkbLogHandler(rxkb_context *context, rxkb_log_level priority, const char *format, va_list args)
{
Q_UNUSED(context)
char buf[1024];
int length = std::vsnprintf(buf, 1023, format, args);
while (length > 0 && std::isspace(buf[length - 1])) {
--length;
}
if (length <= 0) {
return;
}
switch (priority) {
case RXKB_LOG_LEVEL_DEBUG:
qCDebug(KCM_KEYBOARD(), "XKB: %.*s", length, buf);
break;
case RXKB_LOG_LEVEL_INFO:
qCInfo(KCM_KEYBOARD(), "XKB: %.*s", length, buf);
break;
case RXKB_LOG_LEVEL_WARNING:
qCWarning(KCM_KEYBOARD(), "XKB: %.*s", length, buf);
break;
case RXKB_LOG_LEVEL_ERROR:
case RXKB_LOG_LEVEL_CRITICAL:
default:
qCCritical(KCM_KEYBOARD(), "XKB: %.*s", length, buf);
break;
}
}
Rules &Rules::self()
{
static Rules instance = readRules();
return instance;
}
Rules Rules::readRules()
{
rxkb_context *context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
if (!context) {
qCDebug(KCM_KEYBOARD) << "Could not create xkb-registry context";
return {};
}
rxkb_context_set_log_level(context, RXKB_LOG_LEVEL_DEBUG);
rxkb_context_set_log_fn(context, &rxkbLogHandler);
if (!rxkb_context_parse_default_ruleset(context)) {
rxkb_context_unref(context);
qCDebug(KCM_KEYBOARD) << "Could not parse xkb rules";
return {};
}
Rules rules;
rxkb_model *m = rxkb_model_first(context);
while (m != nullptr) {
rules.modelInfos << ModelInfo(rxkb_model_get_name(m), rxkb_model_get_description(m), rxkb_model_get_vendor(m));
m = rxkb_model_next(m);
}
for (rxkb_layout *l = rxkb_layout_first(context); l != nullptr; l = rxkb_layout_next(l)) {
QStringList languages;
for (rxkb_iso639_code *iso639 = rxkb_layout_get_iso639_first(l); iso639 != nullptr; iso639 = rxkb_iso639_code_next(iso639)) {
languages << QString::fromUtf8(rxkb_iso639_code_get_code(iso639));
}
const char *layoutName = rxkb_layout_get_name(l);
const char *variant = rxkb_layout_get_variant(l);
if (variant == nullptr) {
LayoutInfo layout(layoutName, rxkb_layout_get_description(l));
layout.languages = languages;
rules.layoutInfos << layout;
continue;
}
auto layoutIt = std::find_if(rules.layoutInfos.rbegin(), rules.layoutInfos.rend(), [layoutName](const auto &info) {
return info.name == layoutName;
});
if (layoutIt != rules.layoutInfos.rend()) {
VariantInfo v(variant, rxkb_layout_get_description(l));
v.languages = languages;
layoutIt->variantInfos << v;
}
}
rxkb_option_group *g = rxkb_option_group_first(context);
while (g != nullptr) {
OptionGroupInfo group(rxkb_option_group_get_name(g), rxkb_option_group_get_description(g), !rxkb_option_group_allows_multiple(g));
rxkb_option *o = rxkb_option_first(g);
while (o != nullptr) {
group.optionInfos << OptionInfo(rxkb_option_get_name(o), rxkb_option_get_description(o));
o = rxkb_option_next(o);
}
rules.optionGroupInfos << group;
g = rxkb_option_group_next(g);
}
postProcess(rules);
rxkb_context_unref(context);
return rules;
}