// ****************************************************************** // 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.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 const string TOAST_ACTIVATED_LAUNCH_ARG = "-ToastActivated"; private static bool _registeredAumidAndComServer; private static string _aumid; 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 /// 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 { if (string.IsNullOrWhiteSpace(aumid)) { throw new ArgumentException("You must provide an AUMID.", nameof(aumid)); } // If running as Desktop Bridge if (DesktopBridgeHelpers.IsRunningAsUwp()) { // 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; _registeredAumidAndComServer = true; return; } _aumid = aumid; String exePath = Process.GetCurrentProcess().MainModule.FileName; RegisterComServer(exePath); _registeredAumidAndComServer = true; } 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); // 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); } /// /// Registers the activator type as a COM server client so that Windows can launch your activator. /// /// Your implementation of NotificationActivator. Must have GUID and ComVisible attributes on class. public static void RegisterActivator() where T : NotificationActivator { // Register type var regService = new RegistrationServices(); 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. /// /// public static ToastNotifier CreateToastNotifier() { EnsureRegistered(); if (_aumid != null) { // Non-Desktop Bridge return ToastNotificationManager.CreateToastNotifier(_aumid); } // Desktop Bridge return ToastNotificationManager.CreateToastNotifier(); } /// /// 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 { get { EnsureRegistered(); return new DesktopNotificationHistoryCompat(_aumid); } } private static void EnsureRegistered() { // If not registered AUMID yet if (!_registeredAumidAndComServer) { // Check if Desktop Bridge if (DesktopBridgeHelpers.IsRunningAsUwp()) { // Implicitly registered, all good! _registeredAumidAndComServer = true; } else { // Otherwise, incorrect usage throw new Exception("You must call RegisterAumidAndComServer first."); } } // If not registered activator yet if (!_registeredActivator) { // Incorrect usage throw new Exception("You must call RegisterActivator first."); } } /// /// 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(); } } }