Register shortcut for Windows toast notifications
This commit is contained in:
parent
36bbc863f8
commit
6cf86d41f5
@ -8,9 +8,9 @@ public partial class App : Application
|
|||||||
{
|
{
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
{
|
{
|
||||||
|
_ = ToastShortcutRegistration.TryEnsureStartMenuShortcut(NotificationService.ToastAppUserModelId);
|
||||||
_ = AppUserModelIdRegistration.TryRegister(NotificationService.ToastAppUserModelId);
|
_ = AppUserModelIdRegistration.TryRegister(NotificationService.ToastAppUserModelId);
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
155
EmbyToolbox/Interop/ToastShortcutRegistration.cs
Normal file
155
EmbyToolbox/Interop/ToastShortcutRegistration.cs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.InteropServices.ComTypes;
|
||||||
|
|
||||||
|
namespace EmbyToolbox.Interop;
|
||||||
|
|
||||||
|
internal static class ToastShortcutRegistration
|
||||||
|
{
|
||||||
|
private const int StgmReadwrite = 0x00000002;
|
||||||
|
|
||||||
|
private static readonly PropertyKey AppUserModelIdKey = new(
|
||||||
|
new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"),
|
||||||
|
5);
|
||||||
|
|
||||||
|
public static string? LastDiagnostics { get; private set; }
|
||||||
|
|
||||||
|
public static bool TryEnsureStartMenuShortcut(string appUserModelId)
|
||||||
|
{
|
||||||
|
LastDiagnostics = null;
|
||||||
|
|
||||||
|
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 10240))
|
||||||
|
{
|
||||||
|
LastDiagnostics = "требуется Windows 10 (10240) или новее.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var exePath = Environment.ProcessPath;
|
||||||
|
if (string.IsNullOrWhiteSpace(exePath) || !File.Exists(exePath))
|
||||||
|
{
|
||||||
|
LastDiagnostics = "не удалось определить путь к exe.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var programs = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu);
|
||||||
|
var shortcutPath = Path.Combine(programs, "Programs", "Emby Toolbox.lnk");
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(shortcutPath)!);
|
||||||
|
|
||||||
|
var shellLinkObject = (object)new CShellLink();
|
||||||
|
var shellLink = (IShellLinkW)shellLinkObject;
|
||||||
|
shellLink.SetPath(exePath);
|
||||||
|
shellLink.SetArguments(string.Empty);
|
||||||
|
shellLink.SetWorkingDirectory(Path.GetDirectoryName(exePath) ?? AppContext.BaseDirectory);
|
||||||
|
shellLink.SetDescription("Emby Toolbox");
|
||||||
|
|
||||||
|
if (File.Exists(Path.Combine(AppContext.BaseDirectory, "Resources", "AppIcon.ico")))
|
||||||
|
{
|
||||||
|
shellLink.SetIconLocation(Path.Combine(AppContext.BaseDirectory, "Resources", "AppIcon.ico"), 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shellLink.SetIconLocation(exePath, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
using var appId = PropVariant.FromString(appUserModelId);
|
||||||
|
var propertyStore = (IPropertyStore)shellLink;
|
||||||
|
propertyStore.SetValue(AppUserModelIdKey, appId);
|
||||||
|
propertyStore.Commit();
|
||||||
|
|
||||||
|
var persistFile = (IPersistFile)shellLink;
|
||||||
|
persistFile.Save(shortcutPath, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LastDiagnostics = $"{ex.GetType().Name}: {ex.Message}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[Guid("00021401-0000-0000-C000-000000000046")]
|
||||||
|
private sealed class CShellLink
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
[Guid("000214F9-0000-0000-C000-000000000046")]
|
||||||
|
private interface IShellLinkW
|
||||||
|
{
|
||||||
|
void GetPath(IntPtr pszFile, int cchMaxPath, IntPtr pfd, uint fFlags);
|
||||||
|
void GetIDList(out IntPtr ppidl);
|
||||||
|
void SetIDList(IntPtr pidl);
|
||||||
|
void GetDescription(IntPtr pszName, int cchMaxName);
|
||||||
|
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||||
|
void GetWorkingDirectory(IntPtr pszDir, int cchMaxPath);
|
||||||
|
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||||
|
void GetArguments(IntPtr pszArgs, int cchMaxPath);
|
||||||
|
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
||||||
|
void GetHotkey(out short pwHotkey);
|
||||||
|
void SetHotkey(short wHotkey);
|
||||||
|
void GetShowCmd(out int piShowCmd);
|
||||||
|
void SetShowCmd(int iShowCmd);
|
||||||
|
void GetIconLocation(IntPtr pszIconPath, int cchIconPath, out int piIcon);
|
||||||
|
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||||
|
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, uint dwReserved);
|
||||||
|
void Resolve(IntPtr hwnd, uint fFlags);
|
||||||
|
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
[Guid("00000138-0000-0000-C000-000000000046")]
|
||||||
|
private interface IPropertyStore
|
||||||
|
{
|
||||||
|
void GetCount(out uint cProps);
|
||||||
|
void GetAt(uint iProp, out PropertyKey pkey);
|
||||||
|
void GetValue(ref PropertyKey key, out PropVariant pv);
|
||||||
|
void SetValue(in PropertyKey key, in PropVariant pv);
|
||||||
|
void Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
|
private readonly struct PropertyKey(Guid formatId, int propertyId)
|
||||||
|
{
|
||||||
|
private readonly Guid _formatId = formatId;
|
||||||
|
private readonly int _propertyId = propertyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private sealed class PropVariant : IDisposable
|
||||||
|
{
|
||||||
|
private ushort _valueType;
|
||||||
|
private ushort _reserved1;
|
||||||
|
private ushort _reserved2;
|
||||||
|
private ushort _reserved3;
|
||||||
|
private IntPtr _value;
|
||||||
|
private IntPtr _reserved4;
|
||||||
|
|
||||||
|
public static PropVariant FromString(string value)
|
||||||
|
{
|
||||||
|
return new PropVariant
|
||||||
|
{
|
||||||
|
_valueType = 31, // VT_LPWSTR
|
||||||
|
_value = Marshal.StringToCoTaskMemUni(value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
PropVariantClear(this);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~PropVariant()
|
||||||
|
{
|
||||||
|
PropVariantClear(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("ole32.dll")]
|
||||||
|
private static extern int PropVariantClear([In, Out] PropVariant pvar);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,6 +35,13 @@ public sealed class NotificationService
|
|||||||
|
|
||||||
private void LogAppUserModelRegistrationState()
|
private void LogAppUserModelRegistrationState()
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(ToastShortcutRegistration.LastDiagnostics))
|
||||||
|
{
|
||||||
|
_logging.Warning(
|
||||||
|
$"Windows toast: ярлык Start Menu не подготовлен ({ToastShortcutRegistration.LastDiagnostics})",
|
||||||
|
"notify");
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(AppUserModelIdRegistration.LastRegisteredId, ToastAppUserModelId, StringComparison.Ordinal))
|
if (string.Equals(AppUserModelIdRegistration.LastRegisteredId, ToastAppUserModelId, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_logging.Info($"App User Model ID: {ToastAppUserModelId}", "notify");
|
_logging.Info($"App User Model ID: {ToastAppUserModelId}", "notify");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user