diff --git a/QuickShell.Core.Tests/ShortcutLaunchExecutorTests.cs b/QuickShell.Core.Tests/ShortcutLaunchExecutorTests.cs
index 664f583..4388b9a 100644
--- a/QuickShell.Core.Tests/ShortcutLaunchExecutorTests.cs
+++ b/QuickShell.Core.Tests/ShortcutLaunchExecutorTests.cs
@@ -109,12 +109,20 @@ public void LaunchEntry_DoesNotOpenDevServerWhenOptedIn()
],
};
- var result = ShortcutLaunchExecutor.LaunchEntry(
- shortcut,
- shortcut.Launches[0],
- "wt",
- "default");
+ TerminalLauncher.StartProcessOverride = _ => true;
+ try
+ {
+ var result = ShortcutLaunchExecutor.LaunchEntry(
+ shortcut,
+ shortcut.Launches[0],
+ "wt",
+ "default");
- Assert.True(result.Dismiss);
+ Assert.True(result.Dismiss);
+ }
+ finally
+ {
+ TerminalLauncher.StartProcessOverride = null;
+ }
}
}
diff --git a/QuickShell.Core.Tests/TerminalLauncherArgsTests.cs b/QuickShell.Core.Tests/TerminalLauncherArgsTests.cs
index 037d288..408bef6 100644
--- a/QuickShell.Core.Tests/TerminalLauncherArgsTests.cs
+++ b/QuickShell.Core.Tests/TerminalLauncherArgsTests.cs
@@ -122,4 +122,27 @@ public void ToWslArguments_PrefersDistroFromWtCommandLineOverProfileName()
Assert.Contains("-d \"Ubuntu\"", args, StringComparison.Ordinal);
Assert.DoesNotContain("Dev Shell", args, StringComparison.Ordinal);
}
+
+ [Fact]
+ public void ToWslArguments_ParsesLongDistributionFlagFromWtCommandLine()
+ {
+ var shortcut = new TerminalShortcut { Directory = @"C:\Projects\App" };
+ var target = new LaunchTarget
+ {
+ Id = "wt:dev-shell",
+ DisplayName = "Dev Shell",
+ Kind = LaunchTargetKind.WindowsTerminal,
+ ProfileOrDistro = "Dev Shell",
+ WtCommandLine = "wsl.exe --distribution Debian",
+ };
+ var location = new WslPathResolver.WslLocation
+ {
+ LinuxPath = "/mnt/c/Projects/App",
+ };
+
+ var args = TerminalLauncherArgs.ToWslArguments(shortcut, target, location);
+
+ Assert.Contains("-d \"Debian\"", args, StringComparison.Ordinal);
+ Assert.DoesNotContain("Dev Shell", args, StringComparison.Ordinal);
+ }
}
diff --git a/QuickShell.Core.Tests/TerminalProfileIconResolverTests.cs b/QuickShell.Core.Tests/TerminalProfileIconResolverTests.cs
index 2fba3a9..0e32612 100644
--- a/QuickShell.Core.Tests/TerminalProfileIconResolverTests.cs
+++ b/QuickShell.Core.Tests/TerminalProfileIconResolverTests.cs
@@ -1,3 +1,4 @@
+using QuickShell.Models;
using QuickShell.Services;
namespace QuickShell.Core.Tests;
@@ -111,6 +112,48 @@ public void Resolve_ReturnsNullWhenPathDoesNotExist()
Assert.Null(resolved);
}
+ [Fact]
+ public void IsCmdPalGlyphIcon_AcceptsEmojiAndRejectsFilePaths()
+ {
+ Assert.True(TerminalProfileIconResolver.IsCmdPalGlyphIcon("🐧"));
+ Assert.True(TerminalProfileIconResolver.IsCmdPalGlyphIcon("\uE756"));
+ Assert.False(TerminalProfileIconResolver.IsCmdPalGlyphIcon(@"C:\Apps\wt\ProfileIcons\debian.png"));
+ Assert.False(TerminalProfileIconResolver.IsCmdPalGlyphIcon("ms-appx:///ProfileIcons/foo.png"));
+ }
+
+ [Fact]
+ public void IsWslProfile_DetectsWslCommandLine()
+ {
+ var profile = CreateProfile("Debian", "wsl.exe -d Debian");
+ Assert.True(TerminalLaunchGlyphs.IsWslProfile(profile));
+ }
+
+ [Fact]
+ public void GetForLaunch_WslProfile_UsesLinuxPenguinWhenOnlyPngIconExists()
+ {
+ var launch = new WorkspaceEntry
+ {
+ Id = "1",
+ Label = "Main",
+ Terminal = "wt",
+ WtProfile = "Debian",
+ };
+
+ var glyph = TerminalLaunchGlyphs.GetForLaunch(launch);
+ Assert.Equal(ShortcutGlyphs.Linux, glyph);
+ }
+
+ private static WtProfileInfo CreateProfile(string name, string commandline) => new()
+ {
+ Name = name,
+ Commandline = commandline,
+ SettingsPath = Path.Combine(Path.GetTempPath(), "settings.json"),
+ Source = TerminalSettingsSource.WindowsTerminal,
+ HostExecutable = "wt.exe",
+ IdPrefix = "wt",
+ SourceLabel = "Windows Terminal",
+ };
+
public void Dispose()
{
try
diff --git a/QuickShell.Core/Services/CompanionAppCatalog.cs b/QuickShell.Core/Services/CompanionAppCatalog.cs
index 50ede86..c9f2bf6 100644
--- a/QuickShell.Core/Services/CompanionAppCatalog.cs
+++ b/QuickShell.Core/Services/CompanionAppCatalog.cs
@@ -70,6 +70,12 @@ public static string GetDisplayName(string? executablePath)
return Definitions.First(definition => definition.Id == preset).Title;
}
+ public static string GetContextMenuIcon(string? executablePath)
+ {
+ _ = InferPresetFromPath(executablePath);
+ return ShortcutGlyphs.OpenCompanionApp;
+ }
+
public static bool TryApplyPreset(string presetId, out string? executablePath, out string arguments)
{
executablePath = null;
diff --git a/QuickShell.Core/Services/ShortcutGlyphs.cs b/QuickShell.Core/Services/ShortcutGlyphs.cs
index cc8ac44..ece6fcc 100644
--- a/QuickShell.Core/Services/ShortcutGlyphs.cs
+++ b/QuickShell.Core/Services/ShortcutGlyphs.cs
@@ -26,7 +26,18 @@ internal static class ShortcutGlyphs
public const string Add = "\uE710";
+ public const string Remove = "\uE738";
+
public const string Saved = "\uE73E";
public const string Workspace = "\uE8A7";
+
+ public const string Duplicate = "\uE8C8";
+
+ public const string CopyPath = "\uF413";
+
+ public const string OpenRepository = "\uE8A7";
+
+ /// Segoe MDL2 OpenWith — generic "launch/open application".
+ public const string OpenCompanionApp = "\uE7AC";
}
diff --git a/QuickShell.Core/Services/TerminalLaunchGlyphs.cs b/QuickShell.Core/Services/TerminalLaunchGlyphs.cs
index f94511e..58f48f0 100644
--- a/QuickShell.Core/Services/TerminalLaunchGlyphs.cs
+++ b/QuickShell.Core/Services/TerminalLaunchGlyphs.cs
@@ -13,38 +13,75 @@ public static string GetForShortcut(TerminalShortcut shortcut)
public static string GetForLaunch(WorkspaceEntry launch)
{
- if (TryGetProfileIcon(launch, out var profileIcon))
+ var profile = TerminalProfileResolver.ResolveForLaunch(launch);
+
+ if (TryGetProfileIcon(profile, out var profileIcon))
{
return profileIcon;
}
- return GetFallbackGlyph(launch);
+ return GetFallbackGlyph(launch, profile);
}
- private static bool TryGetProfileIcon(WorkspaceEntry launch, out string icon)
+ private static bool TryGetProfileIcon(WtProfileInfo? profile, out string icon)
{
icon = string.Empty;
- var profile = TerminalProfileResolver.ResolveForLaunch(launch);
if (profile is null)
{
return false;
}
+ if (IsWslProfile(profile))
+ {
+ return false;
+ }
+
var resolved = TerminalProfileIconResolver.ResolveEffectiveIcon(profile);
- if (string.IsNullOrWhiteSpace(resolved))
+ if (!TerminalProfileIconResolver.IsCmdPalGlyphIcon(resolved))
{
return false;
}
- icon = resolved;
+ icon = resolved!;
return true;
}
- private static string GetFallbackGlyph(WorkspaceEntry launch)
+ internal static bool IsWslProfile(WtProfileInfo profile)
{
+ if (profile.Commandline?.Contains("wsl.exe", StringComparison.OrdinalIgnoreCase) == true)
+ {
+ return true;
+ }
+
+ if (profile.ProfileSource?.Contains("WSL", StringComparison.OrdinalIgnoreCase) == true
+ || profile.ProfileSource?.Contains("Windows.Subsystem.Linux", StringComparison.OrdinalIgnoreCase) == true)
+ {
+ return true;
+ }
+
+ return IsLinuxDistroName(profile.Name);
+ }
+
+ private static bool IsWslProfile(string? terminal, string? profileName)
+ {
+ if (terminal?.Equals("wsl", StringComparison.OrdinalIgnoreCase) == true)
+ {
+ return true;
+ }
+
+ return IsLinuxDistroName(profileName);
+ }
+
+ private static string GetFallbackGlyph(WorkspaceEntry launch, WtProfileInfo? profile)
+ {
+ if (profile is not null && IsWslProfile(profile))
+ {
+ return ShortcutGlyphs.Linux;
+ }
+
var terminal = (launch.Terminal ?? "default").Trim().ToLowerInvariant();
- if (IsLinuxTarget(terminal, launch.WtProfile))
+ if (IsWslProfile(terminal, launch.WtProfile))
{
return ShortcutGlyphs.Linux;
}
@@ -60,22 +97,24 @@ private static string GetFallbackGlyph(WorkspaceEntry launch)
};
}
- private static bool IsLinuxTarget(string terminal, string? profile)
+ private static bool IsLinuxDistroName(string? value)
{
- if (terminal.Equals("wsl", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- if (string.IsNullOrWhiteSpace(profile))
+ if (string.IsNullOrWhiteSpace(value))
{
return false;
}
- return profile.Contains("ubuntu", StringComparison.OrdinalIgnoreCase)
- || profile.Contains("debian", StringComparison.OrdinalIgnoreCase)
- || profile.Contains("fedora", StringComparison.OrdinalIgnoreCase)
- || profile.Contains("linux", StringComparison.OrdinalIgnoreCase)
- || profile.Contains("wsl", StringComparison.OrdinalIgnoreCase);
+ return value.Contains("ubuntu", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("debian", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("fedora", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("linux", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("wsl", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("alpine", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("arch", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("kali", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("opensuse", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("suse", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("mint", StringComparison.OrdinalIgnoreCase)
+ || value.Contains("gentoo", StringComparison.OrdinalIgnoreCase);
}
}
diff --git a/QuickShell.Core/Services/TerminalLauncher.cs b/QuickShell.Core/Services/TerminalLauncher.cs
index 5d97047..6d44604 100644
--- a/QuickShell.Core/Services/TerminalLauncher.cs
+++ b/QuickShell.Core/Services/TerminalLauncher.cs
@@ -5,6 +5,8 @@ namespace QuickShell.Services;
internal static class TerminalLauncher
{
+ internal static Func? StartProcessOverride { get; set; }
+
public static void Open(
TerminalShortcut shortcut,
string terminalApplicationId,
@@ -58,7 +60,14 @@ public static void Open(
startInfo.Verb = "runas";
}
- if (Process.Start(startInfo) is null)
+ if (StartProcessOverride is { } startOverride)
+ {
+ if (!startOverride(startInfo))
+ {
+ throw new InvalidOperationException($"Failed to start {startInfo.FileName}.");
+ }
+ }
+ else if (Process.Start(startInfo) is null)
{
throw new InvalidOperationException($"Failed to start {startInfo.FileName}.");
}
diff --git a/QuickShell.Core/Services/TerminalProfileIconResolver.cs b/QuickShell.Core/Services/TerminalProfileIconResolver.cs
index 69953d5..2abf720 100644
--- a/QuickShell.Core/Services/TerminalProfileIconResolver.cs
+++ b/QuickShell.Core/Services/TerminalProfileIconResolver.cs
@@ -71,6 +71,28 @@ internal static class TerminalProfileIconResolver
return File.Exists(relativePath) ? relativePath : null;
}
+ ///
+ /// Command Palette icons render Segoe glyphs and emoji, not PNG paths or packaged URIs
+ /// resolved from Windows Terminal profile settings.
+ ///
+ public static bool IsCmdPalGlyphIcon(string? value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ {
+ return false;
+ }
+
+ if (value.StartsWith("ms-", StringComparison.OrdinalIgnoreCase)
+ || Path.IsPathRooted(value)
+ || value.Contains('\\', StringComparison.Ordinal)
+ || value.Contains('/', StringComparison.Ordinal))
+ {
+ return false;
+ }
+
+ return LooksLikeEmojiOrInlineGlyph(value);
+ }
+
private static IEnumerable GetIconCandidates(WtProfileInfo profile)
{
if (!string.IsNullOrWhiteSpace(profile.Icon))
diff --git a/QuickShell.Core/Services/WslPathResolver.cs b/QuickShell.Core/Services/WslPathResolver.cs
index e2cb19e..616d150 100644
--- a/QuickShell.Core/Services/WslPathResolver.cs
+++ b/QuickShell.Core/Services/WslPathResolver.cs
@@ -131,7 +131,13 @@ private static bool TryParseUncRemainder(string remainder, string fullUnc, out W
return null;
}
- const string marker = "-d ";
+ return ExtractFlagValue(commandLine, "-d ")
+ ?? ExtractFlagValue(commandLine, "--distribution ")
+ ?? ExtractFlagValue(commandLine, "--distribution=");
+ }
+
+ private static string? ExtractFlagValue(string commandLine, string marker)
+ {
var index = commandLine.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
if (index < 0)
{
@@ -144,8 +150,20 @@ private static bool TryParseUncRemainder(string remainder, string fullUnc, out W
return null;
}
+ if (remainder.StartsWith('"'))
+ {
+ var endQuote = remainder.IndexOf('"', 1);
+ return endQuote > 0 ? remainder[1..endQuote] : remainder.Trim('"');
+ }
+
+ if (remainder.StartsWith('\''))
+ {
+ var endQuote = remainder.IndexOf('\'', 1);
+ return endQuote > 0 ? remainder[1..endQuote] : remainder.Trim('\'');
+ }
+
var end = remainder.IndexOf(' ');
- return (end < 0 ? remainder : remainder[..end]).Trim('"');
+ return (end < 0 ? remainder : remainder[..end]).Trim();
}
private static string EscapeShell(string value) => value.Replace("\"", "\\\"");
diff --git a/QuickShell/Commands/WorkspaceUtilityCommands.cs b/QuickShell/Commands/WorkspaceUtilityCommands.cs
index 5581027..6a9029a 100644
--- a/QuickShell/Commands/WorkspaceUtilityCommands.cs
+++ b/QuickShell/Commands/WorkspaceUtilityCommands.cs
@@ -13,7 +13,7 @@ public CopyShortcutPathCommand(string shortcutId)
{
_shortcutId = shortcutId;
Name = "Copy path";
- Icon = new IconInfo("\uE8C8");
+ Icon = new IconInfo(ShortcutGlyphs.CopyPath);
}
public override CommandResult Invoke()
@@ -104,7 +104,8 @@ public OpenWorkspaceLinkCommand(string shortcutId, WorkspaceLinkKind kind)
WorkspaceLinkKind.Repo => "Open repository",
_ => "Open link",
};
- Icon = new IconInfo(kind == WorkspaceLinkKind.Repo ? "\uE737" : "\uE774");
+ Icon = new IconInfo(
+ kind == WorkspaceLinkKind.Repo ? ShortcutGlyphs.OpenRepository : "\uE774");
}
public override CommandResult Invoke()
@@ -133,7 +134,7 @@ public OpenCompanionAppCommand(TerminalShortcut shortcut)
{
_shortcutId = shortcut.Id;
Name = $"Open {CompanionAppCatalog.GetDisplayName(shortcut.CompanionAppPath)}";
- Icon = new IconInfo("\uE70F");
+ Icon = new IconInfo(CompanionAppCatalog.GetContextMenuIcon(shortcut.CompanionAppPath));
}
public override CommandResult Invoke()
diff --git a/QuickShell/Services/ShortcutContextCommands.cs b/QuickShell/Services/ShortcutContextCommands.cs
index 843ec3d..1928202 100644
--- a/QuickShell/Services/ShortcutContextCommands.cs
+++ b/QuickShell/Services/ShortcutContextCommands.cs
@@ -313,7 +313,7 @@ private static void AddFolderAndLinkCommands(List items, Ter
items.Add(new CommandContextItem(new CopyShortcutPathCommand(shortcut.Id))
{
Title = "Copy path",
- Icon = new IconInfo("\uE8C8"),
+ Icon = new IconInfo(ShortcutGlyphs.CopyPath),
#if CMDPAL_HOVER_ACTIONS
ShowInHoverActions = true,
HoverOrder = HoverOrderCopyPath,
@@ -338,7 +338,7 @@ private static void AddFolderAndLinkCommands(List items, Ter
items.Add(new CommandContextItem(new OpenWorkspaceLinkCommand(shortcut.Id, WorkspaceLinkKind.Repo))
{
Title = "Open repository",
- Icon = new IconInfo("\uE737"),
+ Icon = new IconInfo(ShortcutGlyphs.OpenRepository),
#if CMDPAL_HOVER_ACTIONS
ShowInHoverActions = true,
HoverOrder = HoverOrderRepo,
@@ -351,7 +351,7 @@ private static void AddFolderAndLinkCommands(List items, Ter
items.Add(new CommandContextItem(new OpenCompanionAppCommand(shortcut))
{
Title = $"Open {CompanionAppCatalog.GetDisplayName(shortcut.CompanionAppPath)}",
- Icon = new IconInfo("\uE70F"),
+ Icon = new IconInfo(CompanionAppCatalog.GetContextMenuIcon(shortcut.CompanionAppPath)),
#if CMDPAL_HOVER_ACTIONS
ShowInHoverActions = true,
HoverOrder = HoverOrderCompanionApp,