Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(notifications): linux notification actions #5976

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
- Bugfix: Fixed a potential way to escape the Lua Plugin sandbox. (#5846)
- Bugfix: Fixed a crash relating to Lua HTTP. (#5800)
Expand Down
122 changes: 102 additions & 20 deletions src/singletons/Toasts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,46 @@ class AvatarDownloader : public QObject
void downloadComplete();
};

#ifdef CHATTERINO_WITH_LIBNOTIFY
void onAction(NotifyNotification *notif, const char *actionRaw, void *userData)
{
QString action(actionRaw);
auto *channelName = static_cast<QString *>(userData);

// by default we perform the action that is specified in the settings
auto toastReaction =
static_cast<ToastReaction>(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;
}
Comment on lines +76 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a function findReactionFromString next to findStringFromReaction. At this point, we could also use qmagicenum.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I'll look into using qmagicenum.


Toasts::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<QString *>(data);
delete channelNameHeap;
}
#endif

} // namespace

namespace chatterino {
Expand Down Expand Up @@ -124,6 +164,28 @@ QString Toasts::findStringFromReaction(
return Toasts::findStringFromReaction(static_cast<ToastReaction>(value));
}

void Toasts::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;
}
}

// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
void Toasts::sendChannelNotification(const QString &channelName,
const QString &channelTitle)
Expand Down Expand Up @@ -182,24 +244,7 @@ class CustomHandler : public WinToastLib::IWinToastHandler
auto toastReaction =
static_cast<ToastReaction>(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;
}
Toasts::performReaction(toastReaction, channelName_);
}

void toastActivated(int actionIndex) const override
Expand Down Expand Up @@ -310,6 +355,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<ToastReaction>(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)
Expand All @@ -322,8 +399,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

Expand Down
3 changes: 3 additions & 0 deletions src/singletons/Toasts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class Toasts final

static bool isEnabled();

static void performReaction(const ToastReaction &reaction,
const QString &channelName);

private:
#ifdef Q_OS_WIN
void ensureInitialized();
Expand Down
3 changes: 1 addition & 2 deletions src/widgets/settingspages/NotificationPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<QHBoxLayout>().withoutMargin();
{
openIn
Expand Down
Loading