arcane-os 0.59.1 → 0.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +1 -1
- package/browser-runtime/ai/core-image.mjs +360 -0
- package/browser-runtime/ai/core-model-assets.mjs +152 -0
- package/browser-runtime/core/client.mjs +8 -0
- package/browser-runtime/core/contracts.mjs +5 -0
- package/docs/reference/core/arcane-ai-contracts.md +17 -1
- package/docs/reference/core/arcane-api.md +12 -1
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +20 -11
- package/docs/reference/core/reference/arcane-api/namespaces.md +14 -0
- package/docs/reference/core-client.md +67 -0
- package/docs/reference/core-native-packaging.md +66 -2
- package/docs/reference/inventory/package-api.json +2 -2
- package/docs/reference/inventory/runtime-modules.json +2 -2
- package/docs/reference/local-image-editing.md +79 -0
- package/docs/reference/local-image-generation.md +267 -0
- package/docs/reference/model-assets.md +147 -0
- package/docs/reference/runtime-modules.md +37 -5
- package/docs/reference/sdk-api.md +41 -5
- package/package.json +8 -3
- package/runtime/arcane/modules/ThemeBootstrap.js +217 -2
- package/schemas/arcane-app.schema.json +2 -2
- package/schemas/arcane-package.schema.json +2 -2
- package/src/core/hosts/windows/ArcaneHost.cs +211 -0
- package/src/core/hosts/windows/ArcaneLauncher.cs +5 -0
- package/src/core/services/image.mjs +563 -0
- package/src/core/services/local-ai.mjs +8 -3
- package/src/core/services/model-assets.mjs +267 -0
- package/src/import-map.mjs +2 -0
- package/src/local-ai/config.mjs +41 -1
- package/src/local-ai/dev.mjs +46 -9
- package/src/local-ai/image-editing.mjs +159 -0
- package/src/local-ai/image-library.mjs +985 -0
- package/src/local-ai/image-models/sd14.mjs +15 -0
- package/src/local-ai/image-png.mjs +74 -0
- package/src/local-ai/image-runtime.mjs +336 -0
- package/src/local-ai/image-worker.mjs +218 -0
- package/src/local-ai/install.mjs +326 -10
- package/src/native/portable-layout.mjs +28 -2
- package/src/native/windows-icon.mjs +582 -0
- package/src/native/windows-provider.mjs +60 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
using System;
|
|
2
2
|
using System.Collections;
|
|
3
3
|
using System.Collections.Generic;
|
|
4
|
+
using System.Drawing;
|
|
4
5
|
using System.IO;
|
|
5
6
|
using System.Runtime.InteropServices;
|
|
6
7
|
using System.Threading;
|
|
@@ -20,6 +21,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
20
21
|
public string OriginHost { get; set; }
|
|
21
22
|
public string ProfileDirectory { get; set; }
|
|
22
23
|
public string Title { get; set; }
|
|
24
|
+
public string IconPath { get; set; }
|
|
23
25
|
public string ClassicClientSource { get; set; }
|
|
24
26
|
public string CoreExecutable { get; set; }
|
|
25
27
|
public string CoreArguments { get; set; }
|
|
@@ -106,6 +108,8 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
106
108
|
private Task initialization;
|
|
107
109
|
private Task coreLifetime;
|
|
108
110
|
private Task shutdown;
|
|
111
|
+
private Task iconLoading;
|
|
112
|
+
private Icon applicationIcon;
|
|
109
113
|
private string injectedScript;
|
|
110
114
|
private Exception transportFailure;
|
|
111
115
|
private long generation;
|
|
@@ -162,6 +166,8 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
162
166
|
string profile = Path.GetFullPath(options.ProfileDirectory);
|
|
163
167
|
Uri origin = new UriBuilder(Uri.UriSchemeHttps, options.OriginHost).Uri;
|
|
164
168
|
Uri start = new Uri(origin, options.StartPath);
|
|
169
|
+
iconLoading = LoadApplicationIconAsync();
|
|
170
|
+
iconLoading.ContinueWith(ObserveUnexpectedFailure, TaskContinuationOptions.OnlyOnFaulted);
|
|
165
171
|
core = ArcaneCoreProcess.Start(options.CoreExecutable, options.CoreArguments,
|
|
166
172
|
options.CoreWorkingDirectory, ReceiveCoreMessage, DeliverDiagnostic, CoreFailed);
|
|
167
173
|
coreLifetime = ObserveCoreLifetimeAsync();
|
|
@@ -228,6 +234,18 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
228
234
|
{
|
|
229
235
|
if (documentGeneration != generation || closing)
|
|
230
236
|
throw new InvalidOperationException("The requesting document is no longer connected.");
|
|
237
|
+
if (requestId != null && EnvelopeString(envelope, "protocol") == "arcane/1"
|
|
238
|
+
&& EnvelopeString(envelope, "method") == "window.setTheme")
|
|
239
|
+
{
|
|
240
|
+
// This method belongs to this exact window, not the Core
|
|
241
|
+
// child or a machine-wide appearance service.
|
|
242
|
+
string response = WindowThemeResponse(envelope, requestId);
|
|
243
|
+
pending.Enqueue(
|
|
244
|
+
new CoreDelivery(documentGeneration, response)
|
|
245
|
+
);
|
|
246
|
+
ScheduleDelivery();
|
|
247
|
+
return Task.FromResult<object>(null);
|
|
248
|
+
}
|
|
231
249
|
Task accepted = core.SendAsync(json);
|
|
232
250
|
// Hold the same lock used by the reader so an immediate response
|
|
233
251
|
// cannot overtake its accepted transport correlation record.
|
|
@@ -237,6 +255,181 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
237
255
|
}
|
|
238
256
|
}
|
|
239
257
|
|
|
258
|
+
private async Task LoadApplicationIconAsync()
|
|
259
|
+
{
|
|
260
|
+
if (String.IsNullOrEmpty(options.IconPath)) return;
|
|
261
|
+
try
|
|
262
|
+
{
|
|
263
|
+
Icon loaded = await Task.Run(new Func<Icon>(ReadApplicationIcon));
|
|
264
|
+
if (closing)
|
|
265
|
+
{
|
|
266
|
+
loaded.Dispose();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
applicationIcon = loaded;
|
|
270
|
+
Icon = loaded;
|
|
271
|
+
}
|
|
272
|
+
catch (Exception error)
|
|
273
|
+
{
|
|
274
|
+
// Image codec availability is separate from application
|
|
275
|
+
// execution. Preserve ordinary startup and complete diagnostics.
|
|
276
|
+
object record = ArcaneHost.ErrorRecord(error, "ARCANE_WINDOW_ICON_UNAVAILABLE");
|
|
277
|
+
string diagnostic = ArcaneHost.Serializer().Serialize(record);
|
|
278
|
+
DeliverDiagnostic(diagnostic + Environment.NewLine);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private Icon ReadApplicationIcon()
|
|
283
|
+
{
|
|
284
|
+
string extension = Path.GetExtension(options.IconPath);
|
|
285
|
+
if (String.Equals(extension, ".ico", StringComparison.OrdinalIgnoreCase))
|
|
286
|
+
return new Icon(options.IconPath);
|
|
287
|
+
using (Bitmap bitmap = new Bitmap(options.IconPath))
|
|
288
|
+
{
|
|
289
|
+
IntPtr handle = bitmap.GetHicon();
|
|
290
|
+
try
|
|
291
|
+
{
|
|
292
|
+
using (Icon borrowed = System.Drawing.Icon.FromHandle(handle))
|
|
293
|
+
{
|
|
294
|
+
return (Icon)borrowed.Clone();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
finally
|
|
298
|
+
{
|
|
299
|
+
DestroyIcon(handle);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private string WindowThemeResponse(Dictionary<string, object> request, string id)
|
|
305
|
+
{
|
|
306
|
+
Dictionary<string, object> applied = new Dictionary<string, object>();
|
|
307
|
+
List<string> unsupported = new List<string>();
|
|
308
|
+
Dictionary<string, object> response = new Dictionary<string, object>
|
|
309
|
+
{
|
|
310
|
+
{ "protocol", "arcane/1" },
|
|
311
|
+
{ "type", "response" },
|
|
312
|
+
{ "id", id }
|
|
313
|
+
};
|
|
314
|
+
try
|
|
315
|
+
{
|
|
316
|
+
object parameters;
|
|
317
|
+
Dictionary<string, object> presentation = request.TryGetValue("parameters", out parameters)
|
|
318
|
+
? parameters as Dictionary<string, object> : null;
|
|
319
|
+
if (presentation == null) throw new ArgumentException("Window presentation must be an object.");
|
|
320
|
+
ApplyWindowColor(presentation, "backgroundColor", 35, applied, unsupported);
|
|
321
|
+
ApplyWindowColor(presentation, "textColor", 36, applied, unsupported);
|
|
322
|
+
response["ok"] = true;
|
|
323
|
+
response["result"] = new Dictionary<string, object>
|
|
324
|
+
{
|
|
325
|
+
{ "platform", "windows" },
|
|
326
|
+
{ "supported", applied.Count != 0 },
|
|
327
|
+
{ "applied", applied },
|
|
328
|
+
{ "unsupported", unsupported }
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
catch (Exception error)
|
|
332
|
+
{
|
|
333
|
+
Dictionary<string, object> record = (Dictionary<string, object>)ArcaneHost.ErrorRecord(error,
|
|
334
|
+
error is ArgumentException ? "INVALID_ARGUMENT" : "ARCANE_WINDOW_THEME_FAILED");
|
|
335
|
+
record["details"] = new Dictionary<string, object>
|
|
336
|
+
{
|
|
337
|
+
{ "applied", applied },
|
|
338
|
+
{ "unsupported", unsupported }
|
|
339
|
+
};
|
|
340
|
+
response["ok"] = false;
|
|
341
|
+
response["error"] = record;
|
|
342
|
+
}
|
|
343
|
+
return ArcaneHost.Serializer().Serialize(response);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private void ApplyWindowColor(Dictionary<string, object> presentation, string field, int attribute,
|
|
347
|
+
Dictionary<string, object> applied, List<string> unsupported)
|
|
348
|
+
{
|
|
349
|
+
object supplied;
|
|
350
|
+
if (!presentation.TryGetValue(field, out supplied)) return;
|
|
351
|
+
uint nativeColor = UInt32.MaxValue;
|
|
352
|
+
object accepted = null;
|
|
353
|
+
double alpha = 1;
|
|
354
|
+
if (supplied != null)
|
|
355
|
+
{
|
|
356
|
+
Dictionary<string, object> color = supplied as Dictionary<string, object>;
|
|
357
|
+
if (color == null) throw new ArgumentException(field + " must be an RGBA record or null.");
|
|
358
|
+
int red = (int)Math.Round(
|
|
359
|
+
ColorChannel(color, "red", 255),
|
|
360
|
+
MidpointRounding.AwayFromZero
|
|
361
|
+
);
|
|
362
|
+
int green = (int)Math.Round(
|
|
363
|
+
ColorChannel(color, "green", 255),
|
|
364
|
+
MidpointRounding.AwayFromZero
|
|
365
|
+
);
|
|
366
|
+
int blue = (int)Math.Round(
|
|
367
|
+
ColorChannel(color, "blue", 255),
|
|
368
|
+
MidpointRounding.AwayFromZero
|
|
369
|
+
);
|
|
370
|
+
alpha = ColorChannel(color, "alpha", 1);
|
|
371
|
+
nativeColor = (uint)(red | green << 8 | blue << 16);
|
|
372
|
+
accepted = new Dictionary<string, object>
|
|
373
|
+
{
|
|
374
|
+
{ "red", red },
|
|
375
|
+
{ "green", green },
|
|
376
|
+
{ "blue", blue },
|
|
377
|
+
{ "alpha", alpha }
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (alpha != 1 || !WindowColorsSupported())
|
|
381
|
+
{
|
|
382
|
+
unsupported.Add(field);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
int result = DwmSetWindowAttribute(Handle, attribute, ref nativeColor, sizeof(uint));
|
|
386
|
+
if (result < 0) throw new COMException("Applying window " + field + " failed.", result);
|
|
387
|
+
applied[field] = accepted;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private static double ColorChannel(Dictionary<string, object> color, string name, double maximum)
|
|
391
|
+
{
|
|
392
|
+
object value;
|
|
393
|
+
if (!color.TryGetValue(name, out value)
|
|
394
|
+
|| !(value is int || value is long || value is double || value is decimal))
|
|
395
|
+
throw new ArgumentException("Window color " + name + " must be numeric.");
|
|
396
|
+
double number = Convert.ToDouble(value);
|
|
397
|
+
if (Double.IsNaN(number) || Double.IsInfinity(number) || number < 0 || number > maximum)
|
|
398
|
+
throw new ArgumentException("Window color " + name + " is outside its color-channel range.");
|
|
399
|
+
return number;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private static bool WindowColorsSupported()
|
|
403
|
+
{
|
|
404
|
+
WindowsVersion version = new WindowsVersion();
|
|
405
|
+
version.StructureSize = (uint)Marshal.SizeOf(typeof(WindowsVersion));
|
|
406
|
+
int result = RtlGetVersion(ref version);
|
|
407
|
+
if (result != 0) throw new ExternalException("Reading the Windows version failed.", result);
|
|
408
|
+
return version.Major > 10 || (version.Major == 10 && version.Build >= 22000);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
412
|
+
private struct WindowsVersion
|
|
413
|
+
{
|
|
414
|
+
internal uint StructureSize;
|
|
415
|
+
internal uint Major;
|
|
416
|
+
internal uint Minor;
|
|
417
|
+
internal uint Build;
|
|
418
|
+
internal uint Platform;
|
|
419
|
+
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
|
420
|
+
internal string ServicePack;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
[DllImport("ntdll.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
|
|
424
|
+
private static extern int RtlGetVersion(ref WindowsVersion version);
|
|
425
|
+
|
|
426
|
+
[DllImport("dwmapi.dll")]
|
|
427
|
+
private static extern int DwmSetWindowAttribute(IntPtr window, int attribute, ref uint value, int valueSize);
|
|
428
|
+
|
|
429
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
430
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
431
|
+
private static extern bool DestroyIcon(IntPtr icon);
|
|
432
|
+
|
|
240
433
|
private void DocumentConnected(long documentGeneration)
|
|
241
434
|
{
|
|
242
435
|
ArcaneBridge previous;
|
|
@@ -498,6 +691,19 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
498
691
|
catch (Exception error) { ReportTaskFailure(coreLifetime, error); }
|
|
499
692
|
}
|
|
500
693
|
}
|
|
694
|
+
// Joining the independent image owner belongs after Core input has
|
|
695
|
+
// closed; image I/O must never delay accepted service work draining.
|
|
696
|
+
if (iconLoading != null)
|
|
697
|
+
{
|
|
698
|
+
try
|
|
699
|
+
{
|
|
700
|
+
await iconLoading;
|
|
701
|
+
}
|
|
702
|
+
catch (Exception error)
|
|
703
|
+
{
|
|
704
|
+
ReportTaskFailure(iconLoading, error);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
501
707
|
Task notifications = Task.WhenAll(failureNotifications);
|
|
502
708
|
try { await notifications; }
|
|
503
709
|
catch (Exception error) { ReportTaskFailure(notifications, error); }
|
|
@@ -546,6 +752,11 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
546
752
|
protected override void OnFormClosed(FormClosedEventArgs args)
|
|
547
753
|
{
|
|
548
754
|
base.OnFormClosed(args);
|
|
755
|
+
if (applicationIcon != null)
|
|
756
|
+
{
|
|
757
|
+
applicationIcon.Dispose();
|
|
758
|
+
applicationIcon = null;
|
|
759
|
+
}
|
|
549
760
|
CompleteWindow();
|
|
550
761
|
}
|
|
551
762
|
|
|
@@ -51,6 +51,10 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
51
51
|
string appId = RequiredString(app, "id");
|
|
52
52
|
object title;
|
|
53
53
|
string name = app.TryGetValue("displayName", out title) && title is string ? (string)title : appId;
|
|
54
|
+
object hostValue;
|
|
55
|
+
object iconValue;
|
|
56
|
+
Dictionary<string, object> host = manifest.TryGetValue("host", out hostValue) ? hostValue as Dictionary<string, object> : null;
|
|
57
|
+
string icon = host != null && host.TryGetValue("icon", out iconValue) ? iconValue as string : null;
|
|
54
58
|
|
|
55
59
|
// Core rereads this exact selected file. Do not normalize,
|
|
56
60
|
// rewrite, filter or copy the application's launch record.
|
|
@@ -67,6 +71,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
67
71
|
OriginHost = "arcane.local",
|
|
68
72
|
ProfileDirectory = Path.Combine(profileParent, "WebView2"),
|
|
69
73
|
Title = name,
|
|
74
|
+
IconPath = String.IsNullOrEmpty(icon) ? null : Path.Combine(directory, icon),
|
|
70
75
|
ClassicClientSource = File.ReadAllText(Path.Combine(directory, RequiredString(client, "source"))),
|
|
71
76
|
CoreExecutable = Path.Combine(directory, "runtime", "ArcaneCore.exe"),
|
|
72
77
|
// Forward the actual host selection separately; Core keeps
|