Files
LiveWall/docs/plugins/getting-started.md
T
2026-11-04 14:29:22 -06:00

5.8 KiB

Getting started

This walkthrough builds a plugin that draws a clock on the desktop, then adds a settings page and a tray command. Ten minutes, no LiveWall source needed.

1. Create the project

mkdir MyClock && cd MyClock
dotnet new classlib -n MyClock -f net8.0-windows

Edit MyClock.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>              <!-- overlays and pages are WPF elements -->
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <!-- Point this at LiveWall.Sdk.dll or its project. Private=false keeps the contract out of
         your output: LiveWall supplies it at run time. -->
    <ProjectReference Include="..\LiveWall.Sdk\LiveWall.Sdk.csproj">
      <Private>false</Private>
      <ExcludeAssets>runtime</ExcludeAssets>
    </ProjectReference>
  </ItemGroup>
</Project>

If you only have the shipped binaries, reference the DLL instead:

<Reference Include="LiveWall.Sdk">
  <HintPath>C:\path\to\LiveWall\LiveWall.Sdk.dll</HintPath>
  <Private>false</Private>
</Reference>

Never ship LiveWall.Sdk.dll in your plugin folder. LiveWall loads the contract itself; a second copy would give you a different ILiveWallPlugin type and the plugin would be rejected.

2. Write the plugin

ClockPlugin.cs:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using LiveWall.Sdk;

public sealed class ClockPlugin : ILiveWallPlugin
{
    private TextBlock? _clock;

    public void Initialize(IPluginContext context)
    {
        context.Log.Info("Clock plugin starting");

        context.Extensions.AddOverlay(new ClockOverlay(this));

        context.Extensions.AddCommand(new PluginCommand("clock.say", "Clock: say hello",
            cmd => cmd.Host.ShowNotification("Clock", "Hello!"))
        {
            Category = "Clock"
        });

        context.Extensions.AddSettingsPage(new ClockSettingsPage(context));
    }

    public void Shutdown() => _clock = null;

    private sealed class ClockOverlay : IWallpaperOverlay
    {
        private readonly ClockPlugin _plugin;
        public ClockOverlay(ClockPlugin plugin) => _plugin = plugin;

        public string Name => "Clock";

        public FrameworkElement CreateView(OverlayContext context)
        {
            _plugin._clock = new TextBlock
            {
                Text = "12:00",
                FontFamily = new FontFamily("Segoe UI Light"),
                FontSize = 44,
                Foreground = Brushes.White,
                HorizontalAlignment = HorizontalAlignment.Right,
                Margin = new Thickness(0, 24, 32, 0)
            };
            return _plugin._clock;
        }

        public void Update(OverlayState state) { /* wallpaper changed - optional */ }
    }

    private sealed class ClockSettingsPage : IPluginSettingsPage
    {
        private readonly IPluginContext _context;
        public ClockSettingsPage(IPluginContext context) => _context = context;

        public string Title => "Clock";
        public string Description => "Colour of the desktop clock.";

        public FrameworkElement CreateView()
        {
            var box = new TextBox { Text = _context.Storage.Get("colour", "#FFFFFF"), Width = 140 };
            box.TextChanged += (_, _) => _context.Storage.Set("colour", box.Text);
            return box;
        }
    }
}

plugin.json (copy it next to the assembly, and add <None Include="plugin.json" CopyToOutputDirectory="PreserveNewest" /> to the csproj):

{
  "id": "com.example.clock",
  "name": "Desktop Clock",
  "version": "1.0.0",
  "author": "Your name",
  "description": "Draws a clock on top of the wallpaper.",
  "apiVersion": "1.0",
  "entry": "MyClock.dll",
  "type": "ClockPlugin",
  "minHostVersion": "1.8.0",
  "permissions": ["DesktopOverlay", "SettingsUi", "Commands"]
}

3. Build and install

dotnet build -c Release
mkdir "%APPDATA%\LiveWall\plugins\com.example.clock"
copy bin\Release\net8.0-windows\MyClock.dll        "%APPDATA%\LiveWall\plugins\com.example.clock\"
copy bin\Release\net8.0-windows\plugin.json        "%APPDATA%\LiveWall\plugins\com.example.clock\"

Then in LiveWall:

  1. Plugins tab → tick Enable the plugin system → read the warning → accept.
  2. Press Rescan plugins. Desktop Clock appears with an unverified badge.
  3. Press Enable → the per-plugin warning appears, listing the hash and the three permissions → accept. The clock appears on your desktop.

The folder can be opened from the app: Plugins ▸ Open plugin folder.

4. Debugging

  • Everything you log goes to %APPDATA%\LiveWall\logs\livewall-YYYYMMDD.log, prefixed with your plugin id: [com.example.clock] Clock plugin starting.
  • Plugins ▸ Plugin log shows the same summary in the app, and Advanced ▸ Diagnostics includes a — Plugins — section with each plugin's state, hash and permissions.
  • Attach a debugger with Debug ▸ Attach to Process ▸ LiveWall.exe. Your plugin's exceptions are visible in the Visual Studio exception window; Initialize is called on the UI thread.
  • Edit → Rescan reloads your assembly without restarting LiveWall (the collectible load context really does unload — see troubleshooting.md).
  • After changing code, rebuild, overwrite the DLL, and press Disable then Enable (or Rescan).

5. Next steps

  • extension-points.md — every extension point, with the airspace and threading caveats you will hit as soon as you do something real.
  • api-reference.md — the complete surface.
  • examples.md — recipes: custom renderer, source resolver, playlist provider.