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

WinToast needs a special shortcut #5954

Open
4 tasks done
8thony opened this issue Feb 14, 2025 · 9 comments · May be fixed by #5975
Open
4 tasks done

WinToast needs a special shortcut #5954

8thony opened this issue Feb 14, 2025 · 9 comments · May be fixed by #5975
Labels
issue-report An issue reported by a user.

Comments

@8thony
Copy link
Contributor

8thony commented Feb 14, 2025

Checklist

  • I'm reporting a problem with Chatterino
  • I've verified that I'm running the most recent nightly build or stable release
  • I've looked for my problem on the wiki
  • I've searched the issues and pull requests for similar looking reports

Describe your issue

Windows uses a Shortcut with AUMI (App User Model ID) to associate a notification with an application.
If this shortcut is missing, notifications may fail to display or behave incorrectly See here
Since 6e0852f we disabled creation of this shortcut.

For me, if this is missing, the following unexpected behavior occurs:

  • notification has no icon and a different name
  • unable to customize the Toast behavior within Windows settings System -> Notifications & actions
Steps to Reproduce

- Enable live notification for any current streaming channel
- Close Chatterino
- Remove all Chatterino shortcuts at:
  - %AppData%\Roaming\Microsoft\Windows\Start Menu\Programs
  - %ProgramData%\Microsoft\Windows\Start Menu\Programs
- Clear Notification Center
- Run Chatterino
- Receive "broken" notification
Maybe fixable with this patch

This should create a shortcut, unless you explicitly disable it (what #3817 was about).

I couldn't figure out a different way to get this working.

diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp
--- a/src/singletons/Settings.hpp
+++ b/src/singletons/Settings.hpp
@@ -526,6 +526,7 @@ public:
         "/notifications/suppressInitialLive", false};
 
     BoolSetting notificationToast = {"/notifications/enableToast", false};
+    BoolSetting notificationToastDisableShortcut = {"/notifications/disableWinToastShortcut", false};
     IntSetting openFromToast = {"/notifications/openFromToast",
                                 static_cast<int>(ToastReaction::OpenInBrowser)};
 
diff --git a/src/singletons/Toasts.cpp b/src/singletons/Toasts.cpp
--- a/src/singletons/Toasts.cpp
+++ b/src/singletons/Toasts.cpp
@@ -230,7 +230,9 @@ void Toasts::ensureInitialized()
     instance->setAppUserModelId(
         WinToast::configureAUMI(L"", L"Chatterino 2", L"",
                                 Version::instance().version().toStdWString()));
-    instance->setShortcutPolicy(WinToast::SHORTCUT_POLICY_IGNORE);
+    if (getSettings()->notificationToastDisableShortcut) {
+        instance->setShortcutPolicy(WinToast::SHORTCUT_POLICY_IGNORE);
+    }
     WinToast::WinToastError error{};
     instance->initialize(&error);
 
diff --git a/src/widgets/settingspages/NotificationPage.cpp b/src/widgets/settingspages/NotificationPage.cpp
--- a/src/widgets/settingspages/NotificationPage.cpp
+++ b/src/widgets/settingspages/NotificationPage.cpp
@@ -49,6 +49,8 @@ NotificationPage::NotificationPage()
 #ifdef Q_OS_WIN
                 settings.append(this->createCheckBox(
                     "Show notification", getSettings()->notificationToast));
+                settings.append(this->createCheckBox(
+                    "Disable creation of WinToast Shortcut (requires restart)", getSettings()->notificationToastDisableShortcut));
                 auto openIn = settings.emplace<QHBoxLayout>().withoutMargin();
                 {
                     openIn

Check if your shortcut has AUMI configured Use a Tool called [LECmd](https://github.com/EricZimmerman/LECmd) with this parameter on a shortcut file:

LECmd.exe -f Chatterino2.lnk

If the shortcut has AUMI configured, you find this in the output App User Model ID ==> .Chatterino 2

Manually create a shortcut with AUMI Use a Tool called [make-shortcut-with-appusermodelid](https://github.com/Robertof/make-shortcut-with-appusermodelid) makelnk.exe ".Chatterino 2" "C:\PathToChatterino\chatterino.exe" Chatterino2

@Nerixyz if you have time, could you try to reproduce this?

Screenshots

Shortcut without AUMI Shortcut with AUMI
Image Image
Image Image

*Reply Box removed since d0cf6c4
**Shortcut was renamed to Chatterino by myself

OS and Chatterino Version

Chatterino Nightly 2.5.2 (commit 449aefc) Qt 6.7.1 Running on Windows 10

@8thony 8thony added the issue-report An issue reported by a user. label Feb 14, 2025
@Nerixyz
Copy link
Contributor

Nerixyz commented Feb 14, 2025

I can reproduce that, yeah.

@Nerixyz
Copy link
Contributor

Nerixyz commented Feb 17, 2025

It's silly that we need to create a shell shortcut. We can fix the name if we initialize WinToast at the start (which calls SetCurrentProcessExplicitAppUserModelID). For the image, we must create a shortcut (even then, it doesn't work immediately, you need to restart the app for the icon to appear).

Nerixyz@3bfe55f implements this. It's hard to properly test, because the system appears to be caching some stuff. If you're testing this, test portable first and try non-portable afterward.

Also found a WinToast bug while testing 🙂

@8thony
Copy link
Contributor Author

8thony commented Feb 18, 2025

Thanks for trying to fix this @Nerixyz.

Yes, the caching thing is really annoying while trying to debug this. With your patch applied, I get these results:
On portable, I get no icon and the name ".Chatterino 2" every time.

Switching to normal mode, I get for the first notification no icon and ".Chatterino 2" as name,
but on the second notification I do get an icon and the correct name.

It looks like it only works if a shortcut is present.

I also noticed the other bug you mentioned. Thanks for reporting and fixing this as well.

@Nerixyz
Copy link
Contributor

Nerixyz commented Feb 18, 2025

On portable, I get no icon and the name ".Chatterino 2" every time.

This is the unfortunate thing. Maybe there could be an option to create a shortcut? Not sure. I don't want portable to mess with the start menu though - that's not what I'd expect from it. I'm also running portable when testing, so I wouldn't want that to plant itself in my start menu.

@8thony
Copy link
Contributor Author

8thony commented Feb 22, 2025

Maybe there could be an option to create a shortcut? Not sure. I don't want portable to mess with the start menu though

Yes, we could add an option for this.
Not sure how we should handle this with the portable version and, at the same time, don't make a regression for #3817.
Enabled for all versions except for portable by default, but also customizable on all versions?

We should also keep in mind that without a valid shortcut, options like specifying action buttons are not possible.
See here (if we want to use them at some point)

@8thony
Copy link
Contributor Author

8thony commented Feb 22, 2025

I tried making this setting, could you try this and give me feedback?

New diff
diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp
--- a/src/singletons/Settings.hpp
+++ b/src/singletons/Settings.hpp
@@ -3,6 +3,7 @@
 #include "common/Channel.hpp"
 #include "common/ChatterinoSetting.hpp"
 #include "common/enums/MessageOverflow.hpp"
+#include "common/Modes.hpp"
 #include "common/SignalVector.hpp"
 #include "controllers/filters/FilterRecord.hpp"
 #include "controllers/highlights/HighlightBadge.hpp"
@@ -526,6 +527,9 @@ public:
         "/notifications/suppressInitialLive", false};
 
     BoolSetting notificationToast = {"/notifications/enableToast", false};
+    BoolSetting notificationToastDisableShortcut = {
+        "/notifications/disableWinToastShortcut",
+        Modes::instance().isPortable ? true : false};
     IntSetting openFromToast = {"/notifications/openFromToast",
                                 static_cast<int>(ToastReaction::OpenInBrowser)};
 
diff --git a/src/singletons/Toasts.cpp b/src/singletons/Toasts.cpp
--- a/src/singletons/Toasts.cpp
+++ b/src/singletons/Toasts.cpp
@@ -232,7 +232,10 @@ void Toasts::ensureInitialized()
     instance->setAppUserModelId(
         WinToast::configureAUMI(L"", L"Chatterino 2", L"",
                                 Version::instance().version().toStdWString()));
-    instance->setShortcutPolicy(WinToast::SHORTCUT_POLICY_IGNORE);
+    if (getSettings()->notificationToastDisableShortcut)
+    {
+        instance->setShortcutPolicy(WinToast::SHORTCUT_POLICY_IGNORE);
+    }
     WinToast::WinToastError error{};
     instance->initialize(&error);
 
diff --git a/src/widgets/settingspages/NotificationPage.cpp b/src/widgets/settingspages/NotificationPage.cpp
--- a/src/widgets/settingspages/NotificationPage.cpp
+++ b/src/widgets/settingspages/NotificationPage.cpp
@@ -51,6 +51,14 @@ NotificationPage::NotificationPage()
                     "Show notification", getSettings()->notificationToast));
 #endif
 #ifdef Q_OS_WIN
+                settings.append(this->createCheckBox(
+                    "Disable creation of WinToast Shortcut (requires "
+                    "restart)",
+                    getSettings()->notificationToastDisableShortcut,
+                    "When enabled, no shortcut will be crated under "
+                    "\n%AppData%\\Roaming\\Microsoft\\Windows\\Start "
+                    "Menu\\Programs\\ \n(On portable mode, this is enabled by "
+                    "default)"));
                 auto openIn = settings.emplace<QHBoxLayout>().withoutMargin();
                 {
                     openIn
diff --git a/src/widgets/settingspages/SettingsPage.cpp b/src/widgets/settingspages/SettingsPage.cpp
--- a/src/widgets/settingspages/SettingsPage.cpp
+++ b/src/widgets/settingspages/SettingsPage.cpp
@@ -88,9 +88,11 @@ void SettingsPage::setTab(SettingsDialogTab *tab)
 }
 
 QCheckBox *SettingsPage::createCheckBox(
-    const QString &text, pajlada::Settings::Setting<bool> &setting)
+    const QString &text, pajlada::Settings::Setting<bool> &setting,
+    QString toolTipText)
 {
     QCheckBox *checkbox = new SCheckBox(text);
+    checkbox->setToolTip(toolTipText);
 
     // update when setting changes
     setting.connect(
diff --git a/src/widgets/settingspages/SettingsPage.hpp b/src/widgets/settingspages/SettingsPage.hpp
--- a/src/widgets/settingspages/SettingsPage.hpp
+++ b/src/widgets/settingspages/SettingsPage.hpp
@@ -57,7 +57,8 @@ public:
     void setTab(SettingsDialogTab *tab);
 
     QCheckBox *createCheckBox(const QString &text,
-                              pajlada::Settings::Setting<bool> &setting);
+                              pajlada::Settings::Setting<bool> &setting,
+                              QString toolTipText = {});
     QComboBox *createComboBox(const QStringList &items,
                               pajlada::Settings::Setting<QString> &setting);
     QLineEdit *createLineEdit(pajlada::Settings::Setting<QString> &setting);

@Nerixyz
Copy link
Contributor

Nerixyz commented Feb 22, 2025

I tried making this setting, could you try this and give me feedback?

We should try to make the setting inverse. Currently, having this disabled will create a shortcut. I'd rather have it like createShortcutForToasts (or similar). Not sure about the default being different in portable/non-portable, but I don't see a way around it.

In the user-visible setting name, it shouldn't mention WinToast (or even notifications). This could be explained in the tooltip for why it's needed. Maybe something like "Create explorer shortcut" or "Create start menu shortcut".

We should also ensure that the installer creates the same shortcut.

@8thony
Copy link
Contributor Author

8thony commented Feb 22, 2025

Applied your suggestions

Another diff
diff --git a/.CI/chatterino-installer.iss b/.CI/chatterino-installer.iss
--- a/.CI/chatterino-installer.iss
+++ b/.CI/chatterino-installer.iss
@@ -77,7 +77,7 @@ Source: "{#WORKING_DIR}vc_redist.x64.exe"; DestDir: "{tmp}"; Tasks: vcredist;
 ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
 
 [Icons]
-Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
+Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; AppUserModelID: ".Chatterino 2"
 Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
 
 [Run]
diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp
--- a/src/singletons/Settings.hpp
+++ b/src/singletons/Settings.hpp
@@ -3,6 +3,7 @@
 #include "common/Channel.hpp"
 #include "common/ChatterinoSetting.hpp"
 #include "common/enums/MessageOverflow.hpp"
+#include "common/Modes.hpp"
 #include "common/SignalVector.hpp"
 #include "controllers/filters/FilterRecord.hpp"
 #include "controllers/highlights/HighlightBadge.hpp"
@@ -526,6 +527,9 @@ public:
         "/notifications/suppressInitialLive", false};
 
     BoolSetting notificationToast = {"/notifications/enableToast", false};
+    BoolSetting createShortcutForToasts = {
+        "/notifications/createShortcutForToasts",
+        Modes::instance().isPortable ? false : true};
     IntSetting openFromToast = {"/notifications/openFromToast",
                                 static_cast<int>(ToastReaction::OpenInBrowser)};
 
diff --git a/src/singletons/Toasts.cpp b/src/singletons/Toasts.cpp
--- a/src/singletons/Toasts.cpp
+++ b/src/singletons/Toasts.cpp
@@ -232,7 +232,10 @@ void Toasts::ensureInitialized()
     instance->setAppUserModelId(
         WinToast::configureAUMI(L"", L"Chatterino 2", L"",
                                 Version::instance().version().toStdWString()));
-    instance->setShortcutPolicy(WinToast::SHORTCUT_POLICY_IGNORE);
+    if (!getSettings()->createShortcutForToasts)
+    {
+        instance->setShortcutPolicy(WinToast::SHORTCUT_POLICY_IGNORE);
+    }
     WinToast::WinToastError error{};
     instance->initialize(&error);
 
diff --git a/src/widgets/settingspages/NotificationPage.cpp b/src/widgets/settingspages/NotificationPage.cpp
--- a/src/widgets/settingspages/NotificationPage.cpp
+++ b/src/widgets/settingspages/NotificationPage.cpp
@@ -51,6 +51,14 @@ NotificationPage::NotificationPage()
                     "Show notification", getSettings()->notificationToast));
 #endif
 #ifdef Q_OS_WIN
+                settings.append(this->createCheckBox(
+                    "Create start menu shortcut (requires "
+                    "restart)",
+                    getSettings()->createShortcutForToasts,
+                    "When enabled, a shortcut will be created inside your "
+                    "start menu folder if needed by WinToast."
+                    "\n(On portable mode, this is disabled by "
+                    "default)"));
                 auto openIn = settings.emplace<QHBoxLayout>().withoutMargin();
                 {
                     openIn
diff --git a/src/widgets/settingspages/SettingsPage.cpp b/src/widgets/settingspages/SettingsPage.cpp
--- a/src/widgets/settingspages/SettingsPage.cpp
+++ b/src/widgets/settingspages/SettingsPage.cpp
@@ -88,9 +88,11 @@ void SettingsPage::setTab(SettingsDialogTab *tab)
 }
 
 QCheckBox *SettingsPage::createCheckBox(
-    const QString &text, pajlada::Settings::Setting<bool> &setting)
+    const QString &text, pajlada::Settings::Setting<bool> &setting,
+    QString toolTipText)
 {
     QCheckBox *checkbox = new SCheckBox(text);
+    checkbox->setToolTip(toolTipText);
 
     // update when setting changes
     setting.connect(
diff --git a/src/widgets/settingspages/SettingsPage.hpp b/src/widgets/settingspages/SettingsPage.hpp
--- a/src/widgets/settingspages/SettingsPage.hpp
+++ b/src/widgets/settingspages/SettingsPage.hpp
@@ -57,7 +57,8 @@ public:
     void setTab(SettingsDialogTab *tab);
 
     QCheckBox *createCheckBox(const QString &text,
-                              pajlada::Settings::Setting<bool> &setting);
+                              pajlada::Settings::Setting<bool> &setting,
+                              QString toolTipText = {});
     QComboBox *createComboBox(const QStringList &items,
                               pajlada::Settings::Setting<QString> &setting);
     QLineEdit *createLineEdit(pajlada::Settings::Setting<QString> &setting);

@Nerixyz
Copy link
Contributor

Nerixyz commented Feb 22, 2025

This looks good. Mini nits: The argument for createCheckBox could be a const QString& and the mention of WinToast could be "live notifications" (many users don't know what WinToast is). Otherwise, feel free to open a PR. I couldn't try the installer yet, but CI should build one.

@8thony 8thony linked a pull request Feb 22, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
issue-report An issue reported by a user.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants