Introduce application context
This commit is contained in:
+1
-21
@@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using DesktopNotifications;
|
||||
using DesktopNotifications.FreeDesktop;
|
||||
using DesktopNotifications.Windows;
|
||||
using Example.Win32;
|
||||
|
||||
namespace Example
|
||||
{
|
||||
@@ -25,24 +22,7 @@ namespace Example
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
const string AppName = "DesktopNotificationsExample";
|
||||
|
||||
SetCurrentProcessExplicitAppUserModelID(AppName);
|
||||
|
||||
using var shortcut = new ShellLink
|
||||
{
|
||||
TargetPath = Process.GetCurrentProcess().MainModule.FileName,
|
||||
Arguments = string.Empty,
|
||||
AppUserModelID = AppName
|
||||
};
|
||||
|
||||
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
var startMenuPath = Path.Combine(appData, @"Microsoft\Windows\Start Menu\Programs");
|
||||
var shortcutFile = Path.Combine(startMenuPath, $"{AppName}.lnk");
|
||||
|
||||
shortcut.Save(shortcutFile);
|
||||
|
||||
return new WindowsNotificationManager(AppName);
|
||||
return new WindowsNotificationManager();
|
||||
}
|
||||
|
||||
throw new PlatformNotSupportedException();
|
||||
|
||||
@@ -1,449 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
|
||||
namespace Example.Win32
|
||||
{
|
||||
// Modified from http://smdn.jp/programming/tips/createlnk/
|
||||
// Originally from http://www.vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects
|
||||
// /Creating_and_Modifying_Shortcuts/article.asp
|
||||
// Partly based on Sending toast notifications from desktop apps sample
|
||||
public class ShellLink : IDisposable
|
||||
{
|
||||
#region Win32 and COM
|
||||
|
||||
// IShellLink Interface
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("000214F9-0000-0000-C000-000000000046")]
|
||||
private interface IShellLinkW
|
||||
{
|
||||
uint GetPath([Out] [MarshalAs(UnmanagedType.LPWStr)]
|
||||
StringBuilder pszFile,
|
||||
int cchMaxPath, ref WIN32_FIND_DATAW pfd, uint fFlags);
|
||||
|
||||
uint GetIDList(out IntPtr ppidl);
|
||||
uint SetIDList(IntPtr pidl);
|
||||
|
||||
uint GetDescription([Out] [MarshalAs(UnmanagedType.LPWStr)]
|
||||
StringBuilder pszName,
|
||||
int cchMaxName);
|
||||
|
||||
uint SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||
|
||||
uint GetWorkingDirectory([Out] [MarshalAs(UnmanagedType.LPWStr)]
|
||||
StringBuilder pszDir,
|
||||
int cchMaxPath);
|
||||
|
||||
uint SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||
|
||||
uint GetArguments([Out] [MarshalAs(UnmanagedType.LPWStr)]
|
||||
StringBuilder pszArgs,
|
||||
int cchMaxPath);
|
||||
|
||||
uint SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
||||
uint GetHotKey(out ushort pwHotkey);
|
||||
uint SetHotKey(ushort wHotKey);
|
||||
uint GetShowCmd(out int piShowCmd);
|
||||
uint SetShowCmd(int iShowCmd);
|
||||
|
||||
uint GetIconLocation([Out] [MarshalAs(UnmanagedType.LPWStr)]
|
||||
StringBuilder pszIconPath,
|
||||
int cchIconPath, out int piIcon);
|
||||
|
||||
uint SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||
|
||||
uint SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel,
|
||||
uint dwReserved);
|
||||
|
||||
uint Resolve(IntPtr hwnd, uint fFlags);
|
||||
uint SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||
}
|
||||
|
||||
// ShellLink CoClass (ShellLink object)
|
||||
[ComImport]
|
||||
[ClassInterface(ClassInterfaceType.None)]
|
||||
[Guid("00021401-0000-0000-C000-000000000046")]
|
||||
private class CShellLink
|
||||
{
|
||||
}
|
||||
|
||||
// WIN32_FIND_DATAW Structure
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Unicode)]
|
||||
private struct WIN32_FIND_DATAW
|
||||
{
|
||||
public readonly uint dwFileAttributes;
|
||||
public readonly FILETIME ftCreationTime;
|
||||
public readonly FILETIME ftLastAccessTime;
|
||||
public readonly FILETIME ftLastWriteTime;
|
||||
public readonly uint nFileSizeHigh;
|
||||
public readonly uint nFileSizeLow;
|
||||
public readonly uint dwReserved0;
|
||||
public readonly uint dwReserved1;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
|
||||
public readonly string cFileName;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
|
||||
public readonly string cAlternateFileName;
|
||||
}
|
||||
|
||||
// IPropertyStore Interface
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
|
||||
private interface IPropertyStore
|
||||
{
|
||||
uint GetCount([Out] out uint cProps);
|
||||
uint GetAt([In] uint iProp, out PropertyKey pkey);
|
||||
uint GetValue([In] ref PropertyKey key, [Out] PropVariant pv);
|
||||
uint SetValue([In] ref PropertyKey key, [In] PropVariant pv);
|
||||
uint Commit();
|
||||
}
|
||||
|
||||
// PropertyKey Structure
|
||||
// Narrowed down from PropertyKey.cs of Windows API Code Pack 1.1
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
private struct PropertyKey
|
||||
{
|
||||
#region Fields
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public Guid FormatId { get; }
|
||||
|
||||
public int PropertyId { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public PropertyKey(Guid formatId, int propertyId)
|
||||
{
|
||||
FormatId = formatId;
|
||||
PropertyId = propertyId;
|
||||
}
|
||||
|
||||
public PropertyKey(string formatId, int propertyId)
|
||||
{
|
||||
FormatId = new Guid(formatId);
|
||||
PropertyId = propertyId;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
// PropVariant Class (only for string value)
|
||||
// Narrowed down from PropVariant.cs of Windows API Code Pack 1.1
|
||||
// Originally from http://blogs.msdn.com/b/adamroot/archive/2008/04/11
|
||||
// /interop-with-propvariants-in-net.aspx
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private sealed class PropVariant : IDisposable
|
||||
{
|
||||
#region Fields
|
||||
|
||||
[FieldOffset(0)] private ushort valueType; // Value type
|
||||
|
||||
// [FieldOffset(2)]
|
||||
// ushort wReserved1; // Reserved field
|
||||
// [FieldOffset(4)]
|
||||
// ushort wReserved2; // Reserved field
|
||||
// [FieldOffset(6)]
|
||||
// ushort wReserved3; // Reserved field
|
||||
|
||||
[FieldOffset(8)] private readonly IntPtr ptr; // Value
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
// Value type (System.Runtime.InteropServices.VarEnum)
|
||||
public VarEnum VarType
|
||||
{
|
||||
get => (VarEnum) valueType;
|
||||
set => valueType = (ushort) value;
|
||||
}
|
||||
|
||||
// Whether value is empty or null
|
||||
public bool IsNullOrEmpty =>
|
||||
valueType == (ushort) VarEnum.VT_EMPTY ||
|
||||
valueType == (ushort) VarEnum.VT_NULL;
|
||||
|
||||
// Value (only for string value)
|
||||
public string? Value => Marshal.PtrToStringUni(ptr);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public PropVariant()
|
||||
{
|
||||
}
|
||||
|
||||
// Construct with string value
|
||||
public PropVariant(string value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentException("Failed to set value.");
|
||||
}
|
||||
|
||||
valueType = (ushort) VarEnum.VT_LPWSTR;
|
||||
ptr = Marshal.StringToCoTaskMemUni(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Destructor
|
||||
|
||||
~PropVariant()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
PropVariantClear(this);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[DllImport("Ole32.dll", PreserveSig = false)]
|
||||
private static extern void PropVariantClear([In] [Out] PropVariant pvar);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
|
||||
private IShellLinkW? shellLinkW;
|
||||
|
||||
// Name = System.AppUserModel.ID
|
||||
// ShellPKey = PKEY_AppUserModel_ID
|
||||
// FormatID = 9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3
|
||||
// PropID = 5
|
||||
// Type = String (VT_LPWSTR)
|
||||
private readonly PropertyKey AppUserModelIDKey =
|
||||
new PropertyKey("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}", 5);
|
||||
|
||||
private const int MAX_PATH = 260;
|
||||
private const int INFOTIPSIZE = 1024;
|
||||
|
||||
private const int STGM_READ = 0x00000000; // STGM constants
|
||||
private const uint SLGP_UNCPRIORITY = 0x0002; // SLGP flags
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Properties (Interfaces)
|
||||
|
||||
private IPersistFile PersistFile
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!(shellLinkW is IPersistFile persistFile))
|
||||
{
|
||||
throw new COMException("Failed to create IPersistFile.");
|
||||
}
|
||||
|
||||
return persistFile;
|
||||
}
|
||||
}
|
||||
|
||||
private IPropertyStore PropertyStore
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!(shellLinkW is IPropertyStore PropertyStore))
|
||||
{
|
||||
throw new COMException("Failed to create IPropertyStore.");
|
||||
}
|
||||
|
||||
return PropertyStore;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties (Minimal)
|
||||
|
||||
// Path of loaded shortcut file
|
||||
public string ShortcutFile
|
||||
{
|
||||
get
|
||||
{
|
||||
string shortcutFile;
|
||||
|
||||
PersistFile.GetCurFile(out shortcutFile);
|
||||
|
||||
return shortcutFile;
|
||||
}
|
||||
}
|
||||
|
||||
// Path of target file
|
||||
public string TargetPath
|
||||
{
|
||||
get
|
||||
{
|
||||
// No limitation to length of buffer string in the case of Unicode though.
|
||||
StringBuilder targetPath = new StringBuilder(MAX_PATH);
|
||||
|
||||
var data = new WIN32_FIND_DATAW();
|
||||
|
||||
VerifySucceeded(shellLinkW!.GetPath(targetPath, targetPath.Capacity, ref data,
|
||||
SLGP_UNCPRIORITY));
|
||||
|
||||
return targetPath.ToString();
|
||||
}
|
||||
set => VerifySucceeded(shellLinkW!.SetPath(value));
|
||||
}
|
||||
|
||||
public string Arguments
|
||||
{
|
||||
get
|
||||
{
|
||||
// No limitation to length of buffer string in the case of Unicode though.
|
||||
StringBuilder arguments = new StringBuilder(INFOTIPSIZE);
|
||||
|
||||
VerifySucceeded(shellLinkW!.GetArguments(arguments, arguments.Capacity));
|
||||
|
||||
return arguments.ToString();
|
||||
}
|
||||
set => VerifySucceeded(shellLinkW!.SetArguments(value));
|
||||
}
|
||||
|
||||
// AppUserModelID to be used for Windows 7 or later.
|
||||
public string AppUserModelID
|
||||
{
|
||||
get
|
||||
{
|
||||
using (PropVariant pv = new PropVariant())
|
||||
{
|
||||
VerifySucceeded(PropertyStore.GetValue(AppUserModelIDKey, pv));
|
||||
|
||||
if (pv.Value == null)
|
||||
{
|
||||
return "Null";
|
||||
}
|
||||
|
||||
return pv.Value;
|
||||
}
|
||||
}
|
||||
set
|
||||
{
|
||||
using (PropVariant pv = new PropVariant(value))
|
||||
{
|
||||
VerifySucceeded(PropertyStore.SetValue(AppUserModelIDKey, pv));
|
||||
VerifySucceeded(PropertyStore.Commit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
public ShellLink()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
// Construct with loading shortcut file.
|
||||
public ShellLink(string? file)
|
||||
{
|
||||
try
|
||||
{
|
||||
shellLinkW = (IShellLinkW) new CShellLink();
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new COMException("Failed to create ShellLink object.");
|
||||
}
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
Load(file);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Destructor
|
||||
|
||||
~ShellLink()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (shellLinkW != null)
|
||||
{
|
||||
// Release all references.
|
||||
Marshal.FinalReleaseComObject(shellLinkW);
|
||||
shellLinkW = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
// Save shortcut file.
|
||||
public void Save()
|
||||
{
|
||||
string file = ShortcutFile;
|
||||
|
||||
if (file == null)
|
||||
{
|
||||
throw new InvalidOperationException("File name is not given.");
|
||||
}
|
||||
|
||||
Save(file);
|
||||
}
|
||||
|
||||
public void Save(string file)
|
||||
{
|
||||
if (file == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(file));
|
||||
}
|
||||
|
||||
PersistFile.Save(file, true);
|
||||
}
|
||||
|
||||
// Load shortcut file.
|
||||
public void Load(string file)
|
||||
{
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
throw new FileNotFoundException("File is not found.", file);
|
||||
}
|
||||
|
||||
PersistFile.Load(file, STGM_READ);
|
||||
}
|
||||
|
||||
// Verify if operation succeeded.
|
||||
public static void VerifySucceeded(uint hresult)
|
||||
{
|
||||
if (hresult > 1)
|
||||
{
|
||||
throw new InvalidOperationException("Failed with HRESULT: " +
|
||||
hresult.ToString("X"));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user