f31522130f
Build system (5 gaps hardened): - COOKBOOK_OFFLINE defaults to true (fork-mode) - normalize_patch handles diff -ruN format - New 'repo validate-patches' command (25/25 relibc patches) - 14 patched Qt/Wayland/display recipes added to protected list - relibc archive regenerated with current patch chain Boot fixes (fixable): - Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset) - D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped) - redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped) - daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch) - udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async) - relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs - greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait) - greeter-ui: built and linked (header guard unification, sem_compat stubs removed) - mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps - greeter config: removed stale keymapd dependency from display/greeter services - prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified Unfixable (diagnosed, upstream): - i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort - kded6/greeter-ui: page fault 0x8 — Qt library null deref - Thread panics fd != -1 — Rust std library on Redox - DHCP timeout / eth0 MAC — QEMU user-mode networking - hwrngd/thermald — no hardware RNG/thermal in VM - live preload allocation — BIOS memory fragmentation, continues on demand
341 lines
13 KiB
C++
341 lines
13 KiB
C++
// Copyright (C) 2023 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
// Qt-Security score:significant reason:default
|
|
|
|
#include "qquickmenuitem_p.h"
|
|
|
|
#include <QtCore/qloggingcategory.h>
|
|
//#include <QtGui/qicon.h>
|
|
//#if QT_CONFIG(shortcut)
|
|
//#include <QtGui/qkeysequence.h>
|
|
//#endif
|
|
#include <QtGui/qpa/qplatformmenu.h>
|
|
#include <QtGui/qpa/qplatformtheme.h>
|
|
#include <QtGui/private/qguiapplication_p.h>
|
|
//#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h>
|
|
#include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h>
|
|
#include <QtQuickTemplates2/private/qquickaction_p.h>
|
|
#include <QtQuickTemplates2/private/qquickmenu_p_p.h>
|
|
#include <QtQuickTemplates2/private/qquickmenuseparator_p.h>
|
|
#include <QtQuickTemplates2/private/qquicknativeiconloader_p.h>
|
|
#include <QtQuickTemplates2/private/qquicknativemenuitem_p.h>
|
|
#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
Q_STATIC_LOGGING_CATEGORY(lcNativeMenuItem, "qt.quick.controls.nativemenuitem")
|
|
|
|
/*!
|
|
\class QQuickNativeMenuItem
|
|
\brief A native menu item.
|
|
\since 6.7
|
|
\internal
|
|
|
|
Creates a native menu item from an Action/MenuItem/Menu,
|
|
and syncs the properties and signals. It can also represent a
|
|
MenuSeparator.
|
|
|
|
\sa Menu, Action
|
|
*/
|
|
|
|
QQuickNativeMenuItem *QQuickNativeMenuItem::createFromNonNativeItem(
|
|
QQuickMenu *parentMenu, QQuickItem *nonNativeItem)
|
|
{
|
|
auto *menuItem = qobject_cast<QQuickMenuItem *>(nonNativeItem);
|
|
Type type = Type::Unknown;
|
|
if (menuItem) {
|
|
if (menuItem->action()) {
|
|
type = Type::Action;
|
|
} else if (menuItem->subMenu()) {
|
|
type = Type::SubMenu;
|
|
} else {
|
|
// It's a plain MenuItem, rather than a MenuItem created by us for an Action or Menu.
|
|
type = Type::MenuItem;
|
|
}
|
|
} else if (qobject_cast<QQuickMenuSeparator *>(nonNativeItem)) {
|
|
type = Type::Separator;
|
|
}
|
|
|
|
std::unique_ptr<QQuickNativeMenuItem> nativeMenuItemPtr(new QQuickNativeMenuItem(
|
|
parentMenu, nonNativeItem, type));
|
|
if (type == Type::Unknown) {
|
|
// It's not a Menu/Action/MenuSeparator that we're dealing with, but we still need
|
|
// to create the QQuickNativeMenu item for it so that our container has the same
|
|
// indices as the menu's contentModel.
|
|
return nativeMenuItemPtr.release();
|
|
}
|
|
|
|
qCDebug(lcNativeMenuItem) << "attemping to create native menu item for"
|
|
<< nativeMenuItemPtr->debugText();
|
|
auto *parentMenuPrivate = QQuickMenuPrivate::get(parentMenu);
|
|
nativeMenuItemPtr->m_handle.reset(parentMenuPrivate->handle->createMenuItem());
|
|
if (!nativeMenuItemPtr->m_handle)
|
|
nativeMenuItemPtr->m_handle.reset(QGuiApplicationPrivate::platformTheme()->createPlatformMenuItem());
|
|
if (!nativeMenuItemPtr->m_handle)
|
|
return nullptr;
|
|
|
|
auto *nativeMenuItem = nativeMenuItemPtr.release();
|
|
switch (type) {
|
|
case Type::Action:
|
|
// Ensure that the action is triggered when the user clicks on a native menu item.
|
|
connect(nativeMenuItem->m_handle.get(), &QPlatformMenuItem::activated,
|
|
nativeMenuItem->action(), [nativeMenuItem, parentMenu](){
|
|
nativeMenuItem->action()->trigger(parentMenu);
|
|
});
|
|
// Handle programmatic changes in the Action.
|
|
connect(nativeMenuItem->action(), &QQuickAction::textChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
connect(nativeMenuItem->action(), &QQuickAction::iconChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
connect(nativeMenuItem->action(), &QQuickAction::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
connect(nativeMenuItem->action(), &QQuickAction::checkedChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
connect(nativeMenuItem->action(), &QQuickAction::checkableChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
break;
|
|
case Type::SubMenu:
|
|
nativeMenuItem->m_handle->setMenu(QQuickMenuPrivate::get(
|
|
nativeMenuItem->subMenu())->handle.get());
|
|
|
|
// Handle programmatic changes in the Menu.
|
|
connect(nativeMenuItem->subMenu(), &QQuickMenu::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
connect(nativeMenuItem->subMenu(), &QQuickMenu::titleChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
break;
|
|
case Type::MenuItem:
|
|
// Ensure that the MenuItem is clicked when the user clicks on a native menu item.
|
|
connect(nativeMenuItem->m_handle.get(), &QPlatformMenuItem::activated,
|
|
menuItem, [menuItem](){
|
|
if (menuItem->isCheckable()) {
|
|
// This changes the checked state, which we need when syncing but also to ensure that
|
|
// the user can still use MenuItem's API even though they can't actually interact with it.
|
|
menuItem->toggle();
|
|
}
|
|
// The same applies here: allow users to respond to the MenuItem's clicked signal.
|
|
QQuickAbstractButtonPrivate::get(menuItem)->click();
|
|
});
|
|
// Handle programmatic changes in the MenuItem.
|
|
connect(menuItem, &QQuickMenuItem::textChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
connect(menuItem, &QQuickMenuItem::iconChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
connect(menuItem, &QQuickMenuItem::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
connect(menuItem, &QQuickMenuItem::checkedChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
connect(menuItem, &QQuickMenuItem::checkableChanged, nativeMenuItem, &QQuickNativeMenuItem::sync);
|
|
break;
|
|
case Type::Separator:
|
|
case Type::Unknown:
|
|
break;
|
|
}
|
|
|
|
return nativeMenuItem;
|
|
}
|
|
|
|
QQuickNativeMenuItem::QQuickNativeMenuItem(QQuickMenu *parentMenu, QQuickItem *nonNativeItem,
|
|
QQuickNativeMenuItem::Type type)
|
|
: QObject(parentMenu)
|
|
, m_parentMenu(parentMenu)
|
|
, m_nonNativeItem(nonNativeItem)
|
|
, m_type(type)
|
|
{
|
|
}
|
|
|
|
QQuickNativeMenuItem::~QQuickNativeMenuItem()
|
|
{
|
|
qCDebug(lcNativeMenuItem) << "destroying" << this << debugText();
|
|
}
|
|
|
|
QQuickAction *QQuickNativeMenuItem::action() const
|
|
{
|
|
return m_type == Type::Action ? qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->action() : nullptr;
|
|
}
|
|
|
|
QQuickMenu *QQuickNativeMenuItem::subMenu() const
|
|
{
|
|
return m_type == Type::SubMenu ? qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->subMenu() : nullptr;
|
|
}
|
|
|
|
QQuickMenuSeparator *QQuickNativeMenuItem::separator() const
|
|
{
|
|
return m_type == Type::Separator ? qobject_cast<QQuickMenuSeparator *>(m_nonNativeItem) : nullptr;
|
|
}
|
|
|
|
QPlatformMenuItem *QQuickNativeMenuItem::handle() const
|
|
{
|
|
return m_handle.get();
|
|
}
|
|
|
|
void QQuickNativeMenuItem::sync()
|
|
{
|
|
if (m_type == Type::Unknown)
|
|
return;
|
|
|
|
if (m_syncing)
|
|
return;
|
|
|
|
QScopedValueRollback recursionGuard(m_syncing, true);
|
|
|
|
const auto *action = this->action();
|
|
const auto *separator = this->separator();
|
|
auto *subMenu = this->subMenu();
|
|
auto *menuItem = qobject_cast<QQuickMenuItem *>(m_nonNativeItem);
|
|
|
|
// Store the values in variables so that we can use it in the debug output below.
|
|
const bool enabled = action ? action->isEnabled()
|
|
: subMenu ? subMenu->isEnabled()
|
|
: menuItem && menuItem->isEnabled();
|
|
m_handle->setEnabled(enabled);
|
|
// m_handle->setVisible(isVisible());
|
|
|
|
const bool isSeparator = separator != nullptr;
|
|
m_handle->setIsSeparator(isSeparator);
|
|
|
|
const bool checkable = action ? action->isCheckable() : menuItem && menuItem->isCheckable();
|
|
m_handle->setCheckable(checkable);
|
|
|
|
const bool checked = action ? action->isChecked() : menuItem && menuItem->isChecked();
|
|
m_handle->setChecked(checked);
|
|
|
|
m_handle->setRole(QPlatformMenuItem::TextHeuristicRole);
|
|
|
|
const QString text = action ? action->text()
|
|
: subMenu ? subMenu->title()
|
|
: menuItem ? menuItem->text() : QString();
|
|
m_handle->setText(text);
|
|
|
|
// m_handle->setFont(m_font);
|
|
// m_handle->setHasExclusiveGroup(m_group && m_group->isExclusive());
|
|
m_handle->setHasExclusiveGroup(false);
|
|
|
|
const QQuickIcon icon = effectiveIcon();
|
|
const auto *menuPrivate = QQuickMenuPrivate::get(m_parentMenu);
|
|
|
|
// We should reload the icon if the window's DPR has changed, regardless if its properties have changed.
|
|
// We can't check for ItemDevicePixelRatioHasChanged in QQuickMenu::itemChange,
|
|
// because that isn't sent when the menu isn't visible, and will never
|
|
// be sent for native menus. We instead store lastDevicePixelRatio in QQuickMenu
|
|
// (to avoid storing it for each menu item) and set it whenever it's opened.
|
|
bool dprChanged = false;
|
|
if (!qGuiApp->topLevelWindows().isEmpty()) {
|
|
const auto *window = qGuiApp->topLevelWindows().first();
|
|
dprChanged = !qFuzzyCompare(window->devicePixelRatio(), menuPrivate->lastDevicePixelRatio);
|
|
}
|
|
|
|
if (!icon.isEmpty() && (icon != iconLoader()->icon() || dprChanged)) {
|
|
// This will load the icon, which will call sync() recursively, hence the m_syncing check.
|
|
reloadIcon();
|
|
}
|
|
|
|
if (subMenu) {
|
|
// Sync first as dynamically created menus may need to get the handle recreated.
|
|
auto *subMenuPrivate = QQuickMenuPrivate::get(subMenu);
|
|
subMenuPrivate->syncWithNativeMenu();
|
|
if (subMenuPrivate->handle)
|
|
m_handle->setMenu(subMenuPrivate->handle.get());
|
|
}
|
|
|
|
#if QT_CONFIG(shortcut)
|
|
if (action)
|
|
m_handle->setShortcut(action->shortcut());
|
|
#endif
|
|
|
|
if (m_parentMenu) {
|
|
auto *menuPrivate = QQuickMenuPrivate::get(m_parentMenu);
|
|
if (menuPrivate->handle)
|
|
menuPrivate->handle->syncMenuItem(m_handle.get());
|
|
}
|
|
|
|
qCDebug(lcNativeMenuItem) << "sync called on" << debugText() << "handle" << m_handle.get()
|
|
<< "enabled:" << enabled << "isSeparator" << isSeparator << "checkable" << checkable
|
|
<< "checked" << checked << "text" << text;
|
|
}
|
|
|
|
QQuickIcon QQuickNativeMenuItem::effectiveIcon() const
|
|
{
|
|
if (const auto *action = this->action())
|
|
return action->icon();
|
|
if (const auto *subMenu = this->subMenu())
|
|
return subMenu->icon();
|
|
if (const auto *menuItem = qobject_cast<QQuickMenuItem *>(m_nonNativeItem))
|
|
return menuItem->icon();
|
|
return {};
|
|
}
|
|
|
|
QQuickNativeIconLoader *QQuickNativeMenuItem::iconLoader() const
|
|
{
|
|
if (!m_iconLoader) {
|
|
QQuickNativeMenuItem *that = const_cast<QQuickNativeMenuItem *>(this);
|
|
static int slot = staticMetaObject.indexOfSlot("updateIcon()");
|
|
m_iconLoader = new QQuickNativeIconLoader(slot, that);
|
|
// Qt Labs Platform's QQuickMenuItem would call m_iconLoader->setEnabled(m_complete) here,
|
|
// but since QQuickMenuPrivate::maybeCreateAndInsertNativeItem asserts that the menu's
|
|
// completed loading, we can just set it to true.
|
|
m_iconLoader->setEnabled(true);
|
|
}
|
|
return m_iconLoader;
|
|
}
|
|
|
|
void QQuickNativeMenuItem::reloadIcon()
|
|
{
|
|
iconLoader()->setIcon(effectiveIcon());
|
|
m_handle->setIcon(iconLoader()->toQIcon());
|
|
}
|
|
|
|
void QQuickNativeMenuItem::updateIcon()
|
|
{
|
|
sync();
|
|
}
|
|
|
|
void QQuickNativeMenuItem::addShortcut()
|
|
{
|
|
#if QT_CONFIG(shortcut)
|
|
const auto *action = this->action();
|
|
const QKeySequence sequence = action ? action->shortcut() : QKeySequence();
|
|
if (!sequence.isEmpty() && action->isEnabled()) {
|
|
m_shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(this, sequence,
|
|
Qt::WindowShortcut, QQuickShortcutContext::matcher);
|
|
} else {
|
|
m_shortcutId = -1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void QQuickNativeMenuItem::removeShortcut()
|
|
{
|
|
#if QT_CONFIG(shortcut)
|
|
if (m_shortcutId == -1)
|
|
return;
|
|
|
|
QKeySequence sequence;
|
|
switch (m_type) {
|
|
case Type::Action:
|
|
sequence = action()->shortcut();
|
|
break;
|
|
default:
|
|
// TODO
|
|
break;
|
|
}
|
|
|
|
QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(m_shortcutId, this, sequence);
|
|
#endif
|
|
}
|
|
|
|
QString QQuickNativeMenuItem::debugText() const
|
|
{
|
|
// Prepend below to avoid extra lines from break statements.
|
|
QString text = QDebug::toString(m_handle.get());
|
|
|
|
switch (m_type) {
|
|
case Type::Action:
|
|
return text.prepend(QString::fromLatin1("Action(text = %1) ").arg(action()->text()));
|
|
case Type::SubMenu:
|
|
return text.prepend(QString::fromLatin1("Sub-menu(title = %1) ").arg(subMenu()->title()));
|
|
case Type::MenuItem:
|
|
return text.prepend(QString::fromLatin1("MenuItem(text = %1) ").arg(
|
|
qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->text()));
|
|
case Type::Separator:
|
|
return text.prepend(QStringLiteral("Separator "));
|
|
case Type::Unknown:
|
|
return text.prepend(QStringLiteral("(Unknown) "));
|
|
}
|
|
|
|
Q_UNREACHABLE();
|
|
}
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
#include "moc_qquicknativemenuitem_p.cpp"
|