diff --git a/CMakeLists.txt b/CMakeLists.txt index b6e04239c..8c0470797 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,190 +1,190 @@ cmake_minimum_required(VERSION 3.0) project(plasma-workspace) -set(PROJECT_VERSION "5.13.80") +set(PROJECT_VERSION "5.14.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.11.0") set(KF5_MIN_VERSION "5.50.0") set(INSTALL_SDDM_THEME TRUE) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Widgets Quick QuickWidgets Concurrent Test Network) find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMMarkNonGuiExecutable) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckIncludeFiles) include(FeatureSummary) include(ECMOptionalAddSubdirectory) include(ECMQtDeclareLoggingCategory) include(KDEPackageAppTemplates) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Plasma DocTools Runner JsEmbed NotifyConfig Su NewStuff Wallet IdleTime Declarative TextWidgets KDELibs4Support Crash GlobalAccel DBusAddons Wayland CoreAddons) find_package(KF5NetworkManagerQt ${KF5_MIN_VERSION}) set_package_properties(KF5NetworkManagerQt PROPERTIES DESCRIPTION "Qt wrapper for NetworkManager API" TYPE OPTIONAL PURPOSE "Needed by geolocation data engine." ) # WARNING PlasmaQuick provides unversioned CMake config find_package(KF5 REQUIRED COMPONENTS PlasmaQuick) find_package(KF5 REQUIRED COMPONENTS SysGuard) find_package(KF5 REQUIRED COMPONENTS Package) find_package(KF5Baloo) set_package_properties(KF5Baloo PROPERTIES DESCRIPTION "File Searching" TYPE RECOMMENDED PURPOSE "Needed for the File Search runner." ) find_package(KF5TextEditor) find_package(KWinDBusInterface CONFIG REQUIRED) find_package(KScreenLocker 5.13.80 REQUIRED) find_package(ScreenSaverDBusInterface CONFIG REQUIRED) find_package(KF5Holidays) set_package_properties(KF5Holidays PROPERTIES DESCRIPTION "Holidays provider for Plasma calendar" TYPE OPTIONAL PURPOSE "Needed to for holidays plugin for Plasma Calendar." ) find_package(Phonon4Qt5 4.6.60 REQUIRED NO_MODULE) set_package_properties(Phonon4Qt5 PROPERTIES DESCRIPTION "Qt-based audio library" TYPE REQUIRED) find_package(KF5Activities ${KF5_MIN_VERSION}) set_package_properties(KF5Activities PROPERTIES DESCRIPTION "management of Plasma activities" TYPE OPTIONAL PURPOSE "Needed by activity related plasmoids." ) find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Support for gzip compressed files and data streams" URL "http://www.zlib.net" TYPE REQUIRED ) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "http://www.x.org" TYPE OPTIONAL PURPOSE "Required for building the X11 based workspace") if(X11_FOUND) find_package(XCB MODULE REQUIRED COMPONENTS XCB RANDR) set_package_properties(XCB PROPERTIES TYPE REQUIRED) if(NOT X11_SM_FOUND) message(FATAL_ERROR "\nThe X11 Session Management (SM) development package could not be found.\nPlease install libSM.\n") endif(NOT X11_SM_FOUND) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras) endif() if(X11_FOUND AND XCB_XCB_FOUND) set(HAVE_X11 1) endif() find_package(AppStreamQt 0.10.6) set_package_properties(AppStreamQt PROPERTIES DESCRIPTION "Access metadata for listing available software" URL "https://www.freedesktop.org/wiki/Distributions/AppStream/" TYPE OPTIONAL ) include(ConfigureChecks.cmake) include_directories("${CMAKE_CURRENT_BINARY_DIR}") configure_file(config-workspace.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-workspace.h) configure_file(config-unix.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-unix.h ) configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h) configure_file(plasma.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/plasma.desktop) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasma.desktop DESTINATION ${KDE_INSTALL_DATADIR}/xsessions ) configure_file(plasmawayland.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/plasmawayland.desktop) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/plasmawayland.desktop DESTINATION ${KDE_INSTALL_DATADIR}/wayland-sessions ) plasma_install_package(lookandfeel org.kde.breeze.desktop look-and-feel lookandfeel) if (INSTALL_SDDM_THEME) configure_file(sddm-theme/theme.conf.cmake ${CMAKE_CURRENT_BINARY_DIR}/sddm-theme/theme.conf) # Install the login theme into the SDDM directory # Longer term we need to look at making SDDM load from look and feel somehow.. and allow copying at runtime #NOTE this trailing slash is important to rename the directory install(DIRECTORY sddm-theme/ DESTINATION ${KDE_INSTALL_FULL_DATADIR}/sddm/themes/breeze PATTERN "README.txt" EXCLUDE PATTERN "components" EXCLUDE PATTERN "dummydata" EXCLUDE PATTERN "theme.conf.cmake" EXCLUDE) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sddm-theme/theme.conf DESTINATION ${KDE_INSTALL_FULL_DATADIR}/sddm/themes/breeze) install(DIRECTORY lookandfeel/contents/components DESTINATION ${KDE_INSTALL_FULL_DATADIR}/sddm/themes/breeze PATTERN "README.txt" EXCLUDE) endif() add_definitions(-DQT_NO_URL_CAST_FROM_STRING) add_subdirectory(doc) add_subdirectory(libkworkspace) add_subdirectory(libdbusmenuqt) add_subdirectory(appmenu) add_subdirectory(libtaskmanager) add_subdirectory(libcolorcorrect) add_subdirectory(components) add_subdirectory(plasma-windowed) add_subdirectory(shell) add_subdirectory(freespacenotifier) add_subdirectory(klipper) add_subdirectory(krunner) add_subdirectory(ksmserver) add_subdirectory(ksplash) add_subdirectory(systemmonitor) add_subdirectory(statusnotifierwatcher) add_subdirectory(startkde) add_subdirectory(themes) add_subdirectory(containmentactions) add_subdirectory(runners) add_subdirectory(applets) add_subdirectory(dataengines) add_subdirectory(wallpapers) add_subdirectory(kioslave) add_subdirectory(ktimezoned) add_subdirectory(kuiserver) add_subdirectory(menu) add_subdirectory(phonon) # This ensures pressing the eject button on a CD drive ejects the disc # It listens to the Solid::OpticalDrive::ejectPressed signal that is only # supported by Solid in the HAL backend and does nothing with UDev if(CMAKE_SYSTEM_NAME MATCHES FreeBSD) add_subdirectory(solidautoeject) endif() ecm_optional_add_subdirectory(xembed-sni-proxy) ecm_optional_add_subdirectory(gmenu-dbusmenu-proxy) add_subdirectory(soliduiserver) if(KF5Holidays_FOUND) add_subdirectory(plasmacalendarintegration) endif() add_subdirectory(templates) install( FILES plasma-workspace.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/gmenu-dbusmenu-proxy/CMakeLists.txt b/gmenu-dbusmenu-proxy/CMakeLists.txt index a7fc20ce4..41af80808 100644 --- a/gmenu-dbusmenu-proxy/CMakeLists.txt +++ b/gmenu-dbusmenu-proxy/CMakeLists.txt @@ -1,48 +1,49 @@ find_package(AppMenuGtkModule) set_package_properties(AppMenuGtkModule PROPERTIES TYPE RUNTIME) add_definitions(-DQT_NO_CAST_TO_ASCII -DQT_NO_CAST_FROM_ASCII -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_FROM_BYTEARRAY) find_package(XCB REQUIRED COMPONENTS XCB ) set(GMENU_DBUSMENU_PROXY_SRCS main.cpp menuproxy.cpp window.cpp menu.cpp actions.cpp gdbusmenutypes_p.cpp icons.cpp utils.cpp ../libdbusmenuqt/dbusmenutypes_p.cpp ) qt5_add_dbus_adaptor(GMENU_DBUSMENU_PROXY_SRCS ../libdbusmenuqt/com.canonical.dbusmenu.xml window.h Window) ecm_qt_declare_logging_category(GMENU_DBUSMENU_PROXY_SRCS HEADER debug.h IDENTIFIER DBUSMENUPROXY CATEGORY_NAME kde.dbusmenuproxy DEFAULT_SEVERITY Info) add_executable(gmenudbusmenuproxy ${GMENU_DBUSMENU_PROXY_SRCS}) set_package_properties(XCB PROPERTIES TYPE REQUIRED) target_link_libraries(gmenudbusmenuproxy Qt5::Core Qt5::X11Extras Qt5::DBus + KF5::CoreAddons KF5::ConfigCore KF5::WindowSystem XCB::XCB ) install(TARGETS gmenudbusmenuproxy ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES gmenudbusmenuproxy.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) diff --git a/gmenu-dbusmenu-proxy/menuproxy.cpp b/gmenu-dbusmenu-proxy/menuproxy.cpp index f4600fa36..24542f286 100644 --- a/gmenu-dbusmenu-proxy/menuproxy.cpp +++ b/gmenu-dbusmenu-proxy/menuproxy.cpp @@ -1,291 +1,398 @@ /* * Copyright (C) 2018 Kai Uwe Broulik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "menuproxy.h" #include #include "debug.h" #include #include #include #include #include #include #include #include #include +#include #include +#include #include #include #include #include #include #include "window.h" static const QString s_ourServiceName = QStringLiteral("org.kde.plasma.gmenu_dbusmenu_proxy"); static const QString s_dbusMenuRegistrar = QStringLiteral("com.canonical.AppMenu.Registrar"); static const QByteArray s_gtkUniqueBusName = QByteArrayLiteral("_GTK_UNIQUE_BUS_NAME"); static const QByteArray s_gtkApplicationObjectPath = QByteArrayLiteral("_GTK_APPLICATION_OBJECT_PATH"); static const QByteArray s_unityObjectPath = QByteArrayLiteral("_UNITY_OBJECT_PATH"); static const QByteArray s_gtkWindowObjectPath = QByteArrayLiteral("_GTK_WINDOW_OBJECT_PATH"); static const QByteArray s_gtkMenuBarObjectPath = QByteArrayLiteral("_GTK_MENUBAR_OBJECT_PATH"); // that's the generic app menu with Help and Options and will be used if window doesn't have a fully-blown menu bar static const QByteArray s_gtkAppMenuObjectPath = QByteArrayLiteral("_GTK_APP_MENU_OBJECT_PATH"); static const QByteArray s_kdeNetWmAppMenuServiceName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"); static const QByteArray s_kdeNetWmAppMenuObjectPath = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"); +static const QString s_gtkModules = QStringLiteral("gtk-modules"); +static const QString s_appMenuGtkModule = QStringLiteral("appmenu-gtk-module"); + MenuProxy::MenuProxy() : QObject() , m_xConnection(QX11Info::connection()) , m_serviceWatcher(new QDBusServiceWatcher(this)) + , m_gtk2RcWatch(new KDirWatch(this)) + , m_writeGtk2SettingsTimer(new QTimer(this)) { m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration | QDBusServiceWatcher::WatchForRegistration); m_serviceWatcher->addWatchedService(s_dbusMenuRegistrar); connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, [this](const QString &service) { Q_UNUSED(service); qCDebug(DBUSMENUPROXY) << "Global menu service became available, starting"; init(); }); connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &service) { Q_UNUSED(service); qCDebug(DBUSMENUPROXY) << "Global menu service disappeared, cleaning up"; teardown(); }); // It's fine to do a blocking call here as we're a separate binary with no UI if (QDBusConnection::sessionBus().interface()->isServiceRegistered(s_dbusMenuRegistrar)) { qCDebug(DBUSMENUPROXY) << "Global menu service is running, starting right away"; init(); } else { qCDebug(DBUSMENUPROXY) << "No global menu service available, waiting for it to start before doing anything"; // be sure when started to restore gtk menus when there's no dbus menu around in case we crashed - setGtkShellShowsMenuBar(false); + enableGtkSettings(false); } + + // kde-gtk-config just deletes and re-creates the gtkrc-2.0, watch this and add out config to it again + m_writeGtk2SettingsTimer->setSingleShot(true); + m_writeGtk2SettingsTimer->setInterval(1000); + connect(m_writeGtk2SettingsTimer, &QTimer::timeout, this, &MenuProxy::writeGtk2Settings); + + auto startGtk2SettingsTimer = [this] { + if (!m_writeGtk2SettingsTimer->isActive()) { + m_writeGtk2SettingsTimer->start(); + } + }; + + connect(m_gtk2RcWatch, &KDirWatch::created, this, startGtk2SettingsTimer); + connect(m_gtk2RcWatch, &KDirWatch::dirty, this, startGtk2SettingsTimer); + m_gtk2RcWatch->addFile(gtkRc2Path()); } MenuProxy::~MenuProxy() { teardown(); } bool MenuProxy::init() { if (!QDBusConnection::sessionBus().registerService(s_ourServiceName)) { qCWarning(DBUSMENUPROXY) << "Failed to register DBus service" << s_ourServiceName; return false; } - setGtkShellShowsMenuBar(true); + enableGtkSettings(true); connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &MenuProxy::onWindowAdded); connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &MenuProxy::onWindowRemoved); const auto windows = KWindowSystem::windows(); for (WId id : windows) { onWindowAdded(id); } if (m_windows.isEmpty()) { qCDebug(DBUSMENUPROXY) << "Up and running but no windows with menus in sight"; } return true; } void MenuProxy::teardown() { - setGtkShellShowsMenuBar(false); + enableGtkSettings(false); QDBusConnection::sessionBus().unregisterService(s_ourServiceName); disconnect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &MenuProxy::onWindowAdded); disconnect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &MenuProxy::onWindowRemoved); qDeleteAll(m_windows); m_windows.clear(); } -void MenuProxy::setGtkShellShowsMenuBar(bool show) +void MenuProxy::enableGtkSettings(bool enable) { - qCDebug(DBUSMENUPROXY) << "Setting gtk-shell-shows-menu-bar to" << show << "which will" << (show ? "hide" : "show") << "menu bars in applications"; + m_enabled = enable; - // mostly taken from kde-gtk-config - QString root = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); - if (root.isEmpty()) { - root = QFileInfo(QDir::home(), QStringLiteral(".config")).absoluteFilePath(); + writeGtk2Settings(); + writeGtk3Settings(); + + // TODO use gconf/dconf directly or at least signal a change somehow? +} + +QString MenuProxy::gtkRc2Path() +{ + return QDir::homePath() + QLatin1String("/.gtkrc-2.0"); +} + +QString MenuProxy::gtk3SettingsIniPath() +{ + return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/gtk-3.0/settings.ini"); +} + +void MenuProxy::writeGtk2Settings() +{ + qCDebug(DBUSMENUPROXY) << "Writing gtkrc-2.0 to" << (m_enabled ? "enable" : "disable") << "global menu support"; + + QFile rcFile(gtkRc2Path()); + if (!rcFile.open(QIODevice::ReadWrite | QIODevice::Text)) { + return; + } + + QByteArray content; + + QStringList gtkModules; + + while (!rcFile.atEnd()) { + const QByteArray rawLine = rcFile.readLine(); + + const QString line = QString::fromUtf8(rawLine.trimmed()); + + if (!line.startsWith(s_gtkModules)) { + // keep line as-is + content += rawLine; + continue; + } + + const int equalSignIdx = line.indexOf(QLatin1Char('=')); + if (equalSignIdx < 1) { + continue; + } + + gtkModules = line.mid(equalSignIdx + 1).split(QLatin1Char(':'), QString::SkipEmptyParts); + + break; } - const QString settingsFilePath = root + QStringLiteral("/gtk-3.0/settings.ini"); + addOrRemoveAppMenuGtkModule(gtkModules); + + if (!gtkModules.isEmpty()) { + content += QStringLiteral("%1=%2").arg(s_gtkModules, gtkModules.join(QLatin1Char(':'))).toUtf8(); + } - auto cfg = KSharedConfig::openConfig(settingsFilePath, KConfig::NoGlobals); + qCDebug(DBUSMENUPROXY) << " gtk-modules:" << gtkModules; + + m_gtk2RcWatch->stopScan(); + + // now write the new contents of the file + rcFile.resize(0); + rcFile.write(content); + rcFile.close(); + + m_gtk2RcWatch->startScan(); +} + +void MenuProxy::writeGtk3Settings() +{ + qCDebug(DBUSMENUPROXY) << "Writing gtk-3.0/settings.ini" << (m_enabled ? "enable" : "disable") << "global menu support"; + + // mostly taken from kde-gtk-config + auto cfg = KSharedConfig::openConfig(gtk3SettingsIniPath(), KConfig::NoGlobals); KConfigGroup group(cfg, "Settings"); - if (show) { + QStringList gtkModules = group.readEntry("gtk-modules", QString()).split(QLatin1Char(':'), QString::SkipEmptyParts); + addOrRemoveAppMenuGtkModule(gtkModules); + + if (!gtkModules.isEmpty()) { + group.writeEntry("gtk-modules", gtkModules.join(QLatin1Char(':'))); + } else { + group.deleteEntry("gtk-modules"); + } + + qCDebug(DBUSMENUPROXY) << " gtk-modules:" << gtkModules; + + if (m_enabled) { group.writeEntry("gtk-shell-shows-menubar", 1); } else { group.deleteEntry("gtk-shell-shows-menubar"); } + qCDebug(DBUSMENUPROXY) << " gtk-shell-shows-menubar:" << (m_enabled ? 1 : 0); + group.sync(); +} - // TODO use gconf/dconf directly or at least signal a change somehow? +void MenuProxy::addOrRemoveAppMenuGtkModule(QStringList &list) +{ + if (m_enabled && !list.contains(s_appMenuGtkModule)) { + list.append(s_appMenuGtkModule); + } else if (!m_enabled) { + list.removeAll(s_appMenuGtkModule); + } } void MenuProxy::onWindowAdded(WId id) { if (m_windows.contains(id)) { return; } KWindowInfo info(id, NET::WMWindowType); NET::WindowType wType = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask); // Only top level windows typically have a menu bar, dialogs, such as settings don't if (wType != NET::Normal) { qCInfo(DBUSMENUPROXY) << "Ignoring window" << id << "of type" << wType; return; } const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_gtkUniqueBusName)); if (serviceName.isEmpty()) { return; } const QString applicationObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkApplicationObjectPath)); const QString unityObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_unityObjectPath)); const QString windowObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkWindowObjectPath)); const QString applicationMenuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkAppMenuObjectPath)); const QString menuBarObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_gtkMenuBarObjectPath)); if (applicationMenuObjectPath.isEmpty() && menuBarObjectPath.isEmpty()) { return; } Window *window = new Window(serviceName); window->setWinId(id); window->setApplicationObjectPath(applicationObjectPath); window->setUnityObjectPath(unityObjectPath); window->setWindowObjectPath(windowObjectPath); window->setApplicationMenuObjectPath(applicationMenuObjectPath); window->setMenuBarObjectPath(menuBarObjectPath); m_windows.insert(id, window); connect(window, &Window::requestWriteWindowProperties, this, [this, window] { Q_ASSERT(!window->proxyObjectPath().isEmpty()); writeWindowProperty(window->winId(), s_kdeNetWmAppMenuServiceName, s_ourServiceName.toUtf8()); writeWindowProperty(window->winId(), s_kdeNetWmAppMenuObjectPath, window->proxyObjectPath().toUtf8()); }); connect(window, &Window::requestRemoveWindowProperties, this, [this, window] { writeWindowProperty(window->winId(), s_kdeNetWmAppMenuServiceName, QByteArray()); writeWindowProperty(window->winId(), s_kdeNetWmAppMenuObjectPath, QByteArray()); }); window->init(); } void MenuProxy::onWindowRemoved(WId id) { // no need to cleanup() (which removes window properties) when the window is gone, delete right away delete m_windows.take(id); } QByteArray MenuProxy::getWindowPropertyString(WId id, const QByteArray &name) { QByteArray value; auto atom = getAtom(name); if (atom == XCB_ATOM_NONE) { return value; } // GTK properties aren't XCB_ATOM_STRING but a custom one auto utf8StringAtom = getAtom(QByteArrayLiteral("UTF8_STRING")); static const long MAX_PROP_SIZE = 10000; auto propertyCookie = xcb_get_property(m_xConnection, false, id, atom, utf8StringAtom, 0, MAX_PROP_SIZE); QScopedPointer propertyReply(xcb_get_property_reply(m_xConnection, propertyCookie, nullptr)); if (propertyReply.isNull()) { qCWarning(DBUSMENUPROXY) << "XCB property reply for atom" << name << "on" << id << "was null"; return value; } if (propertyReply->type == utf8StringAtom && propertyReply->format == 8 && propertyReply->value_len > 0) { const char *data = (const char *) xcb_get_property_value(propertyReply.data()); int len = propertyReply->value_len; if (data) { value = QByteArray(data, data[len - 1] ? len : len - 1); } } return value; } void MenuProxy::writeWindowProperty(WId id, const QByteArray &name, const QByteArray &value) { auto atom = getAtom(name); if (atom == XCB_ATOM_NONE) { return; } if (value.isEmpty()) { xcb_delete_property(m_xConnection, id, atom); } else { xcb_change_property(m_xConnection, XCB_PROP_MODE_REPLACE, id, atom, XCB_ATOM_STRING, 8, value.length(), value.constData()); } } xcb_atom_t MenuProxy::getAtom(const QByteArray &name) { static QHash s_atoms; auto atom = s_atoms.value(name, XCB_ATOM_NONE); if (atom == XCB_ATOM_NONE) { const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(m_xConnection, false, name.length(), name.constData()); QScopedPointer atomReply(xcb_intern_atom_reply(m_xConnection, atomCookie, nullptr)); if (!atomReply.isNull()) { atom = atomReply->atom; if (atom != XCB_ATOM_NONE) { s_atoms.insert(name, atom); } } } return atom; } diff --git a/gmenu-dbusmenu-proxy/menuproxy.h b/gmenu-dbusmenu-proxy/menuproxy.h index 1e1806628..76672355d 100644 --- a/gmenu-dbusmenu-proxy/menuproxy.h +++ b/gmenu-dbusmenu-proxy/menuproxy.h @@ -1,61 +1,77 @@ /* * Copyright (C) 2018 Kai Uwe Broulik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #pragma once #include #include #include #include // for WId #include class QDBusServiceWatcher; +class QTimer; + +class KDirWatch; class Window; class MenuProxy : public QObject { Q_OBJECT public: MenuProxy(); ~MenuProxy() override; private Q_SLOTS: void onWindowAdded(WId id); void onWindowRemoved(WId id); private: bool init(); void teardown(); - void setGtkShellShowsMenuBar(bool show); + static QString gtkRc2Path(); + static QString gtk3SettingsIniPath(); + + void enableGtkSettings(bool enabled); + + void writeGtk2Settings(); + void writeGtk3Settings(); + + void addOrRemoveAppMenuGtkModule(QStringList &list); xcb_connection_t *m_xConnection; QByteArray getWindowPropertyString(WId id, const QByteArray &name); void writeWindowProperty(WId id, const QByteArray &name, const QByteArray &value); xcb_atom_t getAtom(const QByteArray &name); QHash m_windows; QDBusServiceWatcher *m_serviceWatcher; + KDirWatch *m_gtk2RcWatch; + QTimer *m_writeGtk2SettingsTimer; + + bool m_enabled = false; + }; diff --git a/runners/appstream/appstreamrunner.cpp b/runners/appstream/appstreamrunner.cpp index e2148c2c2..fa96e8397 100644 --- a/runners/appstream/appstreamrunner.cpp +++ b/runners/appstream/appstreamrunner.cpp @@ -1,127 +1,133 @@ /*************************************************************************** * Copyright © 2016 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "appstreamrunner.h" #include #include #include #include #include #include #include #include "debug.h" K_EXPORT_PLASMA_RUNNER(installer, InstallerRunner) InstallerRunner::InstallerRunner(QObject *parent, const QVariantList &args) : Plasma::AbstractRunner(parent, args) { Q_UNUSED(args) setObjectName("Installation Suggestions"); setPriority(AbstractRunner::HighestPriority); addSyntax(Plasma::RunnerSyntax(":q:", i18n("Looks for non-installed components according to :q:"))); } InstallerRunner::~InstallerRunner() { } static QIcon componentIcon(const AppStream::Component &comp) { QIcon ret; const auto icons = comp.icons(); if (icons.isEmpty()) { ret = QIcon::fromTheme(QStringLiteral("package-x-generic")); } else foreach(const AppStream::Icon &icon, icons) { QStringList stock; switch(icon.kind()) { case AppStream::Icon::KindLocal: ret.addFile(icon.url().toLocalFile(), icon.size()); break; case AppStream::Icon::KindCached: ret.addFile(icon.url().toLocalFile(), icon.size()); break; case AppStream::Icon::KindStock: stock += icon.name(); break; default: break; } if (ret.isNull() && !stock.isEmpty()) { ret = QIcon::fromTheme(stock.first()); } } return ret; } void InstallerRunner::match(Plasma::RunnerContext &context) { if(context.query().size() <= 2) return; auto components = findComponentsByString(context.query()); foreach(const AppStream::Component &component, components) { if (component.kind() != AppStream::Component::KindDesktopApp) continue; const auto idWithoutDesktop = component.id().remove(".desktop"); const auto serviceQuery = QStringLiteral("exist Exec and (('%1' =~ DesktopEntryName) or '%2' =~ DesktopEntryName)").arg(component.id(), idWithoutDesktop); const auto servicesFound = KServiceTypeTrader::self()->query(QStringLiteral("Application"), serviceQuery); if (!servicesFound.isEmpty()) continue; Plasma::QueryMatch match(this); match.setType(Plasma::QueryMatch::PossibleMatch); match.setId(component.id()); match.setIcon(componentIcon(component)); match.setText(i18n("Get %1...", component.name())); match.setSubtext(component.summary()); match.setData(QUrl("appstream://" + component.id())); context.addMatch(match); } } void InstallerRunner::run(const Plasma::RunnerContext &/*context*/, const Plasma::QueryMatch &match) { const QUrl appstreamUrl = match.data().toUrl(); if (!QDesktopServices::openUrl(appstreamUrl)) qCWarning(RUNNER_APPSTREAM) << "couldn't open" << appstreamUrl; } QList InstallerRunner::findComponentsByString(const QString &query) { QMutexLocker locker(&m_appstreamMutex); QString error; + static bool warnedOnce = false; static bool opened = m_db.load(&error); if(!opened) { - qCWarning(RUNNER_APPSTREAM) << "Had errors when loading AppStream metadata pool" << error; + if (warnedOnce) { + qCDebug(RUNNER_APPSTREAM) << "Had errors when loading AppStream metadata pool" << error; + } else { + qCWarning(RUNNER_APPSTREAM) << "Had errors when loading AppStream metadata pool" << error; + warnedOnce = true; + } } return m_db.search(query); } #include "appstreamrunner.moc"