Create a minimal extension
An extension project for WindowSill is essentially a .NET Class Library. It can be used with Visual Studio, Visual Studio Code and JetBrains Rider.
This guide will walk you through the process of creating and configuring a WindowSill extension project using these editors.
Create a Class Library
To create a Class Library project with Visual Studio 2022:
- Create a new project, from Visual Studio's Start Page or by pressing
Ctrl+Shift+N
on the main window, and select the Class Library template. - Assign a name and location for your project.
- Choose .NET 9.0 or later, and click Create.
Adding the WindowSill.API NuGet package
The WindowSill.Api is installed via NuGet. To add it to your project:
- Open a command line prompt.
- Navigate to the directory containing the
*.csproj
file you created is (it is the project file create through the previous steps). - Execute the following command:
Replacedotnet add package WindowSill.Api --version VERSION_NUMBER
VERSION_NUMBER
with the version number of your choice. You can find version numbers on the NuGet website.
Understanding a WindowSill extension architecture
A WindowSill extension typically consists of a minimum of three components:
While additional components can be added to extend features like WindowSill's Activators, this tutorial will focus on the basics. In the following sections, we will implement a minimal command bar and explain how to create and implement each of the components mentioned above.
Create a .resw file
A RESW file is an XML-based resource file for WASDK, typically containing localized strings displayed to the user. For this sample extension, we will create a resource file with the following entries:
- DisplayName: The sill's title, displayed in the Settings dialog.
- CommandTitle: The name of a command that we will display in WindowSill.
To create a RESW file with Visual Studio 2022:
- In the Solution Explorer, press
Ctrl+Shift+A
and create a file with the.resw
extension in aStrings/en-US
folder. - Add the following entries, and save:
Create a sill
Add a new C# document named MySill.cs
to the project and add the following code:
using CommunityToolkit.Diagnostics;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using Windows.System;
using WindowSill.API;
namespace MyExtension;
[Export(typeof(ISill))] // Marks this class as a Sill to be discovered by MEF.
[Name("My Awesome Sill")] // A unique, internal name of the sill.
[Priority(Priority.Lowest)] // Optional. The priority of this sill relative to others. Lowest means it will be after all other sills.
[Order(Before = "My other sill", After = "Another sill")] // Optional. Helped break up ties with other sills by precising the ordering with other specific sill names.
public sealed class MySill
: ISillActivatedByProcess, // Indicates that this sill will be activated when a specific process or window gets the focus.
ISillListView // Indicates that this sill provides a list of commands (buttons, popup, etc.).
{
private readonly IProcessInteractionService _processInteractionService;
private WindowInfo? _activeProcessWindow;
[ImportingConstructor]
public MySill(IProcessInteractionService processInteractionService)
{
_processInteractionService = processInteractionService; // Import a MEF service allowing to interact with processes.
}
public string DisplayName => "/MyExtension/Misc/DisplayName".GetLocalizedString(); // Get the `DisplayName` from the resources.
public IconElement CreateIcon()
=> new FontIcon
{
Glyph = "\uED56" // Pizza icon
};
public ObservableCollection<SillListViewItem> ViewList
=> [
// A button command with a '+' icon and a localized tooltip that triggers `OnCommandButtonClickAsync` when clicked.
new SillListViewButtonItem(
'\uE710', // '+' icon
"/MyExtension/Misc/CommandTitle".GetLocalizedString(),
OnCommandButtonClickAsync),
];
public SillView? PlaceholderView => throw new NotImplementedException();
public SillSettingsView[]? SettingsViews => throw new NotImplementedException();
// This sill will be activated when a process detected by `NotepadProcessActivator` gets the focus.
public string[] ProcessActivatorTypeNames => [NotepadProcessActivator.InternalName];
public ValueTask OnActivatedAsync(string processActivatorTypeName, WindowInfo currentProcessWindow)
{
_activeProcessWindow = currentProcessWindow;
return ValueTask.CompletedTask;
}
public ValueTask OnDeactivatedAsync()
{
_activeProcessWindow = null;
return ValueTask.CompletedTask;
}
private async Task OnCommandButtonClickAsync()
{
// Sends Ctrl+N to the foreground window, which we expect to be Notepad.
Guard.IsNotNull(_activeProcessWindow);
await _processInteractionService.SimulateKeysOnWindow(
_activeProcessWindow,
VirtualKey.LeftControl,
VirtualKey.N);
}
}
Add a new C# document named NotepadProcessActivator.cs
to the project and add the following code:
using System.ComponentModel.Composition;
using System.Diagnostics;
using WindowSill.API;
namespace MyExtension;
[Export(typeof(ISillProcessActivator))]
[ActivationType(InternalName)]
internal sealed class NotepadProcessActivator : ISillProcessActivator
{
internal const string InternalName = "Notepad Activator";
public ValueTask<bool> GetShouldBeActivatedAsync(string applicationIdentifier, Process process, Version? version, CancellationToken cancellationToken)
{
return ValueTask.FromResult(applicationIdentifier.EndsWith("Notepad.exe"));
}
}
That's it! In the next section, we will cover how to build and debug this extension, and later one, we will understand what this code does and how to make your own sill.