342 lines
12 KiB
C#
342 lines
12 KiB
C#
using System.Globalization;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Effects;
|
|
using System.Windows.Threading;
|
|
using LiveWall.Sdk;
|
|
|
|
namespace ClockOverlayPlugin;
|
|
|
|
/// <summary>
|
|
/// Example plugin that touches every extension point, in the order a real plugin would use them:
|
|
///
|
|
/// 1. a desktop overlay (a clock, drawn above the wallpaper),
|
|
/// 2. a settings page (cursor colour, 12/24 h, show wallpaper name),
|
|
/// 3. a command ("Say hello") that appears in the app and the tray menu,
|
|
/// 4. a wallpaper filter that can veto changes while "pin wallpaper" is on,
|
|
/// 5. a global hotkey (Ctrl+Alt+F9),
|
|
/// 6. storage (colour + flags survive restarts),
|
|
/// 7. event subscriptions (wallpaper changes, playback changes).
|
|
/// </summary>
|
|
public sealed class ClockPlugin : ILiveWallPlugin
|
|
{
|
|
private IPluginContext? _context;
|
|
private IDisposable? _wallpaperSub;
|
|
private IDisposable? _playbackSub;
|
|
|
|
private string _colour = "#FFFFFF";
|
|
private bool _use24Hour = true;
|
|
private bool _showWallpaperName = true;
|
|
private bool _pinWallpaper;
|
|
|
|
public void Initialize(IPluginContext context)
|
|
{
|
|
_context = context;
|
|
|
|
var log = context.Log;
|
|
log.Info($"Clock plugin starting (plugin API {PluginApi.Version}, host {PluginApi.HostVersion})");
|
|
|
|
// ---- 6. persisted settings -------------------------------------------------
|
|
_colour = context.Storage.Get("colour", "#FFFFFF") ?? "#FFFFFF";
|
|
_use24Hour = context.Storage.Get("use24Hour", true);
|
|
_showWallpaperName = context.Storage.Get("showWallpaperName", true);
|
|
_pinWallpaper = context.Storage.Get("pinWallpaper", false);
|
|
|
|
// ---- 1. overlay ------------------------------------------------------------
|
|
context.Extensions.AddOverlay(new ClockOverlay(this));
|
|
|
|
// ---- 2. settings page ------------------------------------------------------
|
|
context.Extensions.AddSettingsPage(new ClockSettingsPage(this, context));
|
|
|
|
// ---- 3. command ------------------------------------------------------------
|
|
context.Extensions.AddCommand(new PluginCommand("clock.hello", "Say hello from the clock plugin",
|
|
ctx =>
|
|
{
|
|
ctx.Host.ShowNotification("Clock plugin", "Hello! Your wallpaper is " + Describe(ctx.Host.Current));
|
|
ctx.Host.SetStatus("Clock plugin says hello");
|
|
})
|
|
{
|
|
Category = "Example",
|
|
CanExecute = () => _context != null
|
|
});
|
|
|
|
// ---- 4. wallpaper filter ---------------------------------------------------
|
|
context.Extensions.AddFilter(new PinWallpaperFilter(this));
|
|
|
|
// ---- 5. hotkey -------------------------------------------------------------
|
|
context.Extensions.AddHotkey(new PluginHotkey("clock.toggle24", "Ctrl+Alt+F9", () =>
|
|
{
|
|
_use24Hour = !_use24Hour;
|
|
Save();
|
|
_context?.Host.SetStatus(_use24Hour ? "Clock: 24 hour" : "Clock: 12 hour");
|
|
})
|
|
{
|
|
Description = "Clock: toggle 12/24 hour"
|
|
});
|
|
|
|
// ---- 7. events -------------------------------------------------------------
|
|
_wallpaperSub = context.Events.Subscribe<WallpaperChangedEvent>(e =>
|
|
context.Log.Info($"Wallpaper changed: {e.Kind} {e.Source} via {e.RendererName}"));
|
|
|
|
_playbackSub = context.Events.Subscribe<PlaybackChangedEvent>(e =>
|
|
context.Log.Debug($"Playback: playing={e.IsPlaying} paused={e.IsPaused}"));
|
|
|
|
context.Events.Subscribe<InteractionChangedEvent>(e =>
|
|
context.Log.Debug($"Interactive mode: {e.Interactive}"));
|
|
}
|
|
|
|
public void Shutdown()
|
|
{
|
|
_wallpaperSub?.Dispose();
|
|
_playbackSub?.Dispose();
|
|
_context = null;
|
|
}
|
|
|
|
// ------------------------------------------------------------------ used by the overlay / page
|
|
|
|
public string Colour => _colour;
|
|
public bool Use24Hour => _use24Hour;
|
|
public bool ShowWallpaperName => _showWallpaperName;
|
|
public bool PinWallpaper => _pinWallpaper;
|
|
|
|
public void SetColour(string colour)
|
|
{
|
|
_colour = colour;
|
|
Save();
|
|
Refresh();
|
|
}
|
|
|
|
public void SetUse24Hour(bool value)
|
|
{
|
|
_use24Hour = value;
|
|
Save();
|
|
Refresh();
|
|
}
|
|
|
|
public void SetShowWallpaperName(bool value)
|
|
{
|
|
_showWallpaperName = value;
|
|
Save();
|
|
Refresh();
|
|
}
|
|
|
|
public void SetPinWallpaper(bool value)
|
|
{
|
|
_pinWallpaper = value;
|
|
Save();
|
|
}
|
|
|
|
private void Save()
|
|
{
|
|
var storage = _context?.Storage;
|
|
if (storage == null) return;
|
|
|
|
storage.Set("colour", _colour);
|
|
storage.Set("use24Hour", _use24Hour);
|
|
storage.Set("showWallpaperName", _showWallpaperName);
|
|
storage.Set("pinWallpaper", _pinWallpaper);
|
|
}
|
|
|
|
private void Refresh() => _overlay?.Refresh();
|
|
|
|
private ClockOverlay? _overlay;
|
|
|
|
private void AttachOverlay(ClockOverlay overlay) => _overlay = overlay;
|
|
|
|
private static string Describe(WallpaperState state)
|
|
=> string.IsNullOrEmpty(state.Kind) ? "(unknown)" : $"{state.Kind}: {state.Source}";
|
|
|
|
// ------------------------------------------------------------------ the overlay itself
|
|
|
|
private sealed class ClockOverlay : IWallpaperOverlay
|
|
{
|
|
private readonly ClockPlugin _plugin;
|
|
private TextBlock? _clock;
|
|
private TextBlock? _source;
|
|
private DispatcherTimer? _timer;
|
|
|
|
public ClockOverlay(ClockPlugin plugin)
|
|
{
|
|
_plugin = plugin;
|
|
plugin.AttachOverlay(this);
|
|
}
|
|
|
|
public string Name => "Clock";
|
|
|
|
public FrameworkElement CreateView(OverlayContext context)
|
|
{
|
|
var panel = new StackPanel
|
|
{
|
|
HorizontalAlignment = HorizontalAlignment.Right,
|
|
VerticalAlignment = VerticalAlignment.Top,
|
|
Margin = new Thickness(0, 24, 32, 0),
|
|
IsHitTestVisible = false
|
|
};
|
|
|
|
_clock = new TextBlock
|
|
{
|
|
FontFamily = new FontFamily("Segoe UI Light"),
|
|
FontSize = 46,
|
|
Foreground = ParseBrush(_plugin.Colour),
|
|
TextAlignment = TextAlignment.Right,
|
|
Effect = new DropShadowEffect { BlurRadius = 18, ShadowDepth = 0, Opacity = 0.85, Color = Colors.Black }
|
|
};
|
|
|
|
_source = new TextBlock
|
|
{
|
|
FontSize = 12,
|
|
Foreground = new SolidColorBrush(Color.FromArgb(180, 255, 255, 255)),
|
|
TextAlignment = TextAlignment.Right,
|
|
Margin = new Thickness(0, 2, 2, 0),
|
|
Visibility = _plugin.ShowWallpaperName ? Visibility.Visible : Visibility.Collapsed
|
|
};
|
|
|
|
panel.Children.Add(_clock);
|
|
panel.Children.Add(_source);
|
|
|
|
_timer = new DispatcherTimer(DispatcherPriority.Background) { Interval = TimeSpan.FromSeconds(1) };
|
|
_timer.Tick += (_, _) => Tick();
|
|
_timer.Start();
|
|
|
|
Tick();
|
|
return panel;
|
|
}
|
|
|
|
public void Update(OverlayState state)
|
|
{
|
|
if (_source != null && _plugin.ShowWallpaperName)
|
|
{
|
|
_source.Text = string.IsNullOrEmpty(state.Kind) ? "" : $"{state.Kind} · {System.IO.Path.GetFileName(state.Source)}";
|
|
_source.Visibility = Visibility.Visible;
|
|
}
|
|
else if (_source != null)
|
|
{
|
|
_source.Visibility = Visibility.Collapsed;
|
|
}
|
|
}
|
|
|
|
public void OnVisibilityChanged(bool visible)
|
|
{
|
|
if (visible) _timer?.Start();
|
|
else _timer?.Stop();
|
|
}
|
|
|
|
public void Refresh()
|
|
{
|
|
if (_source != null)
|
|
_source.Visibility = _plugin.ShowWallpaperName ? Visibility.Visible : Visibility.Collapsed;
|
|
Tick();
|
|
}
|
|
|
|
private void Tick()
|
|
{
|
|
if (_clock == null) return;
|
|
|
|
var now = DateTime.Now;
|
|
string format = _plugin.Use24Hour ? "HH:mm" : "h:mm tt";
|
|
_clock.Text = now.ToString(format, CultureInfo.CurrentCulture);
|
|
_clock.Foreground = ParseBrush(_plugin.Colour);
|
|
}
|
|
|
|
private static Brush ParseBrush(string hex)
|
|
{
|
|
try { return (Brush)new BrushConverter().ConvertFromString(hex)!; }
|
|
catch { return Brushes.White; }
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------ the filter
|
|
|
|
private sealed class PinWallpaperFilter : IWallpaperFilter
|
|
{
|
|
private readonly ClockPlugin _plugin;
|
|
public PinWallpaperFilter(ClockPlugin plugin) => _plugin = plugin;
|
|
|
|
public int Priority => 0;
|
|
|
|
public WallpaperRequest Apply(WallpaperRequest request, IHostActions host)
|
|
{
|
|
if (!_plugin.PinWallpaper) return request;
|
|
|
|
// "Pin wallpaper" means: keep whatever is on screen, refuse the change.
|
|
host.SetStatus("Clock plugin: wallpaper changes are pinned");
|
|
return new WallpaperRequest
|
|
{
|
|
Kind = host.Current.Kind,
|
|
Source = host.Current.Source,
|
|
Title = "pinned"
|
|
};
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------ the settings page
|
|
|
|
private sealed class ClockSettingsPage : IPluginSettingsPage
|
|
{
|
|
private readonly ClockPlugin _plugin;
|
|
private readonly IPluginContext _context;
|
|
|
|
public ClockSettingsPage(ClockPlugin plugin, IPluginContext context)
|
|
{
|
|
_plugin = plugin;
|
|
_context = context;
|
|
}
|
|
|
|
public string Title => "Clock";
|
|
public string Description => "Position, colour and format of the clock overlay.";
|
|
|
|
public FrameworkElement CreateView()
|
|
{
|
|
var panel = new StackPanel();
|
|
|
|
panel.Children.Add(new TextBlock
|
|
{
|
|
Text = "Colour (any WPF colour syntax)",
|
|
Margin = new Thickness(0, 0, 0, 4),
|
|
Foreground = new SolidColorBrush(Color.FromRgb(0x8C, 0x95, 0xA5))
|
|
});
|
|
|
|
var colourBox = new TextBox { Text = _plugin.Colour, Width = 160, HorizontalAlignment = HorizontalAlignment.Left };
|
|
colourBox.TextChanged += (_, _) => _plugin.SetColour(colourBox.Text);
|
|
panel.Children.Add(colourBox);
|
|
|
|
var use24 = new CheckBox { Content = "24 hour clock", IsChecked = _plugin.Use24Hour, Margin = new Thickness(0, 12, 0, 0) };
|
|
use24.Checked += (_, _) => _plugin.SetUse24Hour(true);
|
|
use24.Unchecked += (_, _) => _plugin.SetUse24Hour(false);
|
|
panel.Children.Add(use24);
|
|
|
|
var showName = new CheckBox
|
|
{
|
|
Content = "Show the wallpaper name under the clock",
|
|
IsChecked = _plugin.ShowWallpaperName,
|
|
Margin = new Thickness(0, 8, 0, 0)
|
|
};
|
|
showName.Checked += (_, _) => _plugin.SetShowWallpaperName(true);
|
|
showName.Unchecked += (_, _) => _plugin.SetShowWallpaperName(false);
|
|
panel.Children.Add(showName);
|
|
|
|
var pin = new CheckBox
|
|
{
|
|
Content = "Pin the wallpaper (this plugin's filter blocks changes)",
|
|
IsChecked = _plugin.PinWallpaper,
|
|
Margin = new Thickness(0, 8, 0, 0)
|
|
};
|
|
pin.Checked += (_, _) => _plugin.SetPinWallpaper(true);
|
|
pin.Unchecked += (_, _) => _plugin.SetPinWallpaper(false);
|
|
panel.Children.Add(pin);
|
|
|
|
panel.Children.Add(new TextBlock
|
|
{
|
|
Text = "Storage file: " + _context.Storage.PathFor("storage.json"),
|
|
Margin = new Thickness(0, 16, 0, 0),
|
|
FontSize = 11,
|
|
TextWrapping = TextWrapping.Wrap,
|
|
Foreground = new SolidColorBrush(Color.FromRgb(0x8C, 0x95, 0xA5))
|
|
});
|
|
|
|
return panel;
|
|
}
|
|
}
|
|
}
|