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,167 @@
|
||||
add_library(KF6Notifications)
|
||||
add_library(KF6::Notifications ALIAS KF6Notifications)
|
||||
|
||||
qt_extract_metatypes(KF6Notifications)
|
||||
|
||||
set_target_properties(KF6Notifications PROPERTIES
|
||||
VERSION ${KNOTIFICATIONS_VERSION}
|
||||
SOVERSION ${KNOTIFICATIONS_SOVERSION}
|
||||
EXPORT_NAME Notifications
|
||||
)
|
||||
|
||||
ecm_create_qm_loader(KF6Notifications knotifications6_qt)
|
||||
|
||||
target_sources(KF6Notifications PRIVATE
|
||||
knotification.cpp
|
||||
knotificationreplyaction.cpp
|
||||
knotificationmanager.cpp
|
||||
knotificationpermission.cpp
|
||||
|
||||
knotifyconfig.cpp
|
||||
knotificationplugin.cpp
|
||||
)
|
||||
|
||||
if (HAVE_DBUS)
|
||||
target_sources(KF6Notifications PRIVATE
|
||||
imageconverter.cpp #needed to marshal images for sending over dbus by NotifyByPopup
|
||||
notifybypopup.cpp
|
||||
notifybyportal.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
add_subdirectory(android)
|
||||
target_sources(KF6Notifications PRIVATE
|
||||
notifybyandroid.cpp
|
||||
knotifications.qrc
|
||||
)
|
||||
endif()
|
||||
|
||||
if (WITH_SNORETOAST)
|
||||
target_sources(KF6Notifications PRIVATE notifybysnore.cpp)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
target_sources(KF6Notifications PRIVATE notifybymacosnotificationcenter.mm)
|
||||
endif()
|
||||
|
||||
ecm_qt_declare_logging_category(KF6Notifications
|
||||
HEADER debug_p.h
|
||||
IDENTIFIER LOG_KNOTIFICATIONS
|
||||
CATEGORY_NAME kf.notifications
|
||||
OLD_CATEGORY_NAMES org.kde.knotifications
|
||||
DESCRIPTION "KNotifications"
|
||||
EXPORT KNOTIFICATIONS
|
||||
)
|
||||
|
||||
if (TARGET Canberra::Canberra)
|
||||
target_sources(KF6Notifications PRIVATE
|
||||
notifybyaudio.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (HAVE_DBUS)
|
||||
set(notifications_xml org.freedesktop.Notifications.xml)
|
||||
qt_add_dbus_interface(knotifications_dbus_SRCS ${notifications_xml} notifications_interface)
|
||||
target_sources(KF6Notifications PRIVATE ${knotifications_dbus_SRCS})
|
||||
endif()
|
||||
|
||||
configure_file(config-knotifications.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-knotifications.h )
|
||||
|
||||
ecm_generate_export_header(KF6Notifications
|
||||
EXPORT_FILE_NAME knotifications_export.h
|
||||
BASE_NAME KNotifications
|
||||
GROUP_BASE_NAME KF
|
||||
VERSION ${KF_VERSION}
|
||||
USE_VERSION_HEADER
|
||||
DEPRECATED_BASE_VERSION 0
|
||||
DEPRECATION_VERSIONS
|
||||
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
|
||||
)
|
||||
|
||||
target_include_directories(KF6Notifications INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KNotifications>")
|
||||
|
||||
target_link_libraries(KF6Notifications PUBLIC Qt6::Gui)
|
||||
if (HAVE_DBUS)
|
||||
target_link_libraries(KF6Notifications PUBLIC Qt6::DBus)
|
||||
endif()
|
||||
target_link_libraries(KF6Notifications PRIVATE
|
||||
KF6::ConfigCore
|
||||
)
|
||||
if (ANDROID)
|
||||
target_link_libraries(KF6Notifications PRIVATE Qt::CorePrivate)
|
||||
endif()
|
||||
|
||||
if (TARGET SnoreToast::SnoreToastActions)
|
||||
target_link_libraries(KF6Notifications PRIVATE Qt6::Network SnoreToast::SnoreToastActions)
|
||||
endif ()
|
||||
|
||||
if (TARGET Canberra::Canberra)
|
||||
target_link_libraries(KF6Notifications PRIVATE Canberra::Canberra)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(KF6Notifications PRIVATE "-framework Foundation" "-framework AppKit")
|
||||
endif()
|
||||
|
||||
ecm_generate_headers(KNotifications_HEADERS
|
||||
HEADER_NAMES
|
||||
KNotification
|
||||
KNotificationPermission
|
||||
KNotificationReplyAction
|
||||
KNotifyConfig
|
||||
|
||||
REQUIRED_HEADERS KNotifications_HEADERS
|
||||
)
|
||||
|
||||
install(TARGETS KF6Notifications EXPORT KF6NotificationsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/knotifications_export.h
|
||||
${KNotifications_HEADERS}
|
||||
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KNotifications COMPONENT Devel
|
||||
)
|
||||
|
||||
ecm_qt_install_logging_categories(
|
||||
EXPORT KNOTIFICATIONS
|
||||
FILE knotifications.categories
|
||||
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
|
||||
)
|
||||
|
||||
if(BUILD_QCH)
|
||||
ecm_add_qch(
|
||||
KF6Notifications_QCH
|
||||
NAME KNotifications
|
||||
BASE_NAME KF6Notifications
|
||||
VERSION ${KF_VERSION}
|
||||
ORG_DOMAIN org.kde
|
||||
SOURCES # using only public headers, to cover only public API
|
||||
${KNotifications_HEADERS}
|
||||
"${CMAKE_SOURCE_DIR}/Mainpage.dox"
|
||||
IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics"
|
||||
LINK_QCHS
|
||||
Qt6Gui_QCH
|
||||
INCLUDE_DIRS
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
BLANK_MACROS
|
||||
KNOTIFICATIONS_EXPORT
|
||||
KNOTIFICATIONS_DEPRECATED
|
||||
KNOTIFICATIONS_DEPRECATED_EXPORT
|
||||
"KNOTIFICATIONS_DEPRECATED_VERSION(x, y, t)"
|
||||
"KNOTIFICATIONS_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
|
||||
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
COMPONENT Devel
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
install(FILES KF6Notifications-android-dependencies.xml
|
||||
DESTINATION ${KDE_INSTALL_LIBDIR}
|
||||
RENAME KF6Notifications_${CMAKE_ANDROID_ARCH_ABI}-android-dependencies.xml
|
||||
)
|
||||
endif()
|
||||
|
||||
if (TARGET Qt6::Qml)
|
||||
# add_subdirectory(qml)
|
||||
endif()
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<rules>
|
||||
<dependencies>
|
||||
<lib name="KF6Notifications">
|
||||
<depends>
|
||||
<jar bundling="1" file="jar/KF6Notifications.aar"/>
|
||||
</depends>
|
||||
</lib>
|
||||
</dependencies>
|
||||
</rules>
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Extract strings from all source files.
|
||||
# EXTRACT_TR_STRINGS extracts strings with lupdate and convert them to .pot with
|
||||
# lconvert.
|
||||
$EXTRACT_TR_STRINGS `find . -name \*.cpp -o -name \*.h -o -name \*.ui -o -name \*.qml` -o $podir/knotifications6_qt.pot
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.kde.knotifications">
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,2 @@
|
||||
gradle_add_aar(knotifications_aar BUILDFILE ${CMAKE_CURRENT_SOURCE_DIR}/build.gradle NAME KF6Notifications)
|
||||
gradle_install_aar(knotifications_aar DESTINATION jar)
|
||||
@@ -0,0 +1,39 @@
|
||||
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.knotifications"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package org.kde.knotifications;
|
||||
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
|
||||
import java.lang.Object;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
/** Java side of KNotification.
|
||||
* Used to convey the relevant notification data to Java.
|
||||
*/
|
||||
public class KNotification
|
||||
{
|
||||
public int id;
|
||||
public String text;
|
||||
public String richText;
|
||||
public String title;
|
||||
public Object icon;
|
||||
public HashMap<String, String> actions = new HashMap<>();
|
||||
public String channelId;
|
||||
public String channelName;
|
||||
public String channelDescription;
|
||||
public String group;
|
||||
public int urgency;
|
||||
public String visibility;
|
||||
|
||||
public String inlineReplyLabel;
|
||||
public String inlineReplyPlaceholder;
|
||||
|
||||
// see knotification.h
|
||||
public static final int LowUrgency = 10;
|
||||
public static final int NormalUrgency = 50;
|
||||
public static final int HighUrgency = 70;
|
||||
public static final int CriticalUrgency = 90;
|
||||
|
||||
public void setIconFromData(byte[] data, int length)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
icon = Icon.createWithData(data, 0, length);
|
||||
}
|
||||
}
|
||||
|
||||
public void addAction(String id, String label)
|
||||
{
|
||||
actions.put(id, label);
|
||||
}
|
||||
}
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
package org.kde.knotifications;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.RemoteInput;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.util.Log;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
/** Java side of the Android notification backend. */
|
||||
public class NotifyByAndroid extends BroadcastReceiver
|
||||
{
|
||||
private static final String TAG = "org.kde.knotifications";
|
||||
|
||||
private static final String NOTIFICATION_ACTION = ".org.kde.knotifications.NOTIFICATION_ACTION";
|
||||
private static final String NOTIFICATION_DELETED = ".org.kde.knotifications.NOTIFICATION_DELETED";
|
||||
private static final String NOTIFICATION_OPENED = ".org.kde.knotifications.NOTIFICATION_OPENED";
|
||||
private static final String NOTIFICATION_REPLIED = ".org.kde.knotifications.NOTIFICATION_REPLIED";
|
||||
// the id of the notification triggering an intent
|
||||
private static final String NOTIFICATION_ID_EXTRA = "org.kde.knotifications.NOTIFICATION_ID";
|
||||
// the id of the action that was triggered for a notification
|
||||
private static final String NOTIFICATION_ACTION_ID_EXTRA = "org.kde.knotifications.NOTIFICATION_ACTION_ID";
|
||||
// the group a notification belongs too
|
||||
private static final String NOTIFICATION_GROUP_EXTRA = "org.kde.knotifications.NOTIFICATION_GROUP";
|
||||
// RemoteInput value key
|
||||
private static final String REMOTE_INPUT_KEY = "REPLY";
|
||||
|
||||
// notification id offset for group summary notifications
|
||||
// we need this to stay out of the regular notification's id space (which comes from the C++ side)
|
||||
// and so we can distinguish if we received actions on regular notifications or group summaries
|
||||
private static final int NOTIFICATION_GROUP_ID_FLAG = (1 << 24);
|
||||
|
||||
private android.content.Context m_ctx;
|
||||
private NotificationManager m_notificationManager;
|
||||
private int m_uniquePendingIntentId = 0;
|
||||
private HashSet<String> m_channels = new HashSet();
|
||||
|
||||
private class GroupData {
|
||||
public HashSet<Integer> childIds = new HashSet();
|
||||
public int groupId;
|
||||
};
|
||||
private HashMap<String, GroupData> m_groupSummaries = new HashMap();
|
||||
|
||||
public NotifyByAndroid(android.content.Context context)
|
||||
{
|
||||
Log.i(TAG, context.getPackageName());
|
||||
m_ctx = context;
|
||||
m_notificationManager = (NotificationManager)m_ctx.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(m_ctx.getPackageName() + NOTIFICATION_ACTION);
|
||||
filter.addAction(m_ctx.getPackageName() + NOTIFICATION_DELETED);
|
||||
filter.addAction(m_ctx.getPackageName() + NOTIFICATION_OPENED);
|
||||
filter.addAction(m_ctx.getPackageName() + NOTIFICATION_REPLIED);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
m_ctx.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
m_ctx.registerReceiver(this, filter);
|
||||
}
|
||||
}
|
||||
|
||||
public void notify(KNotification notification)
|
||||
{
|
||||
Log.i(TAG, notification.text);
|
||||
|
||||
// notification channel
|
||||
if (!m_channels.contains(notification.channelId)) {
|
||||
m_channels.add(notification.channelId);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
NotificationChannel channel = new NotificationChannel(notification.channelId, notification.channelName, NotificationManager.IMPORTANCE_DEFAULT);
|
||||
channel.setDescription(notification.channelDescription);
|
||||
|
||||
switch (notification.urgency) {
|
||||
case KNotification.CriticalUrgency:
|
||||
channel.setImportance(NotificationManager.IMPORTANCE_HIGH);
|
||||
break;
|
||||
case KNotification.NormalUrgency:
|
||||
channel.setImportance(NotificationManager.IMPORTANCE_LOW);
|
||||
break;
|
||||
case KNotification.LowUrgency:
|
||||
channel.setImportance(NotificationManager.IMPORTANCE_MIN);
|
||||
break;
|
||||
case KNotification.HighUrgency:
|
||||
default:
|
||||
channel.setImportance(NotificationManager.IMPORTANCE_DEFAULT);
|
||||
break;
|
||||
}
|
||||
|
||||
m_notificationManager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
Notification.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
builder = new Notification.Builder(m_ctx, notification.channelId);
|
||||
} else {
|
||||
builder = new Notification.Builder(m_ctx);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
builder.setSmallIcon((Icon)notification.icon);
|
||||
} else {
|
||||
builder.setSmallIcon(m_ctx.getApplicationInfo().icon);
|
||||
}
|
||||
builder.setContentTitle(notification.title);
|
||||
builder.setContentText(notification.text);
|
||||
// regular notifications show only a single line of content, if we have more
|
||||
// we need the "BigTextStyle" expandable notifications to make everything readable
|
||||
// in the single line case this behaves like the regular one, so no special-casing needed
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
builder.setStyle(new Notification.BigTextStyle().bigText(Html.fromHtml(notification.richText, Html.FROM_HTML_MODE_COMPACT)));
|
||||
} else {
|
||||
builder.setStyle(new Notification.BigTextStyle().bigText(Html.fromHtml(notification.richText)));
|
||||
}
|
||||
|
||||
// lock screen visibility
|
||||
switch (notification.visibility) {
|
||||
case "public":
|
||||
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
|
||||
break;
|
||||
case "private":
|
||||
builder.setVisibility(Notification.VISIBILITY_PRIVATE);
|
||||
break;
|
||||
case "secret":
|
||||
builder.setVisibility(Notification.VISIBILITY_SECRET);
|
||||
break;
|
||||
}
|
||||
|
||||
// grouping
|
||||
if (notification.group != null) {
|
||||
createGroupNotification(notification);
|
||||
builder.setGroup(notification.group);
|
||||
}
|
||||
|
||||
// legacy priority handling for versions without NotificationChannel support
|
||||
if (Build.VERSION.SDK_INT < 26) {
|
||||
switch (notification.urgency) {
|
||||
case KNotification.CriticalUrgency:
|
||||
builder.setPriority(Notification.PRIORITY_HIGH);
|
||||
break;
|
||||
case KNotification.NormalUrgency:
|
||||
builder.setPriority(Notification.PRIORITY_LOW);
|
||||
break;
|
||||
case KNotification.LowUrgency:
|
||||
builder.setPriority(Notification.PRIORITY_MIN);
|
||||
break;
|
||||
case KNotification.HighUrgency:
|
||||
default:
|
||||
builder.setPriority(Notification.PRIORITY_DEFAULT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// pending intent flags backward compatibility
|
||||
int PENDING_INTENT_FLAG_IMMUTABLE = 0;
|
||||
int PENDING_INTENT_FLAG_MUTABLE = 0;
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
PENDING_INTENT_FLAG_IMMUTABLE = PendingIntent.FLAG_IMMUTABLE;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= 31) {
|
||||
PENDING_INTENT_FLAG_MUTABLE = PendingIntent.FLAG_MUTABLE;
|
||||
}
|
||||
|
||||
// taping the notification shows the app
|
||||
Intent intent = new Intent(m_ctx.getPackageName() + NOTIFICATION_OPENED);
|
||||
intent.putExtra(NOTIFICATION_ID_EXTRA, notification.id);
|
||||
PendingIntent contentIntent = PendingIntent.getBroadcast(m_ctx, m_uniquePendingIntentId++, intent, PendingIntent.FLAG_UPDATE_CURRENT | PENDING_INTENT_FLAG_IMMUTABLE);
|
||||
builder.setContentIntent(contentIntent);
|
||||
|
||||
// actions
|
||||
for (HashMap.Entry<String, String> entry : notification.actions.entrySet()) {
|
||||
String id = entry.getKey();
|
||||
String label = entry.getValue();
|
||||
|
||||
Intent actionIntent = new Intent(m_ctx.getPackageName() + NOTIFICATION_ACTION);
|
||||
actionIntent.putExtra(NOTIFICATION_ID_EXTRA, notification.id);
|
||||
actionIntent.putExtra(NOTIFICATION_ACTION_ID_EXTRA, id);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(m_ctx, m_uniquePendingIntentId++, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT | PENDING_INTENT_FLAG_IMMUTABLE);
|
||||
Notification.Action action = new Notification.Action.Builder(0, label, pendingIntent).build();
|
||||
builder.addAction(action);
|
||||
}
|
||||
|
||||
// inline reply actions
|
||||
if (notification.inlineReplyLabel != null) {
|
||||
Intent replyIntent = new Intent(m_ctx.getPackageName() + NOTIFICATION_REPLIED);
|
||||
replyIntent.putExtra(NOTIFICATION_ID_EXTRA, notification.id);
|
||||
PendingIntent pendingReplyIntent = PendingIntent.getBroadcast(m_ctx, m_uniquePendingIntentId++, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT | PENDING_INTENT_FLAG_MUTABLE);
|
||||
|
||||
RemoteInput input = new RemoteInput.Builder(REMOTE_INPUT_KEY)
|
||||
.setAllowFreeFormInput(true)
|
||||
.setLabel(notification.inlineReplyPlaceholder)
|
||||
.build();
|
||||
Notification.Action replyAction = new Notification.Action.Builder(0, notification.inlineReplyLabel, pendingReplyIntent)
|
||||
.addRemoteInput(input)
|
||||
.build();
|
||||
builder.addAction(replyAction);
|
||||
}
|
||||
|
||||
// notification about user closing the notification
|
||||
Intent deleteIntent = new Intent(m_ctx.getPackageName() + NOTIFICATION_DELETED);
|
||||
deleteIntent.putExtra(NOTIFICATION_ID_EXTRA, notification.id);
|
||||
if (notification.group != null) {
|
||||
deleteIntent.putExtra(NOTIFICATION_GROUP_EXTRA, notification.group);
|
||||
}
|
||||
Log.i(TAG, deleteIntent.getExtras() + " " + notification.id);
|
||||
builder.setDeleteIntent(PendingIntent.getBroadcast(m_ctx, m_uniquePendingIntentId++, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT | PENDING_INTENT_FLAG_IMMUTABLE));
|
||||
|
||||
m_notificationManager.notify(notification.id, builder.build());
|
||||
}
|
||||
|
||||
public void close(int id, String group)
|
||||
{
|
||||
m_notificationManager.cancel(id);
|
||||
|
||||
if (group != null && m_groupSummaries.containsKey(group)) {
|
||||
GroupData g = m_groupSummaries.get(group);
|
||||
g.childIds.remove(id);
|
||||
if (g.childIds.isEmpty()) {
|
||||
m_groupSummaries.remove(group);
|
||||
m_notificationManager.cancel(g.groupId);
|
||||
} else {
|
||||
m_groupSummaries.put(group, g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
String action = intent.getAction();
|
||||
|
||||
int id = intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1);
|
||||
Log.i(TAG, action + ": " + id + " " + intent.getExtras());
|
||||
|
||||
if (action.equals(m_ctx.getPackageName() + NOTIFICATION_ACTION)) {
|
||||
// user activated one of the custom actions
|
||||
String actionId = intent.getStringExtra(NOTIFICATION_ACTION_ID_EXTRA);
|
||||
notificationActionInvoked(id, actionId);
|
||||
} else if (action.equals(m_ctx.getPackageName() + NOTIFICATION_DELETED)) {
|
||||
// user (or system) dismissed the notification - this can happen both for groups and regular notifications
|
||||
String group = null;
|
||||
if (intent.hasExtra(NOTIFICATION_GROUP_EXTRA)) {
|
||||
group = intent.getStringExtra(NOTIFICATION_GROUP_EXTRA);
|
||||
}
|
||||
|
||||
if ((id & NOTIFICATION_GROUP_ID_FLAG) != 0) {
|
||||
// entire group has been deleted
|
||||
m_groupSummaries.remove(group);
|
||||
} else {
|
||||
// a single regular notification, so reduce the refcount of the group if there is one
|
||||
notificationFinished(id);
|
||||
if (group != null && m_groupSummaries.containsKey(group)) {
|
||||
// we do not need to handle the case of childIds being empty here, the system will send us a deletion intent for the group too
|
||||
// this only matters for the logic in close().
|
||||
GroupData g = m_groupSummaries.get(group);
|
||||
g.childIds.remove(id);
|
||||
m_groupSummaries.put(group, g);
|
||||
}
|
||||
}
|
||||
} else if (action.equals(m_ctx.getPackageName() + NOTIFICATION_OPENED)) {
|
||||
// user tapped the notification
|
||||
Intent newintent = new Intent(m_ctx, m_ctx.getClass());
|
||||
newintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
m_ctx.startActivity(newintent);
|
||||
notificationActionInvoked(id, "default");
|
||||
} else if (action.equals(m_ctx.getPackageName() + NOTIFICATION_REPLIED)) {
|
||||
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||
if (remoteInput != null) {
|
||||
String s = (String) remoteInput.getCharSequence(REMOTE_INPUT_KEY);
|
||||
notificationInlineReply(id, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public native void notificationFinished(int notificationId);
|
||||
public native void notificationActionInvoked(int notificationId, String action);
|
||||
public native void notificationInlineReply(int notificationId, String text);
|
||||
|
||||
private void createGroupNotification(KNotification notification)
|
||||
{
|
||||
if (m_groupSummaries.containsKey(notification.group)) {
|
||||
GroupData group = m_groupSummaries.get(notification.group);
|
||||
group.childIds.add(notification.id);
|
||||
m_groupSummaries.put(notification.group, group);
|
||||
return;
|
||||
}
|
||||
|
||||
GroupData group = new GroupData();
|
||||
group.childIds.add(notification.id);
|
||||
group.groupId = m_uniquePendingIntentId++ + NOTIFICATION_GROUP_ID_FLAG;
|
||||
m_groupSummaries.put(notification.channelId, group);
|
||||
|
||||
Notification.Builder builder;
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
builder = new Notification.Builder(m_ctx, notification.channelId);
|
||||
} else {
|
||||
builder = new Notification.Builder(m_ctx);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
builder.setSmallIcon((Icon)notification.icon);
|
||||
} else {
|
||||
builder.setSmallIcon(m_ctx.getApplicationInfo().icon);
|
||||
}
|
||||
builder.setContentTitle(notification.channelName);
|
||||
builder.setContentText(notification.channelDescription);
|
||||
builder.setGroup(notification.group);
|
||||
builder.setGroupSummary(true);
|
||||
|
||||
// monitor for deletion (which happens when the last child notification is closed)
|
||||
int PENDING_INTENT_FLAG_IMMUTABLE = 0;
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
PENDING_INTENT_FLAG_IMMUTABLE = PendingIntent.FLAG_IMMUTABLE;
|
||||
}
|
||||
|
||||
Intent deleteIntent = new Intent(m_ctx.getPackageName() + NOTIFICATION_DELETED);
|
||||
deleteIntent.putExtra(NOTIFICATION_GROUP_EXTRA, notification.group);
|
||||
deleteIntent.putExtra(NOTIFICATION_ID_EXTRA, group.groupId);
|
||||
builder.setDeleteIntent(PendingIntent.getBroadcast(m_ctx, m_uniquePendingIntentId++, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT | PENDING_INTENT_FLAG_IMMUTABLE));
|
||||
|
||||
// try to stay out of the normal id space for regular notifications
|
||||
m_notificationManager.notify(group.groupId, builder.build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
[Event/fatalerror]
|
||||
Name=Fatal Error
|
||||
Name[ar]=خطأ فادح
|
||||
Name[ast]=Error fatal
|
||||
Name[az]=Ciddi Xəta
|
||||
Name[be]=Фатальная памылка
|
||||
Name[bg]=Фатална грешка
|
||||
Name[bs]=Kobna greška
|
||||
Name[ca]=Error fatal
|
||||
Name[ca@valencia]=Error fatal
|
||||
Name[cs]=Kritická chyba
|
||||
Name[da]=Fatal fejl
|
||||
Name[de]=Schwerer Fehler
|
||||
Name[el]=Μοιραίο σφάλμα
|
||||
Name[en_GB]=Fatal Error
|
||||
Name[eo]=Fatala Eraro
|
||||
Name[es]=Error fatal
|
||||
Name[et]=Parandamatu tõrge
|
||||
Name[eu]=Zoritxarreko errorea
|
||||
Name[fi]=Vakava virhe
|
||||
Name[fr]=Erreur fatale
|
||||
Name[gd]=Mearachd mharbhtach
|
||||
Name[gl]=Erro fatal
|
||||
Name[he]=שגיאה חמורה
|
||||
Name[hi]=गंभीर त्रुटि
|
||||
Name[hu]=Végzetes hiba
|
||||
Name[ia]=Error Fatal
|
||||
Name[id]=Error Fatal
|
||||
Name[is]=Banvæn villa
|
||||
Name[it]=Errore grave
|
||||
Name[ka]=ფატალური შეცდომა
|
||||
Name[ko]=치명적 오류
|
||||
Name[lt]=Lemtingoji klaida
|
||||
Name[lv]=Fatāla kļūda
|
||||
Name[ml]=ഗുുരുതരപിശക്
|
||||
Name[nb]=Kritisk feil
|
||||
Name[nds]=Swoor Fehler
|
||||
Name[nl]=Fatale fout
|
||||
Name[nn]=Kritisk feil
|
||||
Name[pa]=ਘਾਤਕ ਗਲਤੀ
|
||||
Name[pl]=Poważny błąd
|
||||
Name[pt]=Erro Fatal
|
||||
Name[pt_BR]=Erro fatal
|
||||
Name[ro]=Eroare fatală
|
||||
Name[ru]=Критическая ошибка
|
||||
Name[sa]=घातक त्रुटिः
|
||||
Name[sk]=Fatálna chyba
|
||||
Name[sl]=Usodna napaka
|
||||
Name[sr]=Кобна грешка
|
||||
Name[sr@ijekavian]=Кобна грешка
|
||||
Name[sr@ijekavianlatin]=Kobna greška
|
||||
Name[sr@latin]=Kobna greška
|
||||
Name[sv]=Allvarligt fel
|
||||
Name[ta]=முறிவுண்டாக்கும் சிக்கல்
|
||||
Name[tg]=Хатои ҷиддӣ
|
||||
Name[tr]=Onulmaz Hata
|
||||
Name[uk]=Критична помилка
|
||||
Name[vi]=Lỗi nghiêm trọng
|
||||
Name[x-test]=xxFatal Errorxx
|
||||
Name[zh_CN]=严重错误
|
||||
Name[zh_TW]=嚴重錯誤
|
||||
Comment=There was a serious error causing the program to exit
|
||||
Comment[ar]=حدث خطأ جاد سبّب للبرنامج بإغلاقه
|
||||
Comment[az]=Proqramdan çıxış zamanı ciddi xəta baş verdi
|
||||
Comment[be]=Адбылася сур'ёзная памылка, якая прывяла да выхаду з праграмы
|
||||
Comment[bg]=Имаше сериозна грешка, водеща до изход от програмата
|
||||
Comment[bs]=Došlo je do ozbiljne greške zbog koje je program obustavljen
|
||||
Comment[ca]=S'ha produït un error seriós que ha provocat la sortida del programa
|
||||
Comment[ca@valencia]=S'ha produït un error seriós que ha provocat l'eixida del programa
|
||||
Comment[cs]=Nastala závažná chyba, v důsledku které byl program ukončen
|
||||
Comment[da]=Der var en alvorlig fejl som forårsagede at programmet afsluttede
|
||||
Comment[de]=Es ist ein schwerer Fehler aufgetreten, woraufhin das Programm beendet wurde.
|
||||
Comment[el]=Υπήρξε ένα σοβαρό σφάλμα που προκάλεσε την έξοδο του προγράμματος
|
||||
Comment[en_GB]=There was a serious error causing the program to exit
|
||||
Comment[eo]=Estis grava eraro kaŭzanta programon eliri
|
||||
Comment[es]=Ha ocurrido un error serio que ha causado la salida del programa
|
||||
Comment[et]=Tekkis tõsine viga, mis sundis programmi tööd lõpetama
|
||||
Comment[eu]=Errore garrantzitsua bat egon da programa ixtea eragin duena
|
||||
Comment[fi]=Vakava virhe aiheutti ohjelman päättymisen
|
||||
Comment[fr]=Une erreur grave a entraîné la fin du programme
|
||||
Comment[gd]=Chaidh am prògram fhàgail ri linn mearachd dona
|
||||
Comment[gl]=Produciuse un erro grave que fixo que o programa saíse
|
||||
Comment[he]=אירעה שגיאה חמורה שגרמה ליישום להיסגר
|
||||
Comment[hi]=किसी गम्भीर त्रुटि के कारण अनुप्रयोग बन्द हो गया
|
||||
Comment[hu]=Olyan hiba történt a programban, melynek hatására a program futása befejeződött
|
||||
Comment[ia]=Il esseva un serie error que il causava le programma a exir
|
||||
Comment[id]=Ada error serius yang menyebabkan program keluar
|
||||
Comment[is]=Alvarleg villa átti sér stað sem olli því að forritið slökkti á sér
|
||||
Comment[it]=Si è verificato un errore grave, che ha causato la chiusura del programma
|
||||
Comment[ka]=სერიოზული შეცდომა, რომელმაც პროგრამის მუშაობა დაასრულა
|
||||
Comment[ko]=프로그램이 종료될 정도의 큰 오류가 발생함
|
||||
Comment[lt]=Įvyko rimta klaida, privertusi programą užbaigti darbą
|
||||
Comment[lv]=Programmā ir notikusi nopietna kļūda, tāpēc tā izslēdzās
|
||||
Comment[ml]=ഒരു ഗുരുതര പിശക് കാരണം പ്രോഗ്രാമില് നിന്ന് പുറത്ത് കടന്നിരിക്കുന്നു
|
||||
Comment[nb]=Det oppsto en alvorlig feil som gjorde at programmet avsluttet
|
||||
Comment[nds]=Dat geev en swoor Fehler, dat Programm is utstegen
|
||||
Comment[nl]=Er deed zich een serieuze fout voor die het programma deed afsluiten
|
||||
Comment[nn]=Det oppstod ein alvorleg feil som gjorde av programmet avslutta
|
||||
Comment[pa]=ਗੰਭੀਰ ਗ਼ਲਤੀ ਪਰੋਗਰਾਮ ਤੋਂ ਬਾਹਰ ਜਾਣ ਦਾ ਕਾਰਨ ਬਣੀ
|
||||
Comment[pl]=Wystąpił poważny błąd i program zakończył działanie
|
||||
Comment[pt]=Ocorreu um erro sério que fez com que o programa saísse
|
||||
Comment[pt_BR]=Ocorreu um erro grave que causou o fechamento do programa
|
||||
Comment[ro]=A intervenit o eroare gravă ce a provocat terminarea programului
|
||||
Comment[ru]=Произошла серьёзная ошибка, приведшая к завершению работы программы
|
||||
Comment[sa]=गम्भीरदोषः अभवत् यत् कार्यक्रमस्य निर्गमनं जातम्
|
||||
Comment[sk]=Vyskytla sa vážna chyba, ktorá spôsobila ukončenie programu
|
||||
Comment[sl]=Prišlo je do resne napake, zaradi katere se je program končal
|
||||
Comment[sr]=Дошло је до озбиљне грешке због које је програм обустављен
|
||||
Comment[sr@ijekavian]=Дошло је до озбиљне грешке због које је програм обустављен
|
||||
Comment[sr@ijekavianlatin]=Došlo je do ozbiljne greške zbog koje je program obustavljen
|
||||
Comment[sr@latin]=Došlo je do ozbiljne greške zbog koje je program obustavljen
|
||||
Comment[sv]=Ett allvarligt fel uppstod vilket fick programmet att avslutas
|
||||
Comment[ta]=செயலியை வெளியேற செய்யும் சிக்கல் ஏற்பட்டுள்ளது
|
||||
Comment[tg]=Хатои ҷиддие рӯй дод, ки барномаро қатъ кард
|
||||
Comment[tr]=Uygulamadan çıkacak kadar önemli bir hata oluştu
|
||||
Comment[uk]=Трапилась серйозна помилка, що призвела до закриття програми
|
||||
Comment[vi]=Đã có một lỗi nghiêm trọng khiến chương trình phải thoát
|
||||
Comment[x-test]=xxThere was a serious error causing the program to exitxx
|
||||
Comment[zh_CN]=有一个严重错误导致程序退出
|
||||
Comment[zh_TW]=程式發生了嚴重錯誤,必須離開
|
||||
Action=Popup
|
||||
|
||||
[Event/notification]
|
||||
Name=Notification
|
||||
Name[ar]=إخطار
|
||||
Name[az]=Bildiriş
|
||||
Name[be]=Апавяшчэнне
|
||||
Name[bg]=Известия
|
||||
Name[bs]=Obavještenje
|
||||
Name[ca]=Notificació
|
||||
Name[ca@valencia]=Notificació
|
||||
Name[cs]=Upozornění
|
||||
Name[da]=Bekendtgørelse
|
||||
Name[de]=Benachrichtigung
|
||||
Name[el]=Ειδοποίηση
|
||||
Name[en_GB]=Notification
|
||||
Name[eo]=Sciigo
|
||||
Name[es]=Notificación
|
||||
Name[et]=Märguanne
|
||||
Name[eu]=Jakinarazpena
|
||||
Name[fi]=Ilmoitus
|
||||
Name[fr]=Notification
|
||||
Name[gd]=Brath
|
||||
Name[gl]=Notificación
|
||||
Name[he]=התראה
|
||||
Name[hi]=अधिसूचना
|
||||
Name[hu]=Értesítés
|
||||
Name[ia]=Notification
|
||||
Name[id]=Notifikasi
|
||||
Name[is]=Tilkynning
|
||||
Name[it]=Notifica
|
||||
Name[ka]=შეტყობინება
|
||||
Name[ko]=알림
|
||||
Name[lt]=Pranešimas
|
||||
Name[lv]=Paziņojums
|
||||
Name[ml]=മുന്നറിയിപ്പുകള്
|
||||
Name[nb]=Varsling
|
||||
Name[nds]=Bescheed
|
||||
Name[nl]=Melding
|
||||
Name[nn]=Varsling
|
||||
Name[pa]=ਸੂਚਨਾ
|
||||
Name[pl]=Powiadomienie
|
||||
Name[pt]=Notificação
|
||||
Name[pt_BR]=Notificação
|
||||
Name[ro]=Notificare
|
||||
Name[ru]=Уведомление
|
||||
Name[sa]=अधिसूचना
|
||||
Name[sk]=Pripomienka
|
||||
Name[sl]=Obvestila
|
||||
Name[sr]=Обавештење
|
||||
Name[sr@ijekavian]=Обавештење
|
||||
Name[sr@ijekavianlatin]=Obaveštenje
|
||||
Name[sr@latin]=Obaveštenje
|
||||
Name[sv]=Underrättelse
|
||||
Name[ta]=அறிவிப்பு
|
||||
Name[tg]=Огоҳинома
|
||||
Name[tr]=Bildirim
|
||||
Name[uk]=Сповіщення
|
||||
Name[vi]=Thông báo
|
||||
Name[x-test]=xxNotificationxx
|
||||
Name[zh_CN]=通知
|
||||
Name[zh_TW]=通知
|
||||
Comment=Something special happened in the program
|
||||
Comment[ar]=حدث شيء خاص في البرنامج
|
||||
Comment[az]=Proqramda xüsusi bir şey baş verdi
|
||||
Comment[be]=У праграме адбылося нешта незразумелае
|
||||
Comment[bg]=Случи се нещо необичайно при изпълнение на програмата
|
||||
Comment[bs]=Nešto posebno se dogodilo u programu
|
||||
Comment[ca]=Quelcom especial ha passat en el programa
|
||||
Comment[ca@valencia]=Alguna cosa especial ha passat en el programa
|
||||
Comment[cs]=Něco zvláštního se stalo s programem
|
||||
Comment[da]=Noget specielt skete i programmet
|
||||
Comment[de]=Im Programm ist ein besonderes Ereignis eingetreten
|
||||
Comment[el]=Κάτι ιδιαίτερο συνέβη στο πρόγραμμα
|
||||
Comment[en_GB]=Something special happened in the program
|
||||
Comment[eo]=Io speciala okazis en la programo
|
||||
Comment[es]=Ha ocurrido algo especial en el programa
|
||||
Comment[et]=Programmis juhtus midagi erilist
|
||||
Comment[eu]=Zerbait berezia gertatu da programan
|
||||
Comment[fi]=Ohjelmassa tapahtui jotakin erikoista
|
||||
Comment[fr]=Quelque chose d'anormal s'est produit dans le programme
|
||||
Comment[gd]=Thachair rudeigin sònraichte sa phrògram
|
||||
Comment[gl]=Ocorreu algo especial no programa
|
||||
Comment[he]=משהו מיוחד קרה ביישום
|
||||
Comment[hi]=अनुप्रयोग में कुछ विशेष हुआ
|
||||
Comment[hu]=Valamilyen esemény következett be a programban
|
||||
Comment[ia]=Il occurreva qualcosa de special in le programma
|
||||
Comment[id]=Sesuatu yang istimewa terjadi dalam program
|
||||
Comment[is]=Eitthvað sérstakt gerðist í keyrslu forritsins
|
||||
Comment[it]=Si è verificato qualcosa di speciale nel programma
|
||||
Comment[ka]=პროგრამას რაღაც განსაკუთრებული მოუვიდა
|
||||
Comment[ko]=프로그램에서 특별한 일이 발생함
|
||||
Comment[lt]=Programoje nutikos kažkas specialaus
|
||||
Comment[lv]=Programmā ir noticis kaut kas vērā ņemams
|
||||
Comment[ml]=പ്രത്യേകതയുള്ളയെന്തോ പ്രോഗ്രാമില് സംഭവിച്ചിരിക്കുന്നു
|
||||
Comment[nb]=Det hendte noe spesielt i programmet
|
||||
Comment[nds]=Dat geev en afsünnerlich Begeefnis binnen dat Programm
|
||||
Comment[nl]=Er gebeurde iets bijzonders in het programma
|
||||
Comment[nn]=Det skjedde noko spesielt i programmet
|
||||
Comment[pl]=Coś niezwykłego stało się z programem
|
||||
Comment[pt]=Ocorreu algo de especial no programa
|
||||
Comment[pt_BR]=Algo especial aconteceu no programa
|
||||
Comment[ro]=S-a întâmplat ceva deosebit în program
|
||||
Comment[ru]=В программе что-то произошло
|
||||
Comment[sa]=कार्यक्रमे किञ्चित् विशेषं घटितम्
|
||||
Comment[sk]=V programe sa stalo niečo zvláštne
|
||||
Comment[sl]=V programu se je zgodilo nekaj posebnega
|
||||
Comment[sr]=Нешто посебно се догодило у програму
|
||||
Comment[sr@ijekavian]=Нешто посебно се догодило у програму
|
||||
Comment[sr@ijekavianlatin]=Nešto posebno se dogodilo u programu
|
||||
Comment[sr@latin]=Nešto posebno se dogodilo u programu
|
||||
Comment[sv]=Någonting speciellt inträffade i programmet
|
||||
Comment[ta]=செயலியில் ஏதோ நடந்துள்ளது
|
||||
Comment[tg]=Чизе махсус дар барнома рӯй дод
|
||||
Comment[tr]=Uygulamada bazı özel durumlar oluştu
|
||||
Comment[uk]=Щось особливе сталось у програмі
|
||||
Comment[vi]=Thứ gì đó đặc biệt đã xảy ra trong chương trình
|
||||
Comment[x-test]=xxSomething special happened in the programxx
|
||||
Comment[zh_CN]=这个程序中发生了特殊的事情
|
||||
Comment[zh_TW]=程式中發生了特殊事件
|
||||
Action=Popup
|
||||
|
||||
[Event/warning]
|
||||
Name=Warning
|
||||
Name[ar]=تحذير
|
||||
Name[az]=Xəbərdarlıq
|
||||
Name[be]=Папярэджанне
|
||||
Name[bg]=Предупреждение
|
||||
Name[bs]=Upozorenje
|
||||
Name[ca]=Avís
|
||||
Name[ca@valencia]=Avís
|
||||
Name[cs]=Varování
|
||||
Name[da]=Advarsel
|
||||
Name[de]=Warnung
|
||||
Name[el]=Προειδοποίηση
|
||||
Name[en_GB]=Warning
|
||||
Name[eo]=Averto
|
||||
Name[es]=Advertencia
|
||||
Name[et]=Hoiatus
|
||||
Name[eu]=Abisua
|
||||
Name[fi]=Varoitus
|
||||
Name[fr]=Avertissement
|
||||
Name[gd]=Rabhadh
|
||||
Name[gl]=Aviso
|
||||
Name[he]=אזהרה
|
||||
Name[hi]=चेतावनी
|
||||
Name[hu]=Figyelmeztetés
|
||||
Name[ia]=Aviso
|
||||
Name[id]=Peringatan
|
||||
Name[is]=Aðvörun
|
||||
Name[it]=Avviso
|
||||
Name[ka]=გაფრთხილება
|
||||
Name[ko]=경고
|
||||
Name[lt]=Įspėjimas
|
||||
Name[lv]=Uzmanību
|
||||
Name[ml]=താക്കീത്0
|
||||
Name[nb]=Advarsel
|
||||
Name[nds]=Wohrschoen
|
||||
Name[nl]=Waarschuwing
|
||||
Name[nn]=Åtvaring
|
||||
Name[pa]=ਚੇਤਾਵਨੀ
|
||||
Name[pl]=Ostrzeżenie
|
||||
Name[pt]=Aviso
|
||||
Name[pt_BR]=Aviso
|
||||
Name[ro]=Avertisment
|
||||
Name[ru]=Предупреждение
|
||||
Name[sa]=चेतवानी
|
||||
Name[sk]=Upozornenie
|
||||
Name[sl]=Opozorilo
|
||||
Name[sr]=Упозорење
|
||||
Name[sr@ijekavian]=Упозорење
|
||||
Name[sr@ijekavianlatin]=Upozorenje
|
||||
Name[sr@latin]=Upozorenje
|
||||
Name[sv]=Varning
|
||||
Name[ta]=எச்சரிக்கை
|
||||
Name[tg]=Огоҳӣ
|
||||
Name[tr]=Uyarı
|
||||
Name[uk]=Попередження
|
||||
Name[vi]=Cảnh báo
|
||||
Name[x-test]=xxWarningxx
|
||||
Name[zh_CN]=警告
|
||||
Name[zh_TW]=警告
|
||||
Comment=There was an error in the program which may cause problems
|
||||
Comment[ar]=حدث خطأ في البرنامج مما تسبّب بمشاكل
|
||||
Comment[az]=Proqramda problemlər yarada biləcək xəta baş verdi
|
||||
Comment[be]=У праграме адбылася памылка, праз якую могуць паўстаць праблемы
|
||||
Comment[bg]=Имаше грешка в програмата, която може да доведе до проблеми
|
||||
Comment[bs]=Došlo je do greške u programu koja može izazvati probleme
|
||||
Comment[ca]=S'ha produït un error en el programa que pot provocar problemes
|
||||
Comment[ca@valencia]=S'ha produït un error en el programa que pot provocar problemes
|
||||
Comment[cs]=V programu nastala chyba, která může způsobit problémy
|
||||
Comment[da]=Der var en fejl i program som kan volde problemer
|
||||
Comment[de]=Im Programm ist ein Fehler aufgetreten, der Probleme verursachen könnte
|
||||
Comment[el]=Υπήρξε σφάλμα στο πρόγραμμα που μπορεί να προκαλέσει προβλήματα
|
||||
Comment[en_GB]=There was an error in the program which may cause problems
|
||||
Comment[eo]=Estis eraro en la programo, kiu povas kaŭzi problemojn
|
||||
Comment[es]=Ha ocurrido un error en el programa que puede causar problemas
|
||||
Comment[et]=Programmis tekkis viga, mis võib tekitada probleeme
|
||||
Comment[eu]=Errore bat egon da programan arazoak sor ditzakeena
|
||||
Comment[fi]=Ohjelmassa oli virhe, joka voi aiheuttaa ongelmia
|
||||
Comment[fr]=Il y a eu une erreur dans le programme qui peut causer des problèmes
|
||||
Comment[gd]=Bha mearachd sa phrògram a dh'adhbharaicheas trioblaid ma dh'fhaoidte
|
||||
Comment[gl]=Produciuse un erro no programa que pode causar problemas
|
||||
Comment[he]=אירעה שגיאה ביישום, מה שעלול לגרום לבעיות
|
||||
Comment[hi]=अनुप्रयोग में कोई त्रुटि हुई है, जिसके कारण उसमें कुछ परेशानियाँ आ सकती हेैं
|
||||
Comment[hu]=Hiba történt a programban, de a program futása folytatódhat
|
||||
Comment[ia]=Il habeva un error in le programma que il pote causar problemas
|
||||
Comment[id]=Ada sebuah error dalam program yang menyebabkan masalah
|
||||
Comment[is]=Villa kom upp í forritinu sem valdið vandamálum
|
||||
Comment[it]=Si è verificato un errore nel programma che potrebbe causare problemi
|
||||
Comment[ka]=პროგრამის შეცდომა, რომელსაც პრობლემების გამოწვევა შეუძლია
|
||||
Comment[ko]=프로그램에 문제를 가져올 수 있는 오류가 발생함
|
||||
Comment[lt]=Programoje įvyko klaida, galinti sukelti problemų
|
||||
Comment[lv]=Programmā ir notikusi kļūda, kas var radīt problēmas
|
||||
Comment[ml]=പ്രശ്നങ്ങള്ക്ക് കാരണമായേക്കാവുന്ന പിശക് പ്രോഗ്രാമില് സംഭവിച്ചിരിക്കുന്നു
|
||||
Comment[nb]=I programmet oppsto det en feil som kan gi vanskeligheter
|
||||
Comment[nds]=Dat geev en Fehler binnen dat Programm, villicht suert dor Problemen bi rut
|
||||
Comment[nl]=Er deed zich een fout voor in het programma dat problemen kan veroorzaken
|
||||
Comment[nn]=Det oppstod ein feil i programmet som kan gje problem
|
||||
Comment[pl]=W programie wystąpił błąd, może powodować problemy
|
||||
Comment[pt]=Ocorreu um erro no programa que poderá provocar problemas
|
||||
Comment[pt_BR]=Ocorreu um erro no programa, que pode causar problemas
|
||||
Comment[ro]=A avut loc o eroare în program ce ar putea provoca probleme
|
||||
Comment[ru]=В программе произошла ошибка, которая может вызвать проблемы
|
||||
Comment[sa]=कार्यक्रमे त्रुटिः आसीत् या समस्यां जनयितुं शक्नोति
|
||||
Comment[sk]=V programe sa vyskytla chyba, ktorá môže spôsobiť problémy
|
||||
Comment[sl]=V programu je prišlo do napake, ki lahko povzroči težave
|
||||
Comment[sr]=Дошло је до грешке у програму која може изазвати проблеме
|
||||
Comment[sr@ijekavian]=Дошло је до грешке у програму која може изазвати проблеме
|
||||
Comment[sr@ijekavianlatin]=Došlo je do greške u programu koja može izazvati probleme
|
||||
Comment[sr@latin]=Došlo je do greške u programu koja može izazvati probleme
|
||||
Comment[sv]=Det uppstod ett fel i programmet vilket kan orsaka problem
|
||||
Comment[ta]=பிரச்சனைகளை உண்டாக்கக்கூடிய சிக்கல் செயலியில் ஏற்பட்டுள்ளது
|
||||
Comment[tg]=Хатое дар барнома рӯй дод, ки метавонад мушкилиеро ба вуҷуд орад
|
||||
Comment[tr]=Uygulamada sorunlara neden olabilecek bir hata oluştu
|
||||
Comment[uk]=В програмі трапилась помилка, яка може спричинити проблеми
|
||||
Comment[vi]=Có lỗi trong chương trình, có thể gây ra vấn đề
|
||||
Comment[x-test]=xxThere was an error in the program which may cause problemsxx
|
||||
Comment[zh_CN]=程序中有一个错误,它可能导致一些问题
|
||||
Comment[zh_TW]=程式中發生錯誤,可能造成問題
|
||||
Action=Popup
|
||||
|
||||
[Event/catastrophe]
|
||||
Name=Catastrophe
|
||||
Name[ar]=كارثة
|
||||
Name[az]=Fəlakət
|
||||
Name[be]=Катастрофа
|
||||
Name[bg]=Срив
|
||||
Name[bs]=Katastrofa
|
||||
Name[ca]=Catàstrofe
|
||||
Name[ca@valencia]=Catàstrofe
|
||||
Name[cs]=Katastrofa
|
||||
Name[da]=Katastrofe
|
||||
Name[de]=Katastrophe
|
||||
Name[el]=Καταστροφή
|
||||
Name[en_GB]=Catastrophe
|
||||
Name[eo]=Katastrofo
|
||||
Name[es]=Catástrofe
|
||||
Name[et]=Katastroof
|
||||
Name[eu]=Hondamendia
|
||||
Name[fi]=Katastrofi
|
||||
Name[fr]=Catastrophe
|
||||
Name[gd]=Tubaist
|
||||
Name[gl]=Catástrofe
|
||||
Name[he]=קטטסטרופה
|
||||
Name[hi]=दुर्घटना
|
||||
Name[hu]=Súlyos hiba
|
||||
Name[ia]=Catastrophe
|
||||
Name[id]=Malapetaka
|
||||
Name[is]=Hörmungarástand
|
||||
Name[it]=Catastrofe
|
||||
Name[ka]=კატასტროფა
|
||||
Name[ko]=중대한 오류
|
||||
Name[lt]=Katastrofa
|
||||
Name[lv]=Katastrofa
|
||||
Name[ml]=വിപത്ത്
|
||||
Name[nb]=Katastrofe
|
||||
Name[nds]=Groot Malöör
|
||||
Name[nl]=Catastrofe
|
||||
Name[nn]=Katastrofe
|
||||
Name[pl]=Katastrofa
|
||||
Name[pt]=Catástrofe
|
||||
Name[pt_BR]=Catástrofe
|
||||
Name[ro]=Catastrofă
|
||||
Name[ru]=Катастрофа
|
||||
Name[sa]=प्रलयम्
|
||||
Name[sk]=Katastrofa
|
||||
Name[sl]=Katastrofa
|
||||
Name[sr]=Катастрофа
|
||||
Name[sr@ijekavian]=Катастрофа
|
||||
Name[sr@ijekavianlatin]=Katastrofa
|
||||
Name[sr@latin]=Katastrofa
|
||||
Name[sv]=Katastrof
|
||||
Name[ta]=மிகப்பெரிய சிக்கல்
|
||||
Name[tg]=Садама
|
||||
Name[tr]=Felaket
|
||||
Name[uk]=Катастрофа
|
||||
Name[vi]=Thảm hoạ
|
||||
Name[x-test]=xxCatastrophexx
|
||||
Name[zh_CN]=严重故障
|
||||
Name[zh_TW]=發生大災難
|
||||
Comment=A very serious error occurred, at least causing the program to exit
|
||||
Comment[ar]=حدث خطأ جاد جدًا، تسبّب للبرنامج بإنهائه على أقل تقدير
|
||||
Comment[az]=Ən azı proqramın bağlanmasına səbəb olan çox ciddi bir səhv baş verdi
|
||||
Comment[be]=Адбылася вельмі сур'ёзная памылка, якая прывяла да выхаду з праграмы
|
||||
Comment[bg]=Възникна много сериозна грешка, която доведе до прекратяване на програмата
|
||||
Comment[bs]=Došlo je do vrlo ozbiljne greške, zbog koje je bar program obustavljen
|
||||
Comment[ca]=S'ha produït un error molt seriós, com a mínim ha provocat la sortida del programa
|
||||
Comment[ca@valencia]=S'ha produït un error molt seriós, com a mínim ha provocat l'eixida del programa
|
||||
Comment[cs]=Nastala velmi závažná chyba, která má za následek minimálně ukončení programu
|
||||
Comment[da]=En meget alvorlig fejl opstod, som mindst forårsagede at programmet afsluttede
|
||||
Comment[de]=Ein sehr schwerer Fehler ist aufgetreten, der wenigstens das Programm zum Beenden bringt
|
||||
Comment[el]=Συνέβη ένα πολύ σοβαρό σφάλμα, που προκάλεσε την έξοδο του προγράμματος
|
||||
Comment[en_GB]=A very serious error occurred, at least causing the program to exit
|
||||
Comment[eo]=Tre grava eraro okazis, almenaŭ kaŭzante la eliro de la programo
|
||||
Comment[es]=Ha ocurrido un error muy serio que ha causado, al menos, la salida del programa
|
||||
Comment[et]=Tekkis väga tõsine viga, mis sundis programmi tööd lõpetama
|
||||
Comment[eu]=Errore oso garrantzitsu bat egon da, gutxienez programa ixtea eragin duena
|
||||
Comment[fi]=Tapahtui erittäin vakava virhe, joka aiheutti ainakin ohjelman päättymisen
|
||||
Comment[fr]=Une erreur très grave s'est produite, provoquant au minimum la fin du programme
|
||||
Comment[gd]=Thachair mearachd glè dhona agus chaidh am prògram fhàgail air a char as lugha
|
||||
Comment[gl]=Produciuse un erro moi grave, que como mínimo fixo que o programa saíse
|
||||
Comment[he]=אירעה שגיאה חמורה מאוד, שכנראה גרמה ליישום להיסגר
|
||||
Comment[hi]=एक अत्यन्त गम्भीर त्रुटि हुई है जिसके कारण अनुप्रयोग बन्द हो गया
|
||||
Comment[hu]=Hiba történt a programban, a program futása befejeződött
|
||||
Comment[ia]=Il occurreva un error le plus serie, al minus causante le exito del programma
|
||||
Comment[id]=Sebuah eror yang sangat serius terjadi, setidaknya menyebabkan program keluar
|
||||
Comment[is]=Mjög alvarleg villa átti sér stað, a.m.k. næg til að forritið slökkti á sér
|
||||
Comment[it]=Si è verificato un errore molto grave, che ha causato almeno la chiusura del programma
|
||||
Comment[ka]=ძალიან სერიოზული შეცდომა, რომელმაც, ყველაზე ცოტა, პროგრამის მუშაობა დაასრულა
|
||||
Comment[ko]=프로그램이 종료될 정도의 매우 큰 오류가 발생함
|
||||
Comment[lt]=Įvyko labai rimta klaida, privertusi programą bent jau užbaigti darbą
|
||||
Comment[lv]=Programmā ir notikusi ļoti nopietna kļūda, kas, mazākais, ir likusi programmai izslēgties
|
||||
Comment[nb]=Det oppsto en meget alvorlig feil som minst førte til at programmet stoppet
|
||||
Comment[nds]=Dat geev en groot Malöör, tominnst dat Programm is utstegen
|
||||
Comment[nl]=Er deed zich een serieuze fout voor dat er op zijn minst voor zorgde dat het programma werd afgesloten
|
||||
Comment[nn]=Det oppstod ein svært alvorleg feil som førte til at programmet avslutta
|
||||
Comment[pl]=Wystąpił bardzo poważny błąd, co najmniej nastąpiło zakończenie pracy programu
|
||||
Comment[pt]=Ocorreu um erro muito sério, que fez pelo menos com que o programa saísse
|
||||
Comment[pt_BR]=Um erro muito sério aconteceu, que causou (no mínimo) o fechamento do programa
|
||||
Comment[ro]=A intervenit o eroare foarte gravă, provocând cel puțin terminarea programului
|
||||
Comment[ru]=Произошла очень серьёзная ошибка, как минимум, вызвавшая завершение работы программы
|
||||
Comment[sa]=अतीव गम्भीरः त्रुटिः अभवत्, न्यूनातिन्यूनं कार्यक्रमस्य निर्गमनस्य कारणम्
|
||||
Comment[sk]=Nastala veľmi vážna chyba, ktorá zapríčinila minimálne ukončenie programu
|
||||
Comment[sl]=Prišlo je do zelo resne napake, zaradi katere se je program končal
|
||||
Comment[sr]=Дошло је до врло озбиљне грешке, због које је бар програм обустављен
|
||||
Comment[sr@ijekavian]=Дошло је до врло озбиљне грешке, због које је бар програм обустављен
|
||||
Comment[sr@ijekavianlatin]=Došlo je do vrlo ozbiljne greške, zbog koje je bar program obustavljen
|
||||
Comment[sr@latin]=Došlo je do vrlo ozbiljne greške, zbog koje je bar program obustavljen
|
||||
Comment[sv]=Ett mycket allvarligt fel uppstod, som åtminstone fick programmet att avslutas
|
||||
Comment[ta]=குறைந்தபட்சம் செயலியை வெளியேற வைக்கும் சிக்கல் ஏற்பட்டுள்ளது
|
||||
Comment[tg]=Хатои хеле ҷиддӣ рӯй дод, ки ақаллан барномаро қатъ кард
|
||||
Comment[tr]=En azından uygulamadan çıkacak kadar önemli bir hata oluştu
|
||||
Comment[uk]=Трапилась дуже серйозна помилка, яка призвела до закриття програми
|
||||
Comment[vi]=Một lỗi rất nghiêm trọng đã xảy ra, ít nhất nó khiến chương trình phải thoát
|
||||
Comment[x-test]=xxA very serious error occurred, at least causing the program to exitxx
|
||||
Comment[zh_CN]=发生了一个非常严重的错误,至少已经导致了这个程序退出
|
||||
Comment[zh_TW]=發生了非常嚴重的問題,至少造成程式被迫結束
|
||||
Action=Popup
|
||||
@@ -0,0 +1 @@
|
||||
#cmakedefine WITH_SNORETOAST
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2009 Canonical
|
||||
SPDX-FileContributor: Aurélien Gâteau <aurelien.gateau@canonical.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only
|
||||
*/
|
||||
|
||||
#include "imageconverter.h"
|
||||
|
||||
#include <QDBusArgument>
|
||||
#include <QDBusMetaType>
|
||||
#include <QImage>
|
||||
|
||||
namespace ImageConverter
|
||||
{
|
||||
/**
|
||||
* A structure representing an image which can be marshalled to fit the
|
||||
* notification spec.
|
||||
*/
|
||||
struct SpecImage {
|
||||
int width, height, rowStride;
|
||||
bool hasAlpha;
|
||||
int bitsPerSample, channels;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
QDBusArgument &operator<<(QDBusArgument &argument, const SpecImage &image)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument << image.width << image.height << image.rowStride << image.hasAlpha;
|
||||
argument << image.bitsPerSample << image.channels << image.data;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
const QDBusArgument &operator>>(const QDBusArgument &argument, SpecImage &image)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument >> image.width >> image.height >> image.rowStride >> image.hasAlpha;
|
||||
argument >> image.bitsPerSample >> image.channels >> image.data;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// This must be before the QVariant::fromValue below (#211726)
|
||||
Q_DECLARE_METATYPE(ImageConverter::SpecImage)
|
||||
|
||||
namespace ImageConverter
|
||||
{
|
||||
QVariant variantForImage(const QImage &_image)
|
||||
{
|
||||
qDBusRegisterMetaType<SpecImage>();
|
||||
|
||||
const bool hasAlpha = _image.hasAlphaChannel();
|
||||
QImage image;
|
||||
if (hasAlpha) {
|
||||
image = _image.convertToFormat(QImage::Format_RGBA8888);
|
||||
} else {
|
||||
image = _image.convertToFormat(QImage::Format_RGB888);
|
||||
}
|
||||
|
||||
QByteArray data((const char *)image.constBits(), image.sizeInBytes());
|
||||
|
||||
SpecImage specImage;
|
||||
specImage.width = image.width();
|
||||
specImage.height = image.height();
|
||||
specImage.rowStride = image.bytesPerLine();
|
||||
specImage.hasAlpha = hasAlpha;
|
||||
specImage.bitsPerSample = 8;
|
||||
specImage.channels = hasAlpha ? 4 : 3;
|
||||
specImage.data = data;
|
||||
|
||||
return QVariant::fromValue(specImage);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2009 Canonical
|
||||
SPDX-FileContributor: Aurélien Gâteau <aurelien.gateau@canonical.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only
|
||||
*/
|
||||
|
||||
#ifndef IMAGECONVERTER_H
|
||||
#define IMAGECONVERTER_H
|
||||
|
||||
class QVariant;
|
||||
class QImage;
|
||||
|
||||
namespace ImageConverter
|
||||
{
|
||||
/**
|
||||
* Returns a variant representing an image using the format describe in the
|
||||
* freedesktop.org spec
|
||||
*/
|
||||
QVariant variantForImage(const QImage &image);
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif /* IMAGECONVERTER_H */
|
||||
@@ -0,0 +1,654 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2013-2014 Martin Klapetek <mklapetek@kde.org>
|
||||
|
||||
code from KNotify/KNotifyClient
|
||||
SPDX-FileCopyrightText: 1997 Christian Esken <esken@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Charles Samuels <charles@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Stefan Schimanski <1Stein@gmx.de>
|
||||
SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
|
||||
SPDX-FileCopyrightText: 2000-2003 Carsten Pfeiffer <pfeiffer@kde.org>
|
||||
SPDX-FileCopyrightText: 2005 Allan Sandfeld Jensen <kde@carewolf.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#include "knotification.h"
|
||||
#include "debug_p.h"
|
||||
#include "knotification_p.h"
|
||||
#include "knotificationmanager_p.h"
|
||||
#include "knotificationreplyaction.h"
|
||||
|
||||
#include <config-knotifications.h>
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
||||
// incremental notification ID
|
||||
static int notificationIdCounter = 0;
|
||||
|
||||
class KNotificationActionPrivate
|
||||
{
|
||||
public:
|
||||
QString label;
|
||||
QString id;
|
||||
};
|
||||
|
||||
KNotificationAction::KNotificationAction(QObject *parent)
|
||||
: QObject(parent)
|
||||
, d(new KNotificationActionPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
KNotificationAction::KNotificationAction(const QString &label)
|
||||
: QObject()
|
||||
, d(new KNotificationActionPrivate)
|
||||
{
|
||||
d->label = label;
|
||||
}
|
||||
|
||||
KNotificationAction::~KNotificationAction()
|
||||
{
|
||||
}
|
||||
|
||||
QString KNotificationAction::label() const
|
||||
{
|
||||
return d->label;
|
||||
}
|
||||
|
||||
void KNotificationAction::setLabel(const QString &label)
|
||||
{
|
||||
if (d->label != label) {
|
||||
d->label = label;
|
||||
Q_EMIT labelChanged(label);
|
||||
}
|
||||
}
|
||||
|
||||
QString KNotificationAction::id() const
|
||||
{
|
||||
return d->id;
|
||||
}
|
||||
|
||||
void KNotificationAction::setId(const QString &id)
|
||||
{
|
||||
d->id = id;
|
||||
}
|
||||
|
||||
KNotification::KNotification(const QString &eventId, NotificationFlags flags, QObject *parent)
|
||||
: QObject(parent)
|
||||
, d(new Private)
|
||||
{
|
||||
d->eventId = eventId;
|
||||
d->flags = flags;
|
||||
connect(&d->updateTimer, &QTimer::timeout, this, &KNotification::update);
|
||||
d->updateTimer.setSingleShot(true);
|
||||
d->updateTimer.setInterval(100);
|
||||
d->id = ++notificationIdCounter;
|
||||
}
|
||||
|
||||
KNotification::~KNotification()
|
||||
{
|
||||
if (d->ownsActions) {
|
||||
qDeleteAll(d->actions);
|
||||
delete d->defaultAction;
|
||||
}
|
||||
|
||||
if (d->id >= 0) {
|
||||
KNotificationManager::self()->close(d->id);
|
||||
}
|
||||
}
|
||||
|
||||
QString KNotification::eventId() const
|
||||
{
|
||||
return d->eventId;
|
||||
}
|
||||
|
||||
void KNotification::setEventId(const QString &eventId)
|
||||
{
|
||||
if (d->eventId != eventId) {
|
||||
d->eventId = eventId;
|
||||
Q_EMIT eventIdChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString KNotification::title() const
|
||||
{
|
||||
return d->title;
|
||||
}
|
||||
|
||||
QString KNotification::text() const
|
||||
{
|
||||
return d->text;
|
||||
}
|
||||
|
||||
void KNotification::setTitle(const QString &title)
|
||||
{
|
||||
if (title == d->title) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->needUpdate = true;
|
||||
d->title = title;
|
||||
Q_EMIT titleChanged();
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
void KNotification::setText(const QString &text)
|
||||
{
|
||||
if (text == d->text) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->needUpdate = true;
|
||||
d->text = text;
|
||||
Q_EMIT textChanged();
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
void KNotification::setIconName(const QString &icon)
|
||||
{
|
||||
if (icon == d->iconName) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->needUpdate = true;
|
||||
d->iconName = icon;
|
||||
Q_EMIT iconNameChanged();
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
QString KNotification::iconName() const
|
||||
{
|
||||
return d->iconName;
|
||||
}
|
||||
|
||||
QPixmap KNotification::pixmap() const
|
||||
{
|
||||
return d->pixmap;
|
||||
}
|
||||
|
||||
void KNotification::setPixmap(const QPixmap &pix)
|
||||
{
|
||||
d->needUpdate = true;
|
||||
d->pixmap = pix;
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
QList<KNotificationAction *> KNotification::actions() const
|
||||
{
|
||||
return d->actions;
|
||||
}
|
||||
|
||||
void KNotification::clearActions()
|
||||
{
|
||||
if (d->ownsActions) {
|
||||
qDeleteAll(d->actions);
|
||||
}
|
||||
d->actions.clear();
|
||||
d->actionIdCounter = 1;
|
||||
|
||||
d->needUpdate = true;
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
KNotificationAction *KNotification::addAction(const QString &label)
|
||||
{
|
||||
d->needUpdate = true;
|
||||
|
||||
KNotificationAction *action = new KNotificationAction(label);
|
||||
action->setId(QString::number(d->actionIdCounter));
|
||||
d->actionIdCounter++;
|
||||
|
||||
d->actions << action;
|
||||
d->ownsActions = true;
|
||||
Q_EMIT actionsChanged();
|
||||
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
void KNotification::setActionsQml(QList<KNotificationAction *> actions)
|
||||
{
|
||||
if (actions == d->actions) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->actions.clear();
|
||||
|
||||
d->needUpdate = true;
|
||||
d->actions = actions;
|
||||
d->ownsActions = false;
|
||||
Q_EMIT actionsChanged();
|
||||
|
||||
int idCounter = 1;
|
||||
|
||||
for (KNotificationAction *action : d->actions) {
|
||||
action->setId(QString::number(idCounter));
|
||||
++idCounter;
|
||||
}
|
||||
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
KNotificationReplyAction *KNotification::replyAction() const
|
||||
{
|
||||
return d->replyAction.get();
|
||||
}
|
||||
|
||||
void KNotification::setReplyAction(std::unique_ptr<KNotificationReplyAction> replyAction)
|
||||
{
|
||||
if (replyAction == d->replyAction) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->needUpdate = true;
|
||||
d->replyAction = std::move(replyAction);
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
KNotificationAction *KNotification::addDefaultAction(const QString &label)
|
||||
{
|
||||
if (d->ownsActions) {
|
||||
delete d->defaultAction;
|
||||
}
|
||||
|
||||
d->needUpdate = true;
|
||||
d->ownsActions = true;
|
||||
d->defaultAction = new KNotificationAction(label);
|
||||
|
||||
d->defaultAction->setId(QStringLiteral("default"));
|
||||
|
||||
Q_EMIT defaultActionChanged();
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
|
||||
return d->defaultAction;
|
||||
}
|
||||
|
||||
void KNotification::setDefaultActionQml(KNotificationAction *defaultAction)
|
||||
{
|
||||
if (defaultAction == d->defaultAction) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->needUpdate = true;
|
||||
d->defaultAction = defaultAction;
|
||||
d->ownsActions = false;
|
||||
|
||||
d->defaultAction->setId(QStringLiteral("default"));
|
||||
|
||||
Q_EMIT defaultActionChanged();
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
KNotificationAction *KNotification::defaultAction() const
|
||||
{
|
||||
return d->defaultAction;
|
||||
}
|
||||
|
||||
KNotification::NotificationFlags KNotification::flags() const
|
||||
{
|
||||
return d->flags;
|
||||
}
|
||||
|
||||
void KNotification::setFlags(const NotificationFlags &flags)
|
||||
{
|
||||
if (d->flags == flags) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->needUpdate = true;
|
||||
d->flags = flags;
|
||||
Q_EMIT flagsChanged();
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
QString KNotification::componentName() const
|
||||
{
|
||||
return d->componentName;
|
||||
}
|
||||
|
||||
void KNotification::setComponentName(const QString &c)
|
||||
{
|
||||
if (d->componentName != c) {
|
||||
d->componentName = c;
|
||||
Q_EMIT componentNameChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QList<QUrl> KNotification::urls() const
|
||||
{
|
||||
return QUrl::fromStringList(d->hints[QStringLiteral("x-kde-urls")].toStringList());
|
||||
}
|
||||
|
||||
void KNotification::setUrls(const QList<QUrl> &urls)
|
||||
{
|
||||
setHint(QStringLiteral("x-kde-urls"), QUrl::toStringList(urls));
|
||||
Q_EMIT urlsChanged();
|
||||
}
|
||||
|
||||
KNotification::Urgency KNotification::urgency() const
|
||||
{
|
||||
return d->urgency;
|
||||
}
|
||||
|
||||
void KNotification::setUrgency(Urgency urgency)
|
||||
{
|
||||
if (d->urgency == urgency) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->needUpdate = true;
|
||||
d->urgency = urgency;
|
||||
Q_EMIT urgencyChanged();
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
void KNotification::activate(const QString &actionId)
|
||||
{
|
||||
if (d->defaultAction && actionId == QLatin1String("default")) {
|
||||
Q_EMIT d->defaultAction->activated();
|
||||
}
|
||||
|
||||
for (KNotificationAction *action : d->actions) {
|
||||
if (action->id() == actionId) {
|
||||
Q_EMIT action->activated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KNotification::close()
|
||||
{
|
||||
if (d->id >= 0) {
|
||||
KNotificationManager::self()->close(d->id);
|
||||
}
|
||||
|
||||
if (d->id == -1) {
|
||||
d->id = -2;
|
||||
Q_EMIT closed();
|
||||
if (d->autoDelete) {
|
||||
deleteLater();
|
||||
} else {
|
||||
// reset for being reused
|
||||
d->isNew = true;
|
||||
d->id = ++notificationIdCounter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static QString defaultComponentName()
|
||||
{
|
||||
#if defined(Q_OS_ANDROID)
|
||||
return QStringLiteral("android_defaults");
|
||||
#else
|
||||
return QStringLiteral("plasma_workspace");
|
||||
#endif
|
||||
}
|
||||
|
||||
KNotification *KNotification::event(const QString &eventid,
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
const QPixmap &pixmap,
|
||||
const NotificationFlags &flags,
|
||||
const QString &componentName)
|
||||
{
|
||||
KNotification *notify = new KNotification(eventid, flags);
|
||||
notify->setTitle(title);
|
||||
notify->setText(text);
|
||||
notify->setPixmap(pixmap);
|
||||
notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName);
|
||||
|
||||
QTimer::singleShot(0, notify, &KNotification::sendEvent);
|
||||
|
||||
return notify;
|
||||
}
|
||||
|
||||
KNotification *
|
||||
KNotification::event(const QString &eventid, const QString &text, const QPixmap &pixmap, const NotificationFlags &flags, const QString &componentName)
|
||||
{
|
||||
return event(eventid, QString(), text, pixmap, flags, componentName);
|
||||
}
|
||||
|
||||
KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QPixmap &pixmap, const NotificationFlags &flags)
|
||||
{
|
||||
return event(standardEventToEventId(eventid), title, text, pixmap, flags | DefaultEvent);
|
||||
}
|
||||
|
||||
KNotification *KNotification::event(StandardEvent eventid, const QString &text, const QPixmap &pixmap, const NotificationFlags &flags)
|
||||
{
|
||||
return event(eventid, QString(), text, pixmap, flags);
|
||||
}
|
||||
|
||||
KNotification *KNotification::event(const QString &eventid,
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
const QString &iconName,
|
||||
const NotificationFlags &flags,
|
||||
const QString &componentName)
|
||||
{
|
||||
KNotification *notify = new KNotification(eventid, flags);
|
||||
notify->setTitle(title);
|
||||
notify->setText(text);
|
||||
notify->setIconName(iconName);
|
||||
notify->setComponentName((flags & DefaultEvent) ? defaultComponentName() : componentName);
|
||||
|
||||
QTimer::singleShot(0, notify, &KNotification::sendEvent);
|
||||
|
||||
return notify;
|
||||
}
|
||||
|
||||
KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const QString &iconName, const NotificationFlags &flags)
|
||||
{
|
||||
return event(standardEventToEventId(eventid), title, text, iconName, flags | DefaultEvent);
|
||||
}
|
||||
|
||||
KNotification *KNotification::event(StandardEvent eventid, const QString &title, const QString &text, const NotificationFlags &flags)
|
||||
{
|
||||
return event(standardEventToEventId(eventid), title, text, standardEventToIconName(eventid), flags | DefaultEvent);
|
||||
}
|
||||
|
||||
void KNotification::ref()
|
||||
{
|
||||
d->ref++;
|
||||
}
|
||||
void KNotification::deref()
|
||||
{
|
||||
Q_ASSERT(d->ref > 0);
|
||||
d->ref--;
|
||||
if (d->ref == 0) {
|
||||
d->id = -1;
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
void KNotification::beep(const QString &reason)
|
||||
{
|
||||
event(QStringLiteral("beep"), reason, QPixmap(), CloseOnTimeout | DefaultEvent);
|
||||
}
|
||||
|
||||
void KNotification::sendEvent()
|
||||
{
|
||||
d->needUpdate = false;
|
||||
if (d->isNew) {
|
||||
d->isNew = false;
|
||||
KNotificationManager::self()->notify(this);
|
||||
} else {
|
||||
KNotificationManager::self()->reemit(this);
|
||||
}
|
||||
}
|
||||
|
||||
int KNotification::id()
|
||||
{
|
||||
if (!d) {
|
||||
return -1;
|
||||
}
|
||||
return d->id;
|
||||
}
|
||||
|
||||
QString KNotification::appName() const
|
||||
{
|
||||
QString appname;
|
||||
|
||||
if (d->flags & DefaultEvent) {
|
||||
appname = defaultComponentName();
|
||||
} else if (!d->componentName.isEmpty()) {
|
||||
appname = d->componentName;
|
||||
} else {
|
||||
appname = QCoreApplication::applicationName();
|
||||
}
|
||||
|
||||
return appname;
|
||||
}
|
||||
|
||||
bool KNotification::isAutoDelete() const
|
||||
{
|
||||
return d->autoDelete;
|
||||
}
|
||||
|
||||
void KNotification::setAutoDelete(bool autoDelete)
|
||||
{
|
||||
if (d->autoDelete != autoDelete) {
|
||||
d->autoDelete = autoDelete;
|
||||
Q_EMIT autoDeleteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void KNotification::update()
|
||||
{
|
||||
if (d->needUpdate) {
|
||||
KNotificationManager::self()->update(this);
|
||||
}
|
||||
}
|
||||
|
||||
QString KNotification::standardEventToEventId(KNotification::StandardEvent event)
|
||||
{
|
||||
QString eventId;
|
||||
switch (event) {
|
||||
case Warning:
|
||||
eventId = QStringLiteral("warning");
|
||||
break;
|
||||
case Error:
|
||||
eventId = QStringLiteral("fatalerror");
|
||||
break;
|
||||
case Catastrophe:
|
||||
eventId = QStringLiteral("catastrophe");
|
||||
break;
|
||||
case Notification: // fall through
|
||||
default:
|
||||
eventId = QStringLiteral("notification");
|
||||
break;
|
||||
}
|
||||
return eventId;
|
||||
}
|
||||
|
||||
QString KNotification::standardEventToIconName(KNotification::StandardEvent event)
|
||||
{
|
||||
QString iconName;
|
||||
switch (event) {
|
||||
case Warning:
|
||||
iconName = QStringLiteral("dialog-warning");
|
||||
break;
|
||||
case Error:
|
||||
iconName = QStringLiteral("dialog-error");
|
||||
break;
|
||||
case Catastrophe:
|
||||
iconName = QStringLiteral("dialog-error");
|
||||
break;
|
||||
case Notification: // fall through
|
||||
default:
|
||||
iconName = QStringLiteral("dialog-information");
|
||||
break;
|
||||
}
|
||||
return iconName;
|
||||
}
|
||||
|
||||
void KNotification::setHint(const QString &hint, const QVariant &value)
|
||||
{
|
||||
if (value == d->hints.value(hint)) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->needUpdate = true;
|
||||
d->hints[hint] = value;
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
Q_EMIT hintsChanged();
|
||||
}
|
||||
|
||||
QVariantMap KNotification::hints() const
|
||||
{
|
||||
return d->hints;
|
||||
}
|
||||
|
||||
void KNotification::setHints(const QVariantMap &hints)
|
||||
{
|
||||
if (hints == d->hints) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->needUpdate = true;
|
||||
d->hints = hints;
|
||||
if (d->id >= 0) {
|
||||
d->updateTimer.start();
|
||||
}
|
||||
Q_EMIT hintsChanged();
|
||||
}
|
||||
|
||||
QString KNotification::xdgActivationToken() const
|
||||
{
|
||||
return d->xdgActivationToken;
|
||||
}
|
||||
|
||||
void KNotification::setWindow(QWindow *window)
|
||||
{
|
||||
if (window == d->window) {
|
||||
return;
|
||||
}
|
||||
|
||||
disconnect(d->window, &QWindow::activeChanged, this, &KNotification::slotWindowActiveChanged);
|
||||
d->window = window;
|
||||
connect(d->window, &QWindow::activeChanged, this, &KNotification::slotWindowActiveChanged);
|
||||
}
|
||||
|
||||
void KNotification::slotWindowActiveChanged()
|
||||
{
|
||||
if (d->window->isActive() && (d->flags & CloseWhenWindowActivated)) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
QWindow *KNotification::window() const
|
||||
{
|
||||
return d->window;
|
||||
}
|
||||
|
||||
#include "moc_knotification.cpp"
|
||||
@@ -0,0 +1,827 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2013-2015 Martin Klapetek <mklapetek@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KNOTIFICATION_H
|
||||
#define KNOTIFICATION_H
|
||||
|
||||
#include <knotifications_export.h>
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QPixmap>
|
||||
#include <QUrl>
|
||||
#include <QVariant>
|
||||
#include <QWindow>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class KNotificationReplyAction;
|
||||
class KNotificationAction;
|
||||
|
||||
class KNotificationActionPrivate;
|
||||
|
||||
/**
|
||||
* @class KNotificationAction knotification.h KNotificationAction
|
||||
*
|
||||
* This class represents a notification. This can be a button on the notification
|
||||
* popup, or triggered by clicking the notification popup itself.
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
class KNOTIFICATIONS_EXPORT KNotificationAction : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
/**
|
||||
* @copydoc label
|
||||
*/
|
||||
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
|
||||
|
||||
public:
|
||||
explicit KNotificationAction(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Creates an action with given label
|
||||
* @param label The label for the action
|
||||
*/
|
||||
explicit KNotificationAction(const QString &label);
|
||||
|
||||
~KNotificationAction() override;
|
||||
|
||||
/**
|
||||
* The user-facing label for the action
|
||||
*/
|
||||
QString label() const;
|
||||
|
||||
/**
|
||||
* Set the user-facing label for the action
|
||||
*/
|
||||
void setLabel(const QString &label);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* Emitted when the user activates the action
|
||||
*/
|
||||
void activated();
|
||||
|
||||
/**
|
||||
* Emitted when @p label changed.
|
||||
*/
|
||||
void labelChanged(const QString &label);
|
||||
|
||||
private:
|
||||
friend class KNotification;
|
||||
friend class NotifyByPortalPrivate;
|
||||
friend class NotifyByPopup;
|
||||
friend class NotifyBySnore;
|
||||
friend class NotifyByAndroid;
|
||||
|
||||
void setId(const QString &id);
|
||||
QString id() const;
|
||||
|
||||
std::unique_ptr<KNotificationActionPrivate> const d;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class KNotification knotification.h KNotification
|
||||
*
|
||||
* KNotification is the main class for creating notifications.
|
||||
*/
|
||||
class KNOTIFICATIONS_EXPORT KNotification : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
/**
|
||||
* @copydoc setEventId
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(QString eventId READ eventId WRITE setEventId NOTIFY eventIdChanged)
|
||||
/**
|
||||
* @copydoc setTitle
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
|
||||
/**
|
||||
* @copydoc setText
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
|
||||
/**
|
||||
* @copydoc setIconName
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(QString iconName READ iconName WRITE setIconName NOTIFY iconNameChanged)
|
||||
/**
|
||||
* @copydoc setFlags
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(NotificationFlags flags READ flags WRITE setFlags NOTIFY flagsChanged)
|
||||
/**
|
||||
* @copydoc setComponentName
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(QString componentName READ componentName WRITE setComponentName NOTIFY componentNameChanged)
|
||||
/**
|
||||
* @copydoc setUrls
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(QList<QUrl> urls READ urls WRITE setUrls NOTIFY urlsChanged)
|
||||
/**
|
||||
* @copydoc setUrgency
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(Urgency urgency READ urgency WRITE setUrgency NOTIFY urgencyChanged)
|
||||
/**
|
||||
* @copydoc setAutoDelete
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(bool autoDelete READ isAutoDelete WRITE setAutoDelete NOTIFY autoDeleteChanged)
|
||||
/**
|
||||
* @since 5.90
|
||||
*/
|
||||
Q_PROPERTY(QString xdgActivationToken READ xdgActivationToken NOTIFY xdgActivationTokenChanged)
|
||||
/**
|
||||
* @copydoc setHint
|
||||
* @since 5.101
|
||||
*/
|
||||
Q_PROPERTY(QVariantMap hints READ hints WRITE setHints NOTIFY hintsChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @see NotificationFlags
|
||||
*/
|
||||
enum NotificationFlag {
|
||||
/**
|
||||
* The notification will be automatically closed after a timeout. (this is the default)
|
||||
*/
|
||||
CloseOnTimeout = 0x00,
|
||||
|
||||
/**
|
||||
* The notification will NOT be automatically closed after a timeout.
|
||||
* You will have to track the notification, and close it with the
|
||||
* close function manually when the event is done, otherwise there will be a memory leak
|
||||
*/
|
||||
Persistent = 0x02,
|
||||
|
||||
/**
|
||||
* The audio plugin will loop the sound until the notification is closed
|
||||
*/
|
||||
LoopSound = 0x08,
|
||||
|
||||
/**
|
||||
* Sends a hint to Plasma to skip grouping for this notification
|
||||
*
|
||||
* @since 5.18
|
||||
*/
|
||||
SkipGrouping = 0x10,
|
||||
|
||||
/**
|
||||
* The notification will be automatically closed if the window() becomes
|
||||
* activated.
|
||||
*
|
||||
* You need to set a window using setWindow().
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
CloseWhenWindowActivated = 0x20,
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* The event is a standard kde event, and not an event of the application
|
||||
*/
|
||||
DefaultEvent = 0xF000,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores a combination of #NotificationFlag values.
|
||||
*/
|
||||
Q_DECLARE_FLAGS(NotificationFlags, NotificationFlag)
|
||||
Q_FLAG(NotificationFlags)
|
||||
|
||||
/**
|
||||
* default events you can use in the event function
|
||||
*/
|
||||
enum StandardEvent {
|
||||
Notification,
|
||||
Warning,
|
||||
Error,
|
||||
Catastrophe,
|
||||
};
|
||||
|
||||
/**
|
||||
* The urgency of a notification.
|
||||
*
|
||||
* @since 5.58
|
||||
* @sa setUrgency
|
||||
*/
|
||||
enum Urgency {
|
||||
DefaultUrgency = -1,
|
||||
LowUrgency = 10,
|
||||
NormalUrgency = 50,
|
||||
HighUrgency = 70,
|
||||
CriticalUrgency = 90,
|
||||
};
|
||||
Q_ENUM(Urgency)
|
||||
|
||||
/**
|
||||
* Create a new notification.
|
||||
*
|
||||
* You have to use sendEvent to show the notification.
|
||||
*
|
||||
* The pointer is automatically deleted when the event is closed.
|
||||
*
|
||||
* @since 4.4
|
||||
*
|
||||
* @param eventId is the name of the event
|
||||
* @param flags is a bitmask of NotificationFlag
|
||||
* @param parent parent object
|
||||
*/
|
||||
explicit KNotification(const QString &eventId, NotificationFlags flags = CloseOnTimeout, QObject *parent = nullptr);
|
||||
|
||||
~KNotification() override;
|
||||
|
||||
/**
|
||||
* @return the name of the event
|
||||
*/
|
||||
QString eventId() const;
|
||||
/**
|
||||
* Set the event id, if not already passed to the constructor.
|
||||
* @since 5.88
|
||||
*/
|
||||
void setEventId(const QString &eventId);
|
||||
|
||||
/**
|
||||
* @return the notification title
|
||||
* @see setTitle
|
||||
* @since 4.3
|
||||
*/
|
||||
QString title() const;
|
||||
|
||||
/**
|
||||
* Set the title of the notification popup.
|
||||
* If no title is set, the application name will be used.
|
||||
*
|
||||
* @param title The title of the notification
|
||||
* @since 4.3
|
||||
*/
|
||||
void setTitle(const QString &title);
|
||||
|
||||
/**
|
||||
* @return the notification text
|
||||
* @see setText
|
||||
*/
|
||||
QString text() const;
|
||||
|
||||
/**
|
||||
* Set the notification text that will appear in the popup.
|
||||
*
|
||||
* In Plasma workspace, the text is shown in a QML label which uses Text.StyledText,
|
||||
* ie. it supports a small subset of HTML entities (mostly just formatting tags)
|
||||
*
|
||||
* If the notifications server does not advertise "body-markup" capability,
|
||||
* all HTML tags are stripped before sending it to the server
|
||||
*
|
||||
* @param text The text to display in the notification popup
|
||||
*/
|
||||
void setText(const QString &text);
|
||||
|
||||
/**
|
||||
* \return the icon shown in the popup
|
||||
* \see setIconName
|
||||
* \since 5.4
|
||||
*/
|
||||
QString iconName() const;
|
||||
|
||||
/**
|
||||
* Set the icon that will be shown in the popup.
|
||||
*
|
||||
* @param icon the icon
|
||||
* @since 5.4
|
||||
*/
|
||||
void setIconName(const QString &icon);
|
||||
|
||||
/**
|
||||
* \return the pixmap shown in the popup
|
||||
* \see setPixmap
|
||||
*/
|
||||
QPixmap pixmap() const;
|
||||
/**
|
||||
* Set the pixmap that will be shown in the popup. If you want to use an icon from the icon theme use setIconName instead.
|
||||
*
|
||||
* @param pix the pixmap
|
||||
*/
|
||||
void setPixmap(const QPixmap &pix);
|
||||
|
||||
/**
|
||||
* @return the default action, or nullptr if none is set
|
||||
* @since 6.0
|
||||
*/
|
||||
KNotificationAction *defaultAction() const;
|
||||
|
||||
/**
|
||||
* Add a default action that will be triggered when the notification is
|
||||
* activated (typically, by clicking on the notification popup). The default
|
||||
* action typically raises a window belonging to the application that sent it.
|
||||
*
|
||||
* The string will be used as a label for the action, so ideally it should
|
||||
* be wrapped in i18n() or tr() calls.
|
||||
*
|
||||
* The visual representation of actions depends on the notification server.
|
||||
* In Plasma and Gnome desktops, the actions are performed by clicking on
|
||||
* the notification popup, and the label is not presented to the user.
|
||||
*
|
||||
* Calling this overrides the current default action
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
[[nodiscard]] KNotificationAction *addDefaultAction(const QString &label);
|
||||
|
||||
/**
|
||||
* Add an action to the notification.
|
||||
*
|
||||
* The visual representation of actions depends
|
||||
* on the notification server.
|
||||
*
|
||||
* @param label the user-visible label of the action
|
||||
*
|
||||
* @see KNotificationAction
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
[[nodiscard]] KNotificationAction *addAction(const QString &label);
|
||||
|
||||
/**
|
||||
* Removes all actions previously added by addAction()
|
||||
* from the notification.
|
||||
*
|
||||
* @see addAction
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
void clearActions();
|
||||
|
||||
/**
|
||||
* @return the inline reply action.
|
||||
* @since 5.81
|
||||
*/
|
||||
KNotificationReplyAction *replyAction() const;
|
||||
|
||||
/**
|
||||
* @brief Add an inline reply action to the notification.
|
||||
*
|
||||
* On supported platforms this lets the user type a reply to a notification,
|
||||
* such as replying to a chat message, from the notification popup, for example:
|
||||
*
|
||||
* @code{.cpp}
|
||||
* KNotification *notification = new KNotification(QStringLiteral("notification"));
|
||||
* ...
|
||||
* auto replyAction = std::make_unique<KNotificationReplyAction>(i18nc("@action:button", "Reply"));
|
||||
* replyAction->setPlaceholderText(i18nc("@info:placeholder", "Reply to Dave..."));
|
||||
* QObject::connect(replyAction.get(), &KNotificationReplyAction::replied, [](const QString &text) {
|
||||
* qDebug() << "you replied with" << text;
|
||||
* });
|
||||
* notification->setReplyAction(std::move(replyAction));
|
||||
* @endcode
|
||||
*
|
||||
* @param replyAction the reply action to set
|
||||
* @since 5.81
|
||||
*/
|
||||
void setReplyAction(std::unique_ptr<KNotificationReplyAction> replyAction);
|
||||
|
||||
/**
|
||||
* @return the notification flags.
|
||||
*/
|
||||
NotificationFlags flags() const;
|
||||
|
||||
/**
|
||||
* Set the notification flags.
|
||||
* These must be set before calling sendEvent()
|
||||
*/
|
||||
void setFlags(const NotificationFlags &flags);
|
||||
|
||||
/**
|
||||
* Returns the component name used to determine the location of the configuration file.
|
||||
* @since 5.88
|
||||
*/
|
||||
QString componentName() const;
|
||||
/**
|
||||
* The componentData is used to determine the location of the config file.
|
||||
*
|
||||
* If no componentName is set, the app name is used by default
|
||||
*
|
||||
* @param componentName the new component name
|
||||
*/
|
||||
void setComponentName(const QString &componentName);
|
||||
|
||||
/**
|
||||
* URLs associated with this notification
|
||||
* @since 5.29
|
||||
*/
|
||||
QList<QUrl> urls() const;
|
||||
|
||||
/**
|
||||
* Sets URLs associated with this notification
|
||||
*
|
||||
* For example, a screenshot application might want to provide the
|
||||
* URL to the file that was just taken so the notification service
|
||||
* can show a preview.
|
||||
*
|
||||
* @note This feature might not be supported by the user's notification service
|
||||
*
|
||||
* @param urls A list of URLs
|
||||
* @since 5.29
|
||||
*/
|
||||
void setUrls(const QList<QUrl> &urls);
|
||||
|
||||
/**
|
||||
* The urgency of the notification.
|
||||
* @since 5.58
|
||||
*/
|
||||
Urgency urgency() const;
|
||||
|
||||
/**
|
||||
* Sets the urgency of the notification.
|
||||
*
|
||||
* This defines the importance of the notification. For example,
|
||||
* a track change in a media player would be a low urgency.
|
||||
* "You have new mail" would be normal urgency. "Your battery level
|
||||
* is low" would be a critical urgency.
|
||||
*
|
||||
* Use critical notifications with care as they might be shown even
|
||||
* when giving a presentation or when notifications are turned off.
|
||||
*
|
||||
* @param urgency The urgency.
|
||||
* @since 5.58
|
||||
*/
|
||||
void setUrgency(Urgency urgency);
|
||||
|
||||
/**
|
||||
* Sets the window associated with this notification.
|
||||
* This is relevant when using the CloseWhenWindowActivated flag.
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
void setWindow(QWindow *window);
|
||||
|
||||
/**
|
||||
* The window associated with this notification. nullptr by default.
|
||||
* @return the window set by setWindow()
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
QWindow *window() const;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* appname used for the D-Bus object
|
||||
*/
|
||||
QString appName() const;
|
||||
|
||||
/**
|
||||
* Returns whether this notification object will be automatically deleted after closing.
|
||||
* @since 5.88
|
||||
*/
|
||||
bool isAutoDelete() const;
|
||||
/**
|
||||
* Sets whether this notification object will be automatically deleted after closing.
|
||||
* This is on by default for C++, and off by default for QML.
|
||||
* @since 5.88
|
||||
*/
|
||||
void setAutoDelete(bool autoDelete);
|
||||
|
||||
/**
|
||||
* Returns the activation token to use to activate a window.
|
||||
* @since 5.90
|
||||
*/
|
||||
QString xdgActivationToken() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* Emitted when the notification is closed.
|
||||
*
|
||||
* Can be closed either by the user clicking the close button,
|
||||
* the timeout running out or when an action was triggered.
|
||||
*/
|
||||
void closed();
|
||||
|
||||
/**
|
||||
* The notification has been ignored
|
||||
*/
|
||||
void ignored();
|
||||
|
||||
/**
|
||||
* Emitted when @c eventId changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void eventIdChanged();
|
||||
/**
|
||||
* Emitted when @c title changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void titleChanged();
|
||||
/**
|
||||
* Emitted when @c text changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void textChanged();
|
||||
/**
|
||||
* Emitted when @c iconName changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void iconNameChanged();
|
||||
/**
|
||||
* Emitted when @c defaultAction changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void defaultActionChanged();
|
||||
/**
|
||||
* Emitted when @c actions changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void actionsChanged();
|
||||
/**
|
||||
* Emitted when @p flags changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void flagsChanged();
|
||||
/**
|
||||
* Emitted when @p componentName changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void componentNameChanged();
|
||||
/**
|
||||
* Emitted when @p urls changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void urlsChanged();
|
||||
/**
|
||||
* Emitted when @p urgency changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void urgencyChanged();
|
||||
/**
|
||||
* Emitted when @p autoDelete changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void autoDeleteChanged();
|
||||
/**
|
||||
* Emitted when @p xdgActivationToken changes.
|
||||
* @since 5.90
|
||||
*/
|
||||
void xdgActivationTokenChanged();
|
||||
/**
|
||||
* Emitted when @p hints changes.
|
||||
* @since 5.101
|
||||
*/
|
||||
void hintsChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* Close the notification without activating it.
|
||||
*
|
||||
* This will delete the notification.
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Send the notification to the server.
|
||||
*
|
||||
* This will cause all the configured plugins to execute their actions on this notification
|
||||
* (eg. a sound will play, a popup will show, a command will be executed etc).
|
||||
*/
|
||||
void sendEvent();
|
||||
|
||||
/**
|
||||
* @since 5.57
|
||||
* Adds a custom hint to the notification. Those are key-value pairs that can be interpreted by the respective notification backend to trigger additional,
|
||||
* non-standard features.
|
||||
* @param hint the hint's key
|
||||
* @param value the hint's value
|
||||
*/
|
||||
Q_INVOKABLE void setHint(const QString &hint, const QVariant &value);
|
||||
|
||||
/**
|
||||
* @since 5.57
|
||||
* Returns the custom hints set by setHint()
|
||||
*/
|
||||
QVariantMap hints() const;
|
||||
|
||||
/**
|
||||
* @since 5.101
|
||||
* Set custom hints on the notification.
|
||||
* @sa setHint
|
||||
*/
|
||||
void setHints(const QVariantMap &hints);
|
||||
|
||||
private:
|
||||
friend class KNotificationManager;
|
||||
friend class NotificationWrapper;
|
||||
friend class NotifyByPopup;
|
||||
friend class NotifyByPortal;
|
||||
friend class NotifyByPortalPrivate;
|
||||
friend class NotifyByExecute;
|
||||
friend class NotifyBySnore;
|
||||
friend class NotifyByAndroid;
|
||||
friend class NotifyByMacOSNotificationCenter;
|
||||
struct Private;
|
||||
|
||||
KNOTIFICATIONS_NO_EXPORT void slotWindowActiveChanged();
|
||||
|
||||
/**
|
||||
* @brief Activate the action specified action
|
||||
* If the action is zero, then the default action is activated
|
||||
*/
|
||||
KNOTIFICATIONS_NO_EXPORT void activate(const QString &action);
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* the id given by the notification manager
|
||||
*/
|
||||
KNOTIFICATIONS_NO_EXPORT int id();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* update the texts, the icon, and the actions of one existing notification
|
||||
*/
|
||||
KNOTIFICATIONS_NO_EXPORT void update();
|
||||
|
||||
/**
|
||||
* The notification will automatically be closed if all presentations are finished.
|
||||
* if you want to show your own presentation in your application, you should use this
|
||||
* function, so it will not be automatically closed when there is nothing to show.
|
||||
*
|
||||
* Don't forgot to deref, or the notification may be never closed if there is no timeout.
|
||||
*
|
||||
* @see deref
|
||||
*/
|
||||
KNOTIFICATIONS_NO_EXPORT void ref();
|
||||
|
||||
/**
|
||||
* Remove a reference made with ref(). If the ref counter hits zero,
|
||||
* the notification will be closed and deleted.
|
||||
*
|
||||
* @see ref
|
||||
*/
|
||||
KNOTIFICATIONS_NO_EXPORT void deref();
|
||||
|
||||
// Like setActions, but doesn't take ownership
|
||||
void setActionsQml(QList<KNotificationAction *> actions);
|
||||
void setDefaultActionQml(KNotificationAction *action);
|
||||
QList<KNotificationAction *> actions() const;
|
||||
|
||||
static QString standardEventToEventId(StandardEvent event);
|
||||
static QString standardEventToIconName(StandardEvent event);
|
||||
|
||||
std::unique_ptr<Private> const d;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief emit an event
|
||||
*
|
||||
* This method creates the KNotification, setting every parameter, and fire the event.
|
||||
* You don't need to call sendEvent
|
||||
*
|
||||
* A popup may be displayed or a sound may be played, depending the config.
|
||||
*
|
||||
* @return a KNotification . You may use that pointer to connect some signals or slot.
|
||||
* the pointer is automatically deleted when the event is closed.
|
||||
*
|
||||
* @param eventId is the name of the event
|
||||
* @param title is title of the notification to show in the popup.
|
||||
* @param text is the text of the notification to show in the popup.
|
||||
* @param pixmap is a picture which may be shown in the popup.
|
||||
* @param flags is a bitmask of NotificationFlag
|
||||
* @param componentName used to determine the location of the config file. by default, appname is used
|
||||
* @since 4.4
|
||||
*/
|
||||
static KNotification *event(const QString &eventId,
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
const QPixmap &pixmap = QPixmap(),
|
||||
const NotificationFlags &flags = CloseOnTimeout,
|
||||
const QString &componentName = QString());
|
||||
|
||||
/**
|
||||
* @brief emit a standard event
|
||||
*
|
||||
* @overload
|
||||
*
|
||||
* This will emit a standard event
|
||||
*
|
||||
* @param eventId is the name of the event
|
||||
* @param text is the text of the notification to show in the popup.
|
||||
* @param pixmap is a picture which may be shown in the popup.
|
||||
* @param flags is a bitmask of NotificationFlag
|
||||
* @param componentName used to determine the location of the config file. by default, plasma_workspace is used
|
||||
*/
|
||||
static KNotification *event(const QString &eventId,
|
||||
const QString &text = QString(),
|
||||
const QPixmap &pixmap = QPixmap(),
|
||||
const NotificationFlags &flags = CloseOnTimeout,
|
||||
const QString &componentName = QString());
|
||||
|
||||
/**
|
||||
* @brief emit a standard event
|
||||
*
|
||||
* @overload
|
||||
*
|
||||
* This will emit a standard event
|
||||
*
|
||||
* @param eventId is the name of the event
|
||||
* @param text is the text of the notification to show in the popup
|
||||
* @param pixmap is a picture which may be shown in the popup
|
||||
* @param flags is a bitmask of NotificationFlag
|
||||
*/
|
||||
static KNotification *
|
||||
event(StandardEvent eventId, const QString &text = QString(), const QPixmap &pixmap = QPixmap(), const NotificationFlags &flags = CloseOnTimeout);
|
||||
|
||||
/**
|
||||
* @brief emit a standard event
|
||||
*
|
||||
* @overload
|
||||
*
|
||||
* This will emit a standard event
|
||||
*
|
||||
* @param eventId is the name of the event
|
||||
* @param title is title of the notification to show in the popup.
|
||||
* @param text is the text of the notification to show in the popup
|
||||
* @param pixmap is a picture which may be shown in the popup
|
||||
* @param flags is a bitmask of NotificationFlag
|
||||
* @since 4.4
|
||||
*/
|
||||
static KNotification *
|
||||
event(StandardEvent eventId, const QString &title, const QString &text, const QPixmap &pixmap, const NotificationFlags &flags = CloseOnTimeout);
|
||||
|
||||
/**
|
||||
* @brief emit a standard event with the possibility of setting an icon by icon name
|
||||
*
|
||||
* @overload
|
||||
*
|
||||
* This will emit a standard event
|
||||
*
|
||||
* @param eventId is the name of the event
|
||||
* @param title is title of the notification to show in the popup.
|
||||
* @param text is the text of the notification to show in the popup
|
||||
* @param iconName a Freedesktop compatible icon name to be shown in the popup
|
||||
* @param flags is a bitmask of NotificationFlag
|
||||
* @param componentName used to determine the location of the config file. by default, plasma_workspace is used
|
||||
* @since 5.4
|
||||
*/
|
||||
static KNotification *event(const QString &eventId,
|
||||
const QString &title,
|
||||
const QString &text,
|
||||
const QString &iconName,
|
||||
const NotificationFlags &flags = CloseOnTimeout,
|
||||
const QString &componentName = QString());
|
||||
|
||||
/**
|
||||
* @brief emit a standard event with the possibility of setting an icon by icon name
|
||||
*
|
||||
* @overload
|
||||
*
|
||||
* This will emit a standard event with a custom icon
|
||||
*
|
||||
* @param eventId the type of the standard (not app-defined) event
|
||||
* @param title is title of the notification to show in the popup.
|
||||
* @param text is the text of the notification to show in the popup
|
||||
* @param iconName a Freedesktop compatible icon name to be shown in the popup
|
||||
* @param flags is a bitmask of NotificationFlag
|
||||
* @since 5.9
|
||||
*/
|
||||
static KNotification *
|
||||
event(StandardEvent eventId, const QString &title, const QString &text, const QString &iconName, const NotificationFlags &flags = CloseOnTimeout);
|
||||
|
||||
/**
|
||||
* @brief emit a standard event
|
||||
*
|
||||
* @overload
|
||||
*
|
||||
* This will emit a standard event with its standard icon
|
||||
*
|
||||
* @param eventId the type of the standard (not app-defined) event
|
||||
* @param title is title of the notification to show in the popup.
|
||||
* @param text is the text of the notification to show in the popup
|
||||
* @param flags is a bitmask of NotificationFlag
|
||||
* @since 5.9
|
||||
*/
|
||||
static KNotification *event(StandardEvent eventId, const QString &title, const QString &text, const NotificationFlags &flags = CloseOnTimeout);
|
||||
|
||||
/**
|
||||
* This is a simple substitution for QApplication::beep()
|
||||
*
|
||||
* @param reason a short text explaining what has happened (may be empty)
|
||||
*/
|
||||
static void beep(const QString &reason = QString());
|
||||
|
||||
// prevent warning
|
||||
using QObject::event;
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(KNotification::NotificationFlags)
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2013-2014 Martin Klapetek <mklapetek@kde.org>
|
||||
|
||||
code from KNotify/KNotifyClient
|
||||
SPDX-FileCopyrightText: 1997 Christian Esken <esken@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Charles Samuels <charles@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Stefan Schimanski <1Stein@gmx.de>
|
||||
SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
|
||||
SPDX-FileCopyrightText: 2000-2003 Carsten Pfeiffer <pfeiffer@kde.org>
|
||||
SPDX-FileCopyrightText: 2005 Allan Sandfeld Jensen <kde@carewolf.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KNOITIFICATION_P_H
|
||||
#define KNOITIFICATION_P_H
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
struct Q_DECL_HIDDEN KNotification::Private {
|
||||
QString eventId;
|
||||
int id = -1;
|
||||
int ref = 0;
|
||||
|
||||
QString title;
|
||||
QString text;
|
||||
QString iconName;
|
||||
KNotificationAction *defaultAction = nullptr;
|
||||
QList<KNotificationAction *> actions;
|
||||
bool ownsActions = true;
|
||||
QString xdgActivationToken;
|
||||
std::unique_ptr<KNotificationReplyAction> replyAction;
|
||||
QPixmap pixmap;
|
||||
NotificationFlags flags = KNotification::CloseOnTimeout;
|
||||
QString componentName;
|
||||
KNotification::Urgency urgency = KNotification::DefaultUrgency;
|
||||
QVariantMap hints;
|
||||
|
||||
QTimer updateTimer;
|
||||
bool needUpdate = false;
|
||||
bool isNew = true;
|
||||
bool autoDelete = true;
|
||||
QWindow *window = nullptr;
|
||||
int actionIdCounter = 1;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2005 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2013-2015 Martin Klapetek <mklapetek@kde.org>
|
||||
SPDX-FileCopyrightText: 2017 Eike Hein <hein@kde.org>
|
||||
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#include "knotification.h"
|
||||
#include "knotification_p.h"
|
||||
#include "knotificationmanager_p.h"
|
||||
|
||||
#include <config-knotifications.h>
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusConnectionInterface>
|
||||
#endif
|
||||
|
||||
#include "knotificationplugin.h"
|
||||
#include "knotificationreplyaction.h"
|
||||
#include "knotifyconfig.h"
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#include "notifybyandroid.h"
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#include "notifybymacosnotificationcenter.h"
|
||||
#elif defined(WITH_SNORETOAST)
|
||||
#include "notifybysnore.h"
|
||||
#elif defined(HAVE_DBUS)
|
||||
#include "notifybypopup.h"
|
||||
#include "notifybyportal.h"
|
||||
#endif
|
||||
#include "debug_p.h"
|
||||
|
||||
#if defined(HAVE_CANBERRA)
|
||||
#include "notifybyaudio.h"
|
||||
#endif
|
||||
|
||||
typedef QHash<QString, QString> Dict;
|
||||
|
||||
struct Q_DECL_HIDDEN KNotificationManager::Private {
|
||||
QHash<int, KNotification *> notifications;
|
||||
QHash<QString, KNotificationPlugin *> notifyPlugins;
|
||||
|
||||
QStringList dirtyConfigCache;
|
||||
bool portalDBusServiceExists = false;
|
||||
};
|
||||
|
||||
class KNotificationManagerSingleton
|
||||
{
|
||||
public:
|
||||
KNotificationManager instance;
|
||||
};
|
||||
|
||||
Q_GLOBAL_STATIC(KNotificationManagerSingleton, s_self)
|
||||
|
||||
KNotificationManager *KNotificationManager::self()
|
||||
{
|
||||
return &s_self()->instance;
|
||||
}
|
||||
|
||||
KNotificationManager::KNotificationManager()
|
||||
: d(new Private)
|
||||
{
|
||||
qDeleteAll(d->notifyPlugins);
|
||||
d->notifyPlugins.clear();
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
if (isInsideSandbox()) {
|
||||
QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
|
||||
d->portalDBusServiceExists = interface->isServiceRegistered(QStringLiteral("org.freedesktop.portal.Desktop"));
|
||||
}
|
||||
|
||||
QDBusConnection::sessionBus().connect(QString(),
|
||||
QStringLiteral("/Config"),
|
||||
QStringLiteral("org.kde.knotification"),
|
||||
QStringLiteral("reparseConfiguration"),
|
||||
this,
|
||||
SLOT(reparseConfiguration(QString)));
|
||||
#endif
|
||||
}
|
||||
|
||||
KNotificationManager::~KNotificationManager() = default;
|
||||
|
||||
KNotificationPlugin *KNotificationManager::pluginForAction(const QString &action)
|
||||
{
|
||||
KNotificationPlugin *plugin = d->notifyPlugins.value(action);
|
||||
|
||||
// We already loaded a plugin for this action.
|
||||
if (plugin) {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
auto addPlugin = [this](KNotificationPlugin *plugin) {
|
||||
d->notifyPlugins[plugin->optionName()] = plugin;
|
||||
connect(plugin, &KNotificationPlugin::finished, this, &KNotificationManager::notifyPluginFinished);
|
||||
connect(plugin, &KNotificationPlugin::xdgActivationTokenReceived, this, &KNotificationManager::xdgActivationTokenReceived);
|
||||
connect(plugin, &KNotificationPlugin::actionInvoked, this, &KNotificationManager::notificationActivated);
|
||||
connect(plugin, &KNotificationPlugin::replied, this, &KNotificationManager::notificationReplied);
|
||||
};
|
||||
|
||||
// Load plugin.
|
||||
// We have a series of built-ins up first, and fall back to trying
|
||||
// to instantiate an externally supplied plugin.
|
||||
if (action == QLatin1String("Popup")) {
|
||||
#if defined(Q_OS_ANDROID)
|
||||
plugin = new NotifyByAndroid(this);
|
||||
#elif defined(WITH_SNORETOAST)
|
||||
plugin = new NotifyBySnore(this);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
plugin = new NotifyByMacOSNotificationCenter(this);
|
||||
#elif defined(HAVE_DBUS)
|
||||
if (d->portalDBusServiceExists) {
|
||||
plugin = new NotifyByPortal(this);
|
||||
} else {
|
||||
plugin = new NotifyByPopup(this);
|
||||
}
|
||||
#endif
|
||||
addPlugin(plugin);
|
||||
} else if (action == QLatin1String("Sound")) {
|
||||
#if defined(HAVE_CANBERRA)
|
||||
plugin = new NotifyByAudio(this);
|
||||
addPlugin(plugin);
|
||||
#endif
|
||||
}
|
||||
|
||||
return plugin;
|
||||
}
|
||||
|
||||
void KNotificationManager::notifyPluginFinished(KNotification *notification)
|
||||
{
|
||||
if (!notification || !d->notifications.contains(notification->id())) {
|
||||
return;
|
||||
}
|
||||
|
||||
notification->deref();
|
||||
}
|
||||
|
||||
void KNotificationManager::notificationActivated(int id, const QString &actionId)
|
||||
{
|
||||
if (d->notifications.contains(id)) {
|
||||
qCDebug(LOG_KNOTIFICATIONS) << id << " " << actionId;
|
||||
KNotification *n = d->notifications[id];
|
||||
n->activate(actionId);
|
||||
|
||||
// Resident actions delegate control over notification lifetime to the client
|
||||
if (!n->hints().value(QStringLiteral("resident")).toBool()) {
|
||||
close(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KNotificationManager::xdgActivationTokenReceived(int id, const QString &token)
|
||||
{
|
||||
KNotification *n = d->notifications.value(id);
|
||||
if (n) {
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "Token received for" << id << token;
|
||||
n->d->xdgActivationToken = token;
|
||||
Q_EMIT n->xdgActivationTokenChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void KNotificationManager::notificationReplied(int id, const QString &text)
|
||||
{
|
||||
if (KNotification *n = d->notifications.value(id)) {
|
||||
if (auto *replyAction = n->replyAction()) {
|
||||
// cannot really send out a "activate inline-reply" signal from plugin to manager
|
||||
// so we instead assume empty reply is not supported and means normal invocation
|
||||
if (text.isEmpty() && replyAction->fallbackBehavior() == KNotificationReplyAction::FallbackBehavior::UseRegularAction) {
|
||||
Q_EMIT replyAction->activated();
|
||||
} else {
|
||||
Q_EMIT replyAction->replied(text);
|
||||
}
|
||||
close(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KNotificationManager::notificationClosed()
|
||||
{
|
||||
KNotification *notification = qobject_cast<KNotification *>(sender());
|
||||
if (!notification) {
|
||||
return;
|
||||
}
|
||||
// We cannot do d->notifications.find(notification->id()); here because the
|
||||
// notification->id() is -1 or -2 at this point, so we need to look for value
|
||||
for (auto iter = d->notifications.begin(); iter != d->notifications.end(); ++iter) {
|
||||
if (iter.value() == notification) {
|
||||
d->notifications.erase(iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KNotificationManager::close(int id)
|
||||
{
|
||||
if (d->notifications.contains(id)) {
|
||||
KNotification *n = d->notifications.value(id);
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "Closing notification" << id;
|
||||
|
||||
// Find plugins that are actually acting on this notification
|
||||
// call close() only on those, otherwise each KNotificationPlugin::close()
|
||||
// will call finish() which may close-and-delete the KNotification object
|
||||
// before it finishes calling close on all the other plugins.
|
||||
// For example: Action=Popup is a single actions but there is 5 loaded
|
||||
// plugins, calling close() on the second would already close-and-delete
|
||||
// the notification
|
||||
KNotifyConfig notifyConfig(n->appName(), n->eventId());
|
||||
QString notifyActions = notifyConfig.readEntry(QStringLiteral("Action"));
|
||||
|
||||
const auto listActions = notifyActions.split(QLatin1Char('|'));
|
||||
for (const QString &action : listActions) {
|
||||
if (!d->notifyPlugins.contains(action)) {
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "No plugin for action" << action;
|
||||
continue;
|
||||
}
|
||||
|
||||
d->notifyPlugins[action]->close(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KNotificationManager::notify(KNotification *n)
|
||||
{
|
||||
KNotifyConfig notifyConfig(n->appName(), n->eventId());
|
||||
|
||||
if (d->dirtyConfigCache.contains(n->appName())) {
|
||||
notifyConfig.reparseSingleConfiguration(n->appName());
|
||||
d->dirtyConfigCache.removeOne(n->appName());
|
||||
}
|
||||
|
||||
if (!notifyConfig.isValid()) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "No event config could be found for event id" << n->eventId() << "under notifyrc file for app" << n->appName();
|
||||
}
|
||||
|
||||
const QString notifyActions = notifyConfig.readEntry(QStringLiteral("Action"));
|
||||
|
||||
if (notifyActions.isEmpty() || notifyActions == QLatin1String("None")) {
|
||||
// this will cause KNotification closing itself fast
|
||||
n->ref();
|
||||
n->deref();
|
||||
return;
|
||||
}
|
||||
|
||||
d->notifications.insert(n->id(), n);
|
||||
|
||||
// TODO KF6 d-pointer KNotifyConfig and add this there
|
||||
if (n->urgency() == KNotification::DefaultUrgency) {
|
||||
const QString urgency = notifyConfig.readEntry(QStringLiteral("Urgency"));
|
||||
if (urgency == QLatin1String("Low")) {
|
||||
n->setUrgency(KNotification::LowUrgency);
|
||||
} else if (urgency == QLatin1String("Normal")) {
|
||||
n->setUrgency(KNotification::NormalUrgency);
|
||||
} else if (urgency == QLatin1String("High")) {
|
||||
n->setUrgency(KNotification::HighUrgency);
|
||||
} else if (urgency == QLatin1String("Critical")) {
|
||||
n->setUrgency(KNotification::CriticalUrgency);
|
||||
}
|
||||
}
|
||||
|
||||
const auto actionsList = notifyActions.split(QLatin1Char('|'));
|
||||
|
||||
// Make sure all plugins can ref the notification
|
||||
// otherwise a plugin may finish and deref before everyone got a chance to ref
|
||||
for (const QString &action : actionsList) {
|
||||
KNotificationPlugin *notifyPlugin = pluginForAction(action);
|
||||
|
||||
if (!notifyPlugin) {
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "No plugin for action" << action;
|
||||
continue;
|
||||
}
|
||||
|
||||
n->ref();
|
||||
}
|
||||
|
||||
for (const QString &action : actionsList) {
|
||||
KNotificationPlugin *notifyPlugin = pluginForAction(action);
|
||||
|
||||
if (!notifyPlugin) {
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "No plugin for action" << action;
|
||||
continue;
|
||||
}
|
||||
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "Calling notify on" << notifyPlugin->optionName();
|
||||
notifyPlugin->notify(n, notifyConfig);
|
||||
}
|
||||
|
||||
connect(n, &KNotification::closed, this, &KNotificationManager::notificationClosed);
|
||||
}
|
||||
|
||||
void KNotificationManager::update(KNotification *n)
|
||||
{
|
||||
KNotifyConfig notifyConfig(n->appName(), n->eventId());
|
||||
|
||||
for (KNotificationPlugin *p : std::as_const(d->notifyPlugins)) {
|
||||
p->update(n, notifyConfig);
|
||||
}
|
||||
}
|
||||
|
||||
void KNotificationManager::reemit(KNotification *n)
|
||||
{
|
||||
notify(n);
|
||||
}
|
||||
|
||||
void KNotificationManager::reparseConfiguration(const QString &app)
|
||||
{
|
||||
if (!d->dirtyConfigCache.contains(app)) {
|
||||
d->dirtyConfigCache << app;
|
||||
}
|
||||
}
|
||||
|
||||
bool KNotificationManager::isInsideSandbox()
|
||||
{
|
||||
// logic is taken from KSandbox::isInside()
|
||||
static const bool isFlatpak = QFileInfo::exists(QStringLiteral("/.flatpak-info"));
|
||||
static const bool isSnap = qEnvironmentVariableIsSet("SNAP");
|
||||
|
||||
return isFlatpak || isSnap;
|
||||
}
|
||||
|
||||
#include "moc_knotificationmanager_p.cpp"
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2005 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2006 Thiago Macieira <thiago@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KNOTIFICATIONMANAGER_H
|
||||
#define KNOTIFICATIONMANAGER_H
|
||||
|
||||
#include <knotification.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class KNotification;
|
||||
class QPixmap;
|
||||
class KNotificationPlugin;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @author Olivier Goffart
|
||||
*/
|
||||
class KNotificationManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static KNotificationManager *self();
|
||||
~KNotificationManager() override;
|
||||
|
||||
KNotificationPlugin *pluginForAction(const QString &action);
|
||||
|
||||
/**
|
||||
* send the dbus call to the knotify server
|
||||
*/
|
||||
void notify(KNotification *n);
|
||||
|
||||
/**
|
||||
* send the close dcop call to the knotify server for the notification with the identifier @p id .
|
||||
* And remove the notification from the internal map
|
||||
* @param id the id of the notification
|
||||
* @param force if false, only close registered notification
|
||||
*/
|
||||
void close(int id);
|
||||
|
||||
/**
|
||||
* update one notification text and pixmap and actions
|
||||
*/
|
||||
void update(KNotification *n);
|
||||
|
||||
/**
|
||||
* re-emit the notification
|
||||
*/
|
||||
void reemit(KNotification *n);
|
||||
|
||||
private Q_SLOTS:
|
||||
void notificationClosed();
|
||||
void xdgActivationTokenReceived(int id, const QString &token);
|
||||
void notificationActivated(int id, const QString &action);
|
||||
void notificationReplied(int id, const QString &text);
|
||||
void notifyPluginFinished(KNotification *notification);
|
||||
void reparseConfiguration(const QString &app);
|
||||
|
||||
private:
|
||||
bool isInsideSandbox();
|
||||
|
||||
struct Private;
|
||||
std::unique_ptr<Private> const d;
|
||||
KNotificationManager();
|
||||
|
||||
friend class KNotificationManagerSingleton;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "knotificationpermission.h"
|
||||
#include <qnamespace.h>
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <QtCore/private/qandroidextras_p.h>
|
||||
|
||||
Qt::PermissionStatus KNotificationPermission::checkPermission()
|
||||
{
|
||||
if (QtAndroidPrivate::androidSdkVersion() < 33) {
|
||||
return Qt::PermissionStatus::Granted;
|
||||
}
|
||||
|
||||
return QtAndroidPrivate::checkPermission(QStringLiteral("android.permission.POST_NOTIFICATIONS")).result() == QtAndroidPrivate::PermissionResult::Authorized
|
||||
? Qt::PermissionStatus::Granted
|
||||
: Qt::PermissionStatus::Denied;
|
||||
}
|
||||
|
||||
void KNotificationPermission::requestPermission(QObject *context, const std::function<void(Qt::PermissionStatus)> &callback)
|
||||
{
|
||||
if (QtAndroidPrivate::androidSdkVersion() < 33) {
|
||||
callback(Qt::PermissionStatus::Granted);
|
||||
}
|
||||
|
||||
QtAndroidPrivate::requestPermission(QStringLiteral("android.permission.POST_NOTIFICATIONS"))
|
||||
.then(context, [callback, context](QtAndroidPrivate::PermissionResult res) {
|
||||
QMetaObject::invokeMethod(
|
||||
context,
|
||||
[res, callback]() {
|
||||
callback(res == QtAndroidPrivate::PermissionResult::Authorized ? Qt::PermissionStatus::Granted : Qt::PermissionStatus::Denied);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
#else
|
||||
Qt::PermissionStatus KNotificationPermission::checkPermission()
|
||||
{
|
||||
return Qt::PermissionStatus::Granted;
|
||||
}
|
||||
|
||||
void KNotificationPermission::requestPermission([[maybe_unused]] QObject *context, const std::function<void(Qt::PermissionStatus)> &callback)
|
||||
{
|
||||
callback(Qt::PermissionStatus::Granted);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KNOTIFICATIONPERMISSION_H
|
||||
#define KNOTIFICATIONPERMISSION_H
|
||||
|
||||
#include <knotifications_export.h>
|
||||
|
||||
#include <qnamespace.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
/** Check or request permissions to show notifications on platforms where
|
||||
* that is necessary.
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
namespace KNotificationPermission
|
||||
{
|
||||
|
||||
/** Check if the current application has permissions to show notifications. */
|
||||
KNOTIFICATIONS_EXPORT Qt::PermissionStatus checkPermission();
|
||||
|
||||
/** Request notification permissions. */
|
||||
KNOTIFICATIONS_EXPORT void requestPermission(QObject *context, const std::function<void(Qt::PermissionStatus)> &callback);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "knotificationplugin.h"
|
||||
|
||||
class KNotificationPluginPrivate
|
||||
{
|
||||
};
|
||||
|
||||
KNotificationPlugin::KNotificationPlugin(QObject *parent, const QVariantList &args)
|
||||
: QObject(parent)
|
||||
, d(nullptr)
|
||||
{
|
||||
Q_UNUSED(args);
|
||||
}
|
||||
|
||||
KNotificationPlugin::~KNotificationPlugin()
|
||||
{
|
||||
}
|
||||
|
||||
void KNotificationPlugin::update(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
Q_UNUSED(notification);
|
||||
Q_UNUSED(notifyConfig);
|
||||
}
|
||||
|
||||
void KNotificationPlugin::close(KNotification *notification)
|
||||
{
|
||||
Q_EMIT finished(notification);
|
||||
}
|
||||
|
||||
void KNotificationPlugin::finish(KNotification *notification)
|
||||
{
|
||||
Q_EMIT finished(notification);
|
||||
}
|
||||
|
||||
#include "moc_knotificationplugin.cpp"
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef KNOTIFICATIONPLUGIN_H
|
||||
#define KNOTIFICATIONPLUGIN_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTextDocumentFragment>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class KNotification;
|
||||
class KNotificationPluginPrivate;
|
||||
class KNotifyConfig;
|
||||
|
||||
/**
|
||||
* @class KNotificationPlugin knotificationplugin.h KNotificationPlugin
|
||||
*
|
||||
* @brief abstract class for KNotification actions
|
||||
*
|
||||
* A KNotificationPlugin is responsible of notification presentation.
|
||||
* You can subclass it to have your own presentation of a notification.
|
||||
*
|
||||
* You should reimplement the KNotificationPlugin::notify method to display the notification.
|
||||
*
|
||||
* Porting from KF5 to KF6:
|
||||
*
|
||||
* The signature of the virtual method
|
||||
* KNotificationPlugin::notify(KNotification *notification, KNotifyConfig *)
|
||||
* was changed to
|
||||
* KNotificationPlugin::notify(KNotification *notification, const KNotifyConfig &).
|
||||
*
|
||||
* The signature of the virtual method
|
||||
* KNotificationPlugin::update(KNotification *notification, KNotifyConfig *)
|
||||
* was changed to
|
||||
* KNotificationPlugin::update(KNotification *notification, const KNotifyConfig &).
|
||||
*
|
||||
* @author Olivier Goffart <ogoffart at kde.org>
|
||||
*/
|
||||
class KNotificationPlugin : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
KNotificationPlugin(QObject *parent = nullptr, const QVariantList &args = QVariantList());
|
||||
~KNotificationPlugin() override;
|
||||
|
||||
/**
|
||||
* @brief return the name of this plugin.
|
||||
*
|
||||
* this is the name that should appear in the .notifyrc file,
|
||||
* in the field Action=... if a notification is set to use this plugin
|
||||
*/
|
||||
virtual QString optionName() = 0;
|
||||
|
||||
/**
|
||||
* This function is called when the notification is sent.
|
||||
* (or re-sent)
|
||||
* You should implement this function to display a notification
|
||||
*
|
||||
* for each call to this function (even for re-notification), you MUST call finish(KNotification*)
|
||||
*
|
||||
* @param notification is the KNotification object
|
||||
* @param notifyConfig is the configuration of the notification
|
||||
*/
|
||||
virtual void notify(KNotification *notification, const KNotifyConfig ¬ifyConfig) = 0;
|
||||
|
||||
/**
|
||||
* This function is called when the notification has changed (such as the text or the icon)
|
||||
*/
|
||||
virtual void update(KNotification *notification, const KNotifyConfig ¬ifyConfig);
|
||||
|
||||
/**
|
||||
* This function is called when the notification has been closed
|
||||
*/
|
||||
virtual void close(KNotification *notification);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* emit the finished signal
|
||||
* you MUST call this function for each call to notify(), even if you do nothing there
|
||||
*
|
||||
* call it when the presentation is finished (because the user closed the popup or the sound is finished)
|
||||
*
|
||||
* If your presentation is synchronous, you can even call this function from the notify() call itself
|
||||
*/
|
||||
void finish(KNotification *notification);
|
||||
|
||||
static inline QString stripRichText(const QString &s)
|
||||
{
|
||||
return QTextDocumentFragment::fromHtml(s).toPlainText();
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* the presentation is finished.
|
||||
*/
|
||||
void finished(KNotification *notification);
|
||||
/**
|
||||
* emit this signal if one action was invoked
|
||||
* @param id is the id of the notification
|
||||
* @param action is the action number. zero for the default action
|
||||
*/
|
||||
void actionInvoked(int id, const QString &action);
|
||||
|
||||
void xdgActivationTokenReceived(int id, const QString &token);
|
||||
|
||||
void replied(int id, const QString &text);
|
||||
|
||||
private:
|
||||
std::unique_ptr<KNotificationPluginPrivate> const d;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
This file is part of the KDE Frameworks
|
||||
SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "knotificationreplyaction.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
class KNotificationReplyActionPrivate
|
||||
{
|
||||
public:
|
||||
QString label;
|
||||
QString placeholderText;
|
||||
QString submitButtonText;
|
||||
QString submitButtonIconName;
|
||||
KNotificationReplyAction::FallbackBehavior fallbackBehavior = KNotificationReplyAction::FallbackBehavior::HideAction;
|
||||
};
|
||||
|
||||
KNotificationReplyAction::KNotificationReplyAction(const QString &label)
|
||||
: QObject()
|
||||
, d(new KNotificationReplyActionPrivate)
|
||||
{
|
||||
d->label = label;
|
||||
}
|
||||
|
||||
KNotificationReplyAction::~KNotificationReplyAction() = default;
|
||||
|
||||
QString KNotificationReplyAction::label() const
|
||||
{
|
||||
return d->label;
|
||||
}
|
||||
|
||||
void KNotificationReplyAction::setLabel(const QString &label)
|
||||
{
|
||||
if (d->label != label) {
|
||||
d->label = label;
|
||||
Q_EMIT labelChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString KNotificationReplyAction::placeholderText() const
|
||||
{
|
||||
return d->placeholderText;
|
||||
}
|
||||
|
||||
void KNotificationReplyAction::setPlaceholderText(const QString &placeholderText)
|
||||
{
|
||||
if (d->placeholderText != placeholderText) {
|
||||
d->placeholderText = placeholderText;
|
||||
Q_EMIT placeholderTextChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString KNotificationReplyAction::submitButtonText() const
|
||||
{
|
||||
return d->submitButtonText;
|
||||
}
|
||||
|
||||
void KNotificationReplyAction::setSubmitButtonText(const QString &submitButtonText)
|
||||
{
|
||||
if (d->submitButtonText != submitButtonText) {
|
||||
d->submitButtonText = submitButtonText;
|
||||
Q_EMIT submitButtonTextChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString KNotificationReplyAction::submitButtonIconName() const
|
||||
{
|
||||
return d->submitButtonIconName;
|
||||
}
|
||||
|
||||
void KNotificationReplyAction::setSubmitButtonIconName(const QString &submitButtonIconName)
|
||||
{
|
||||
if (d->submitButtonIconName != submitButtonIconName) {
|
||||
d->submitButtonIconName = submitButtonIconName;
|
||||
Q_EMIT submitButtonIconNameChanged();
|
||||
}
|
||||
}
|
||||
|
||||
KNotificationReplyAction::FallbackBehavior KNotificationReplyAction::fallbackBehavior() const
|
||||
{
|
||||
return d->fallbackBehavior;
|
||||
}
|
||||
|
||||
void KNotificationReplyAction::setFallbackBehavior(FallbackBehavior fallbackBehavior)
|
||||
{
|
||||
if (d->fallbackBehavior != fallbackBehavior) {
|
||||
d->fallbackBehavior = fallbackBehavior;
|
||||
Q_EMIT fallbackBehaviorChanged();
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_knotificationreplyaction.cpp"
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
This file is part of the KDE Frameworks
|
||||
SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KNOTIFICATIONREPLYACTION_H
|
||||
#define KNOTIFICATIONREPLYACTION_H
|
||||
|
||||
#include <knotifications_export.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QString;
|
||||
|
||||
class KNotificationReplyActionPrivate;
|
||||
|
||||
/**
|
||||
* @class KNotificationReplyAction knotificationreplyaction.h KNotificationReplyAction
|
||||
*
|
||||
* @brief An inline reply action
|
||||
*
|
||||
* This class represents an inline reply action, which lets the user type a
|
||||
* reply to a chat message or email in the notification popup.
|
||||
*/
|
||||
class KNOTIFICATIONS_EXPORT KNotificationReplyAction : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
/**
|
||||
* @copydoc label
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
|
||||
/**
|
||||
* @copydoc placeholderText
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText NOTIFY placeholderTextChanged)
|
||||
/**
|
||||
* @copydoc submitButtonText
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(QString submitButtonText READ submitButtonText WRITE setSubmitButtonText NOTIFY submitButtonTextChanged)
|
||||
/**
|
||||
* @copydoc submitButtonIconName
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(QString submitButtonIconName READ submitButtonIconName WRITE setSubmitButtonIconName NOTIFY submitButtonIconNameChanged)
|
||||
/**
|
||||
* @copydoc fallbackBehavior
|
||||
* @since 5.88
|
||||
*/
|
||||
Q_PROPERTY(FallbackBehavior fallbackBehavior READ fallbackBehavior WRITE setFallbackBehavior NOTIFY fallbackBehaviorChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a inline reply action with given label
|
||||
* @param label The label for the action
|
||||
*/
|
||||
explicit KNotificationReplyAction(const QString &label);
|
||||
/**
|
||||
* Destroys this inline reply action
|
||||
*/
|
||||
~KNotificationReplyAction() override;
|
||||
|
||||
/**
|
||||
* The label for the action button
|
||||
*/
|
||||
QString label() const;
|
||||
/**
|
||||
* Set the label for the action button
|
||||
*/
|
||||
void setLabel(const QString &label);
|
||||
|
||||
/**
|
||||
* The placeholder text for the inline reply text field
|
||||
*/
|
||||
QString placeholderText() const;
|
||||
/**
|
||||
* Set the placeholder text for the inline reply text field, for example "Reply to Konqi..."
|
||||
*/
|
||||
void setPlaceholderText(const QString &placeholderText);
|
||||
|
||||
/**
|
||||
* The label for the button to send the typed reply
|
||||
*/
|
||||
QString submitButtonText() const;
|
||||
/**
|
||||
* Set the label for the button to send the typed reply
|
||||
*/
|
||||
void setSubmitButtonText(const QString &submitButtonText);
|
||||
|
||||
/**
|
||||
* The icon name for the button to send the typed reply
|
||||
*/
|
||||
QString submitButtonIconName() const;
|
||||
/**
|
||||
* Set the icon name for the button to send the typed reply
|
||||
*/
|
||||
void setSubmitButtonIconName(const QString &submitButtonIconName);
|
||||
|
||||
/**
|
||||
* Behavior when the notification server does not support inline replies
|
||||
*/
|
||||
enum class FallbackBehavior {
|
||||
/**
|
||||
* Don't add the reply action (default)
|
||||
*/
|
||||
HideAction,
|
||||
/**
|
||||
* Add the reply action as regular button
|
||||
*
|
||||
* Use this if you want to provide your own reply functionality
|
||||
*
|
||||
* @note The @c activated signal is emitted instead of @c replied!
|
||||
*/
|
||||
UseRegularAction,
|
||||
};
|
||||
Q_ENUM(FallbackBehavior)
|
||||
|
||||
/**
|
||||
* Gets the fallback behavior when the notification server does not support inline replies
|
||||
*/
|
||||
FallbackBehavior fallbackBehavior() const;
|
||||
/**
|
||||
* Set the fallback behavior for when the notification server does not support inline replies
|
||||
*/
|
||||
void setFallbackBehavior(FallbackBehavior fallbackBehavior);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* Emitted when the user has submitted a reply
|
||||
*
|
||||
* @note This is never emitted when the notification server does not support inline replies
|
||||
*
|
||||
* @param text The text the user entered
|
||||
*/
|
||||
void replied(const QString &text);
|
||||
/**
|
||||
* Emitted when the user clicks the reply fallback button
|
||||
*
|
||||
* @note This is emitted when the notification server does not support inline replies
|
||||
* and fallbackBehavior is set to @c UseRegularAction.
|
||||
*/
|
||||
void activated();
|
||||
|
||||
/**
|
||||
* Emitted when @p label changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void labelChanged();
|
||||
/**
|
||||
* Emitted when @p placeholderText changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void placeholderTextChanged();
|
||||
/**
|
||||
* Emitted when @p submitButtonText changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void submitButtonTextChanged();
|
||||
/**
|
||||
* Emitted when @p submitButtonIconName changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void submitButtonIconNameChanged();
|
||||
/**
|
||||
* Emitted when @p fallbackBehavior changed.
|
||||
* @since 5.88
|
||||
*/
|
||||
void fallbackBehaviorChanged();
|
||||
|
||||
private:
|
||||
std::unique_ptr<KNotificationReplyActionPrivate> const d;
|
||||
};
|
||||
|
||||
#endif // KNOTIFICATIONREPLYACTION_H
|
||||
@@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/knotifications6">
|
||||
<file>android_defaults.notifyrc</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2005-2009 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "knotifyconfig.h"
|
||||
|
||||
#include <KConfigGroup>
|
||||
#include <KSharedConfig>
|
||||
|
||||
#include <QCache>
|
||||
#include <QStandardPaths>
|
||||
|
||||
typedef QCache<QString, KSharedConfig::Ptr> ConfigCache;
|
||||
Q_GLOBAL_STATIC_WITH_ARGS(ConfigCache, static_cache, (15))
|
||||
|
||||
class KNotifyConfigPrivate : public QSharedData
|
||||
{
|
||||
public:
|
||||
QString readEntry(const QString &group, const QString &key, bool path) const;
|
||||
|
||||
QString applicationName;
|
||||
QString eventId;
|
||||
|
||||
KSharedConfig::Ptr eventsFile;
|
||||
KSharedConfig::Ptr configFile;
|
||||
};
|
||||
|
||||
QString KNotifyConfigPrivate::readEntry(const QString &group, const QString &key, bool path) const
|
||||
{
|
||||
if (configFile->hasGroup(group)) {
|
||||
KConfigGroup cg(configFile, group);
|
||||
const QString value = path ? cg.readPathEntry(key, QString()) : cg.readEntry(key, QString());
|
||||
if (!value.isNull()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
if (eventsFile->hasGroup(group)) {
|
||||
KConfigGroup cg(eventsFile, group);
|
||||
const QString value = path ? cg.readPathEntry(key, QString()) : cg.readEntry(key, QString());
|
||||
if (!value.isNull()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
static KSharedConfig::Ptr retrieve_from_cache(const QString &filename, QStandardPaths::StandardLocation type = QStandardPaths::GenericConfigLocation)
|
||||
{
|
||||
QCache<QString, KSharedConfig::Ptr> &cache = *static_cache;
|
||||
if (cache.contains(filename)) {
|
||||
return *cache[filename];
|
||||
}
|
||||
|
||||
KSharedConfig::Ptr m = KSharedConfig::openConfig(filename, KConfig::NoGlobals, type);
|
||||
// also search for event config files in qrc resources
|
||||
if (type == QStandardPaths::GenericDataLocation) {
|
||||
m->addConfigSources({QStringLiteral(":/") + filename});
|
||||
}
|
||||
cache.insert(filename, new KSharedConfig::Ptr(m));
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
void KNotifyConfig::reparseConfiguration()
|
||||
{
|
||||
QCache<QString, KSharedConfig::Ptr> &cache = *static_cache;
|
||||
const auto listFiles = cache.keys();
|
||||
for (const QString &filename : listFiles) {
|
||||
(*cache[filename])->reparseConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
void KNotifyConfig::reparseSingleConfiguration(const QString &app)
|
||||
{
|
||||
QCache<QString, KSharedConfig::Ptr> &cache = *static_cache;
|
||||
const QString appCacheKey = app + QStringLiteral(".notifyrc");
|
||||
if (cache.contains(appCacheKey)) {
|
||||
(*cache[appCacheKey])->reparseConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
KNotifyConfig::KNotifyConfig(const QString &applicationName, const QString &eventId)
|
||||
: d(new KNotifyConfigPrivate)
|
||||
{
|
||||
d->applicationName = applicationName;
|
||||
d->eventId = eventId;
|
||||
|
||||
d->eventsFile = retrieve_from_cache(QLatin1String("knotifications6/") + applicationName + QLatin1String(".notifyrc"), QStandardPaths::GenericDataLocation);
|
||||
d->configFile = retrieve_from_cache(applicationName + QStringLiteral(".notifyrc"));
|
||||
}
|
||||
|
||||
KNotifyConfig::KNotifyConfig(const KNotifyConfig &other)
|
||||
: d(other.d)
|
||||
{
|
||||
}
|
||||
|
||||
KNotifyConfig &KNotifyConfig::operator=(const KNotifyConfig &other)
|
||||
{
|
||||
d = other.d;
|
||||
return *this;
|
||||
}
|
||||
|
||||
KNotifyConfig::~KNotifyConfig() = default;
|
||||
|
||||
QString KNotifyConfig::applicationName() const
|
||||
{
|
||||
return d->applicationName;
|
||||
}
|
||||
|
||||
QString KNotifyConfig::eventId() const
|
||||
{
|
||||
return d->eventId;
|
||||
}
|
||||
|
||||
bool KNotifyConfig::isValid() const
|
||||
{
|
||||
const QString group = QLatin1String("Event/") + d->eventId;
|
||||
return d->configFile->hasGroup(group) || d->eventsFile->hasGroup(group);
|
||||
}
|
||||
|
||||
QString KNotifyConfig::readGlobalEntry(const QString &key) const
|
||||
{
|
||||
return d->readEntry(QStringLiteral("Global"), key, false);
|
||||
}
|
||||
|
||||
QString KNotifyConfig::readEntry(const QString &key) const
|
||||
{
|
||||
const QString group = QLatin1String("Event/") + d->eventId;
|
||||
return d->readEntry(group, key, false);
|
||||
}
|
||||
|
||||
QString KNotifyConfig::readPathEntry(const QString &key) const
|
||||
{
|
||||
const QString group = QLatin1String("Event/") + d->eventId;
|
||||
return d->readEntry(group, key, true);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2005-2009 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef KNOTIFYCONFIG_H
|
||||
#define KNOTIFYCONFIG_H
|
||||
|
||||
#include "knotifications_export.h"
|
||||
#include <QSharedDataPointer>
|
||||
|
||||
class KNotifyConfigPrivate;
|
||||
|
||||
/**
|
||||
* @class KNotifyConfig knotifyconfig.h KNotifyConfig
|
||||
*
|
||||
* Represent the configuration for an event
|
||||
*
|
||||
* @author Olivier Goffart <ogoffart@kde.org>
|
||||
* @author Kai Uwe Broulik <kde@broulik.de>
|
||||
*/
|
||||
class KNOTIFICATIONS_EXPORT KNotifyConfig
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Creates a notify config for the given application name and event id
|
||||
* @param applicationName The application name, typically the name of the notifyrc file without its extension.
|
||||
* @param eventId The notification event ID, i.e. the part after Event/ in its notifyrc file.
|
||||
*/
|
||||
KNotifyConfig(const QString &applicationName, const QString &eventId);
|
||||
~KNotifyConfig();
|
||||
|
||||
KNotifyConfig(const KNotifyConfig &other);
|
||||
KNotifyConfig &operator=(const KNotifyConfig &other);
|
||||
|
||||
/**
|
||||
* the name of the application that triggered the notification
|
||||
*/
|
||||
QString applicationName() const;
|
||||
|
||||
/**
|
||||
* the name of the notification
|
||||
*/
|
||||
QString eventId() const;
|
||||
|
||||
/**
|
||||
* Whether there exists an event with the given id under the given application name.
|
||||
*/
|
||||
bool isValid() const;
|
||||
|
||||
/**
|
||||
* @return entry from the relevant Global notifyrc config group
|
||||
*
|
||||
* This will return the configuration from the user for the given key.
|
||||
* It first look into the user config file, and then in the global config file.
|
||||
*
|
||||
* return a null string if the entry doesn't exist
|
||||
*/
|
||||
QString readGlobalEntry(const QString &key) const;
|
||||
|
||||
/**
|
||||
* @return entry from the relevant Event/ notifyrc config group
|
||||
*
|
||||
* This will return the configuration from the user for the given key.
|
||||
* It first look into the user config file, and then in the global config file.
|
||||
*
|
||||
* return a null string if the entry doesn't exist
|
||||
*/
|
||||
QString readEntry(const QString &key) const;
|
||||
|
||||
/**
|
||||
* @return path entry from the relevant Event/ notifyrc config group
|
||||
*
|
||||
* This will return the configuration from the user for the given key
|
||||
* and interpret it as a path.
|
||||
*/
|
||||
QString readPathEntry(const QString &key) const;
|
||||
/**
|
||||
* reparse the cached configs. to be used when the config may have changed
|
||||
*/
|
||||
static void reparseConfiguration();
|
||||
|
||||
static void reparseSingleConfiguration(const QString &app);
|
||||
|
||||
private:
|
||||
QSharedDataPointer<KNotifyConfigPrivate> d;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "notifybyandroid.h"
|
||||
#include "debug_p.h"
|
||||
#include "knotification.h"
|
||||
#include "knotificationreplyaction.h"
|
||||
#include "knotifyconfig.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJniEnvironment>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QIcon>
|
||||
|
||||
static NotifyByAndroid *s_instance = nullptr;
|
||||
|
||||
static void notificationFinished(JNIEnv *env, jobject that, jint notificationId)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(that);
|
||||
if (s_instance) {
|
||||
s_instance->notificationFinished(notificationId);
|
||||
}
|
||||
}
|
||||
|
||||
static void notificationActionInvoked(JNIEnv *env, jobject that, jint id, jstring action)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(that);
|
||||
if (s_instance) {
|
||||
const char *str = env->GetStringUTFChars(action, nullptr);
|
||||
s_instance->notificationActionInvoked(id, QString::fromUtf8(str));
|
||||
env->ReleaseStringUTFChars(action, str);
|
||||
}
|
||||
}
|
||||
|
||||
static void notificationInlineReply(JNIEnv *env, jobject that, jint id, jstring text)
|
||||
{
|
||||
Q_UNUSED(that);
|
||||
if (s_instance) {
|
||||
const char *str = env->GetStringUTFChars(text, nullptr);
|
||||
s_instance->notificationInlineReply(id, QString::fromUtf8(str));
|
||||
env->ReleaseStringUTFChars(text, str);
|
||||
}
|
||||
}
|
||||
|
||||
static const JNINativeMethod methods[] = {
|
||||
{"notificationFinished", "(I)V", (void *)notificationFinished},
|
||||
{"notificationActionInvoked", "(ILjava/lang/String;)V", (void *)notificationActionInvoked},
|
||||
{"notificationInlineReply", "(ILjava/lang/String;)V", (void *)notificationInlineReply},
|
||||
};
|
||||
|
||||
KNOTIFICATIONS_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *)
|
||||
{
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return JNI_VERSION_1_4;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
JNIEnv *env = nullptr;
|
||||
if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Failed to get JNI environment.";
|
||||
return -1;
|
||||
}
|
||||
jclass cls = env->FindClass("org/kde/knotifications/NotifyByAndroid");
|
||||
if (env->RegisterNatives(cls, methods, sizeof(methods) / sizeof(JNINativeMethod)) < 0) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Failed to register native functions.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return JNI_VERSION_1_4;
|
||||
}
|
||||
|
||||
NotifyByAndroid::NotifyByAndroid(QObject *parent)
|
||||
: KNotificationPlugin(parent)
|
||||
{
|
||||
s_instance = this;
|
||||
QJniObject context = QNativeInterface::QAndroidApplication::context();
|
||||
m_backend = QJniObject("org/kde/knotifications/NotifyByAndroid", "(Landroid/content/Context;)V", context.object<jobject>());
|
||||
}
|
||||
|
||||
NotifyByAndroid::~NotifyByAndroid()
|
||||
{
|
||||
s_instance = nullptr;
|
||||
}
|
||||
|
||||
QString NotifyByAndroid::optionName()
|
||||
{
|
||||
return QStringLiteral("Popup");
|
||||
}
|
||||
|
||||
void NotifyByAndroid::notify(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
Q_UNUSED(notifyConfig);
|
||||
// HACK work around that notification->id() is only populated after returning from here
|
||||
// note that config will be invalid at that point, so we can't pass that along
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this, notification]() {
|
||||
notifyDeferred(notification);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QJniObject NotifyByAndroid::createAndroidNotification(KNotification *notification, const KNotifyConfig ¬ifyConfig) const
|
||||
{
|
||||
QJniEnvironment env;
|
||||
QJniObject n("org/kde/knotifications/KNotification", "()V");
|
||||
n.setField("id", notification->id());
|
||||
n.setField("text", QJniObject::fromString(stripRichText(notification->text())).object<jstring>());
|
||||
n.setField("richText", QJniObject::fromString(notification->text()).object<jstring>());
|
||||
n.setField("title", QJniObject::fromString(notification->title()).object<jstring>());
|
||||
n.setField("urgency", (jint)(notification->urgency() == KNotification::DefaultUrgency ? KNotification::HighUrgency : notification->urgency()));
|
||||
n.setField("visibility", QJniObject::fromString(notification->hints().value(QLatin1String("x-kde-visibility")).toString().toLower()).object<jstring>());
|
||||
|
||||
n.setField("channelId", QJniObject::fromString(notification->eventId()).object<jstring>());
|
||||
n.setField("channelName", QJniObject::fromString(notifyConfig.readEntry(QLatin1String("Name"))).object<jstring>());
|
||||
n.setField("channelDescription", QJniObject::fromString(notifyConfig.readEntry(QLatin1String("Comment"))).object<jstring>());
|
||||
|
||||
if ((notification->flags() & KNotification::SkipGrouping) == 0) {
|
||||
n.setField("group", QJniObject::fromString(notification->eventId()).object<jstring>());
|
||||
}
|
||||
|
||||
// icon
|
||||
QPixmap pixmap;
|
||||
if (!notification->iconName().isEmpty()) {
|
||||
const auto icon = QIcon::fromTheme(notification->iconName());
|
||||
pixmap = icon.pixmap(32, 32);
|
||||
} else {
|
||||
pixmap = notification->pixmap();
|
||||
}
|
||||
QByteArray iconData;
|
||||
QBuffer buffer(&iconData);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
pixmap.save(&buffer, "PNG");
|
||||
auto jIconData = env->NewByteArray(iconData.length());
|
||||
env->SetByteArrayRegion(jIconData, 0, iconData.length(), reinterpret_cast<const jbyte *>(iconData.constData()));
|
||||
n.callMethod<void>("setIconFromData", "([BI)V", jIconData, iconData.length());
|
||||
env->DeleteLocalRef(jIconData);
|
||||
|
||||
// actions
|
||||
const auto actions = notification->actions();
|
||||
for (const KNotificationAction *action : actions) {
|
||||
n.callMethod<void>("addAction",
|
||||
"(Ljava/lang/String;Ljava/lang/String;)V",
|
||||
QJniObject::fromString(action->id()).object<jstring>(),
|
||||
QJniObject::fromString(action->label()).object<jstring>());
|
||||
}
|
||||
|
||||
if (notification->replyAction()) {
|
||||
n.setField("inlineReplyLabel", QJniObject::fromString(notification->replyAction()->label()).object<jstring>());
|
||||
n.setField("inlineReplyPlaceholder", QJniObject::fromString(notification->replyAction()->placeholderText()).object<jstring>());
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void NotifyByAndroid::notifyDeferred(KNotification *notification)
|
||||
{
|
||||
KNotifyConfig config(notification->appName(), notification->eventId());
|
||||
const auto n = createAndroidNotification(notification, config);
|
||||
m_notifications.insert(notification->id(), notification);
|
||||
|
||||
m_backend.callMethod<void>("notify", "(Lorg/kde/knotifications/KNotification;)V", n.object<jobject>());
|
||||
}
|
||||
|
||||
void NotifyByAndroid::update(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
const auto n = createAndroidNotification(notification, notifyConfig);
|
||||
m_backend.callMethod<void>("notify", "(Lorg/kde/knotifications/KNotification;)V", n.object<jobject>());
|
||||
}
|
||||
|
||||
void NotifyByAndroid::close(KNotification *notification)
|
||||
{
|
||||
m_backend.callMethod<void>("close", "(ILjava/lang/String;)V", notification->id(), QJniObject::fromString(notification->eventId()).object<jstring>());
|
||||
KNotificationPlugin::close(notification);
|
||||
}
|
||||
|
||||
void NotifyByAndroid::notificationFinished(int id)
|
||||
{
|
||||
qCDebug(LOG_KNOTIFICATIONS) << id;
|
||||
const auto it = m_notifications.find(id);
|
||||
if (it == m_notifications.end()) {
|
||||
return;
|
||||
}
|
||||
m_notifications.erase(it);
|
||||
if (it.value()) {
|
||||
finish(it.value());
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByAndroid::notificationActionInvoked(int id, const QString &action)
|
||||
{
|
||||
qCDebug(LOG_KNOTIFICATIONS) << id << action;
|
||||
Q_EMIT actionInvoked(id, action);
|
||||
}
|
||||
|
||||
void NotifyByAndroid::notificationInlineReply(int id, const QString &text)
|
||||
{
|
||||
qCDebug(LOG_KNOTIFICATIONS) << id << text;
|
||||
Q_EMIT replied(id, text);
|
||||
}
|
||||
|
||||
#include "moc_notifybyandroid.cpp"
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef NOTIFYBYANDROID_H
|
||||
#define NOTIFYBYANDROID_H
|
||||
|
||||
#include "knotificationplugin.h"
|
||||
|
||||
#include <QJniObject>
|
||||
#include <QPointer>
|
||||
|
||||
/** Android notification backend. */
|
||||
class NotifyByAndroid : public KNotificationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NotifyByAndroid(QObject *parent = nullptr);
|
||||
~NotifyByAndroid() override;
|
||||
|
||||
// interface of KNotificationPlugin
|
||||
QString optionName() override;
|
||||
void notify(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
void update(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
void close(KNotification *notification) override;
|
||||
|
||||
// interface from Java
|
||||
void notificationFinished(int id);
|
||||
void notificationActionInvoked(int id, const QString &action);
|
||||
void notificationInlineReply(int id, const QString &text);
|
||||
|
||||
private:
|
||||
void notifyDeferred(KNotification *notification);
|
||||
QJniObject createAndroidNotification(KNotification *notification, const KNotifyConfig ¬ifyConfig) const;
|
||||
|
||||
QJniObject m_backend;
|
||||
QHash<int, QPointer<KNotification>> m_notifications;
|
||||
};
|
||||
|
||||
#endif // NOTIFYBYANDROID_H
|
||||
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2014-2015 Martin Klapetek <mklapetek@kde.org>
|
||||
SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||
SPDX-FileCopyrightText: 2023 Ismael Asensio <isma.af@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "notifybyaudio.h"
|
||||
#include "debug_p.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QGuiApplication>
|
||||
#include <QIcon>
|
||||
#include <QString>
|
||||
|
||||
#include "knotification.h"
|
||||
#include "knotifyconfig.h"
|
||||
|
||||
#include <canberra.h>
|
||||
|
||||
const QString DEFAULT_SOUND_THEME = QStringLiteral("ocean");
|
||||
|
||||
NotifyByAudio::NotifyByAudio(QObject *parent)
|
||||
: KNotificationPlugin(parent)
|
||||
, m_soundTheme(DEFAULT_SOUND_THEME)
|
||||
, m_enabled(true)
|
||||
{
|
||||
qRegisterMetaType<uint32_t>("uint32_t");
|
||||
|
||||
m_settingsWatcher = KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("kdeglobals")));
|
||||
connect(m_settingsWatcher.get(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) {
|
||||
if (group.name() != QLatin1String("Sounds")) {
|
||||
return;
|
||||
}
|
||||
if (names.contains(QByteArrayLiteral("Theme"))) {
|
||||
m_soundTheme = group.readEntry("Theme", DEFAULT_SOUND_THEME);
|
||||
}
|
||||
if (names.contains(QByteArrayLiteral("Enable"))) {
|
||||
m_enabled = group.readEntry("Enable", true);
|
||||
}
|
||||
});
|
||||
|
||||
const KConfigGroup group = m_settingsWatcher->config()->group(QStringLiteral("Sounds"));
|
||||
m_soundTheme = group.readEntry("Theme", DEFAULT_SOUND_THEME);
|
||||
m_enabled = group.readEntry("Enable", true);
|
||||
}
|
||||
|
||||
NotifyByAudio::~NotifyByAudio()
|
||||
{
|
||||
if (m_context) {
|
||||
ca_context_destroy(m_context);
|
||||
}
|
||||
m_context = nullptr;
|
||||
}
|
||||
|
||||
ca_context *NotifyByAudio::context()
|
||||
{
|
||||
if (m_context) {
|
||||
return m_context;
|
||||
}
|
||||
|
||||
int ret = ca_context_create(&m_context);
|
||||
if (ret != CA_SUCCESS) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Failed to initialize canberra context for audio notification:" << ca_strerror(ret);
|
||||
m_context = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString desktopFileName = QGuiApplication::desktopFileName();
|
||||
// handle apps which set the desktopFileName property with filename suffix,
|
||||
// due to unclear API dox (https://bugreports.qt.io/browse/QTBUG-75521)
|
||||
if (desktopFileName.endsWith(QLatin1String(".desktop"))) {
|
||||
desktopFileName.chop(8);
|
||||
}
|
||||
ret = ca_context_change_props(m_context,
|
||||
CA_PROP_APPLICATION_NAME,
|
||||
qUtf8Printable(qApp->applicationDisplayName()),
|
||||
CA_PROP_APPLICATION_ID,
|
||||
qUtf8Printable(desktopFileName),
|
||||
CA_PROP_APPLICATION_ICON_NAME,
|
||||
qUtf8Printable(qApp->windowIcon().name()),
|
||||
nullptr);
|
||||
if (ret != CA_SUCCESS) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Failed to set application properties on canberra context for audio notification:" << ca_strerror(ret);
|
||||
}
|
||||
|
||||
return m_context;
|
||||
}
|
||||
|
||||
void NotifyByAudio::notify(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
if (!m_enabled) {
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "Notification sounds are globally disabled";
|
||||
return;
|
||||
}
|
||||
|
||||
const QString soundName = notifyConfig.readEntry(QStringLiteral("Sound"));
|
||||
if (soundName.isEmpty()) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Audio notification requested, but no sound name provided in notifyrc file, aborting audio notification";
|
||||
|
||||
finish(notification);
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy implementation. Fallback lookup for a full path within the `$XDG_DATA_LOCATION/sounds` dirs
|
||||
QUrl fallbackUrl;
|
||||
const auto dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
|
||||
for (const QString &dataLocation : dataLocations) {
|
||||
fallbackUrl = QUrl::fromUserInput(soundName, dataLocation + QStringLiteral("/sounds"), QUrl::AssumeLocalFile);
|
||||
if (fallbackUrl.isLocalFile() && QFileInfo::exists(fallbackUrl.toLocalFile())) {
|
||||
break;
|
||||
} else if (!fallbackUrl.isLocalFile() && fallbackUrl.isValid()) {
|
||||
break;
|
||||
}
|
||||
fallbackUrl.clear();
|
||||
}
|
||||
|
||||
// Looping happens in the finishCallback
|
||||
if (!playSound(m_currentId, soundName, fallbackUrl)) {
|
||||
finish(notification);
|
||||
return;
|
||||
}
|
||||
|
||||
if (notification->flags() & KNotification::LoopSound) {
|
||||
m_loopSoundUrls.insert(m_currentId, {soundName, fallbackUrl});
|
||||
}
|
||||
|
||||
Q_ASSERT(!m_notifications.value(m_currentId));
|
||||
m_notifications.insert(m_currentId, notification);
|
||||
|
||||
++m_currentId;
|
||||
}
|
||||
|
||||
bool NotifyByAudio::playSound(quint32 id, const QString &soundName, const QUrl &fallbackUrl)
|
||||
{
|
||||
if (!context()) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Cannot play notification sound without canberra context";
|
||||
return false;
|
||||
}
|
||||
|
||||
ca_proplist *props = nullptr;
|
||||
ca_proplist_create(&props);
|
||||
|
||||
ca_proplist_sets(props, CA_PROP_EVENT_ID, soundName.toLatin1().constData());
|
||||
ca_proplist_sets(props, CA_PROP_CANBERRA_XDG_THEME_NAME, m_soundTheme.toLatin1().constData());
|
||||
// Fallback to filename
|
||||
if (!fallbackUrl.isEmpty()) {
|
||||
ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, QFile::encodeName(fallbackUrl.toLocalFile()).constData());
|
||||
}
|
||||
// We'll also want this cached for a time. volatile makes sure the cache is
|
||||
// dropped after some time or when the cache is under pressure.
|
||||
ca_proplist_sets(props, CA_PROP_CANBERRA_CACHE_CONTROL, "volatile");
|
||||
|
||||
int ret = ca_context_play_full(context(), id, props, &ca_finish_callback, this);
|
||||
|
||||
ca_proplist_destroy(props);
|
||||
|
||||
if (ret != CA_SUCCESS) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Failed to play sound with canberra:" << ca_strerror(ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NotifyByAudio::ca_finish_callback(ca_context *c, uint32_t id, int error_code, void *userdata)
|
||||
{
|
||||
Q_UNUSED(c);
|
||||
QMetaObject::invokeMethod(static_cast<NotifyByAudio *>(userdata), "finishCallback", Q_ARG(uint32_t, id), Q_ARG(int, error_code));
|
||||
}
|
||||
|
||||
void NotifyByAudio::finishCallback(uint32_t id, int error_code)
|
||||
{
|
||||
KNotification *notification = m_notifications.value(id, nullptr);
|
||||
if (!notification) {
|
||||
// We may have gotten a late finish callback.
|
||||
return;
|
||||
}
|
||||
|
||||
if (error_code == CA_SUCCESS) {
|
||||
// Loop the sound now if we have one
|
||||
auto soundInfoIt = m_loopSoundUrls.constFind(id);
|
||||
if (soundInfoIt != m_loopSoundUrls.constEnd()) {
|
||||
if (!playSound(id, soundInfoIt->first, soundInfoIt->second)) {
|
||||
finishNotification(notification, id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (error_code != CA_ERROR_CANCELED) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Playing audio notification failed:" << ca_strerror(error_code);
|
||||
}
|
||||
|
||||
finishNotification(notification, id);
|
||||
}
|
||||
|
||||
void NotifyByAudio::close(KNotification *notification)
|
||||
{
|
||||
if (!m_notifications.values().contains(notification)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto id = m_notifications.key(notification);
|
||||
if (m_context) {
|
||||
int ret = ca_context_cancel(m_context, id);
|
||||
if (ret != CA_SUCCESS) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Failed to cancel canberra context for audio notification:" << ca_strerror(ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Consider the notification finished. ca_context_cancel schedules a cancel
|
||||
// but we need to stop using the notification immediately or we could access
|
||||
// a notification past its lifetime (close() may, or indeed must,
|
||||
// schedule deletion of the notification).
|
||||
// https://bugs.kde.org/show_bug.cgi?id=398695
|
||||
finishNotification(notification, id);
|
||||
}
|
||||
|
||||
void NotifyByAudio::finishNotification(KNotification *notification, quint32 id)
|
||||
{
|
||||
m_notifications.remove(id);
|
||||
m_loopSoundUrls.remove(id);
|
||||
finish(notification);
|
||||
}
|
||||
|
||||
#include "moc_notifybyaudio.cpp"
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef NOTIFYBYAUDIO_H
|
||||
#define NOTIFYBYAUDIO_H
|
||||
|
||||
#include "knotificationplugin.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QUrl>
|
||||
|
||||
#include <KConfigWatcher>
|
||||
|
||||
class KNotification;
|
||||
|
||||
struct ca_context;
|
||||
|
||||
class NotifyByAudio : public KNotificationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NotifyByAudio(QObject *parent = nullptr);
|
||||
~NotifyByAudio() override;
|
||||
|
||||
QString optionName() override
|
||||
{
|
||||
return QStringLiteral("Sound");
|
||||
}
|
||||
void notify(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
void close(KNotification *notification) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void finishCallback(uint32_t id, int error_code);
|
||||
|
||||
private:
|
||||
static void ca_finish_callback(ca_context *c, uint32_t id, int error_code, void *userdata);
|
||||
|
||||
ca_context *context();
|
||||
void finishNotification(KNotification *notification, quint32 id);
|
||||
|
||||
bool playSound(quint32 id, const QString &eventName, const QUrl &fallbackUrl);
|
||||
|
||||
ca_context *m_context = nullptr;
|
||||
quint32 m_currentId = 0;
|
||||
QHash<quint32, KNotification *> m_notifications;
|
||||
// in case we loop we store the URL for the notification to be able to replay it
|
||||
QHash<quint32, std::pair<QString, QUrl>> m_loopSoundUrls;
|
||||
|
||||
KConfigWatcher::Ptr m_settingsWatcher;
|
||||
QString m_soundTheme;
|
||||
bool m_enabled = true;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2019-2020 Weixuan XIAO <veyx.shaw at gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef NOTIFYBYMACOSNOTIFICATIONCENTER_H
|
||||
#define NOTIFYBYMACOSNOTIFICATIONCENTER_H
|
||||
|
||||
#include "knotificationplugin.h"
|
||||
|
||||
class NotifyByMacOSNotificationCenter : public KNotificationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
NotifyByMacOSNotificationCenter(QObject *parent);
|
||||
~NotifyByMacOSNotificationCenter() override;
|
||||
|
||||
QString optionName() override
|
||||
{
|
||||
return QStringLiteral("Popup");
|
||||
}
|
||||
void notify(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
void update(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
void close(KNotification *notification) override;
|
||||
};
|
||||
|
||||
#endif // NOTIFYBYMACOSNOTIFICATIONCENTER_H
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2019-2020 Weixuan XIAO <veyx.shaw at gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "notifybymacosnotificationcenter.h"
|
||||
#include "knotification.h"
|
||||
#include "knotifyconfig.h"
|
||||
#include "debug_p.h"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QDebug>
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <objc/runtime.h>
|
||||
|
||||
|
||||
Q_FORWARD_DECLARE_OBJC_CLASS(MacOSNotificationCenterDelegate);
|
||||
|
||||
class MacOSNotificationCenterPrivate
|
||||
{
|
||||
public:
|
||||
static MacOSNotificationCenterPrivate *instance();
|
||||
~MacOSNotificationCenterPrivate();
|
||||
|
||||
QMap<int, KNotification*> m_notifications;
|
||||
|
||||
int m_internalCounter;
|
||||
private:
|
||||
MacOSNotificationCenterPrivate();
|
||||
MacOSNotificationCenterDelegate *m_delegate;
|
||||
static MacOSNotificationCenterPrivate *m_instance;
|
||||
};
|
||||
|
||||
@interface MacOSNotificationCenterDelegate : NSObject<NSUserNotificationCenterDelegate> {}
|
||||
@end
|
||||
|
||||
@implementation MacOSNotificationCenterDelegate
|
||||
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification
|
||||
{
|
||||
Q_UNUSED(center);
|
||||
Q_UNUSED(notification);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didDeliverNotification:(NSUserNotification *)notification
|
||||
{
|
||||
Q_UNUSED(center);
|
||||
Q_UNUSED(notification);
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "Send notification " << [notification.userInfo[@"id"] intValue];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
MacOSNotificationCenterPrivate *MacOSNotificationCenterPrivate::m_instance = nullptr;
|
||||
|
||||
MacOSNotificationCenterPrivate::MacOSNotificationCenterPrivate()
|
||||
: m_internalCounter(0)
|
||||
{
|
||||
// Set delegate
|
||||
m_delegate = [MacOSNotificationCenterDelegate alloc];
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:m_delegate];
|
||||
}
|
||||
|
||||
MacOSNotificationCenterPrivate::~MacOSNotificationCenterPrivate()
|
||||
{
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate: nil];
|
||||
[m_delegate release];
|
||||
|
||||
// Try to finish all NSNotification
|
||||
NSArray<NSUserNotification *> *deliveredNotifications = [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications;
|
||||
for (NSUserNotification *deliveredNotification in deliveredNotifications) {
|
||||
// Remove NSNotification in notification center
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: deliveredNotification];
|
||||
}
|
||||
}
|
||||
|
||||
MacOSNotificationCenterPrivate *MacOSNotificationCenterPrivate::instance() {
|
||||
if (!m_instance) {
|
||||
m_instance = new MacOSNotificationCenterPrivate();
|
||||
}
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
|
||||
NotifyByMacOSNotificationCenter::NotifyByMacOSNotificationCenter(QObject* parent)
|
||||
: KNotificationPlugin(parent)
|
||||
{
|
||||
// Clear notifications
|
||||
NSArray<NSUserNotification *> *deliveredNotifications = [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications;
|
||||
for (NSUserNotification *deliveredNotification in deliveredNotifications) {
|
||||
// Remove NSNotification in notification center
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: deliveredNotification];
|
||||
}
|
||||
}
|
||||
|
||||
NotifyByMacOSNotificationCenter::~NotifyByMacOSNotificationCenter() {}
|
||||
|
||||
void NotifyByMacOSNotificationCenter::notify(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
Q_UNUSED(notifyConfig);
|
||||
|
||||
int internalId = MacOSNotificationCenterPrivate::instance()->m_internalCounter++;
|
||||
NSUserNotification *osxNotification = [[[NSUserNotification alloc] init] autorelease];
|
||||
NSString *notificationId = [NSString stringWithFormat: @"%d", notification->id()];
|
||||
NSString *internalNotificationId = [NSString stringWithFormat: @"%d", internalId];
|
||||
NSString *title = notification->title().toNSString();
|
||||
NSString *text = notification->text().toNSString();
|
||||
|
||||
osxNotification.title = title;
|
||||
osxNotification.userInfo = [NSDictionary dictionaryWithObjectsAndKeys: notificationId, @"id",
|
||||
internalNotificationId, @"internalId", nil];
|
||||
osxNotification.informativeText = text;
|
||||
|
||||
if (notification->pixmap().isNull()) {
|
||||
QIcon notificationIcon = QIcon::fromTheme(notification->iconName());
|
||||
if (!notificationIcon.isNull()) {
|
||||
osxNotification.contentImage = [[NSImage alloc]
|
||||
initWithCGImage: notificationIcon.pixmap(QSize(64, 64)).toImage().toCGImage() size: NSMakeSize(64, 64)];
|
||||
}
|
||||
} else {
|
||||
osxNotification.contentImage = [[NSImage alloc]
|
||||
initWithCGImage: notification->pixmap().toImage().toCGImage() size: NSMakeSize(64, 64)];
|
||||
}
|
||||
|
||||
if (notification->actions().isEmpty()) {
|
||||
// Remove all buttons
|
||||
osxNotification.hasReplyButton = false;
|
||||
osxNotification.hasActionButton = false;
|
||||
} else {
|
||||
osxNotification.hasActionButton = true;
|
||||
// Workaround: this "API" will cause refuse from Apple
|
||||
// [osxNotification setValue:[NSNumber numberWithBool:YES] forKey: @"_alwaysShowAlternateActionMenu"];
|
||||
|
||||
// Assign first action to action button
|
||||
if (!notification->actions().empty()) {
|
||||
osxNotification.actionButtonTitle = notification->actions().at(0)->label().toNSString();
|
||||
}
|
||||
|
||||
// Construct a list for all actions left for additional buttons
|
||||
NSMutableArray<NSUserNotificationAction*> *actions = [[NSMutableArray alloc] init];
|
||||
for (int index = 1; index < notification->actions().length(); index++) {
|
||||
NSUserNotificationAction *action =
|
||||
[NSUserNotificationAction actionWithIdentifier: [NSString stringWithFormat:@"%d", index]
|
||||
title: notification->actions().at(index)->label().toNSString()];
|
||||
[actions addObject: action];
|
||||
}
|
||||
osxNotification.additionalActions = actions;
|
||||
}
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: osxNotification];
|
||||
|
||||
MacOSNotificationCenterPrivate::instance()->m_notifications.insert(internalId, notification);
|
||||
}
|
||||
|
||||
void NotifyByMacOSNotificationCenter::close(KNotification *notification)
|
||||
{
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "Remove notification " << notification->id();
|
||||
|
||||
NSArray<NSUserNotification *> *deliveredNotifications = [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications;
|
||||
for (NSUserNotification *deliveredNotification in deliveredNotifications) {
|
||||
if ([deliveredNotification.userInfo[@"id"] intValue] == notification->id()) {
|
||||
// Remove KNotification in mapping
|
||||
int internalId = [deliveredNotification.userInfo[@"id"] intValue];
|
||||
|
||||
MacOSNotificationCenterPrivate::instance()->m_notifications.remove(internalId);
|
||||
|
||||
// Remove NSNotification in notification center
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: deliveredNotification];
|
||||
finish(notification);
|
||||
return;
|
||||
}
|
||||
}
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "Notification " << notification->id() << " not found";
|
||||
finish(notification);
|
||||
}
|
||||
|
||||
void NotifyByMacOSNotificationCenter::update(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
close(notification);
|
||||
notify(notification, notifyConfig);
|
||||
}
|
||||
|
||||
#include "moc_notifybymacosnotificationcenter.cpp"
|
||||
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2005-2009 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2008 Dmitry Suzdalev <dimsuz@gmail.com>
|
||||
SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "notifybypopup.h"
|
||||
|
||||
#include "debug_p.h"
|
||||
#include "imageconverter.h"
|
||||
#include "knotification.h"
|
||||
#include "knotificationreplyaction.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDBusConnection>
|
||||
#include <QGuiApplication>
|
||||
#include <QHash>
|
||||
#include <QIcon>
|
||||
#include <QMutableListIterator>
|
||||
#include <QPointer>
|
||||
#include <QUrl>
|
||||
|
||||
#include <KConfigGroup>
|
||||
|
||||
NotifyByPopup::NotifyByPopup(QObject *parent)
|
||||
: KNotificationPlugin(parent)
|
||||
, m_dbusInterface(QStringLiteral("org.freedesktop.Notifications"), QStringLiteral("/org/freedesktop/Notifications"), QDBusConnection::sessionBus())
|
||||
{
|
||||
m_dbusServiceCapCacheDirty = true;
|
||||
|
||||
connect(&m_dbusInterface, &org::freedesktop::Notifications::ActionInvoked, this, &NotifyByPopup::onNotificationActionInvoked);
|
||||
connect(&m_dbusInterface, &org::freedesktop::Notifications::ActivationToken, this, &NotifyByPopup::onNotificationActionTokenReceived);
|
||||
|
||||
// TODO can we check if this actually worked?
|
||||
// probably not as this just does a DBus filter which will work but the signal might still get caught in apparmor :/
|
||||
connect(&m_dbusInterface, &org::freedesktop::Notifications::NotificationReplied, this, &NotifyByPopup::onNotificationReplied);
|
||||
|
||||
connect(&m_dbusInterface, &org::freedesktop::Notifications::NotificationClosed, this, &NotifyByPopup::onNotificationClosed);
|
||||
}
|
||||
|
||||
NotifyByPopup::~NotifyByPopup()
|
||||
{
|
||||
if (!m_notificationQueue.isEmpty()) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Had queued notifications on destruction. Was the eventloop running?";
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByPopup::notify(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
if (m_dbusServiceCapCacheDirty) {
|
||||
// if we don't have the server capabilities yet, we need to query for them first;
|
||||
// as that is an async dbus operation, we enqueue the notification and process them
|
||||
// when we receive dbus reply with the server capabilities
|
||||
m_notificationQueue.append(qMakePair(notification, notifyConfig));
|
||||
queryPopupServerCapabilities();
|
||||
} else {
|
||||
if (!sendNotificationToServer(notification, notifyConfig)) {
|
||||
finish(notification); // an error occurred.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByPopup::update(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
sendNotificationToServer(notification, notifyConfig, true);
|
||||
}
|
||||
|
||||
void NotifyByPopup::close(KNotification *notification)
|
||||
{
|
||||
QMutableListIterator<QPair<KNotification *, KNotifyConfig>> iter(m_notificationQueue);
|
||||
while (iter.hasNext()) {
|
||||
auto &item = iter.next();
|
||||
if (item.first == notification) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
uint id = m_notifications.key(notification, 0);
|
||||
|
||||
if (id == 0) {
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "not found dbus id to close" << notification->id();
|
||||
return;
|
||||
}
|
||||
|
||||
m_dbusInterface.CloseNotification(id);
|
||||
}
|
||||
|
||||
void NotifyByPopup::onNotificationActionTokenReceived(uint notificationId, const QString &xdgActivationToken)
|
||||
{
|
||||
auto iter = m_notifications.find(notificationId);
|
||||
if (iter == m_notifications.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
KNotification *n = *iter;
|
||||
if (n) {
|
||||
Q_EMIT xdgActivationTokenReceived(n->id(), xdgActivationToken);
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByPopup::onNotificationActionInvoked(uint notificationId, const QString &actionKey)
|
||||
{
|
||||
auto iter = m_notifications.find(notificationId);
|
||||
if (iter == m_notifications.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
KNotification *n = *iter;
|
||||
if (n) {
|
||||
if (actionKey == QLatin1String("inline-reply") && n->replyAction()) {
|
||||
Q_EMIT replied(n->id(), QString());
|
||||
} else {
|
||||
Q_EMIT actionInvoked(n->id(), actionKey);
|
||||
}
|
||||
} else {
|
||||
m_notifications.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByPopup::onNotificationClosed(uint dbus_id, uint reason)
|
||||
{
|
||||
auto iter = m_notifications.find(dbus_id);
|
||||
if (iter == m_notifications.end()) {
|
||||
return;
|
||||
}
|
||||
KNotification *n = *iter;
|
||||
m_notifications.remove(dbus_id);
|
||||
|
||||
if (n) {
|
||||
Q_EMIT finished(n);
|
||||
// The popup bubble is the only user facing part of a notification,
|
||||
// if the user closes the popup, it means he wants to get rid
|
||||
// of the notification completely, including playing sound etc
|
||||
// Therefore we close the KNotification completely after closing
|
||||
// the popup, but only if the reason is 2, which means "user closed"
|
||||
if (reason == 2) {
|
||||
n->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByPopup::onNotificationReplied(uint notificationId, const QString &text)
|
||||
{
|
||||
auto iter = m_notifications.find(notificationId);
|
||||
if (iter == m_notifications.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
KNotification *n = *iter;
|
||||
if (n) {
|
||||
if (n->replyAction()) {
|
||||
Q_EMIT replied(n->id(), text);
|
||||
}
|
||||
} else {
|
||||
m_notifications.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByPopup::getAppCaptionAndIconName(const KNotifyConfig ¬ifyConfig, QString *appCaption, QString *iconName)
|
||||
{
|
||||
*appCaption = notifyConfig.readGlobalEntry(QStringLiteral("Name"));
|
||||
if (appCaption->isEmpty()) {
|
||||
*appCaption = notifyConfig.readGlobalEntry(QStringLiteral("Comment"));
|
||||
}
|
||||
if (appCaption->isEmpty()) {
|
||||
*appCaption = notifyConfig.applicationName();
|
||||
}
|
||||
|
||||
*iconName = notifyConfig.readEntry(QStringLiteral("IconName"));
|
||||
if (iconName->isEmpty()) {
|
||||
*iconName = notifyConfig.readGlobalEntry(QStringLiteral("IconName"));
|
||||
}
|
||||
if (iconName->isEmpty()) {
|
||||
*iconName = qGuiApp->windowIcon().name();
|
||||
}
|
||||
if (iconName->isEmpty()) {
|
||||
*iconName = notifyConfig.applicationName();
|
||||
}
|
||||
}
|
||||
|
||||
bool NotifyByPopup::sendNotificationToServer(KNotification *notification, const KNotifyConfig ¬ifyConfig_nocheck, bool update)
|
||||
{
|
||||
uint updateId = m_notifications.key(notification, 0);
|
||||
|
||||
if (update) {
|
||||
if (updateId == 0) {
|
||||
// we have nothing to update; the notification we're trying to update
|
||||
// has been already closed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QString appCaption;
|
||||
QString iconName;
|
||||
getAppCaptionAndIconName(notifyConfig_nocheck, &appCaption, &iconName);
|
||||
|
||||
// did the user override the icon name?
|
||||
if (!notification->iconName().isEmpty()) {
|
||||
iconName = notification->iconName();
|
||||
}
|
||||
|
||||
QString title = notification->title().isEmpty() ? appCaption : notification->title();
|
||||
QString text = notification->text();
|
||||
|
||||
if (!m_popupServerCapabilities.contains(QLatin1String("body-markup"))) {
|
||||
text = stripRichText(text);
|
||||
}
|
||||
|
||||
QVariantMap hintsMap;
|
||||
|
||||
// freedesktop.org spec defines action list to be list like
|
||||
// (act_id1, action1, act_id2, action2, ...)
|
||||
//
|
||||
// assign id's to actions like it's done in fillPopup() method
|
||||
// (i.e. starting from 1)
|
||||
QStringList actionList;
|
||||
if (m_popupServerCapabilities.contains(QLatin1String("actions"))) {
|
||||
if (notification->defaultAction()) {
|
||||
actionList.append(QStringLiteral("default"));
|
||||
actionList.append(notification->defaultAction()->label());
|
||||
}
|
||||
int actId = 0;
|
||||
const auto listActions = notification->actions();
|
||||
for (const KNotificationAction *action : listActions) {
|
||||
actId++;
|
||||
actionList.append(action->id());
|
||||
actionList.append(action->label());
|
||||
}
|
||||
|
||||
if (auto *replyAction = notification->replyAction()) {
|
||||
const bool supportsInlineReply = m_popupServerCapabilities.contains(QLatin1String("inline-reply"));
|
||||
|
||||
if (supportsInlineReply || replyAction->fallbackBehavior() == KNotificationReplyAction::FallbackBehavior::UseRegularAction) {
|
||||
actionList.append(QStringLiteral("inline-reply"));
|
||||
actionList.append(replyAction->label());
|
||||
|
||||
if (supportsInlineReply) {
|
||||
if (!replyAction->placeholderText().isEmpty()) {
|
||||
hintsMap.insert(QStringLiteral("x-kde-reply-placeholder-text"), replyAction->placeholderText());
|
||||
}
|
||||
if (!replyAction->submitButtonText().isEmpty()) {
|
||||
hintsMap.insert(QStringLiteral("x-kde-reply-submit-button-text"), replyAction->submitButtonText());
|
||||
}
|
||||
if (replyAction->submitButtonIconName().isEmpty()) {
|
||||
hintsMap.insert(QStringLiteral("x-kde-reply-submit-button-icon-name"), replyAction->submitButtonIconName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the application name to the hints.
|
||||
// According to freedesktop.org spec, the app_name is supposed to be the application's "pretty name"
|
||||
// but in some places it's handy to know the application name itself
|
||||
if (!notification->appName().isEmpty()) {
|
||||
hintsMap[QStringLiteral("x-kde-appname")] = notification->appName();
|
||||
}
|
||||
|
||||
if (!notification->eventId().isEmpty()) {
|
||||
hintsMap[QStringLiteral("x-kde-eventId")] = notification->eventId();
|
||||
}
|
||||
|
||||
if (notification->flags() & KNotification::SkipGrouping) {
|
||||
hintsMap[QStringLiteral("x-kde-skipGrouping")] = 1;
|
||||
}
|
||||
|
||||
QString desktopFileName = QGuiApplication::desktopFileName();
|
||||
if (!desktopFileName.isEmpty()) {
|
||||
// handle apps which set the desktopFileName property with filename suffix,
|
||||
// due to unclear API dox (https://bugreports.qt.io/browse/QTBUG-75521)
|
||||
if (desktopFileName.endsWith(QLatin1String(".desktop"))) {
|
||||
desktopFileName.chop(8);
|
||||
}
|
||||
hintsMap[QStringLiteral("desktop-entry")] = desktopFileName;
|
||||
}
|
||||
|
||||
int urgency = -1;
|
||||
switch (notification->urgency()) {
|
||||
case KNotification::DefaultUrgency:
|
||||
break;
|
||||
case KNotification::LowUrgency:
|
||||
urgency = 0;
|
||||
break;
|
||||
case KNotification::NormalUrgency:
|
||||
Q_FALLTHROUGH();
|
||||
// freedesktop.org m_notifications only know low, normal, critical
|
||||
case KNotification::HighUrgency:
|
||||
urgency = 1;
|
||||
break;
|
||||
case KNotification::CriticalUrgency:
|
||||
urgency = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (urgency > -1) {
|
||||
hintsMap[QStringLiteral("urgency")] = urgency;
|
||||
}
|
||||
|
||||
const QVariantMap hints = notification->hints();
|
||||
for (auto it = hints.constBegin(); it != hints.constEnd(); ++it) {
|
||||
hintsMap[it.key()] = it.value();
|
||||
}
|
||||
|
||||
// FIXME - re-enable/fix
|
||||
// let's see if we've got an image, and store the image in the hints map
|
||||
if (!notification->pixmap().isNull()) {
|
||||
QByteArray pixmapData;
|
||||
QBuffer buffer(&pixmapData);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
notification->pixmap().save(&buffer, "PNG");
|
||||
buffer.close();
|
||||
hintsMap[QStringLiteral("image_data")] = ImageConverter::variantForImage(QImage::fromData(pixmapData));
|
||||
}
|
||||
|
||||
// Persistent => 0 == infinite timeout
|
||||
// CloseOnTimeout => -1 == let the server decide
|
||||
int timeout = (notification->flags() & KNotification::Persistent) ? 0 : -1;
|
||||
|
||||
const QDBusPendingReply<uint> reply = m_dbusInterface.Notify(appCaption, updateId, iconName, title, text, actionList, hintsMap, timeout);
|
||||
|
||||
// parent is set to the notification so that no-one ever accesses a dangling pointer on the notificationObject property
|
||||
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, notification);
|
||||
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, notification](QDBusPendingCallWatcher *watcher) {
|
||||
watcher->deleteLater();
|
||||
QDBusPendingReply<uint> reply = *watcher;
|
||||
m_notifications.insert(reply.argumentAt<0>(), notification);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NotifyByPopup::queryPopupServerCapabilities()
|
||||
{
|
||||
if (!m_dbusServiceCapCacheDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDBusPendingReply<QStringList> call = m_dbusInterface.GetCapabilities();
|
||||
|
||||
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call);
|
||||
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
|
||||
watcher->deleteLater();
|
||||
const QDBusPendingReply<QStringList> reply = *watcher;
|
||||
const QStringList capabilities = reply.argumentAt<0>();
|
||||
m_popupServerCapabilities = capabilities;
|
||||
m_dbusServiceCapCacheDirty = false;
|
||||
|
||||
// re-run notify() on all enqueued m_notifications
|
||||
for (const QPair<KNotification *, KNotifyConfig> ¬i : std::as_const(m_notificationQueue)) {
|
||||
notify(noti.first, noti.second);
|
||||
}
|
||||
|
||||
m_notificationQueue.clear();
|
||||
});
|
||||
}
|
||||
|
||||
#include "moc_notifybypopup.cpp"
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2008 Dmitry Suzdalev <dimsuz@gmail.com>
|
||||
SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef NOTIFYBYPOPUP_H
|
||||
#define NOTIFYBYPOPUP_H
|
||||
|
||||
#include "knotificationplugin.h"
|
||||
|
||||
#include "knotifyconfig.h"
|
||||
#include <QStringList>
|
||||
|
||||
#include "notifications_interface.h"
|
||||
|
||||
class KNotification;
|
||||
class QDBusPendingCallWatcher;
|
||||
|
||||
class NotifyByPopup : public KNotificationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NotifyByPopup(QObject *parent = nullptr);
|
||||
~NotifyByPopup() override;
|
||||
|
||||
QString optionName() override
|
||||
{
|
||||
return QStringLiteral("Popup");
|
||||
}
|
||||
void notify(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
void close(KNotification *notification) override;
|
||||
void update(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
// slot which gets called when DBus signals that some notification action was invoked
|
||||
void onNotificationActionInvoked(uint notificationId, const QString &actionKey);
|
||||
void onNotificationActionTokenReceived(uint notificationId, const QString &xdgActionToken);
|
||||
// slot which gets called when DBus signals that some notification was closed
|
||||
void onNotificationClosed(uint, uint);
|
||||
void onNotificationReplied(uint notificationId, const QString &text);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Sends notification to DBus "org.freedesktop.notifications" interface.
|
||||
* @param id knotify-sid identifier of notification
|
||||
* @param config notification data
|
||||
* @param update If true, will request the DBus service to update
|
||||
the notification with new data from \c notification
|
||||
* Otherwise will put new notification on screen
|
||||
* @return true for success or false if there was an error.
|
||||
*/
|
||||
bool sendNotificationToServer(KNotification *notification, const KNotifyConfig &config, bool update = false);
|
||||
|
||||
/**
|
||||
* Find the caption and the icon name of the application
|
||||
*/
|
||||
void getAppCaptionAndIconName(const KNotifyConfig &config, QString *appCaption, QString *iconName);
|
||||
/*
|
||||
* Query the dbus server for notification capabilities
|
||||
*/
|
||||
void queryPopupServerCapabilities();
|
||||
|
||||
/**
|
||||
* DBus notification daemon capabilities cache.
|
||||
* Do not use this variable. Use #popupServerCapabilities() instead.
|
||||
* @see popupServerCapabilities
|
||||
*/
|
||||
QStringList m_popupServerCapabilities;
|
||||
|
||||
/**
|
||||
* In case we still don't know notification server capabilities,
|
||||
* we need to query those first. That's done in an async way
|
||||
* so we queue all notifications while waiting for the capabilities
|
||||
* to return, then process them from this queue
|
||||
*/
|
||||
QList<QPair<KNotification *, KNotifyConfig>> m_notificationQueue;
|
||||
/**
|
||||
* Whether the DBus notification daemon capability cache is up-to-date.
|
||||
*/
|
||||
bool m_dbusServiceCapCacheDirty;
|
||||
|
||||
/*
|
||||
* As we communicate with the notification server over dbus
|
||||
* we use only ids, this is for fast KNotifications lookup
|
||||
*/
|
||||
QHash<uint, QPointer<KNotification>> m_notifications;
|
||||
|
||||
org::freedesktop::Notifications m_dbusInterface;
|
||||
|
||||
Q_DISABLE_COPY_MOVE(NotifyByPopup)
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2008 Dmitry Suzdalev <dimsuz@gmail.com>
|
||||
SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
|
||||
SPDX-FileCopyrightText: 2016 Jan Grulich <jgrulich@redhat.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "notifybyportal.h"
|
||||
|
||||
#include "debug_p.h"
|
||||
#include "knotification.h"
|
||||
#include "knotifyconfig.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusConnectionInterface>
|
||||
#include <QDBusError>
|
||||
#include <QDBusMessage>
|
||||
#include <QDBusMetaType>
|
||||
#include <QDBusServiceWatcher>
|
||||
#include <QGuiApplication>
|
||||
#include <QHash>
|
||||
#include <QIcon>
|
||||
#include <QMap>
|
||||
#include <QPointer>
|
||||
|
||||
#include <KConfigGroup>
|
||||
static const char portalDbusServiceName[] = "org.freedesktop.portal.Desktop";
|
||||
static const char portalDbusInterfaceName[] = "org.freedesktop.portal.Notification";
|
||||
static const char portalDbusPath[] = "/org/freedesktop/portal/desktop";
|
||||
|
||||
class NotifyByPortalPrivate
|
||||
{
|
||||
public:
|
||||
struct PortalIcon {
|
||||
QString str;
|
||||
QDBusVariant data;
|
||||
};
|
||||
|
||||
NotifyByPortalPrivate(NotifyByPortal *parent)
|
||||
: dbusServiceExists(false)
|
||||
, q(parent)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends notification to DBus "org.freedesktop.notifications" interface.
|
||||
* @param id knotify-sid identifier of notification
|
||||
* @param config notification data
|
||||
* @param update If true, will request the DBus service to update
|
||||
the notification with new data from \c notification
|
||||
* Otherwise will put new notification on screen
|
||||
* @return true for success or false if there was an error.
|
||||
*/
|
||||
bool sendNotificationToPortal(KNotification *notification, const KNotifyConfig &config);
|
||||
|
||||
/**
|
||||
* Sends request to close Notification with id to DBus "org.freedesktop.notifications" interface
|
||||
* @param id knotify-side notification ID to close
|
||||
*/
|
||||
|
||||
void closePortalNotification(KNotification *notification);
|
||||
/**
|
||||
* Find the caption and the icon name of the application
|
||||
*/
|
||||
|
||||
void getAppCaptionAndIconName(const KNotifyConfig &config, QString *appCaption, QString *iconName);
|
||||
|
||||
/**
|
||||
* Specifies if DBus Notifications interface exists on session bus
|
||||
*/
|
||||
bool dbusServiceExists;
|
||||
|
||||
/*
|
||||
* As we communicate with the notification server over dbus
|
||||
* we use only ids, this is for fast KNotifications lookup
|
||||
*/
|
||||
QHash<uint, QPointer<KNotification>> portalNotifications;
|
||||
|
||||
/*
|
||||
* Holds the id that will be assigned to the next notification source
|
||||
* that will be created
|
||||
*/
|
||||
uint nextId;
|
||||
|
||||
NotifyByPortal *const q;
|
||||
};
|
||||
|
||||
QDBusArgument &operator<<(QDBusArgument &argument, const NotifyByPortalPrivate::PortalIcon &icon)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument << icon.str << icon.data;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
const QDBusArgument &operator>>(const QDBusArgument &argument, NotifyByPortalPrivate::PortalIcon &icon)
|
||||
{
|
||||
argument.beginStructure();
|
||||
argument >> icon.str >> icon.data;
|
||||
argument.endStructure();
|
||||
return argument;
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(NotifyByPortalPrivate::PortalIcon)
|
||||
|
||||
//---------------------------------------------------------------------------------------
|
||||
|
||||
NotifyByPortal::NotifyByPortal(QObject *parent)
|
||||
: KNotificationPlugin(parent)
|
||||
, d(new NotifyByPortalPrivate(this))
|
||||
{
|
||||
// check if service already exists on plugin instantiation
|
||||
QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
|
||||
d->dbusServiceExists = interface && interface->isServiceRegistered(QString::fromLatin1(portalDbusServiceName));
|
||||
|
||||
if (d->dbusServiceExists) {
|
||||
onServiceOwnerChanged(QString::fromLatin1(portalDbusServiceName), QString(), QStringLiteral("_")); // connect signals
|
||||
}
|
||||
|
||||
// to catch register/unregister events from service in runtime
|
||||
QDBusServiceWatcher *watcher = new QDBusServiceWatcher(this);
|
||||
watcher->setConnection(QDBusConnection::sessionBus());
|
||||
watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
|
||||
watcher->addWatchedService(QString::fromLatin1(portalDbusServiceName));
|
||||
connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &NotifyByPortal::onServiceOwnerChanged);
|
||||
}
|
||||
|
||||
NotifyByPortal::~NotifyByPortal() = default;
|
||||
|
||||
void NotifyByPortal::notify(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
if (d->portalNotifications.contains(notification->id())) {
|
||||
// notification is already on the screen, do nothing
|
||||
finish(notification);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if Notifications DBus service exists on bus, use it if it does
|
||||
if (d->dbusServiceExists) {
|
||||
if (!d->sendNotificationToPortal(notification, notifyConfig)) {
|
||||
finish(notification); // an error occurred.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByPortal::close(KNotification *notification)
|
||||
{
|
||||
if (d->dbusServiceExists) {
|
||||
d->closePortalNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByPortal::update(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
// TODO not supported by portals
|
||||
Q_UNUSED(notification);
|
||||
Q_UNUSED(notifyConfig);
|
||||
}
|
||||
|
||||
void NotifyByPortal::onServiceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
|
||||
{
|
||||
Q_UNUSED(serviceName);
|
||||
// close all notifications we currently hold reference to
|
||||
for (KNotification *n : std::as_const(d->portalNotifications)) {
|
||||
if (n) {
|
||||
Q_EMIT finished(n);
|
||||
}
|
||||
}
|
||||
|
||||
d->portalNotifications.clear();
|
||||
|
||||
if (newOwner.isEmpty()) {
|
||||
d->dbusServiceExists = false;
|
||||
} else if (oldOwner.isEmpty()) {
|
||||
d->dbusServiceExists = true;
|
||||
d->nextId = 1;
|
||||
|
||||
// connect to action invocation signals
|
||||
bool connected = QDBusConnection::sessionBus().connect(QString(), // from any service
|
||||
QString::fromLatin1(portalDbusPath),
|
||||
QString::fromLatin1(portalDbusInterfaceName),
|
||||
QStringLiteral("ActionInvoked"),
|
||||
this,
|
||||
SLOT(onPortalNotificationActionInvoked(QString, QString, QVariantList)));
|
||||
if (!connected) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "warning: failed to connect to ActionInvoked dbus signal";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByPortal::onPortalNotificationActionInvoked(const QString &id, const QString &action, const QVariantList ¶meter)
|
||||
{
|
||||
Q_UNUSED(parameter);
|
||||
|
||||
auto iter = d->portalNotifications.find(id.toUInt());
|
||||
if (iter == d->portalNotifications.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
KNotification *n = *iter;
|
||||
if (n) {
|
||||
Q_EMIT actionInvoked(n->id(), action);
|
||||
} else {
|
||||
d->portalNotifications.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyByPortalPrivate::getAppCaptionAndIconName(const KNotifyConfig ¬ifyConfig, QString *appCaption, QString *iconName)
|
||||
{
|
||||
*appCaption = notifyConfig.readGlobalEntry(QStringLiteral("Name"));
|
||||
if (appCaption->isEmpty()) {
|
||||
*appCaption = notifyConfig.readGlobalEntry(QStringLiteral("Comment"));
|
||||
}
|
||||
if (appCaption->isEmpty()) {
|
||||
*appCaption = notifyConfig.applicationName();
|
||||
}
|
||||
|
||||
*iconName = notifyConfig.readEntry(QStringLiteral("IconName"));
|
||||
if (iconName->isEmpty()) {
|
||||
*iconName = notifyConfig.readGlobalEntry(QStringLiteral("IconName"));
|
||||
}
|
||||
if (iconName->isEmpty()) {
|
||||
*iconName = qGuiApp->windowIcon().name();
|
||||
}
|
||||
if (iconName->isEmpty()) {
|
||||
*iconName = notifyConfig.applicationName();
|
||||
}
|
||||
}
|
||||
|
||||
bool NotifyByPortalPrivate::sendNotificationToPortal(KNotification *notification, const KNotifyConfig ¬ifyConfig_nocheck)
|
||||
{
|
||||
QDBusMessage dbusNotificationMessage;
|
||||
dbusNotificationMessage = QDBusMessage::createMethodCall(QString::fromLatin1(portalDbusServiceName),
|
||||
QString::fromLatin1(portalDbusPath),
|
||||
QString::fromLatin1(portalDbusInterfaceName),
|
||||
QStringLiteral("AddNotification"));
|
||||
|
||||
QVariantList args;
|
||||
// Will be used only with xdg-desktop-portal
|
||||
QVariantMap portalArgs;
|
||||
|
||||
QString appCaption;
|
||||
QString iconName;
|
||||
getAppCaptionAndIconName(notifyConfig_nocheck, &appCaption, &iconName);
|
||||
|
||||
// did the user override the icon name?
|
||||
if (!notification->iconName().isEmpty()) {
|
||||
iconName = notification->iconName();
|
||||
}
|
||||
|
||||
QString title = notification->title().isEmpty() ? appCaption : notification->title();
|
||||
QString text = notification->text();
|
||||
|
||||
if (notification->defaultAction()) {
|
||||
portalArgs.insert(QStringLiteral("default-action"), QStringLiteral("default"));
|
||||
portalArgs.insert(QStringLiteral("default-action-target"), QStringLiteral("0"));
|
||||
}
|
||||
|
||||
QString priority;
|
||||
switch (notification->urgency()) {
|
||||
case KNotification::DefaultUrgency:
|
||||
break;
|
||||
case KNotification::LowUrgency:
|
||||
priority = QStringLiteral("low");
|
||||
break;
|
||||
case KNotification::NormalUrgency:
|
||||
priority = QStringLiteral("normal");
|
||||
break;
|
||||
case KNotification::HighUrgency:
|
||||
priority = QStringLiteral("high");
|
||||
break;
|
||||
case KNotification::CriticalUrgency:
|
||||
priority = QStringLiteral("urgent");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!priority.isEmpty()) {
|
||||
portalArgs.insert(QStringLiteral("priority"), priority);
|
||||
}
|
||||
|
||||
// freedesktop.org spec defines action list to be list like
|
||||
// (act_id1, action1, act_id2, action2, ...)
|
||||
//
|
||||
// assign id's to actions like it's done in fillPopup() method
|
||||
// (i.e. starting from 1)
|
||||
QList<QVariantMap> buttons;
|
||||
buttons.reserve(notification->actions().count());
|
||||
|
||||
const auto listActions = notification->actions();
|
||||
for (KNotificationAction *action : listActions) {
|
||||
QVariantMap button = {{QStringLiteral("action"), action->id()}, //
|
||||
{QStringLiteral("label"), action->label()}};
|
||||
buttons << button;
|
||||
}
|
||||
|
||||
qDBusRegisterMetaType<QList<QVariantMap>>();
|
||||
qDBusRegisterMetaType<PortalIcon>();
|
||||
|
||||
if (!notification->pixmap().isNull()) {
|
||||
QByteArray pixmapData;
|
||||
QBuffer buffer(&pixmapData);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
notification->pixmap().save(&buffer, "PNG");
|
||||
buffer.close();
|
||||
|
||||
PortalIcon icon;
|
||||
icon.str = QStringLiteral("bytes");
|
||||
icon.data.setVariant(pixmapData);
|
||||
portalArgs.insert(QStringLiteral("icon"), QVariant::fromValue<PortalIcon>(icon));
|
||||
} else {
|
||||
// Use this for now for backwards compatibility, we can as well set the variant to be (sv) where the
|
||||
// string is keyword "themed" and the variant is an array of strings with icon names
|
||||
portalArgs.insert(QStringLiteral("icon"), iconName);
|
||||
}
|
||||
|
||||
portalArgs.insert(QStringLiteral("title"), title);
|
||||
portalArgs.insert(QStringLiteral("body"), text);
|
||||
portalArgs.insert(QStringLiteral("buttons"), QVariant::fromValue<QList<QVariantMap>>(buttons));
|
||||
|
||||
args.append(QString::number(nextId));
|
||||
args.append(portalArgs);
|
||||
|
||||
dbusNotificationMessage.setArguments(args);
|
||||
|
||||
QDBusPendingCall notificationCall = QDBusConnection::sessionBus().asyncCall(dbusNotificationMessage, -1);
|
||||
|
||||
// If we are in sandbox we don't need to wait for returned notification id
|
||||
portalNotifications.insert(nextId++, notification);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void NotifyByPortalPrivate::closePortalNotification(KNotification *notification)
|
||||
{
|
||||
uint id = portalNotifications.key(notification, 0);
|
||||
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "ID: " << id;
|
||||
|
||||
if (id == 0) {
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "not found dbus id to close" << notification->id();
|
||||
return;
|
||||
}
|
||||
|
||||
QDBusMessage m = QDBusMessage::createMethodCall(QString::fromLatin1(portalDbusServiceName),
|
||||
QString::fromLatin1(portalDbusPath),
|
||||
QString::fromLatin1(portalDbusInterfaceName),
|
||||
QStringLiteral("RemoveNotification"));
|
||||
m.setArguments({QString::number(id)});
|
||||
|
||||
// send(..) does not block
|
||||
bool queued = QDBusConnection::sessionBus().send(m);
|
||||
|
||||
if (!queued) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Failed to queue dbus message for closing a notification";
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_notifybyportal.cpp"
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2005-2006 Olivier Goffart <ogoffart at kde.org>
|
||||
SPDX-FileCopyrightText: 2008 Dmitry Suzdalev <dimsuz@gmail.com>
|
||||
SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
|
||||
SPDX-FileCopyrightText: 2016 Jan Grulich <jgrulich@redhat.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef NOTIFYBYPORTAL_H
|
||||
#define NOTIFYBYPORTAL_H
|
||||
|
||||
#include "knotificationplugin.h"
|
||||
|
||||
#include <QVariantList>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class KNotification;
|
||||
class NotifyByPortalPrivate;
|
||||
|
||||
class NotifyByPortal : public KNotificationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NotifyByPortal(QObject *parent = nullptr);
|
||||
~NotifyByPortal() override;
|
||||
|
||||
QString optionName() override
|
||||
{
|
||||
return QStringLiteral("Popup");
|
||||
}
|
||||
void notify(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
void close(KNotification *notification) override;
|
||||
void update(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
|
||||
// slot to catch appearance or disappearance of org.freedesktop.Desktop DBus service
|
||||
void onServiceOwnerChanged(const QString &, const QString &, const QString &);
|
||||
|
||||
void onPortalNotificationActionInvoked(const QString &, const QString &, const QVariantList &);
|
||||
|
||||
private:
|
||||
std::unique_ptr<NotifyByPortalPrivate> const d;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2019 Piyush Aggarwal <piyushaggarwal002@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "notifybysnore.h"
|
||||
#include "debug_p.h"
|
||||
#include "knotification.h"
|
||||
#include "knotifyconfig.h"
|
||||
#include "knotificationreplyaction.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QIcon>
|
||||
#include <QLocalSocket>
|
||||
|
||||
#include <snoretoastactions.h>
|
||||
|
||||
/*
|
||||
* On Windows a shortcut to your app is needed to be installed in the Start Menu
|
||||
* (and subsequently, registered with the OS) in order to show notifications.
|
||||
* Since KNotifications is a library, an app using it can't (feasibly) be properly
|
||||
* registered with the OS. It is possible we could come up with some complicated solution
|
||||
* which would require every KNotification-using app to do some special and probably
|
||||
* difficult to understand change to support Windows. Or we can have SnoreToast.exe
|
||||
* take care of all that nonsense for us.
|
||||
* Note that, up to this point, there have been no special
|
||||
* KNotifications changes to the generic application codebase to make this work,
|
||||
* just some tweaks to the Craft blueprint and packaging script
|
||||
* to pull in SnoreToast and trigger shortcut building respectively.
|
||||
* Be sure to have a shortcut installed in Windows Start Menu by SnoreToast.
|
||||
*
|
||||
* So the location doesn't matter, but it's only possible to register the internal COM server in an executable.
|
||||
* We could make it a static lib and link it in all KDE applications,
|
||||
* but to make the action center integration work, we would need to also compile a class
|
||||
* into the executable using a compile time uuid.
|
||||
*
|
||||
* The used header is meant to help with parsing the response.
|
||||
* The cmake target for LibSnoreToast is a INTERFACE lib, it only provides the include path.
|
||||
*
|
||||
*
|
||||
* Trigger the shortcut installation during the installation of your app; syntax for shortcut installation is -
|
||||
* ./SnoreToast.exe -install <absolute\address\of\shortcut> <absolute\address\to\app.exe> <appID>
|
||||
*
|
||||
* appID: use as-is from your app's QCoreApplication::applicationName() when installing the shortcut.
|
||||
* NOTE: Install the shortcut in Windows Start Menu folder.
|
||||
* For example, check out Craft Blueprint for Quassel-IRC or KDE Connect.
|
||||
*/
|
||||
|
||||
namespace
|
||||
{
|
||||
const QString SnoreToastExecName()
|
||||
{
|
||||
return QStringLiteral("SnoreToast.exe");
|
||||
}
|
||||
}
|
||||
|
||||
NotifyBySnore::NotifyBySnore(QObject *parent)
|
||||
: KNotificationPlugin(parent)
|
||||
{
|
||||
m_server.listen(QString::number(qHash(qApp->applicationDirPath())));
|
||||
connect(&m_server, &QLocalServer::newConnection, this, [this]() {
|
||||
QLocalSocket *responseSocket = m_server.nextPendingConnection();
|
||||
connect(responseSocket, &QLocalSocket::readyRead, [this, responseSocket]() {
|
||||
const QByteArray rawNotificationResponse = responseSocket->readAll();
|
||||
responseSocket->deleteLater();
|
||||
|
||||
const QString notificationResponse = QString::fromWCharArray(reinterpret_cast<const wchar_t *>(rawNotificationResponse.constData()),
|
||||
rawNotificationResponse.size() / sizeof(wchar_t));
|
||||
qCDebug(LOG_KNOTIFICATIONS) << notificationResponse;
|
||||
|
||||
QMap<QString, QStringView> notificationResponseMap;
|
||||
for (const auto str : QStringView(notificationResponse).split(QLatin1Char(';'))) {
|
||||
const int equalIndex = str.indexOf(QLatin1Char('='));
|
||||
notificationResponseMap.insert(str.mid(0, equalIndex).toString(), str.mid(equalIndex + 1));
|
||||
}
|
||||
|
||||
const QString responseAction = notificationResponseMap[QStringLiteral("action")].toString();
|
||||
const int responseNotificationId = notificationResponseMap[QStringLiteral("notificationId")].toInt();
|
||||
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "The notification ID is : " << responseNotificationId;
|
||||
|
||||
KNotification *notification;
|
||||
const auto iter = m_notifications.constFind(responseNotificationId);
|
||||
if (iter != m_notifications.constEnd()) {
|
||||
notification = iter.value();
|
||||
} else {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Received a response for an unknown notification.";
|
||||
return;
|
||||
}
|
||||
|
||||
std::wstring w_action(responseAction.size(), 0);
|
||||
responseAction.toWCharArray(const_cast<wchar_t *>(w_action.data()));
|
||||
|
||||
switch (SnoreToastActions::getAction(w_action)) {
|
||||
case SnoreToastActions::Actions::Clicked:
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "User clicked on the toast.";
|
||||
break;
|
||||
|
||||
case SnoreToastActions::Actions::Hidden:
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "The toast got hidden.";
|
||||
break;
|
||||
|
||||
case SnoreToastActions::Actions::Dismissed:
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "User dismissed the toast.";
|
||||
break;
|
||||
|
||||
case SnoreToastActions::Actions::Timedout:
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "The toast timed out.";
|
||||
break;
|
||||
|
||||
case SnoreToastActions::Actions::ButtonClicked: {
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "User clicked an action button in the toast.";
|
||||
const QString responseButton = notificationResponseMap[QStringLiteral("button")].toString();
|
||||
Q_EMIT actionInvoked(responseNotificationId, responseButton);
|
||||
break;
|
||||
}
|
||||
|
||||
case SnoreToastActions::Actions::TextEntered: {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "User entered some text in the toast.";
|
||||
const QString replyText = notificationResponseMap[QStringLiteral("text")].toString();
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Text entered was :: " << replyText;
|
||||
Q_EMIT replied(responseNotificationId, replyText);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Unexpected behaviour with the toast. Please file a bug report / feature request.";
|
||||
break;
|
||||
}
|
||||
|
||||
// Action Center callbacks are not yet supported so just close the notification once done
|
||||
if (notification != nullptr) {
|
||||
NotifyBySnore::close(notification);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
NotifyBySnore::~NotifyBySnore()
|
||||
{
|
||||
m_server.close();
|
||||
}
|
||||
|
||||
void NotifyBySnore::notify(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
Q_UNUSED(notifyConfig);
|
||||
// HACK work around that notification->id() is only populated after returning from here
|
||||
// note that config will be invalid at that point, so we can't pass that along
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this, notification]() {
|
||||
NotifyBySnore::notifyDeferred(notification);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void NotifyBySnore::notifyDeferred(KNotification *notification)
|
||||
{
|
||||
m_notifications.insert(notification->id(), notification);
|
||||
|
||||
const QString notificationTitle = ((!notification->title().isEmpty()) ? notification->title() : qApp->applicationDisplayName());
|
||||
QStringList snoretoastArgsList{QStringLiteral("-id"),
|
||||
QString::number(notification->id()),
|
||||
QStringLiteral("-t"),
|
||||
notificationTitle,
|
||||
QStringLiteral("-m"),
|
||||
stripRichText(notification->text()),
|
||||
QStringLiteral("-appID"),
|
||||
qApp->applicationName(),
|
||||
QStringLiteral("-pid"),
|
||||
QString::number(qApp->applicationPid()),
|
||||
QStringLiteral("-pipename"),
|
||||
m_server.fullServerName()};
|
||||
|
||||
// handle the icon for toast notification
|
||||
const QString iconPath = m_iconDir.path() + QLatin1Char('/') + QString::number(notification->id());
|
||||
const bool hasIcon = (notification->pixmap().isNull()) ? qApp->windowIcon().pixmap(1024, 1024).save(iconPath, "PNG") //
|
||||
: notification->pixmap().save(iconPath, "PNG");
|
||||
if (hasIcon) {
|
||||
snoretoastArgsList << QStringLiteral("-p") << iconPath;
|
||||
}
|
||||
|
||||
// if'd below, because SnoreToast currently doesn't support both textbox and buttons in the same notification
|
||||
if (notification->replyAction()) {
|
||||
snoretoastArgsList << QStringLiteral("-tb");
|
||||
} else if (!notification->actions().isEmpty()) {
|
||||
// add actions if any
|
||||
|
||||
const auto actions = notification->actions();
|
||||
QStringList actionLabels;
|
||||
|
||||
for (KNotificationAction *action : actions) {
|
||||
action->setId(action->label());
|
||||
actionLabels << action->label();
|
||||
}
|
||||
|
||||
snoretoastArgsList << QStringLiteral("-b") << actionLabels.join(QLatin1Char(';'));
|
||||
}
|
||||
|
||||
QProcess *snoretoastProcess = new QProcess();
|
||||
connect(snoretoastProcess, &QProcess::readyReadStandardError, [snoretoastProcess, snoretoastArgsList]() {
|
||||
const auto data = snoretoastProcess->readAllStandardError();
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "SnoreToast process stderr:" << snoretoastArgsList << data;
|
||||
});
|
||||
connect(snoretoastProcess, &QProcess::readyReadStandardOutput, [snoretoastProcess, snoretoastArgsList]() {
|
||||
const auto data = snoretoastProcess->readAllStandardOutput();
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "SnoreToast process stdout:" << snoretoastArgsList << data;
|
||||
});
|
||||
connect(snoretoastProcess, &QProcess::errorOccurred, this, [this, snoretoastProcess, snoretoastArgsList, iconPath](QProcess::ProcessError error) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "SnoreToast process errored:" << snoretoastArgsList << error;
|
||||
snoretoastProcess->deleteLater();
|
||||
QFile::remove(iconPath);
|
||||
});
|
||||
connect(snoretoastProcess,
|
||||
qOverload<int, QProcess::ExitStatus>(&QProcess::finished),
|
||||
this,
|
||||
[this, snoretoastProcess, snoretoastArgsList, iconPath](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "SnoreToast process finished:" << snoretoastArgsList;
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "code:" << exitCode << "status:" << exitStatus;
|
||||
snoretoastProcess->deleteLater();
|
||||
QFile::remove(iconPath);
|
||||
});
|
||||
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "SnoreToast process starting:" << snoretoastArgsList;
|
||||
snoretoastProcess->start(SnoreToastExecName(), snoretoastArgsList);
|
||||
}
|
||||
|
||||
void NotifyBySnore::close(KNotification *notification)
|
||||
{
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "Requested to close notification with ID:" << notification->id();
|
||||
if (m_notifications.constFind(notification->id()) == m_notifications.constEnd()) {
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "Couldn't find the notification in m_notifications. Nothing to close.";
|
||||
return;
|
||||
}
|
||||
|
||||
m_notifications.remove(notification->id());
|
||||
|
||||
const QStringList snoretoastArgsList{QStringLiteral("-close"), QString::number(notification->id()), QStringLiteral("-appID"), qApp->applicationName()};
|
||||
|
||||
qCDebug(LOG_KNOTIFICATIONS) << "Closing notification; SnoreToast process arguments:" << snoretoastArgsList;
|
||||
QProcess::startDetached(SnoreToastExecName(), snoretoastArgsList);
|
||||
|
||||
finish(notification);
|
||||
}
|
||||
|
||||
void NotifyBySnore::update(KNotification *notification, const KNotifyConfig ¬ifyConfig)
|
||||
{
|
||||
Q_UNUSED(notification);
|
||||
Q_UNUSED(notifyConfig);
|
||||
qCWarning(LOG_KNOTIFICATIONS) << "updating a notification is not supported yet.";
|
||||
}
|
||||
|
||||
#include "moc_notifybysnore.cpp"
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2019 Piyush Aggarwal <piyushaggarwal002@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef NOTIFYBYSNORE_H
|
||||
#define NOTIFYBYSNORE_H
|
||||
|
||||
#include "knotificationplugin.h"
|
||||
|
||||
#include <QLocalServer>
|
||||
#include <QPointer>
|
||||
#include <QProcess>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
class NotifyBySnore : public KNotificationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit NotifyBySnore(QObject *parent = nullptr);
|
||||
~NotifyBySnore() override;
|
||||
|
||||
QString optionName() override
|
||||
{
|
||||
return QStringLiteral("Popup");
|
||||
}
|
||||
void notify(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
void notifyDeferred(KNotification *notification);
|
||||
void close(KNotification *notification) override;
|
||||
void update(KNotification *notification, const KNotifyConfig ¬ifyConfig) override;
|
||||
|
||||
private:
|
||||
QHash<int, QPointer<KNotification>> m_notifications;
|
||||
QLocalServer m_server;
|
||||
QTemporaryDir m_iconDir;
|
||||
};
|
||||
|
||||
#endif // NOTIFYBYSNORE_H
|
||||
@@ -0,0 +1,69 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.freedesktop.Notifications">
|
||||
<signal name="NotificationClosed">
|
||||
<arg name="id" type="u" direction="out"/>
|
||||
<arg name="reason" type="u" direction="out"/>
|
||||
</signal>
|
||||
<signal name="ActionInvoked">
|
||||
<arg name="id" type="u" direction="out"/>
|
||||
<arg name="action_key" type="s" direction="out"/>
|
||||
</signal>
|
||||
<!-- non-standard -->
|
||||
<signal name="NotificationReplied">
|
||||
<arg name="id" type="u" direction="out"/>
|
||||
<arg name="text" type="s" direction="out"/>
|
||||
</signal>
|
||||
<signal name="ActivationToken">
|
||||
<arg name="id" type="u" direction="out"/>
|
||||
<arg name="activation_token" type="s" direction="out"/>
|
||||
</signal>
|
||||
<method name="Notify">
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In6" value="QVariantMap"/>
|
||||
<arg type="u" direction="out"/>
|
||||
<arg name="app_name" type="s" direction="in"/>
|
||||
<arg name="replaces_id" type="u" direction="in"/>
|
||||
<arg name="app_icon" type="s" direction="in"/>
|
||||
<arg name="summary" type="s" direction="in"/>
|
||||
<arg name="body" type="s" direction="in"/>
|
||||
<arg name="actions" type="as" direction="in"/>
|
||||
<arg name="hints" type="a{sv}" direction="in"/>
|
||||
<arg name="timeout" type="i" direction="in"/>
|
||||
</method>
|
||||
<method name="CloseNotification">
|
||||
<arg name="id" type="u" direction="in"/>
|
||||
</method>
|
||||
<method name="GetCapabilities">
|
||||
<arg type="as" name="caps" direction="out"/>
|
||||
</method>
|
||||
<method name="GetServerInformation">
|
||||
<arg type="s" name="name" direction="out"/>
|
||||
<arg type="s" name="vendor" direction="out"/>
|
||||
<arg type="s" name="version" direction="out"/>
|
||||
<arg type="s" name="spec_version" direction="out"/>
|
||||
</method>
|
||||
|
||||
<!-- Inhibitions -->
|
||||
<method name="Inhibit">
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
|
||||
<arg type="u" direction="out"/>
|
||||
<arg name="desktop_entry" type="s" direction="in"/>
|
||||
<arg name="reason" type="s" direction="in"/>
|
||||
<arg name="hints" type="a{sv}" direction="in"/>
|
||||
</method>
|
||||
|
||||
<method name="UnInhibit">
|
||||
<arg type="u" direction="in"/>
|
||||
</method>
|
||||
|
||||
<property name="Inhibited" type="b" access="read">
|
||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
|
||||
</property>
|
||||
|
||||
<!--<method name="ListInhibitors">
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList<Inhibition>"/>
|
||||
<arg name="inhibitors" type="a(ssa{sv})" direction="out"/>
|
||||
</method>-->
|
||||
|
||||
</interface>
|
||||
</node>
|
||||
@@ -0,0 +1,15 @@
|
||||
# SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
ecm_add_qml_module(knotificationqmlplugin URI org.kde.notification VERSION 1.0 GENERATE_PLUGIN_SOURCE)
|
||||
|
||||
target_sources(knotificationqmlplugin PRIVATE
|
||||
knotificationqmlplugin.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(knotificationqmlplugin PRIVATE
|
||||
KF6Notifications
|
||||
Qt6::Qml
|
||||
)
|
||||
|
||||
ecm_finalize_qml_module(knotificationqmlplugin)
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "knotificationqmlplugin.h"
|
||||
|
||||
NotificationWrapper::NotificationWrapper(QObject *parent)
|
||||
: KNotification(QString(), KNotification::CloseOnTimeout, parent)
|
||||
{
|
||||
setAutoDelete(false);
|
||||
|
||||
m_actionsProperty = QQmlListProperty<KNotificationAction>(this,
|
||||
nullptr,
|
||||
&NotificationWrapper::appendAction,
|
||||
&NotificationWrapper::actionsCount,
|
||||
&NotificationWrapper::actionAt,
|
||||
&NotificationWrapper::clearActions);
|
||||
}
|
||||
|
||||
KNotificationReplyAction *NotificationWrapper::replyActionFactory()
|
||||
{
|
||||
if (!replyAction()) {
|
||||
setReplyAction(std::make_unique<KNotificationReplyAction>(QString()));
|
||||
}
|
||||
return replyAction();
|
||||
}
|
||||
|
||||
int NotificationWrapper::actionCount() const
|
||||
{
|
||||
return actions().count();
|
||||
}
|
||||
|
||||
KNotificationAction *NotificationWrapper::actionAt(qsizetype index)
|
||||
{
|
||||
return actions().at(index);
|
||||
}
|
||||
|
||||
QQmlListProperty<KNotificationAction> NotificationWrapper::actionsProperty() const
|
||||
{
|
||||
return m_actionsProperty;
|
||||
}
|
||||
|
||||
qsizetype NotificationWrapper::actionsCount(QQmlListProperty<KNotificationAction> *list)
|
||||
{
|
||||
return static_cast<NotificationWrapper *>(list->object)->actionCount();
|
||||
}
|
||||
|
||||
void NotificationWrapper::appendAction(QQmlListProperty<KNotificationAction> *list, KNotificationAction *value)
|
||||
{
|
||||
auto notification = static_cast<NotificationWrapper *>(list->object);
|
||||
auto actions = notification->actions();
|
||||
actions << value;
|
||||
notification->setActionsQml(actions);
|
||||
}
|
||||
|
||||
KNotificationAction *NotificationWrapper::actionAt(QQmlListProperty<KNotificationAction> *list, qsizetype index)
|
||||
{
|
||||
return static_cast<NotificationWrapper *>(list->object)->actionAt(index);
|
||||
}
|
||||
|
||||
void NotificationWrapper::clearActions(QQmlListProperty<KNotificationAction> *list)
|
||||
{
|
||||
auto notification = static_cast<KNotification *>(list->object);
|
||||
notification->clearActions();
|
||||
}
|
||||
|
||||
#include "moc_knotificationqmlplugin.cpp"
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KNOTIFICATIONQMLPLUGIN_H
|
||||
#define KNOTIFICATIONQMLPLUGIN_H
|
||||
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <KNotification>
|
||||
#include <KNotificationPermission>
|
||||
#include <KNotificationReplyAction>
|
||||
|
||||
struct NotificationActionForeign {
|
||||
Q_GADGET
|
||||
QML_NAMED_ELEMENT(NotificationAction)
|
||||
QML_FOREIGN(KNotificationAction);
|
||||
};
|
||||
|
||||
struct NotificationReplyActionForeign {
|
||||
Q_GADGET
|
||||
QML_NAMED_ELEMENT(NotificationReplyAction)
|
||||
QML_UNCREATABLE("")
|
||||
QML_FOREIGN(KNotificationReplyAction);
|
||||
};
|
||||
|
||||
class NotificationWrapper : public KNotification
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_NAMED_ELEMENT(Notification)
|
||||
Q_PROPERTY(KNotificationReplyAction *replyAction READ replyActionFactory CONSTANT)
|
||||
Q_PROPERTY(QQmlListProperty<KNotificationAction> actions READ actionsProperty NOTIFY actionsChanged)
|
||||
Q_PROPERTY(KNotificationAction *defaultAction READ defaultAction WRITE setDefaultActionQml NOTIFY defaultActionChanged)
|
||||
public:
|
||||
explicit NotificationWrapper(QObject *parent = nullptr);
|
||||
|
||||
KNotificationReplyAction *replyActionFactory();
|
||||
|
||||
int actionCount() const;
|
||||
|
||||
KNotificationAction *actionAt(qsizetype index);
|
||||
|
||||
QQmlListProperty<KNotificationAction> actionsProperty() const;
|
||||
|
||||
static qsizetype actionsCount(QQmlListProperty<KNotificationAction> *list);
|
||||
|
||||
static void appendAction(QQmlListProperty<KNotificationAction> *list, KNotificationAction *value);
|
||||
|
||||
static KNotificationAction *actionAt(QQmlListProperty<KNotificationAction> *list, qsizetype index);
|
||||
|
||||
static void clearActions(QQmlListProperty<KNotificationAction> *list);
|
||||
|
||||
private:
|
||||
QQmlListProperty<KNotificationAction> m_actionsProperty;
|
||||
};
|
||||
|
||||
class NotificationPermissionWrapper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_NAMED_ELEMENT(NotificationPermission)
|
||||
QML_SINGLETON
|
||||
public:
|
||||
Q_INVOKABLE bool checkPermission()
|
||||
{
|
||||
return KNotificationPermission::checkPermission() == Qt::PermissionStatus::Granted;
|
||||
}
|
||||
|
||||
Q_INVOKABLE void requestPermission(const QJSValue &callback)
|
||||
{
|
||||
KNotificationPermission::requestPermission(this, [callback](Qt::PermissionStatus status) {
|
||||
callback.call({status == Qt::PermissionStatus::Granted});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
#endif // KNOTIFICATIONQMLPLUGIN_H
|
||||
Reference in New Issue
Block a user