Files
RedBear-OS/local/recipes/kde/kf6-kxmlgui/source/src/kswitchlanguagedialog_p.cpp
T
vasilito 36c8c3d95a fix: Qt6 Wayland crash — root cause identified, kded6 fix deployed
ROOT CAUSE: Qt6's auto-generated Wayland wrappers pass NULL proxies
to wl_*_add_listener() during initialization. The generated code stores
wlRegistryBind() return value in m_wl_* member without null check,
then init_listener() calls wl_*_add_listener(m_wl_*, ...) which
page-faults at null+8 (write to proxy->object.implementation).

FIX (kded6): wrapper script renames libqwayland.so to .disabled
before launching kded6.real. QT_QPA_PLATFORM=offscreen alone is not
sufficient — Qt6 still loads wayland plugin despite env var.

FIX (libwayland): null guards in redox.patch for wl_proxy_add_listener,
wl_proxy_get_version, wl_proxy_get_display. Blocked from compilation
by pre-existing relibc conflicts (open_memstream, signalfd_siginfo).

FIX (Qt6 wrappers): regex-based null guard insertion proven in concept.
Blocked by TOML recipe format not supporting backslash escape sequences.
Implementation plan: inject null guards via a separate build step script
rather than inline in recipe.toml.
2026-05-06 16:34:46 +01:00

415 lines
15 KiB
C++

/*
This file is part of the KDE Libraries
SPDX-FileCopyrightText: 2007 Krzysztof Lichota <lichota@mimuw.edu.pl>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kswitchlanguagedialog_p.h"
#include "debug.h"
#include <QApplication>
#include <QDialogButtonBox>
#include <QDir>
#include <QGridLayout>
#include <QLabel>
#include <QMap>
#include <QPushButton>
#include <QSettings>
#include <QStandardPaths>
#include <KLanguageButton>
#include <KLocalizedString>
#include <KMessageBox>
// Believe it or not we can't use KConfig from here
// (we need KConfig during QCoreApplication ctor which is too early for it)
// So we cooked a QSettings based solution
static std::unique_ptr<QSettings> localeOverridesSettings()
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
const QDir configDir(configPath);
if (!configDir.exists()) {
configDir.mkpath(QStringLiteral("."));
}
return std::make_unique<QSettings>(configPath + QLatin1String("/klanguageoverridesrc"), QSettings::IniFormat);
}
static QByteArray getApplicationSpecificLanguage(const QByteArray &defaultCode = QByteArray())
{
std::unique_ptr<QSettings> settings = localeOverridesSettings();
settings->beginGroup(QStringLiteral("Language"));
return settings->value(qAppName(), defaultCode).toByteArray();
}
namespace KDEPrivate
{
Q_COREAPP_STARTUP_FUNCTION(initializeLanguages)
void setApplicationSpecificLanguage(const QByteArray &languageCode)
{
std::unique_ptr<QSettings> settings = localeOverridesSettings();
settings->beginGroup(QStringLiteral("Language"));
if (languageCode.isEmpty()) {
settings->remove(qAppName());
} else {
settings->setValue(qAppName(), languageCode);
}
}
void initializeLanguages()
{
const QByteArray languageCode = getApplicationSpecificLanguage();
if (!languageCode.isEmpty()) {
QByteArray languages = qgetenv("LANGUAGE");
if (languages.isEmpty()) {
qputenv("LANGUAGE", languageCode);
} else {
qputenv("LANGUAGE", QByteArray(languageCode + ':' + languages));
}
// Ideally setting the LANGUAGE would change the default QLocale too
// but unfortunately this is too late since the QCoreApplication constructor
// already created a QLocale at this stage so we need to set the reset it
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // by triggering the creation and destruction of a QSystemLocale
// this is highly dependent on Qt internals, so may break, but oh well
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// QSystemLocale *dummy = new QSystemLocale();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// delete dummy;
}
}
struct LanguageRowData {
LanguageRowData()
{
label = nullptr;
languageButton = nullptr;
removeButton = nullptr;
}
QLabel *label;
KLanguageButton *languageButton;
QPushButton *removeButton;
void setRowWidgets(QLabel *label, KLanguageButton *languageButton, QPushButton *removeButton)
{
this->label = label;
this->languageButton = languageButton;
this->removeButton = removeButton;
}
};
class KSwitchLanguageDialogPrivate
{
public:
KSwitchLanguageDialogPrivate(KSwitchLanguageDialog *parent);
KSwitchLanguageDialog *p; // parent class
/**
Fills language button with names of languages for which given application has translation.
*/
void fillApplicationLanguages(KLanguageButton *button);
/**
Adds one button with language to widget.
*/
void addLanguageButton(const QString &languageCode, bool primaryLanguage);
/**
Returns list of languages chosen for application or default languages is they are not set.
*/
QStringList applicationLanguageList();
QMap<QPushButton *, LanguageRowData> languageRows;
QList<KLanguageButton *> languageButtons;
QGridLayout *languagesLayout;
};
/*************************** KSwitchLanguageDialog **************************/
KSwitchLanguageDialog::KSwitchLanguageDialog(QWidget *parent)
: QDialog(parent)
, d(new KSwitchLanguageDialogPrivate(this))
{
setWindowTitle(i18nc("@title:window", "Configure Language"));
QVBoxLayout *topLayout = new QVBoxLayout(this);
QLabel *label = new QLabel(i18n("Please choose the language which should be used for this application:"), this);
topLayout->addWidget(label);
QHBoxLayout *languageHorizontalLayout = new QHBoxLayout();
topLayout->addLayout(languageHorizontalLayout);
d->languagesLayout = new QGridLayout();
languageHorizontalLayout->addLayout(d->languagesLayout);
languageHorizontalLayout->addStretch();
const QStringList defaultLanguages = d->applicationLanguageList();
int count = defaultLanguages.count();
for (int i = 0; i < count; ++i) {
QString language = defaultLanguages[i];
bool primaryLanguage = (i == 0);
d->addLanguageButton(language, primaryLanguage);
}
if (!count) {
QLocale l;
d->addLanguageButton(l.name(), true);
}
QHBoxLayout *addButtonHorizontalLayout = new QHBoxLayout();
topLayout->addLayout(addButtonHorizontalLayout);
QPushButton *addLangButton = new QPushButton(i18nc("@action:button", "Add Fallback Language"), this);
addLangButton->setToolTip(i18nc("@info:tooltip", "Adds one more language which will be used if other translations do not contain a proper translation."));
connect(addLangButton, &QPushButton::clicked, this, &KSwitchLanguageDialog::slotAddLanguageButton);
addButtonHorizontalLayout->addWidget(addLangButton);
addButtonHorizontalLayout->addStretch();
topLayout->addStretch(10);
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults());
topLayout->addWidget(buttonBox);
connect(buttonBox, &QDialogButtonBox::accepted, this, &KSwitchLanguageDialog::slotOk);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &KSwitchLanguageDialog::slotDefault);
}
KSwitchLanguageDialog::~KSwitchLanguageDialog()
{
delete d;
}
void KSwitchLanguageDialog::slotAddLanguageButton()
{
// adding new button with en_US as it should always be present
d->addLanguageButton(QStringLiteral("en_US"), d->languageButtons.isEmpty());
}
void KSwitchLanguageDialog::removeButtonClicked()
{
QObject const *signalSender = sender();
if (!signalSender) {
qCCritical(DEBUG_KXMLGUI) << "KSwitchLanguageDialog::removeButtonClicked() called directly, not using signal";
return;
}
QPushButton *removeButton = const_cast<QPushButton *>(::qobject_cast<const QPushButton *>(signalSender));
if (!removeButton) {
qCCritical(DEBUG_KXMLGUI) << "KSwitchLanguageDialog::removeButtonClicked() called from something else than QPushButton";
return;
}
QMap<QPushButton *, LanguageRowData>::iterator it = d->languageRows.find(removeButton);
if (it == d->languageRows.end()) {
qCCritical(DEBUG_KXMLGUI) << "KSwitchLanguageDialog::removeButtonClicked called from unknown QPushButton";
return;
}
LanguageRowData languageRowData = it.value();
d->languageButtons.removeAll(languageRowData.languageButton);
languageRowData.label->deleteLater();
languageRowData.languageButton->deleteLater();
languageRowData.removeButton->deleteLater();
d->languageRows.erase(it);
}
void KSwitchLanguageDialog::languageOnButtonChanged(const QString &languageCode)
{
Q_UNUSED(languageCode);
#if 0
for (int i = 0, count = d->languageButtons.count(); i < count; ++i) {
KLanguageButton *languageButton = d->languageButtons[i];
if (languageButton->current() == languageCode) {
//update all buttons which have matching id
//might update buttons which were not changed, but well...
languageButton->setText(KLocale::global()->languageCodeToName(languageCode));
}
}
#endif
}
void KSwitchLanguageDialog::slotOk()
{
QStringList languages;
languages.reserve(d->languageButtons.size());
for (auto *languageButton : std::as_const(d->languageButtons)) {
languages << languageButton->current();
}
if (d->applicationLanguageList() != languages) {
QString languageString = languages.join(QLatin1Char(':'));
// list is different from defaults or saved languages list
setApplicationSpecificLanguage(languageString.toLatin1());
KMessageBox::information(
this,
i18n("The language for this application has been changed. The change will take effect the next time the application is started."), // text
i18nc("@title:window", "Application Language Changed"), // caption
QStringLiteral("ApplicationLanguageChangedWarning") // dontShowAgainName
);
}
accept();
}
void KSwitchLanguageDialog::slotDefault()
{
const QStringList defaultLanguages = d->applicationLanguageList();
setApplicationSpecificLanguage(QByteArray());
// read back the new default
QString language = QString::fromLatin1(getApplicationSpecificLanguage("en_US"));
if (defaultLanguages != (QStringList() << language)) {
KMessageBox::information(
this,
i18n("The language for this application has been changed. The change will take effect the next time the application is started."), // text
i18n("Application Language Changed"), // caption
QStringLiteral("ApplicationLanguageChangedWarning") // dontShowAgainName
);
}
accept();
}
/************************ KSwitchLanguageDialogPrivate ***********************/
KSwitchLanguageDialogPrivate::KSwitchLanguageDialogPrivate(KSwitchLanguageDialog *parent)
: p(parent)
{
// NOTE: do NOT use "p" in constructor, it is not fully constructed
}
static bool stripCountryCode(QString *languageCode)
{
const int idx = languageCode->indexOf(QLatin1Char('_'));
if (idx != -1) {
*languageCode = languageCode->left(idx);
return true;
}
return false;
}
void KSwitchLanguageDialogPrivate::fillApplicationLanguages(KLanguageButton *button)
{
const QLocale cLocale(QLocale::C);
QSet<QString> insertedLanguages;
const QList<QLocale> allLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry);
for (const QLocale &l : allLocales) {
if (l != cLocale) {
QString languageCode = l.name();
if (!insertedLanguages.contains(languageCode) && KLocalizedString::isApplicationTranslatedInto(languageCode)) {
button->insertLanguage(languageCode);
insertedLanguages << languageCode;
} else if (stripCountryCode(&languageCode)) {
if (!insertedLanguages.contains(languageCode) && KLocalizedString::isApplicationTranslatedInto(languageCode)) {
button->insertLanguage(languageCode);
insertedLanguages << languageCode;
}
}
}
}
}
QStringList KSwitchLanguageDialogPrivate::applicationLanguageList()
{
QStringList languagesList;
QByteArray languageCode = getApplicationSpecificLanguage();
if (!languageCode.isEmpty()) {
languagesList = QString::fromLatin1(languageCode).split(QLatin1Char(':'));
}
if (languagesList.isEmpty()) {
QLocale l;
languagesList = l.uiLanguages();
// We get en-US here but we use en_US
for (auto &language : languagesList) {
language.replace(QLatin1Char('-'), QLatin1Char('_'));
}
}
for (int i = 0; i < languagesList.count();) {
QString languageCode = languagesList[i];
if (!KLocalizedString::isApplicationTranslatedInto(languageCode)) {
if (stripCountryCode(&languageCode)) {
if (KLocalizedString::isApplicationTranslatedInto(languageCode)) {
languagesList[i] = languageCode;
++i;
continue;
}
}
languagesList.removeAt(i);
} else {
++i;
}
}
return languagesList;
}
void KSwitchLanguageDialogPrivate::addLanguageButton(const QString &languageCode, bool primaryLanguage)
{
QString labelText = primaryLanguage ? i18n("Primary language:") : i18n("Fallback language:");
KLanguageButton *languageButton = new KLanguageButton(p);
languageButton->showLanguageCodes(true);
fillApplicationLanguages(languageButton);
languageButton->setCurrentItem(languageCode);
QObject::connect(languageButton, &KLanguageButton::activated, p, &KSwitchLanguageDialog::languageOnButtonChanged);
LanguageRowData languageRowData;
QPushButton *removeButton = nullptr;
if (!primaryLanguage) {
removeButton = new QPushButton(i18nc("@action:button", "Remove"), p);
QObject::connect(removeButton, &QPushButton::clicked, p, &KSwitchLanguageDialog::removeButtonClicked);
}
languageButton->setToolTip(
primaryLanguage ? i18nc("@info:tooltip", "This is the main application language which will be used first, before any other languages.")
: i18nc("@info:tooltip", "This is the language which will be used if any previous languages do not contain a proper translation."));
int numRows = languagesLayout->rowCount();
QLabel *languageLabel = new QLabel(labelText, p);
languagesLayout->addWidget(languageLabel, numRows + 1, 1, Qt::AlignLeft);
languagesLayout->addWidget(languageButton, numRows + 1, 2, Qt::AlignLeft);
if (!primaryLanguage) {
languagesLayout->addWidget(removeButton, numRows + 1, 3, Qt::AlignLeft);
languageRowData.setRowWidgets(languageLabel, languageButton, removeButton);
removeButton->show();
}
languageRows.insert(removeButton, languageRowData);
languageButtons.append(languageButton);
languageButton->show();
languageLabel->show();
}
}
#include "moc_kswitchlanguagedialog_p.cpp"