diff --git a/CHANGELOG.md b/CHANGELOG.md index 20255f46..0b2bdf14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - Minor: When (re-)connecting, visible channels are now joined first. (#5850) - Minor: Added the ability to filter on messages by the author's user ID (example: `author.user_id == "22484632"`). (#5862) - Minor: Improved error messaging of the `/clip` command. (#5879) -- Minor: Added Linux support for Live Notifications toasts. (#5881, #5971) +- Minor: Added Linux support for Live Notifications toasts. (#5881, #5971, #5976) - Minor: Messages can now be deleted from the context menu in a channel. (#5956) - Minor: Overlay windows now inherit the global zoom level and can be zoomed independently. (#6016) - Bugfix: Fixed a potential way to escape the Lua Plugin sandbox. (#5846) diff --git a/src/singletons/Toasts.cpp b/src/singletons/Toasts.cpp index 5f67df44..f0bcf13f 100644 --- a/src/singletons/Toasts.cpp +++ b/src/singletons/Toasts.cpp @@ -63,6 +63,67 @@ Q_SIGNALS: void downloadComplete(); }; +void performReaction(const ToastReaction &reaction, const QString &channelName) +{ + switch (reaction) + { + case ToastReaction::OpenInBrowser: + QDesktopServices::openUrl( + QUrl(u"https://www.twitch.tv/" % channelName)); + break; + case ToastReaction::OpenInPlayer: + QDesktopServices::openUrl(QUrl(TWITCH_PLAYER_URL.arg(channelName))); + break; + case ToastReaction::OpenInStreamlink: { + openStreamlinkForChannel(channelName); + break; + } + case ToastReaction::DontOpen: + // nothing should happen + break; + } +} + +#ifdef CHATTERINO_WITH_LIBNOTIFY +void onAction(NotifyNotification *notif, const char *actionRaw, void *userData) +{ + QString action(actionRaw); + auto *channelName = static_cast(userData); + + // by default we perform the action that is specified in the settings + auto toastReaction = + static_cast(getSettings()->openFromToast.getValue()); + + if (action == OPEN_IN_BROWSER) + { + toastReaction = ToastReaction::OpenInBrowser; + } + else if (action == OPEN_PLAYER_IN_BROWSER) + { + toastReaction = ToastReaction::OpenInPlayer; + } + else if (action == OPEN_IN_STREAMLINK) + { + toastReaction = ToastReaction::OpenInStreamlink; + } + + performReaction(toastReaction, *channelName); + + notify_notification_close(notif, nullptr); +} + +void onActionClosed(NotifyNotification *notif, void * /*userData*/) +{ + g_object_unref(notif); +} + +void onNotificationDestroyed(void *data) +{ + auto *channelNameHeap = static_cast(data); + delete channelNameHeap; +} +#endif + } // namespace namespace chatterino { @@ -182,24 +243,7 @@ public: auto toastReaction = static_cast(getSettings()->openFromToast.getValue()); - switch (toastReaction) - { - case ToastReaction::OpenInBrowser: - QDesktopServices::openUrl( - QUrl(u"https://www.twitch.tv/" % channelName_)); - break; - case ToastReaction::OpenInPlayer: - QDesktopServices::openUrl( - QUrl(TWITCH_PLAYER_URL.arg(channelName_))); - break; - case ToastReaction::OpenInStreamlink: { - openStreamlinkForChannel(channelName_); - break; - } - case ToastReaction::DontOpen: - // nothing should happen - break; - } + performReaction(toastReaction, channelName_); } void toastActivated(int actionIndex) const override @@ -310,6 +354,38 @@ void Toasts::sendLibnotify(const QString &channelName, NotifyNotification *notif = notify_notification_new( str.toUtf8().constData(), channelTitle.toUtf8().constData(), nullptr); + // this will be freed in onNotificationDestroyed + auto *channelNameHeap = new QString(channelName); + + // we only set onNotificationDestroyed as free_func in the first action + // because all free_funcs will be called once the notification is destroyed + // which would cause a double-free otherwise + notify_notification_add_action(notif, OPEN_IN_BROWSER.toUtf8().constData(), + OPEN_IN_BROWSER.toUtf8().constData(), + (NotifyActionCallback)onAction, + channelNameHeap, onNotificationDestroyed); + notify_notification_add_action( + notif, OPEN_PLAYER_IN_BROWSER.toUtf8().constData(), + OPEN_PLAYER_IN_BROWSER.toUtf8().constData(), + (NotifyActionCallback)onAction, channelNameHeap, nullptr); + notify_notification_add_action( + notif, OPEN_IN_STREAMLINK.toUtf8().constData(), + OPEN_IN_STREAMLINK.toUtf8().constData(), (NotifyActionCallback)onAction, + channelNameHeap, nullptr); + + auto defaultToastReaction = + static_cast(getSettings()->openFromToast.getValue()); + + if (defaultToastReaction != ToastReaction::DontOpen) + { + notify_notification_add_action( + notif, "default", + Toasts::findStringFromReaction(defaultToastReaction) + .toUtf8() + .constData(), + (NotifyActionCallback)onAction, channelNameHeap, nullptr); + } + GdkPixbuf *img = gdk_pixbuf_new_from_file( avatarFilePath(channelName).toUtf8().constData(), nullptr); if (img == nullptr) @@ -322,8 +398,13 @@ void Toasts::sendLibnotify(const QString &channelName, g_object_unref(img); } - notify_notification_show(notif, nullptr); - g_object_unref(notif); + g_signal_connect(notif, "closed", (GCallback)onActionClosed, nullptr); + + gboolean success = notify_notification_show(notif, nullptr); + if (success == 0) + { + g_object_unref(notif); + } } #endif diff --git a/src/widgets/settingspages/NotificationPage.cpp b/src/widgets/settingspages/NotificationPage.cpp index a6f20d01..9006d3b9 100644 --- a/src/widgets/settingspages/NotificationPage.cpp +++ b/src/widgets/settingspages/NotificationPage.cpp @@ -49,8 +49,7 @@ NotificationPage::NotificationPage() #if defined(Q_OS_WIN) || defined(CHATTERINO_WITH_LIBNOTIFY) settings.append(this->createCheckBox( "Show notification", getSettings()->notificationToast)); -#endif -#ifdef Q_OS_WIN + auto openIn = settings.emplace().withoutMargin(); { openIn