| 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 | |
| 24 | KNotifyConfigActionsWidget::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 | |
| 46 | KNotifyConfigActionsWidget::~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 | |
| 56 | void 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 | |
| 70 | void 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 | |
| 86 | void 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 | |