diff --git a/Greenshot/greenshot.manifest b/Greenshot/greenshot.manifest
index 919fcb895..cae29e2dc 100644
--- a/Greenshot/greenshot.manifest
+++ b/Greenshot/greenshot.manifest
@@ -1,44 +1,48 @@
-
-
-
-
- True/PM
-
-
+
+
+
+
+ True/PM
+
+ true
+ true
+ PerMonitorV2,PerMonitor
+ true
+ true
+
+
-
+
+
-
+
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/GreenshotPlugin/Core/WindowsVersion.cs b/GreenshotPlugin/Core/WindowsVersion.cs
index ae9600f25..6dd4faa08 100644
--- a/GreenshotPlugin/Core/WindowsVersion.cs
+++ b/GreenshotPlugin/Core/WindowsVersion.cs
@@ -15,6 +15,7 @@ namespace GreenshotPlugin.Core
///
public static Version WinVersion { get; } = Environment.OSVersion.Version;
+ public static double WinVersionTotal = WinVersion.Major + (double)WinVersion.Minor / 10;
///
/// Test if the current OS is Windows 10
///
@@ -39,6 +40,8 @@ namespace GreenshotPlugin.Core
/// true if we are running on Windows 7 or later
public static bool IsWindows7OrLater { get; } = WinVersion.Major == 6 && WinVersion.Minor >= 1 || WinVersion.Major > 6;
+ public static bool IsWindows7OrLower { get; } = WinVersionTotal <= 6.1;
+
///
/// Test if the current OS is Windows 8.0
///
diff --git a/GreenshotWin10Plugin/Native/DesktopNotificationManagerCompat.cs b/GreenshotWin10Plugin/Native/DesktopNotificationManagerCompat.cs
new file mode 100644
index 000000000..703768582
--- /dev/null
+++ b/GreenshotWin10Plugin/Native/DesktopNotificationManagerCompat.cs
@@ -0,0 +1,384 @@
+// ******************************************************************
+// 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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/GreenshotWin10Plugin/Native/GreenshotNotificationActivator.cs b/GreenshotWin10Plugin/Native/GreenshotNotificationActivator.cs
new file mode 100644
index 000000000..74922476f
--- /dev/null
+++ b/GreenshotWin10Plugin/Native/GreenshotNotificationActivator.cs
@@ -0,0 +1,41 @@
+/*
+ * 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.Runtime.InteropServices;
+using log4net;
+
+namespace GreenshotWin10Plugin.Native
+{
+ // The GUID CLSID must be unique to your app. Create a new GUID if copying this code.
+ [ClassInterface(ClassInterfaceType.None)]
+ [ComSourceInterfaces(typeof(INotificationActivationCallback))]
+ [Guid("F48E86D3-E34C-4DB7-8F8F-9A0EA55F0D08"), ComVisible(true)]
+ public class GreenshotNotificationActivator : NotificationActivator
+ {
+ private static readonly ILog Log = LogManager.GetLogger(typeof(GreenshotNotificationActivator));
+
+ public override void OnActivated(string invokedArgs, NotificationUserInput userInput, string appUserModelId)
+ {
+ // TODO: Handle activation
+ Log.Info("Activated");
+ }
+ }
+}
diff --git a/GreenshotWin10Plugin/ToastNotificationService.cs b/GreenshotWin10Plugin/ToastNotificationService.cs
index 1c3ad9dda..05d930bc8 100644
--- a/GreenshotWin10Plugin/ToastNotificationService.cs
+++ b/GreenshotWin10Plugin/ToastNotificationService.cs
@@ -27,6 +27,7 @@ using Windows.UI.Notifications;
using GreenshotPlugin.Core;
using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces;
+using GreenshotWin10Plugin.Native;
using log4net;
namespace GreenshotWin10Plugin
@@ -42,6 +43,11 @@ namespace GreenshotWin10Plugin
private readonly string _imageFilePath;
public ToastNotificationService()
{
+ // Register AUMID and COM server (for Desktop Bridge apps, this no-ops)
+ DesktopNotificationManagerCompat.RegisterAumidAndComServer("Greenshot.Greenshot");
+ // Register COM server and activator type
+ DesktopNotificationManagerCompat.RegisterActivator();
+
var localAppData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Greenshot");
if (!Directory.Exists(localAppData))
{
@@ -58,6 +64,12 @@ namespace GreenshotWin10Plugin
greenshotImage.Save(_imageFilePath, ImageFormat.Png);
}
+ ///
+ /// This creates the actual toast
+ ///
+ /// string
+ /// Action called when clicked
+ /// Action called when the toast is closed
private void ShowMessage(string message, Action onClickAction, Action onClosedAction)
{
// Do not inform the user if this is disabled
@@ -65,6 +77,14 @@ namespace GreenshotWin10Plugin
{
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);
@@ -83,7 +103,6 @@ namespace GreenshotWin10Plugin
}
}
-
// Create the toast and attach event listeners
var toast = new ToastNotification(toastXml);
@@ -121,10 +140,17 @@ namespace GreenshotWin10Plugin
toast.Dismissed -= ToastDismissedHandler;
// Remove the other handler too
toast.Activated -= ToastActivatedHandler;
+ toast.Failed -= ToastOnFailed;
}
toast.Dismissed += ToastDismissedHandler;
- // Show the toast. Be sure to specify the AppUserModelId on your application's shortcut!
- ToastNotificationManager.CreateToastNotifier(@"Greenshot").Show(toast);
+ 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)