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:
@@ -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
|
||||
)
|
||||
+14
@@ -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"
|
||||
}
|
||||
}
|
||||
+76
@@ -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}
|
||||
)
|
||||
+50
@@ -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
|
||||
+2
@@ -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;
|
||||
}
|
||||
+49
@@ -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
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
+49
@@ -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
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
SPDX-FileCopyrightText: 2022 Florian Edelmann <florian-edelmann@online.de>
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
+49
@@ -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
|
||||
+2
@@ -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
|
||||
+143
@@ -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
|
||||
+278
@@ -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 ®ion)
|
||||
{
|
||||
// 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 ®ionOrCountry)
|
||||
: 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 ®ionOrCountry);
|
||||
~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
|
||||
+123
@@ -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
|
||||
Reference in New Issue
Block a user