1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2005-2007 Olivier Goffart <ogoffart at kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include "knotifyconfigactionswidget.h"
9
10#include "knotifyconfigelement.h"
11#include <knotifyconfig_debug.h>
12
13#include <QFile>
14#include <QStandardPaths>
15#include <QUrl>
16
17#if HAVE_CANBERRA
18#include <canberra.h>
19#elif HAVE_QTMULTIMEDIA
20#include <QAudioOutput>
21#include <QMediaPlayer>
22#endif
23
24KNotifyConfigActionsWidget::KNotifyConfigActionsWidget(QWidget *parent)
25 : QWidget(parent)
26{
27 m_ui.setupUi(this);
28
29 // Show sounds directory by default
30 QStringList soundDirs = QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, QStringLiteral("sounds"), options: QStandardPaths::LocateDirectory);
31 if (!soundDirs.isEmpty()) {
32 m_ui.Sound_select->setStartDir(QUrl::fromLocalFile(localfile: soundDirs.last()));
33 }
34 m_ui.Sound_select->setMimeTypeFilters({QStringLiteral("audio/x-vorbis+ogg"), QStringLiteral("audio/x-wav")});
35
36 m_ui.Sound_play->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
37 m_ui.Sound_check->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")));
38 m_ui.Popup_check->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information")));
39
40 connect(sender: m_ui.Sound_check, SIGNAL(toggled(bool)), receiver: this, SIGNAL(changed()));
41 connect(sender: m_ui.Popup_check, SIGNAL(toggled(bool)), receiver: this, SIGNAL(changed()));
42 connect(sender: m_ui.Sound_select, SIGNAL(textChanged(QString)), receiver: this, SIGNAL(changed()));
43 connect(sender: m_ui.Sound_play, SIGNAL(clicked()), receiver: this, SLOT(slotPlay()));
44}
45
46KNotifyConfigActionsWidget::~KNotifyConfigActionsWidget()
47{
48#if HAVE_CANBERRA
49 if (m_context) {
50 ca_context_destroy(c: m_context);
51 }
52 m_context = nullptr;
53#endif
54}
55
56void KNotifyConfigActionsWidget::setConfigElement(KNotifyConfigElement *config)
57{
58 bool blocked = blockSignals(b: true); // to block the changed() signal
59 QString prstring = config->readEntry(QStringLiteral("Action"));
60 QStringList actions = prstring.split(sep: QLatin1Char('|'));
61
62 m_ui.Sound_check->setChecked(actions.contains(QStringLiteral("Sound")));
63 m_ui.Popup_check->setChecked(actions.contains(QStringLiteral("Popup")));
64
65 m_ui.Sound_select->setUrl(QUrl(config->readEntry(QStringLiteral("Sound"), path: true)));
66
67 blockSignals(b: blocked);
68}
69
70void KNotifyConfigActionsWidget::save(KNotifyConfigElement *config)
71{
72 QStringList actions;
73 if (m_ui.Sound_check->isChecked()) {
74 actions << QStringLiteral("Sound");
75 }
76 if (m_ui.Popup_check->isChecked()) {
77 actions << QStringLiteral("Popup");
78 }
79
80 config->writeEntry(QStringLiteral("Action"), data: actions.join(sep: QLatin1Char('|')));
81
82 config->writeEntry(QStringLiteral("Sound"),
83 data: m_ui.Sound_select->text()); // don't use .url() here, .notifyrc files have predefined "static" entries with no path
84}
85
86void KNotifyConfigActionsWidget::slotPlay()
87{
88 const QString soundFilename = m_ui.Sound_select->text();
89 QUrl soundURL;
90 const auto dataLocations = QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation);
91 for (const QString &dataLocation : dataLocations) {
92 soundURL = QUrl::fromUserInput(userInput: soundFilename, workingDirectory: dataLocation + QStringLiteral("/sounds"), options: QUrl::AssumeLocalFile);
93 if (soundURL.isLocalFile() && QFile::exists(fileName: soundURL.toLocalFile())) {
94 break;
95 } else if (!soundURL.isLocalFile() && soundURL.isValid()) {
96 break;
97 }
98 soundURL.clear();
99 }
100
101#if HAVE_CANBERRA
102 if (!m_context) {
103 int ret = ca_context_create(c: &m_context);
104 if (ret != CA_SUCCESS) {
105 qCWarning(KNOTIFYCONFIG_LOG) << "Failed to initialize canberra context for audio notification:" << ca_strerror(code: ret);
106 m_context = nullptr;
107 return;
108 }
109
110 QString desktopFileName = QGuiApplication::desktopFileName();
111 // handle apps which set the desktopFileName property with filename suffix,
112 // due to unclear API dox (https://bugreports.qt.io/browse/QTBUG-75521)
113 if (desktopFileName.endsWith(s: QLatin1String(".desktop"))) {
114 desktopFileName.chop(n: 8);
115 }
116 ret = ca_context_change_props(c: m_context,
117 CA_PROP_APPLICATION_NAME,
118 qUtf8Printable(qApp->applicationDisplayName()),
119 CA_PROP_APPLICATION_ID,
120 qUtf8Printable(desktopFileName),
121 CA_PROP_APPLICATION_ICON_NAME,
122 qUtf8Printable(qApp->windowIcon().name()),
123 nullptr);
124 if (ret != CA_SUCCESS) {
125 qCWarning(KNOTIFYCONFIG_LOG) << "Failed to set application properties on canberra context for audio notification:" << ca_strerror(code: ret);
126 }
127 }
128
129 ca_proplist *props = nullptr;
130 ca_proplist_create(p: &props);
131
132 // We'll also want this cached for a time. volatile makes sure the cache is
133 // dropped after some time or when the cache is under pressure.
134 ca_proplist_sets(p: props, CA_PROP_MEDIA_FILENAME, value: QFile::encodeName(fileName: soundURL.toLocalFile()).constData());
135 ca_proplist_sets(p: props, CA_PROP_CANBERRA_CACHE_CONTROL, value: "volatile");
136
137 int ret = ca_context_play_full(c: m_context, id: 0, p: props, cb: nullptr, userdata: nullptr);
138
139 ca_proplist_destroy(p: props);
140
141 if (ret != CA_SUCCESS) {
142 qCWarning(KNOTIFYCONFIG_LOG) << "Failed to play sound with canberra:" << ca_strerror(code: ret);
143 return;
144 }
145#elif HAVE_QTMULTIMEDIA
146 auto player = new QMediaPlayer(this);
147 auto audioOutput = new QAudioOutput(player);
148 connect(player, &QMediaPlayer::playingChanged, player, [player](bool playing) {
149 if (!playing) {
150 player->deleteLater();
151 }
152 });
153 connect(player, &QMediaPlayer::errorOccurred, player, [player]() {
154 player->deleteLater();
155 });
156 player->setAudioOutput(audioOutput);
157 player->setSource(soundURL);
158 player->play();
159#endif
160}
161
162#include "moc_knotifyconfigactionswidget.cpp"
163

source code of knotifyconfig/src/knotifyconfigactionswidget.cpp