refactor(installer): extract ShortcutFactory COM helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
49
src/ClaudeDo.Installer/Core/ShortcutFactory.cs
Normal file
49
src/ClaudeDo.Installer/Core/ShortcutFactory.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.InteropServices.ComTypes;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
public static class ShortcutFactory
|
||||||
|
{
|
||||||
|
public static void CreateShortcut(string shortcutPath, string targetPath, string workingDir, string description)
|
||||||
|
{
|
||||||
|
var link = (IShellLink)new ShellLink();
|
||||||
|
link.SetPath(targetPath);
|
||||||
|
link.SetWorkingDirectory(workingDir);
|
||||||
|
link.SetDescription(description);
|
||||||
|
link.SetIconLocation(targetPath, 0);
|
||||||
|
|
||||||
|
var file = (IPersistFile)link;
|
||||||
|
file.Save(shortcutPath, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[Guid("00021401-0000-0000-C000-000000000046")]
|
||||||
|
private class ShellLink { }
|
||||||
|
|
||||||
|
[ComImport]
|
||||||
|
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
[Guid("000214F9-0000-0000-C000-000000000046")]
|
||||||
|
private interface IShellLink
|
||||||
|
{
|
||||||
|
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, IntPtr pfd, int fFlags);
|
||||||
|
void GetIDList(out IntPtr ppidl);
|
||||||
|
void SetIDList(IntPtr pidl);
|
||||||
|
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
||||||
|
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||||
|
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
||||||
|
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||||
|
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder 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([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
||||||
|
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||||
|
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
||||||
|
void Resolve(IntPtr hwnd, int fFlags);
|
||||||
|
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.InteropServices.ComTypes;
|
|
||||||
using System.Text;
|
|
||||||
using ClaudeDo.Installer.Core;
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
namespace ClaudeDo.Installer.Steps;
|
namespace ClaudeDo.Installer.Steps;
|
||||||
@@ -23,7 +20,7 @@ public sealed class CreateShortcutsStep : IInstallStep
|
|||||||
"Programs");
|
"Programs");
|
||||||
Directory.CreateDirectory(startMenuDir);
|
Directory.CreateDirectory(startMenuDir);
|
||||||
var startMenuPath = Path.Combine(startMenuDir, "ClaudeDo.lnk");
|
var startMenuPath = Path.Combine(startMenuDir, "ClaudeDo.lnk");
|
||||||
CreateShortcut(startMenuPath, appExe, workingDir, "ClaudeDo Task Manager");
|
ShortcutFactory.CreateShortcut(startMenuPath, appExe, workingDir, "ClaudeDo Task Manager");
|
||||||
progress.Report($"Created Start Menu shortcut: {startMenuPath}");
|
progress.Report($"Created Start Menu shortcut: {startMenuPath}");
|
||||||
|
|
||||||
// Desktop shortcut (optional)
|
// Desktop shortcut (optional)
|
||||||
@@ -32,7 +29,7 @@ public sealed class CreateShortcutsStep : IInstallStep
|
|||||||
var desktopPath = Path.Combine(
|
var desktopPath = Path.Combine(
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory),
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory),
|
||||||
"ClaudeDo.lnk");
|
"ClaudeDo.lnk");
|
||||||
CreateShortcut(desktopPath, appExe, workingDir, "ClaudeDo Task Manager");
|
ShortcutFactory.CreateShortcut(desktopPath, appExe, workingDir, "ClaudeDo Task Manager");
|
||||||
progress.Report($"Created Desktop shortcut: {desktopPath}");
|
progress.Report($"Created Desktop shortcut: {desktopPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,48 +41,5 @@ public sealed class CreateShortcutsStep : IInstallStep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CreateShortcut(string shortcutPath, string targetPath, string workingDir, string description)
|
|
||||||
{
|
|
||||||
var link = (IShellLink)new ShellLink();
|
|
||||||
link.SetPath(targetPath);
|
|
||||||
link.SetWorkingDirectory(workingDir);
|
|
||||||
link.SetDescription(description);
|
|
||||||
link.SetIconLocation(targetPath, 0);
|
|
||||||
|
|
||||||
var file = (IPersistFile)link;
|
|
||||||
file.Save(shortcutPath, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region COM Interop for IShellLink
|
|
||||||
|
|
||||||
[ComImport]
|
|
||||||
[Guid("00021401-0000-0000-C000-000000000046")]
|
|
||||||
private class ShellLink { }
|
|
||||||
|
|
||||||
[ComImport]
|
|
||||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
[Guid("000214F9-0000-0000-C000-000000000046")]
|
|
||||||
private interface IShellLink
|
|
||||||
{
|
|
||||||
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, IntPtr pfd, int fFlags);
|
|
||||||
void GetIDList(out IntPtr ppidl);
|
|
||||||
void SetIDList(IntPtr pidl);
|
|
||||||
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
|
||||||
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
|
||||||
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
|
||||||
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
|
||||||
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder 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([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
|
||||||
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
|
||||||
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
|
||||||
void Resolve(IntPtr hwnd, int fFlags);
|
|
||||||
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
tests/ClaudeDo.Installer.Tests/ShortcutFactoryTests.cs
Normal file
25
tests/ClaudeDo.Installer.Tests/ShortcutFactoryTests.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.IO;
|
||||||
|
using ClaudeDo.Installer.Core;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Installer.Tests;
|
||||||
|
|
||||||
|
public class ShortcutFactoryTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CreateShortcut_writes_lnk_file()
|
||||||
|
{
|
||||||
|
var dir = Path.Combine(Path.GetTempPath(), "cdshortcut-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var target = Path.Combine(dir, "fake.exe");
|
||||||
|
File.WriteAllText(target, "");
|
||||||
|
var lnk = Path.Combine(dir, "x.lnk");
|
||||||
|
|
||||||
|
ShortcutFactory.CreateShortcut(lnk, target, dir, "desc");
|
||||||
|
|
||||||
|
Assert.True(File.Exists(lnk));
|
||||||
|
}
|
||||||
|
finally { Directory.Delete(dir, recursive: true); }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user