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."); } }
/// <summary> /// If you're not using MSIX or sparse packages, 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. /// </summary> /// <typeparam name="T">Your implementation of NotificationActivator. Must have GUID and ComVisible attributes on class.</typeparam> /// <param name="aumid">An AUMID that uniquely identifies your application.</param> public static void RegisterAumidAndComServer <T>(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 <T>(exePath); _registeredAumidAndComServer = true; }
private static void Initialize() { // If containerized if (DesktopBridgeHelpers.IsContainerized()) { // No need to do anything additional, already registered through manifest return; } Win32AppInfo win32AppInfo = null; // If sparse if (DesktopBridgeHelpers.HasIdentity()) { _win32Aumid = new ManifestHelper().GetAumidFromPackageManifest(); } else { win32AppInfo = Win32AppInfo.Get(); _win32Aumid = win32AppInfo.Aumid; } // Create and register activator var activatorType = CreateAndRegisterActivator(); // Register via registry using (var rootKey = Registry.CurrentUser.CreateSubKey(GetRegistrySubKey())) { // If they don't have identity, we need to specify the display assets if (!DesktopBridgeHelpers.HasIdentity()) { // Set the display name and icon uri rootKey.SetValue("DisplayName", win32AppInfo.DisplayName); if (win32AppInfo.IconPath != null) { rootKey.SetValue("IconUri", win32AppInfo.IconPath); } else { if (rootKey.GetValue("IconUri") != null) { rootKey.DeleteValue("IconUri"); } } // Background color only appears in the settings page, format is // hex without leading #, like "FFDDDDDD" rootKey.SetValue("IconBackgroundColor", "FFDDDDDD"); // Additionally, we need to read whether they've sent a notification before _hasSentNotification = rootKey.GetValue(REG_HAS_SENT_NOTIFICATION) != null; } rootKey.SetValue("CustomActivator", string.Format("{{{0}}}", activatorType.GUID)); } }
private void PreprocessScheduledToast(ScheduledToastNotification notification) { // For apps that don't have identity... if (!DesktopBridgeHelpers.HasIdentity()) { // If tag is specified if (!string.IsNullOrEmpty(notification.Tag)) { // If group isn't specified, we have to add a group since otherwise can't remove without a group notification.Group = ToastNotificationManagerCompat.DEFAULT_GROUP; } } }
/// <summary> /// Updates the existing toast notification that has the specified tag. /// </summary> /// <param name="data">An object that contains the updated info.</param> /// <param name="tag">The identifier of the toast notification to update.</param> /// <returns>A value that indicates the result of the update (failure, success, etc).</returns> public NotificationUpdateResult Update(NotificationData data, string tag) { #if WIN32 // For apps that don't have identity... if (!DesktopBridgeHelpers.HasIdentity()) { // If group isn't specified, we have to add a group since otherwise can't remove without a group return(Update(data, tag, ToastNotificationManagerCompat.DEFAULT_GROUP)); } #endif return(_notifier.Update(data, tag)); }
private static Type CreateActivatorType() { // https://stackoverflow.com/questions/24069352/c-sharp-typebuilder-generate-class-with-function-dynamically // For .NET Core we use https://stackoverflow.com/questions/36937276/is-there-any-replace-of-assemblybuilder-definedynamicassembly-in-net-core AssemblyName aName = new AssemblyName("DynamicComActivator"); AssemblyBuilder aBuilder = AssemblyBuilder.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run); // For a single-module assembly, the module name is usually the assembly name plus an extension. ModuleBuilder mb = aBuilder.DefineDynamicModule(aName.Name); // Create class which extends NotificationActivator TypeBuilder tb = mb.DefineType( name: "MyNotificationActivator", attr: TypeAttributes.Public, parent: typeof(Internal.InternalNotificationActivator), interfaces: new Type[0]); if (DesktopBridgeHelpers.IsContainerized()) { _clsid = new ManifestHelper().GetClsidFromPackageManifest(); } else { _clsid = Win32AppInfo.GenerateGuid(_win32Aumid); } tb.SetCustomAttribute(new CustomAttributeBuilder( con: typeof(GuidAttribute).GetConstructor(new Type[] { typeof(string) }), constructorArgs: new object[] { _clsid })); tb.SetCustomAttribute(new CustomAttributeBuilder( con: typeof(ComVisibleAttribute).GetConstructor(new Type[] { typeof(bool) }), constructorArgs: new object[] { true })); tb.SetCustomAttribute(new CustomAttributeBuilder( #pragma warning disable CS0618 // Type or member is obsolete con: typeof(ComSourceInterfacesAttribute).GetConstructor(new Type[] { typeof(Type) }), #pragma warning restore CS0618 // Type or member is obsolete constructorArgs: new object[] { typeof(Internal.InternalNotificationActivator.INotificationActivationCallback) })); tb.SetCustomAttribute(new CustomAttributeBuilder( con: typeof(ClassInterfaceAttribute).GetConstructor(new Type[] { typeof(ClassInterfaceType) }), constructorArgs: new object[] { ClassInterfaceType.None })); return(tb.CreateType()); }
private static void RegisterActivator(Type activatorType) { if (!DesktopBridgeHelpers.IsContainerized()) { string exePath = Process.GetCurrentProcess().MainModule.FileName; RegisterComServer(activatorType, exePath); } // 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; NativeMethods.CoRegisterClassObject( uuid, new NotificationActivatorClassFactory(activatorType), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, out _); }
internal static void SetHasSentToastNotification() { // For plain Win32 apps, record that we've sent a notification if (!_hasSentNotification && !DesktopBridgeHelpers.HasIdentity()) { _hasSentNotification = true; try { using (var rootKey = Registry.CurrentUser.CreateSubKey(GetRegistrySubKey())) { rootKey.SetValue(REG_HAS_SENT_NOTIFICATION, 1); } } catch { } } }
/// <summary> /// Creates a toast notifier. /// </summary> /// <returns><see cref="ToastNotifierCompat"/>An instance of the toast notifier.</returns> public static ToastNotifierCompat CreateToastNotifier() { #if WIN32 if (_initializeEx != null) { throw _initializeEx; } if (DesktopBridgeHelpers.HasIdentity()) { return(new ToastNotifierCompat(ToastNotificationManager.CreateToastNotifier())); } else { return(new ToastNotifierCompat(ToastNotificationManager.CreateToastNotifier(_win32Aumid))); } #else return(new ToastNotifierCompat(ToastNotificationManager.CreateToastNotifier())); #endif }
internal static void PreRegisterIdentityLessApp() { // For plain Win32 apps, we first have to have send a toast notification once before using scheduled toasts. if (!_hasSentNotification && !DesktopBridgeHelpers.HasIdentity()) { const string tag = "toolkit1stNotif"; // Show the toast new ToastContentBuilder() .AddText("New notification") .Show(toast => { // We'll hide the popup and set the toast to expire in case removing doesn't work toast.SuppressPopup = true; toast.Tag = tag; toast.ExpirationTime = DateTime.Now.AddSeconds(15); }); // And then remove it ToastNotificationManagerCompat.History.Remove(tag); } }
/// <summary> /// If you're not using MSIX, call this when your app is being uninstalled to properly clean up all notifications and notification-related resources. Note that this must be called from your app's main EXE (the one that you used notifications for) and not a separate uninstall EXE. If called from a MSIX app, this method no-ops. /// </summary> public static void Uninstall() { if (DesktopBridgeHelpers.IsContainerized()) { // Packaged containerized apps automatically clean everything up already return; } if (!DesktopBridgeHelpers.HasIdentity()) { try { // Remove all scheduled notifications (do this first before clearing current notifications) var notifier = CreateToastNotifier(); foreach (var scheduled in notifier.GetScheduledToastNotifications()) { try { notifier.RemoveFromSchedule(scheduled); } catch { } } } catch { } try { // Clear all current notifications History.Clear(); } catch { } } try { // Remove registry key if (_win32Aumid != null) { Registry.CurrentUser.DeleteSubKey(GetRegistrySubKey()); } } catch { } try { if (_clsid != null) { 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 { } try { // Delete any of the app files var appDataFolderPath = Win32AppInfo.GetAppDataFolderPath(_win32Aumid); if (Directory.Exists(appDataFolderPath)) { Directory.Delete(appDataFolderPath, recursive: true); } } catch { } }