From f20604601056d6a3d3bdd8feec23a2727f883c2e Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 16 Mar 2020 14:20:14 +0100 Subject: [PATCH] Made notifications work again, activation doesn't work yet due to missing ToastActivatorCLSID on the shortcut. --- Greenshot/Helpers/CaptureHelper.cs | 5 +- Greenshot/releases/innosetup/setup.iss | 5 +- .../Native/DesktopBridgeHelpers.cs | 53 ++++ .../DesktopNotificationHistoryCompat.cs | 109 +++++++ .../DesktopNotificationManagerCompat.cs | 298 +++--------------- .../Native/GreenshotNotificationActivator.cs | 4 +- .../Native/INotificationActivationCallback.cs | 35 ++ .../Native/NotificationActivator.cs | 35 ++ .../Native/NotificationUserInput.cs | 70 ++++ .../Structs/NotificationUserInputData.cs | 30 ++ .../ToastNotificationService.cs | 17 +- 11 files changed, 392 insertions(+), 269 deletions(-) create mode 100644 GreenshotWin10Plugin/Native/DesktopBridgeHelpers.cs create mode 100644 GreenshotWin10Plugin/Native/DesktopNotificationHistoryCompat.cs create mode 100644 GreenshotWin10Plugin/Native/INotificationActivationCallback.cs create mode 100644 GreenshotWin10Plugin/Native/NotificationActivator.cs create mode 100644 GreenshotWin10Plugin/Native/NotificationUserInput.cs create mode 100644 GreenshotWin10Plugin/Native/Structs/NotificationUserInputData.cs diff --git a/Greenshot/Helpers/CaptureHelper.cs b/Greenshot/Helpers/CaptureHelper.cs index 0007727ef..ea7707f1f 100644 --- a/Greenshot/Helpers/CaptureHelper.cs +++ b/Greenshot/Helpers/CaptureHelper.cs @@ -552,7 +552,10 @@ namespace Greenshot.Helpers { notifyIconClassicMessageHandler.ShowErrorMessage(eventArgs.Message, 10000); break; case SurfaceMessageTyp.Info: - notifyIconClassicMessageHandler.ShowInfoMessage(eventArgs.Message, 10000); + notifyIconClassicMessageHandler.ShowInfoMessage(eventArgs.Message, 10000, () => + { + Log.Info("Clicked!"); + }); break; case SurfaceMessageTyp.FileSaved: case SurfaceMessageTyp.UploadedUri: diff --git a/Greenshot/releases/innosetup/setup.iss b/Greenshot/releases/innosetup/setup.iss index 5933fd823..bf4485edf 100644 --- a/Greenshot/releases/innosetup/setup.iss +++ b/Greenshot/releases/innosetup/setup.iss @@ -189,10 +189,11 @@ Root: HKLM; Subkey: Software\Classes\Greenshot\DefaultIcon; ValueType: string; V Root: HKLM; Subkey: Software\Classes\Greenshot\shell\open\command; ValueType: string; ValueName: ""; ValueData: """{app}\Greenshot.EXE"" --openfile ""%1"""; Permissions: admins-modify; Flags: uninsdeletevalue noerror; Check: not IsRegularUser [Icons] -Name: {group}\{#ExeName}; Filename: {app}\{#ExeName}.exe; WorkingDir: {app} -Name: {group}\Uninstall {#ExeName}; Filename: {uninstallexe}; WorkingDir: {app}; AppUserModelID: "{#ExeName}.{#ExeName}" +Name: {group}\{#ExeName}; Filename: {app}\{#ExeName}.exe; WorkingDir: {app}; AppUserModelID: "{#ExeName}" +Name: {group}\Uninstall {#ExeName}; Filename: {uninstallexe}; WorkingDir: {app}; Name: {group}\Readme.txt; Filename: {app}\readme.txt; WorkingDir: {app} Name: {group}\License.txt; Filename: {app}\license.txt; WorkingDir: {app} + [Languages] Name: en; MessagesFile: compiler:Default.isl Name: cn; MessagesFile: Languages\ChineseSimplified.isl diff --git a/GreenshotWin10Plugin/Native/DesktopBridgeHelpers.cs b/GreenshotWin10Plugin/Native/DesktopBridgeHelpers.cs new file mode 100644 index 000000000..0c7728ee0 --- /dev/null +++ b/GreenshotWin10Plugin/Native/DesktopBridgeHelpers.cs @@ -0,0 +1,53 @@ +// ****************************************************************** +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE. +// ****************************************************************** + +using System.Runtime.InteropServices; +using System.Text; +using GreenshotPlugin.Core; + +namespace GreenshotWin10Plugin.Native +{ + /// + /// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/edit/master/DesktopBridge.Helpers/Helpers.cs + /// + public static class DesktopBridgeHelpers + { + const long AppModelErrorNoPackage = 15700L; + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName); + + private static bool? _isRunningAsUwp; + public static bool IsRunningAsUwp() + { + if (_isRunningAsUwp != null) return _isRunningAsUwp.Value; + + if (WindowsVersion.IsWindows7OrLower) + { + _isRunningAsUwp = false; + } + else + { + int length = 0; + StringBuilder sb = new StringBuilder(0); + GetCurrentPackageFullName(ref length, sb); + + sb = new StringBuilder(length); + int result = GetCurrentPackageFullName(ref length, sb); + + _isRunningAsUwp = result != AppModelErrorNoPackage; + } + + return _isRunningAsUwp.Value; + } + } +} diff --git a/GreenshotWin10Plugin/Native/DesktopNotificationHistoryCompat.cs b/GreenshotWin10Plugin/Native/DesktopNotificationHistoryCompat.cs new file mode 100644 index 000000000..f536755ab --- /dev/null +++ b/GreenshotWin10Plugin/Native/DesktopNotificationHistoryCompat.cs @@ -0,0 +1,109 @@ +// ****************************************************************** +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE. +// ****************************************************************** + +using System.Collections.Generic; +using Windows.UI.Notifications; + +namespace GreenshotWin10Plugin.Native +{ + /// + /// Manages the toast notifications for an app including the ability the clear all toast history and removing individual toasts. + /// + public sealed class DesktopNotificationHistoryCompat + { + private readonly string _applicationUserModelId; + private readonly ToastNotificationHistory _history; + + /// + /// Do not call this. Instead, call to obtain an instance. + /// + /// + internal DesktopNotificationHistoryCompat(string applicationUserModelId) + { + _applicationUserModelId = applicationUserModelId; + _history = ToastNotificationManager.History; + } + + /// + /// Removes all notifications sent by this app from action center. + /// + public void Clear() + { + if (_applicationUserModelId != null) + { + _history.Clear(_applicationUserModelId); + } + else + { + _history.Clear(); + } + } + + /// + /// Gets all notifications sent by this app that are currently still in Action Center. + /// + /// A collection of toasts. + public IReadOnlyList GetHistory() + { + return _applicationUserModelId != null ? _history.GetHistory(_applicationUserModelId) : _history.GetHistory(); + } + + /// + /// Removes an individual toast, with the specified tag label, from action center. + /// + /// The tag label of the toast notification to be removed. + public void Remove(string tag) + { + if (_applicationUserModelId != null) + { + _history.Remove(tag, string.Empty, _applicationUserModelId); + } + else + { + _history.Remove(tag); + } + } + + /// + /// Removes a toast notification from the action using the notification's tag and group labels. + /// + /// The tag label of the toast notification to be removed. + /// The group label of the toast notification to be removed. + public void Remove(string tag, string group) + { + if (_applicationUserModelId != null) + { + _history.Remove(tag, group, _applicationUserModelId); + } + else + { + _history.Remove(tag, group); + } + } + + /// + /// Removes a group of toast notifications, identified by the specified group label, from action center. + /// + /// The group label of the toast notifications to be removed. + public void RemoveGroup(string group) + { + if (_applicationUserModelId != null) + { + _history.RemoveGroup(group, _applicationUserModelId); + } + else + { + _history.RemoveGroup(group); + } + } + } +} \ No newline at end of file diff --git a/GreenshotWin10Plugin/Native/DesktopNotificationManagerCompat.cs b/GreenshotWin10Plugin/Native/DesktopNotificationManagerCompat.cs index 703768582..a30c0ff3e 100644 --- a/GreenshotWin10Plugin/Native/DesktopNotificationManagerCompat.cs +++ b/GreenshotWin10Plugin/Native/DesktopNotificationManagerCompat.cs @@ -11,37 +11,31 @@ // ****************************************************************** using System; -using System.Collections; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; using Windows.UI.Notifications; -using GreenshotPlugin.Core; namespace GreenshotWin10Plugin.Native { - public class DesktopNotificationManagerCompat + public static class DesktopNotificationManagerCompat { public const string TOAST_ACTIVATED_LAUNCH_ARG = "-ToastActivated"; private static bool _registeredAumidAndComServer; - private static string _aumid; + private static string _applicationUserModelId; private static bool _registeredActivator; /// - /// If not running under the Desktop Bridge, you must call this method to register your AUMID with the Compat library and to + /// If not running under the Desktop Bridge, you must call this method to register your applicationUserModelId (AUMID) with the Compat library and to /// register your COM CLSID and EXE in LocalServer32 registry. Feel free to call this regardless, and we will no-op if running /// under Desktop Bridge. Call this upon application startup, before calling any other APIs. /// - /// An AUMID that uniquely identifies your application. - public static void RegisterAumidAndComServer(string aumid) - where T : NotificationActivator + /// An applicationUserModelId (AUMID) that uniquely identifies your application. + public static void RegisterAumidAndComServer(string applicationUserModelId) where T : NotificationActivator { - if (string.IsNullOrWhiteSpace(aumid)) + if (string.IsNullOrWhiteSpace(applicationUserModelId)) { - throw new ArgumentException("You must provide an AUMID.", nameof(aumid)); + throw new ArgumentException("You must provide an Application User Model Id (AUMID).", nameof(applicationUserModelId)); } // If running as Desktop Bridge @@ -50,29 +44,41 @@ namespace GreenshotWin10Plugin.Native // Clear the AUMID since Desktop Bridge doesn't use it, and then we're done. // Desktop Bridge apps are registered with platform through their manifest. // Their LocalServer32 key is also registered through their manifest. - _aumid = null; + _applicationUserModelId = null; _registeredAumidAndComServer = true; return; } - _aumid = aumid; + _applicationUserModelId = applicationUserModelId; - String exePath = Process.GetCurrentProcess().MainModule.FileName; - RegisterComServer(exePath); + string exePath = Process.GetCurrentProcess().MainModule?.FileName; + if (exePath != null) + { + RegisterComServer(exePath); + } _registeredAumidAndComServer = true; } - private static void RegisterComServer(string exePath) - where T : NotificationActivator + /// + /// Register the application an a com server + /// + /// type to register for + /// string + private static void RegisterComServer(string exePath) where T : NotificationActivator { // We register the EXE to start up when the notification is activated - string regString = $"SOFTWARE\\Classes\\CLSID\\{{{typeof(T).GUID}}}\\LocalServer32"; - var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(regString); + var guid = typeof(T).GUID; + if (guid == null) + { + throw new ArgumentException("You must provide an Guid on your NotificationActivator."); + } + string regString = $"SOFTWARE\\Classes\\CLSID\\{{{guid}}}\\LocalServer32"; + using var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(regString); // Include a flag so we know this was a toast activation and should wait for COM to process // We also wrap EXE path in quotes for extra security - key.SetValue(null, '"' + exePath + '"' + " " + TOAST_ACTIVATED_LAUNCH_ARG); + key?.SetValue(null, '"' + exePath + '"' + " " + TOAST_ACTIVATED_LAUNCH_ARG); } /// @@ -84,26 +90,23 @@ namespace GreenshotWin10Plugin.Native // Register type var regService = new RegistrationServices(); - regService.RegisterTypeForComClients( - typeof(T), - RegistrationClassContext.LocalServer, - RegistrationConnectionType.MultipleUse); + regService.RegisterTypeForComClients(typeof(T), RegistrationClassContext.LocalServer, RegistrationConnectionType.MultipleUse); _registeredActivator = true; } /// - /// Creates a toast notifier. You must have called first (and also if you're a classic Win32 app), or this will throw an exception. + /// Creates a toast notifier. You must have called first (and also if you're a classic Win32 app), or this will throw an exception. /// - /// + /// ToastNotifier public static ToastNotifier CreateToastNotifier() { EnsureRegistered(); - if (_aumid != null) + if (_applicationUserModelId != null) { // Non-Desktop Bridge - return ToastNotificationManager.CreateToastNotifier(_aumid); + return ToastNotificationManager.CreateToastNotifier(_applicationUserModelId); } // Desktop Bridge @@ -111,7 +114,7 @@ namespace GreenshotWin10Plugin.Native } /// - /// Gets the object. You must have called first (and also if you're a classic Win32 app), or this will throw an exception. + /// Gets the object. You must have called first (and also if you're a classic Win32 app), or this will throw an exception. /// public static DesktopNotificationHistoryCompat History { @@ -119,10 +122,13 @@ namespace GreenshotWin10Plugin.Native { EnsureRegistered(); - return new DesktopNotificationHistoryCompat(_aumid); + return new DesktopNotificationHistoryCompat(_applicationUserModelId); } } + /// + /// Checks if the AUMID is correctly registered, if not this throws an exception + /// private static void EnsureRegistered() { // If not registered AUMID yet @@ -153,232 +159,6 @@ namespace GreenshotWin10Plugin.Native /// /// Gets a boolean representing whether http images can be used within toasts. This is true if running under Desktop Bridge. /// - public static bool CanUseHttpImages { get { return DesktopBridgeHelpers.IsRunningAsUwp(); } } - - /// - /// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/edit/master/DesktopBridge.Helpers/Helpers.cs - /// - private static class DesktopBridgeHelpers - { - const long APPMODEL_ERROR_NO_PACKAGE = 15700L; - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName); - - private static bool? _isRunningAsUwp; - public static bool IsRunningAsUwp() - { - if (_isRunningAsUwp != null) return _isRunningAsUwp.Value; - - if (WindowsVersion.IsWindows7OrLower) - { - _isRunningAsUwp = false; - } - else - { - int length = 0; - StringBuilder sb = new StringBuilder(0); - GetCurrentPackageFullName(ref length, sb); - - sb = new StringBuilder(length); - int result = GetCurrentPackageFullName(ref length, sb); - - _isRunningAsUwp = result != APPMODEL_ERROR_NO_PACKAGE; - } - - return _isRunningAsUwp.Value; - } - } - } - - /// - /// Manages the toast notifications for an app including the ability the clear all toast history and removing individual toasts. - /// - public sealed class DesktopNotificationHistoryCompat - { - private string _aumid; - private ToastNotificationHistory _history; - - /// - /// Do not call this. Instead, call to obtain an instance. - /// - /// - internal DesktopNotificationHistoryCompat(string aumid) - { - _aumid = aumid; - _history = ToastNotificationManager.History; - } - - /// - /// Removes all notifications sent by this app from action center. - /// - public void Clear() - { - if (_aumid != null) - { - _history.Clear(_aumid); - } - else - { - _history.Clear(); - } - } - - /// - /// Gets all notifications sent by this app that are currently still in Action Center. - /// - /// A collection of toasts. - public IReadOnlyList GetHistory() - { - return _aumid != null ? _history.GetHistory(_aumid) : _history.GetHistory(); - } - - /// - /// Removes an individual toast, with the specified tag label, from action center. - /// - /// The tag label of the toast notification to be removed. - public void Remove(string tag) - { - if (_aumid != null) - { - _history.Remove(tag, string.Empty, _aumid); - } - else - { - _history.Remove(tag); - } - } - - /// - /// Removes a toast notification from the action using the notification's tag and group labels. - /// - /// The tag label of the toast notification to be removed. - /// The group label of the toast notification to be removed. - public void Remove(string tag, string group) - { - if (_aumid != null) - { - _history.Remove(tag, group, _aumid); - } - else - { - _history.Remove(tag, group); - } - } - - /// - /// Removes a group of toast notifications, identified by the specified group label, from action center. - /// - /// The group label of the toast notifications to be removed. - public void RemoveGroup(string group) - { - if (_aumid != null) - { - _history.RemoveGroup(group, _aumid); - } - else - { - _history.RemoveGroup(group); - } - } - } - - /// - /// Apps must implement this activator to handle notification activation. - /// - public abstract class NotificationActivator : NotificationActivator.INotificationActivationCallback - { - public void Activate(string appUserModelId, string invokedArgs, NOTIFICATION_USER_INPUT_DATA[] data, uint dataCount) - { - OnActivated(invokedArgs, new NotificationUserInput(data), appUserModelId); - } - - /// - /// This method will be called when the user clicks on a foreground or background activation on a toast. Parent app must implement this method. - /// - /// The arguments from the original notification. This is either the launch argument if the user clicked the body of your toast, or the arguments from a button on your toast. - /// Text and selection values that the user entered in your toast. - /// Your AUMID. - public abstract void OnActivated(string arguments, NotificationUserInput userInput, string appUserModelId); - - // These are the new APIs for Windows 10 - #region NewAPIs - [StructLayout(LayoutKind.Sequential), Serializable] - public struct NOTIFICATION_USER_INPUT_DATA - { - [MarshalAs(UnmanagedType.LPWStr)] - public string Key; - - [MarshalAs(UnmanagedType.LPWStr)] - public string Value; - } - - [ComImport, - Guid("53E31837-6600-4A81-9395-75CFFE746F94"), ComVisible(true), - InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface INotificationActivationCallback - { - void Activate( - [In, MarshalAs(UnmanagedType.LPWStr)] - string appUserModelId, - [In, MarshalAs(UnmanagedType.LPWStr)] - string invokedArgs, - [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] - NOTIFICATION_USER_INPUT_DATA[] data, - [In, MarshalAs(UnmanagedType.U4)] - uint dataCount); - } - #endregion - } - - /// - /// Text and selection values that the user entered on your notification. The Key is the ID of the input, and the Value is what the user entered. - /// - public class NotificationUserInput : IReadOnlyDictionary - { - private NotificationActivator.NOTIFICATION_USER_INPUT_DATA[] _data; - - internal NotificationUserInput(NotificationActivator.NOTIFICATION_USER_INPUT_DATA[] data) - { - _data = data; - } - - public string this[string key] => _data.First(i => i.Key == key).Value; - - public IEnumerable Keys => _data.Select(i => i.Key); - - public IEnumerable Values => _data.Select(i => i.Value); - - public int Count => _data.Length; - - public bool ContainsKey(string key) - { - return _data.Any(i => i.Key == key); - } - - public IEnumerator> GetEnumerator() - { - return _data.Select(i => new KeyValuePair(i.Key, i.Value)).GetEnumerator(); - } - - public bool TryGetValue(string key, out string value) - { - foreach (var item in _data) - { - if (item.Key == key) - { - value = item.Value; - return true; - } - } - - value = null; - return false; - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public static bool CanUseHttpImages => DesktopBridgeHelpers.IsRunningAsUwp(); } } \ No newline at end of file diff --git a/GreenshotWin10Plugin/Native/GreenshotNotificationActivator.cs b/GreenshotWin10Plugin/Native/GreenshotNotificationActivator.cs index 74922476f..d2da5f4c9 100644 --- a/GreenshotWin10Plugin/Native/GreenshotNotificationActivator.cs +++ b/GreenshotWin10Plugin/Native/GreenshotNotificationActivator.cs @@ -24,7 +24,9 @@ using log4net; namespace GreenshotWin10Plugin.Native { - // The GUID CLSID must be unique to your app. Create a new GUID if copying this code. + /// + /// This implements the NotificationActivator + /// [ClassInterface(ClassInterfaceType.None)] [ComSourceInterfaces(typeof(INotificationActivationCallback))] [Guid("F48E86D3-E34C-4DB7-8F8F-9A0EA55F0D08"), ComVisible(true)] diff --git a/GreenshotWin10Plugin/Native/INotificationActivationCallback.cs b/GreenshotWin10Plugin/Native/INotificationActivationCallback.cs new file mode 100644 index 000000000..59dd61443 --- /dev/null +++ b/GreenshotWin10Plugin/Native/INotificationActivationCallback.cs @@ -0,0 +1,35 @@ +// ****************************************************************** +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE. +// ****************************************************************** + +using System.Runtime.InteropServices; +using GreenshotWin10Plugin.Native.Structs; + +namespace GreenshotWin10Plugin.Native +{ + /// + /// This is the interface which allows your notifications to be clicked, which active the application + /// + [ComImport, Guid("53E31837-6600-4A81-9395-75CFFE746F94"), + ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface INotificationActivationCallback + { + void Activate( + [In, MarshalAs(UnmanagedType.LPWStr)] + string appUserModelId, + [In, MarshalAs(UnmanagedType.LPWStr)] + string invokedArgs, + [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] + NotificationUserInputData[] data, + [In, MarshalAs(UnmanagedType.U4)] + uint dataCount); + } +} diff --git a/GreenshotWin10Plugin/Native/NotificationActivator.cs b/GreenshotWin10Plugin/Native/NotificationActivator.cs new file mode 100644 index 000000000..bedf3f5f4 --- /dev/null +++ b/GreenshotWin10Plugin/Native/NotificationActivator.cs @@ -0,0 +1,35 @@ +// ****************************************************************** +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE. +// ****************************************************************** + +using GreenshotWin10Plugin.Native.Structs; + +namespace GreenshotWin10Plugin.Native +{ + /// + /// Apps must implement this activator to handle notification activation. + /// + public abstract class NotificationActivator : INotificationActivationCallback + { + public void Activate(string appUserModelId, string invokedArgs, NotificationUserInputData[] data, uint dataCount) + { + OnActivated(invokedArgs, new NotificationUserInput(data), appUserModelId); + } + + /// + /// This method will be called when the user clicks on a foreground or background activation on a toast. Parent app must implement this method. + /// + /// The arguments from the original notification. This is either the launch argument if the user clicked the body of your toast, or the arguments from a button on your toast. + /// Text and selection values that the user entered in your toast. + /// Your AUMID. + public abstract void OnActivated(string arguments, NotificationUserInput userInput, string appUserModelId); + } +} \ No newline at end of file diff --git a/GreenshotWin10Plugin/Native/NotificationUserInput.cs b/GreenshotWin10Plugin/Native/NotificationUserInput.cs new file mode 100644 index 000000000..57b184c70 --- /dev/null +++ b/GreenshotWin10Plugin/Native/NotificationUserInput.cs @@ -0,0 +1,70 @@ +// ****************************************************************** +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE. +// ****************************************************************** + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using GreenshotWin10Plugin.Native.Structs; + +namespace GreenshotWin10Plugin.Native +{ + /// + /// Text and selection values that the user entered on your notification. The Key is the ID of the input, and the Value is what the user entered. + /// + public class NotificationUserInput : IReadOnlyDictionary + { + private readonly NotificationUserInputData[] _notificationUserInputData; + + internal NotificationUserInput(NotificationUserInputData[] notificationUserInputData) + { + _notificationUserInputData = notificationUserInputData; + } + + public string this[string key] => _notificationUserInputData.First(i => i.Key == key).Value; + + public IEnumerable Keys => _notificationUserInputData.Select(i => i.Key); + + public IEnumerable Values => _notificationUserInputData.Select(i => i.Value); + + public int Count => _notificationUserInputData.Length; + + public bool ContainsKey(string key) + { + return _notificationUserInputData.Any(i => i.Key == key); + } + + public IEnumerator> GetEnumerator() + { + return _notificationUserInputData.Select(i => new KeyValuePair(i.Key, i.Value)).GetEnumerator(); + } + + public bool TryGetValue(string key, out string value) + { + foreach (var item in _notificationUserInputData) + { + if (item.Key == key) + { + value = item.Value; + return true; + } + } + + value = null; + return false; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/GreenshotWin10Plugin/Native/Structs/NotificationUserInputData.cs b/GreenshotWin10Plugin/Native/Structs/NotificationUserInputData.cs new file mode 100644 index 000000000..357aaa378 --- /dev/null +++ b/GreenshotWin10Plugin/Native/Structs/NotificationUserInputData.cs @@ -0,0 +1,30 @@ +// ****************************************************************** +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THE CODE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH +// THE CODE OR THE USE OR OTHER DEALINGS IN THE CODE. +// ****************************************************************** + +using System; +using System.Runtime.InteropServices; + +namespace GreenshotWin10Plugin.Native.Structs +{ + /// + /// See NOTIFICATION_USER_INPUT_DATA structure + /// + [StructLayout(LayoutKind.Sequential), Serializable] + public struct NotificationUserInputData + { + [MarshalAs(UnmanagedType.LPWStr)] + public string Key; + + [MarshalAs(UnmanagedType.LPWStr)] + public string Value; + } +} diff --git a/GreenshotWin10Plugin/ToastNotificationService.cs b/GreenshotWin10Plugin/ToastNotificationService.cs index 05d930bc8..4af060bf9 100644 --- a/GreenshotWin10Plugin/ToastNotificationService.cs +++ b/GreenshotWin10Plugin/ToastNotificationService.cs @@ -44,7 +44,7 @@ namespace GreenshotWin10Plugin public ToastNotificationService() { // Register AUMID and COM server (for Desktop Bridge apps, this no-ops) - DesktopNotificationManagerCompat.RegisterAumidAndComServer("Greenshot.Greenshot"); + DesktopNotificationManagerCompat.RegisterAumidAndComServer("Greenshot"); // Register COM server and activator type DesktopNotificationManagerCompat.RegisterActivator(); @@ -68,9 +68,10 @@ namespace GreenshotWin10Plugin /// This creates the actual toast /// /// string + /// milliseconds until the toast timeouts /// Action called when clicked /// Action called when the toast is closed - private void ShowMessage(string message, Action onClickAction, Action onClosedAction) + private void ShowMessage(string message, int timeout, Action onClickAction, Action onClosedAction) { // Do not inform the user if this is disabled if (!CoreConfiguration.ShowTrayNotification) @@ -104,7 +105,11 @@ namespace GreenshotWin10Plugin } // Create the toast and attach event listeners - var toast = new ToastNotification(toastXml); + var toast = new ToastNotification(toastXml) + { + ExpiresOnReboot = true, + ExpirationTime = timeout > 0 ? DateTimeOffset.Now.AddMilliseconds(timeout) : (DateTimeOffset?)null + }; void ToastActivatedHandler(ToastNotification toastNotification, object sender) { @@ -155,17 +160,17 @@ namespace GreenshotWin10Plugin public void ShowWarningMessage(string message, int timeout, Action onClickAction = null, Action onClosedAction = null) { - ShowMessage(message, onClickAction, onClosedAction); + ShowMessage(message, timeout, onClickAction, onClosedAction); } public void ShowErrorMessage(string message, int timeout, Action onClickAction = null, Action onClosedAction = null) { - ShowMessage(message, onClickAction, onClosedAction); + ShowMessage(message, timeout, onClickAction, onClosedAction); } public void ShowInfoMessage(string message, int timeout, Action onClickAction = null, Action onClosedAction = null) { - ShowMessage(message, onClickAction, onClosedAction); + ShowMessage(message, timeout, onClickAction, onClosedAction); } } }