From 87f3b6a8717f80bfb0297eb112a418c817b0518c Mon Sep 17 00:00:00 2001 From: Robin Date: Tue, 14 Apr 2020 12:31:52 +0200 Subject: [PATCH] Improving stability for the notification issue reported in #182 --- GreenshotPlugin/Core/SimpleServiceProvider.cs | 4 + .../ToastNotificationService.cs | 375 ++++++++++-------- GreenshotWin10Plugin/Win10Plugin.cs | 2 +- 3 files changed, 204 insertions(+), 177 deletions(-) diff --git a/GreenshotPlugin/Core/SimpleServiceProvider.cs b/GreenshotPlugin/Core/SimpleServiceProvider.cs index 5482f107a..9ffd9d590 100644 --- a/GreenshotPlugin/Core/SimpleServiceProvider.cs +++ b/GreenshotPlugin/Core/SimpleServiceProvider.cs @@ -43,6 +43,10 @@ namespace GreenshotPlugin.Core foreach (var service in services) { + if (service == null) + { + continue; + } currentServices.Add(service); } } diff --git a/GreenshotWin10Plugin/ToastNotificationService.cs b/GreenshotWin10Plugin/ToastNotificationService.cs index 70ef5084a..413a51c00 100644 --- a/GreenshotWin10Plugin/ToastNotificationService.cs +++ b/GreenshotWin10Plugin/ToastNotificationService.cs @@ -1,176 +1,199 @@ -/* - * Greenshot - a free and open source screenshot tool - * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom - * - * For more information see: http://getgreenshot.org/ - * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using System; -using System.Drawing.Imaging; -using System.IO; -using System.Linq; -using Windows.UI.Notifications; -using GreenshotPlugin.Core; -using GreenshotPlugin.IniFile; -using GreenshotPlugin.Interfaces; -using GreenshotWin10Plugin.Native; -using log4net; - -namespace GreenshotWin10Plugin -{ - /// - /// This service provides a way to inform (notify) the user. - /// - public class ToastNotificationService : INotificationService - { - private static readonly ILog Log = LogManager.GetLogger(typeof(ToastNotificationService)); - private static readonly CoreConfiguration CoreConfiguration = IniConfig.GetIniSection(); - - private readonly string _imageFilePath; - public ToastNotificationService() - { - // Register AUMID and COM server (for Desktop Bridge apps, this no-ops) - DesktopNotificationManagerCompat.RegisterAumidAndComServer("Greenshot"); - // Register COM server and activator type - DesktopNotificationManagerCompat.RegisterActivator(); - - var localAppData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Greenshot"); - if (!Directory.Exists(localAppData)) - { - Directory.CreateDirectory(localAppData); - } - _imageFilePath = Path.Combine(localAppData, "greenshot.png"); - - if (File.Exists(_imageFilePath)) - { - return; - } - - using var greenshotImage = GreenshotResources.GetGreenshotIcon().ToBitmap(); - greenshotImage.Save(_imageFilePath, ImageFormat.Png); - } - - /// - /// 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, int timeout, Action onClickAction, Action onClosedAction) - { - // Do not inform the user if this is disabled - if (!CoreConfiguration.ShowTrayNotification) - { - return; - } - // Prepare the toast notifier. Be sure to specify the AppUserModelId on your application's shortcut! - var toastNotifier = DesktopNotificationManagerCompat.CreateToastNotifier(); - if (toastNotifier.Setting != NotificationSetting.Enabled) - { - Log.DebugFormat("Ignored toast due to {0}", toastNotifier.Setting); - return; - } - - // Get a toast XML template - var toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText01); - - // Fill in the text elements - var stringElement = toastXml.GetElementsByTagName("text").First(); - stringElement.AppendChild(toastXml.CreateTextNode(message)); - - if (_imageFilePath != null && File.Exists(_imageFilePath)) - { - // Specify the absolute path to an image - var imageElement = toastXml.GetElementsByTagName("image").First(); - var imageSrcNode = imageElement.Attributes.GetNamedItem("src"); - if (imageSrcNode != null) - { - imageSrcNode.NodeValue = _imageFilePath; - } - } - - // Create the toast and attach event listeners - var toast = new ToastNotification(toastXml) - { - // Windows 10 first with 1903: ExpiresOnReboot = true, - ExpirationTime = timeout > 0 ? DateTimeOffset.Now.AddMilliseconds(timeout) : (DateTimeOffset?)null - }; - - void ToastActivatedHandler(ToastNotification toastNotification, object sender) - { - try - { - onClickAction?.Invoke(); - } - catch (Exception ex) - { - Log.Warn("Exception while handling the onclick action: ", ex); - } - - toast.Activated -= ToastActivatedHandler; - } - - if (onClickAction != null) - { - toast.Activated += ToastActivatedHandler; - } - - void ToastDismissedHandler(ToastNotification toastNotification, ToastDismissedEventArgs eventArgs) - { - Log.Debug("Toast closed"); - try - { - onClosedAction?.Invoke(); - } - catch (Exception ex) - { - Log.Warn("Exception while handling the onClosed action: ", ex); - } - - toast.Dismissed -= ToastDismissedHandler; - // Remove the other handler too - toast.Activated -= ToastActivatedHandler; - toast.Failed -= ToastOnFailed; - } - toast.Dismissed += ToastDismissedHandler; - toast.Failed += ToastOnFailed; - toastNotifier.Show(toast); - } - - private void ToastOnFailed(ToastNotification sender, ToastFailedEventArgs args) - { - Log.WarnFormat("Failed to display a toast due to {0}", args.ErrorCode); - Log.Debug(sender.Content.GetXml()); - } - - public void ShowWarningMessage(string message, int timeout, Action onClickAction = null, Action onClosedAction = null) - { - ShowMessage(message, timeout, onClickAction, onClosedAction); - } - - public void ShowErrorMessage(string message, int timeout, Action onClickAction = null, Action onClosedAction = null) - { - ShowMessage(message, timeout, onClickAction, onClosedAction); - } - - public void ShowInfoMessage(string message, int timeout, Action onClickAction = null, Action onClosedAction = null) - { - ShowMessage(message, timeout, onClickAction, onClosedAction); - } - } -} +/* + * Greenshot - a free and open source screenshot tool + * Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom + * + * For more information see: http://getgreenshot.org/ + * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using Windows.Foundation.Metadata; +using Windows.UI.Notifications; +using GreenshotPlugin.Core; +using GreenshotPlugin.IniFile; +using GreenshotPlugin.Interfaces; +using GreenshotWin10Plugin.Native; +using log4net; + +namespace GreenshotWin10Plugin +{ + /// + /// This service provides a way to inform (notify) the user. + /// + public class ToastNotificationService : INotificationService + { + private static readonly ILog Log = LogManager.GetLogger(typeof(ToastNotificationService)); + private static readonly CoreConfiguration CoreConfiguration = IniConfig.GetIniSection(); + + private readonly string _imageFilePath; + public ToastNotificationService() + { + // Register AUMID and COM server (for Desktop Bridge apps, this no-ops) + DesktopNotificationManagerCompat.RegisterAumidAndComServer("Greenshot"); + // Register COM server and activator type + DesktopNotificationManagerCompat.RegisterActivator(); + + var localAppData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Greenshot"); + if (!Directory.Exists(localAppData)) + { + Directory.CreateDirectory(localAppData); + } + _imageFilePath = Path.Combine(localAppData, "greenshot.png"); + + if (File.Exists(_imageFilePath)) + { + return; + } + + using var greenshotImage = GreenshotResources.GetGreenshotIcon().ToBitmap(); + greenshotImage.Save(_imageFilePath, ImageFormat.Png); + } + + /// + /// 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, int timeout, Action onClickAction, Action onClosedAction) + { + // Do not inform the user if this is disabled + if (!CoreConfiguration.ShowTrayNotification) + { + return; + } + // Prepare the toast notifier. Be sure to specify the AppUserModelId on your application's shortcut! + var toastNotifier = DesktopNotificationManagerCompat.CreateToastNotifier(); + if (toastNotifier.Setting != NotificationSetting.Enabled) + { + Log.DebugFormat("Ignored toast due to {0}", toastNotifier.Setting); + return; + } + + // Get a toast XML template + var toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastImageAndText01); + + // Fill in the text elements + var stringElement = toastXml.GetElementsByTagName("text").First(); + stringElement.AppendChild(toastXml.CreateTextNode(message)); + + if (_imageFilePath != null && File.Exists(_imageFilePath)) + { + // Specify the absolute path to an image + var imageElement = toastXml.GetElementsByTagName("image").First(); + var imageSrcNode = imageElement.Attributes.GetNamedItem("src"); + if (imageSrcNode != null) + { + imageSrcNode.NodeValue = _imageFilePath; + } + } + + // Create the toast and attach event listeners + var toast = new ToastNotification(toastXml) + { + // Windows 10 first with 1903: ExpiresOnReboot = true, + ExpirationTime = timeout > 0 ? DateTimeOffset.Now.AddMilliseconds(timeout) : (DateTimeOffset?)null + }; + + void ToastActivatedHandler(ToastNotification toastNotification, object sender) + { + try + { + onClickAction?.Invoke(); + } + catch (Exception ex) + { + Log.Warn("Exception while handling the onclick action: ", ex); + } + + toast.Activated -= ToastActivatedHandler; + } + + if (onClickAction != null) + { + toast.Activated += ToastActivatedHandler; + } + + void ToastDismissedHandler(ToastNotification toastNotification, ToastDismissedEventArgs eventArgs) + { + Log.Debug("Toast closed"); + try + { + onClosedAction?.Invoke(); + } + catch (Exception ex) + { + Log.Warn("Exception while handling the onClosed action: ", ex); + } + + toast.Dismissed -= ToastDismissedHandler; + // Remove the other handler too + toast.Activated -= ToastActivatedHandler; + toast.Failed -= ToastOnFailed; + } + toast.Dismissed += ToastDismissedHandler; + toast.Failed += ToastOnFailed; + try + { + toastNotifier.Show(toast); + } + catch (Exception ex) + { + Log.Error("Couldn't show notification.", ex); + } + } + + private void ToastOnFailed(ToastNotification sender, ToastFailedEventArgs args) + { + Log.WarnFormat("Failed to display a toast due to {0}", args.ErrorCode); + Log.Debug(sender.Content.GetXml()); + } + + public void ShowWarningMessage(string message, int timeout, Action onClickAction = null, Action onClosedAction = null) + { + ShowMessage(message, timeout, onClickAction, onClosedAction); + } + + public void ShowErrorMessage(string message, int timeout, Action onClickAction = null, Action onClosedAction = null) + { + ShowMessage(message, timeout, onClickAction, onClosedAction); + } + + public void ShowInfoMessage(string message, int timeout, Action onClickAction = null, Action onClosedAction = null) + { + ShowMessage(message, timeout, onClickAction, onClosedAction); + } + + /// + /// Factory method, helping with checking if the notification service is even available + /// + /// ToastNotificationService + public static ToastNotificationService Create() + { + if (ApiInformation.IsTypePresent("Windows.ApplicationModel.Background.ToastNotificationActionTrigger")) + { + return new ToastNotificationService(); + } + Log.Warn("ToastNotificationActionTrigger not available."); + + return null; + } + } +} diff --git a/GreenshotWin10Plugin/Win10Plugin.cs b/GreenshotWin10Plugin/Win10Plugin.cs index 947ce180c..1e0519071 100644 --- a/GreenshotWin10Plugin/Win10Plugin.cs +++ b/GreenshotWin10Plugin/Win10Plugin.cs @@ -63,7 +63,7 @@ namespace GreenshotWin10Plugin return false; } - SimpleServiceProvider.Current.AddService(new ToastNotificationService()); + SimpleServiceProvider.Current.AddService(ToastNotificationService.Create()); // Set this as IOcrProvider SimpleServiceProvider.Current.AddService(new Win10OcrProvider()); // Add the processor