diff --git a/Directory.Build.targets b/Directory.Build.targets index 2f44693b6c2..ced73e00dd3 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,6 +1,6 @@ - + 10.0.$(DefaultTargetPlatformVersion).0 diff --git a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationHistoryCompat.cs b/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationHistoryCompat.cs new file mode 100644 index 00000000000..77d9f29c154 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationHistoryCompat.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Windows.UI.Notifications; + +namespace Microsoft.Toolkit.Uwp.Notifications +{ + /// + /// 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; + + /// + /// Initializes a new instance of the class. + /// Do not call this. Instead, call to obtain an instance. + /// + /// An AUMID that uniquely identifies your application. + 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); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationManagerCompat.cs b/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationManagerCompat.cs new file mode 100644 index 00000000000..81a0e1ed334 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/DesktopNotificationManagerCompat.cs @@ -0,0 +1,209 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +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; + +namespace Microsoft.Toolkit.Uwp.Notifications +{ + /// + /// Helper for .NET Framework applications to display toast notifications and respond to toast events + /// + public class DesktopNotificationManagerCompat + { + /// + /// A constant that is used as the launch arg when your EXE is launched from a toast notification. + /// + public const string ToastActivatedLaunchArg = "-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. + /// + /// Your implementation of NotificationActivator. Must have GUID and ComVisible attributes on class. + /// 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 = string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}\\LocalServer32", typeof(T).GUID); + 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 + '"' + " " + ToastActivatedLaunchArg); + } + + /// + /// 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); + } + else + { + // 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 value indicating 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 class DesktopBridgeHelpers + { + private const long APPMODEL_ERROR_NO_PACKAGE = 15700L; + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName); + + private static bool? _isRunningAsUwp; + + public static bool IsRunningAsUwp() + { + if (_isRunningAsUwp == null) + { + if (IsWindows7OrLower) + { + _isRunningAsUwp = false; + } + else + { + int length = 0; + StringBuilder sb = new StringBuilder(0); + int result = GetCurrentPackageFullName(ref length, sb); + + sb = new StringBuilder(length); + result = GetCurrentPackageFullName(ref length, sb); + + _isRunningAsUwp = result != APPMODEL_ERROR_NO_PACKAGE; + } + } + + return _isRunningAsUwp.Value; + } + + private static bool IsWindows7OrLower + { + get + { + int versionMajor = Environment.OSVersion.Version.Major; + int versionMinor = Environment.OSVersion.Version.Minor; + double version = versionMajor + ((double)versionMinor / 10); + return version <= 6.1; + } + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationActivator.cs b/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationActivator.cs new file mode 100644 index 00000000000..b8cf3c8ae27 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationActivator.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +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; + +namespace Microsoft.Toolkit.Uwp.Notifications +{ + /// + /// 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); + + /// + /// A single user input key/value pair. + /// + [StructLayout(LayoutKind.Sequential)] + [Serializable] + public struct NOTIFICATION_USER_INPUT_DATA + { + /// + /// The key of the user input. + /// + [MarshalAs(UnmanagedType.LPWStr)] + public string Key; + + /// + /// The value of the user input. + /// + [MarshalAs(UnmanagedType.LPWStr)] + public string Value; + } + + /// + /// The COM callback that is triggered when your notification is clicked. + /// + [ComImport] + [Guid("53E31837-6600-4A81-9395-75CFFE746F94")] + [ComVisible(true)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface INotificationActivationCallback + { + /// + /// The method called when your notification is clicked. + /// + /// The app id of the app that sent the toast. + /// The activation arguments from the toast. + /// The user input from the toast. + /// The number of user inputs. + 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); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationUserInput.cs b/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationUserInput.cs new file mode 100644 index 00000000000..9948ed5353a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Notifications/DesktopNotificationManager/NotificationUserInput.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +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; + +namespace Microsoft.Toolkit.Uwp.Notifications +{ + /// + /// 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; + } + + /// + /// Gets the value of an input with the given key. + /// + /// The key of the inpupt. + /// The value of the input. + public string this[string key] => _data.First(i => i.Key == key).Value; + + /// + /// Gets all the keys of the inputs. + /// + public IEnumerable Keys => _data.Select(i => i.Key); + + /// + /// Gets all the values of the inputs. + /// + public IEnumerable Values => _data.Select(i => i.Value); + + /// + /// Gets how many inputs there were. + /// + public int Count => _data.Length; + + /// + /// Checks whether any inpupts have the given key. + /// + /// The key to look for. + /// A boolean representing whether any inputs have the given key. + public bool ContainsKey(string key) + { + return _data.Any(i => i.Key == key); + } + + /// + /// Gets an enumerator of the inputs. + /// + /// An enumerator of the inputs. + public IEnumerator> GetEnumerator() + { + return _data.Select(i => new KeyValuePair(i.Key, i.Value)).GetEnumerator(); + } + + /// + /// Tries to get the input value for the given key. + /// + /// The key of the input to look for. + /// The value of the input. + /// True if found an input with the specified key, else false. + 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(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj b/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj index 2f06440d369..d872594d24e 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj +++ b/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj @@ -1,7 +1,7 @@  - netstandard1.4;uap10.0;native + netstandard1.4;uap10.0;native;net461 $(DefineConstants);NETFX_CORE Windows Community Toolkit Notifications @@ -14,7 +14,7 @@ notifications win10 windows 10 tile tiles toast toasts badge xml uwp c# csharp c++ true - + @@ -26,8 +26,21 @@ - + + + + + + + + + $(MSBuildProgramFiles32)\Windows Kits\10\UnionMetadata\$(TargetPlatformVersion)\Windows.winmd + true + False + + + winmdobj UAP