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; }