using System.Windows;
namespace LiveWall.Sdk;
/// Version of the plugin API. Bumped when contracts change in an incompatible way.
public static class PluginApi
{
/// Current contract version. Plugins must declare a compatible version in plugin.json.
public const string Version = "1.0";
/// Host application version this SDK shipped with (informational).
public const string HostVersion = "1.8.4";
}
///
/// What a plugin is allowed to do, declared in its plugin.json and shown to the user before
/// the plugin is enabled. These are declarations, not a sandbox: a plugin runs inside LiveWall's
/// process with full .NET rights. They exist so the user knows what they are agreeing to.
///
[Flags]
public enum PluginPermission
{
None = 0,
/// Read the current wallpaper, playback state and a snapshot of the settings.
ReadWallpaper = 1 << 0,
/// Play / pause / next / set the wallpaper.
ControlPlayback = 1 << 1,
/// Draw a layer on top of the wallpaper (clocks, visualisers, badges…).
DesktopOverlay = 1 << 2,
/// Contribute pages to the LiveWall settings window.
SettingsUi = 1 << 3,
/// Contribute commands the user can run from the app.
Commands = 1 << 4,
/// Contribute entries to the tray menu.
TrayMenu = 1 << 5,
/// Register global hotkeys.
Hotkeys = 1 << 6,
/// Read and write files (the plugin's data folder, and anything the user points it at).
FileSystem = 1 << 7,
/// Make outbound network requests.
Network = 1 << 8,
/// Supply a wallpaper renderer for sources it claims (overrides the built-in players).
Renderer = 1 << 9,
/// Rewrite or veto wallpaper requests before the engine applies them.
Filters = 1 << 10,
All = ReadWallpaper | ControlPlayback | DesktopOverlay | SettingsUi | Commands | TrayMenu |
Hotkeys | FileSystem | Network | Renderer | Filters
}
/// Human readable description of what every permission means (used by the warning dialog).
public static class PluginPermissionInfo
{
public static string Describe(PluginPermission permission) => permission switch
{
PluginPermission.ReadWallpaper => "Read your current wallpaper, playback state and settings",
PluginPermission.ControlPlayback => "Start, stop, skip and replace your wallpaper",
PluginPermission.DesktopOverlay => "Draw content on top of your desktop wallpaper",
PluginPermission.SettingsUi => "Add pages to the LiveWall settings window",
PluginPermission.Commands => "Add commands to LiveWall",
PluginPermission.TrayMenu => "Add entries to the LiveWall tray menu",
PluginPermission.Hotkeys => "Register system-wide keyboard shortcuts",
PluginPermission.FileSystem => "Read and write files on your computer",
PluginPermission.Network => "Connect to the internet",
PluginPermission.Renderer => "Take over wallpaper playback with its own renderer",
PluginPermission.Filters => "Rewrite or block wallpaper changes",
_ => permission.ToString()
};
/// Expands a flag set into individual descriptions.
public static IReadOnlyList<(PluginPermission Permission, string Description)> Expand(PluginPermission permissions)
{
var list = new List<(PluginPermission, string)>();
foreach (PluginPermission p in Enum.GetValues())
{
if (p is PluginPermission.None or PluginPermission.All) continue;
if (permissions.HasFlag(p)) list.Add((p, Describe(p)));
}
return list;
}
}
/// Everything the host knows about a plugin. Filled in by LiveWall before Initialize.
public sealed class PluginInfo
{
public string Id { get; init; } = "";
public string Name { get; init; } = "";
public string Version { get; init; } = "";
public string Author { get; init; } = "";
public string Description { get; init; } = "";
public string Homepage { get; init; } = "";
public string ApiVersion { get; init; } = "";
public PluginPermission Permissions { get; init; }
/// Absolute path of the plugin folder.
public string Folder { get; init; } = "";
/// Absolute path of the plugin assembly.
public string AssemblyPath { get; init; } = "";
/// SHA-256 of the plugin assembly (lower case hex).
public string Sha256 { get; init; } = "";
///
/// True when the loaded assembly hash matches the hash the user trusted on this machine, or the
/// publisher ships it in LiveWall's built-in trusted list. Unverified plugins run with a warning.
///
public bool Verified { get; init; }
}
/// The plugin entry point. One public class implementing this per assembly.
public interface ILiveWallPlugin
{
///
/// Called once, on the UI thread, after the user enabled the plugin. Register your extensions
/// and subscribe to events here. Exceptions are caught, logged and disable the plugin.
///
void Initialize(IPluginContext context);
/// Called when the plugin is disabled, reloaded or the app is closing.
void Shutdown();
}
/// Everything a plugin can reach. One instance per plugin.
public interface IPluginContext
{
PluginInfo Info { get; }
/// Writes to the LiveWall log next to the host's own messages.
IPluginLog Log { get; }
/// Persistent key/value storage owned by the plugin (JSON file in its data folder).
IPluginStorage Storage { get; }
/// Read-only snapshot of the user's configuration.
ISettingsSnapshot Settings { get; }
/// Actions a plugin may trigger in the host.
IHostActions Host { get; }
/// Type based publish/subscribe bus.
IPluginEvents Events { get; }
/// Registers extension points (overlays, pages, commands, renderers, filters, hotkeys).
IPluginRegistrar Extensions { get; }
/// Cancelled when the plugin is being shut down. Pass it to your async work.
CancellationToken ShutdownToken { get; }
}
/// Logging façade. Everything lands in %APPDATA%\LiveWall\logs.
public interface IPluginLog
{
void Debug(string message);
void Info(string message);
void Warn(string message);
void Error(string message, Exception? exception = null);
}
///
/// Per plugin JSON storage. Values are serialised with System.Text.Json, so any POCO works.
///
public interface IPluginStorage
{
/// The plugin's private data folder (created on demand).
string DataFolder { get; }
T? Get(string key, T? fallback = default);
void Set(string key, T value);
bool Remove(string key);
IReadOnlyCollection Keys { get; }
/// Absolute path inside the data folder; the directory is created for you.
string PathFor(string fileName);
/// Writes pending changes immediately (normally done automatically).
void Flush();
}
/// Read-only view of the live settings.
public interface ISettingsSnapshot
{
Guid SnapshotId { get; }
string SourceKind { get; } // "Video", "AnimatedGif", "Image", "YouTube", "WebPage"
string SourceLocation { get; } // file path or URL
bool Muted { get; }
double Volume { get; } // 0–100
double Speed { get; } // 0.25–4
bool Loop { get; }
bool InteractiveMode { get; }
bool Paused { get; }
int DimPercent { get; }
string DimColor { get; }
IReadOnlyList DisplayDevices { get; }
}
/// The current wallpaper, as seen by plugins.
public sealed class WallpaperState
{
public string Kind { get; init; } = "";
public string Source { get; init; } = "";
public string Title { get; init; } = "";
public bool IsPlaying { get; init; }
public bool IsPaused { get; init; }
public bool IsInteractive { get; init; }
public bool SuppliedByPlugin { get; init; }
public string RendererName { get; init; } = "";
}
/// Host actions. Each one is a no-op when the plugin lacks the permission for it.
public interface IHostActions
{
WallpaperState Current { get; }
/// Replaces the wallpaper (needs ).
void SetWallpaper(WallpaperRequest request);
void Play();
void Pause();
void TogglePause();
void NextWallpaper();
void ReloadWallpaper();
void Reattach();
/// Opens the LiveWall settings window.
void OpenSettings();
/// Status line in the settings window.
void SetStatus(string text);
/// Balloon notification (respects the user's notification setting).
void ShowNotification(string title, string message);
/// Shows or hides the desktop overlay layer for this plugin.
void SetOverlayVisible(bool visible);
}
/// A wallpaper a plugin wants to show.
public sealed class WallpaperRequest
{
/// "Video", "AnimatedGif", "Image", "YouTube", "WebPage" or "Custom".
public string Kind { get; set; } = "Custom";
/// File path, URL or any string your renderer interprets.
public string Source { get; set; } = "";
public string Title { get; set; } = "";
/// Free-form data for your own renderer (survives filters, not persisted).
public Dictionary Metadata { get; } = new(StringComparer.OrdinalIgnoreCase);
public WallpaperRequest Clone()
{
var clone = new WallpaperRequest { Kind = Kind, Source = Source, Title = Title };
foreach (var pair in Metadata) clone.Metadata[pair.Key] = pair.Value;
return clone;
}
public override string ToString() => $"{Kind}:{Source}";
}
/// Registers extension points. All calls are thread safe and may happen in Initialize.
public interface IPluginRegistrar
{
/// Draws above the wallpaper (needs ).
void AddOverlay(IWallpaperOverlay overlay);
void RemoveOverlay(IWallpaperOverlay overlay);
/// Adds a page to the Plugins tab (needs ).
void AddSettingsPage(IPluginSettingsPage page);
/// Adds a command to the app and the tray menu.
void AddCommand(PluginCommand command);
/// Adds a wallpaper filter (needs ).
void AddFilter(IWallpaperFilter filter);
/// Takes over rendering of sources it claims (needs ).
void AddRenderer(IWallpaperRendererPlugin renderer);
/// Registers a global hotkey such as "Ctrl+Alt+K" (needs ).
void AddHotkey(PluginHotkey hotkey);
}
/// An entry in the app's plugin command list and the tray menu.
public sealed class PluginCommand
{
public PluginCommand(string id, string title, Action execute)
{
Id = id;
Title = title;
Execute = execute;
}
public string Id { get; }
public string Title { get; set; }
/// Optional grouping label, e.g. "Wallpaper".
public string Category { get; set; } = "";
/// Return false to disable the entry (evaluated when the menu opens).
public Func? CanExecute { get; set; }
internal Action Execute { get; }
public void Run(PluginCommandContext context) => Execute(context);
}
/// Passed to .
public sealed class PluginCommandContext
{
public PluginCommandContext(IPluginContext plugin, IHostActions host)
{
Plugin = plugin;
Host = host;
}
public IPluginContext Plugin { get; }
public IHostActions Host { get; }
}
/// A global keyboard shortcut contributed by a plugin.
public sealed class PluginHotkey
{
public PluginHotkey(string id, string gesture, Action action)
{
Id = id;
Gesture = gesture;
Action = action;
}
public string Id { get; }
/// Combination such as "Ctrl+Alt+K", parsed like LiveWall's own hotkeys.
public string Gesture { get; }
public string Description { get; set; } = "";
public Action Action { get; }
}
/// Raised by plugins to inform others about something (or to listen).
public interface IPluginEvents
{
IDisposable Subscribe(Action handler) where TEvent : class;
void Publish(TEvent payload) where TEvent : class;
}