Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,577 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1998 Mark Donohoe <donohoe@kde.org>
SPDX-FileCopyrightText: 2001 Ellis Whitehead <ellis@kde.org>
SPDX-FileCopyrightText: 2007 Andreas Hartmetz <ahartmetz@gmail.com>
SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "config-xmlgui.h"
#include "kkeysequencewidget.h"
#include "debug.h"
#include "kactioncollection.h"
#include <QAction>
#include <QApplication>
#include <QHBoxLayout>
#include <QHash>
#include <QToolButton>
#include <KKeySequenceRecorder>
#include <KLocalizedString>
#include <KMessageBox>
#if HAVE_GLOBALACCEL
#include <KGlobalAccel>
#endif
static constexpr QStringView inputRecordingMarkupSuffix(u"");
static bool shortcutsConflictWith(const QList<QKeySequence> &shortcuts, const QKeySequence &needle)
{
if (needle.isEmpty()) {
return false;
}
for (const QKeySequence &sequence : shortcuts) {
if (sequence.isEmpty()) {
continue;
}
if (sequence.matches(needle) != QKeySequence::NoMatch //
|| needle.matches(sequence) != QKeySequence::NoMatch) {
return true;
}
}
return false;
}
class KKeySequenceWidgetPrivate
{
public:
KKeySequenceWidgetPrivate(KKeySequenceWidget *qq);
void init();
void updateShortcutDisplay();
void startRecording();
// Conflicts the key sequence @p seq with a current standard shortcut?
bool conflictWithStandardShortcuts(const QKeySequence &seq);
// Conflicts the key sequence @p seq with a current local shortcut?
bool conflictWithLocalShortcuts(const QKeySequence &seq);
// Conflicts the key sequence @p seq with a current global shortcut?
bool conflictWithGlobalShortcuts(const QKeySequence &seq);
bool promptStealLocalShortcut(const QList<QAction *> &actions, const QKeySequence &seq);
bool promptstealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq);
#if HAVE_GLOBALACCEL
struct KeyConflictInfo {
QKeySequence key;
QList<KGlobalShortcutInfo> shortcutInfo;
};
bool promptStealGlobalShortcut(const std::vector<KeyConflictInfo> &shortcuts, const QKeySequence &sequence);
#endif
void wontStealShortcut(QAction *item, const QKeySequence &seq);
bool checkAgainstStandardShortcuts() const
{
return checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts;
}
bool checkAgainstGlobalShortcuts() const
{
return checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts;
}
bool checkAgainstLocalShortcuts() const
{
return checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts;
}
// private slot
void doneRecording();
// members
KKeySequenceWidget *const q;
KKeySequenceRecorder *recorder;
QHBoxLayout *layout;
QPushButton *keyButton;
QToolButton *clearButton;
QKeySequence keySequence;
QKeySequence oldKeySequence;
QString componentName;
//! Check the key sequence against KStandardShortcut::find()
KKeySequenceWidget::ShortcutTypes checkAgainstShortcutTypes;
/**
* The list of action collections to check against for conflict shortcut
*/
QList<KActionCollection *> checkActionCollections;
/**
* The action to steal the shortcut from.
*/
QList<QAction *> stealActions;
};
KKeySequenceWidgetPrivate::KKeySequenceWidgetPrivate(KKeySequenceWidget *qq)
: q(qq)
, layout(nullptr)
, keyButton(nullptr)
, clearButton(nullptr)
, componentName()
, checkAgainstShortcutTypes(KKeySequenceWidget::LocalShortcuts | KKeySequenceWidget::GlobalShortcuts)
, stealActions()
{
}
void KKeySequenceWidgetPrivate::init()
{
layout = new QHBoxLayout(q);
layout->setContentsMargins(0, 0, 0, 0);
keyButton = new QPushButton(q);
keyButton->setFocusPolicy(Qt::StrongFocus);
keyButton->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
keyButton->setToolTip(
i18nc("@info:tooltip",
"Click on the button, then enter the shortcut like you would in the program.\nExample for Ctrl+A: hold the Ctrl key and press A."));
layout->addWidget(keyButton);
clearButton = new QToolButton(q);
layout->addWidget(clearButton);
if (qApp->isLeftToRight()) {
clearButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-rtl")));
} else {
clearButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-locationbar-ltr")));
}
recorder = new KKeySequenceRecorder(q->window()->windowHandle(), q);
recorder->setModifierlessAllowed(false);
recorder->setMultiKeyShortcutsAllowed(true);
updateShortcutDisplay();
}
bool KKeySequenceWidgetPrivate::promptStealLocalShortcut(const QList<QAction *> &actions, const QKeySequence &seq)
{
const int listSize = actions.size();
QString title = i18ncp("%1 is the number of conflicts", "Shortcut Conflict", "Shortcut Conflicts", listSize);
QString conflictingShortcuts;
for (const QAction *action : actions) {
conflictingShortcuts += i18n("Shortcut '%1' for action '%2'\n",
action->shortcut().toString(QKeySequence::NativeText),
KLocalizedString::removeAcceleratorMarker(action->text()));
}
QString message = i18ncp("%1 is the number of ambiguous shortcut clashes (hidden)",
"The \"%2\" shortcut is ambiguous with the following shortcut.\n"
"Do you want to assign an empty shortcut to this action?\n"
"%3",
"The \"%2\" shortcut is ambiguous with the following shortcuts.\n"
"Do you want to assign an empty shortcut to these actions?\n"
"%3",
listSize,
seq.toString(QKeySequence::NativeText),
conflictingShortcuts);
return KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18nc("@action:button", "Reassign"))) == KMessageBox::Continue;
}
void KKeySequenceWidgetPrivate::wontStealShortcut(QAction *item, const QKeySequence &seq)
{
QString title(i18nc("@title:window", "Shortcut conflict"));
QString msg(
i18n("<qt>The '%1' key combination is already used by the <b>%2</b> action.<br>"
"Please select a different one.</qt>",
seq.toString(QKeySequence::NativeText),
KLocalizedString::removeAcceleratorMarker(item->text())));
KMessageBox::error(q, msg, title);
}
bool KKeySequenceWidgetPrivate::conflictWithLocalShortcuts(const QKeySequence &keySequence)
{
if (!(checkAgainstShortcutTypes & KKeySequenceWidget::LocalShortcuts)) {
return false;
}
// Add all the actions from the checkActionCollections list to a single list to
// be able to process them in a single loop below.
// Note that this can't be done in setCheckActionCollections(), because we
// keep pointers to the action collections, and between the call to
// setCheckActionCollections() and this function some actions might already be
// removed from the collection again.
QList<QAction *> allActions;
for (KActionCollection *collection : std::as_const(checkActionCollections)) {
allActions += collection->actions();
}
// Because of multikey shortcuts we can have clashes with many shortcuts.
//
// Example 1:
//
// Application currently uses 'CTRL-X,a', 'CTRL-X,f' and 'CTRL-X,CTRL-F'
// and the user wants to use 'CTRL-X'. 'CTRL-X' will only trigger as
// 'activatedAmbiguously()' for obvious reasons.
//
// Example 2:
//
// Application currently uses 'CTRL-X'. User wants to use 'CTRL-X,CTRL-F'.
// This will shadow 'CTRL-X' for the same reason as above.
//
// Example 3:
//
// Some weird combination of Example 1 and 2 with three shortcuts using
// 1/2/3 key shortcuts. I think you can imagine.
QList<QAction *> conflictingActions;
// find conflicting shortcuts with existing actions
for (QAction *qaction : std::as_const(allActions)) {
if (shortcutsConflictWith(qaction->shortcuts(), keySequence)) {
// A conflict with a KAction. If that action is configurable
// ask the user what to do. If not reject this keySequence.
if (KActionCollection::isShortcutsConfigurable(qaction)) {
conflictingActions.append(qaction);
} else {
wontStealShortcut(qaction, keySequence);
return true;
}
}
}
if (conflictingActions.isEmpty()) {
// No conflicting shortcuts found.
return false;
}
if (promptStealLocalShortcut(conflictingActions, keySequence)) {
stealActions = conflictingActions;
// Announce that the user agreed
for (QAction *stealAction : std::as_const(stealActions)) {
Q_EMIT q->stealShortcut(keySequence, stealAction);
}
return false;
}
return true;
}
#if HAVE_GLOBALACCEL
bool KKeySequenceWidgetPrivate::promptStealGlobalShortcut(const std::vector<KeyConflictInfo> &clashing, const QKeySequence &sequence)
{
QString clashingKeys;
for (const auto &[key, shortcutInfo] : clashing) {
const QString seqAsString = key.toString();
for (const KGlobalShortcutInfo &info : shortcutInfo) {
clashingKeys += i18n("Shortcut '%1' in Application '%2' for action '%3'\n", //
seqAsString,
info.componentFriendlyName(),
info.friendlyName());
}
}
const int hashSize = clashing.size();
QString message = i18ncp("%1 is the number of conflicts (hidden), %2 is the key sequence of the shortcut that is problematic",
"The shortcut '%2' conflicts with the following key combination:\n",
"The shortcut '%2' conflicts with the following key combinations:\n",
hashSize,
sequence.toString());
message += clashingKeys;
QString title = i18ncp("%1 is the number of shortcuts with which there is a conflict",
"Conflict with Registered Global Shortcut",
"Conflict with Registered Global Shortcuts",
hashSize);
return KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18nc("@action:button", "Reassign"))) == KMessageBox::Continue;
}
#endif
bool KKeySequenceWidgetPrivate::conflictWithGlobalShortcuts(const QKeySequence &keySequence)
{
#ifdef Q_OS_WIN
// on windows F12 is reserved by the debugger at all times, so we can't use it for a global shortcut
if (KKeySequenceWidget::GlobalShortcuts && keySequence.toString().contains(QLatin1String("F12"))) {
QString title = i18n("Reserved Shortcut");
QString message = i18n(
"The F12 key is reserved on Windows, so cannot be used for a global shortcut.\n"
"Please choose another one.");
KMessageBox::error(q, message, title);
return false;
}
#endif
#if HAVE_GLOBALACCEL
if (!(checkAgainstShortcutTypes & KKeySequenceWidget::GlobalShortcuts)) {
return false;
}
// Global shortcuts are on key+modifier shortcuts. They can clash with
// each of the keys of a multi key shortcut.
std::vector<KeyConflictInfo> clashing;
for (int i = 0; i < keySequence.count(); ++i) {
QKeySequence keys(keySequence[i]);
if (!KGlobalAccel::isGlobalShortcutAvailable(keySequence, componentName)) {
clashing.push_back({keySequence, KGlobalAccel::globalShortcutsByKey(keys)});
}
}
if (clashing.empty()) {
return false;
}
if (!promptStealGlobalShortcut(clashing, keySequence)) {
return true;
}
// The user approved stealing the shortcut. We have to steal
// it immediately because KAction::setGlobalShortcut() refuses
// to set a global shortcut that is already used. There is no
// error it just silently fails. So be nice because this is
// most likely the first action that is done in the slot
// listening to keySequenceChanged().
KGlobalAccel::stealShortcutSystemwide(keySequence);
return false;
#else
Q_UNUSED(keySequence);
return false;
#endif
}
bool KKeySequenceWidgetPrivate::promptstealStandardShortcut(KStandardShortcut::StandardShortcut std, const QKeySequence &seq)
{
QString title = i18nc("@title:window", "Conflict with Standard Application Shortcut");
QString message = i18n(
"The '%1' key combination is also used for the standard action "
"\"%2\" that some applications use.\n"
"Do you really want to use it as a global shortcut as well?",
seq.toString(QKeySequence::NativeText),
KStandardShortcut::label(std));
return KMessageBox::warningContinueCancel(q, message, title, KGuiItem(i18nc("@action:button", "Reassign"))) == KMessageBox::Continue;
}
bool KKeySequenceWidgetPrivate::conflictWithStandardShortcuts(const QKeySequence &seq)
{
if (!(checkAgainstShortcutTypes & KKeySequenceWidget::StandardShortcuts)) {
return false;
}
KStandardShortcut::StandardShortcut ssc = KStandardShortcut::find(seq);
if (ssc != KStandardShortcut::AccelNone && !promptstealStandardShortcut(ssc, seq)) {
return true;
}
return false;
}
void KKeySequenceWidgetPrivate::startRecording()
{
keyButton->setDown(true);
recorder->startRecording();
updateShortcutDisplay();
}
void KKeySequenceWidgetPrivate::doneRecording()
{
keyButton->setDown(false);
stealActions.clear();
keyButton->setText(keyButton->text().chopped(inputRecordingMarkupSuffix.size()));
q->setKeySequence(recorder->currentKeySequence(), KKeySequenceWidget::Validate);
updateShortcutDisplay();
}
void KKeySequenceWidgetPrivate::updateShortcutDisplay()
{
QString s;
QKeySequence sequence = recorder->isRecording() ? recorder->currentKeySequence() : keySequence;
if (!sequence.isEmpty()) {
s = sequence.toString(QKeySequence::NativeText);
} else if (recorder->isRecording()) {
s = i18nc("What the user inputs now will be taken as the new shortcut", "Input");
} else {
s = i18nc("No shortcut defined", "None");
}
if (recorder->isRecording()) {
// make it clear that input is still going on
s.append(inputRecordingMarkupSuffix);
}
s = QLatin1Char(' ') + s + QLatin1Char(' ');
keyButton->setText(s);
}
KKeySequenceWidget::KKeySequenceWidget(QWidget *parent)
: QWidget(parent)
, d(new KKeySequenceWidgetPrivate(this))
{
d->init();
setFocusProxy(d->keyButton);
connect(d->keyButton, &QPushButton::clicked, this, &KKeySequenceWidget::captureKeySequence);
connect(d->clearButton, &QToolButton::clicked, this, &KKeySequenceWidget::clearKeySequence);
connect(d->recorder, &KKeySequenceRecorder::currentKeySequenceChanged, this, [this] {
d->updateShortcutDisplay();
});
connect(d->recorder, &KKeySequenceRecorder::recordingChanged, this, [this] {
if (!d->recorder->isRecording()) {
d->doneRecording();
}
});
}
KKeySequenceWidget::~KKeySequenceWidget()
{
delete d;
}
KKeySequenceWidget::ShortcutTypes KKeySequenceWidget::checkForConflictsAgainst() const
{
return d->checkAgainstShortcutTypes;
}
void KKeySequenceWidget::setComponentName(const QString &componentName)
{
d->componentName = componentName;
}
bool KKeySequenceWidget::multiKeyShortcutsAllowed() const
{
return d->recorder->multiKeyShortcutsAllowed();
}
void KKeySequenceWidget::setMultiKeyShortcutsAllowed(bool allowed)
{
d->recorder->setMultiKeyShortcutsAllowed(allowed);
}
void KKeySequenceWidget::setCheckForConflictsAgainst(ShortcutTypes types)
{
d->checkAgainstShortcutTypes = types;
}
void KKeySequenceWidget::setModifierlessAllowed(bool allow)
{
d->recorder->setModifierlessAllowed(allow);
}
bool KKeySequenceWidget::isKeySequenceAvailable(const QKeySequence &keySequence) const
{
if (keySequence.isEmpty()) {
return true;
}
return !(d->conflictWithLocalShortcuts(keySequence) //
|| d->conflictWithGlobalShortcuts(keySequence) //
|| d->conflictWithStandardShortcuts(keySequence));
}
bool KKeySequenceWidget::isModifierlessAllowed()
{
return d->recorder->modifierlessAllowed();
}
bool KKeySequenceWidget::modifierOnlyAllowed() const
{
return d->recorder->modifierOnlyAllowed();
}
void KKeySequenceWidget::setModifierOnlyAllowed(bool allow)
{
d->recorder->setModifierOnlyAllowed(allow);
}
void KKeySequenceWidget::setClearButtonShown(bool show)
{
d->clearButton->setVisible(show);
}
void KKeySequenceWidget::setCheckActionCollections(const QList<KActionCollection *> &actionCollections)
{
d->checkActionCollections = actionCollections;
}
// slot
void KKeySequenceWidget::captureKeySequence()
{
d->recorder->setWindow(window()->windowHandle());
d->recorder->startRecording();
}
QKeySequence KKeySequenceWidget::keySequence() const
{
return d->keySequence;
}
// slot
void KKeySequenceWidget::setKeySequence(const QKeySequence &seq, Validation validate)
{
if (d->keySequence == seq) {
return;
}
if (validate == Validate && !isKeySequenceAvailable(seq)) {
return;
}
d->keySequence = seq;
d->updateShortcutDisplay();
Q_EMIT keySequenceChanged(seq);
}
// slot
void KKeySequenceWidget::clearKeySequence()
{
setKeySequence(QKeySequence());
}
// slot
void KKeySequenceWidget::applyStealShortcut()
{
QSet<KActionCollection *> changedCollections;
for (QAction *stealAction : std::as_const(d->stealActions)) {
// Stealing a shortcut means setting it to an empty one.
stealAction->setShortcuts(QList<QKeySequence>());
// The following code will find the action we are about to
// steal from and save it's actioncollection.
KActionCollection *parentCollection = nullptr;
for (KActionCollection *collection : std::as_const(d->checkActionCollections)) {
if (collection->actions().contains(stealAction)) {
parentCollection = collection;
break;
}
}
// Remember the changed collection
if (parentCollection) {
changedCollections.insert(parentCollection);
}
}
for (KActionCollection *col : std::as_const(changedCollections)) {
col->writeSettings();
}
d->stealActions.clear();
}
bool KKeySequenceWidget::event(QEvent *ev)
{
constexpr char _highlight[] = "_kde_highlight_neutral";
if (ev->type() == QEvent::DynamicPropertyChange) {
auto dpev = static_cast<QDynamicPropertyChangeEvent *>(ev);
if (dpev->propertyName() == _highlight) {
d->keyButton->setProperty(_highlight, property(_highlight));
return true;
}
}
return QWidget::event(ev);
}
#include "moc_kkeysequencewidget.cpp"