-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathToolBase.cs
More file actions
181 lines (147 loc) · 5.55 KB
/
Copy pathToolBase.cs
File metadata and controls
181 lines (147 loc) · 5.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
ImageGlass.SDK – ImageGlass 10 Plugins Development Kit
Copyright (C) 2026 DUONG DIEU PHAP
Project homepage: https://imageglass.org
MIT License
*/
namespace ImageGlass.SDK.Tools;
/// <summary>
/// Base class for non-hosted external tools.
/// Handles named-pipe connection, message dispatch, and lifecycle.
/// Tool authors subclass this and override <c>OnXxx</c> methods.
/// </summary>
public abstract class ToolBase : IDisposable
{
private ToolClient? _client;
/// <summary>
/// When <c>true</c>, the SDK writes detailed IPC lifecycle traces
/// (pipe connect, every received message, dispatch enter/exit/failure)
/// to <see cref="DebugLog"/>. Use this to diagnose tools that appear
/// to "do nothing" — e.g. wire-format mismatches, swallowed exceptions
/// in event handlers, or pipe-connection failures.
/// <para>Set BEFORE calling <see cref="RunAsync"/>.</para>
/// </summary>
public bool EnableDebug { get; set; }
/// <summary>
/// Sink that receives debug trace lines when <see cref="EnableDebug"/>
/// is <c>true</c>. Typical implementations append to a log file or
/// forward to <c>Console.WriteLine</c>. Exceptions thrown by the sink
/// are swallowed so logging cannot crash the tool.
/// </summary>
public Action<string>? DebugLog { get; set; }
/// <summary>
/// Writes a trace line to <see cref="DebugLog"/> when
/// <see cref="EnableDebug"/> is enabled.
/// </summary>
internal void Trace(string msg)
{
if (!EnableDebug) return;
try { DebugLog?.Invoke(msg); } catch { }
}
/// <summary>
/// Unique tool identifier. Must match the <c>ToolId</c> registered in <c>igconfig.json</c>.
/// </summary>
public abstract string ToolId { get; }
/// <summary>
/// The tool client providing host API access.
/// </summary>
private protected ToolClient Client => _client
?? throw new InvalidOperationException("Tool not initialized.");
/// <summary>
/// Host API proxy — viewer, photo info, dialogs, etc.
/// </summary>
public IToolHostProxy HostApi => Client.HostApi;
/// <summary>
/// Data directory for this tool (set by host during INIT).
/// Tools can store local caches or state here.
/// </summary>
public string DataDirectory { get; internal set; } = string.Empty;
/// <summary>
/// Current theme information (updated by host events).
/// </summary>
public ThemeInfo? CurrentTheme { get; internal set; }
#region Lifecycle hooks
/// <summary>
/// Called after connection established and settings loaded.
/// </summary>
protected internal virtual Task OnInitializedAsync() => Task.CompletedTask;
/// <summary>
/// Called when the host requests tool execution.
/// </summary>
protected internal virtual Task OnExecuteAsync(CancellationToken ct) => Task.CompletedTask;
/// <summary>
/// Called when the host requests the tool to shut down.
/// </summary>
protected internal virtual Task OnShutdownAsync() => Task.CompletedTask;
#endregion
#region Event hooks
/// <summary>
/// Called when the current photo changes or is unloaded.
/// </summary>
protected internal virtual void OnPhotoChanged(PhotoChangedEventArgs e) { }
/// <summary>
/// Called when theme changes.
/// </summary>
protected internal virtual void OnThemeChanged(ThemeInfo theme) { }
/// <summary>
/// Called when the viewer color profile changes.
/// </summary>
protected internal virtual void OnColorProfileChanged() { }
/// <summary>
/// Called when language changes.
/// </summary>
protected internal virtual void OnLanguageChanged(LanguageChangedEventArgs e) { }
/// <summary>
/// Called when cursor moves over viewer (only if subscribed).
/// </summary>
protected internal virtual void OnPointerMoved(PointerEventArgs e) { }
/// <summary>
/// Called when click on viewer (only if subscribed).
/// </summary>
protected internal virtual void OnPointerPressed(PointerEventArgs e) { }
/// <summary>
/// Called when selection changes (only if subscribed).
/// </summary>
protected internal virtual void OnSelectionChanged(SelectionEventArgs? e) { }
/// <summary>
/// Called when animation frame changes (only if subscribed).
/// </summary>
protected internal virtual void OnFrameChanged(int frameIndex) { }
#endregion
#region Entry point
/// <summary>
/// Main entry point. Call from <c>Program.Main(args)</c>.
/// Connects to host, runs message loop until shutdown.
/// </summary>
public async Task RunAsync(string[] args)
{
var pipeName = ParsePipeNameFromArgs(args);
if (string.IsNullOrEmpty(pipeName))
{
throw new ArgumentException(
"Missing --pipe argument. This tool must be launched by ImageGlass.");
}
_client = new ToolClient(pipeName, this);
await _client.ConnectAndRunAsync().ConfigureAwait(false);
}
private static string? ParsePipeNameFromArgs(string[] args)
{
for (var i = 0; i < args.Length - 1; i++)
{
if (args[i].Equals("--pipe", StringComparison.OrdinalIgnoreCase))
{
return args[i + 1];
}
}
return null;
}
#endregion
/// <summary>
/// Disconnects from the host and releases the underlying pipe client.
/// </summary>
public virtual void Dispose()
{
_client?.Dispose();
GC.SuppressFinalize(this);
}
}