diff --git a/Directory.Build.targets b/Directory.Build.targets index ab132c6cf4c..2101233710b 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,19 +1,21 @@ - true + true - + + true $(MSBuildThisFileDirectory)toolkit.snk - - + + + diff --git a/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj b/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj index 64b54d1d3c1..c980f7c4205 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj +++ b/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.csproj @@ -1,22 +1,10 @@  - - - netstandard1.4;uap10.0;net461;netcoreapp3.1 + netstandard1.4;uap10.0.19041;net461;netcoreapp3.1;net5.0;net5.0-windows10.0.17763.0;native $(DefineConstants);NETFX_CORE - Windows Community Toolkit Notifications - - Generate tile, toast, and badge notifications for Windows 10 via code, with the help of IntelliSense. - Adds Support for adaptive tiles and adaptive/interactive toasts for Windows 10. It is part of the Windows Community Toolkit. - Supports C# and C++ UWP project types. - Also works with C# portable class libraries and non-UWP C# projects like server projects. - This project contains outputs for netstandard1.4, uap10.0 and native for WinRT. - - notifications win10 windows 10 tile tiles toast toasts badge xml uwp c# csharp c++ true - 10240 - 10.0.10240.0 + Microsoft.Toolkit.Uwp.Notifications.nuspec @@ -27,16 +15,17 @@ - - - $(DefineConstants);WINDOWS_UWP;WIN32 - + + + + $(DefineConstants);WINDOWS_UWP;WIN32 + - + @@ -54,6 +43,12 @@ + + + 10.0.19041.0 + 16299 + 10.0.16299.0 + winmdobj @@ -63,8 +58,9 @@ native UAP,Version=v10.0 uap10.0 - 10.0.19041.0 - 10.0.10240.0 + 10.0.19041.0 + 10240 + 10.0.10240.0 $(DefineConstants);NETFX_CORE;WINDOWS_UWP;WINRT false .NETCore @@ -76,5 +72,12 @@ UAP$(TargetPlatformMinVersion.Replace('.', '_')) true + + + + + buildOutput=bin\$(Configuration);version=$(Version) + + diff --git a/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.nuspec b/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.nuspec new file mode 100644 index 00000000000..7778144f3cb --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Notifications/Microsoft.Toolkit.Uwp.Notifications.nuspec @@ -0,0 +1,84 @@ + + + + Microsoft.Toolkit.Uwp.Notifications + $version$ + Windows Community Toolkit Notifications + Microsoft.Toolkit,dotnetfoundation + true + https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/master/license.md + https://github.com/windows-toolkit/WindowsCommunityToolkit + https://raw.githubusercontent.com/windows-toolkit/WindowsCommunityToolkit/master/build/nuget.png + The official way to send toast notifications on Windows 10 via code rather than XML, with the help of IntelliSense. Supports all C# app types, including WPF, UWP, WinForms, and Console, even without packaging your app as MSIX. Also supports C++ UWP apps. + + Additionally, generate notification payloads from your ASP.NET web server to send as push notifications, or generate notification payloads from class libraries. + + For UWP/MSIX apps, you can also generate tile and badge notifications. + https://github.com/windows-toolkit/WindowsCommunityToolkit/releases + (c) .NET Foundation and Contributors. All rights reserved. + notifications win10 windows 10 tile tiles toast toasts badge xml uwp c# csharp c++ wpf winforms + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopBridgeHelpers.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopBridgeHelpers.cs index 6134db08869..500d0418cd2 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopBridgeHelpers.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopBridgeHelpers.cs @@ -20,9 +20,6 @@ internal 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? _hasIdentity; public static bool HasIdentity() @@ -37,10 +34,10 @@ public static bool HasIdentity() { int length = 0; var sb = new StringBuilder(0); - GetCurrentPackageFullName(ref length, sb); + NativeMethods.GetCurrentPackageFullName(ref length, sb); sb = new StringBuilder(length); - int error = GetCurrentPackageFullName(ref length, sb); + int error = NativeMethods.GetCurrentPackageFullName(ref length, sb); _hasIdentity = error != APPMODEL_ERROR_NO_PACKAGE; } diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopNotificationManagerCompat.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopNotificationManagerCompat.cs index 38359aa56dc..164c50e8672 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopNotificationManagerCompat.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/DesktopNotificationManagerCompat.cs @@ -50,7 +50,7 @@ public static void RegisterAumidAndComServer(string aumid) } // If running as Desktop Bridge - if (DesktopBridgeHelpers.IsRunningAsUwp()) + if (DesktopBridgeHelpers.HasIdentity()) { // 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. @@ -94,7 +94,7 @@ public static void RegisterActivator() // Big thanks to FrecherxDachs for figuring out the following code which works in .NET Core 3: https://github.com/FrecherxDachs/UwpNotificationNetCoreTest var uuid = typeof(T).GUID; uint cookie; - CoRegisterClassObject( + NativeMethods.CoRegisterClassObject( uuid, new NotificationActivatorClassFactory(), CLSCTX_LOCAL_SERVER, @@ -151,14 +151,6 @@ public int LockServer(bool fLock) } } - [DllImport("ole32.dll")] - private static extern int CoRegisterClassObject( - [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, - [MarshalAs(UnmanagedType.IUnknown)] object pUnk, - uint dwClsContext, - uint flags, - out uint lpdwRegister); - /// /// Creates a toast notifier. You must have called first (and also if you're a classic Win32 app), or this will throw an exception. /// @@ -198,7 +190,7 @@ private static void EnsureRegistered() if (!_registeredAumidAndComServer) { // Check if Desktop Bridge - if (DesktopBridgeHelpers.IsRunningAsUwp()) + if (DesktopBridgeHelpers.HasIdentity()) { // Implicitly registered, all good! _registeredAumidAndComServer = true; @@ -223,55 +215,7 @@ private static void EnsureRegistered() /// public static bool CanUseHttpImages { - get { return DesktopBridgeHelpers.IsRunningAsUwp(); } - } - - /// - /// Code from https://github.com/qmatteoq/DesktopBridgeHelpers/tree/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; - var sb = new StringBuilder(0); - GetCurrentPackageFullName(ref length, sb); - - sb = new StringBuilder(length); - int error = GetCurrentPackageFullName(ref length, sb); - - _isRunningAsUwp = error != 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; - } - } + get { return DesktopBridgeHelpers.HasIdentity(); } } } } diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/NativeMethods.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/NativeMethods.cs new file mode 100644 index 00000000000..2de3b811bb7 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Native/NativeMethods.cs @@ -0,0 +1,32 @@ +// 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. + +#if WIN32 + +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.Toolkit.Uwp.Notifications +{ + internal class NativeMethods + { + [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeleteObject([In] IntPtr hObject); + + [DllImport("ole32.dll")] + internal static extern int CoRegisterClassObject( + [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, + [MarshalAs(UnmanagedType.IUnknown)] object pUnk, + uint dwClsContext, + uint flags, + out uint lpdwRegister); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int GetCurrentPackageFullName(ref int packageFullNameLength, StringBuilder packageFullName); + } +} + +#endif \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Win32AppInfo.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Win32AppInfo.cs index 44a5a3f2344..b4fabd2d43d 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Win32AppInfo.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/Desktop/Win32AppInfo.cs @@ -84,7 +84,7 @@ public static Win32AppInfo Get() { } - DeleteObject(nativeHBitmap); + NativeMethods.DeleteObject(nativeHBitmap); } } catch @@ -231,10 +231,6 @@ private static bool IsAlphaBitmap(Bitmap bmp, out BitmapData bmpData) return false; } - [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DeleteObject([In] IntPtr hObject); - /// /// From https://stackoverflow.com/a/41622689/1454643 /// Generates Guid based on String. Key assumption for this algorithm is that name is unique (across where it it's being used) diff --git a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/ToastNotificationManagerCompat.cs b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/ToastNotificationManagerCompat.cs index 74d45d901ac..758746a7624 100644 --- a/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/ToastNotificationManagerCompat.cs +++ b/Microsoft.Toolkit.Uwp.Notifications/Toasts/Compat/ToastNotificationManagerCompat.cs @@ -59,7 +59,7 @@ public static event OnActivated OnActivated } catch (Exception ex) { - _initializeEx = ex; + _initializeEx = new InvalidOperationException("Failed to register notification activator", ex); } } @@ -108,7 +108,7 @@ internal static void OnActivatedInternal(string args, Internal.InternalNotificat private static string _win32Aumid; private static string _clsid; - private static Exception _initializeEx; + private static InvalidOperationException _initializeEx; static ToastNotificationManagerCompat() { @@ -119,7 +119,7 @@ static ToastNotificationManagerCompat() catch (Exception ex) { // We catch the exception so that things like subscribing to the event handler doesn't crash app - _initializeEx = ex; + _initializeEx = new InvalidOperationException("Failed initializing notifications", ex); } } @@ -251,7 +251,7 @@ private static void RegisterActivator(Type activatorType) // Big thanks to FrecherxDachs for figuring out the following code which works in .NET Core 3: https://github.com/FrecherxDachs/UwpNotificationNetCoreTest var uuid = activatorType.GUID; - CoRegisterClassObject( + NativeMethods.CoRegisterClassObject( uuid, new NotificationActivatorClassFactory(activatorType), CLSCTX_LOCAL_SERVER, @@ -262,12 +262,36 @@ private static void RegisterActivator(Type activatorType) private static void RegisterComServer(Type activatorType, string exePath) { // We register the EXE to start up when the notification is activated - string regString = string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}\\LocalServer32", activatorType.GUID); - var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(regString); + string regString = string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}", activatorType.GUID); + using (var key = Registry.CurrentUser.CreateSubKey(regString + "\\LocalServer32")) + { + // 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); + } + + if (IsElevated) + { + //// For elevated apps, we need to ensure they'll activate in existing running process by adding + //// some values in local machine + using (var key = Registry.LocalMachine.CreateSubKey(regString)) + { + // Same as above, except also including AppId to link to our AppId entry below + using (var localServer32 = key.CreateSubKey("LocalServer32")) + { + localServer32.SetValue(null, '"' + exePath + '"' + " " + TOAST_ACTIVATED_LAUNCH_ARG); + } - // 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("AppId", "{" + activatorType.GUID + "}"); + } + + // This tells COM to match any client, so Action Center will activate our elevated process. + // More info: https://docs.microsoft.com/windows/win32/com/runas + using (var key = Registry.LocalMachine.CreateSubKey(string.Format("SOFTWARE\\Classes\\AppID\\{{{0}}}", activatorType.GUID))) + { + key.SetValue("RunAs", "Interactive User"); + } + } } /// @@ -332,13 +356,13 @@ public int LockServer(bool fLock) } } - [DllImport("ole32.dll")] - private static extern int CoRegisterClassObject( - [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, - [MarshalAs(UnmanagedType.IUnknown)] object pUnk, - uint dwClsContext, - uint flags, - out uint lpdwRegister); + private static bool IsElevated + { + get + { + return new System.Security.Principal.WindowsPrincipal(System.Security.Principal.WindowsIdentity.GetCurrent()).IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator); + } + } #endif /// @@ -460,7 +484,32 @@ public static void Uninstall() { if (_clsid != null) { - Registry.CurrentUser.DeleteSubKey(string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}\\LocalServer32", _clsid)); + try + { + Registry.CurrentUser.DeleteSubKeyTree(string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}", _clsid)); + } + catch + { + } + + if (IsElevated) + { + try + { + Registry.LocalMachine.DeleteSubKeyTree(string.Format("SOFTWARE\\Classes\\CLSID\\{{{0}}}", _clsid)); + } + catch + { + } + + try + { + Registry.LocalMachine.DeleteSubKeyTree(string.Format("SOFTWARE\\Classes\\AppID\\{{{0}}}", _clsid)); + } + catch + { + } + } } } catch diff --git a/global.json b/global.json index 80d84a257c6..a3d78890e48 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "msbuild-sdks": { - "MSBuild.Sdk.Extras": "2.0.54" + "MSBuild.Sdk.Extras": "3.0.22" } } \ No newline at end of file