Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add WSL extension to Dev Home #3365

Merged
merged 16 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
24 changes: 24 additions & 0 deletions DevHome.sln
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{E768
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevHome.Services.DesiredStateConfiguration", "services\DevHome.Services.DesiredStateConfiguration\DevHome.Services.DesiredStateConfiguration.csproj", "{D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WSLExtension", "WSLExtension", "{73D1E84F-56CC-412B-BF2B-FA692BF6B396}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WSLExtension", "extensions\WSLExtension\WSLExtension.csproj", "{B6153EEA-EADE-4BAA-B47D-6B48205C6696}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug_FailFast|arm64 = Debug_FailFast|arm64
Expand Down Expand Up @@ -1057,6 +1061,24 @@ Global
{D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Release|x64.Build.0 = Release|x64
{D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Release|x86.ActiveCfg = Release|x86
{D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F}.Release|x86.Build.0 = Release|x86
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|arm64.ActiveCfg = Debug|ARM64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|arm64.Build.0 = Debug|ARM64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|x64.ActiveCfg = Debug|x64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|x64.Build.0 = Debug|x64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|x86.ActiveCfg = Debug|x86
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug_FailFast|x86.Build.0 = Debug|x86
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|arm64.ActiveCfg = Debug|ARM64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|arm64.Build.0 = Debug|ARM64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|x64.ActiveCfg = Debug|x64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|x64.Build.0 = Debug|x64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|x86.ActiveCfg = Debug|x86
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Debug|x86.Build.0 = Debug|x86
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|arm64.ActiveCfg = Release|ARM64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|arm64.Build.0 = Release|ARM64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|x64.ActiveCfg = Release|x64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|x64.Build.0 = Release|x64
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|x86.ActiveCfg = Release|x86
{B6153EEA-EADE-4BAA-B47D-6B48205C6696}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1117,6 +1139,8 @@ Global
{8FB1EF90-B693-4A2A-A7F2-44ECA499D769} = {E0A15760-487A-4CCB-8093-DE6FF3C4BC23}
{E768781A-D1F7-4C03-B46D-E76354FAB587} = {A972EC5B-FC61-4964-A6FF-F9633EB75DFD}
{D7A1C2CE-36B1-43A8-9BA6-1EE2CF24479F} = {E0A15760-487A-4CCB-8093-DE6FF3C4BC23}
{73D1E84F-56CC-412B-BF2B-FA692BF6B396} = {DCAF188B-60C3-4EDB-8049-BAA927FBCD7D}
{B6153EEA-EADE-4BAA-B47D-6B48205C6696} = {73D1E84F-56CC-412B-BF2B-FA692BF6B396}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {030B5641-B206-46BB-BF71-36FF009088FA}
Expand Down
7 changes: 7 additions & 0 deletions common/DevHome.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<None Remove="Environments\CustomControls\CardHeader.xaml" />
<None Remove="Environments\Styles\HorizontalCardStyles.xaml" />
<None Remove="Environments\Templates\EnvironmentsTemplates.xaml" />
<None Remove="Views\AdaptiveCardViews\EnvironmentsSettingsCard.xaml" />
<None Remove="Views\AddCreateButton.xaml" />
<None Remove="Views\AddCreateHyperlinkButton.xaml" />
<None Remove="Views\Banner.xaml" />
Expand Down Expand Up @@ -113,6 +114,12 @@
</Content>
</ItemGroup>

<ItemGroup>
<Page Update="Views\AdaptiveCardViews\EnvironmentsSettingsCard.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>

bbonaby marked this conversation as resolved.
Show resolved Hide resolved
<PropertyGroup>
<DefineConstants Condition="'$(BuildRing)'=='Canary'">$(DefineConstants);CANARY_BUILD</DefineConstants>
<DefineConstants Condition="'$(BuildRing)'=='Stable'">$(DefineConstants);STABLE_BUILD</DefineConstants>
Expand Down
8 changes: 8 additions & 0 deletions common/Environments/Styles/HorizontalCardStyles.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,12 @@
<Setter Property="Padding" Value="20,8,15,8" />
</Style>

<Style
x:Key="CardBodyLaunchButtonStyle"
TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Padding" Value="34,7,34,7" />
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
</Style>
</ResourceDictionary>
4 changes: 3 additions & 1 deletion common/Helpers/CommonConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ namespace DevHome.Common.Helpers;

public static class CommonConstants
{
public const string HyperVExtensionClassId = "F8B26528-976A-488C-9B40-7198FB425C9E";
public const string HyperVExtensionClassId = "F8B26528-976A-488C-9B40-7198FB425C9E";

public const string WSLExtensionClassId = "121253AB-BA5D-4E73-99CF-25A2CB8BF173";

public const string HyperVWindowsOptionalFeatureName = "Microsoft-Hyper-V";
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,26 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cardModels="using:DevHome.Common.DevHomeAdaptiveCards.CardModels"
xmlns:controls="using:CommunityToolkit.WinUI.Controls">
xmlns:local="using:DevHome.Common.Views.AdaptiveCardViews"
xmlns:converters="using:CommunityToolkit.WinUI.Converters">

<!--- Data templates for custom adaptive card renders can go here -->

<converters:EmptyObjectToObjectConverter x:Key="EmptyObjectToObjectConverter" NotEmptyValue="Visible" EmptyValue="Collapsed"/>

<DataTemplate x:Key="SettingsCardWithButtonThatLaunchesContentDialog" x:DataType="cardModels:DevHomeSettingsCard">
<ItemContainer AutomationProperties.Name="{Binding Header}">
<controls:SettingsCard
<local:EnvironmentsSettingsCard
x:Name="SettingsCardWithButtonToLaunchContentDialog"
Description="{Binding Description}"
Header="{Binding Header}"
HeaderIcon="{Binding HeaderIconImage}">
<Button
x:Name="LaunchDialog"
Visibility="{Binding NonSubmitActionElement, Converter={StaticResource EmptyObjectToObjectConverter}}"
Content="{Binding NonSubmitActionElement.ActionText}"
Command="{Binding NonSubmitActionElement.InvokeActionCommand}"
CommandParameter="{Binding ElementName=LaunchDialog}" />
</controls:SettingsCard>
</local:EnvironmentsSettingsCard>
</ItemContainer>
</DataTemplate>

Expand Down
20 changes: 20 additions & 0 deletions common/Views/AdaptiveCardViews/EnvironmentsSettingsCard.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<ctControls:SettingsCard
x:Class="DevHome.Common.Views.AdaptiveCardViews.EnvironmentsSettingsCard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ctControls="using:CommunityToolkit.WinUI.Controls"
mc:Ignorable="d">

<!--
Override SettingsCard default resources. This is used because the default
SettingsCardHeaderIconMaxSize value for settings cards is 20. This makes
the icons sent from extensions in the creation flow appear too small and pixelated.
So we override it here, to make sure they are bigger.
-->
<ctControls:SettingsCard.Resources>
<x:Double x:Key="SettingsCardHeaderIconMaxSize">40</x:Double>
bbonaby marked this conversation as resolved.
Show resolved Hide resolved
</ctControls:SettingsCard.Resources>
</ctControls:SettingsCard>
14 changes: 14 additions & 0 deletions common/Views/AdaptiveCardViews/EnvironmentsSettingsCard.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using CommunityToolkit.WinUI.Controls;

namespace DevHome.Common.Views.AdaptiveCardViews;

public sealed partial class EnvironmentsSettingsCard : SettingsCard
{
public EnvironmentsSettingsCard()
{
this.InitializeComponent();
}
}
36 changes: 36 additions & 0 deletions extensions/WSLExtension/ClassExtensions/IHostExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace WSLExtension.ClassExtensions;

public static class IHostExtensions
bbonaby marked this conversation as resolved.
Show resolved Hide resolved
{
/// <inheritdoc cref="ActivatorUtilities.CreateInstance(IServiceProvider, Type, object[])"/>
public static T CreateInstance<T>(this IHost host, params object[] parameters)
{
return ActivatorUtilities.CreateInstance<T>(host.Services, parameters);
}

/// <summary>
/// Gets the service object for the specified type, or throws an exception
/// if type was not registered.
/// </summary>
/// <typeparam name="T">Service type</typeparam>
/// <param name="host">Host object</param>
/// <returns>Service object</returns>
/// <exception cref="ArgumentException">Throw an exception if the specified
/// type is not registered</exception>
public static T GetService<T>(this IHost host)
where T : class
{
if (host.Services.GetService(typeof(T)) is not T service)
{
throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices.");
}

return service;
}
}
13 changes: 13 additions & 0 deletions extensions/WSLExtension/ClassExtensions/ResourceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Windows.ApplicationModel.Resources;

namespace WSLExtension.ClassExtensions;

public static class ResourceExtensions
{
private static readonly ResourceLoader _resourceLoader = new();

public static string GetLocalized(this string resourceKey) => _resourceLoader.GetString(resourceKey);
}
34 changes: 34 additions & 0 deletions extensions/WSLExtension/ClassExtensions/ServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Windows.DevHome.SDK;
using WSLExtension.Contracts;
using WSLExtension.DevHomeProviders;
using WSLExtension.DistributionDefinitions;
using WSLExtension.Models;
using WSLExtension.Services;

namespace WSLExtension.ClassExtensions;

public static class ServiceExtensions
{
public static IServiceCollection AddWslExtensionServices(this IServiceCollection services)
{
// Services
services.AddSingleton<IWslServicesMediator, WslServicesMediator>();
services.AddSingleton<IDistributionDefinitionHelper, DistributionDefinitionHelper>();
services.AddSingleton<IStringResource, StringResource>();
services.AddSingleton<IComputeSystemProvider, WslProvider>();
services.AddSingleton<WslExtension>();
services.AddSingleton<IProcessCreator, ProcessCreator>();
services.AddSingleton<IWslManager, WslManager>();

// Factory delegate to create WslComputeSystems
services.AddSingleton<WslRegisteredDistributionFactory>(
serviceProvider => wslDistribution => ActivatorUtilities.CreateInstance<WslComputeSystem>(serviceProvider, wslDistribution));

return services;
}
}
14 changes: 14 additions & 0 deletions extensions/WSLExtension/ClassExtensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Globalization;

namespace WSLExtension.ClassExtensions;

public static class StringExtensions
{
public static string FormatArgs(this string source, params object[] args)
{
return string.Format(CultureInfo.InvariantCulture, source, args);
}
}
70 changes: 70 additions & 0 deletions extensions/WSLExtension/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace WSLExtension;

public static class Constants
{
#if CANARY_BUILD
public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome.Canary/Files/WslAssets/wslLinux.png";
#elif STABLE_BUILD
public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome/Files/WslAssets/wslLinux.png";
#else
public const string ExtensionIcon = "ms-resource://Microsoft.Windows.DevHome.Dev/Files/WslAssets/wslLinux.png";
#endif

// Common strings used for WSL extension
bbonaby marked this conversation as resolved.
Show resolved Hide resolved
public const string WslProviderDisplayName = "Microsoft WSL";
public const string WslProviderId = "Microsoft.WSL";
public const string WindowsTerminalShimExe = "wt.exe";
public const string WindowsTerminalPackageFamilyName = "Microsoft.WindowsTerminal_8wekyb3d8bbwe";
public const string WslExe = "wsl.exe";
public const string WslTemplateSubfolderName = "WslTemplates";

public const string DefaultWslLogoPath = @"ms-appx:///WslAssets/wslLinux.png";
public const string WslLogoPathFormat = @"ms-appx:///WslAssets/{0}";
public const string KnownDistributionsLocalYamlLocation = @"ms-appx:///DistributionDefinitions/DistributionDefinition.yaml";
public const string KnownDistributionsWebJsonLocation = @"https://aka.ms/wsldistributionsjson";

// Wsl registry location for registered distributions.
public const string WslRegistryLocation = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss";

// Wsl registry data names within a distribution location.
public const string PackageFamilyRegistryName = "PackageFamilyName";
public const string DistributionRegistryName = "DistributionName";
public const string DefaultDistributionRegistryName = "DefaultDistribution";
public const string WslVersion = "Version";
public const string WslState = "State";
public const int WslVersion1 = 1;
public const int WslVersion2 = 2;
public const int WslExeExitSuccess = 0;

// Launch terminal with specific profile and log the user into their home directory in the login shell
// Note: this opens a new terminal window in the UI
public static string LaunchDistributionInTerminalWithProfile { get; } = "--profile {0} -- wsl --shell-type login --cd ~ --distribution {1}";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT for this and the other strings. Explain what the placeholders are for. This way calling code does not need to look at the string.

Another option is to remove the properties all together and change these into methods. Then, the code can describe the format with parameters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it would make sense to make them into methods, because in the future when the WSL team provides us with apis then we'd need to remove/change those methods. Today we're using wsl.exe under the hood, but I've made it in a way where, when we get new apis, only the body of the methods that use these strings need to change, not the method themselves.

Ultimately if you typed in wsl --help in your command line window you'll see what these commands are used for but I can add it to the comments if you feel strongly about it.


// Launch without using a Terminal profile and log the user into their home directory using the login shell
// Note: this opens a new terminal window in the UI
public static string LaunchDistributionInTerminalWithNoProfile { get; } = "wsl --shell-type login --cd ~ --distribution {0}";

// Launch into the wsl process without terminal and log the user into their home directory using the login shell
// Note: this opens a new terminal window in the UI
public static string LaunchDistributionWithoutTerminal { get; } = "--shell-type login --cd ~ --distribution {0}";

// Arguments to unregister a wsl distribution from a machine using wsl.exe
public const string UnregisterDistributionArgs = "--unregister {0}";

// Arguments to terminate all wsl sessions for a specific distribution using wsl.exe
public const string TerminateDistributionArgs = "--terminate {0}";

// Arguments to download, install and register a wsl distribution using Terminal
// Note: this opens a new terminal window in the UI
public const string InstallDistributionWithTerminal = "wsl --install --distribution {0}";

// Arguments to download, install and register a wsl distribution without using terminal
// Note: this opens a cmd window in the UI
public const string InstallDistributionWithoutTerminal = "--install --distribution {0}";

// Arguments to list of all running distributions on a machine using wsl.exe
public const string ListAllRunningDistributions = "--list --running";
}
23 changes: 23 additions & 0 deletions extensions/WSLExtension/Contracts/IDistributionDefinitionHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using WSLExtension.DistributionDefinitions;

namespace WSLExtension.Contracts;

/// <summary>
/// Provides definition information about all the WSL distributions that can be found at
/// <see cref="Constants.KnownDistributionsWebJsonLocation"/>.
/// </summary>
public interface IDistributionDefinitionHelper
{
/// <summary>
/// Retrieves a list of objects that contain metadata about WSL distributions that can be
/// installed from the wsl.exe executable.
/// </summary>
/// <returns>
/// A Dictionary where the key is the name of the distribution and the value is its
/// <see cref="DistributionDefinition"/> metadata.
/// </returns>
public Task<Dictionary<string, DistributionDefinition>> GetDistributionDefinitionsAsync();
}
31 changes: 31 additions & 0 deletions extensions/WSLExtension/Contracts/IProcessCreator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using WSLExtension.Models;

namespace WSLExtension.Contracts;

/// <summary>
/// Abstraction used to create processes throughout the WSL extension.
bbonaby marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public interface IProcessCreator
{
/// <summary>
/// Creates and starts a new process without opening a new Window. Note:
bbonaby marked this conversation as resolved.
Show resolved Hide resolved
/// Execution does not block on Starting the process.
bbonaby marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="fileName">The name of the executable to start</param>
/// <param name="arguments">The arguments that will be passed to the executable at process startup</param>
public void CreateProcessWithWindow(string fileName, string arguments);

/// <summary>
/// Creates and starts a new process in a new Window. Note: execution is blocked
/// until the process exits.
/// </summary>
/// <param name="fileName">The name of the executable to start</param>
/// <param name="arguments">The arguments that will be passed to the executable at process startup</param>
/// <returns>The meta data associated with the exited process.
/// E.g StdOutput, StdError and its exit code.
/// </returns>
public WslProcessData CreateProcessWithoutWindow(string fileName, string arguments);
bbonaby marked this conversation as resolved.
Show resolved Hide resolved
}
9 changes: 9 additions & 0 deletions extensions/WSLExtension/Contracts/IStringResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace WSLExtension.Contracts;

public interface IStringResource
{
public string GetLocalized(string key, params object[] args);
}
Loading
Loading