Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions QuickShell.Core.Tests/ShortcutLaunchExecutorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
23 changes: 23 additions & 0 deletions QuickShell.Core.Tests/TerminalLauncherArgsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
43 changes: 43 additions & 0 deletions QuickShell.Core.Tests/TerminalProfileIconResolverTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using QuickShell.Models;
using QuickShell.Services;

namespace QuickShell.Core.Tests;
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions QuickShell.Core/Services/CompanionAppCatalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions QuickShell.Core/Services/ShortcutGlyphs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/// <summary>Segoe MDL2 OpenWith — generic "launch/open application".</summary>
public const string OpenCompanionApp = "\uE7AC";
}
79 changes: 59 additions & 20 deletions QuickShell.Core/Services/TerminalLaunchGlyphs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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);
}
}
11 changes: 10 additions & 1 deletion QuickShell.Core/Services/TerminalLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace QuickShell.Services;

internal static class TerminalLauncher
{
internal static Func<ProcessStartInfo, bool>? StartProcessOverride { get; set; }

public static void Open(
TerminalShortcut shortcut,
string terminalApplicationId,
Expand Down Expand Up @@ -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}.");
}
Expand Down
22 changes: 22 additions & 0 deletions QuickShell.Core/Services/TerminalProfileIconResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,28 @@ internal static class TerminalProfileIconResolver
return File.Exists(relativePath) ? relativePath : null;
}

/// <summary>
/// Command Palette icons render Segoe glyphs and emoji, not PNG paths or packaged URIs
/// resolved from Windows Terminal profile settings.
/// </summary>
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<string> GetIconCandidates(WtProfileInfo profile)
{
if (!string.IsNullOrWhiteSpace(profile.Icon))
Expand Down
22 changes: 20 additions & 2 deletions QuickShell.Core/Services/WslPathResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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("\"", "\\\"");
Expand Down
7 changes: 4 additions & 3 deletions QuickShell/Commands/WorkspaceUtilityCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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));
Comment thread
tonythethompson marked this conversation as resolved.
}

public override CommandResult Invoke()
Expand Down
Loading
Loading