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,248 @@
configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
add_library(KF6GuiAddons)
add_library(KF6::GuiAddons ALIAS KF6GuiAddons)
set_target_properties(KF6GuiAddons PROPERTIES
VERSION ${KGUIADDONS_VERSION}
SOVERSION ${KGUIADDONS_SOVERSION}
EXPORT_NAME GuiAddons
)
target_sources(KF6GuiAddons PRIVATE
colors/kcolorspaces.cpp
colors/kcolorutils.cpp
colors/kcolorcollection.cpp
colors/kcolormimedata.cpp
colors/kcolorschemewatcher.cpp
colors/kcolorschemewatcherbackend.cpp
colors/kcolorschemewatcher_qt.cpp
colors/kwindowinsetscontroller.cpp
text/kdatevalidator.cpp
text/kwordwrap.cpp
fonts/kfontutils.cpp
util/kiconutils.cpp
util/klocalimagecacheimpl.cpp
util/kmodifierkeyinfo.cpp
util/kmodifierkeyinfoprovider.cpp
util/kurlhandler_p.cpp
util/kcursorsaver.cpp
util/kcountryflagemojiiconengine.cpp
util/kjobwindows.cpp
recorder/keyboardgrabber.cpp
recorder/kkeysequencerecorder.cpp
systemclipboard/qtclipboard.cpp
systemclipboard/ksystemclipboard.cpp
colors/kcolorspaces_p.h
colors/kcolorutils.h
colors/kcolorcollection.h
colors/kcolormimedata.h
text/kdatevalidator.h
text/kwordwrap.h
fonts/kfontutils.h
util/kiconutils.h
util/klocalimagecacheimpl.h
util/kmodifierkeyinfo.h
util/kmodifierkeyinfoprovider_p.h
util/kurlhandler_p.h
util/kcursorsaver.h
util/kcountryflagemojiiconengine.h
util/kjobwindows.h
recorder/keyboardgrabber_p.h
recorder/kkeysequencerecorder.h
systemclipboard/qtclipboard_p.h
systemclipboard/ksystemclipboard.h
)
ecm_qt_declare_logging_category(KF6GuiAddons
HEADER kguiaddons_debug.h
IDENTIFIER KGUIADDONS_LOG
CATEGORY_NAME kf.guiaddons
DESCRIPTION "KGuiAddons"
EXPORT KGUIADDONS
)
if(WIN32)
target_sources(KF6GuiAddons PRIVATE colors/kcolorschemewatcher_win.cpp)
target_link_libraries(KF6GuiAddons PRIVATE advapi32)
endif()
if(APPLE)
find_library(APPKIT_LIBRARY AppKit)
target_sources(KF6GuiAddons PRIVATE colors/kcolorschemewatcher_mac.mm)
target_link_libraries(KF6GuiAddons PRIVATE ${APPKIT_LIBRARY})
endif()
if(WITH_DBUS)
target_sources(KF6GuiAddons PRIVATE colors/kcolorschemewatcher_xdg.cpp)
target_link_libraries(KF6GuiAddons PRIVATE Qt6::DBus)
endif()
if(WITH_WAYLAND)
if (Qt6_VERSION VERSION_GREATER_EQUAL "6.8.0")
set(private_code_option "PRIVATE_CODE")
endif()
qt_generate_wayland_protocol_client_sources(KF6GuiAddons
FILES
"${CMAKE_CURRENT_SOURCE_DIR}/recorder/keyboard-shortcuts-inhibit-unstable-v1.xml"
"${CMAKE_CURRENT_SOURCE_DIR}/systemclipboard/wlr-data-control-unstable-v1.xml"
"${PLASMA_WAYLAND_PROTOCOLS_DIR}/keystate.xml"
"${Wayland_DATADIR}/wayland.xml"
${private_code_option}
)
target_sources(KF6GuiAddons PRIVATE
recorder/waylandinhibition.cpp
systemclipboard/waylandclipboard.cpp
util/kmodifierkeyinfoprovider_wayland.cpp
recorder/waylandinhibition_p.h
systemclipboard/waylandclipboard_p.h
util/kmodifierkeyinfoprovider_wayland.h
)
target_compile_definitions(KF6GuiAddons PRIVATE WITH_WAYLAND)
target_link_libraries(KF6GuiAddons PRIVATE Qt6::GuiPrivate Qt6::WaylandClient Wayland::Client)
endif()
if(WITH_X11)
target_sources(KF6GuiAddons PRIVATE util/kmodifierkeyinfoprovider_xcb.cpp util/kmodifierkeyinfoprovider_xcb.h)
target_link_libraries(KF6GuiAddons PRIVATE X11::Xkb XCB::XCB)
endif()
if(ANDROID)
add_subdirectory(android)
endif()
ecm_generate_export_header(KF6GuiAddons
BASE_NAME KGuiAddons
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS 6.3
)
set(kguiaddons_INCLUDES
${CMAKE_CURRENT_SOURCE_DIR}/colors
${CMAKE_CURRENT_SOURCE_DIR}/fonts
${CMAKE_CURRENT_SOURCE_DIR}/text
${CMAKE_CURRENT_SOURCE_DIR}/util
${CMAKE_CURRENT_SOURCE_DIR}/recorder
${CMAKE_CURRENT_SOURCE_DIR}/systemclipboard
)
target_include_directories(KF6GuiAddons PUBLIC "$<BUILD_INTERFACE:${kguiaddons_INCLUDES}>")
target_include_directories(KF6GuiAddons INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KGuiAddons>" )
target_compile_definitions(KF6GuiAddons INTERFACE "$<INSTALL_INTERFACE:KGUIADDONS_LIB>")
target_link_libraries(KF6GuiAddons PUBLIC Qt6::Gui)
qt_extract_metatypes(KF6GuiAddons)
ecm_generate_headers(KGuiAddons_HEADERS
HEADER_NAMES
KColorUtils
KColorCollection
KColorMimeData
KColorSchemeWatcher
KWindowInsetsController
RELATIVE colors
REQUIRED_HEADERS KGuiAddons_HEADERS
)
ecm_generate_headers(KGuiAddons_HEADERS
HEADER_NAMES
KDateValidator
KWordWrap
RELATIVE text
REQUIRED_HEADERS KGuiAddons_HEADERS
)
ecm_generate_headers(KGuiAddons_HEADERS
HEADER_NAMES
KFontUtils
RELATIVE fonts
REQUIRED_HEADERS KGuiAddons_HEADERS
)
ecm_generate_headers(KGuiAddons_HEADERS
HEADER_NAMES
KIconUtils
KImageCache
KModifierKeyInfo
KCursorSaver
KCountryFlagEmojiIconEngine
KJobWindows
RELATIVE util
REQUIRED_HEADERS KGuiAddons_HEADERS
)
ecm_generate_headers(KGuiAddons_HEADERS
HEADER_NAMES
KKeySequenceRecorder
RELATIVE recorder
REQUIRED_HEADERS KGuiAddons_HEADERS
)
#add_subdirectory(systemclipboard)
ecm_generate_headers(KGuiAddons_HEADERS
HEADER_NAMES
KSystemClipboard
RELATIVE systemclipboard
REQUIRED_HEADERS KGuiAddons_HEADERS
)
install(TARGETS KF6GuiAddons EXPORT KF6GuiAddonsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/kguiaddons_export.h
util/kmodifierkeyinfoprovider_p.h
util/klocalimagecacheimpl.h # implementation detail, no forwarding header
${KGuiAddons_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KGuiAddons COMPONENT Devel
)
if(BUILD_QCH)
ecm_add_qch(
KF6GuiAddons_QCH
NAME KGuiAddons
BASE_NAME KF6GuiAddons
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KGuiAddons_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Gui_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
${kguiaddons_INCLUDES}
BLANK_MACROS
KGUIADDONS_EXPORT
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
ecm_qt_install_logging_categories(
EXPORT KGUIADDONS
FILE kguiaddons.categories
DESTINATION "${KDE_INSTALL_LOGGINGCATEGORIESDIR}"
)
if(BUILD_TESTING)
add_executable(pasteclient systemclipboard/tests/paste.cpp)
target_link_libraries(pasteclient
KF6GuiAddons
)
endif()
if (NOT ANDROID AND BUILD_GEO_SCHEME_HANDLER)
add_subdirectory(geo-scheme-handler)
endif()
if (TARGET Qt6::Qml)
add_subdirectory(qml)
endif()
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: BSD-2-Clause
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.kde.guiaddons">
</manifest>
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
gradle_add_aar(kguiaddons_aar BUILDFILE ${CMAKE_CURRENT_SOURCE_DIR}/build.gradle NAME KF6GuiAddons)
gradle_install_aar(kguiaddons_aar DESTINATION jar)
install(
FILES KF6GuiAddons-android-dependencies.xml
DESTINATION ${KDE_INSTALL_LIBDIR}
RENAME KF6GuiAddons_${CMAKE_ANDROID_ARCH_ABI}-android-dependencies.xml
)
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: BSD-2-Clause
-->
<rules>
<dependencies>
<lib name="KF6GuiAddons">
<depends>
<jar bundling="1" file="jar/KF6GuiAddons.aar"/>
</depends>
</lib>
</dependencies>
</rules>
@@ -0,0 +1,44 @@
/*
SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: BSD-2-Clause
*/
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:@Gradle_ANDROID_GRADLE_PLUGIN_VERSION@'
}
}
repositories {
google()
jcenter()
}
apply plugin: 'com.android.library'
android {
compileSdkVersion @ANDROID_SDK_COMPILE_API@
buildToolsVersion '@ANDROID_SDK_BUILD_TOOLS_REVISION@'
sourceSets {
main {
manifest.srcFile '@CMAKE_CURRENT_SOURCE_DIR@/AndroidManifest.xml'
java.srcDirs = ['@CMAKE_CURRENT_SOURCE_DIR@/org']
}
}
lintOptions {
abortOnError false
}
defaultConfig {
minSdkVersion @ANDROID_API_LEVEL@
targetSdkVersion @ANDROID_SDK_COMPILE_API@
namespace "org.kde.guiaddons"
}
}
@@ -0,0 +1,76 @@
/*
SPDX-FileCopyrightText: 2024 Mathis Brüchert <mbb@kaidan.im>
SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
package org.kde.guiaddons;
import android.app.Activity;
import android.os.Build;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowInsetsController;
public class KWindowInsetsController
{
public static void setStatusBarBackground(android.app.Activity activity, int color)
{
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(color);
if (Build.VERSION.SDK_INT >= 30) {
if (isDark(color)) {
window.getInsetsController().setSystemBarsAppearance(0, WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);
} else {
window.getInsetsController().setSystemBarsAppearance(WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS, WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS);
}
} else {
int visibility = window.getDecorView().getSystemUiVisibility();
if (isDark(color)) {
visibility &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else {
visibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
window.getDecorView().setSystemUiVisibility(visibility);
}
}
public static void setNavigationBarBackground(android.app.Activity activity, int color)
{
Window window = activity.getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setNavigationBarColor(color);
if (Build.VERSION.SDK_INT >= 30) {
if (isDark(color)) {
window.getInsetsController().setSystemBarsAppearance(0, WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS);
} else {
window.getInsetsController().setSystemBarsAppearance(WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS, WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS);
}
} else {
int visibility = window.getDecorView().getSystemUiVisibility();
if (isDark(color)) {
visibility &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else {
visibility |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
}
window.getDecorView().setSystemUiVisibility(visibility);
}
}
private static double luma(int color)
{
return (0.299 * (color >> 24) + 0.587 * ((color >> 16) & 0xff) + 0.114 * ((color >> 8) & 0xff)) / 255.0;
}
private static boolean isDark(int color)
{
return luma(color) <= 0.5;
}
}
@@ -0,0 +1,245 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
// KDE color collection
#include "kcolorcollection.h"
#if KGUIADDONS_BUILD_DEPRECATED_SINCE(6, 3)
#include <QDir>
#include <QFile>
#include <QSaveFile>
#include <QSharedData>
#include <QStandardPaths>
#include <QTextStream>
// BEGIN KColorCollectionPrivate
class KColorCollectionPrivate : public QSharedData
{
public:
KColorCollectionPrivate(const QString &);
KColorCollectionPrivate(const KColorCollectionPrivate &) = default;
~KColorCollectionPrivate()
{
}
struct ColorNode {
ColorNode(const QColor &c, const QString &n)
: color(c)
, name(n)
{
}
QColor color;
QString name;
};
QList<ColorNode> colorList;
QString name;
QString desc;
KColorCollection::Editable editable;
};
KColorCollectionPrivate::KColorCollectionPrivate(const QString &_name)
: name(_name)
{
if (name.isEmpty()) {
return;
}
QString filename = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("colors/") + name);
if (filename.isEmpty()) {
return;
}
QFile paletteFile(filename);
if (!paletteFile.exists()) {
return;
}
if (!paletteFile.open(QIODevice::ReadOnly)) {
return;
}
// Read first line
// Expected "GIMP Palette"
QString line = QString::fromLocal8Bit(paletteFile.readLine());
if (line.contains(QLatin1String(" Palette"))) {
return;
}
while (!paletteFile.atEnd()) {
line = QString::fromLocal8Bit(paletteFile.readLine());
if (line[0] == QLatin1Char('#')) {
// This is a comment line
line.remove(0, 1); // Strip '#'
line = line.trimmed(); // Strip remaining white space..
if (!line.isEmpty()) {
desc += line + QLatin1Char('\n'); // Add comment to description
}
} else {
// This is a color line, hopefully
line = line.trimmed();
if (line.isEmpty()) {
continue;
}
int r;
int g;
int b;
int pos = 0;
if (sscanf(line.toLatin1().constData(), "%d %d %d%n", &r, &g, &b, &pos) >= 3) {
r = qBound(0, r, 255);
g = qBound(0, g, 255);
b = qBound(0, b, 255);
QString name = line.mid(pos).trimmed();
colorList.append(ColorNode(QColor(r, g, b), name));
}
}
}
}
// END KColorCollectionPrivate
QStringList KColorCollection::installedCollections()
{
const QStringList paletteDirs = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("colors"), QStandardPaths::LocateDirectory);
QStringList paletteList;
for (const QString &dir : paletteDirs) {
paletteList += QDir(dir).entryList(QDir::Files | QDir::NoDotAndDotDot);
}
paletteList.removeDuplicates();
return paletteList;
}
KColorCollection::KColorCollection(const QString &name)
: d(new KColorCollectionPrivate(name))
{
}
KColorCollection::KColorCollection(const KColorCollection &p) = default;
// Need auto-save?
KColorCollection::~KColorCollection() = default;
bool KColorCollection::save()
{
QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/colors/") + d->name;
QSaveFile sf(filename);
if (!sf.open(QIODevice::WriteOnly)) {
return false;
}
QTextStream str(&sf);
QString description = d->desc.trimmed();
description = QLatin1Char('#') + description.split(QLatin1Char('\n'), Qt::KeepEmptyParts).join(QLatin1String("\n#"));
str << QLatin1String("KDE RGB Palette\n");
str << description << QLatin1Char('\n');
for (const KColorCollectionPrivate::ColorNode &node : std::as_const(d->colorList)) {
int r;
int g;
int b;
node.color.getRgb(&r, &g, &b);
str << r << " " << g << " " << b << " " << node.name << "\n";
}
return sf.commit();
}
QString KColorCollection::description() const
{
return d->desc;
}
void KColorCollection::setDescription(const QString &desc)
{
d->desc = desc;
}
QString KColorCollection::name() const
{
return d->name;
}
void KColorCollection::setName(const QString &name)
{
d->name = name;
}
KColorCollection::Editable KColorCollection::editable() const
{
return d->editable;
}
void KColorCollection::setEditable(Editable editable)
{
d->editable = editable;
}
int KColorCollection::count() const
{
return d->colorList.count();
}
KColorCollection &KColorCollection::operator=(const KColorCollection &p) = default;
QColor KColorCollection::color(int index) const
{
if ((index < 0) || (index >= count())) {
return QColor();
}
return d->colorList[index].color;
}
int KColorCollection::findColor(const QColor &color) const
{
for (int i = 0; i < d->colorList.size(); ++i) {
if (d->colorList[i].color == color) {
return i;
}
}
return -1;
}
QString KColorCollection::name(int index) const
{
if ((index < 0) || (index >= count())) {
return QString();
}
return d->colorList[index].name;
}
QString KColorCollection::name(const QColor &color) const
{
return name(findColor(color));
}
int KColorCollection::addColor(const QColor &newColor, const QString &newColorName)
{
d->colorList.append(KColorCollectionPrivate::ColorNode(newColor, newColorName));
return count() - 1;
}
int KColorCollection::changeColor(int index, const QColor &newColor, const QString &newColorName)
{
if ((index < 0) || (index >= count())) {
return -1;
}
KColorCollectionPrivate::ColorNode &node = d->colorList[index];
node.color = newColor;
node.name = newColorName;
return index;
}
int KColorCollection::changeColor(const QColor &oldColor, const QColor &newColor, const QString &newColorName)
{
return changeColor(findColor(oldColor), newColor, newColorName);
}
#endif
@@ -0,0 +1,195 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
// KDE color collection.
#ifndef KDELIBS_KCOLORCOLLECTION_H
#define KDELIBS_KCOLORCOLLECTION_H
#include <kguiaddons_export.h>
#include <QColor>
#include <QSharedDataPointer>
#include <QString>
#include <QStringList>
#if KGUIADDONS_ENABLE_DEPRECATED_SINCE(6, 3)
/**
* @class KColorCollection kcolorcollection.h KColorCollection
*
* Class for handling color collections ("palettes").
*
* This class makes it easy to handle color collections, sometimes referred to
* as "palettes". This class can read and write collections from and to a file.
*
* This class uses the "GIMP" palette file format.
*
* @author Waldo Bastian (bastian@kde.org)
*
* @deprecated since 6.3, unused and backing data no longer available
*/
class KGUIADDONS_EXPORT KGUIADDONS_DEPRECATED_VERSION(6, 3, "unused") KColorCollection
{
public:
/**
* Query which KDE color collections are installed.
*
* @return A list with installed color collection names.
*/
static QStringList installedCollections();
/**
* KColorCollection constructor. Creates a KColorCollection from a file
* the filename is derived from the name.
* @param name The name of collection as returned by installedCollections()
*/
explicit KColorCollection(const QString &name = QString());
/**
* KColorCollection copy constructor.
*/
KColorCollection(const KColorCollection &);
/**
* KColorCollection destructor.
*/
~KColorCollection();
/**
* KColorCollection assignment operator
*/
KColorCollection &operator=(const KColorCollection &);
/**
* Save the collection
*
* @return 'true' if successful
*/
bool save();
/**
* Get the description of the collection.
* @return the description of the collection.
*/
QString description() const;
/**
* Set the description of the collection.
* @param desc the new description
*/
void setDescription(const QString &desc);
/**
* Get the name of the collection.
* @return the name of the collection
*/
QString name() const;
/**
* Set the name of the collection.
* @param name the name of the collection
*/
void setName(const QString &name);
/**
* Used to specify whether a collection may be edited.
* @see editable()
* @see setEditable()
*/
enum Editable {
Yes, ///< Collection may be edited
No, ///< Collection may not be edited
Ask, ///< Ask user before editing
};
/**
* Returns whether the collection may be edited.
* @return the state of the collection
*/
Editable editable() const;
/**
* Change whether the collection may be edited.
* @param editable the state of the collection
*/
void setEditable(Editable editable);
/**
* Return the number of colors in the collection.
* @return the number of colors
*/
int count() const;
/**
* Find color by index.
* @param index the index of the desired color
* @return The @p index -th color of the collection, null if not found.
*/
QColor color(int index) const;
/**
* Find index by @p color.
* @param color the color to find
* @return The index of the color in the collection or -1 if the
* color is not found.
*/
int findColor(const QColor &color) const;
/**
* Find color name by @p index.
* @param index the index of the color
* @return The name of the @p index -th color.
* Note that not all collections have named the colors. Null is
* returned if the color does not exist or has no name.
*/
QString name(int index) const;
/**
* Find color name by @p color.
* @return The name of color according to this collection.
* Note that not all collections have named the colors.
* Note also that each collection can give the same color
* a different name.
*/
QString name(const QColor &color) const;
/**
* Add a color.
* @param newColor The color to add.
* @param newColorName The name of the color, null to remove
* the name.
* @return The index of the added color.
*/
int addColor(const QColor &newColor, const QString &newColorName = QString());
/**
* Change a color.
* @param index Index of the color to change
* @param newColor The new color.
* @param newColorName The new color name, null to remove
* the name.
* @return The index of the new color or -1 if the color couldn't
* be changed.
*/
int changeColor(int index, const QColor &newColor, const QString &newColorName = QString());
/**
* Change a color.
* @param oldColor The original color
* @param newColor The new color.
* @param newColorName The new color name, null to remove
* the name.
* @return The index of the new color or -1 if the color couldn't
* be changed.
*/
int changeColor(const QColor &oldColor, const QColor &newColor, const QString &newColorName = QString());
private:
QSharedDataPointer<class KColorCollectionPrivate> d;
};
#endif
#endif // KDELIBS_KCOLORCOLLECTION_H
@@ -0,0 +1,61 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Steffen Hansen <hansen@kde.org>
SPDX-FileCopyrightText: 2005 Joseph Wenninger <jowenn@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcolormimedata.h"
#include <QColor>
#include <QDrag>
#include <QMimeData>
#include <QPainter>
void KColorMimeData::populateMimeData(QMimeData *mimeData, const QColor &color)
{
mimeData->setColorData(color);
mimeData->setText(color.name());
}
bool KColorMimeData::canDecode(const QMimeData *mimeData)
{
if (mimeData->hasColor()) {
return true;
}
if (mimeData->hasText()) {
const QString colorName = mimeData->text();
if ((colorName.length() >= 4) && (colorName[0] == QLatin1Char('#'))) {
return true;
}
}
return false;
}
QColor KColorMimeData::fromMimeData(const QMimeData *mimeData)
{
if (mimeData->hasColor()) {
return mimeData->colorData().value<QColor>();
}
if (canDecode(mimeData)) {
return QColor(mimeData->text());
}
return QColor();
}
QDrag *KColorMimeData::createDrag(const QColor &color, QObject *dragsource)
{
QDrag *drag = new QDrag(dragsource);
QMimeData *mime = new QMimeData;
populateMimeData(mime, color);
drag->setMimeData(mime);
QPixmap colorpix(25, 20);
colorpix.fill(color);
QPainter p(&colorpix);
p.setPen(Qt::black);
p.drawRect(0, 0, 24, 19);
p.end();
drag->setPixmap(colorpix);
drag->setHotSpot(QPoint(-5, -7));
return drag;
}
@@ -0,0 +1,55 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Steffen Hansen <hansen@kde.org>
SPDX-FileCopyrightText: 2005 Joseph Wenninger <jowenn@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef _KCOLORMIMEDATA_H
#define _KCOLORMIMEDATA_H
#include <kguiaddons_export.h>
class QColor;
class QDrag;
class QMimeData;
class QObject;
/**
* Drag-and-drop and clipboard mimedata manipulation for QColor objects. The according MIME type
* is set to application/x-color.
*
* See the Qt drag'n'drop documentation.
*/
namespace KColorMimeData
{
/**
* Sets the color and text representation fields for the specified color in the mimedata object:
* application/x-color and text/plain types are set
*/
KGUIADDONS_EXPORT void populateMimeData(QMimeData *mimeData, const QColor &color);
/**
* Returns true if the MIME data @p mimeData contains a color object.
* First checks for application/x-color and if that fails, for a text/plain entry, which
* represents a color in the format \#hexnumbers
*/
KGUIADDONS_EXPORT bool canDecode(const QMimeData *mimeData);
/**
* Decodes the MIME data @p mimeData and returns the resulting color.
* First tries application/x-color and if that fails, a text/plain entry, which
* represents a color in the format \#hexnumbers. If this fails too,
* an invalid QColor object is returned, use QColor::isValid() to test it.
*/
KGUIADDONS_EXPORT QColor fromMimeData(const QMimeData *mimeData);
/**
* Creates a color drag object. Either you have to start this drag or delete it
* The drag object's mime data has the application/x-color and text/plain type set and a pixmap
* filled with the specified color, which is going to be displayed next to the mouse cursor
*/
KGUIADDONS_EXPORT QDrag *createDrag(const QColor &color, QObject *dragsource);
}
#endif // _KCOLORMIMEDATA_H
@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcolorschemewatcher.h"
#include "kcolorschemewatcher_qt.h"
#include "kcolorschemewatcherbackend.h"
#ifdef Q_OS_WINDOWS
#include "kcolorschemewatcher_win.h"
#endif
#ifdef Q_OS_MACOS
#include "kcolorschemewatcher_mac.h"
#endif
#ifdef QT_DBUS_LIB
#include "kcolorschemewatcher_xdg.h"
#endif
class KColorSchemeWatcherPrivate
{
public:
std::unique_ptr<KColorSchemeWatcherBackend> backend;
KColorSchemeWatcherPrivate()
{
#ifdef Q_OS_WINDOWS
backend = std::make_unique<KColorSchemeWatcherWin>();
#elif defined(Q_OS_MACOS)
backend = std::make_unique<KColorSchemeWatcherMac>();
#elif defined(QT_DBUS_LIB)
backend = std::make_unique<KColorSchemeWatcherXDG>();
#else
backend = std::make_unique<KColorSchemeWatcherQt>();
#endif
}
};
KColorSchemeWatcher::KColorSchemeWatcher(QObject *parent)
: QObject(parent)
, d(new KColorSchemeWatcherPrivate)
{
if (d->backend) {
connect(d->backend.get(), &KColorSchemeWatcherBackend::systemPreferenceChanged, this, &KColorSchemeWatcher::systemPreferenceChanged);
}
}
KColorSchemeWatcher::~KColorSchemeWatcher()
{
}
KColorSchemeWatcher::ColorPreference KColorSchemeWatcher::systemPreference() const
{
if (d->backend) {
return d->backend->systemPreference();
}
return NoPreference;
}
#include "moc_kcolorschemewatcher.cpp"
@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOLORSCHEMEWATCHER_H
#define KCOLORSCHEMEWATCHER_H
#include "kguiaddons_export.h"
#include <QObject>
#include <memory>
class KColorSchemeWatcherPrivate;
/**
* Information about system-wide color preferences.
* @since 5.100
*/
class KGUIADDONS_EXPORT KColorSchemeWatcher : public QObject
{
Q_OBJECT
public:
enum ColorPreference {
NoPreference = 0, /** No preference available */
PreferDark, /** The user prefers a dark color scheme */
PreferLight, /** The user prefers a light color scheme */
};
Q_ENUM(ColorPreference)
KColorSchemeWatcher(QObject *parent = nullptr);
~KColorSchemeWatcher() override;
/**
* The system-wide color preference.
*/
ColorPreference systemPreference() const;
Q_SIGNALS:
/**
* Emitted when systemPreference changes.
*/
void systemPreferenceChanged();
private:
std::unique_ptr<KColorSchemeWatcherPrivate> const d;
};
#endif
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2022 Jyrki Gadinger <nilsding@nilsding.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOLORSCHEMEWATCHER_MAC_H
#define KCOLORSCHEMEWATCHER_MAC_H
#include "kcolorschemewatcherbackend.h"
class KColorSchemeWatcherMac : public KColorSchemeWatcherBackend
{
Q_OBJECT
public:
explicit KColorSchemeWatcherMac();
~KColorSchemeWatcherMac();
KColorSchemeWatcher::ColorPreference systemPreference() const override;
private:
// not ideal, in obj-c++ this would be an `id`, but since this header is
// included from pure C++ files the compiler will not know what to do with
// an `id`
void *m_observer;
};
#endif
@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2022 Jyrki Gadinger <nilsding@nilsding.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcolorschemewatcher_mac.h"
#import <AppKit/AppKit.h>
#include <QTimer>
KColorSchemeWatcherMac::KColorSchemeWatcherMac()
{
// subscribe to the distributed notification centre
id notificationCenter = NSDistributedNotificationCenter.defaultCenter;
m_observer = [notificationCenter addObserverForName:@"AppleInterfaceThemeChangedNotification"
object:nil
queue:nil
usingBlock:^(NSNotification *) {
// "fun" workaround to not emit the signal immediately after receiving the notification.
// for some reason NSAppearance.currentDrawingAppearance is still set to the old value here, after a short
// delay it is updated correctly
QTimer::singleShot(0, [this]() {
Q_EMIT systemPreferenceChanged();
});
}];
}
KColorSchemeWatcherMac::~KColorSchemeWatcherMac()
{
[NSDistributedNotificationCenter.defaultCenter removeObserver:static_cast<id>(m_observer)];
}
KColorSchemeWatcher::ColorPreference KColorSchemeWatcherMac::systemPreference() const
{
NSAppearance *appearance = nullptr;
if (@available(macOS 11.0, *)) {
appearance = NSAppearance.currentDrawingAppearance;
} else if (@available(macOS 10.14, *)) {
appearance = NSAppearance.currentAppearance;
} else {
// macOS < 10.14 (Mojave) does not support a light/dark mode switch, always prefer light mode
return KColorSchemeWatcher::PreferLight;
}
return appearance.name == NSAppearanceNameDarkAqua ? KColorSchemeWatcher::PreferDark : KColorSchemeWatcher::PreferLight;
}
#include "moc_kcolorschemewatcher_mac.cpp"
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcolorschemewatcher_qt.h"
#include <QGuiApplication>
#include <QStyleHints>
KColorSchemeWatcherQt::KColorSchemeWatcherQt()
{
connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &KColorSchemeWatcherBackend::systemPreferenceChanged);
}
KColorSchemeWatcher::ColorPreference KColorSchemeWatcherQt::systemPreference() const
{
switch (QGuiApplication::styleHints()->colorScheme()) {
case Qt::ColorScheme::Unknown:
return KColorSchemeWatcher::NoPreference;
case Qt::ColorScheme::Light:
return KColorSchemeWatcher::PreferLight;
case Qt::ColorScheme::Dark:
return KColorSchemeWatcher::PreferDark;
}
return KColorSchemeWatcher::NoPreference;
}
#include "moc_kcolorschemewatcher_qt.cpp"
@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOLORSCHEMEWATCHER_QT_H
#define KCOLORSCHEMEWATCHER_QT_H
#include "kcolorschemewatcherbackend.h"
class KColorSchemeWatcherQt : public KColorSchemeWatcherBackend
{
Q_OBJECT
public:
KColorSchemeWatcherQt();
KColorSchemeWatcher::ColorPreference systemPreference() const override;
};
#endif
@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2020 Piyush Aggarwal <piyushaggarwal002@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcolorschemewatcher_win.h"
#include <windows.h>
#include <QAbstractEventDispatcher>
#include <QDebug>
KColorSchemeWatcherWin::KColorSchemeWatcherWin()
{
QAbstractEventDispatcher::instance()->installNativeEventFilter(this);
m_preferDarkMode = !(m_settings.value(QStringLiteral("AppsUseLightTheme"), true).value<bool>());
}
bool KColorSchemeWatcherWin::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *)
{
MSG *msg = static_cast<MSG *>(message);
switch (msg->message) {
case WM_SETTINGCHANGE: {
m_settings.sync();
const bool preferDarkModeNow = !(m_settings.value(QStringLiteral("AppsUseLightTheme"), true).value<bool>());
if (m_preferDarkMode != preferDarkModeNow) {
m_preferDarkMode = preferDarkModeNow;
Q_EMIT systemPreferenceChanged();
}
break;
}
default: {
}
}
return false;
}
KColorSchemeWatcher::ColorPreference KColorSchemeWatcherWin::systemPreference() const
{
return m_preferDarkMode ? KColorSchemeWatcher::PreferDark : KColorSchemeWatcher::PreferLight;
}
#include "moc_kcolorschemewatcher_win.cpp"
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2020 Piyush Aggarwal <piyushaggarwal002@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOLORSCHEMEWATCHER_WIN_H
#define KCOLORSCHEMEWATCHER_WIN_H
#include <QAbstractNativeEventFilter>
#include <QSettings>
#include "kcolorschemewatcherbackend.h"
class KColorSchemeWatcherWin : public KColorSchemeWatcherBackend, public QAbstractNativeEventFilter
{
Q_OBJECT
public:
KColorSchemeWatcherWin();
KColorSchemeWatcher::ColorPreference systemPreference() const override;
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override;
private:
QSettings m_settings{QStringLiteral("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"), QSettings::NativeFormat};
bool m_preferDarkMode = false;
};
#endif
@@ -0,0 +1,71 @@
/*
* SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcolorschemewatcher_xdg.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusReply>
#include <QDBusVariant>
#include <QDebug>
KColorSchemeWatcherXDG::KColorSchemeWatcherXDG()
: KColorSchemeWatcherBackend()
{
QDBusConnection::sessionBus().connect(QStringLiteral("org.freedesktop.portal.Desktop"),
QStringLiteral("/org/freedesktop/portal/desktop"),
QStringLiteral("org.freedesktop.portal.Settings"),
QStringLiteral("SettingChanged"),
this,
SLOT(slotSettingChanged(QString, QString, QDBusVariant)));
QDBusMessage m = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"),
QStringLiteral("/org/freedesktop/portal/desktop"),
QStringLiteral("org.freedesktop.portal.Settings"),
QStringLiteral("Read"));
m.setArguments({QStringLiteral("org.freedesktop.appearance"), QStringLiteral("color-scheme")});
QDBusReply<QDBusVariant> reply = QDBusConnection::sessionBus().call(m);
if (reply.isValid()) {
const uint result = reply.value().variant().value<QDBusVariant>().variant().toUInt();
m_preference = fdoToInternal(result);
}
}
KColorSchemeWatcher::ColorPreference KColorSchemeWatcherXDG::systemPreference() const
{
return m_preference;
}
void KColorSchemeWatcherXDG::slotSettingChanged(QString nameSpace, QString key, QDBusVariant value)
{
if (nameSpace == QLatin1String("org.freedesktop.appearance") && key == QLatin1String("color-scheme")) {
const uint result = value.variant().toUInt();
auto newValue = fdoToInternal(result);
if (m_preference != newValue) {
m_preference = fdoToInternal(result);
Q_EMIT systemPreferenceChanged();
}
}
}
KColorSchemeWatcher::ColorPreference KColorSchemeWatcherXDG::fdoToInternal(uint value) const
{
if (value == 0) {
return KColorSchemeWatcher::NoPreference;
} else if (value == 1) {
return KColorSchemeWatcher::PreferDark;
} else if (value == 2) {
return KColorSchemeWatcher::PreferLight;
} else {
qWarning() << "Unhandled org.freedesktop.appearance color-scheme value" << value;
return KColorSchemeWatcher::NoPreference;
}
}
#include "moc_kcolorschemewatcher_xdg.cpp"
@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOLORSCHEMEWATCHER_XDG_H
#define KCOLORSCHEMEWATCHER_XDG_H
#include "kcolorschemewatcherbackend.h"
#include <QDBusVariant>
class KColorSchemeWatcherXDG : public KColorSchemeWatcherBackend
{
Q_OBJECT
public:
KColorSchemeWatcherXDG();
KColorSchemeWatcher::ColorPreference systemPreference() const override;
private Q_SLOTS:
void slotSettingChanged(QString, QString, QDBusVariant);
private:
KColorSchemeWatcher::ColorPreference fdoToInternal(uint value) const;
KColorSchemeWatcher::ColorPreference m_preference = KColorSchemeWatcher::NoPreference;
};
#endif
@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcolorschemewatcherbackend.h"
#include "moc_kcolorschemewatcherbackend.cpp"
@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOLORSCHEMEWATCHERBACKEND_H
#define KCOLORSCHEMEWATCHERBACKEND_H
#include <QObject>
#include "kcolorschemewatcher.h"
class KColorSchemeWatcherBackend : public QObject
{
Q_OBJECT
public:
virtual KColorSchemeWatcher::ColorPreference systemPreference() const = 0;
Q_SIGNALS:
void systemPreferenceChanged();
};
#endif
@@ -0,0 +1,163 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
SPDX-FileCopyrightText: 2007 Olaf Schmidt <ojschmidt@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcolorspaces_p.h"
#include "kguiaddons_colorhelpers_p.h"
#include <QColor>
#include <math.h>
using namespace KColorSpaces;
static inline qreal wrap(qreal a, qreal d = 1.0)
{
qreal r = fmod(a, d);
return (r < 0.0 ? d + r : (r > 0.0 ? r : 0.0));
}
///////////////////////////////////////////////////////////////////////////////
// HCY color space
#define HCY_REC 709 // use 709 for now
#if HCY_REC == 601
static const qreal yc[3] = {0.299, 0.587, 0.114};
#elif HCY_REC == 709
static const qreal yc[3] = {0.2126, 0.7152, 0.0722};
#else // use Qt values
static const qreal yc[3] = {0.34375, 0.5, 0.15625};
#endif
qreal KHCY::gamma(qreal n)
{
return pow(normalize(n), 2.2);
}
qreal KHCY::igamma(qreal n)
{
return pow(normalize(n), 1.0 / 2.2);
}
qreal KHCY::lumag(qreal r, qreal g, qreal b)
{
return r * yc[0] + g * yc[1] + b * yc[2];
}
KHCY::KHCY(qreal h_, qreal c_, qreal y_, qreal a_)
{
h = h_;
c = c_;
y = y_;
a = a_;
}
KHCY::KHCY(const QColor &color)
{
qreal r = gamma(color.redF());
qreal g = gamma(color.greenF());
qreal b = gamma(color.blueF());
a = color.alphaF();
// luma component
y = lumag(r, g, b);
// hue component
qreal p = qMax(qMax(r, g), b);
qreal n = qMin(qMin(r, g), b);
qreal d = 6.0 * (p - n);
if (n == p) {
h = 0.0;
} else if (r == p) {
h = ((g - b) / d);
} else if (g == p) {
h = ((b - r) / d) + (1.0 / 3.0);
} else {
h = ((r - g) / d) + (2.0 / 3.0);
}
// chroma component
if (r == g && g == b) {
c = 0.0;
} else {
c = qMax((y - n) / y, (p - y) / (1 - y));
}
}
QColor KHCY::qColor() const
{
// start with sane component values
qreal _h = wrap(h);
qreal _c = normalize(c);
qreal _y = normalize(y);
// calculate some needed variables
qreal _hs = _h * 6.0;
qreal th;
qreal tm;
if (_hs < 1.0) {
th = _hs;
tm = yc[0] + yc[1] * th;
} else if (_hs < 2.0) {
th = 2.0 - _hs;
tm = yc[1] + yc[0] * th;
} else if (_hs < 3.0) {
th = _hs - 2.0;
tm = yc[1] + yc[2] * th;
} else if (_hs < 4.0) {
th = 4.0 - _hs;
tm = yc[2] + yc[1] * th;
} else if (_hs < 5.0) {
th = _hs - 4.0;
tm = yc[2] + yc[0] * th;
} else {
th = 6.0 - _hs;
tm = yc[0] + yc[2] * th;
}
// calculate RGB channels in sorted order
qreal tn;
qreal to;
qreal tp;
if (tm >= _y) {
tp = _y + _y * _c * (1.0 - tm) / tm;
to = _y + _y * _c * (th - tm) / tm;
tn = _y - (_y * _c);
} else {
tp = _y + (1.0 - _y) * _c;
to = _y + (1.0 - _y) * _c * (th - tm) / (1.0 - tm);
tn = _y - (1.0 - _y) * _c * tm / (1.0 - tm);
}
// return RGB channels in appropriate order
if (_hs < 1.0) {
return QColor::fromRgbF(igamma(tp), igamma(to), igamma(tn), a);
} else if (_hs < 2.0) {
return QColor::fromRgbF(igamma(to), igamma(tp), igamma(tn), a);
} else if (_hs < 3.0) {
return QColor::fromRgbF(igamma(tn), igamma(tp), igamma(to), a);
} else if (_hs < 4.0) {
return QColor::fromRgbF(igamma(tn), igamma(to), igamma(tp), a);
} else if (_hs < 5.0) {
return QColor::fromRgbF(igamma(to), igamma(tn), igamma(tp), a);
} else {
return QColor::fromRgbF(igamma(tp), igamma(tn), igamma(to), a);
}
}
qreal KHCY::hue(const QColor &color)
{
return wrap(KHCY(color).h);
}
qreal KHCY::chroma(const QColor &color)
{
return KHCY(color).c;
}
qreal KHCY::luma(const QColor &color)
{
return lumag(gamma(color.redF()), gamma(color.greenF()), gamma(color.blueF()));
}
@@ -0,0 +1,39 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
/*
* If you use KColorSpaces in your own KDE code, please drop me a line at
* mw_triad@users.sourceforge.net, as I would like to track if people find it
* useful. Thanks!
*/
#ifndef KCOLORSPACES_H
#define KCOLORSPACES_H
#include <QColor>
namespace KColorSpaces
{
class KHCY
{
public:
explicit KHCY(const QColor &);
explicit KHCY(qreal h_, qreal c_, qreal y_, qreal a_ = 1.0);
QColor qColor() const;
qreal h, c, y, a;
static qreal hue(const QColor &);
static qreal chroma(const QColor &);
static qreal luma(const QColor &);
private:
static qreal gamma(qreal);
static qreal igamma(qreal);
static qreal lumag(qreal, qreal, qreal);
};
}
#endif
@@ -0,0 +1,181 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
SPDX-FileCopyrightText: 2007 Thomas Zander <zander@kde.org>
SPDX-FileCopyrightText: 2007 Zack Rusin <zack@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kcolorspaces_p.h"
#include "kguiaddons_colorhelpers_p.h"
#include <kcolorutils.h>
#include <QColor>
#include <QImage>
#include <QtNumeric> // qIsNaN
#include <math.h>
// BEGIN internal helper functions
static inline qreal mixQreal(qreal a, qreal b, qreal bias)
{
return a + (b - a) * bias;
}
// END internal helper functions
qreal KColorUtils::hue(const QColor &color)
{
return KColorSpaces::KHCY::hue(color);
}
qreal KColorUtils::chroma(const QColor &color)
{
return KColorSpaces::KHCY::chroma(color);
}
qreal KColorUtils::luma(const QColor &color)
{
return KColorSpaces::KHCY::luma(color);
}
void KColorUtils::getHcy(const QColor &color, qreal *h, qreal *c, qreal *y, qreal *a)
{
if (!c || !h || !y) {
return;
}
KColorSpaces::KHCY khcy(color);
*c = khcy.c;
*h = khcy.h + (khcy.h < 0.0 ? 1.0 : 0.0);
*y = khcy.y;
if (a) {
*a = khcy.a;
}
}
QColor KColorUtils::hcyColor(qreal h, qreal c, qreal y, qreal a)
{
return KColorSpaces::KHCY(h, c, y, a).qColor();
}
static qreal contrastRatioForLuma(qreal y1, qreal y2)
{
if (y1 > y2) {
return (y1 + 0.05) / (y2 + 0.05);
} else {
return (y2 + 0.05) / (y1 + 0.05);
}
}
qreal KColorUtils::contrastRatio(const QColor &c1, const QColor &c2)
{
return contrastRatioForLuma(luma(c1), luma(c2));
}
QColor KColorUtils::lighten(const QColor &color, qreal ky, qreal kc)
{
KColorSpaces::KHCY c(color);
c.y = 1.0 - normalize((1.0 - c.y) * (1.0 - ky));
c.c = 1.0 - normalize((1.0 - c.c) * kc);
return c.qColor();
}
QColor KColorUtils::darken(const QColor &color, qreal ky, qreal kc)
{
KColorSpaces::KHCY c(color);
c.y = normalize(c.y * (1.0 - ky));
c.c = normalize(c.c * kc);
return c.qColor();
}
QColor KColorUtils::shade(const QColor &color, qreal ky, qreal kc)
{
KColorSpaces::KHCY c(color);
c.y = normalize(c.y + ky);
c.c = normalize(c.c + kc);
return c.qColor();
}
static KColorSpaces::KHCY tintHelper(const QColor &base, qreal baseLuma, const QColor &color, qreal amount)
{
KColorSpaces::KHCY result(KColorUtils::mix(base, color, pow(amount, 0.3)));
result.y = mixQreal(baseLuma, result.y, amount);
return result;
}
static qreal tintHelperLuma(const QColor &base, qreal baseLuma, const QColor &color, qreal amount)
{
qreal result(KColorUtils::luma(KColorUtils::mix(base, color, pow(amount, 0.3))));
result = mixQreal(baseLuma, result, amount);
return result;
}
QColor KColorUtils::tint(const QColor &base, const QColor &color, qreal amount)
{
if (amount <= 0.0) {
return base;
}
if (amount >= 1.0) {
return color;
}
if (qIsNaN(amount)) {
return base;
}
qreal baseLuma = luma(base); // cache value because luma call is expensive
double ri = contrastRatioForLuma(baseLuma, luma(color));
double rg = 1.0 + ((ri + 1.0) * amount * amount * amount);
double u = 1.0;
double l = 0.0;
double a = 0.5;
for (int i = 12; i; --i) {
a = 0.5 * (l + u);
qreal resultLuma = tintHelperLuma(base, baseLuma, color, a);
double ra = contrastRatioForLuma(baseLuma, resultLuma);
if (ra > rg) {
u = a;
} else {
l = a;
}
}
return tintHelper(base, baseLuma, color, a).qColor();
}
QColor KColorUtils::mix(const QColor &c1, const QColor &c2, qreal bias)
{
if (bias <= 0.0) {
return c1;
}
if (bias >= 1.0) {
return c2;
}
if (qIsNaN(bias)) {
return c1;
}
qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias);
if (a <= 0.0) {
return Qt::transparent;
}
qreal r = qBound(0.0, mixQreal(c1.redF() * c1.alphaF(), c2.redF() * c2.alphaF(), bias), 1.0) / a;
qreal g = qBound(0.0, mixQreal(c1.greenF() * c1.alphaF(), c2.greenF() * c2.alphaF(), bias), 1.0) / a;
qreal b = qBound(0.0, mixQreal(c1.blueF() * c1.alphaF(), c2.blueF() * c2.alphaF(), bias), 1.0) / a;
return QColor::fromRgbF(r, g, b, a);
}
QColor KColorUtils::overlayColors(const QColor &base, const QColor &paint, QPainter::CompositionMode comp)
{
// This isn't the fastest way, but should be "fast enough".
// It's also the only safe way to use QPainter::CompositionMode
QImage img(1, 1, QImage::Format_ARGB32_Premultiplied);
QPainter p(&img);
QColor start = base;
start.setAlpha(255); // opaque
p.fillRect(0, 0, 1, 1, start);
p.setCompositionMode(comp);
p.fillRect(0, 0, 1, 1, paint);
p.end();
return img.pixel(0, 0);
}
@@ -0,0 +1,187 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
SPDX-FileCopyrightText: 2007 Thomas Zander <zander@kde.org>
SPDX-FileCopyrightText: 2007 Zack Rusin <zack@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KCOLORUTILS_H
#define KCOLORUTILS_H
#include <kguiaddons_export.h>
#include <QPainter>
class QColor;
/**
* A set of methods used to work with colors.
*/
namespace KColorUtils
{
/**
* Calculate the hue of a color. The range is from 0.0 (red) to almost 1.0 (slightly blue-ish red).
*
* The result is computed in linear (not sRGB) color space and may differ slightly from QColor::hue().
*
* @see https://en.wikipedia.org/wiki/Hue
* @since 5.68
*/
KGUIADDONS_EXPORT qreal hue(const QColor &);
/**
* Calculate the chroma of a color. The range is from 0.0 (none) to 1.0 (full).
*
* The result is computed in linear (not sRGB) color space.
*
* @see https://en.wikipedia.org/wiki/Colorfulness
* @since 5.68
*/
KGUIADDONS_EXPORT qreal chroma(const QColor &);
/**
* Calculate the luma of a color. Luma is weighted sum of gamma-adjusted
* R'G'B' components of a color. The result is similar to qGray. The range
* is from 0.0 (black) to 1.0 (white).
*
* The result is computed in linear (not sRGB) color space.
*
* KColorUtils::darken(), KColorUtils::lighten() and KColorUtils::shade()
* operate on the luma of a color.
*
* @see http://en.wikipedia.org/wiki/Luma_(video)
*/
KGUIADDONS_EXPORT qreal luma(const QColor &);
/**
* Calculate hue, chroma and luma of a color in one call.
*
* The range of hue is from 0.0 (red) to almost 1.0 (slightly blue-ish red).
* The range of chroma is from 0.0 (none) to 1.0 (full).
* The range of luma is from 0.0 (black) to 1.0 (white).
*
* The hue, chroma and luma values are computed in linear (not sRGB) color space.
*
* @since 5.0
*/
KGUIADDONS_EXPORT void getHcy(const QColor &, qreal *hue, qreal *chroma, qreal *luma, qreal *alpha = nullptr);
/**
* Return a QColor based on the given hue, chroma, luma and alpha values.
*
* The range of hue is cyclical. For example, 0.0 and 1.0 are both red while -0.166667 and 0.833333 are both magenta.
* The range of chroma is from 0.0 (none) to 1.0 (full). Out of range values will be clamped.
* The range of luma is from 0.0 (black) to 1.0 (white). Out of range values will be clamped.
*
* The hue, chroma and luma values are computed in linear (not sRGB) color space.
*
* @since 5.68
*/
KGUIADDONS_EXPORT QColor hcyColor(qreal hue, qreal chroma, qreal luma, qreal alpha = 1.0);
/**
* Calculate the contrast ratio between two colors, according to the
* W3C/WCAG2.0 algorithm, (Lmax + 0.05)/(Lmin + 0.05), where Lmax and Lmin
* are the luma values of the lighter color and the darker color,
* respectively.
*
* A contrast ration of 5:1 (result == 5.0) is the minimum for "normal"
* text to be considered readable (large text can go as low as 3:1). The
* ratio ranges from 1:1 (result == 1.0) to 21:1 (result == 21.0).
*
* @see KColorUtils::luma
*/
KGUIADDONS_EXPORT qreal contrastRatio(const QColor &, const QColor &);
/**
* Adjust the luma of a color by changing its distance from white.
*
* @li amount == 1.0 gives white
* @li amount == 0.5 results in a color whose luma is halfway between 1.0
* and that of the original color
* @li amount == 0.0 gives the original color
* @li amount == -1.0 gives a color that is 'twice as far from white' as
* the original color, that is luma(result) == 1.0 - 2*(1.0 - luma(color))
*
* @param amount factor by which to adjust the luma component of the color
* @param chromaInverseGain (optional) factor by which to adjust the chroma
* component of the color; 1.0 means no change, 0.0 maximizes chroma
* @see KColorUtils::shade
*/
KGUIADDONS_EXPORT QColor lighten(const QColor &, qreal amount = 0.5, qreal chromaInverseGain = 1.0);
/**
* Adjust the luma of a color by changing its distance from black.
*
* @li amount == 1.0 gives black
* @li amount == 0.5 results in a color whose luma is halfway between 0.0
* and that of the original color
* @li amount == 0.0 gives the original color
* @li amount == -1.0 gives a color that is 'twice as far from black' as
* the original color, that is luma(result) == 2*luma(color)
*
* @param amount factor by which to adjust the luma component of the color
* @param chromaGain (optional) factor by which to adjust the chroma
* component of the color; 1.0 means no change, 0.0 minimizes chroma
* @see KColorUtils::shade
*/
KGUIADDONS_EXPORT QColor darken(const QColor &, qreal amount = 0.5, qreal chromaGain = 1.0);
/**
* Adjust the luma and chroma components of a color. The amount is added
* to the corresponding component.
*
* @param lumaAmount amount by which to adjust the luma component of the
* color; 0.0 results in no change, -1.0 turns anything black, 1.0 turns
* anything white
* @param chromaAmount (optional) amount by which to adjust the chroma
* component of the color; 0.0 results in no change, -1.0 minimizes chroma,
* 1.0 maximizes chroma
* @see KColorUtils::luma
*/
KGUIADDONS_EXPORT QColor shade(const QColor &, qreal lumaAmount, qreal chromaAmount = 0.0);
/**
* Create a new color by tinting one color with another. This function is
* meant for creating additional colors withings the same class (background,
* foreground) from colors in a different class. Therefore when @p amount
* is low, the luma of @p base is mostly preserved, while the hue and
* chroma of @p color is mostly inherited.
*
* @param base color to be tinted
* @param color color with which to tint
* @param amount how strongly to tint the base; 0.0 gives @p base,
* 1.0 gives @p color
*/
KGUIADDONS_EXPORT QColor tint(const QColor &base, const QColor &color, qreal amount = 0.3);
/**
* Blend two colors into a new color by linear combination.
* @code
QColor lighter = KColorUtils::mix(myColor, Qt::white)
* @endcode
* @param c1 first color.
* @param c2 second color.
* @param bias weight to be used for the mix. @p bias <= 0 gives @p c1,
* @p bias >= 1 gives @p c2. @p bias == 0.5 gives a 50% blend of @p c1
* and @p c2.
*/
KGUIADDONS_EXPORT QColor mix(const QColor &c1, const QColor &c2, qreal bias = 0.5);
/**
* Blend two colors into a new color by painting the second color over the
* first using the specified composition mode.
* @code
QColor white(Qt::white);
white.setAlphaF(0.5);
QColor lighter = KColorUtils::overlayColors(myColor, white);
@endcode
* @param base the base color (alpha channel is ignored).
* @param paint the color to be overlaid onto the base color.
* @param comp the CompositionMode used to do the blending.
*/
KGUIADDONS_EXPORT QColor overlayColors(const QColor &base, const QColor &paint, QPainter::CompositionMode comp = QPainter::CompositionMode_SourceOver);
}
#endif // KCOLORUTILS_H
@@ -0,0 +1,18 @@
/* This file is part of the KDE project
SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
SPDX-FileCopyrightText: 2007 Olaf Schmidt <ojschmidt@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KGUIADDONS_COLORHELPERS_P_H
#define KGUIADDONS_COLORHELPERS_P_H
// normalize: like qBound(a, 0.0, 1.0) but without needing the args and with
// "safer" behavior on NaN (isnan(a) -> return 0.0)
static inline qreal normalize(qreal a)
{
return (a < 1.0 ? (a > 0.0 ? a : 0.0) : 1.0);
}
#endif // KGUIADDONS_KCOLORHELPERS_P_H
@@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: 2024 Mathis Brüchert <mbb@kaidan.im>
// SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "kwindowinsetscontroller.h"
#include <QCoreApplication>
#ifdef Q_OS_ANDROID
#include <QJniObject>
#endif
class KWindowInsetsControllerPrivate
{
public:
QColor m_statusBarColor;
QColor m_navigationBarColor;
};
KWindowInsetsController::KWindowInsetsController(QObject *parent)
: QObject(parent)
#ifdef Q_OS_ANDROID
, d(new KWindowInsetsControllerPrivate)
#endif
{
}
KWindowInsetsController::~KWindowInsetsController() = default;
QColor KWindowInsetsController::statusBarBackgroundColor() const // NOLINT readability-convert-member-functions-to-static
{
#ifdef Q_OS_ANDROID
return d->m_statusBarColor;
#else
return {};
#endif
}
void KWindowInsetsController::setStatusBarBackgroundColor(const QColor &color) // NOLINT readability-convert-member-functions-to-static
{
#ifdef Q_OS_ANDROID
d->m_statusBarColor = color;
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() {
QJniObject::callStaticMethod<void>("org.kde.guiaddons.KWindowInsetsController",
"setStatusBarBackground",
"(Landroid/app/Activity;I)V",
QNativeInterface::QAndroidApplication::context(),
color.rgba());
});
#else
Q_UNUSED(color)
#endif
}
QColor KWindowInsetsController::navigationBarBackgroundColor() const // NOLINT readability-convert-member-functions-to-static
{
#ifdef Q_OS_ANDROID
return d->m_navigationBarColor;
#else
return {};
#endif
}
void KWindowInsetsController::setNavigationBarBackgroundColor(const QColor &color) // NOLINT readability-convert-member-functions-to-static
{
#ifdef Q_OS_ANDROID
d->m_navigationBarColor = color;
QNativeInterface::QAndroidApplication::runOnAndroidMainThread([=]() {
QJniObject::callStaticMethod<void>("org.kde.guiaddons.KWindowInsetsController",
"setNavigationBarBackground",
"(Landroid/app/Activity;I)V",
QNativeInterface::QAndroidApplication::context(),
color.rgba());
});
#else
Q_UNUSED(color)
#endif
}
#include "moc_kwindowinsetscontroller.cpp"
@@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2024 Mathis Brüchert <mbb@kaidan.im>
// SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#ifndef KWINDOWINSETSCONTROLLER_H
#define KWINDOWINSETSCONTROLLER_H
#include "kguiaddons_export.h"
#include <QColor>
#include <QObject>
#include <memory>
class KWindowInsetsControllerPrivate;
/** Access to window insets colors.
*
* On most platforms this does nothing, on Android it allows
* to customize the (top) status bar and (botton) navigation bar
* background colors, e.g. to match the current window or application
* colors.
*
* Note that the foreground colors on Android are automatically
* chosen based on the background color.
*
* @code
* Component.onComplete: {
* WindowInsetsController.statusBarBackgroundColor = Kirigami.Theme.backgroundColor;
* WindowInsetsController.navigationBarBackgroundColor = Kirigami.Theme.backgroundColor;
* }
* @endcode
*
* @since 6.7
*/
class KGUIADDONS_EXPORT KWindowInsetsController : public QObject
{
Q_OBJECT
/** Background color of the status bar. */
Q_PROPERTY(QColor statusBarBackgroundColor READ statusBarBackgroundColor WRITE setStatusBarBackgroundColor NOTIFY statusBarBackgroundColorChanged)
/** Background color of the navigation bar. */
Q_PROPERTY(
QColor navigationBarBackgroundColor READ navigationBarBackgroundColor WRITE setNavigationBarBackgroundColor NOTIFY navigationBarBackgroundColorChanged)
public:
explicit KWindowInsetsController(QObject *parent = nullptr);
~KWindowInsetsController();
[[nodiscard]] QColor statusBarBackgroundColor() const;
void setStatusBarBackgroundColor(const QColor &color);
[[nodiscard]] QColor navigationBarBackgroundColor() const;
void setNavigationBarBackgroundColor(const QColor &color);
Q_SIGNALS:
void statusBarBackgroundColorChanged();
void navigationBarBackgroundColorChanged();
private:
std::unique_ptr<KWindowInsetsControllerPrivate> d;
};
#endif // KWINDOWINSETSCONTROLLER_H
@@ -0,0 +1,2 @@
#cmakedefine01 WITH_X11
#cmakedefine01 WITH_WAYLAND
@@ -0,0 +1,90 @@
/*
SPDX-FileCopyrightText: 2005, 2009, 2014 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kfontutils.h"
#include <QPainter>
#include <qmath.h>
static bool checkFits(QPainter &painter, const QString &string, qreal width, qreal height, qreal size, KFontUtils::AdaptFontSizeOptions flags)
{
QFont f = painter.font();
f.setPointSizeF(size);
painter.setFont(f);
int qtFlags = Qt::AlignCenter | Qt::TextWordWrap;
if (flags & KFontUtils::DoNotAllowWordWrap) {
qtFlags &= ~Qt::TextWordWrap;
}
const QRectF boundingRect = painter.boundingRect(QRectF(0, 0, width, height), qtFlags, string);
if (boundingRect.width() == 0.0 || boundingRect.height() == 0.0) {
return false;
} else if (boundingRect.width() > width || boundingRect.height() > height) {
return false;
}
return true;
}
qreal KFontUtils::adaptFontSize(QPainter &painter,
const QString &string,
qreal width,
qreal height,
qreal maxFontSize,
qreal minFontSize,
AdaptFontSizeOptions flags)
{
// A invalid range is an error (-1)
if (maxFontSize < minFontSize) {
return -1;
}
// If the max font size already fits, return it
if (checkFits(painter, string, width, height, maxFontSize, flags)) {
return maxFontSize;
}
qreal fontSizeDoesNotFit = maxFontSize;
// If the min font size does not fit, try to see if a font size of 1 fits,
// if it does not return error (-1)
// if it does, we'll return a fontsize smaller than the minFontSize as documented
if (!checkFits(painter, string, width, height, minFontSize, flags)) {
fontSizeDoesNotFit = minFontSize;
minFontSize = 1;
if (!checkFits(painter, string, width, height, minFontSize, flags)) {
return -1;
}
}
qreal fontSizeFits = minFontSize;
qreal nextFontSizeToTry = (fontSizeDoesNotFit + fontSizeFits) / 2;
while (qFloor(fontSizeFits) != qFloor(nextFontSizeToTry)) {
if (checkFits(painter, string, width, height, nextFontSizeToTry, flags)) {
fontSizeFits = nextFontSizeToTry;
nextFontSizeToTry = (fontSizeDoesNotFit + fontSizeFits) / 2;
} else {
fontSizeDoesNotFit = nextFontSizeToTry;
nextFontSizeToTry = (nextFontSizeToTry + fontSizeFits) / 2;
}
}
QFont f = painter.font();
f.setPointSizeF(fontSizeFits);
painter.setFont(f);
return fontSizeFits;
}
qreal KFontUtils::adaptFontSize(QPainter &painter,
const QString &text,
const QSizeF &availableSize,
qreal maxFontSize,
qreal minFontSize,
AdaptFontSizeOptions flags)
{
return adaptFontSize(painter, text, availableSize.width(), availableSize.height(), maxFontSize, minFontSize, flags);
}
@@ -0,0 +1,74 @@
/*
SPDX-FileCopyrightText: 2005, 2009 Albert Astals Cid <aacid@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KFONTUTILS_H
#define KFONTUTILS_H
#include <kguiaddons_export.h>
#include <qglobal.h>
class QPainter;
class QSizeF;
class QString;
/**
* @namespace KFontUtils
* Provides utility functions for font data.
*/
namespace KFontUtils
{
/**
* Modifiers for the adaptFontSize function
* @see AdaptFontSizeOptions
*/
enum AdaptFontSizeOption {
NoFlags = 0x01, ///< No modifier
DoNotAllowWordWrap = 0x02, ///< Do not use word wrapping
};
/**
* Stores a combination of #AdaptFontSizeOption values.
*/
Q_DECLARE_FLAGS(AdaptFontSizeOptions, AdaptFontSizeOption)
Q_DECLARE_OPERATORS_FOR_FLAGS(AdaptFontSizeOptions)
/** Helper function that calculates the biggest font size (in points) used
drawing a centered text using word wrapping.
@param painter The painter where the text will be painted. The font set
in the painter is used for the calculation. Note the
painter font size is modified by this call
@param text The text you want to draw
@param width The available width for drawing
@param height The available height for drawing
@param maxFontSize The maximum font size (in points) to consider
@param minFontSize The minimum font size (in points) to consider
@param flags The modifiers for how the text is painted
@return The calculated biggest font size (in points) that draws the text
in the given dimensions. Can return smaller than minFontSize,
that means the text doesn't fit in the given rectangle. Can
return -1 on error
@since 4.7
*/
qreal KGUIADDONS_EXPORT adaptFontSize(QPainter &painter,
const QString &text,
qreal width,
qreal height,
qreal maxFontSize = 28.0,
qreal minFontSize = 1.0,
AdaptFontSizeOptions flags = NoFlags);
/** Convenience function for adaptFontSize that accepts a QSizeF instead two qreals
@since 4.7
*/
qreal KGUIADDONS_EXPORT adaptFontSize(QPainter &painter,
const QString &text,
const QSizeF &availableSize,
qreal maxFontSize = 28.0,
qreal minFontSize = 1.0,
AdaptFontSizeOptions flags = NoFlags);
}
#endif
@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
add_executable(kde-geo-uri-handler
kgeourihandler.cpp
kgeourihandler_p.h
main.cpp
)
ecm_mark_nongui_executable(kde-geo-uri-handler)
target_include_directories(kde-geo-uri-handler PRIVATE "${CMAKE_BINARY_DIR}/src") # for kguiaddons_version.h
target_link_libraries(kde-geo-uri-handler PRIVATE Qt6::Gui)
install(TARGETS kde-geo-uri-handler ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(
FILES
google-maps-geo-handler.desktop
openstreetmap-geo-handler.desktop
qwant-maps-geo-handler.desktop
wheelmap-geo-handler.desktop
DESTINATION
${KDE_INSTALL_APPDIR}
)
@@ -0,0 +1,50 @@
[Desktop Entry]
Type=Application
Exec=kde-geo-uri-handler --coordinate-template "https://www.google.com/maps/@<LAT>,<LON>,<Z>z" --query-template "https://www.google.com/maps/search/<Q>" --fallback "https://www.google.com/maps/" %u
Name=Google Maps
Name[ar]=خرائط غوغل
Name[ast]=Google Maps
Name[bg]=Google карти
Name[ca]=Google Maps
Name[ca@valencia]=Google Maps
Name[cs]=Mapy Google
Name[de]=Google Maps
Name[en_GB]=Google Maps
Name[eo]=Guglo-Mapoj
Name[es]=Google Maps
Name[eu]=Google Maps
Name[fi]=Google Maps
Name[fr]=Google Maps
Name[gl]=Google Maps
Name[he]=Google מפות
Name[hi]=गूगल मैप्स
Name[hu]=Google Térképek
Name[ia]=Google Maps (Mappas de Google)
Name[is]=Google Maps
Name[it]=Mappe di Google
Name[ka]=Google Maps
Name[ko]=Google 지도
Name[lt]=„Google“ žemėlapiai
Name[lv]=Google Maps
Name[nl]=Google Maps
Name[nn]=Google Maps
Name[pl]=Mapy Google
Name[pt]=Google Maps
Name[pt_BR]=Google Maps
Name[ro]=Hărți Google
Name[ru]=Карты Google
Name[sa]=गूगल मानचित्रम् (Google Maps)
Name[sk]=Google Maps
Name[sl]=Google Maps
Name[sv]=Google kartor
Name[ta]=கூகுள் மேப்ஸ்
Name[tr]=Google Haritalar
Name[uk]=Карти Google
Name[vi]=Google Bản đồ
Name[x-test]=xxGoogle Mapsxx
Name[zh_CN]=Google Maps
Name[zh_TW]=Google 地圖
Icon=map-globe
MimeType=x-scheme-handler/geo;
Terminal=false
NoDisplay=true
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2022 Florian Edelmann <florian-edelmann@online.de>
SPDX-License-Identifier: CC0-1.0
@@ -0,0 +1,69 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kgeourihandler_p.h"
#include <QUrl>
#include <QUrlQuery>
void KGeoUriHandler::setCoordinateTemplate(const QString &coordTmpl)
{
m_coordTmpl = coordTmpl;
}
void KGeoUriHandler::setQueryTemplate(const QString &queryTmpl)
{
m_queryTmpl = queryTmpl;
}
void KGeoUriHandler::setFallbackUrl(const QString &fallbackUrl)
{
m_fallbackUrl = fallbackUrl;
}
static bool isValidCoordinate(double c, double limit)
{
return c != 0.0 && c >= -limit && c <= limit;
}
QString KGeoUriHandler::handleUri(const QUrl &geoUri)
{
const auto pathElems = geoUri.path().split(QLatin1Char(';'));
const auto coordElems = pathElems.isEmpty() ? QStringList() : pathElems.at(0).split(QLatin1Char(','));
const auto lat = coordElems.size() < 2 ? 0.0 : coordElems.at(0).toDouble();
const auto lon = coordElems.size() < 2 ? 0.0 : coordElems.at(1).toDouble();
const auto geoQuery = QUrlQuery(geoUri.query());
const auto query = geoQuery.queryItemValue(QStringLiteral("q"));
bool zoomValid = false;
int zoom = geoQuery.queryItemValue(QStringLiteral("z")).toInt(&zoomValid);
if (!zoomValid || zoom < 0 || zoom > 21) {
zoom = 18;
}
// unsupported coordinate reference system
if (!pathElems.isEmpty() && std::any_of(pathElems.begin() + 1, pathElems.end(), [](const auto &elem) {
return elem.startsWith(QLatin1String("crs="), Qt::CaseInsensitive) && !elem.endsWith(QLatin1String("=wgs84"), Qt::CaseInsensitive);
})) {
return m_fallbackUrl;
}
QString tmpl;
if (!query.isEmpty()) {
tmpl = m_queryTmpl;
} else if (isValidCoordinate(lat, 90.0) && isValidCoordinate(lon, 180.0)) {
tmpl = m_coordTmpl;
} else {
return m_fallbackUrl;
}
tmpl.replace(QLatin1String("<LAT>"), QString::number(lat));
tmpl.replace(QLatin1String("<LON>"), QString::number(lon));
tmpl.replace(QLatin1String("<Q>"), query);
tmpl.replace(QLatin1String("<Z>"), QString::number(zoom));
return tmpl;
}
@@ -0,0 +1,44 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KGEOURIHANDLER_H
#define KGEOURIHANDLER_H
#include <QString>
class QUrl;
/** Fallback handler for geo: URIs by forwarding them to a web service.
*
* This handles three cases of geo: URIs:
* - when containing a query argument, the query URL template is used
* - when containing valid WGS-84 coordinates, the coordinate URL template is used
* - otherwise the fallback URL is returned
*
* URL templates can contain any number of the following placeholders in angle brackets:
* - @c LAT - the latitude
* - @c LON - the longitude
* - @c Q - the query string
* - @c Z - the zoom level for a Web Mercator map projection
*
* @see https://en.wikipedia.org/wiki/Geo_URI_scheme
* @see https://datatracker.ietf.org/doc/html/rfc5870
*/
class KGeoUriHandler
{
public:
void setCoordinateTemplate(const QString &coordTmpl);
void setQueryTemplate(const QString &queryTmpl);
void setFallbackUrl(const QString &fallbackUrl);
QString handleUri(const QUrl &geoUri);
private:
QString m_coordTmpl;
QString m_queryTmpl;
QString m_fallbackUrl;
};
#endif // KGEOURIHANDLER_H
@@ -0,0 +1,49 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kgeourihandler_p.h"
#include <kguiaddons_version.h>
#include <QCommandLineParser>
#include <QDesktopServices>
#include <QGuiApplication>
#include <QUrl>
int main(int argc, char **argv)
{
QCoreApplication::setApplicationName(QStringLiteral("kde-geo-uri-handler"));
QCoreApplication::setOrganizationName(QStringLiteral("KDE"));
QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org"));
QCoreApplication::setApplicationVersion(QStringLiteral(KGUIADDONS_VERSION_STRING));
QGuiApplication app(argc, argv);
QCommandLineParser parser;
QCommandLineOption coordTmplOpt(QStringLiteral("coordinate-template"),
QStringLiteral("URL template for coordinate-based access."),
QStringLiteral("coordinate-template"));
parser.addOption(coordTmplOpt);
QCommandLineOption queryTmplOpt(QStringLiteral("query-template"), QStringLiteral("URL template for query-based access."), QStringLiteral("query-template"));
parser.addOption(queryTmplOpt);
QCommandLineOption fallbackOpt(QStringLiteral("fallback"), QStringLiteral("URL to use in case of errors."), QStringLiteral("fallback-url"));
parser.addOption(fallbackOpt);
parser.addPositionalArgument(QStringLiteral("uri"), QStringLiteral("geo: URI to handle"));
parser.addHelpOption();
parser.addVersionOption();
parser.process(app);
KGeoUriHandler handler;
handler.setCoordinateTemplate(parser.value(coordTmplOpt));
handler.setQueryTemplate(parser.value(queryTmplOpt));
handler.setFallbackUrl(parser.value(fallbackOpt));
const auto args = parser.positionalArguments();
for (const auto &arg : args) {
const auto url = handler.handleUri(QUrl(arg));
QDesktopServices::openUrl(QUrl(url));
}
return 0;
}
@@ -0,0 +1,49 @@
[Desktop Entry]
Type=Application
Exec=kde-geo-uri-handler --coordinate-template "https://www.openstreetmap.org/#map=<Z>/<LAT>/<LON>" --query-template "https://www.openstreetmap.org/search?query=<Q>" --fallback "https://www.openstreetmap.org" %u
Name=OpenStreetMap
Name[ar]=خريطة الشّوارع المفتوحة
Name[ast]=OpenStreetMap
Name[bg]=OpenStreetMap
Name[ca]=OpenStreetMap
Name[ca@valencia]=OpenStreetMap
Name[cs]=OpenStreetMap
Name[de]=OpenStreetMap
Name[en_GB]=OpenStreetMap
Name[eo]=OpenStreetMap
Name[es]=OpenStreetMap
Name[eu]=OpenStreetMap
Name[fi]=OpenStreetMap
Name[fr]=OpenStreetMap
Name[gl]=OpenStreetMap
Name[he]=OpenStreetMap
Name[hi]=ओपनस्ट्रीटमैप
Name[hu]=OpenStreetMap
Name[ia]=OpenStreetMap
Name[is]=OpenStreetMap
Name[it]=OpenStreetMap
Name[ka]=OpenStreetMap
Name[ko]=OpenStreetMap
Name[lt]=OpenStreetMap
Name[lv]=OpenStreetMap
Name[nl]=OpenStreetMap
Name[nn]=OpenStreetMap
Name[pl]=OpenStreetMap
Name[pt]=OpenStreetMap
Name[pt_BR]=OpenStreetMap
Name[ro]=OpenStreetMap
Name[ru]=OpenStreetMap
Name[sa]=वीथिमानचित्रं उद्घाटयन्तु (OpenStreetMap)
Name[sk]=OpenStreetMap
Name[sl]=OpenStreetMap
Name[sv]=OpenStreetMap
Name[tr]=OpenStreetMap
Name[uk]=OpenStreetMap
Name[vi]=OpenStreetMap
Name[x-test]=xxOpenStreetMapxx
Name[zh_CN]=OpenStreetMap
Name[zh_TW]=OpenStreetMap 開放街圖
Icon=map-globe
MimeType=x-scheme-handler/geo;
Terminal=false
NoDisplay=true
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: CC0-1.0
@@ -0,0 +1,49 @@
[Desktop Entry]
Type=Application
Exec=kde-geo-uri-handler --coordinate-template "https://www.qwant.com/maps/#map=<Z>/<LAT>/<LON>" --query-template "https://www.qwant.com/maps/?q=<Q>&client=opensearch" --fallback "https://www.qwant.com/maps/" %u
Name=Qwant Maps
Name[ar]=خرائط كوانت
Name[ast]=Qwant Maps
Name[bg]=Qwant Maps
Name[ca]=Qwant Maps
Name[ca@valencia]=Qwant Maps
Name[cs]=Mapy Qwant
Name[de]=Qwant Maps
Name[en_GB]=Qwant Maps
Name[eo]=Qwant-Mapoj
Name[es]=Qwant Maps
Name[eu]=Qwant Maps
Name[fi]=Qwant Maps
Name[fr]=Qwant Maps
Name[gl]=Qwant Maps
Name[he]=Qwant Maps
Name[hi]=क्वान्ट मैप्स
Name[hu]=Qwant Térképek
Name[ia]=Qwant Maps (Mappas de Qwant)
Name[is]=Qwant Maps
Name[it]=Qwant Maps
Name[ka]=Qwant Maps
Name[ko]=Qwant 지도
Name[lt]=„Qwant“ žemėlapiai
Name[lv]=Qwant Maps
Name[nl]=Qwant Maps
Name[nn]=Qwant Maps
Name[pl]=Mapy Qwant
Name[pt]=Qwant Maps
Name[pt_BR]=Qwant Maps
Name[ro]=Hărți Qwant
Name[ru]=Карты Qwant
Name[sa]=क्वान्ट मानचित्र (Qwant Maps)
Name[sk]=Qwant Maps
Name[sl]=Qwant Maps
Name[sv]=Qwant kartor
Name[tr]=Qwant Haritalar
Name[uk]=Карти Qwant
Name[vi]=Bản đồ Qwant
Name[x-test]=xxQwant Mapsxx
Name[zh_CN]=Qwant Maps
Name[zh_TW]=Qwant 地圖
Icon=map-globe
MimeType=x-scheme-handler/geo;
Terminal=false
NoDisplay=true
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2022 Florian Edelmann <florian-edelmann@online.de>
SPDX-License-Identifier: CC0-1.0
@@ -0,0 +1,49 @@
[Desktop Entry]
Type=Application
Exec=kde-geo-uri-handler --coordinate-template "https://wheelmap.org/?lat=<LAT>&lon=<LON>" --query-template "https://wheelmap.org/search?q=<Q>" --fallback "https://wheelmap.org" %u
Name=wheelmap.org
Name[ar]=wheelmap.org
Name[ast]=wheelmap.org
Name[bg]=wheelmap.org
Name[ca]=wheelmap.org
Name[ca@valencia]=wheelmap.org
Name[cs]=wheelmap.org
Name[de]=wheelmap.org
Name[en_GB]=wheelmap.org
Name[eo]=wheelmap.org
Name[es]=wheelmap.org
Name[eu]=wheelmap.org
Name[fi]=wheelmap.org
Name[fr]=wheelmap.org
Name[gl]=wheelmap.org
Name[he]=wheelmap.org
Name[hi]=व्हीलमैप्स.ऑर्ग
Name[hu]=wheelmap.org
Name[ia]=wheelmap.org
Name[is]=wheelmap.org
Name[it]=wheelmap.org
Name[ka]=wheelmap.org
Name[ko]=wheelmap.org
Name[lt]=wheelmap.org
Name[lv]=wheelmap.org
Name[nl]=wheelmap.org
Name[nn]=wheelmap.org
Name[pl]=wheelmap.org
Name[pt]=wheelmap.org
Name[pt_BR]=wheelmap.org
Name[ro]=wheelmap.org
Name[ru]=wheelmap.org
Name[sa]=wheelmap.org
Name[sk]=wheelmap.org
Name[sl]=wheelmap.org
Name[sv]=wheelmap.org
Name[tr]=wheelmap.org
Name[uk]=wheelmap.org
Name[vi]=wheelmap.org
Name[x-test]=xxwheelmap.orgxx
Name[zh_CN]=wheelmap.org
Name[zh_TW]=wheelmap.org
Icon=map-globe
MimeType=x-scheme-handler/geo;
Terminal=false
NoDisplay=true
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: CC0-1.0
@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
ecm_add_qml_module(kguiaddonsqml URI "org.kde.guiaddons" GENERATE_PLUGIN_SOURCE INSTALLED_PLUGIN_TARGET KF6::kguiaddonsqml)
target_sources(kguiaddonsqml PRIVATE types.h)
target_link_libraries(kguiaddonsqml PRIVATE KF6GuiAddons Qt6::Qml)
ecm_finalize_qml_module(kguiaddonsqml EXPORT KF6GuiAddonsTargets)
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#ifndef KGUIADDONS_QML_TYPES
#define KGUIADDONS_QML_TYPES
#include <KWindowInsetsController>
#include <QQmlEngine>
class KWindowInsetsControllerForeign : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(WindowInsetsController)
QML_FOREIGN(KWindowInsetsController)
QML_SINGLETON
};
#endif
@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="keyboard_shortcuts_inhibit_unstable_v1">
<copyright>
Copyright © 2017 Red Hat Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="Protocol for inhibiting the compositor keyboard shortcuts">
This protocol specifies a way for a client to request the compositor
to ignore its own keyboard shortcuts for a given seat, so that all
key events from that seat get forwarded to a surface.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible
changes may be added together with the corresponding interface
version bump.
Backward incompatible changes are done by bumping the version
number in the protocol and interface names and resetting the
interface version. Once the protocol is to be declared stable,
the 'z' prefix and the version number in the protocol and
interface names are removed and the interface version number is
reset.
</description>
<interface name="zwp_keyboard_shortcuts_inhibit_manager_v1" version="1">
<description summary="context object for keyboard grab_manager">
A global interface used for inhibiting the compositor keyboard shortcuts.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the keyboard shortcuts inhibitor object">
Destroy the keyboard shortcuts inhibitor manager.
</description>
</request>
<request name="inhibit_shortcuts">
<description summary="create a new keyboard shortcuts inhibitor object">
Create a new keyboard shortcuts inhibitor object associated with
the given surface for the given seat.
If shortcuts are already inhibited for the specified seat and surface,
a protocol error "already_inhibited" is raised by the compositor.
</description>
<arg name="id" type="new_id" interface="zwp_keyboard_shortcuts_inhibitor_v1"/>
<arg name="surface" type="object" interface="wl_surface"
summary="the surface that inhibits the keyboard shortcuts behavior"/>
<arg name="seat" type="object" interface="wl_seat"
summary="the wl_seat for which keyboard shortcuts should be disabled"/>
</request>
<enum name="error">
<entry name="already_inhibited"
value="0"
summary="the shortcuts are already inhibited for this surface"/>
</enum>
</interface>
<interface name="zwp_keyboard_shortcuts_inhibitor_v1" version="1">
<description summary="context object for keyboard shortcuts inhibitor">
A keyboard shortcuts inhibitor instructs the compositor to ignore
its own keyboard shortcuts when the associated surface has keyboard
focus. As a result, when the surface has keyboard focus on the given
seat, it will receive all key events originating from the specified
seat, even those which would normally be caught by the compositor for
its own shortcuts.
The Wayland compositor is however under no obligation to disable
all of its shortcuts, and may keep some special key combo for its own
use, including but not limited to one allowing the user to forcibly
restore normal keyboard events routing in the case of an unwilling
client. The compositor may also use the same key combo to reactivate
an existing shortcut inhibitor that was previously deactivated on
user request.
When the compositor restores its own keyboard shortcuts, an
"inactive" event is emitted to notify the client that the keyboard
shortcuts inhibitor is not effectively active for the surface and
seat any more, and the client should not expect to receive all
keyboard events.
When the keyboard shortcuts inhibitor is inactive, the client has
no way to forcibly reactivate the keyboard shortcuts inhibitor.
The user can chose to re-enable a previously deactivated keyboard
shortcuts inhibitor using any mechanism the compositor may offer,
in which case the compositor will send an "active" event to notify
the client.
If the surface is destroyed, unmapped, or loses the seat's keyboard
focus, the keyboard shortcuts inhibitor becomes irrelevant and the
compositor will restore its own keyboard shortcuts but no "inactive"
event is emitted in this case.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the keyboard shortcuts inhibitor object">
Remove the keyboard shortcuts inhibitor from the associated wl_surface.
</description>
</request>
<event name="active">
<description summary="shortcuts are inhibited">
This event indicates that the shortcut inhibitor is active.
The compositor sends this event every time compositor shortcuts
are inhibited on behalf of the surface. When active, the client
may receive input events normally reserved by the compositor
(see zwp_keyboard_shortcuts_inhibitor_v1).
This occurs typically when the initial request "inhibit_shortcuts"
first becomes active or when the user instructs the compositor to
re-enable and existing shortcuts inhibitor using any mechanism
offered by the compositor.
</description>
</event>
<event name="inactive">
<description summary="shortcuts are restored">
This event indicates that the shortcuts inhibitor is inactive,
normal shortcuts processing is restored by the compositor.
</description>
</event>
</interface>
</protocol>
@@ -0,0 +1,41 @@
/*
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
*/
#include "keyboardgrabber_p.h"
#include <QWindow>
KeyboardGrabber::KeyboardGrabber(QWindow *window)
: ShortcutInhibition()
, m_grabbedWindow(window)
, m_grabbingKeyboard(false)
{
}
KeyboardGrabber::~KeyboardGrabber()
{
disableInhibition();
}
void KeyboardGrabber::enableInhibition()
{
if (m_grabbingKeyboard || !m_grabbedWindow) {
return;
}
m_grabbingKeyboard = m_grabbedWindow->setKeyboardGrabEnabled(true);
}
void KeyboardGrabber::disableInhibition()
{
if (!m_grabbingKeyboard) {
return;
}
m_grabbingKeyboard = !(m_grabbedWindow->setKeyboardGrabEnabled(false));
}
bool KeyboardGrabber::shortcutsAreInhibited() const
{
return m_grabbingKeyboard;
}
@@ -0,0 +1,25 @@
/*
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
*/
#ifndef GRABBINGINHIBITION_H
#define GRABBINGINHIBITION_H
#include "shortcutinhibition_p.h"
class KeyboardGrabber : public ShortcutInhibition
{
public:
explicit KeyboardGrabber(QWindow *window);
~KeyboardGrabber() override;
void enableInhibition() override;
void disableInhibition() override;
bool shortcutsAreInhibited() const override;
private:
QWindow *m_grabbedWindow;
bool m_grabbingKeyboard;
};
#endif
@@ -0,0 +1,656 @@
/*
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 "kkeysequencerecorder.h"
#include "keyboardgrabber_p.h"
#include "kguiaddons_debug.h"
#include "shortcutinhibition_p.h"
#include "waylandinhibition_p.h"
#include <QGuiApplication>
#include <QKeyEvent>
#include <QPointer>
#include <QTimer>
#include <QWindow>
#include <array>
#include <chrono>
/// Singleton whose only purpose is to tell us about other sequence recorders getting started
class KKeySequenceRecorderGlobal : public QObject
{
Q_OBJECT
public:
static KKeySequenceRecorderGlobal *self()
{
static KKeySequenceRecorderGlobal s_self;
return &s_self;
}
Q_SIGNALS:
void sequenceRecordingStarted();
};
class KKeySequenceRecorderPrivate : public QObject
{
Q_OBJECT
public:
// Copy of QKeySequencePrivate::MaxKeyCount from private header
enum { MaxKeyCount = 4 };
KKeySequenceRecorderPrivate(KKeySequenceRecorder *qq);
void controlModifierlessTimeout();
bool eventFilter(QObject *watched, QEvent *event) override;
void handleKeyPress(QKeyEvent *event);
void handleKeyRelease(QKeyEvent *event);
void finishRecording();
void receivedRecording();
KKeySequenceRecorder *q;
QKeySequence m_currentKeySequence;
QKeySequence m_previousKeySequence;
QPointer<QWindow> m_window;
bool m_isRecording;
bool m_multiKeyShortcutsAllowed;
bool m_modifierlessAllowed;
bool m_modifierOnlyAllowed = false;
Qt::KeyboardModifiers m_currentModifiers;
QTimer m_modifierlessTimer;
std::unique_ptr<ShortcutInhibition> m_inhibition;
// For use in modifier only shortcuts
Qt::KeyboardModifiers m_lastPressedModifiers;
bool m_isReleasingModifierOnly = false;
std::chrono::nanoseconds m_modifierFirstReleaseTime;
};
constexpr Qt::KeyboardModifiers modifierMask = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier;
// Copied here from KKeyServer
static bool isShiftAsModifierAllowed(int keyQt)
{
// remove any modifiers
keyQt &= ~Qt::KeyboardModifierMask;
// Shift only works as a modifier with certain keys. It's not possible
// to enter the SHIFT+5 key sequence for me because this is handled as
// '%' by qt on my keyboard.
// The working keys are all hardcoded here :-(
if (keyQt >= Qt::Key_F1 && keyQt <= Qt::Key_F35) {
return true;
}
if (QChar::isLetter(keyQt)) {
return true;
}
switch (keyQt) {
case Qt::Key_Return:
case Qt::Key_Space:
case Qt::Key_Backspace:
case Qt::Key_Tab:
case Qt::Key_Backtab:
case Qt::Key_Escape:
case Qt::Key_Print:
case Qt::Key_ScrollLock:
case Qt::Key_Pause:
case Qt::Key_PageUp:
case Qt::Key_PageDown:
case Qt::Key_Insert:
case Qt::Key_Delete:
case Qt::Key_Home:
case Qt::Key_End:
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Enter:
case Qt::Key_SysReq:
case Qt::Key_CapsLock:
case Qt::Key_NumLock:
case Qt::Key_Help:
case Qt::Key_Back:
case Qt::Key_Forward:
case Qt::Key_Stop:
case Qt::Key_Refresh:
case Qt::Key_Favorites:
case Qt::Key_LaunchMedia:
case Qt::Key_OpenUrl:
case Qt::Key_HomePage:
case Qt::Key_Search:
case Qt::Key_VolumeDown:
case Qt::Key_VolumeMute:
case Qt::Key_VolumeUp:
case Qt::Key_BassBoost:
case Qt::Key_BassUp:
case Qt::Key_BassDown:
case Qt::Key_TrebleUp:
case Qt::Key_TrebleDown:
case Qt::Key_MediaPlay:
case Qt::Key_MediaStop:
case Qt::Key_MediaPrevious:
case Qt::Key_MediaNext:
case Qt::Key_MediaRecord:
case Qt::Key_MediaPause:
case Qt::Key_MediaTogglePlayPause:
case Qt::Key_LaunchMail:
case Qt::Key_Calculator:
case Qt::Key_Memo:
case Qt::Key_ToDoList:
case Qt::Key_Calendar:
case Qt::Key_PowerDown:
case Qt::Key_ContrastAdjust:
case Qt::Key_Standby:
case Qt::Key_MonBrightnessUp:
case Qt::Key_MonBrightnessDown:
case Qt::Key_KeyboardLightOnOff:
case Qt::Key_KeyboardBrightnessUp:
case Qt::Key_KeyboardBrightnessDown:
case Qt::Key_PowerOff:
case Qt::Key_WakeUp:
case Qt::Key_Eject:
case Qt::Key_ScreenSaver:
case Qt::Key_WWW:
case Qt::Key_Sleep:
case Qt::Key_LightBulb:
case Qt::Key_Shop:
case Qt::Key_History:
case Qt::Key_AddFavorite:
case Qt::Key_HotLinks:
case Qt::Key_BrightnessAdjust:
case Qt::Key_Finance:
case Qt::Key_Community:
case Qt::Key_AudioRewind:
case Qt::Key_BackForward:
case Qt::Key_ApplicationLeft:
case Qt::Key_ApplicationRight:
case Qt::Key_Book:
case Qt::Key_CD:
case Qt::Key_Clear:
case Qt::Key_ClearGrab:
case Qt::Key_Close:
case Qt::Key_Copy:
case Qt::Key_Cut:
case Qt::Key_Display:
case Qt::Key_DOS:
case Qt::Key_Documents:
case Qt::Key_Excel:
case Qt::Key_Explorer:
case Qt::Key_Game:
case Qt::Key_Go:
case Qt::Key_iTouch:
case Qt::Key_LogOff:
case Qt::Key_Market:
case Qt::Key_Meeting:
case Qt::Key_MenuKB:
case Qt::Key_MenuPB:
case Qt::Key_MySites:
case Qt::Key_News:
case Qt::Key_OfficeHome:
case Qt::Key_Option:
case Qt::Key_Paste:
case Qt::Key_Phone:
case Qt::Key_Reply:
case Qt::Key_Reload:
case Qt::Key_RotateWindows:
case Qt::Key_RotationPB:
case Qt::Key_RotationKB:
case Qt::Key_Save:
case Qt::Key_Send:
case Qt::Key_Spell:
case Qt::Key_SplitScreen:
case Qt::Key_Support:
case Qt::Key_TaskPane:
case Qt::Key_Terminal:
case Qt::Key_Tools:
case Qt::Key_Travel:
case Qt::Key_Video:
case Qt::Key_Word:
case Qt::Key_Xfer:
case Qt::Key_ZoomIn:
case Qt::Key_ZoomOut:
case Qt::Key_Away:
case Qt::Key_Messenger:
case Qt::Key_WebCam:
case Qt::Key_MailForward:
case Qt::Key_Pictures:
case Qt::Key_Music:
case Qt::Key_Battery:
case Qt::Key_Bluetooth:
case Qt::Key_WLAN:
case Qt::Key_UWB:
case Qt::Key_AudioForward:
case Qt::Key_AudioRepeat:
case Qt::Key_AudioRandomPlay:
case Qt::Key_Subtitle:
case Qt::Key_AudioCycleTrack:
case Qt::Key_Time:
case Qt::Key_Select:
case Qt::Key_View:
case Qt::Key_TopMenu:
case Qt::Key_Suspend:
case Qt::Key_Hibernate:
case Qt::Key_Launch0:
case Qt::Key_Launch1:
case Qt::Key_Launch2:
case Qt::Key_Launch3:
case Qt::Key_Launch4:
case Qt::Key_Launch5:
case Qt::Key_Launch6:
case Qt::Key_Launch7:
case Qt::Key_Launch8:
case Qt::Key_Launch9:
case Qt::Key_LaunchA:
case Qt::Key_LaunchB:
case Qt::Key_LaunchC:
case Qt::Key_LaunchD:
case Qt::Key_LaunchE:
case Qt::Key_LaunchF:
case Qt::Key_Shift:
case Qt::Key_Control:
case Qt::Key_Meta:
case Qt::Key_Alt:
case Qt::Key_Super_L:
case Qt::Key_Super_R:
return true;
default:
return false;
}
}
static bool isOkWhenModifierless(int key)
{
// this whole function is a hack, but especially the first line of code
if (QKeySequence(key).toString().length() == 1) {
return false;
}
switch (key) {
case Qt::Key_Return:
case Qt::Key_Space:
case Qt::Key_Tab:
case Qt::Key_Backtab: // does this ever happen?
case Qt::Key_Backspace:
case Qt::Key_Delete:
return false;
default:
return true;
}
}
static QKeySequence appendToSequence(const QKeySequence &sequence, int key)
{
if (sequence.count() >= KKeySequenceRecorderPrivate::MaxKeyCount) {
qCWarning(KGUIADDONS_LOG) << "Cannot append to a key to a sequence which is already of length" << sequence.count();
return sequence;
}
std::array<int, KKeySequenceRecorderPrivate::MaxKeyCount> keys{sequence[0].toCombined(),
sequence[1].toCombined(),
sequence[2].toCombined(),
sequence[3].toCombined()};
// When the user presses Mod(s)+Alt+Print, the SysReq event is fired only
// when the Alt key is released. Before we get the Mod(s)+SysReq event, we
// first get a Mod(s)+Alt event, which we have to ignore.
// Known limitation: only works when the Alt key is released before the Mod(s) key(s).
if ((key & ~Qt::KeyboardModifierMask) == Qt::Key_SysReq) {
key = Qt::Key_Print | (key & Qt::KeyboardModifierMask) | Qt::AltModifier;
if (sequence.count() > 0 && (sequence[sequence.count() - 1].toCombined() & ~Qt::KeyboardModifierMask) == Qt::Key_Alt) {
keys[sequence.count() - 1] = key;
return QKeySequence(keys[0], keys[1], keys[2], keys[3]);
}
}
keys[sequence.count()] = key;
return QKeySequence(keys[0], keys[1], keys[2], keys[3]);
}
KKeySequenceRecorderPrivate::KKeySequenceRecorderPrivate(KKeySequenceRecorder *qq)
: QObject(qq)
, q(qq)
{
}
void KKeySequenceRecorderPrivate::controlModifierlessTimeout()
{
if (m_currentKeySequence != 0 && !m_currentModifiers) {
// No modifier key pressed currently. Start the timeout
m_modifierlessTimer.start(600);
} else {
// A modifier is pressed. Stop the timeout
m_modifierlessTimer.stop();
}
}
bool KKeySequenceRecorderPrivate::eventFilter(QObject *watched, QEvent *event)
{
if (!m_isRecording) {
return QObject::eventFilter(watched, event);
}
if (event->type() == QEvent::ShortcutOverride || event->type() == QEvent::ContextMenu) {
event->accept();
return true;
}
if (event->type() == QEvent::KeyRelease) {
handleKeyRelease(static_cast<QKeyEvent *>(event));
return true;
}
if (event->type() == QEvent::KeyPress) {
handleKeyPress(static_cast<QKeyEvent *>(event));
return true;
}
return QObject::eventFilter(watched, event);
}
static Qt::KeyboardModifiers keyToModifier(int key)
{
switch (key) {
case Qt::Key_Meta:
case Qt::Key_Super_L:
case Qt::Key_Super_R:
// Qt doesn't properly recognize Super_L/Super_R as MetaModifier
return Qt::MetaModifier;
case Qt::Key_Shift:
return Qt::ShiftModifier;
case Qt::Key_Control:
return Qt::ControlModifier;
case Qt::Key_Alt:
return Qt::AltModifier;
default:
return Qt::NoModifier;
}
}
void KKeySequenceRecorderPrivate::handleKeyPress(QKeyEvent *event)
{
m_isReleasingModifierOnly = false;
m_currentModifiers = event->modifiers() & modifierMask;
int key = event->key();
switch (key) {
case -1:
qCWarning(KGUIADDONS_LOG) << "Got unknown key";
// Old behavior was to stop recording here instead of continuing like this
return;
case 0:
break;
case Qt::Key_AltGr:
// or else we get unicode salad
break;
case Qt::Key_Super_L:
case Qt::Key_Super_R:
case Qt::Key_Shift:
case Qt::Key_Control:
case Qt::Key_Alt:
case Qt::Key_Meta:
m_currentModifiers |= keyToModifier(key);
m_lastPressedModifiers = m_currentModifiers;
controlModifierlessTimeout();
Q_EMIT q->currentKeySequenceChanged();
break;
default:
m_lastPressedModifiers = Qt::NoModifier;
if (m_currentKeySequence.count() == 0 && !(m_currentModifiers & ~Qt::ShiftModifier)) {
// It's the first key and no modifier pressed. Check if this is allowed
if (!(isOkWhenModifierless(key) || m_modifierlessAllowed)) {
// No it's not
return;
}
}
// We now have a valid key press.
if ((key == Qt::Key_Backtab) && (m_currentModifiers & Qt::ShiftModifier)) {
key = QKeyCombination(Qt::Key_Tab).toCombined() | m_currentModifiers;
} else if (isShiftAsModifierAllowed(key)) {
key |= m_currentModifiers;
} else {
key |= (m_currentModifiers & ~Qt::ShiftModifier);
}
m_currentKeySequence = appendToSequence(m_currentKeySequence, key);
Q_EMIT q->currentKeySequenceChanged();
// Now we are in a critical region (race), where recording is still
// ongoing, but key sequence has already changed (potentially) to the
// longest. But we still want currentKeySequenceChanged to trigger
// before gotKeySequence, so there's only so much we can do about it.
if ((!m_multiKeyShortcutsAllowed) || (m_currentKeySequence.count() == MaxKeyCount)) {
finishRecording();
break;
}
controlModifierlessTimeout();
}
event->accept();
}
// Turn a bunch of modifiers into mods + key
// so that the ordering is always Meta + Ctrl + Alt + Shift
static int prettifyModifierOnly(Qt::KeyboardModifiers modifier)
{
if (modifier & Qt::ShiftModifier) {
return (Qt::Key_Shift | (modifier & ~Qt::ShiftModifier)).toCombined();
} else if (modifier & Qt::AltModifier) {
return (Qt::Key_Alt | (modifier & ~Qt::AltModifier)).toCombined();
} else if (modifier & Qt::ControlModifier) {
return (Qt::Key_Control | (modifier & ~Qt::ControlModifier)).toCombined();
} else if (modifier & Qt::MetaModifier) {
return (Qt::Key_Meta | (modifier & ~Qt::MetaModifier)).toCombined();
} else {
return Qt::Key(0);
}
}
void KKeySequenceRecorderPrivate::handleKeyRelease(QKeyEvent *event)
{
Qt::KeyboardModifiers modifiers = event->modifiers() & modifierMask;
switch (event->key()) {
case -1:
return;
case Qt::Key_Super_L:
case Qt::Key_Super_R:
case Qt::Key_Meta:
case Qt::Key_Shift:
case Qt::Key_Control:
case Qt::Key_Alt:
modifiers &= ~keyToModifier(event->key());
}
if ((modifiers & m_currentModifiers) < m_currentModifiers) {
constexpr auto releaseTimeout = std::chrono::milliseconds(200);
const auto currentTime = std::chrono::steady_clock::now().time_since_epoch();
if (!m_isReleasingModifierOnly) {
m_isReleasingModifierOnly = true;
m_modifierFirstReleaseTime = currentTime;
}
if (m_modifierOnlyAllowed && !modifiers && (currentTime - m_modifierFirstReleaseTime) < releaseTimeout) {
m_currentKeySequence = appendToSequence(m_currentKeySequence, prettifyModifierOnly(m_lastPressedModifiers));
m_lastPressedModifiers = Qt::NoModifier;
}
m_currentModifiers = modifiers;
Q_EMIT q->currentKeySequenceChanged();
if (m_currentKeySequence.count() == (m_multiKeyShortcutsAllowed ? MaxKeyCount : 1)) {
finishRecording();
}
controlModifierlessTimeout();
};
}
void KKeySequenceRecorderPrivate::receivedRecording()
{
m_modifierlessTimer.stop();
m_isRecording = false;
m_currentModifiers = Qt::NoModifier;
m_lastPressedModifiers = Qt::NoModifier;
m_isReleasingModifierOnly = false;
if (m_inhibition) {
m_inhibition->disableInhibition();
}
QObject::disconnect(KKeySequenceRecorderGlobal::self(), &KKeySequenceRecorderGlobal::sequenceRecordingStarted, q, &KKeySequenceRecorder::cancelRecording);
Q_EMIT q->recordingChanged();
}
void KKeySequenceRecorderPrivate::finishRecording()
{
receivedRecording();
Q_EMIT q->gotKeySequence(m_currentKeySequence);
}
KKeySequenceRecorder::KKeySequenceRecorder(QWindow *window, QObject *parent)
: QObject(parent)
, d(new KKeySequenceRecorderPrivate(this))
{
d->m_isRecording = false;
d->m_modifierlessAllowed = false;
d->m_multiKeyShortcutsAllowed = true;
setWindow(window);
connect(&d->m_modifierlessTimer, &QTimer::timeout, d.get(), &KKeySequenceRecorderPrivate::finishRecording);
}
KKeySequenceRecorder::~KKeySequenceRecorder() noexcept
{
if (d->m_inhibition && d->m_inhibition->shortcutsAreInhibited()) {
d->m_inhibition->disableInhibition();
}
}
void KKeySequenceRecorder::startRecording()
{
d->m_previousKeySequence = d->m_currentKeySequence;
KKeySequenceRecorderGlobal::self()->sequenceRecordingStarted();
connect(KKeySequenceRecorderGlobal::self(),
&KKeySequenceRecorderGlobal::sequenceRecordingStarted,
this,
&KKeySequenceRecorder::cancelRecording,
Qt::UniqueConnection);
if (!d->m_window) {
qCWarning(KGUIADDONS_LOG) << "Cannot record without a window";
return;
}
d->m_isRecording = true;
d->m_currentKeySequence = QKeySequence();
if (d->m_inhibition) {
d->m_inhibition->enableInhibition();
}
Q_EMIT recordingChanged();
Q_EMIT currentKeySequenceChanged();
}
void KKeySequenceRecorder::cancelRecording()
{
setCurrentKeySequence(d->m_previousKeySequence);
d->receivedRecording();
Q_ASSERT(!isRecording());
}
bool KKeySequenceRecorder::isRecording() const
{
return d->m_isRecording;
}
QKeySequence KKeySequenceRecorder::currentKeySequence() const
{
// We need a check for count() here because there's a race between the
// state of recording and a length of currentKeySequence.
if (d->m_isRecording && d->m_currentKeySequence.count() < KKeySequenceRecorderPrivate::MaxKeyCount) {
return appendToSequence(d->m_currentKeySequence, d->m_currentModifiers);
} else {
return d->m_currentKeySequence;
}
}
void KKeySequenceRecorder::setCurrentKeySequence(const QKeySequence &sequence)
{
if (d->m_currentKeySequence == sequence) {
return;
}
d->m_currentKeySequence = sequence;
Q_EMIT currentKeySequenceChanged();
}
QWindow *KKeySequenceRecorder::window() const
{
return d->m_window;
}
void KKeySequenceRecorder::setWindow(QWindow *window)
{
if (window == d->m_window) {
return;
}
if (d->m_window) {
d->m_window->removeEventFilter(d.get());
}
if (window) {
window->installEventFilter(d.get());
qCDebug(KGUIADDONS_LOG) << "listening for events in" << window;
}
if (qGuiApp->platformName() == QLatin1String("wayland")) {
#ifdef WITH_WAYLAND
d->m_inhibition.reset(new WaylandInhibition(window));
#endif
} else {
d->m_inhibition.reset(new KeyboardGrabber(window));
}
d->m_window = window;
Q_EMIT windowChanged();
}
bool KKeySequenceRecorder::multiKeyShortcutsAllowed() const
{
return d->m_multiKeyShortcutsAllowed;
}
void KKeySequenceRecorder::setMultiKeyShortcutsAllowed(bool allowed)
{
if (allowed == d->m_multiKeyShortcutsAllowed) {
return;
}
d->m_multiKeyShortcutsAllowed = allowed;
Q_EMIT multiKeyShortcutsAllowedChanged();
}
bool KKeySequenceRecorder::modifierlessAllowed() const
{
return d->m_modifierlessAllowed;
}
void KKeySequenceRecorder::setModifierlessAllowed(bool allowed)
{
if (allowed == d->m_modifierlessAllowed) {
return;
}
d->m_modifierlessAllowed = allowed;
Q_EMIT modifierlessAllowedChanged();
}
bool KKeySequenceRecorder::modifierOnlyAllowed() const
{
return d->m_modifierOnlyAllowed;
}
void KKeySequenceRecorder::setModifierOnlyAllowed(bool allowed)
{
if (allowed == d->m_modifierOnlyAllowed) {
return;
}
d->m_modifierOnlyAllowed = allowed;
Q_EMIT modifierOnlyAllowedChanged();
}
#include "kkeysequencerecorder.moc"
#include "moc_kkeysequencerecorder.cpp"
@@ -0,0 +1,156 @@
/*
SPDX-FileCopyrightText: 2001, 2002 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
*/
#ifndef KKEYSEQUENCERECORDER_H
#define KKEYSEQUENCERECORDER_H
#include <kguiaddons_export.h>
#include <QKeySequence>
#include <QObject>
#include <QWindow>
#include <memory>
class KKeySequenceRecorderPrivate;
/**
* @class KKeySequenceRecorder kkeysequencerecorder.h KKeySequenceRecorder
*
* @short Record a QKeySequence by listening to key events in a window.
*
* After calling startRecording key events in the set window will be captured until a valid
* QKeySequence has been recorded and gotKeySequence is emitted. See multiKeyShortcutsAllowed and
* modifierlessAllowed for what constitutes a valid key sequence.
*
* During recording any shortcuts are inhibited and cannot be triggered. Either by using the
* <a href="https://cgit.freedesktop.org/wayland/wayland-protocols/tree/unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml">
* keyboard-shortcuts-inhibit protocol </a> on Wayland or grabbing the keyboard.
*
* For graphical elements that record key sequences and can optionally perform conflict checking
* against existing shortcuts see KKeySequenceWidget and KeySequenceItem.
*
* Porting from KF5 to KF6:
*
* The class KeySequenceRecorder was renamed to KKeySequenceRecorder.
*
* @see KKeySequenceWidget, KeySequenceItem
* @since 6.0
*/
class KGUIADDONS_EXPORT KKeySequenceRecorder : public QObject
{
Q_OBJECT
/**
* Whether key events are currently recorded
*/
Q_PROPERTY(bool isRecording READ isRecording NOTIFY recordingChanged)
/**
* The recorded key sequence.
* After construction this is empty.
*
* During recording it is continuously updated with the newest user input.
*
* After recording it contains the last recorded QKeySequence
*/
Q_PROPERTY(QKeySequence currentKeySequence READ currentKeySequence WRITE setCurrentKeySequence NOTIFY currentKeySequenceChanged)
/**
* The window in which the key events are happening that should be recorded
*/
Q_PROPERTY(QWindow *window READ window WRITE setWindow NOTIFY windowChanged)
/**
* If key presses of "plain" keys without a modifier are considered to be a valid finished
* key combination.
* Plain keys include letter and symbol keys and text editing keys (Return, Space, Tab,
* Backspace, Delete). Other keys like F1, Cursor keys, Insert, PageDown will always work.
*
* By default this is `false`.
*/
Q_PROPERTY(bool modifierlessAllowed READ modifierlessAllowed WRITE setModifierlessAllowed NOTIFY modifierlessAllowedChanged)
/** Controls the amount of key combinations that are captured until recording stops and gotKeySequence
* is emitted.
* By default this is `true` and "Emacs-style" key sequences are recorded. Recording does not
* stop until four valid key combination have been recorded. Afterwards `currentKeySequence().count()`
* will be 4.
*
* Otherwise only one key combination is recorded before gotKeySequence is emitted with a
* QKeySequence with a `count()` of 1.
* @see QKeySequence
*/
Q_PROPERTY(bool multiKeyShortcutsAllowed READ multiKeyShortcutsAllowed WRITE setMultiKeyShortcutsAllowed NOTIFY multiKeyShortcutsAllowedChanged)
/**
* It makes it acceptable for the key sequence to be just a modifier (e.g. Shift or Control)
*
* By default, if only a modifier is pressed and then released, the component will remain waiting for the sequence.
* When enabled, it will take the modifier key as the key sequence.
*
* By default this is `false`.
*/
Q_PROPERTY(bool modifierOnlyAllowed READ modifierOnlyAllowed WRITE setModifierOnlyAllowed NOTIFY modifierOnlyAllowedChanged)
public:
/**
* Constructor.
*
* @par window The window whose key events will be recorded.
* @see window
*/
explicit KKeySequenceRecorder(QWindow *window, QObject *parent = nullptr);
~KKeySequenceRecorder() override;
/**
* Start recording.
* Calling startRecording when window() is `nullptr` has no effect.
*/
Q_INVOKABLE void startRecording();
bool isRecording() const;
QKeySequence currentKeySequence() const;
void setCurrentKeySequence(const QKeySequence &sequence);
QWindow *window() const;
void setWindow(QWindow *window);
bool multiKeyShortcutsAllowed() const;
void setMultiKeyShortcutsAllowed(bool allowed);
void setModifierlessAllowed(bool allowed);
bool modifierlessAllowed() const;
void setModifierOnlyAllowed(bool allowed);
bool modifierOnlyAllowed() const;
public Q_SLOTS:
/**
* Stops the recording session
*/
void cancelRecording();
Q_SIGNALS:
/**
* This signal is emitted when a key sequence has been recorded.
*
* Compared to currentKeySequenceChanged and currentKeySequence this is signal is not emitted
* continuously during recording but only after recording has finished.
*/
void gotKeySequence(const QKeySequence &keySequence);
void recordingChanged();
void windowChanged();
void currentKeySequenceChanged();
void multiKeyShortcutsAllowedChanged();
void modifierlessAllowedChanged();
void modifierOnlyAllowedChanged();
private:
friend class KKeySequenceRecorderPrivate;
std::unique_ptr<KKeySequenceRecorderPrivate> const d;
};
#endif
@@ -0,0 +1,22 @@
/*
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
*/
#ifndef SHORTCUTINHIBITION_H
#define SHORTCUTINHIBITION_H
class QWindow;
class ShortcutInhibition
{
public:
virtual ~ShortcutInhibition()
{
}
virtual void enableInhibition() = 0;
virtual void disableInhibition() = 0;
virtual bool shortcutsAreInhibited() const = 0;
};
#endif
@@ -0,0 +1,139 @@
/*
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
*/
#include "waylandinhibition_p.h"
#include "kguiaddons_debug.h"
#include <QDebug>
#include <QGuiApplication>
#include <QSharedPointer>
#include <QWaylandClientExtensionTemplate>
#include <QWindow>
#include <qpa/qplatformwindow_p.h>
#include "qwayland-keyboard-shortcuts-inhibit-unstable-v1.h"
class ShortcutsInhibitor : public QtWayland::zwp_keyboard_shortcuts_inhibitor_v1
{
public:
ShortcutsInhibitor(::zwp_keyboard_shortcuts_inhibitor_v1 *id)
: QtWayland::zwp_keyboard_shortcuts_inhibitor_v1(id)
{
}
~ShortcutsInhibitor() override
{
destroy();
}
void zwp_keyboard_shortcuts_inhibitor_v1_active() override
{
m_active = true;
}
void zwp_keyboard_shortcuts_inhibitor_v1_inactive() override
{
m_active = false;
}
bool isActive() const
{
return m_active;
}
private:
bool m_active = false;
};
class ShortcutsInhibitManager : public QWaylandClientExtensionTemplate<ShortcutsInhibitManager>, public QtWayland::zwp_keyboard_shortcuts_inhibit_manager_v1
{
public:
ShortcutsInhibitManager()
: QWaylandClientExtensionTemplate<ShortcutsInhibitManager>(1)
{
initialize();
}
~ShortcutsInhibitManager() override
{
if (isInitialized()) {
destroy();
}
}
void startInhibition(QWindow *window)
{
if (m_inhibitions.contains(window)) {
return;
}
auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
auto waylandWindow = window->nativeInterface<QNativeInterface::Private::QWaylandWindow>();
if (!waylandApp || !waylandWindow) {
return;
}
auto seat = waylandApp->lastInputSeat();
auto surface = waylandWindow->surface();
if (!seat || !surface) {
return;
}
m_inhibitions[window].reset(new ShortcutsInhibitor(inhibit_shortcuts(surface, seat)));
}
bool isInhibited(QWindow *window) const
{
return m_inhibitions.contains(window);
}
void stopInhibition(QWindow *window)
{
m_inhibitions.remove(window);
}
QHash<QWindow *, QSharedPointer<ShortcutsInhibitor>> m_inhibitions;
};
static std::shared_ptr<ShortcutsInhibitManager> theManager()
{
static std::weak_ptr<ShortcutsInhibitManager> managerInstance;
std::shared_ptr<ShortcutsInhibitManager> ret = managerInstance.lock();
if (!ret) {
ret = std::make_shared<ShortcutsInhibitManager>();
managerInstance = ret;
}
return ret;
}
WaylandInhibition::WaylandInhibition(QWindow *window)
: ShortcutInhibition()
, m_window(window)
, m_manager(theManager())
{
}
WaylandInhibition::~WaylandInhibition() = default;
bool WaylandInhibition::shortcutsAreInhibited() const
{
return m_manager->isInhibited(m_window);
}
void WaylandInhibition::enableInhibition()
{
if (!m_manager->isActive()) {
qCInfo(KGUIADDONS_LOG) << "The compositor does not support the keyboard-shortcuts-inhibit-unstable-v1 protocol. Inhibiting shortcuts will not work.";
return;
}
m_manager->startInhibition(m_window);
}
void WaylandInhibition::disableInhibition()
{
if (!m_manager->isActive()) {
return;
}
m_manager->stopInhibition(m_window);
}
@@ -0,0 +1,30 @@
/*
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
*/
#ifndef WAYLANDSHORTCUTINHIBITOR_H
#define WAYLANDSHORTCUTINHIBITOR_H
#include "shortcutinhibition_p.h"
#include <memory>
class ShortcutsInhibitManager;
class ShortcutsInhibitor;
class WaylandInhibition : public ShortcutInhibition
{
public:
explicit WaylandInhibition(QWindow *window);
~WaylandInhibition() override;
bool shortcutsAreInhibited() const override;
void enableInhibition() override;
void disableInhibition() override;
private:
QWindow *const m_window;
std::shared_ptr<ShortcutsInhibitManager> m_manager;
};
#endif
@@ -0,0 +1,60 @@
/*
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "ksystemclipboard.h"
#include "kguiaddons_debug.h"
#include "qtclipboard_p.h"
#include "waylandclipboard_p.h"
#include <QDebug>
#include <QGuiApplication>
#include <QMimeData>
KSystemClipboard *KSystemClipboard::instance()
{
if (!qGuiApp || qGuiApp->closingDown()) {
return nullptr;
}
static KSystemClipboard *systemClipboard = nullptr;
#ifdef WITH_WAYLAND
static bool s_waylandChecked = false;
if (!systemClipboard && qGuiApp->platformName() == QLatin1String("wayland") && !s_waylandChecked) {
WaylandClipboard *waylandClipboard = new WaylandClipboard(qApp);
s_waylandChecked = true;
if (waylandClipboard->isValid()) {
systemClipboard = waylandClipboard;
} else {
delete waylandClipboard;
qCWarning(KGUIADDONS_LOG) << "Could not init WaylandClipboard, falling back to QtClipboard.";
}
}
#endif
if (!systemClipboard) {
systemClipboard = new QtClipboard(qApp);
}
return systemClipboard;
}
QString KSystemClipboard::text(QClipboard::Mode mode)
{
const QMimeData *data = mimeData(mode);
if (data) {
return data->text();
}
return QString();
}
KSystemClipboard::KSystemClipboard(QObject *parent)
: QObject(parent)
{
}
#include "moc_ksystemclipboard.cpp"
@@ -0,0 +1,63 @@
/*
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSYSTEMCLIPBOARD_H
#define KSYSTEMCLIPBOARD_H
#include <kguiaddons_export.h>
#include <QClipboard>
#include <QObject>
class QMimeData;
/**
* This class mimics QClipboard but unlike QClipboard it will continue
* to get updates even when our window does not have focus.
*
* This may require extra access permissions
*
* @since 5.89
*/
class KGUIADDONS_EXPORT KSystemClipboard : public QObject
{
Q_OBJECT
public:
/**
* Returns a shared global SystemClipboard instance
*/
static KSystemClipboard *instance();
/**
* Sets the clipboard to the new contents
* The clipboard takes ownership of mime
*/
virtual void setMimeData(QMimeData *mime, QClipboard::Mode mode) = 0;
/**
* Clears the current clipboard
*/
virtual void clear(QClipboard::Mode mode) = 0;
/**
* Returns the current mime data received by the clipboard
*/
virtual const QMimeData *mimeData(QClipboard::Mode mode) const = 0;
/**
* Returns the text content of the Clipboard
*
* Similar to QClipboard::text(QClipboard::Mode mode)
*/
QString text(QClipboard::Mode mode);
Q_SIGNALS:
/**
* Emitted when the clipboard changes similar to QClipboard::changed
*/
void changed(QClipboard::Mode mode);
protected:
KSystemClipboard(QObject *parent);
};
#endif
@@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "qtclipboard_p.h"
#include <QClipboard>
#include <QGuiApplication>
QtClipboard::QtClipboard(QObject *parent)
: KSystemClipboard(parent)
{
connect(qGuiApp->clipboard(), &QClipboard::changed, this, &QtClipboard::changed);
}
void QtClipboard::setMimeData(QMimeData *mime, QClipboard::Mode mode)
{
qGuiApp->clipboard()->setMimeData(mime, mode);
}
void QtClipboard::clear(QClipboard::Mode mode)
{
qGuiApp->clipboard()->clear(mode);
}
const QMimeData *QtClipboard::mimeData(QClipboard::Mode mode) const
{
return qGuiApp->clipboard()->mimeData(mode);
}
@@ -0,0 +1,21 @@
/*
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef QTCLIPBOARD_H
#define QTCLIPBOARD_H
#include "ksystemclipboard.h"
class QtClipboard : public KSystemClipboard
{
public:
explicit QtClipboard(QObject *parent);
void setMimeData(QMimeData *mime, QClipboard::Mode mode) override;
void clear(QClipboard::Mode mode) override;
const QMimeData *mimeData(QClipboard::Mode mode) const override;
};
#endif
@@ -0,0 +1,44 @@
/*
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <QDebug>
#include <QGuiApplication>
#include <QImage>
#include <QMimeData>
#include "../systemclipboard/ksystemclipboard.h"
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
auto clip = KSystemClipboard::instance();
QObject::connect(clip, &KSystemClipboard::changed, &app, [clip](QClipboard::Mode mode) {
if (mode != QClipboard::Clipboard) {
return;
}
auto dbg = qDebug();
dbg << "New clipboard content: ";
const QMimeData *mime = clip->mimeData(QClipboard::Clipboard);
if (mime) {
if (mime->hasText()) {
dbg << "text data:" << mime->text();
} else if (mime->hasImage()) {
const QImage image = qvariant_cast<QImage>(mime->imageData());
dbg << "image data: " << image.size();
} else {
dbg << "data: " << mime->formats();
}
} else {
dbg << "[empty]";
}
});
qDebug() << "Watching for new clipboard content...";
return app.exec();
}
@@ -0,0 +1,706 @@
/*
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-FileCopyrightText: 2021 Méven Car <meven.car@enioka.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "waylandclipboard_p.h"
#include <QBuffer>
#include <QFile>
#include <QGuiApplication>
#include <QImageReader>
#include <QImageWriter>
#include <QMimeData>
#include <QPointer>
#include <QWaylandClientExtension>
#include <QWindow>
#include <QtWaylandClientVersion>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include "qwayland-wayland.h"
#include "qwayland-wlr-data-control-unstable-v1.h"
static inline QString applicationQtXImageLiteral()
{
return QStringLiteral("application/x-qt-image");
}
// copied from https://code.woboq.org/qt5/qtbase/src/gui/kernel/qinternalmimedata.cpp.html
static QString utf8Text()
{
return QStringLiteral("text/plain;charset=utf-8");
}
static QStringList imageMimeFormats(const QList<QByteArray> &imageFormats)
{
QStringList formats;
formats.reserve(imageFormats.size());
for (const auto &format : imageFormats)
formats.append(QLatin1String("image/") + QLatin1String(format.toLower()));
// put png at the front because it is best
int pngIndex = formats.indexOf(QLatin1String("image/png"));
if (pngIndex != -1 && pngIndex != 0)
formats.move(pngIndex, 0);
return formats;
}
static inline QStringList imageReadMimeFormats()
{
return imageMimeFormats(QImageReader::supportedImageFormats());
}
static inline QStringList imageWriteMimeFormats()
{
return imageMimeFormats(QImageWriter::supportedImageFormats());
}
// end copied
class DataControlDeviceManager : public QWaylandClientExtensionTemplate<DataControlDeviceManager>, public QtWayland::zwlr_data_control_manager_v1
{
Q_OBJECT
public:
DataControlDeviceManager()
: QWaylandClientExtensionTemplate<DataControlDeviceManager>(2)
{
}
void instantiate()
{
initialize();
}
~DataControlDeviceManager()
{
if (isInitialized()) {
destroy();
}
}
};
class DataControlOffer : public QMimeData, public QtWayland::zwlr_data_control_offer_v1
{
Q_OBJECT
public:
DataControlOffer(struct ::zwlr_data_control_offer_v1 *id)
: QtWayland::zwlr_data_control_offer_v1(id)
{
}
~DataControlOffer()
{
destroy();
}
QStringList formats() const override
{
return m_receivedFormats;
}
bool containsImageData() const
{
if (m_receivedFormats.contains(applicationQtXImageLiteral())) {
return true;
}
const auto formats = imageReadMimeFormats();
for (const auto &receivedFormat : m_receivedFormats) {
if (formats.contains(receivedFormat)) {
return true;
}
}
return false;
}
bool hasFormat(const QString &mimeType) const override
{
if (mimeType == QStringLiteral("text/plain") && m_receivedFormats.contains(utf8Text())) {
return true;
}
if (m_receivedFormats.contains(mimeType)) {
return true;
}
// If we have image data
if (containsImageData()) {
// is the requested output mimeType supported ?
const QStringList imageFormats = imageWriteMimeFormats();
for (const QString &imageFormat : imageFormats) {
if (imageFormat == mimeType) {
return true;
}
}
if (mimeType == applicationQtXImageLiteral()) {
return true;
}
}
return false;
}
protected:
void zwlr_data_control_offer_v1_offer(const QString &mime_type) override
{
if (!m_receivedFormats.contains(mime_type)) {
m_receivedFormats << mime_type;
}
}
QVariant retrieveData(const QString &mimeType, QMetaType type) const override;
private:
/** reads data from a file descriptor with a timeout of 1 second
* true if data is read successfully
*/
static bool readData(int fd, QByteArray &data);
QStringList m_receivedFormats;
mutable QHash<QString, QVariant> m_data;
};
QVariant DataControlOffer::retrieveData(const QString &mimeType, QMetaType type) const
{
Q_UNUSED(type);
auto it = m_data.constFind(mimeType);
if (it != m_data.constEnd())
return *it;
QString mime;
if (!m_receivedFormats.contains(mimeType)) {
if (mimeType == QStringLiteral("text/plain") && m_receivedFormats.contains(utf8Text())) {
mime = utf8Text();
} else if (mimeType == applicationQtXImageLiteral()) {
const auto writeFormats = imageWriteMimeFormats();
for (const auto &receivedFormat : m_receivedFormats) {
if (writeFormats.contains(receivedFormat)) {
mime = receivedFormat;
break;
}
}
if (mime.isEmpty()) {
// default exchange format
mime = QStringLiteral("image/png");
}
}
if (mime.isEmpty()) {
return QVariant();
}
} else {
mime = mimeType;
}
int pipeFds[2];
if (pipe(pipeFds) != 0) {
return QVariant();
}
auto t = const_cast<DataControlOffer *>(this);
t->receive(mime, pipeFds[1]);
close(pipeFds[1]);
/*
* Ideally we need to introduce a non-blocking QMimeData object
* Or a non-blocking constructor to QMimeData with the mimetypes that are relevant
*
* However this isn't actually any worse than X.
*/
auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
auto display = waylandApp->display();
wl_display_flush(display);
QFile readPipe;
if (readPipe.open(pipeFds[0], QIODevice::ReadOnly)) {
QByteArray data;
if (readData(pipeFds[0], data)) {
close(pipeFds[0]);
if (mimeType == applicationQtXImageLiteral()) {
QImage img = QImage::fromData(data, mime.mid(mime.indexOf(QLatin1Char('/')) + 1).toLatin1().toUpper().data());
if (!img.isNull()) {
m_data.insert(mimeType, img);
return img;
}
} else if (data.size() > 1 && mimeType == u"text/uri-list") {
const auto urls = data.split('\n');
QVariantList list;
list.reserve(urls.size());
for (const QByteArray &s : urls) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
if (QUrl url(QUrl::fromEncoded(QByteArrayView(s).trimmed())); url.isValid()) {
#else
if (QUrl url(QUrl::fromEncoded(QByteArrayView(s).trimmed().toByteArray())); url.isValid()) {
#endif
list.emplace_back(std::move(url));
}
}
m_data.insert(mimeType, list);
return list;
}
m_data.insert(mimeType, data);
return data;
}
close(pipeFds[0]);
}
return QVariant();
}
bool DataControlOffer::readData(int fd, QByteArray &data)
{
pollfd pfds[1];
pfds[0].fd = fd;
pfds[0].events = POLLIN;
while (true) {
const int ready = poll(pfds, 1, 1000);
if (ready < 0) {
if (errno != EINTR) {
qWarning("DataControlOffer: poll() failed: %s", strerror(errno));
return false;
}
} else if (ready == 0) {
qWarning("DataControlOffer: timeout reading from pipe");
return false;
} else {
char buf[4096];
int n = read(fd, buf, sizeof buf);
if (n < 0) {
qWarning("DataControlOffer: read() failed: %s", strerror(errno));
return false;
} else if (n == 0) {
return true;
} else if (n > 0) {
data.append(buf, n);
}
}
}
}
class DataControlSource : public QObject, public QtWayland::zwlr_data_control_source_v1
{
Q_OBJECT
public:
DataControlSource(struct ::zwlr_data_control_source_v1 *id, QMimeData *mimeData);
DataControlSource() = default;
~DataControlSource()
{
destroy();
}
QMimeData *mimeData()
{
return m_mimeData.get();
}
std::unique_ptr<QMimeData> releaseMimeData()
{
return std::move(m_mimeData);
}
Q_SIGNALS:
void cancelled();
protected:
void zwlr_data_control_source_v1_send(const QString &mime_type, int32_t fd) override;
void zwlr_data_control_source_v1_cancelled() override;
private:
std::unique_ptr<QMimeData> m_mimeData;
};
DataControlSource::DataControlSource(struct ::zwlr_data_control_source_v1 *id, QMimeData *mimeData)
: QtWayland::zwlr_data_control_source_v1(id)
, m_mimeData(mimeData)
{
const auto formats = mimeData->formats();
for (const QString &format : formats) {
offer(format);
}
if (mimeData->hasText()) {
// ensure GTK applications get this mimetype to avoid them discarding the offer
offer(QStringLiteral("text/plain;charset=utf-8"));
}
if (mimeData->hasImage()) {
const QStringList imageFormats = imageWriteMimeFormats();
for (const QString &imageFormat : imageFormats) {
if (!formats.contains(imageFormat)) {
offer(imageFormat);
}
}
}
}
void DataControlSource::zwlr_data_control_source_v1_send(const QString &mime_type, int32_t fd)
{
QString send_mime_type = mime_type;
if (send_mime_type == QStringLiteral("text/plain;charset=utf-8")) {
// if we get a request on the fallback mime, send the data from the original mime type
send_mime_type = QStringLiteral("text/plain");
}
QByteArray ba;
if (m_mimeData->hasImage()) {
// adapted from QInternalMimeData::renderDataHelper
if (mime_type == applicationQtXImageLiteral()) {
QImage image = qvariant_cast<QImage>(m_mimeData->imageData());
QBuffer buf(&ba);
buf.open(QBuffer::WriteOnly);
// would there not be PNG ??
image.save(&buf, "PNG");
} else if (mime_type.startsWith(QLatin1String("image/"))) {
QImage image = qvariant_cast<QImage>(m_mimeData->imageData());
QBuffer buf(&ba);
buf.open(QBuffer::WriteOnly);
image.save(&buf, mime_type.mid(mime_type.indexOf(QLatin1Char('/')) + 1).toLatin1().toUpper().data());
}
// end adapted
} else {
ba = m_mimeData->data(send_mime_type);
}
// Create a sigpipe handler that does nothing, or clients may be forced to terminate
// if the pipe is closed in the other end.
struct sigaction action, oldAction;
action.sa_handler = SIG_IGN;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
sigaction(SIGPIPE, &action, &oldAction);
write(fd, ba.constData(), ba.size());
sigaction(SIGPIPE, &oldAction, nullptr);
close(fd);
}
void DataControlSource::zwlr_data_control_source_v1_cancelled()
{
Q_EMIT cancelled();
}
class DataControlDevice : public QObject, public QtWayland::zwlr_data_control_device_v1
{
Q_OBJECT
public:
DataControlDevice(struct ::zwlr_data_control_device_v1 *id)
: QtWayland::zwlr_data_control_device_v1(id)
{
}
~DataControlDevice()
{
destroy();
}
void setSelection(std::unique_ptr<DataControlSource> selection);
QMimeData *receivedSelection()
{
return m_receivedSelection.get();
}
QMimeData *selection()
{
return m_selection ? m_selection->mimeData() : nullptr;
}
void setPrimarySelection(std::unique_ptr<DataControlSource> selection);
QMimeData *receivedPrimarySelection()
{
return m_receivedPrimarySelection.get();
}
QMimeData *primarySelection()
{
return m_primarySelection ? m_primarySelection->mimeData() : nullptr;
}
Q_SIGNALS:
void receivedSelectionChanged();
void selectionChanged();
void receivedPrimarySelectionChanged();
void primarySelectionChanged();
protected:
void zwlr_data_control_device_v1_data_offer(struct ::zwlr_data_control_offer_v1 *id) override
{
// this will become memory managed when we retrieve the selection event
// a compositor calling data_offer without doing that would be a bug
new DataControlOffer(id);
}
void zwlr_data_control_device_v1_selection(struct ::zwlr_data_control_offer_v1 *id) override
{
if (!id) {
m_receivedSelection.reset();
} else {
auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(id);
auto offer = dynamic_cast<DataControlOffer *>(derivated); // dynamic because of the dual inheritance
m_receivedSelection.reset(offer);
}
Q_EMIT receivedSelectionChanged();
}
void zwlr_data_control_device_v1_primary_selection(struct ::zwlr_data_control_offer_v1 *id) override
{
if (!id) {
m_receivedPrimarySelection.reset();
} else {
auto derivated = QtWayland::zwlr_data_control_offer_v1::fromObject(id);
auto offer = dynamic_cast<DataControlOffer *>(derivated); // dynamic because of the dual inheritance
m_receivedPrimarySelection.reset(offer);
}
Q_EMIT receivedPrimarySelectionChanged();
}
private:
std::unique_ptr<DataControlSource> m_selection; // selection set locally
std::unique_ptr<DataControlOffer> m_receivedSelection; // latest selection set from externally to here
std::unique_ptr<DataControlSource> m_primarySelection; // selection set locally
std::unique_ptr<DataControlOffer> m_receivedPrimarySelection; // latest selection set from externally to here
friend WaylandClipboard;
};
void DataControlDevice::setSelection(std::unique_ptr<DataControlSource> selection)
{
m_selection = std::move(selection);
connect(m_selection.get(), &DataControlSource::cancelled, this, [this]() {
m_selection.reset();
});
set_selection(m_selection->object());
Q_EMIT selectionChanged();
}
void DataControlDevice::setPrimarySelection(std::unique_ptr<DataControlSource> selection)
{
m_primarySelection = std::move(selection);
connect(m_primarySelection.get(), &DataControlSource::cancelled, this, [this]() {
m_primarySelection.reset();
});
if (zwlr_data_control_device_v1_get_version(object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
set_primary_selection(m_primarySelection->object());
Q_EMIT primarySelectionChanged();
}
}
class Keyboard;
// We are binding to Seat/Keyboard manually because we want to react to gaining focus but inside Qt the events are Qt and arrive to late
class KeyboardFocusWatcher : public QWaylandClientExtensionTemplate<KeyboardFocusWatcher>, public QtWayland::wl_seat
{
Q_OBJECT
public:
KeyboardFocusWatcher()
: QWaylandClientExtensionTemplate(5)
{
initialize();
auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
auto display = waylandApp->display();
// so we get capabilities
wl_display_roundtrip(display);
}
~KeyboardFocusWatcher() override
{
if (isActive()) {
release();
}
}
void seat_capabilities(uint32_t capabilities) override
{
const bool hasKeyboard = capabilities & capability_keyboard;
if (hasKeyboard && !m_keyboard) {
m_keyboard = std::make_unique<Keyboard>(get_keyboard(), *this);
} else if (!hasKeyboard && m_keyboard) {
m_keyboard.reset();
}
}
bool hasFocus() const
{
return m_focus;
}
Q_SIGNALS:
void keyboardEntered();
private:
friend Keyboard;
bool m_focus = false;
std::unique_ptr<Keyboard> m_keyboard;
};
class Keyboard : public QtWayland::wl_keyboard
{
public:
Keyboard(::wl_keyboard *keyboard, KeyboardFocusWatcher &seat)
: wl_keyboard(keyboard)
, m_seat(seat)
{
}
~Keyboard()
{
release();
}
private:
void keyboard_enter([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface, [[maybe_unused]] wl_array *keys) override
{
m_seat.m_focus = true;
Q_EMIT m_seat.keyboardEntered();
}
void keyboard_leave([[maybe_unused]] uint32_t serial, [[maybe_unused]] wl_surface *surface) override
{
m_seat.m_focus = false;
}
KeyboardFocusWatcher &m_seat;
};
WaylandClipboard::WaylandClipboard(QObject *parent)
: KSystemClipboard(parent)
, m_keyboardFocusWatcher(new KeyboardFocusWatcher)
, m_manager(new DataControlDeviceManager)
{
connect(m_manager.get(), &DataControlDeviceManager::activeChanged, this, [this]() {
if (m_manager->isActive()) {
auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
if (!waylandApp) {
return;
}
auto seat = waylandApp->seat();
if (!seat) {
return;
}
m_device.reset(new DataControlDevice(m_manager->get_data_device(seat)));
connect(m_device.get(), &DataControlDevice::receivedSelectionChanged, this, [this]() {
// When our source is still valid, so the offer is for setting it or we emit changed when it is cancelled
if (!m_device->selection()) {
Q_EMIT changed(QClipboard::Clipboard);
}
});
connect(m_device.get(), &DataControlDevice::selectionChanged, this, [this]() {
Q_EMIT changed(QClipboard::Clipboard);
});
connect(m_device.get(), &DataControlDevice::receivedPrimarySelectionChanged, this, [this]() {
// When our source is still valid, so the offer is for setting it or we emit changed when it is cancelled
if (!m_device->primarySelection()) {
Q_EMIT changed(QClipboard::Selection);
}
});
connect(m_device.get(), &DataControlDevice::primarySelectionChanged, this, [this]() {
Q_EMIT changed(QClipboard::Selection);
});
} else {
m_device.reset();
}
});
m_manager->instantiate();
}
WaylandClipboard::~WaylandClipboard() = default;
bool WaylandClipboard::isValid()
{
return m_manager && m_manager->isInitialized();
}
void WaylandClipboard::setMimeData(QMimeData *mime, QClipboard::Mode mode)
{
if (!m_device) {
return;
}
// roundtrip to have accurate focus state when losing focus but setting mime data before processing wayland events.
auto waylandApp = qGuiApp->nativeInterface<QNativeInterface::QWaylandApplication>();
auto display = waylandApp->display();
wl_display_roundtrip(display);
// If the application is focused, use the normal mechanism so a future paste will not deadlock itselfs
if (m_keyboardFocusWatcher->hasFocus()) {
QGuiApplication::clipboard()->setMimeData(mime, mode);
// if we short-circuit the wlr_data_device, when we receive the data
// we cannot identify ourselves as the owner
// because of that we act like it's a synchronous action to not confuse klipper.
wl_display_roundtrip(display);
return;
}
// If not, set the clipboard once the app receives focus to avoid the deadlock
connect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered, this, &WaylandClipboard::gainedFocus, Qt::UniqueConnection);
auto source = std::make_unique<DataControlSource>(m_manager->create_data_source(), mime);
if (mode == QClipboard::Clipboard) {
m_device->setSelection(std::move(source));
} else if (mode == QClipboard::Selection) {
m_device->setPrimarySelection(std::move(source));
}
}
void WaylandClipboard::gainedFocus()
{
disconnect(m_keyboardFocusWatcher.get(), &KeyboardFocusWatcher::keyboardEntered, this, nullptr);
// QClipboard takes ownership of the QMimeData so we need to transfer and unset our selections
if (auto &selection = m_device->m_selection) {
std::unique_ptr<QMimeData> data = selection->releaseMimeData();
selection.reset();
QGuiApplication::clipboard()->setMimeData(data.release(), QClipboard::Clipboard);
}
if (auto &primarySelection = m_device->m_primarySelection) {
std::unique_ptr<QMimeData> data = primarySelection->releaseMimeData();
primarySelection.reset();
QGuiApplication::clipboard()->setMimeData(data.release(), QClipboard::Selection);
}
}
void WaylandClipboard::clear(QClipboard::Mode mode)
{
if (!m_device) {
return;
}
if (mode == QClipboard::Clipboard) {
m_device->set_selection(nullptr);
m_device->m_selection.reset();
} else if (mode == QClipboard::Selection) {
if (zwlr_data_control_device_v1_get_version(m_device->object()) >= ZWLR_DATA_CONTROL_DEVICE_V1_SET_PRIMARY_SELECTION_SINCE_VERSION) {
m_device->set_primary_selection(nullptr);
m_device->m_primarySelection.reset();
}
}
}
const QMimeData *WaylandClipboard::mimeData(QClipboard::Mode mode) const
{
if (!m_device) {
return nullptr;
}
// return our locally set selection if it's not cancelled to avoid copying data to ourselves
if (mode == QClipboard::Clipboard) {
if (m_device->selection()) {
return m_device->selection();
}
// This application owns the clipboard via the regular data_device, use it so we don't block ourselves
if (QGuiApplication::clipboard()->ownsClipboard()) {
return QGuiApplication::clipboard()->mimeData(mode);
}
return m_device->receivedSelection();
} else if (mode == QClipboard::Selection) {
if (m_device->primarySelection()) {
return m_device->primarySelection();
}
// This application owns the primary selection via the regular primary_selection_device, use it so we don't block ourselves
if (QGuiApplication::clipboard()->ownsSelection()) {
return QGuiApplication::clipboard()->mimeData(mode);
}
return m_device->receivedPrimarySelection();
}
return nullptr;
}
#include "waylandclipboard.moc"
@@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2020 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef WAYLANDCLIPBOARD_H
#define WAYLANDCLIPBOARD_H
#include "ksystemclipboard.h"
#include <memory>
class DataControlDevice;
class DataControlDeviceManager;
class KeyboardFocusWatcher;
class WaylandClipboard : public KSystemClipboard
{
public:
WaylandClipboard(QObject *parent);
~WaylandClipboard();
void setMimeData(QMimeData *mime, QClipboard::Mode mode) override;
void clear(QClipboard::Mode mode) override;
const QMimeData *mimeData(QClipboard::Mode mode) const override;
bool isValid();
private:
void gainedFocus();
std::unique_ptr<KeyboardFocusWatcher> m_keyboardFocusWatcher;
std::unique_ptr<DataControlDeviceManager> m_manager;
std::unique_ptr<DataControlDevice> m_device;
};
#endif
@@ -0,0 +1,278 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_data_control_unstable_v1">
<copyright>
Copyright © 2018 Simon Ser
Copyright © 2019 Ivan Molodetskikh
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<description summary="control data devices">
This protocol allows a privileged client to control data devices. In
particular, the client will be able to manage the current selection and take
the role of a clipboard manager.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<interface name="zwlr_data_control_manager_v1" version="2">
<description summary="manager to control data devices">
This interface is a manager that allows creating per-seat data device
controls.
</description>
<request name="create_data_source">
<description summary="create a new data source">
Create a new data source.
</description>
<arg name="id" type="new_id" interface="zwlr_data_control_source_v1"
summary="data source to create"/>
</request>
<request name="get_data_device">
<description summary="get a data device for a seat">
Create a data device that can be used to manage a seat's selection.
</description>
<arg name="id" type="new_id" interface="zwlr_data_control_device_v1"/>
<arg name="seat" type="object" interface="wl_seat"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
</description>
</request>
</interface>
<interface name="zwlr_data_control_device_v1" version="2">
<description summary="manage a data device for a seat">
This interface allows a client to manage a seat's selection.
When the seat is destroyed, this object becomes inert.
</description>
<request name="set_selection">
<description summary="copy data to the selection">
This request asks the compositor to set the selection to the data from
the source on behalf of the client.
The given source may not be used in any further set_selection or
set_primary_selection requests. Attempting to use a previously used
source is a protocol error.
To unset the selection, set the source to NULL.
</description>
<arg name="source" type="object" interface="zwlr_data_control_source_v1"
allow-null="true"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy this data device">
Destroys the data device object.
</description>
</request>
<event name="data_offer">
<description summary="introduce a new wlr_data_control_offer">
The data_offer event introduces a new wlr_data_control_offer object,
which will subsequently be used in either the
wlr_data_control_device.selection event (for the regular clipboard
selections) or the wlr_data_control_device.primary_selection event (for
the primary clipboard selections). Immediately following the
wlr_data_control_device.data_offer event, the new data_offer object
will send out wlr_data_control_offer.offer events to describe the MIME
types it offers.
</description>
<arg name="id" type="new_id" interface="zwlr_data_control_offer_v1"/>
</event>
<event name="selection">
<description summary="advertise new selection">
The selection event is sent out to notify the client of a new
wlr_data_control_offer for the selection for this device. The
wlr_data_control_device.data_offer and the wlr_data_control_offer.offer
events are sent out immediately before this event to introduce the data
offer object. The selection event is sent to a client when a new
selection is set. The wlr_data_control_offer is valid until a new
wlr_data_control_offer or NULL is received. The client must destroy the
previous selection wlr_data_control_offer, if any, upon receiving this
event.
The first selection event is sent upon binding the
wlr_data_control_device object.
</description>
<arg name="id" type="object" interface="zwlr_data_control_offer_v1"
allow-null="true"/>
</event>
<event name="finished">
<description summary="this data control is no longer valid">
This data control object is no longer valid and should be destroyed by
the client.
</description>
</event>
<!-- Version 2 additions -->
<event name="primary_selection" since="2">
<description summary="advertise new primary selection">
The primary_selection event is sent out to notify the client of a new
wlr_data_control_offer for the primary selection for this device. The
wlr_data_control_device.data_offer and the wlr_data_control_offer.offer
events are sent out immediately before this event to introduce the data
offer object. The primary_selection event is sent to a client when a
new primary selection is set. The wlr_data_control_offer is valid until
a new wlr_data_control_offer or NULL is received. The client must
destroy the previous primary selection wlr_data_control_offer, if any,
upon receiving this event.
If the compositor supports primary selection, the first
primary_selection event is sent upon binding the
wlr_data_control_device object.
</description>
<arg name="id" type="object" interface="zwlr_data_control_offer_v1"
allow-null="true"/>
</event>
<request name="set_primary_selection" since="2">
<description summary="copy data to the primary selection">
This request asks the compositor to set the primary selection to the
data from the source on behalf of the client.
The given source may not be used in any further set_selection or
set_primary_selection requests. Attempting to use a previously used
source is a protocol error.
To unset the primary selection, set the source to NULL.
The compositor will ignore this request if it does not support primary
selection.
</description>
<arg name="source" type="object" interface="zwlr_data_control_source_v1"
allow-null="true"/>
</request>
<enum name="error" since="2">
<entry name="used_source" value="1"
summary="source given to set_selection or set_primary_selection was already used before"/>
</enum>
</interface>
<interface name="zwlr_data_control_source_v1" version="1">
<description summary="offer to transfer data">
The wlr_data_control_source object is the source side of a
wlr_data_control_offer. It is created by the source client in a data
transfer and provides a way to describe the offered data and a way to
respond to requests to transfer the data.
</description>
<enum name="error">
<entry name="invalid_offer" value="1"
summary="offer sent after wlr_data_control_device.set_selection"/>
</enum>
<request name="offer">
<description summary="add an offered MIME type">
This request adds a MIME type to the set of MIME types advertised to
targets. Can be called several times to offer multiple types.
Calling this after wlr_data_control_device.set_selection is a protocol
error.
</description>
<arg name="mime_type" type="string"
summary="MIME type offered by the data source"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy this source">
Destroys the data source object.
</description>
</request>
<event name="send">
<description summary="send the data">
Request for data from the client. Send the data as the specified MIME
type over the passed file descriptor, then close it.
</description>
<arg name="mime_type" type="string" summary="MIME type for the data"/>
<arg name="fd" type="fd" summary="file descriptor for the data"/>
</event>
<event name="cancelled">
<description summary="selection was cancelled">
This data source is no longer valid. The data source has been replaced
by another data source.
The client should clean up and destroy this data source.
</description>
</event>
</interface>
<interface name="zwlr_data_control_offer_v1" version="1">
<description summary="offer to transfer data">
A wlr_data_control_offer represents a piece of data offered for transfer
by another client (the source client). The offer describes the different
MIME types that the data can be converted to and provides the mechanism
for transferring the data directly from the source client.
</description>
<request name="receive">
<description summary="request that the data is transferred">
To transfer the offered data, the client issues this request and
indicates the MIME type it wants to receive. The transfer happens
through the passed file descriptor (typically created with the pipe
system call). The source client writes the data in the MIME type
representation requested and then closes the file descriptor.
The receiving client reads from the read end of the pipe until EOF and
then closes its end, at which point the transfer is complete.
This request may happen multiple times for different MIME types.
</description>
<arg name="mime_type" type="string"
summary="MIME type desired by receiver"/>
<arg name="fd" type="fd" summary="file descriptor for data transfer"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy this offer">
Destroys the data offer object.
</description>
</request>
<event name="offer">
<description summary="advertise offered MIME type">
Sent immediately after creating the wlr_data_control_offer object.
One event per offered MIME type.
</description>
<arg name="mime_type" type="string" summary="offered MIME type"/>
</event>
</interface>
</protocol>
@@ -0,0 +1,66 @@
/* -*- C++ -*-
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1997 Tim D. Gilman <tdgilman@best.org>
SPDX-FileCopyrightText: 1998-2001 Mirko Boehm <mirko@kde.org>
SPDX-FileCopyrightText: 2007 John Layt <john@layt.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kdatevalidator.h"
#include <QDate>
#include <QLocale>
class KDateValidatorPrivate
{
public:
KDateValidatorPrivate(KDateValidator *qq)
: q(qq)
{
}
~KDateValidatorPrivate()
{
}
KDateValidator *const q;
};
KDateValidator::KDateValidator(QObject *parent)
: QValidator(parent)
{
}
KDateValidator::~KDateValidator() = default;
QValidator::State KDateValidator::validate(QString &text, int &unused) const
{
Q_UNUSED(unused);
QDate temp;
// ----- everything is tested in date():
return date(text, temp);
}
QValidator::State KDateValidator::date(const QString &text, QDate &d) const
{
QLocale::FormatType formats[] = {QLocale::LongFormat, QLocale::ShortFormat, QLocale::NarrowFormat};
QLocale locale;
for (int i = 0; i < 3; i++) {
QDate tmp = locale.toDate(text, formats[i]);
if (tmp.isValid()) {
d = tmp;
return Acceptable;
}
}
return QValidator::Intermediate;
}
void KDateValidator::fixup(QString &) const
{
}
#include "moc_kdatevalidator.cpp"
@@ -0,0 +1,42 @@
/* -*- C++ -*-
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1997 Tim D. Gilman <tdgilman@best.org>
SPDX-FileCopyrightText: 1998-2001 Mirko Boehm <mirko@kde.org>
SPDX-FileCopyrightText: 2007 John Layt <john@layt.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KDATEVALIDATOR_H
#define KDATEVALIDATOR_H
#include <kguiaddons_export.h>
#include <QValidator>
#include <memory>
class KDateValidatorPrivate;
/**
* @class KDateValidator kdatevalidator.h KDateValidator
*
* Validates user-entered dates.
*/
class KGUIADDONS_EXPORT KDateValidator : public QValidator
{
Q_OBJECT
public:
explicit KDateValidator(QObject *parent = nullptr);
~KDateValidator() override;
public:
State validate(QString &text, int &e) const override;
void fixup(QString &input) const override;
State date(const QString &text, QDate &date) const;
private:
std::unique_ptr<KDateValidatorPrivate> const d;
};
#endif // KDATEVALIDATOR_H
@@ -0,0 +1,305 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kwordwrap.h"
#include <QList>
#include <QPainter>
class KWordWrapPrivate : public QSharedData
{
public:
QRect m_constrainingRect;
QList<int> m_breakPositions;
QList<int> m_lineWidths;
QRect m_boundingRect;
QString m_text;
};
KWordWrap::KWordWrap(const QRect &r)
: d(new KWordWrapPrivate)
{
d->m_constrainingRect = r;
}
KWordWrap KWordWrap::formatText(QFontMetrics &fm, const QRect &r, int /*flags*/, const QString &str, int len)
{
KWordWrap kw(r);
// The wordwrap algorithm
// The variable names and the global shape of the algorithm are inspired
// from QTextFormatterBreakWords::format().
// qDebug() << "KWordWrap::formatText " << str << " r=" << r.x() << "," << r.y() << " " << r.width() << "x" << r.height();
int height = fm.height();
if (len == -1) {
kw.d->m_text = str;
} else {
kw.d->m_text = str.left(len);
}
if (len == -1) {
len = str.length();
}
int lastBreak = -1;
int lineWidth = 0;
int x = 0;
int y = 0;
int w = r.width();
int textwidth = 0;
bool isBreakable = false;
bool wasBreakable = false; // value of isBreakable for last char (i-1)
bool isParens = false; // true if one of ({[
bool wasParens = false; // value of isParens for last char (i-1)
QString inputString = str;
for (int i = 0; i < len; ++i) {
const QChar c = inputString.at(i);
const int ww = fm.horizontalAdvance(c);
isParens = (c == QLatin1Char('(') //
|| c == QLatin1Char('[') //
|| c == QLatin1Char('{'));
// isBreakable is true when we can break _after_ this character.
isBreakable = (c.isSpace() || c.isPunct() || c.isSymbol()) & !isParens;
// Special case for '(', '[' and '{': we want to break before them
if (!isBreakable && i < len - 1) {
const QChar nextc = inputString.at(i + 1); // look at next char
isBreakable = (nextc == QLatin1Char('(') //
|| nextc == QLatin1Char('[') //
|| nextc == QLatin1Char('{'));
}
// Special case for '/': after normal chars it's breakable (e.g. inside a path),
// but after another breakable char it's not (e.g. "mounted at /foo")
// Same thing after a parenthesis (e.g. "dfaure [/fool]")
if (c == QLatin1Char('/') && (wasBreakable || wasParens)) {
isBreakable = false;
}
/*qDebug() << "c='" << QString(c) << "' i=" << i << "/" << len
<< " x=" << x << " ww=" << ww << " w=" << w
<< " lastBreak=" << lastBreak << " isBreakable=" << isBreakable << endl;*/
int breakAt = -1;
if (x + ww > w && lastBreak != -1) { // time to break and we know where
breakAt = lastBreak;
}
if (x + ww > w - 4 && lastBreak == -1) { // time to break but found nowhere [-> break here]
breakAt = i;
}
if (i == len - 2 && x + ww + fm.horizontalAdvance(inputString.at(i + 1)) > w) { // don't leave the last char alone
breakAt = lastBreak == -1 ? i - 1 : lastBreak;
}
if (c == QLatin1Char('\n')) { // Forced break here
if (breakAt == -1 && lastBreak != -1) { // only break if not already breaking
breakAt = i - 1;
lastBreak = -1;
}
// remove the line feed from the string
kw.d->m_text.remove(i, 1);
inputString.remove(i, 1);
len--;
}
if (breakAt != -1) {
// qDebug() << "KWordWrap::formatText breaking after " << breakAt;
kw.d->m_breakPositions.append(breakAt);
int thisLineWidth = lastBreak == -1 ? x + ww : lineWidth;
kw.d->m_lineWidths.append(thisLineWidth);
textwidth = qMax(textwidth, thisLineWidth);
x = 0;
y += height;
wasBreakable = true;
wasParens = false;
if (lastBreak != -1) {
// Breakable char was found, restart from there
i = lastBreak;
lastBreak = -1;
continue;
}
} else if (isBreakable) {
lastBreak = i;
lineWidth = x + ww;
}
x += ww;
wasBreakable = isBreakable;
wasParens = isParens;
}
textwidth = qMax(textwidth, x);
kw.d->m_lineWidths.append(x);
y += height;
// qDebug() << "KWordWrap::formatText boundingRect:" << r.x() << "," << r.y() << " " << textwidth << "x" << y;
if (r.height() >= 0 && y > r.height()) {
textwidth = r.width();
}
int realY = y;
if (r.height() >= 0) {
while (realY > r.height()) {
realY -= height;
}
realY = qMax(realY, 0);
}
kw.d->m_boundingRect.setRect(0, 0, textwidth, realY);
return kw;
}
KWordWrap::~KWordWrap()
{
}
KWordWrap::KWordWrap(const KWordWrap &other)
: d(other.d)
{
}
KWordWrap &KWordWrap::operator=(const KWordWrap &other)
{
d = other.d;
return *this;
}
QString KWordWrap::wrappedString() const
{
const QStringView strView(d->m_text);
// We use the calculated break positions to insert '\n' into the string
QString ws;
int start = 0;
for (int i = 0; i < d->m_breakPositions.count(); ++i) {
int end = d->m_breakPositions.at(i);
ws += strView.mid(start, end - start + 1);
ws += QLatin1Char('\n');
start = end + 1;
}
ws += strView.mid(start);
return ws;
}
QString KWordWrap::truncatedString(bool dots) const
{
if (d->m_breakPositions.isEmpty()) {
return d->m_text;
}
QString ts = d->m_text.left(d->m_breakPositions.first() + 1);
if (dots) {
ts += QLatin1String("...");
}
return ts;
}
static QColor mixColors(double p1, QColor c1, QColor c2)
{
return QColor(int(c1.red() * p1 + c2.red() * (1.0 - p1)), //
int(c1.green() * p1 + c2.green() * (1.0 - p1)), //
int(c1.blue() * p1 + c2.blue() * (1.0 - p1)));
}
void KWordWrap::drawFadeoutText(QPainter *p, int x, int y, int maxW, const QString &t)
{
QFontMetrics fm = p->fontMetrics();
QColor bgColor = p->background().color();
QColor textColor = p->pen().color();
if ((fm.boundingRect(t).width() > maxW) && (t.length() > 1)) {
int tl = 0;
int w = 0;
while (tl < t.length()) {
w += fm.horizontalAdvance(t.at(tl));
if (w >= maxW) {
break;
}
tl++;
}
int n = qMin(tl, 3);
if (t.isRightToLeft()) {
x += maxW; // start from the right side for RTL string
if (tl > 3) {
x -= fm.horizontalAdvance(t.left(tl - 3));
p->drawText(x, y, t.left(tl - 3));
}
for (int i = 0; i < n; i++) {
p->setPen(mixColors(0.70 - i * 0.25, textColor, bgColor));
QString s(t.at(tl - n + i));
x -= fm.horizontalAdvance(s);
p->drawText(x, y, s);
}
} else {
if (tl > 3) {
p->drawText(x, y, t.left(tl - 3));
x += fm.horizontalAdvance(t.left(tl - 3));
}
for (int i = 0; i < n; i++) {
p->setPen(mixColors(0.70 - i * 0.25, textColor, bgColor));
QString s(t.at(tl - n + i));
p->drawText(x, y, s);
x += fm.horizontalAdvance(s);
}
}
} else {
p->drawText(x, y, t);
}
}
void KWordWrap::drawTruncateText(QPainter *p, int x, int y, int maxW, const QString &t)
{
QString tmpText = p->fontMetrics().elidedText(t, Qt::ElideRight, maxW);
p->drawText(x, y, tmpText);
}
void KWordWrap::drawText(QPainter *painter, int textX, int textY, int flags) const
{
// qDebug() << "KWordWrap::drawText text=" << wrappedString() << " x=" << textX << " y=" << textY;
// We use the calculated break positions to draw the text line by line using QPainter
int start = 0;
int y = 0;
QFontMetrics fm = painter->fontMetrics();
int height = fm.height(); // line height
int ascent = fm.ascent();
int maxwidth = d->m_boundingRect.width();
int i;
int lwidth = 0;
int end = 0;
for (i = 0; i < d->m_breakPositions.count(); ++i) {
// if this is the last line, leave the loop
if (d->m_constrainingRect.height() >= 0 //
&& ((y + 2 * height) > d->m_constrainingRect.height())) {
break;
}
end = d->m_breakPositions.at(i);
lwidth = d->m_lineWidths.at(i);
int x = textX;
if (flags & Qt::AlignHCenter) {
x += (maxwidth - lwidth) / 2;
} else if (flags & Qt::AlignRight) {
x += maxwidth - lwidth;
}
painter->drawText(x, textY + y + ascent, d->m_text.mid(start, end - start + 1));
y += height;
start = end + 1;
}
// Draw the last line
lwidth = d->m_lineWidths.last();
int x = textX;
if (flags & Qt::AlignHCenter) {
x += (maxwidth - lwidth) / 2;
} else if (flags & Qt::AlignRight) {
x += maxwidth - lwidth;
}
if ((d->m_constrainingRect.height() < 0) || ((y + height) <= d->m_constrainingRect.height())) {
if (i == d->m_breakPositions.count()) {
painter->drawText(x, textY + y + ascent, d->m_text.mid(start));
} else if (flags & FadeOut) {
drawFadeoutText(painter, textX, textY + y + ascent, d->m_constrainingRect.width(), d->m_text.mid(start));
} else if (flags & Truncate) {
drawTruncateText(painter, textX, textY + y + ascent, d->m_constrainingRect.width(), d->m_text.mid(start));
} else {
painter->drawText(x, textY + y + ascent, d->m_text.mid(start));
}
}
}
QRect KWordWrap::boundingRect() const
{
return d->m_boundingRect;
}
@@ -0,0 +1,146 @@
/* This file is part of the KDE libraries
SPDX-FileCopyrightText: 2001 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef kwordwrap_h
#define kwordwrap_h
#include <kguiaddons_export.h>
#include <QSharedDataPointer>
#include <qnamespace.h>
class QFontMetrics;
class QRect;
class QString;
class QPainter;
class KWordWrapPrivate;
/**
* @class KWordWrap kwordwrap.h KWordWrap
*
* Word-wrap algorithm that takes into account beautifulness ;)
*
* That means:
* @li not letting a letter alone on the last line,
* @li breaking at punctuation signs (not only at spaces)
* @li improved handling of (), [] and {}
* @li improved handling of '/' (e.g. for paths)
*
* Usage: call the static method, formatText, with the text to
* wrap and the constraining rectangle etc., it will return an instance of KWordWrap
* containing internal data, result of the word-wrapping.
* From that instance you can retrieve the boundingRect, and invoke drawing.
*
* This design allows to call the word-wrap algorithm only when the text changes
* and not every time we want to know the bounding rect or draw the text.
*
* @author David Faure <faure@kde.org>
*/
class KGUIADDONS_EXPORT KWordWrap
{
public:
/**
* Use this flag in drawText() if you want to fade out the text if it does
* not fit into the constraining rectangle.
*/
enum {
FadeOut = 0x10000000,
Truncate = 0x20000000,
};
/**
* Main method for wrapping text.
*
* @param fm Font metrics, for the chosen font. Better cache it, creating a QFontMetrics is expensive.
* @param r Constraining rectangle. Only the width and height matter. With
* negative height the complete text will be rendered
* @param flags currently unused
* @param str The text to be wrapped.
* @param len Length of text to wrap (default is -1 for all).
* @return a KWordWrap instance. The caller is responsible for storing and deleting the result.
*/
static KWordWrap formatText(QFontMetrics &fm, const QRect &r, int flags, const QString &str, int len = -1);
/**
* @return the bounding rect, calculated by formatText. The width is the
* width of the widest text line, and never wider than
* the rectangle given to formatText. The height is the
* text block. X and Y are always 0.
*/
QRect boundingRect() const;
/**
* @return the original string, with '\n' inserted where
* the text is broken by the wordwrap algorithm.
*/
QString wrappedString() const; // gift for Dirk :)
/**
* @return the original string, truncated to the first line.
* If @p dots was set, '...' is appended in case the string was truncated.
* Bug: Note that the '...' come out of the bounding rect.
*/
QString truncatedString(bool dots = true) const;
/**
* Draw the text that has been previously wrapped, at position x,y.
* Flags are for alignment, e.g. Qt::AlignHCenter. Default is
* Qt::AlignAuto.
* @param painter the QPainter to use.
* @param x the horizontal position of the text
* @param y the vertical position of the text
* @param flags the ORed text alignment flags from the Qt namespace,
* ORed with FadeOut if you want the text to fade out if it
* does not fit (the @p painter's background must be set
* accordingly)
*/
void drawText(QPainter *painter, int x, int y, int flags = Qt::AlignLeft) const;
/**
* Destructor.
*/
~KWordWrap();
/**
* Copy constructor
*/
KWordWrap(const KWordWrap &other);
/**
* Assignment operator
*/
KWordWrap &operator=(const KWordWrap &other);
/**
* Draws the string @p t at the given coordinates, if it does not
* @p fit into @p maxW the text will be faded out.
* @param p the painter to use. Must have set the pen for the text
* color and the background for the color to fade out
* @param x the horizontal position of the text
* @param y the vertical position of the text
* @param maxW the maximum width of the text (including the fade-out
* effect)
* @param t the text to draw
*/
static void drawFadeoutText(QPainter *p, int x, int y, int maxW, const QString &t);
/**
* Draws the string @p t at the given coordinates, if it does not
* @p fit into @p maxW the text will be truncated.
* @param p the painter to use
* @param x the horizontal position of the text
* @param y the vertical position of the text
* @param maxW the maximum width of the text (including the '...')
* @param t the text to draw
*/
static void drawTruncateText(QPainter *p, int x, int y, int maxW, const QString &t);
private:
KGUIADDONS_NO_EXPORT explicit KWordWrap(const QRect &r);
QExplicitlySharedDataPointer<KWordWrapPrivate> d;
};
#endif
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
// SPDX-FileCopyrightText: 2022-2023 Harald Sitter <sitter@kde.org>
#include "kcountryflagemojiiconengine.h"
#include <QDebug>
#include <QFont>
#include <QGuiApplication>
#include <QPainter>
#include <QPalette>
using namespace Qt::Literals::StringLiterals;
namespace
{
Q_GLOBAL_STATIC(QFont, s_globalDefaultFont, "emoji"_L1)
QString makeCountryEmoji(const QString &country)
{
// The way this was set up by unicode is actually pretty smart. Country flags are based on their two character
// country codes within a given range of code points. And even better, the offset inside the range is the same
// as the offset inside ASCII. Meaning the offset of 'A' from 0 is the same as the offset of 🇦 in the
// flag codepoint range. The way a flag is then denoted is e.g. <SURROGATEPAIR>🇦<SURROGATEPAIR>🇹 resulting in
// the Austrian flag.
// https://en.wikipedia.org/wiki/Regional_indicator_symbol
static constexpr auto surrogatePairCodePoint = 0xD83C; // U+D83C
static constexpr auto flagCodePointStart = 0xDDE6; // U+1F1E6 (🇦) - NB: we are in UTF-16
static constexpr auto offsetCodePointA = 'A'_L1.unicode(); // offset from 0, the flag code points have the same offsets
static constexpr auto basePoint = flagCodePointStart - offsetCodePointA;
QStringList emojiList;
emojiList.reserve(country.size());
for (const auto &c : country) {
emojiList.append(QChar(surrogatePairCodePoint) + QChar(basePoint + c.toUpper().unicode()));
}
// Valid flag country codes have only 2 characters.
// If we have more, separate the flag codepoints to avoid misrepresentations
if (country.size() != 2) {
return emojiList.join(QChar(0x200b)); // U+200B Zero-Width Space
}
return emojiList.join(QString());
}
QString makeRegionEmoji(const QString &region)
{
// Region flags work much the same as country flags but with a slightly different format in a slightly different
// code point region. Specifically they use ISO 3166-2 as input (e.g. GB-SCT for Scotland). It all happens in
// the Unicode Block “Tags” (starting at U+E0000) wherein it functions the same as the country codes do in their
// block. The offsets inside the block are the same as the ascii offsets and the emoji is constructed by combining
// the off set code points of the incoming region tag. They are prefixed with U+1F3F4 🏴 WAVING BLACK FLAG
// and suffixed with U+E007F CANCEL TAG.
// https://en.wikipedia.org/wiki/Regional_indicator_symbol
auto hyphenlessRegion = region;
hyphenlessRegion.remove('-'_L1);
static constexpr auto surrogatePairCodePoint = 0xdb40; // U+DB40
static constexpr auto flagCodePointStart = 0xDC41; // U+E0041 (Tag Latin Capital Letter A) - NB: we are in UTF-16
static constexpr auto offsetCodePointA = 'A'_L1.unicode(); // offset from 0, the flag code points have the same offsets
static constexpr auto basePoint = flagCodePointStart - offsetCodePointA;
auto emoji = u"🏴"_s;
emoji.reserve(emoji.size() + 2 * hyphenlessRegion.size() + 2);
for (const auto &c : hyphenlessRegion) {
emoji.append(QChar(surrogatePairCodePoint));
emoji.append(QChar(basePoint + c.toLower().unicode()));
}
static const auto cancelTag = QString().append(QChar(surrogatePairCodePoint)).append(QChar(0xDC7F));
return emoji.append(cancelTag);
}
} // namespace
class Q_DECL_HIDDEN KCountryFlagEmojiIconEnginePrivate
{
public:
explicit KCountryFlagEmojiIconEnginePrivate(const QString &regionOrCountry)
: m_country(regionOrCountry)
, m_emoji(regionOrCountry.contains("-"_L1) ? makeRegionEmoji(regionOrCountry) : makeCountryEmoji(regionOrCountry))
{
}
const QString m_country;
const QString m_emoji;
};
KCountryFlagEmojiIconEngine::KCountryFlagEmojiIconEngine(const QString &country)
: d(std::make_unique<KCountryFlagEmojiIconEnginePrivate>(country))
{
}
KCountryFlagEmojiIconEngine::~KCountryFlagEmojiIconEngine() = default;
QIconEngine *KCountryFlagEmojiIconEngine::clone() const
{
return new KCountryFlagEmojiIconEngine(d->m_country);
}
QString KCountryFlagEmojiIconEngine::key() const
{
return u"org.kde.KCountryFlagEmojiIconEngine"_s;
}
void KCountryFlagEmojiIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
{
// Not supported
Q_UNUSED(mode);
Q_UNUSED(state);
QFont font(*s_globalDefaultFont, painter->device());
font.setPixelSize(qMax(rect.width(), rect.height()));
font.setFixedPitch(true);
QFontMetricsF metrics(font, painter->device());
QRectF tightRect = metrics.tightBoundingRect(d->m_emoji);
if (tightRect.width() > rect.width() || tightRect.height() > rect.height()) {
const auto ratio = std::max({1.0, tightRect.width() / rect.width(), tightRect.height() / rect.height()});
font.setPixelSize(std::max(1.0, std::floor(font.pixelSize() / ratio)));
metrics = QFontMetricsF(font, painter->device());
tightRect = metrics.tightBoundingRect(d->m_emoji);
}
painter->setPen(qGuiApp->palette().color(QPalette::WindowText)); // in case we render the letters in absence of a flag
QRectF flagBoundingRect = metrics.boundingRect(rect, Qt::AlignCenter, d->m_emoji);
// Confusingly the pixelSize for drawing must actually be without DPR but the rect calculation above
// seems to be correct even with DPR in the pixelSize.
const auto dpr = painter->device()->devicePixelRatioF();
font.setPixelSize(std::floor(font.pixelSize() / dpr));
// The offset of the bounding rect needs to be also adjusted by the DPR
flagBoundingRect.moveTopLeft(flagBoundingRect.topLeft() / dpr);
painter->setFont(font);
painter->drawText(flagBoundingRect, d->m_emoji);
}
QPixmap KCountryFlagEmojiIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
{
return scaledPixmap(size, mode, state, 1.0);
}
QPixmap KCountryFlagEmojiIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
{
QPixmap pixmap(size);
pixmap.setDevicePixelRatio(scale);
pixmap.fill(Qt::transparent);
{
QPainter p(&pixmap);
paint(&p, QRect(QPoint(0, 0), size), mode, state);
}
return pixmap;
}
bool KCountryFlagEmojiIconEngine::isNull()
{
return d->m_emoji.isEmpty();
}
void KCountryFlagEmojiIconEngine::setGlobalDefaultFont(const QFont &font)
{
QFont swapable(font);
s_globalDefaultFont->swap(swapable);
}
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
// SPDX-FileCopyrightText: 2022-2023 Harald Sitter <sitter@kde.org>
#ifndef KCOUNTRYFLAGEMOJIICONENGINE_H
#define KCOUNTRYFLAGEMOJIICONENGINE_H
#include <QIconEngine>
#include <kguiaddons_export.h>
class KCountryFlagEmojiIconEnginePrivate;
/**
* @brief Provides emoji flags as icons
* This is a special icon engine that internally paints flags using emoji fonts.
* It provides access to country and region flags from the system emoji font.
* ```
* auto l = new QLabel;
* l->setMinimumSize(512, 512);
* l->setPixmap(QIcon(new KCountryFlagEmojiIconEngine("AT")).pixmap(512, 512));
* ```
* @since 6.0
*/
class KGUIADDONS_EXPORT KCountryFlagEmojiIconEngine : public QIconEngine
{
public:
/**
* @brief Construct a new KCountryFlagEmojiIconEngine object
* Please note that regional flag support can be spotty in emoji fonts.
* @param regionOrCountry either a ISO 3166-1 alpha-2 country code or a ISO 3166-2 region code (e.g. AT for Austria or GB-SCT for Scotland)
*/
explicit KCountryFlagEmojiIconEngine(const QString &regionOrCountry);
~KCountryFlagEmojiIconEngine() override;
Q_DISABLE_COPY_MOVE(KCountryFlagEmojiIconEngine)
QIconEngine *clone() const override;
QString key() const override;
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
/**
* @brief Check whether the internal emoji unicode sequence is null
* This does not necessarily mean that the pixmap output will be a valid flag - that entirely depends on the system's precise font configuration.
* @return true when the construction of the emoji string failed
* @return false when the construction of the emoji string succeeded
*/
bool isNull() override;
/**
* @brief Set the Global Default Font object
* This is primarily useful for platform themes that wish to force a specific font being used. By default the "emoji" font family will be used.
* Forcing a specific font and making sure it is available as runtime requirement is the most reliable way to ensure that flag support is working
* regardless of system configuration.
* @param font the default font to use
*/
static void setGlobalDefaultFont(const QFont &font);
private:
std::unique_ptr<KCountryFlagEmojiIconEnginePrivate> const d;
};
#endif
@@ -0,0 +1,54 @@
/*
SPDX-License-Identifier: LGPL-2.0-or-later
SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org>
SPDX-FileCopyrightText: 2020 Laurent Montel <montel@kde.org>
*/
#include "kcursorsaver.h"
#include "kguiaddons_debug.h"
#include <QGuiApplication>
class KCursorSaverPrivate
{
public:
bool ownsCursor = true;
};
KCursorSaver::KCursorSaver(Qt::CursorShape shape)
: d(new KCursorSaverPrivate)
{
QGuiApplication::setOverrideCursor(QCursor(shape));
d->ownsCursor = true;
}
KCursorSaver::KCursorSaver(KCursorSaver &&other)
: d(other.d)
{
*this = std::move(other);
}
KCursorSaver::~KCursorSaver()
{
if (d->ownsCursor) {
QGuiApplication::restoreOverrideCursor();
delete d;
}
}
void KCursorSaver::restoreCursor()
{
if (!d->ownsCursor) {
qCWarning(KGUIADDONS_LOG) << "This KCursorSaver doesn't own the cursor anymore, invalid call to restoreCursor().";
return;
}
d->ownsCursor = false;
QGuiApplication::restoreOverrideCursor();
}
KCursorSaver &KCursorSaver::operator=(KCursorSaver &&other)
{
if (this != &other) {
d->ownsCursor = false;
}
return *this;
}
@@ -0,0 +1,52 @@
/*
SPDX-License-Identifier: LGPL-2.0-or-later
SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org>
SPDX-FileCopyrightText: 2020 Laurent Montel <montel@kde.org>
*/
#ifndef KCURSORSAVER_H
#define KCURSORSAVER_H
#include <kguiaddons_export.h>
#include <QCursor>
class KCursorSaverPrivate;
/**
* @class KCursorSaver kcursorsaver.h KCursorSaver
*
* @short Class to temporarily set a mouse cursor and restore the previous one on destruction
*
* Create a KCursorSaver object when you want to set the cursor.
* As soon as it gets out of scope, it will restore the original
* cursor.
* @code
KCursorSaver saver(Qt::WaitCursor);
... long-running operation here ...
@endcode
* @since 5.73
*/
class KGUIADDONS_EXPORT KCursorSaver
{
public:
/// Creates a KCursorSaver, setting the mouse cursor to @p shape.
explicit KCursorSaver(Qt::CursorShape shape);
/// Move-constructs a KCursorSaver from other
KCursorSaver(KCursorSaver &&other);
/// restore the cursor
~KCursorSaver();
/// call this to explicitly restore the cursor
void restoreCursor();
KCursorSaver &operator=(KCursorSaver &&other);
private:
KCursorSaver(KCursorSaver &other) = delete;
void operator=(const KCursorSaver &rhs) = delete;
KCursorSaverPrivate *const d; ///< @internal
};
#endif
@@ -0,0 +1,226 @@
/*
SPDX-FileCopyrightText: 2013 Martin Klapetek <mklapetek@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include <array>
#include "kiconutils.h"
#include <QHash>
#include <QIconEngine>
#include <QLibraryInfo>
#include <QPainter>
#include <QVersionNumber>
class KOverlayIconEngine : public QIconEngine
{
public:
KOverlayIconEngine(const QIcon &icon, const QIcon &overlay, Qt::Corner position);
KOverlayIconEngine(const QIcon &icon, const QHash<Qt::Corner, QIcon> &overlays);
KOverlayIconEngine(const QIcon &icon, const QStringList &overlays);
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
QIconEngine *clone() const override;
QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
void addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state) override;
void addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state) override;
void virtual_hook(int id, void *data) override;
private:
QIcon m_base;
QHash<Qt::Corner, QIcon> m_overlays;
};
KOverlayIconEngine::KOverlayIconEngine(const QIcon &icon, const QIcon &overlay, Qt::Corner position)
: QIconEngine()
, m_base(icon)
{
m_overlays.insert(position, overlay);
}
KOverlayIconEngine::KOverlayIconEngine(const QIcon &icon, const QHash<Qt::Corner, QIcon> &overlays)
: QIconEngine()
, m_base(icon)
, m_overlays(overlays)
{
}
KOverlayIconEngine::KOverlayIconEngine(const QIcon &icon, const QStringList &overlays)
: QIconEngine()
, m_base(icon)
{
const std::array<Qt::Corner, 4> indexToCorner{
Qt::BottomRightCorner,
Qt::BottomLeftCorner,
Qt::TopLeftCorner,
Qt::TopRightCorner,
};
// static_cast becaue size() returns a qsizetype in Qt6
const int count = std::min(4, static_cast<int>(overlays.size()));
m_overlays.reserve(count);
for (int i = 0; i < count; i++) {
m_overlays.insert(indexToCorner[i], QIcon::fromTheme(overlays.at(i)));
}
}
QIconEngine *KOverlayIconEngine::clone() const
{
return new KOverlayIconEngine(*this);
}
QSize KOverlayIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
{
return m_base.actualSize(size, mode, state);
}
QPixmap KOverlayIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
{
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
paint(&p, pixmap.rect(), mode, state);
return pixmap;
}
void KOverlayIconEngine::addPixmap(const QPixmap &pixmap, QIcon::Mode mode, QIcon::State state)
{
m_base.addPixmap(pixmap, mode, state);
}
void KOverlayIconEngine::addFile(const QString &fileName, const QSize &size, QIcon::Mode mode, QIcon::State state)
{
m_base.addFile(fileName, size, mode, state);
}
void KOverlayIconEngine::virtual_hook(int id, void *data)
{
if (id == QIconEngine::ScaledPixmapHook) {
auto *info = reinterpret_cast<ScaledPixmapArgument *>(data);
QSize phyiscalSize = info->size;
// Since https://codereview.qt-project.org/c/qt/qtbase/+/563553 size is in logical pixels
if (QLibraryInfo::version() >= QVersionNumber(6, 8, 0)) {
phyiscalSize *= info->scale;
}
QPixmap pixmap(phyiscalSize);
pixmap.setDevicePixelRatio(info->scale);
pixmap.fill(Qt::transparent);
QRect rect = pixmap.rect();
const QRect logicalRect(rect.x() / info->scale, rect.y() / info->scale, rect.width() / info->scale, rect.height() / info->scale);
QPainter p(&pixmap);
paint(&p, logicalRect, info->mode, info->state);
info->pixmap = pixmap;
return;
}
QIconEngine::virtual_hook(id, data);
}
void KOverlayIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
{
// Paint the base icon as the first layer
m_base.paint(painter, rect, Qt::AlignCenter, mode, state);
if (m_overlays.isEmpty()) {
return;
}
const int width = rect.width();
const int height = rect.height();
const int iconSize = qMin(width, height);
// Determine the overlay icon size
int overlaySize;
if (iconSize < 32) {
overlaySize = 8;
} else if (iconSize <= 48) {
overlaySize = 16;
} else if (iconSize <= 64) {
overlaySize = 22;
} else if (iconSize <= 96) {
overlaySize = 32;
} else if (iconSize <= 128) {
overlaySize = 48;
} else {
overlaySize = (int)(iconSize / 4);
}
// Iterate over stored overlays
QHash<Qt::Corner, QIcon>::const_iterator i = m_overlays.constBegin();
while (i != m_overlays.constEnd()) {
const QPixmap overlayPixmap = i.value().pixmap(overlaySize, overlaySize, mode, state);
if (overlayPixmap.isNull()) {
++i;
continue;
}
QPoint startPoint;
switch (i.key()) {
case Qt::BottomLeftCorner:
startPoint = QPoint(2, height - overlaySize - 2);
break;
case Qt::BottomRightCorner:
startPoint = QPoint(width - overlaySize - 2, height - overlaySize - 2);
break;
case Qt::TopRightCorner:
startPoint = QPoint(width - overlaySize - 2, 2);
break;
case Qt::TopLeftCorner:
startPoint = QPoint(2, 2);
break;
}
// Draw the overlay pixmap
painter->drawPixmap(startPoint, overlayPixmap);
++i;
}
}
// ============================================================================
namespace KIconUtils
{
QIcon addOverlay(const QIcon &icon, const QIcon &overlay, Qt::Corner position)
{
return QIcon(new KOverlayIconEngine(icon, overlay, position));
}
QIcon addOverlays(const QIcon &icon, const QHash<Qt::Corner, QIcon> &overlays)
{
return QIcon(new KOverlayIconEngine(icon, overlays));
}
QIcon addOverlays(const QIcon &icon, const QStringList &overlays)
{
if (overlays.count() == 0) {
return icon;
}
return QIcon(new KOverlayIconEngine(icon, overlays));
}
QIcon addOverlays(const QString &iconName, const QStringList &overlays)
{
const QIcon icon = QIcon::fromTheme(iconName);
if (overlays.count() == 0) {
return icon;
}
return QIcon(new KOverlayIconEngine(icon, overlays));
}
}
@@ -0,0 +1,79 @@
/*
SPDX-FileCopyrightText: 2013 Martin Klapetek <mklapetek@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KICONUTILS_H
#define KICONUTILS_H
#include <kguiaddons_export.h>
#include <QIcon>
/**
* @namespace KIconUtils
* Provides utility functions for icons.
*/
namespace KIconUtils
{
/**
* Adds the \a overlay over the \a icon in the specified \a position
*
* The \a overlay icon is scaled down approx. to 1/3 or 1/4 (depending on the icon size)
* and placed in one of the corners of the base icon.
*/
KGUIADDONS_EXPORT QIcon addOverlay(const QIcon &icon, const QIcon &overlay, Qt::Corner position);
/**
* Adds \a overlays over the \a icon
*
* The \a overlays is a QHash of Qt::Corner and QIcon. The Qt::Corner value
* decides where the overlay icon will be painted, the QIcon value
* is the overlay icon to be painted.
*
* The overlay icon is scaled down to 1/3 or 1/4 (depending on the icon size)
* and placed in one of the corners of the base icon.
*/
KGUIADDONS_EXPORT QIcon addOverlays(const QIcon &icon, const QHash<Qt::Corner, QIcon> &overlays);
/**
* Adds up to four overlays over the @p icon.
*
* The @p overlays is a QStringList of icon names (e.g. the emblems that are drawn on
* icons in Dolphin and KFileWidget, e.g. symlink, un-mounted device ...etc).
*
* Overlays are added in this order:
* - first icon is used to paint an overlay on the bottom-right corner
* - second icon on the bottom-left corner
* - third icon on the top-left corner
* - fourth icon on the top-right corner
*
* Each overlay icon is scaled down to 1/3 or 1/4 (depending on the icon size).
*
* @since 5.90
*/
KGUIADDONS_EXPORT QIcon addOverlays(const QIcon &icon, const QStringList &overlays);
/**
* Adds up to four overlays on the icon constructed from @p iconName.
*
* The @p overlays is a QStringList of icon names (e.g. the emblems that are drawn on
* icons in Dolphin and KFileWidget, e.g. symlink, un-mounted device ...etc).
*
* Overlays are added in this order:
* - first icon is used to paint an overlay on the bottom-right corner
* - second icon on the bottom-left corner
* - third icon on the top-left corner
* - fourth icon on the top-right corner
*
* Each overlay icon is scaled down to 1/3 or 1/4 (depending on the icon size).
*
* All @c QIcon objects are constructed using @c QIcon::fromTheme().
*
* @since 5.82
*/
KGUIADDONS_EXPORT QIcon addOverlays(const QString &iconName, const QStringList &overlays);
}
#endif // KICONUTILS_H
@@ -0,0 +1,219 @@
/* This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KIMAGECACHE_H
#define KIMAGECACHE_H
// check that KGUIADDONS_LIB is defined in case the application is not using CMake
// (if KGUIADDONS_LIB is not defined, we cannot assume that KCOREADDONS_LIB not being
// defined means that we are not linked against KCoreAddons)
#if defined(KGUIADDONS_LIB) && !defined(KCOREADDONS_LIB)
#ifdef __GNUC__
#warning "KImageCache requires KF6CoreAddons (for kshareddatacache.h)"
#else
#pragma message("KImageCache requires KF6CoreAddons (for kshareddatacache.h)")
#endif
#endif
#include <klocalimagecacheimpl.h>
#include <kshareddatacache.h>
#include <QImage>
#include <QPixmap>
#define KImageCache KSharedPixmapCacheMixin<KSharedDataCache>
/**
* @brief A simple wrapping layer over KSharedDataCache to support caching
* images and pixmaps.
*
* This class can be used to share images between different processes, which
* is useful when it is known that such images will be used across many
* processes, or when creating the image is expensive.
*
* In addition, the class also supports caching QPixmaps <em>in a single
* process</em> using the setPixmapCaching() function.
*
* Tips for use: If you already have QPixmaps that you intend to use, and
* you do not need access to the actual image data, then try to store and
* retrieve QPixmaps for use.
*
* On the other hand, if you will need to store and retrieve actual image
* data (to modify the image after retrieval for instance) then you should
* use QImage to save the conversion cost from QPixmap to QImage.
*
* KSharedPixmapCacheMixin is a subclass of KSharedDataCache, so all of the methods that
* can be used with KSharedDataCache can be used with KSharedPixmapCacheMixin,
* <em>with the exception of KSharedDataCache::insert() and
* KSharedDataCache::find()</em>.
*
* @author Michael Pyne <mpyne@kde.org>
* @since 4.5
*/
template<class T>
class KSharedPixmapCacheMixin : public T, private KLocalImageCacheImplementation
{
public:
/**
* Constructs an image cache, named by @p cacheName, with a default
* size of @p defaultCacheSize.
*
* @param cacheName Name of the cache to use.
* @param defaultCacheSize The default size, in bytes, of the cache.
* The actual on-disk size will be slightly larger. If the cache already
* exists, it will not be resized. If it is required to resize the
* cache then use the deleteCache() function to remove that cache first.
* @param expectedItemSize The expected general size of the items to be
* added to the image cache, in bytes. Use 0 if you just want a default
* item size.
*/
KSharedPixmapCacheMixin(const QString &cacheName, unsigned defaultCacheSize, unsigned expectedItemSize = 0)
: T(cacheName, defaultCacheSize, expectedItemSize)
, KLocalImageCacheImplementation(defaultCacheSize)
{
}
/**
* Inserts the pixmap given by @p pixmap to the cache, accessible with
* @p key. The pixmap must be converted to a QImage in order to be stored
* into shared memory. In order to prevent unnecessary conversions from
* taking place @p pixmap will also be cached (but not in shared
* memory) and would be accessible using findPixmap() if pixmap caching is
* enabled.
*
* @param key Name to access @p pixmap with.
* @param pixmap The pixmap to add to the cache.
* @return true if the pixmap was successfully cached, false otherwise.
* @see setPixmapCaching()
*/
bool insertPixmap(const QString &key, const QPixmap &pixmap)
{
insertLocalPixmap(key, pixmap);
// One thing to think about is only inserting things to the shared cache
// that are frequently used. But that would require tracking the use count
// in our local cache too, which I think is probably too much work.
return insertImage(key, pixmap.toImage());
}
/**
* Inserts the @p image into the shared cache, accessible with @p key. This
* variant is preferred over insertPixmap() if your source data is already a
* QImage, if it is essential that the image be in shared memory (such as
* for SVG icons which have a high render time), or if it will need to be
* in QImage form after it is retrieved from the cache.
*
* @param key Name to access @p image with.
* @param image The image to add to the shared cache.
* @return true if the image was successfully cached, false otherwise.
*/
bool insertImage(const QString &key, const QImage &image)
{
if (this->insert(key, serializeImage(image))) {
updateModifiedTime();
return true;
}
return false;
}
/**
* Copies the cached pixmap identified by @p key to @p destination. If no such
* pixmap exists @p destination is unchanged.
*
* @return true if the pixmap identified by @p key existed, false otherwise.
* @see setPixmapCaching()
*/
bool findPixmap(const QString &key, QPixmap *destination) const
{
if (findLocalPixmap(key, destination)) {
return true;
}
QByteArray cachedData;
if (!this->find(key, &cachedData) || cachedData.isNull()) {
return false;
}
if (destination) {
destination->loadFromData(cachedData, "PNG");
// Manually re-insert to pixmap cache if we'll be using this one.
insertLocalPixmap(key, *destination);
}
return true;
}
/**
* Copies the cached image identified by @p key to @p destination. If no such
* image exists @p destination is unchanged.
*
* @return true if the image identified by @p key existed, false otherwise.
*/
bool findImage(const QString &key, QImage *destination) const
{
QByteArray cachedData;
if (!this->find(key, &cachedData) || cachedData.isNull()) {
return false;
}
if (destination) {
destination->loadFromData(cachedData, "PNG");
}
return true;
}
/**
* Removes all entries from the cache. In addition any cached pixmaps (as per
* setPixmapCaching()) are also removed.
*/
void clear()
{
clearLocalCache();
T::clear();
}
/**
* @return The time that an image or pixmap was last inserted into a cache.
*/
using KLocalImageCacheImplementation::lastModifiedTime;
/**
* @return if QPixmaps added with insertPixmap() will be stored in a local
* pixmap cache as well as the shared image cache. The default is to cache
* pixmaps locally.
*/
using KLocalImageCacheImplementation::pixmapCaching;
/**
* Enables or disables local pixmap caching. If it is anticipated that a pixmap
* will be frequently needed then this can actually save memory overall since the
* X server or graphics card will not have to store duplicate copies of the same
* image.
*
* @param enable Enables pixmap caching if true, disables otherwise.
*/
using KLocalImageCacheImplementation::setPixmapCaching;
/**
* @return The highest memory size in bytes to be used by cached pixmaps.
* @since 4.6
*/
using KLocalImageCacheImplementation::pixmapCacheLimit;
/**
* Sets the highest memory size the pixmap cache should use.
*
* @param size The size in bytes
* @since 4.6
*/
using KLocalImageCacheImplementation::setPixmapCacheLimit;
};
#endif /* KIMAGECACHE_H */
@@ -0,0 +1,25 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2013 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kjobwindows.h"
#include <QObject>
#include <QPointer>
#include <QVariant>
#include <QWindow>
void KJobWindows::setWindow(QObject *job, QWindow *window)
{
job->setProperty("window", QVariant::fromValue(QPointer<QWindow>(window)));
if (window) {
job->setProperty("window-id", QVariant::fromValue(window->winId()));
}
}
QWindow *KJobWindows::window(QObject *job)
{
return job->property("window").value<QPointer<QWindow>>();
}
@@ -0,0 +1,48 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2013 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KJOBWINDOWS_H
#define KJOBWINDOWS_H
#include <kguiaddons_export.h>
class QWindow;
class QObject;
/**
* KJobWindows namespace
*/
namespace KJobWindows
{
/**
* Associate this job with a window given by @p window.
*
* @param job should be a KJob subclass
*
* This is used:
* @li by KDialogJobUiDelegate as parent widget for error messages
* @li by KWidgetJobTracker as parent widget for progress dialogs
* @li by KIO::AbstractJobInteractionInterface as parent widget for rename/skip dialogs
* and possibly more.
* @li by KIO::DropJob as parent widget of popup menus.
* This is required on Wayland to properly position the menu.
*
* @since 6.0
*/
KGUIADDONS_EXPORT void setWindow(QObject *job, QWindow *window);
/**
* Return the window associated with this job.
*
* @param job should be a KJob subclass
*
* @since 6.0
*/
KGUIADDONS_EXPORT QWindow *window(QObject *job);
}
#endif
@@ -0,0 +1,144 @@
/* This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "klocalimagecacheimpl.h"
#include <QBuffer>
#include <QCache>
#include <QCoreApplication>
#include <QDateTime>
#include <QImage>
#include <QPixmap>
/**
* This is a QObject subclass so we can catch the signal that the application is about
* to close and properly release any QPixmaps we have cached.
*/
class KLocalImageCacheImplementationPrivate : public QObject
{
Q_OBJECT
public:
KLocalImageCacheImplementationPrivate(QObject *parent = nullptr)
: QObject(parent)
, timestamp(QDateTime::currentDateTime())
{
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &KLocalImageCacheImplementationPrivate::clearPixmaps);
}
/**
* Inserts a pixmap into the pixmap cache if the pixmap cache is enabled, with
* weighting based on image size and bit depth.
*/
bool insertPixmap(const QString &key, QPixmap *pixmap)
{
if (enablePixmapCaching && pixmap && !pixmap->isNull()) {
// "cost" parameter is based on both image size and depth to make cost
// based on size in bytes instead of area on-screen.
return pixmapCache.insert(key, pixmap, pixmap->width() * pixmap->height() * pixmap->depth() / 8);
}
return false;
}
public Q_SLOTS:
void clearPixmaps()
{
pixmapCache.clear();
}
public:
QDateTime timestamp;
/**
* This is used to cache pixmaps as they are inserted, instead of always
* converting to image data and storing that in shared memory.
*/
QCache<QString, QPixmap> pixmapCache;
bool enablePixmapCaching = true;
};
KLocalImageCacheImplementation::KLocalImageCacheImplementation(unsigned defaultCacheSize)
: d(new KLocalImageCacheImplementationPrivate)
{
// Use at least 16 KiB for the pixmap cache
d->pixmapCache.setMaxCost(qMax(defaultCacheSize / 8, (unsigned int)16384));
}
KLocalImageCacheImplementation::~KLocalImageCacheImplementation() = default;
void KLocalImageCacheImplementation::updateModifiedTime()
{
d->timestamp = QDateTime::currentDateTime();
}
QByteArray KLocalImageCacheImplementation::serializeImage(const QImage &image) const
{
QBuffer buffer;
buffer.open(QBuffer::WriteOnly);
image.save(&buffer, "PNG");
return buffer.buffer();
}
bool KLocalImageCacheImplementation::insertLocalPixmap(const QString &key, const QPixmap &pixmap) const
{
return d->insertPixmap(key, new QPixmap(pixmap));
}
bool KLocalImageCacheImplementation::findLocalPixmap(const QString &key, QPixmap *destination) const
{
if (d->enablePixmapCaching) {
QPixmap *cachedPixmap = d->pixmapCache.object(key);
if (cachedPixmap) {
if (destination) {
*destination = *cachedPixmap;
}
return true;
}
}
return false;
}
void KLocalImageCacheImplementation::clearLocalCache()
{
d->pixmapCache.clear();
}
QDateTime KLocalImageCacheImplementation::lastModifiedTime() const
{
return d->timestamp;
}
bool KLocalImageCacheImplementation::pixmapCaching() const
{
return d->enablePixmapCaching;
}
void KLocalImageCacheImplementation::setPixmapCaching(bool enable)
{
if (enable != d->enablePixmapCaching) {
d->enablePixmapCaching = enable;
if (!enable) {
d->pixmapCache.clear();
}
}
}
int KLocalImageCacheImplementation::pixmapCacheLimit() const
{
return d->pixmapCache.maxCost();
}
void KLocalImageCacheImplementation::setPixmapCacheLimit(int size)
{
d->pixmapCache.setMaxCost(size);
}
#include "klocalimagecacheimpl.moc"
@@ -0,0 +1,58 @@
/* This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Michael Pyne <mpyne@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KLOCALIMAGECACHEIMPL_H
#define KLOCALIMAGECACHEIMPL_H
#include <kguiaddons_export.h>
#include <memory>
class KLocalImageCacheImplementationPrivate;
class QImage;
class QPixmap;
class QByteArray;
class QDateTime;
class QString;
/**
* You are not supposed to use this class directly, use KImageCache instead
*
* @internal
*/
class KGUIADDONS_EXPORT KLocalImageCacheImplementation
{
private:
explicit KLocalImageCacheImplementation(unsigned defaultCacheSize);
public:
virtual ~KLocalImageCacheImplementation();
QDateTime lastModifiedTime() const;
bool pixmapCaching() const;
void setPixmapCaching(bool enable);
int pixmapCacheLimit() const;
void setPixmapCacheLimit(int size);
protected:
void updateModifiedTime();
QByteArray serializeImage(const QImage &image) const;
bool insertLocalPixmap(const QString &key, const QPixmap &pixmap) const;
bool findLocalPixmap(const QString &key, QPixmap *destination) const;
void clearLocalCache();
private:
std::unique_ptr<KLocalImageCacheImplementationPrivate> const d; ///< @internal
template<class T>
friend class KSharedPixmapCacheMixin;
};
#endif /* KLOCALIMAGECACHEIMPL_H */
@@ -0,0 +1,97 @@
/*
SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kmodifierkeyinfo.h"
#include "kmodifierkeyinfoprovider_p.h"
#include <kguiaddons_debug.h>
#include <QGuiApplication>
#include "config.h"
#if WITH_WAYLAND
#include "kmodifierkeyinfoprovider_wayland.h"
#endif
#if WITH_X11
#include "kmodifierkeyinfoprovider_xcb.h"
#endif
KModifierKeyInfoProvider *createProvider()
{
#if WITH_WAYLAND
if (qGuiApp->platformName() == QLatin1String("wayland")) {
return new KModifierKeyInfoProviderWayland;
}
#endif
#if WITH_X11
if (qGuiApp->platformName() == QLatin1String("xcb")) {
return new KModifierKeyInfoProviderXcb;
}
#endif
qCWarning(KGUIADDONS_LOG) << "No modifierkeyinfo backend for platform" << qGuiApp->platformName();
return new KModifierKeyInfoProvider;
}
KModifierKeyInfo::KModifierKeyInfo(QObject *parent)
: QObject(parent)
, p(createProvider())
{
connect(p.data(), &KModifierKeyInfoProvider::keyPressed, this, &KModifierKeyInfo::keyPressed);
connect(p.data(), &KModifierKeyInfoProvider::keyLatched, this, &KModifierKeyInfo::keyLatched);
connect(p.data(), &KModifierKeyInfoProvider::keyLocked, this, &KModifierKeyInfo::keyLocked);
connect(p.data(), &KModifierKeyInfoProvider::buttonPressed, this, &KModifierKeyInfo::buttonPressed);
connect(p.data(), &KModifierKeyInfoProvider::keyAdded, this, &KModifierKeyInfo::keyAdded);
connect(p.data(), &KModifierKeyInfoProvider::keyRemoved, this, &KModifierKeyInfo::keyRemoved);
}
KModifierKeyInfo::~KModifierKeyInfo()
{
}
bool KModifierKeyInfo::knowsKey(Qt::Key key) const
{
return p->knowsKey(key);
}
const QList<Qt::Key> KModifierKeyInfo::knownKeys() const
{
return p->knownKeys();
}
bool KModifierKeyInfo::isKeyPressed(Qt::Key key) const
{
return p->isKeyPressed(key);
}
bool KModifierKeyInfo::isKeyLatched(Qt::Key key) const
{
return p->isKeyLatched(key);
}
bool KModifierKeyInfo::setKeyLatched(Qt::Key key, bool latched)
{
return p->setKeyLatched(key, latched);
}
bool KModifierKeyInfo::isKeyLocked(Qt::Key key) const
{
return p->isKeyLocked(key);
}
bool KModifierKeyInfo::setKeyLocked(Qt::Key key, bool locked)
{
return p->setKeyLocked(key, locked);
}
bool KModifierKeyInfo::isButtonPressed(Qt::MouseButton button) const
{
return p->isButtonPressed(button);
}
#include "moc_kmodifierkeyinfo.cpp"
@@ -0,0 +1,178 @@
/*
SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KMODIFIERKEYINFO_H
#define KMODIFIERKEYINFO_H
#include <kguiaddons_export.h>
#include <QExplicitlySharedDataPointer>
#include <QObject>
class KModifierKeyInfoProvider;
/**
* @class KModifierKeyInfo kmodifierkeyinfo.h KModifierKeyInfo
*
* Get information about the state of the keyboard's modifier keys.
*
* This class provides cross-platform information about the state of the
* keyboard's modifier keys and the mouse buttons and allows to change the
* state as well.
*
* It recognizes two states a key can be in:
* @li @em locked: eg. caps-locked (a.k.a. toggled)
* @li @em latched the key is temporarily locked but will be unlocked upon
* the next keypress.
*
* An application can either query the states synchronously (isKeyLatched(),
* isKeyLocked()) or connect to KModifierKeyInfo's signals to be notified about
* changes (keyLatched(), keyLocked()).
*/
class KGUIADDONS_EXPORT KModifierKeyInfo : public QObject
{
Q_OBJECT
public:
/**
* Default constructor
*/
explicit KModifierKeyInfo(QObject *parent = nullptr);
/**
* Destructor
*/
~KModifierKeyInfo() override;
/**
* Check if a key is known by the underlying window system and can be queried.
*
* @param key The key to check
* @return true if the key is available, false if it is unknown
*/
bool knowsKey(Qt::Key key) const;
/**
* Get a list of known keys.
*
* @return A list of known keys of which states will be reported.
*/
const QList<Qt::Key> knownKeys() const;
/**
* Synchronously check if a key is pressed.
*
* @param key The key to check
* @return true if the key is pressed, false if the key is not pressed or unknown.
* @see isKeyLatched, @see isKeyLocked, @see keyPressed
*/
bool isKeyPressed(Qt::Key key) const;
/**
* Synchronously check if a key is latched.
*
* @param key The key to check
* @return true if the key is latched, false if the key is not latched or unknown.
* @see isKeyPressed, @see isKeyLocked, @see keyLatched
*/
bool isKeyLatched(Qt::Key key) const;
/**
* Set the latched state of a key.
*
* @param key The key to latch
* @param latched true to latch the key, false to unlatch it.
* @return false if the key is unknown. true doesn't guarantee you the
* operation worked.
*/
bool setKeyLatched(Qt::Key key, bool latched);
/**
* Synchronously check if a key is locked.
*
* @param key The key to check
* @return true if the key is locked, false if the key is not locked or unknown.
* @see isKeyPressed, @see isKeyLatched, @see keyLocked
*/
bool isKeyLocked(Qt::Key key) const;
/**
* Set the locked state of a key.
*
* @param key The key to lock
* @param latched true to lock the key, false to unlock it.
* @return false if the key is unknown. true doesn't guarantee you the
* operation worked.
*/
bool setKeyLocked(Qt::Key key, bool locked);
/**
* Synchronously check if a mouse button is pressed.
*
* @param button The mouse button to check
* @return true if the mouse button is pressed, false if the mouse button
* is not pressed or its state is unknown.
*/
bool isButtonPressed(Qt::MouseButton button) const;
Q_SIGNALS:
/**
* This signal is emitted whenever the pressed state of a key changes
* (key press or key release).
*
* @param key The key that changed state
* @param pressed true if the key is now pressed, false if is released.
*/
void keyPressed(Qt::Key key, bool pressed);
/**
* This signal is emitted whenever the latched state of a key changes.
*
* @param key The key that changed state
* @param latched true if the key is now latched, false if it isn't
*/
void keyLatched(Qt::Key key, bool latched);
/**
* This signal is emitted whenever the locked state of a key changes.
*
* @param key The key that changed state
* @param locked true if the key is now locked, false if it isn't
*/
void keyLocked(Qt::Key key, bool locked);
/**
* This signal is emitted whenever the pressed state of a mouse button
* changes (mouse button press or release).
*
* @param button The mouse button that changed state
* @param pressed true if the mouse button is now pressed, false if
* is released.
*/
void buttonPressed(Qt::MouseButton button, bool pressed);
/**
* This signal is emitted whenever a new modifier is found due to
* the keyboard mapping changing.
*
* @param key The key that was discovered
*/
void keyAdded(Qt::Key key);
/**
* This signal is emitted whenever a previously known modifier no
* longer exists due to the keyboard mapping changing.
*
* @param key The key that vanished
*/
void keyRemoved(Qt::Key key);
private:
Q_DISABLE_COPY(KModifierKeyInfo)
QExplicitlySharedDataPointer<KModifierKeyInfoProvider> const p; // krazy:exclude=dpointer
};
#endif
@@ -0,0 +1,95 @@
/*
SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kmodifierkeyinfoprovider_p.h"
KModifierKeyInfoProvider::KModifierKeyInfoProvider()
: QObject(nullptr)
{
}
KModifierKeyInfoProvider::~KModifierKeyInfoProvider()
{
}
bool KModifierKeyInfoProvider::setKeyLatched(Qt::Key key, bool latched)
{
Q_UNUSED(key);
Q_UNUSED(latched);
return false;
}
bool KModifierKeyInfoProvider::setKeyLocked(Qt::Key key, bool locked)
{
Q_UNUSED(key);
Q_UNUSED(locked);
return false;
}
bool KModifierKeyInfoProvider::isKeyPressed(Qt::Key key) const
{
auto it = m_modifierStates.constFind(key);
if (it != m_modifierStates.constEnd()) {
return *it & Pressed;
}
return false;
}
bool KModifierKeyInfoProvider::isKeyLatched(Qt::Key key) const
{
auto it = m_modifierStates.constFind(key);
if (it != m_modifierStates.constEnd()) {
return *it & Latched;
}
return false;
}
bool KModifierKeyInfoProvider::isKeyLocked(Qt::Key key) const
{
auto it = m_modifierStates.constFind(key);
if (it != m_modifierStates.constEnd()) {
return *it & Locked;
}
return false;
}
bool KModifierKeyInfoProvider::isButtonPressed(Qt::MouseButton button) const
{
if (m_buttonStates.contains(button)) {
return m_buttonStates[button];
}
return false;
}
bool KModifierKeyInfoProvider::knowsKey(Qt::Key key) const
{
return m_modifierStates.contains(key);
}
const QList<Qt::Key> KModifierKeyInfoProvider::knownKeys() const
{
return m_modifierStates.keys();
}
void KModifierKeyInfoProvider::stateUpdated(Qt::Key key, KModifierKeyInfoProvider::ModifierStates newState)
{
auto &state = m_modifierStates[key];
if (newState != state) {
const auto difference = (newState ^ state);
state = newState;
if (difference & Pressed) {
Q_EMIT keyPressed(key, newState & Pressed);
}
if (difference & Latched) {
Q_EMIT keyLatched(key, newState & Latched);
}
if (difference & Locked) {
Q_EMIT keyLocked(key, newState & Locked);
}
}
}
#include "moc_kmodifierkeyinfoprovider_p.cpp"
@@ -0,0 +1,116 @@
/*
SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KMODIFIERKEYINFOPROVIDER_P_H
#define KMODIFIERKEYINFOPROVIDER_P_H
#include "kguiaddons_export.h"
#include <QHash>
#include <QObject>
#include <QSharedData>
/**
* Background class that implements the behaviour of KModifierKeyInfo for
* the different supported platforms.
* @internal
*/
class KGUIADDONS_EXPORT KModifierKeyInfoProvider : public QObject, public QSharedData
{
Q_OBJECT
public:
enum ModifierState {
Nothing = 0x0,
Pressed = 0x1,
Latched = 0x2,
Locked = 0x4,
};
Q_ENUM(ModifierState)
Q_DECLARE_FLAGS(ModifierStates, ModifierState)
KModifierKeyInfoProvider();
~KModifierKeyInfoProvider() override;
/**
* Detect if a key is pressed.
* @param key Modifier key to query
* @return true if the key is pressed, false if it isn't.
*/
bool isKeyPressed(Qt::Key key) const;
/**
* Detect if a key is latched.
* @param key Modifier key to query
* @return true if the key is latched, false if it isn't.
*/
bool isKeyLatched(Qt::Key key) const;
/**
* Set the latched state of a key.
* @param key Modifier to set the latched state for
* @param latched true to latch the key, false to unlatch it
* @return true if the key is known, false else
*/
virtual bool setKeyLatched(Qt::Key key, bool latched);
/**
* Detect if a key is locked.
* @param key Modifier key to query
* @return true if the key is locked, false if it isn't.
*/
bool isKeyLocked(Qt::Key key) const;
/**
* Set the locked state of a key.
* @param key Modifier to set the locked state for
* @param latched true to lock the key, false to unlock it
* @return true if the key is known, false else
*/
virtual bool setKeyLocked(Qt::Key key, bool locked);
/**
* Check if a mouse button is pressed.
* @param button Mouse button to check
* @return true if pressed, false else
*/
bool isButtonPressed(Qt::MouseButton button) const;
/**
* Check if a key is known/can be queried
* @param key Modifier key to check
* @return true if the key is known, false if it isn't.
*/
bool knowsKey(Qt::Key key) const;
/**
* Get a list of known keys
* @return List of known keys.
*/
const QList<Qt::Key> knownKeys() const;
Q_SIGNALS:
void keyLatched(Qt::Key key, bool state);
void keyLocked(Qt::Key key, bool state);
void keyPressed(Qt::Key key, bool state);
void buttonPressed(Qt::MouseButton button, bool state);
void keyAdded(Qt::Key key);
void keyRemoved(Qt::Key key);
protected:
void stateUpdated(Qt::Key key, KModifierKeyInfoProvider::ModifierStates state);
// the state of each known modifier
QHash<Qt::Key, ModifierStates> m_modifierStates;
// the state of each known mouse button
QHash<Qt::MouseButton, bool> m_buttonStates;
};
Q_DECLARE_INTERFACE(KModifierKeyInfoProvider, "org.kde.kguiaddons.KModifierKeyInfoProvider")
Q_DECLARE_OPERATORS_FOR_FLAGS(KModifierKeyInfoProvider::ModifierStates)
#endif
@@ -0,0 +1,123 @@
/*
SPDX-FileCopyrightText: 2019 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kmodifierkeyinfoprovider_wayland.h"
#include <QDebug>
#include <QGuiApplication>
#include <QWaylandClientExtensionTemplate>
#include <wayland-client-core.h>
#include "qwayland-keystate.h"
class KeyState : public QWaylandClientExtensionTemplate<KeyState>, public QtWayland::org_kde_kwin_keystate
{
Q_OBJECT
public:
KeyState()
: QWaylandClientExtensionTemplate<KeyState>(5)
{
}
~KeyState()
{
if (isInitialized() && qGuiApp) {
if (QtWayland::org_kde_kwin_keystate::version() >= ORG_KDE_KWIN_KEYSTATE_DESTROY_SINCE_VERSION) {
destroy();
} else {
wl_proxy_destroy(reinterpret_cast<struct wl_proxy *>(object()));
}
}
}
void org_kde_kwin_keystate_stateChanged(uint32_t key, uint32_t state) override
{
Q_EMIT stateChanged(toKey(static_cast<KeyState::key>(key)), toState(static_cast<KeyState::state>(state)));
}
KModifierKeyInfoProvider::ModifierState toState(KeyState::state state)
{
switch (state) {
case KeyState::state::state_unlocked:
return KModifierKeyInfoProvider::Nothing;
case KeyState::state::state_locked:
return KModifierKeyInfoProvider::Locked;
case KeyState::state::state_latched:
return KModifierKeyInfoProvider::Latched;
case KeyState::state::state_pressed:
return KModifierKeyInfoProvider::Pressed;
}
Q_UNREACHABLE();
return KModifierKeyInfoProvider::Nothing;
}
Qt::Key toKey(KeyState::key key)
{
switch (key) {
case KeyState::key::key_capslock:
return Qt::Key_CapsLock;
case KeyState::key::key_numlock:
return Qt::Key_NumLock;
case KeyState::key::key_scrolllock:
return Qt::Key_ScrollLock;
case KeyState::key_alt:
return Qt::Key_Alt;
case KeyState::key_shift:
return Qt::Key_Shift;
case KeyState::key_control:
return Qt::Key_Control;
case KeyState::key_meta:
return Qt::Key_Meta;
case KeyState::key_altgr:
return Qt::Key_AltGr;
}
Q_UNREACHABLE();
return {};
}
Q_SIGNAL void stateChanged(Qt::Key key, KModifierKeyInfoProvider::ModifierState state);
};
KModifierKeyInfoProviderWayland::KModifierKeyInfoProviderWayland()
{
m_keystate = new KeyState;
QObject::connect(m_keystate, &KeyState::activeChanged, this, [this]() {
if (m_keystate->isActive()) {
m_keystate->fetchStates();
}
});
connect(m_keystate, &KeyState::stateChanged, this, &KModifierKeyInfoProviderWayland::stateUpdated);
stateUpdated(Qt::Key_CapsLock, KModifierKeyInfoProvider::Nothing);
stateUpdated(Qt::Key_NumLock, KModifierKeyInfoProvider::Nothing);
stateUpdated(Qt::Key_ScrollLock, KModifierKeyInfoProvider::Nothing);
stateUpdated(Qt::Key_Alt, KModifierKeyInfoProvider::Nothing);
stateUpdated(Qt::Key_Shift, KModifierKeyInfoProvider::Nothing);
stateUpdated(Qt::Key_Control, KModifierKeyInfoProvider::Nothing);
stateUpdated(Qt::Key_Meta, KModifierKeyInfoProvider::Nothing);
stateUpdated(Qt::Key_AltGr, KModifierKeyInfoProvider::Nothing);
}
KModifierKeyInfoProviderWayland::~KModifierKeyInfoProviderWayland()
{
delete m_keystate;
}
bool KModifierKeyInfoProviderWayland::setKeyLatched(Qt::Key /*key*/, bool /*latched*/)
{
return false;
}
bool KModifierKeyInfoProviderWayland::setKeyLocked(Qt::Key /*key*/, bool /*locked*/)
{
return false;
}
#include "kmodifierkeyinfoprovider_wayland.moc"
#include "moc_kmodifierkeyinfoprovider_wayland.cpp"
@@ -0,0 +1,29 @@
/*
SPDX-FileCopyrightText: 2019 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-FileCopyrightText: 2022 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KMODIFIERKEYINFOPROVIDERWAYLAND_H
#define KMODIFIERKEYINFOPROVIDERWAYLAND_H
#include <kmodifierkeyinfoprovider_p.h>
class KeyState;
class KModifierKeyInfoProviderWayland : public KModifierKeyInfoProvider
{
Q_OBJECT
public:
KModifierKeyInfoProviderWayland();
~KModifierKeyInfoProviderWayland();
bool setKeyLatched(Qt::Key key, bool latched) override;
bool setKeyLocked(Qt::Key key, bool locked) override;
private:
KeyState *m_keystate;
};
#endif
@@ -0,0 +1,345 @@
/*
SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kmodifierkeyinfoprovider_xcb.h"
#include "kmodifierkeyinfo.h"
#include <QGuiApplication>
#define XK_MISCELLANY
#define XK_XKB_KEYS
#include <X11/XKBlib.h>
#include <X11/keysymdef.h>
#include <xcb/xcb.h>
struct ModifierDefinition {
ModifierDefinition(Qt::Key _key, unsigned int _mask, const char *_name, KeySym _keysym)
: key(_key)
, mask(_mask)
, name(_name)
, keysym(_keysym)
{
}
Qt::Key key;
unsigned int mask;
const char *name; // virtual modifier name
KeySym keysym;
};
/*
* Get the real modifiers related to a virtual modifier.
*/
unsigned int xkbVirtualModifier(XkbDescPtr xkb, const char *name)
{
Q_ASSERT(xkb != nullptr);
unsigned int mask = 0;
bool nameEqual;
for (int i = 0; i < XkbNumVirtualMods; ++i) {
char *modStr = XGetAtomName(xkb->dpy, xkb->names->vmods[i]);
if (modStr != nullptr) {
nameEqual = (strcmp(name, modStr) == 0);
XFree(modStr);
if (nameEqual) {
XkbVirtualModsToReal(xkb, 1 << i, &mask);
break;
}
}
}
return mask;
}
static Display *display()
{
return qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->display();
}
KModifierKeyInfoProviderXcb::KModifierKeyInfoProviderXcb()
: KModifierKeyInfoProvider()
, m_xkbEv(0)
, m_xkbAvailable(false)
{
if (qGuiApp) {
if (qGuiApp->platformName() == QLatin1String("xcb")) {
int code;
int xkberr;
int maj;
int min;
m_xkbAvailable = XkbQueryExtension(display(), &code, &m_xkbEv, &xkberr, &maj, &min);
}
}
if (m_xkbAvailable) {
/* clang-format off */
XkbSelectEvents(display(),
XkbUseCoreKbd,
XkbStateNotifyMask | XkbMapNotifyMask,
XkbStateNotifyMask | XkbMapNotifyMask);
unsigned long int stateMask = XkbModifierStateMask
| XkbModifierBaseMask
| XkbModifierLatchMask
| XkbModifierLockMask
| XkbPointerButtonMask;
/* clang-format on */
XkbSelectEventDetails(display(), XkbUseCoreKbd, XkbStateNotifyMask, stateMask, stateMask);
}
xkbUpdateModifierMapping();
// add known pointer buttons
m_xkbButtons.insert(Qt::LeftButton, Button1Mask);
m_xkbButtons.insert(Qt::MiddleButton, Button2Mask);
m_xkbButtons.insert(Qt::RightButton, Button3Mask);
m_xkbButtons.insert(Qt::XButton1, Button4Mask);
m_xkbButtons.insert(Qt::XButton2, Button5Mask);
// get the initial state
if (m_xkbAvailable) {
XkbStateRec state;
XkbGetState(display(), XkbUseCoreKbd, &state);
xkbModifierStateChanged(state.mods, state.latched_mods, state.locked_mods);
xkbButtonStateChanged(state.ptr_buttons);
QCoreApplication::instance()->installNativeEventFilter(this);
}
}
KModifierKeyInfoProviderXcb::~KModifierKeyInfoProviderXcb()
{
if (m_xkbAvailable) {
QCoreApplication::instance()->removeNativeEventFilter(this);
}
}
bool KModifierKeyInfoProviderXcb::setKeyLatched(Qt::Key key, bool latched)
{
if (!m_xkbModifiers.contains(key)) {
return false;
}
return XkbLatchModifiers(display(), XkbUseCoreKbd, m_xkbModifiers[key], latched ? m_xkbModifiers[key] : 0);
}
bool KModifierKeyInfoProviderXcb::setKeyLocked(Qt::Key key, bool locked)
{
if (!m_xkbModifiers.contains(key)) {
return false;
}
return XkbLockModifiers(display(), XkbUseCoreKbd, m_xkbModifiers[key], locked ? m_xkbModifiers[key] : 0);
}
// HACK: xcb-xkb is not yet a public part of xcb. Because of that we have to include the event structure.
namespace
{
typedef struct _xcb_xkb_map_notify_event_t {
uint8_t response_type;
uint8_t xkbType;
uint16_t sequence;
xcb_timestamp_t time;
uint8_t deviceID;
uint8_t ptrBtnActions;
uint16_t changed;
xcb_keycode_t minKeyCode;
xcb_keycode_t maxKeyCode;
uint8_t firstType;
uint8_t nTypes;
xcb_keycode_t firstKeySym;
uint8_t nKeySyms;
xcb_keycode_t firstKeyAct;
uint8_t nKeyActs;
xcb_keycode_t firstKeyBehavior;
uint8_t nKeyBehavior;
xcb_keycode_t firstKeyExplicit;
uint8_t nKeyExplicit;
xcb_keycode_t firstModMapKey;
uint8_t nModMapKeys;
xcb_keycode_t firstVModMapKey;
uint8_t nVModMapKeys;
uint16_t virtualMods;
uint8_t pad0[2];
} _xcb_xkb_map_notify_event_t;
typedef struct _xcb_xkb_state_notify_event_t {
uint8_t response_type;
uint8_t xkbType;
uint16_t sequence;
xcb_timestamp_t time;
uint8_t deviceID;
uint8_t mods;
uint8_t baseMods;
uint8_t latchedMods;
uint8_t lockedMods;
uint8_t group;
int16_t baseGroup;
int16_t latchedGroup;
uint8_t lockedGroup;
uint8_t compatState;
uint8_t grabMods;
uint8_t compatGrabMods;
uint8_t lookupMods;
uint8_t compatLoockupMods;
uint16_t ptrBtnState;
uint16_t changed;
xcb_keycode_t keycode;
uint8_t eventType;
uint8_t requestMajor;
uint8_t requestMinor;
} _xcb_xkb_state_notify_event_t;
typedef union {
/* All XKB events share these fields. */
struct {
uint8_t response_type;
uint8_t xkbType;
uint16_t sequence;
xcb_timestamp_t time;
uint8_t deviceID;
} any;
_xcb_xkb_map_notify_event_t map_notify;
_xcb_xkb_state_notify_event_t state_notify;
} _xkb_event;
}
bool KModifierKeyInfoProviderXcb::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *)
{
if (!m_xkbAvailable || eventType != "xcb_generic_event_t") {
return false;
}
xcb_generic_event_t *event = static_cast<xcb_generic_event_t *>(message);
if ((event->response_type & ~0x80) == m_xkbEv + XkbEventCode) {
_xkb_event *kbevt = reinterpret_cast<_xkb_event *>(event);
unsigned int stateMask = XkbModifierStateMask | XkbModifierBaseMask | XkbModifierLatchMask | XkbModifierLockMask;
if (kbevt->any.xkbType == XkbMapNotify) {
xkbUpdateModifierMapping();
} else if (kbevt->any.xkbType == XkbStateNotify) {
if (kbevt->state_notify.changed & stateMask) {
xkbModifierStateChanged(kbevt->state_notify.mods, kbevt->state_notify.latchedMods, kbevt->state_notify.lockedMods);
} else if (kbevt->state_notify.changed & XkbPointerButtonMask) {
xkbButtonStateChanged(kbevt->state_notify.ptrBtnState);
}
}
}
return false;
}
void KModifierKeyInfoProviderXcb::xkbModifierStateChanged(unsigned char mods, unsigned char latched_mods, unsigned char locked_mods)
{
// detect keyboard modifiers
ModifierStates newState;
QHash<Qt::Key, unsigned int>::const_iterator it;
QHash<Qt::Key, unsigned int>::const_iterator end = m_xkbModifiers.constEnd();
for (it = m_xkbModifiers.constBegin(); it != end; ++it) {
if (!m_modifierStates.contains(it.key())) {
continue;
}
newState = Nothing;
// determine the new state
if (mods & it.value()) {
newState |= Pressed;
}
if (latched_mods & it.value()) {
newState |= Latched;
}
if (locked_mods & it.value()) {
newState |= Locked;
}
stateUpdated(it.key(), newState);
}
}
void KModifierKeyInfoProviderXcb::xkbButtonStateChanged(unsigned short ptr_buttons)
{
// detect mouse button states
bool newButtonState;
QHash<Qt::MouseButton, unsigned short>::const_iterator it;
QHash<Qt::MouseButton, unsigned short>::const_iterator end = m_xkbButtons.constEnd();
for (it = m_xkbButtons.constBegin(); it != end; ++it) {
newButtonState = (ptr_buttons & it.value());
if (newButtonState != m_buttonStates[it.key()]) {
m_buttonStates[it.key()] = newButtonState;
Q_EMIT buttonPressed(it.key(), newButtonState);
}
}
}
void KModifierKeyInfoProviderXcb::xkbUpdateModifierMapping()
{
if (!m_xkbAvailable) {
return;
}
m_xkbModifiers.clear();
QList<ModifierDefinition> srcModifiers;
/* clang-format off */
srcModifiers << ModifierDefinition(Qt::Key_Shift, ShiftMask, nullptr, 0)
<< ModifierDefinition(Qt::Key_Control, ControlMask, nullptr, 0)
<< ModifierDefinition(Qt::Key_Alt, 0, "Alt", XK_Alt_L)
// << { 0, 0, I18N_NOOP("Win"), "superkey", "" }
<< ModifierDefinition(Qt::Key_Meta, 0, "Meta", XK_Meta_L)
<< ModifierDefinition(Qt::Key_Super_L, 0, "Super", XK_Super_L)
<< ModifierDefinition(Qt::Key_Hyper_L, 0, "Hyper", XK_Hyper_L)
<< ModifierDefinition(Qt::Key_AltGr, 0, "AltGr", 0)
<< ModifierDefinition(Qt::Key_NumLock, 0, "NumLock", XK_Num_Lock)
<< ModifierDefinition(Qt::Key_CapsLock, LockMask, nullptr, 0)
<< ModifierDefinition(Qt::Key_ScrollLock, 0, "ScrollLock", XK_Scroll_Lock);
/* clang-format on */
XkbDescPtr xkb = XkbGetKeyboard(display(), XkbAllComponentsMask, XkbUseCoreKbd);
QList<ModifierDefinition>::const_iterator it;
QList<ModifierDefinition>::const_iterator end = srcModifiers.constEnd();
for (it = srcModifiers.constBegin(); it != end; ++it) {
unsigned int mask = it->mask;
if (mask == 0 && xkb != nullptr) {
// try virtual modifier first
if (it->name != nullptr) {
mask = xkbVirtualModifier(xkb, it->name);
}
if (mask == 0 && it->keysym != 0) {
mask = XkbKeysymToModifiers(display(), it->keysym);
} else if (mask == 0) {
// special case for AltGr
/* clang-format off */
mask = XkbKeysymToModifiers(display(), XK_Mode_switch)
| XkbKeysymToModifiers(display(), XK_ISO_Level3_Shift)
| XkbKeysymToModifiers(display(), XK_ISO_Level3_Latch)
| XkbKeysymToModifiers(display(), XK_ISO_Level3_Lock);
/* clang-format on */
}
}
if (mask != 0) {
m_xkbModifiers.insert(it->key, mask);
// previously unknown modifier
if (!m_modifierStates.contains(it->key)) {
m_modifierStates.insert(it->key, Nothing);
Q_EMIT keyAdded(it->key);
}
}
}
// remove modifiers which are no longer available
QMutableHashIterator<Qt::Key, ModifierStates> i(m_modifierStates);
while (i.hasNext()) {
i.next();
if (!m_xkbModifiers.contains(i.key())) {
Qt::Key key = i.key();
i.remove();
Q_EMIT keyRemoved(key);
}
}
if (xkb != nullptr) {
XkbFreeKeyboard(xkb, 0, true);
}
}
#include "moc_kmodifierkeyinfoprovider_xcb.cpp"
@@ -0,0 +1,40 @@
/*
SPDX-FileCopyrightText: 2009 Michael Leupold <lemma@confuego.org>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KMODIFIERKEYINFOPROVIDERXCB_H
#define KMODIFIERKEYINFOPROVIDERXCB_H
#include "kmodifierkeyinfoprovider_p.h"
#include <QAbstractNativeEventFilter>
class KModifierKeyInfoProviderXcb : public KModifierKeyInfoProvider, public QAbstractNativeEventFilter
{
Q_OBJECT
public:
KModifierKeyInfoProviderXcb();
~KModifierKeyInfoProviderXcb() override;
bool setKeyLatched(Qt::Key key, bool latched) override;
bool setKeyLocked(Qt::Key key, bool locked) override;
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) override;
void xkbUpdateModifierMapping();
void xkbModifierStateChanged(unsigned char mods, unsigned char latched_mods, unsigned char locked_mods);
void xkbButtonStateChanged(unsigned short ptr_buttons);
private:
int m_xkbEv;
bool m_xkbAvailable;
// maps a Qt::Key to a modifier mask
QHash<Qt::Key, unsigned int> m_xkbModifiers;
// maps a Qt::MouseButton to a button mask
QHash<Qt::MouseButton, unsigned short> m_xkbButtons;
};
#endif
@@ -0,0 +1,129 @@
/*
SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kurlhandler_p.h"
#include <kguiaddons_debug.h>
#include <QCoreApplication>
#include <QDebug>
#include <QDesktopServices>
#include <QLocale>
#include <QProcess>
#include <QStandardPaths>
#include <QUrl>
static const char s_khelpcenter_exec[] = "khelpcenter";
static bool openWithKHelpCenter(const QUrl &url)
{
const QString helpcenter = QStandardPaths::findExecutable(QString::fromLatin1(s_khelpcenter_exec));
if (!helpcenter.isEmpty()) {
QUrl u(url);
if (u.path() == QLatin1Char('/')) {
const QString appName = QCoreApplication::applicationName();
u.setPath(appName);
}
QProcess::startDetached(helpcenter, QStringList(u.toString()));
return true;
}
return false;
}
KUrlHandler::KUrlHandler(QObject *parent)
: QObject(parent)
{
}
void KUrlHandler::openHelp(const QUrl &url) const
{
if (openWithKHelpCenter(url)) {
return;
}
const QUrl docUrl = concatDocsUrl(url);
if (docUrl.isValid()) {
QDesktopServices::openUrl(docUrl);
} else {
qCWarning(KGUIADDONS_LOG) << "Could not find a suitable handler for" << url.toString();
}
}
QUrl KUrlHandler::concatDocsUrl(const QUrl &url) const
{
if (QCoreApplication::organizationDomain() != QLatin1String("kde.org")) {
return {};
}
// KHelpCenter is not available and it's a KDE application, open the docs at docs.kde.org
// with the default web browser on the system
QString path = url.path();
const QString fragment = url.fragment();
const QString common = QLatin1String("https://docs.kde.org/index.php?branch=stable5&language=") + QLocale().name();
const QString appName = QCoreApplication::applicationName();
// Special case for KCModules
if (appName == QLatin1String("systemsettings") && path.startsWith(QLatin1String("/kcontrol"))) {
// E.g. change "/kcontrol/fonts/index.html" to "&application=kcontrol/fonts&path=index.html"
// docs.kde.org will resolve the url and add the proper package name, e.g. plasma-workspace:
// https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/fonts/index.html
QString kcmAppName(path);
kcmAppName.remove(0, 1); // Remove leading "/"
const int idx = kcmAppName.indexOf(QLatin1String("/index.html"));
if (idx > 0) {
kcmAppName.truncate(idx);
}
// Some KCModules have a valid fragment, e.g. kcontrol/powerdevil/index.html#advanced-settings
const QString tail = QLatin1String("index.html") + (!fragment.isEmpty() ? QLatin1Char('#') + fragment : QString{});
return QUrl(common + QLatin1String("&application=") + kcmAppName + QLatin1String("&path=") + tail);
}
// E.g. "help:/" and appName is "okular", e.g. opening Help -> Okular HandBook
if (path == QLatin1Char('/')) {
return QUrl(common + QLatin1String("&application=") + appName + QLatin1String("&path=") + QLatin1String("index.html"));
}
// E.g. "help:/okular/configure.html", don't repeat "appName"; e.g. clicking Help button in
// the "Settings -> Configure Okular" dialog
const QString redundant = QLatin1Char('/') + appName + QLatin1Char('/');
if (path.startsWith(redundant)) {
path.remove(0, redundant.size());
if (!fragment.isEmpty()) {
// E.g. "help:/kinfocenter/index.html#kcm_memory", it's actually "kinfocenter/kcm_memory.html"
if (path == QLatin1String("index.html")) {
qCWarning(KGUIADDONS_LOG) << "X-DocPath entry in a .desktop file in" << appName << "is:" << appName + QLatin1String("/index.html#") + fragment
<< ", however it should be:" << appName + QLatin1Char('/') + fragment + QLatin1String(".html");
path = fragment + QLatin1String(".html");
} else {
// E.g. "help:/okular/signatures.html#adding_digital_signatures"
path += QLatin1Char('#') + fragment;
}
}
return QUrl(common + QLatin1String("&application=") + appName + QLatin1String("&path=") + path);
}
return {};
}
Q_GLOBAL_STATIC(KUrlHandler, s_handler)
static void initializeGlobalSettings()
{
QDesktopServices::setUrlHandler(QStringLiteral("help"), s_handler, "openHelp");
}
Q_COREAPP_STARTUP_FUNCTION(initializeGlobalSettings)
#include "moc_kurlhandler_p.cpp"
@@ -0,0 +1,29 @@
/*
SPDX-FileCopyrightText: 2021 Ahmad Samir <a.samirh78@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KURLHANDLER_P_H
#define KURLHANDLER_P_H
#include <kguiaddons_export.h>
#include <QObject>
class QUrl;
class KGUIADDONS_EXPORT KUrlHandler : public QObject
{
Q_OBJECT
public:
explicit KUrlHandler(QObject *parent = nullptr);
QUrl concatDocsUrl(const QUrl &url) const;
public Q_SLOTS:
void openHelp(const QUrl &url) const;
};
#endif // KURLHANDLER_P_H