File: src\Aspire.Tray\Linux\LinuxFolderApplication.cs
Web Access
Project: src\tests\Aspire.Tray.Tests\Aspire.Tray.Tests.csproj (Aspire.Tray.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
 
namespace Aspire.Tray;
 
/// <summary>
/// Known folder-capable applications, launched with arguments rather than shell commands.
/// </summary>
internal sealed record LinuxFolderApplication(string Title, string Executable, IReadOnlyList<string> DirectoryArguments)
{
    internal static IReadOnlyList<LinuxFolderApplication> Discover(string searchPath)
    {
        var directories = searchPath.Split(Path.PathSeparator).Where(Path.IsPathFullyQualified).ToArray();
        List<LinuxFolderApplication> applications = [];
        foreach (var (command, title, arguments) in s_applications)
        {
            var executable = directories.Select(directory => Path.Combine(directory, command))
                .FirstOrDefault(path => File.Exists(path) && (!OperatingSystem.IsLinux()
                    || (File.GetUnixFileMode(path) & (UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute)) != 0));
            if (executable is not null)
            {
                applications.Add(new(title, executable, arguments));
            }
        }
        return applications;
    }
 
    internal ProcessStartInfo CreateStartInfo(string directory)
    {
        directory = Path.GetFullPath(directory);
        if (!Directory.Exists(directory))
        {
            throw new DirectoryNotFoundException("The selected AppHost folder no longer exists.");
        }
        var start = new ProcessStartInfo(Executable)
        {
            UseShellExecute = false,
            WorkingDirectory = directory
        };
        foreach (var argument in DirectoryArguments)
        {
            start.ArgumentList.Add(argument);
        }
        // Some terminals require --working-directory=DIR. In either form DIR is
        // one argv value, never shell source or a terminal's "execute command".
        if (start.ArgumentList.Count > 0 && start.ArgumentList[^1].EndsWith('='))
        {
            start.ArgumentList[^1] += directory;
        }
        else
        {
            start.ArgumentList.Add(directory);
        }
        return start;
    }
 
    internal void Open(string directory)
    {
        using var process = Process.Start(CreateStartInfo(directory))
            ?? throw new InvalidOperationException($"Could not open {Title}.");
    }
 
    private static readonly (string Command, string Title, string[] Arguments)[] s_applications =
    [
        ("code", "Visual Studio Code", ["--"]),
        ("code-insiders", "Visual Studio Code - Insiders", ["--"]),
        ("codium", "VSCodium", ["--"]),
        ("rider", "Rider", []),
        ("subl", "Sublime Text", ["--"]),
        // Terminals can forward to an existing server, so WorkingDirectory alone
        // is insufficient. Supply the terminal's explicit directory option.
        // https://man.archlinux.org/man/gnome-terminal.1.en
        // https://sw.kovidgoyal.net/kitty/invocation/
        ("gnome-terminal", "GNOME Terminal", ["--working-directory"]),
        ("kgx", "GNOME Console", ["--working-directory"]),
        ("ptyxis", "Ptyxis", ["--new-window", "--working-directory"]),
        ("konsole", "Konsole", ["--workdir"]),
        ("xfce4-terminal", "Xfce Terminal", ["--working-directory"]),
        ("kitty", "Kitty", ["--directory"]),
        ("alacritty", "Alacritty", ["--working-directory"]),
        ("foot", "Foot", ["--working-directory"]),
        ("wezterm", "WezTerm", ["start", "--cwd"]),
        ("ghostty", "Ghostty", ["--working-directory="])
    ];
}