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)
|
||||
{
|
||||
_ = ToastShortcutRegistration.TryEnsureStartMenuShortcut(NotificationService.ToastAppUserModelId);
|
||||
_ = AppUserModelIdRegistration.TryRegister(NotificationService.ToastAppUserModelId);
|
||||
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()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(ToastShortcutRegistration.LastDiagnostics))
|
||||
{
|
||||
_logging.Warning(
|
||||
$"Windows toast: ярлык Start Menu не подготовлен ({ToastShortcutRegistration.LastDiagnostics})",
|
||||
"notify");
|
||||
}
|
||||
|
||||
if (string.Equals(AppUserModelIdRegistration.LastRegisteredId, ToastAppUserModelId, StringComparison.Ordinal))
|
||||
{
|
||||
_logging.Info($"App User Model ID: {ToastAppUserModelId}", "notify");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user