arcane-os 0.88.0 → 0.89.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 +32 -0
- package/README.md +1 -1
- package/browser-runtime/ai/browser-decisions.mjs +31 -4
- package/browser-runtime/ai/core-local.mjs +105 -25
- package/browser-runtime/ai/decision-runtime.mjs +7 -0
- package/browser-runtime/ai/decision-worker.mjs +9 -1
- package/browser-runtime/core/client.mjs +6 -0
- package/browser-runtime/core/contracts.mjs +10 -0
- package/docs/reference/ai/browser-decisions.md +89 -7
- package/docs/reference/cli.md +10 -0
- package/docs/reference/core-client.md +59 -0
- package/docs/reference/core-native-packaging.md +57 -10
- package/docs/reference/core-repositories.md +47 -3
- package/docs/reference/inventory/package-api.json +15 -15
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +4 -4
- package/docs/reference/local-ai.md +86 -3
- package/docs/reference/mcp-stdio.md +76 -1
- package/docs/reference/native-app-control.md +17 -5
- package/docs/reference/protocols.md +14 -0
- package/docs/reference/runtime-modules.md +9 -3
- package/docs/reference/sdk-api.md +146 -23
- package/package.json +2 -2
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +1 -5
- package/schemas/arcane-app.schema.json +13 -3
- package/schemas/arcane-package.schema.json +10 -1
- package/src/app-descriptor.mjs +6 -2
- package/src/cli/app-control.mjs +1 -1
- package/src/core/hosts/windows/ArcaneHost.cs +273 -5
- package/src/core/hosts/windows/ArcaneLauncher.cs +9 -0
- package/src/core/hosts/windows/ArcaneWindowControl.cs +7 -7
- package/src/core/repositories.mjs +11 -1
- package/src/core/services/local-ai.mjs +183 -22
- package/src/installed-sdk-runtime.mjs +2 -0
- package/src/local-ai/config.mjs +16 -0
- package/src/local-ai/dev.mjs +2 -2
- package/src/local-ai/discover.mjs +3 -0
- package/src/local-ai/install.mjs +130 -20
- package/src/local-ai/llama-execution-windows.ps1 +158 -0
- package/src/local-ai/llama-execution.mjs +169 -0
- package/src/local-ai/server.mjs +38 -8
- package/src/native/portable-layout.mjs +13 -3
- package/src/workspace-operation-lock.mjs +23 -10
- package/src/workspace-runtime.mjs +97 -1
package/src/app-descriptor.mjs
CHANGED
|
@@ -237,7 +237,7 @@ function validateDocumentCatalog(value){
|
|
|
237
237
|
|
|
238
238
|
function validateNativeWindow(value){
|
|
239
239
|
if(value===undefined)return undefined;
|
|
240
|
-
assertOnlyKeys(value,new Set(['width','height','resizable']),'descriptor.native.window');
|
|
240
|
+
assertOnlyKeys(value,new Set(['width','height','resizable','state']),'descriptor.native.window');
|
|
241
241
|
for(const dimension of ['width','height']){
|
|
242
242
|
if(value[dimension]!==undefined&&(!is.integer(value[dimension])||value[dimension]<=0)){
|
|
243
243
|
fail(`descriptor.native.window.${dimension} must be a positive integer.`);
|
|
@@ -246,10 +246,14 @@ function validateNativeWindow(value){
|
|
|
246
246
|
if(value.resizable!==undefined&&!is.boolean(value.resizable)){
|
|
247
247
|
fail('descriptor.native.window.resizable must be a boolean.');
|
|
248
248
|
}
|
|
249
|
+
if(value.state!==undefined&&!['normal','maximized','fullscreen'].includes(value.state)){
|
|
250
|
+
fail('descriptor.native.window.state must be normal, maximized, or fullscreen.');
|
|
251
|
+
}
|
|
249
252
|
return completeValue({
|
|
250
253
|
...(value.width===undefined?{}:{width:value.width}),
|
|
251
254
|
...(value.height===undefined?{}:{height:value.height}),
|
|
252
|
-
...(value.resizable===undefined?{}:{resizable:value.resizable})
|
|
255
|
+
...(value.resizable===undefined?{}:{resizable:value.resizable}),
|
|
256
|
+
...(value.state===undefined?{}:{state:value.state})
|
|
253
257
|
});
|
|
254
258
|
}
|
|
255
259
|
|
package/src/cli/app-control.mjs
CHANGED
|
@@ -18,7 +18,7 @@ an application or change its profile. --output selects the capture filename;
|
|
|
18
18
|
the other Arcane commands retain their existing output-format option.
|
|
19
19
|
Inspect --request reads the complete parameter object, including shadowPath and
|
|
20
20
|
optional documentGeneration. It cannot be combined with --selector.
|
|
21
|
-
Key --request selects Tab (optionally shiftKey:true), Enter, or
|
|
21
|
+
Key --request selects Tab (optionally shiftKey:true), Enter, Space, or Escape and the
|
|
22
22
|
observed documentGeneration. Resize --request supplies native client width and
|
|
23
23
|
height for a Normal window; its result includes the previous dimensions.
|
|
24
24
|
`;
|
|
@@ -33,6 +33,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
33
33
|
public double? InitialClientWidth { get; set; }
|
|
34
34
|
public double? InitialClientHeight { get; set; }
|
|
35
35
|
public bool? Resizable { get; set; }
|
|
36
|
+
public string InitialWindowState { get; set; }
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
public static class ArcaneHost
|
|
@@ -130,6 +131,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
130
131
|
private bool browserProcessExited;
|
|
131
132
|
private bool closing;
|
|
132
133
|
private bool closeAllowed;
|
|
134
|
+
private WindowRestoreSnapshot fullscreenRestore;
|
|
133
135
|
|
|
134
136
|
public ArcaneHostForm(ArcaneHostOptions options, Action<string> onDiagnostic,
|
|
135
137
|
Action<Exception> onError = null)
|
|
@@ -138,6 +140,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
138
140
|
if (onDiagnostic == null) throw new ArgumentNullException("onDiagnostic");
|
|
139
141
|
ValidateInitialDimension(options.InitialClientWidth, "InitialClientWidth");
|
|
140
142
|
ValidateInitialDimension(options.InitialClientHeight, "InitialClientHeight");
|
|
143
|
+
if (options.InitialWindowState != null) ValidateWindowState(options.InitialWindowState);
|
|
141
144
|
this.options = options;
|
|
142
145
|
this.onDiagnostic = onDiagnostic;
|
|
143
146
|
this.onError = onError;
|
|
@@ -160,9 +163,13 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
160
163
|
|
|
161
164
|
protected override void OnLoad(EventArgs args)
|
|
162
165
|
{
|
|
163
|
-
// Apply the initial
|
|
164
|
-
//
|
|
165
|
-
if (!started && !closing)
|
|
166
|
+
// Apply the initial choices before the composing launcher's Load
|
|
167
|
+
// handler. Later app/user sizing remains application-owned.
|
|
168
|
+
if (!started && !closing)
|
|
169
|
+
{
|
|
170
|
+
ApplyInitialWindowSize();
|
|
171
|
+
if (options.InitialWindowState != null) ApplyWindowState(options.InitialWindowState);
|
|
172
|
+
}
|
|
166
173
|
base.OnLoad(args);
|
|
167
174
|
if (started || closing) return;
|
|
168
175
|
started = true;
|
|
@@ -202,6 +209,263 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
202
209
|
Math.Max(workArea.Top, Math.Min(Top, workArea.Bottom - Height)));
|
|
203
210
|
}
|
|
204
211
|
|
|
212
|
+
private static void ValidateWindowState(string state)
|
|
213
|
+
{
|
|
214
|
+
if (state != "normal" && state != "maximized" && state != "fullscreen")
|
|
215
|
+
throw new ArgumentException("Select window state normal, maximized, or fullscreen.");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private FormWindowState CurrentWindowState()
|
|
219
|
+
{
|
|
220
|
+
if (!IsHandleCreated) return WindowState;
|
|
221
|
+
if (IsIconic(Handle)) return FormWindowState.Minimized;
|
|
222
|
+
return IsZoomed(Handle) ? FormWindowState.Maximized : FormWindowState.Normal;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private string WindowStateName()
|
|
226
|
+
{
|
|
227
|
+
FormWindowState state = CurrentWindowState();
|
|
228
|
+
if (state != FormWindowState.Normal) return state.ToString();
|
|
229
|
+
return fullscreenRestore != null && FormBorderStyle == FormBorderStyle.None
|
|
230
|
+
? "Fullscreen" : "Normal";
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private Dictionary<string, object> WindowStateRecord()
|
|
234
|
+
{
|
|
235
|
+
return new Dictionary<string, object>
|
|
236
|
+
{
|
|
237
|
+
{ "platform", "windows" }, { "supported", true },
|
|
238
|
+
{ "state", WindowStateName().ToLowerInvariant() }
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private void ApplyWindowState(string state)
|
|
243
|
+
{
|
|
244
|
+
ValidateWindowState(state);
|
|
245
|
+
bool visible = IsHandleCreated && IsWindowVisible(Handle);
|
|
246
|
+
if (visible && state == WindowStateName().ToLowerInvariant()
|
|
247
|
+
&& (state == "fullscreen" || fullscreenRestore == null)) return;
|
|
248
|
+
// Initial hidden configuration keeps ordinary startup behavior. A
|
|
249
|
+
// visible WinForms state setter can activate through ShowWindow.
|
|
250
|
+
WindowStateTransition transition = visible ? new WindowStateTransition(this) : null;
|
|
251
|
+
Exception mutationError = null;
|
|
252
|
+
try { ChangeWindowState(state); }
|
|
253
|
+
catch (Exception error)
|
|
254
|
+
{
|
|
255
|
+
mutationError = error;
|
|
256
|
+
throw;
|
|
257
|
+
}
|
|
258
|
+
finally
|
|
259
|
+
{
|
|
260
|
+
Exception cleanupError = transition == null ? null : transition.Release();
|
|
261
|
+
if (cleanupError != null)
|
|
262
|
+
{
|
|
263
|
+
if (mutationError != null)
|
|
264
|
+
throw new AggregateException("Window state change and activation-hook cleanup failed.",
|
|
265
|
+
mutationError, cleanupError);
|
|
266
|
+
throw cleanupError;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private void ChangeWindowState(string state)
|
|
272
|
+
{
|
|
273
|
+
if (state == "fullscreen")
|
|
274
|
+
{
|
|
275
|
+
Rectangle screen = Screen.FromControl(this).Bounds;
|
|
276
|
+
if (fullscreenRestore == null)
|
|
277
|
+
{
|
|
278
|
+
fullscreenRestore = new WindowRestoreSnapshot(
|
|
279
|
+
CurrentWindowState() == FormWindowState.Normal ? Bounds : RestoreBounds,
|
|
280
|
+
FormBorderStyle, MinimizeBox, MaximizeBox, ControlBox);
|
|
281
|
+
}
|
|
282
|
+
// Retain the normal frame and bounds before removing chrome.
|
|
283
|
+
// This affects this window only: no activation or TopMost.
|
|
284
|
+
WindowState = FormWindowState.Normal;
|
|
285
|
+
FormBorderStyle = FormBorderStyle.None;
|
|
286
|
+
Bounds = screen;
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (fullscreenRestore != null)
|
|
291
|
+
{
|
|
292
|
+
// Restore normal geometry before maximizing so WinForms keeps
|
|
293
|
+
// the same restore bounds for a later Normal selection.
|
|
294
|
+
WindowState = FormWindowState.Normal;
|
|
295
|
+
FormBorderStyle = fullscreenRestore.BorderStyle;
|
|
296
|
+
MinimizeBox = fullscreenRestore.MinimizeBox;
|
|
297
|
+
MaximizeBox = fullscreenRestore.MaximizeBox;
|
|
298
|
+
ControlBox = fullscreenRestore.ControlBox;
|
|
299
|
+
Bounds = fullscreenRestore.Bounds;
|
|
300
|
+
fullscreenRestore = null;
|
|
301
|
+
}
|
|
302
|
+
WindowState = state == "maximized" ? FormWindowState.Maximized : FormWindowState.Normal;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private string WindowStateResponse(Dictionary<string, object> request, string id, bool apply)
|
|
306
|
+
{
|
|
307
|
+
Dictionary<string, object> previous = WindowStateRecord();
|
|
308
|
+
object parameters;
|
|
309
|
+
request.TryGetValue("parameters", out parameters);
|
|
310
|
+
Dictionary<string, object> response = new Dictionary<string, object>
|
|
311
|
+
{
|
|
312
|
+
{ "protocol", "arcane/1" }, { "type", "response" }, { "id", id }
|
|
313
|
+
};
|
|
314
|
+
try
|
|
315
|
+
{
|
|
316
|
+
if (apply)
|
|
317
|
+
{
|
|
318
|
+
Dictionary<string, object> selection = parameters as Dictionary<string, object>;
|
|
319
|
+
object state;
|
|
320
|
+
if (selection == null || !selection.TryGetValue("state", out state) || !(state is string))
|
|
321
|
+
throw new ArgumentException("Supply a window state selection.");
|
|
322
|
+
ApplyWindowState((string)state);
|
|
323
|
+
}
|
|
324
|
+
response["ok"] = true;
|
|
325
|
+
response["result"] = WindowStateRecord();
|
|
326
|
+
}
|
|
327
|
+
catch (Exception error)
|
|
328
|
+
{
|
|
329
|
+
Dictionary<string, object> record = (Dictionary<string, object>)ArcaneHost.ErrorRecord(error,
|
|
330
|
+
error is ArgumentException ? "INVALID_ARGUMENT" : "ARCANE_WINDOW_STATE_FAILED");
|
|
331
|
+
record["details"] = new Dictionary<string, object>
|
|
332
|
+
{
|
|
333
|
+
{ "requested", parameters }, { "previous", previous }, { "actual", WindowStateRecord() }
|
|
334
|
+
};
|
|
335
|
+
response["ok"] = false;
|
|
336
|
+
response["error"] = record;
|
|
337
|
+
}
|
|
338
|
+
return ArcaneHost.Serializer().Serialize(response);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private sealed class WindowRestoreSnapshot
|
|
342
|
+
{
|
|
343
|
+
internal readonly Rectangle Bounds;
|
|
344
|
+
internal readonly FormBorderStyle BorderStyle;
|
|
345
|
+
internal readonly bool MinimizeBox;
|
|
346
|
+
internal readonly bool MaximizeBox;
|
|
347
|
+
internal readonly bool ControlBox;
|
|
348
|
+
|
|
349
|
+
internal WindowRestoreSnapshot(Rectangle bounds, FormBorderStyle borderStyle,
|
|
350
|
+
bool minimizeBox, bool maximizeBox, bool controlBox)
|
|
351
|
+
{
|
|
352
|
+
Bounds = bounds;
|
|
353
|
+
BorderStyle = borderStyle;
|
|
354
|
+
MinimizeBox = minimizeBox;
|
|
355
|
+
MaximizeBox = maximizeBox;
|
|
356
|
+
ControlBox = controlBox;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private sealed class WindowStateTransition
|
|
361
|
+
{
|
|
362
|
+
private const int WhCbt = 5;
|
|
363
|
+
private const int HcbtActivate = 5;
|
|
364
|
+
private const int HcbtSetFocus = 9;
|
|
365
|
+
private delegate IntPtr HookProcedure(int code, IntPtr target, IntPtr details);
|
|
366
|
+
// A failed native unhook must never retain a collected delegate.
|
|
367
|
+
private static readonly HookProcedure callback = BeforeWindowChange;
|
|
368
|
+
[ThreadStatic] private static WindowStateTransition current;
|
|
369
|
+
[ThreadStatic] private static List<WindowStateTransition> pendingCleanup;
|
|
370
|
+
private readonly ArcaneHostForm owner;
|
|
371
|
+
private readonly WindowStateTransition previous;
|
|
372
|
+
private IntPtr hook;
|
|
373
|
+
private bool active;
|
|
374
|
+
|
|
375
|
+
internal WindowStateTransition(ArcaneHostForm owner)
|
|
376
|
+
{
|
|
377
|
+
this.owner = owner;
|
|
378
|
+
previous = current;
|
|
379
|
+
hook = SetWindowsHookEx(WhCbt, callback, IntPtr.Zero, GetCurrentThreadId());
|
|
380
|
+
if (hook == IntPtr.Zero)
|
|
381
|
+
{
|
|
382
|
+
int code = Marshal.GetLastWin32Error();
|
|
383
|
+
System.ComponentModel.Win32Exception error = new System.ComponentModel.Win32Exception(code);
|
|
384
|
+
error.Data["operation"] = "SetWindowsHookExW";
|
|
385
|
+
error.Data["nativeErrorCode"] = code;
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
active = true;
|
|
389
|
+
current = this;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private static IntPtr BeforeWindowChange(int code, IntPtr target, IntPtr details)
|
|
393
|
+
{
|
|
394
|
+
if (code == HcbtActivate || code == HcbtSetFocus)
|
|
395
|
+
{
|
|
396
|
+
for (WindowStateTransition transition = current; transition != null; transition = transition.previous)
|
|
397
|
+
{
|
|
398
|
+
if (!transition.active || !transition.owner.IsHandleCreated) continue;
|
|
399
|
+
IntPtr window = transition.owner.Handle;
|
|
400
|
+
if (target == window || (code == HcbtSetFocus && IsChild(window, target)))
|
|
401
|
+
return new IntPtr(1);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return CallNextHookEx(IntPtr.Zero, code, target, details);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
internal Exception Release()
|
|
408
|
+
{
|
|
409
|
+
// Stop vetoing immediately, even if the native unhook fails.
|
|
410
|
+
active = false;
|
|
411
|
+
if (current == this) current = previous;
|
|
412
|
+
if (hook == IntPtr.Zero) return null;
|
|
413
|
+
if (UnhookWindowsHookEx(hook))
|
|
414
|
+
{
|
|
415
|
+
hook = IntPtr.Zero;
|
|
416
|
+
if (pendingCleanup != null) pendingCleanup.Remove(this);
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
int code = Marshal.GetLastWin32Error();
|
|
420
|
+
if (pendingCleanup == null) pendingCleanup = new List<WindowStateTransition>();
|
|
421
|
+
if (!pendingCleanup.Contains(this)) pendingCleanup.Add(this);
|
|
422
|
+
System.ComponentModel.Win32Exception error = new System.ComponentModel.Win32Exception(code);
|
|
423
|
+
error.Data["operation"] = "UnhookWindowsHookEx";
|
|
424
|
+
error.Data["nativeErrorCode"] = code;
|
|
425
|
+
return error;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
internal static void ReleasePending(ArcaneHostForm owner)
|
|
429
|
+
{
|
|
430
|
+
if (pendingCleanup == null) return;
|
|
431
|
+
foreach (WindowStateTransition transition in pendingCleanup.ToArray())
|
|
432
|
+
{
|
|
433
|
+
if (transition.owner != owner) continue;
|
|
434
|
+
Exception error = transition.Release();
|
|
435
|
+
if (error != null) owner.Report(error);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
[DllImport("user32.dll", EntryPoint = "SetWindowsHookExW", SetLastError = true)]
|
|
440
|
+
private static extern IntPtr SetWindowsHookEx(int kind, HookProcedure procedure, IntPtr module, uint threadId);
|
|
441
|
+
|
|
442
|
+
[DllImport("user32.dll", SetLastError = true)]
|
|
443
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
444
|
+
private static extern bool UnhookWindowsHookEx(IntPtr hook);
|
|
445
|
+
|
|
446
|
+
[DllImport("user32.dll")]
|
|
447
|
+
private static extern IntPtr CallNextHookEx(IntPtr hook, int code, IntPtr target, IntPtr details);
|
|
448
|
+
|
|
449
|
+
[DllImport("user32.dll")]
|
|
450
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
451
|
+
private static extern bool IsChild(IntPtr parent, IntPtr child);
|
|
452
|
+
|
|
453
|
+
[DllImport("kernel32.dll")]
|
|
454
|
+
private static extern uint GetCurrentThreadId();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
[DllImport("user32.dll")]
|
|
458
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
459
|
+
private static extern bool IsWindowVisible(IntPtr window);
|
|
460
|
+
|
|
461
|
+
[DllImport("user32.dll")]
|
|
462
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
463
|
+
private static extern bool IsIconic(IntPtr window);
|
|
464
|
+
|
|
465
|
+
[DllImport("user32.dll")]
|
|
466
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
467
|
+
private static extern bool IsZoomed(IntPtr window);
|
|
468
|
+
|
|
205
469
|
private async Task InitializeAsync()
|
|
206
470
|
{
|
|
207
471
|
try
|
|
@@ -296,12 +560,15 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
296
560
|
{
|
|
297
561
|
if (documentGeneration != generation || closing)
|
|
298
562
|
throw new InvalidOperationException("The requesting document is no longer connected.");
|
|
563
|
+
string method = EnvelopeString(envelope, "method");
|
|
299
564
|
if (requestId != null && EnvelopeString(envelope, "protocol") == "arcane/1"
|
|
300
|
-
&&
|
|
565
|
+
&& (method == "window.setTheme" || method == "window.state" || method == "window.setState"))
|
|
301
566
|
{
|
|
302
567
|
// This method belongs to this exact window, not the Core
|
|
303
568
|
// child or a machine-wide appearance service.
|
|
304
|
-
string response =
|
|
569
|
+
string response = method == "window.setTheme"
|
|
570
|
+
? WindowThemeResponse(envelope, requestId)
|
|
571
|
+
: WindowStateResponse(envelope, requestId, method == "window.setState");
|
|
305
572
|
pending.Enqueue(
|
|
306
573
|
new CoreDelivery(documentGeneration, response)
|
|
307
574
|
);
|
|
@@ -747,6 +1014,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
747
1014
|
if (bridge != null) bridge.Stop(new InvalidOperationException("The application window is closing."));
|
|
748
1015
|
if (activeBridge != null) activeBridge.Stop(new InvalidOperationException("The application window is closing."));
|
|
749
1016
|
StopAppControl();
|
|
1017
|
+
WindowStateTransition.ReleasePending(this);
|
|
750
1018
|
shutdown = DrainAndCloseAsync();
|
|
751
1019
|
shutdown.ContinueWith(ObserveUnexpectedFailure, TaskContinuationOptions.OnlyOnFaulted);
|
|
752
1020
|
}
|
|
@@ -98,6 +98,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
98
98
|
InitialClientWidth = OptionalWindowDimension(windowOptions, "width"),
|
|
99
99
|
InitialClientHeight = OptionalWindowDimension(windowOptions, "height"),
|
|
100
100
|
Resizable = OptionalWindowResizable(windowOptions),
|
|
101
|
+
InitialWindowState = OptionalWindowState(windowOptions),
|
|
101
102
|
IconPath = String.IsNullOrEmpty(icon) ? null : Path.Combine(directory, icon),
|
|
102
103
|
ClassicClientSource = File.ReadAllText(Path.Combine(directory, RequiredString(client, "source"))),
|
|
103
104
|
AppControlEndpoint = appControl == null ? null : RequiredString(appControl, "endpoint"),
|
|
@@ -210,6 +211,14 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
210
211
|
throw new FormatException("The native manifest needs a boolean at window.resizable.");
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
private static string OptionalWindowState(Dictionary<string, object> window)
|
|
215
|
+
{
|
|
216
|
+
object value;
|
|
217
|
+
if (window == null || !window.TryGetValue("state", out value)) return null;
|
|
218
|
+
if (value is string) return (string)value;
|
|
219
|
+
throw new FormatException("The native manifest needs a string at window.state.");
|
|
220
|
+
}
|
|
221
|
+
|
|
213
222
|
private static string QuoteArgument(string value)
|
|
214
223
|
{
|
|
215
224
|
// Windows command-line escaping is transport-local. The child
|
|
@@ -148,7 +148,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
148
148
|
{
|
|
149
149
|
return new Dictionary<string, object>
|
|
150
150
|
{
|
|
151
|
-
{ "title", Text }, { "state",
|
|
151
|
+
{ "title", Text }, { "state", WindowStateName() },
|
|
152
152
|
{ "width", ClientSize.Width }, { "height", ClientSize.Height }
|
|
153
153
|
};
|
|
154
154
|
}
|
|
@@ -252,8 +252,8 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
252
252
|
{
|
|
253
253
|
object value;
|
|
254
254
|
string key = parameters.TryGetValue("key", out value) ? value as string : null;
|
|
255
|
-
if (key != "Tab" && key != "Enter" && key != "Space")
|
|
256
|
-
throw new ArgumentException("key must be Tab, Enter, or
|
|
255
|
+
if (key != "Tab" && key != "Enter" && key != "Space" && key != "Escape")
|
|
256
|
+
throw new ArgumentException("key must be Tab, Enter, Space, or Escape.");
|
|
257
257
|
bool shiftKey = false;
|
|
258
258
|
if (parameters.TryGetValue("shiftKey", out value))
|
|
259
259
|
{
|
|
@@ -263,14 +263,14 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
263
263
|
if (shiftKey && key != "Tab") throw new ArgumentException("shiftKey is supported only with Tab.");
|
|
264
264
|
Dictionary<string, object> down = new Dictionary<string, object>
|
|
265
265
|
{
|
|
266
|
-
{ "type", key == "Tab" ? "rawKeyDown" : "keyDown" },
|
|
266
|
+
{ "type", key == "Tab" || key == "Escape" ? "rawKeyDown" : "keyDown" },
|
|
267
267
|
{ "key", key == "Space" ? " " : key }, { "code", key },
|
|
268
|
-
{ "windowsVirtualKeyCode", key == "Tab" ? 9 : key == "Enter" ? 13 : 32 },
|
|
268
|
+
{ "windowsVirtualKeyCode", key == "Tab" ? 9 : key == "Enter" ? 13 : key == "Escape" ? 27 : 32 },
|
|
269
269
|
{ "modifiers", shiftKey ? 8 : 0 }
|
|
270
270
|
};
|
|
271
271
|
Dictionary<string, object> up = new Dictionary<string, object>(down);
|
|
272
272
|
up["type"] = "keyUp";
|
|
273
|
-
if (key
|
|
273
|
+
if (key == "Enter" || key == "Space")
|
|
274
274
|
{
|
|
275
275
|
down["text"] = key == "Enter" ? "\r" : " ";
|
|
276
276
|
down["unmodifiedText"] = down["text"];
|
|
@@ -345,7 +345,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
345
345
|
|
|
346
346
|
private void RequireAppControlNormalWindow()
|
|
347
347
|
{
|
|
348
|
-
if (
|
|
348
|
+
if (WindowStateName() == "Normal") return;
|
|
349
349
|
InvalidOperationException error = new InvalidOperationException("App-control resize requires a Normal window.");
|
|
350
350
|
error.Data["reason"] = "unsupported-window-state";
|
|
351
351
|
error.Data["supportedWindowState"] = "Normal";
|
|
@@ -45,7 +45,7 @@ export function resolveArcaneDataPaths({dataRoot} = {}) {
|
|
|
45
45
|
|
|
46
46
|
/** One connected checkout, composed into an application's own Core service. */
|
|
47
47
|
export function createRepositoryWorkspace(
|
|
48
|
-
{name, directory, dataRoot, remote, branch, longPaths, gitIdentity, onEvent, run = runProcess} = {}
|
|
48
|
+
{name, directory, dataRoot, remote, branch, longPaths, cloneIdentity = {}, gitIdentity, onEvent, run = runProcess} = {}
|
|
49
49
|
) {
|
|
50
50
|
let selected = directory;
|
|
51
51
|
if (selected === undefined) {
|
|
@@ -62,6 +62,15 @@ export function createRepositoryWorkspace(
|
|
|
62
62
|
throw new TypeError('longPaths must be a boolean when supplied.');
|
|
63
63
|
}
|
|
64
64
|
if (!is.function(run)) throw new TypeError('run must implement the SDK process adapter.');
|
|
65
|
+
const {name: cloneName, email: cloneEmail} = cloneIdentity;
|
|
66
|
+
const cloneConfiguration = [];
|
|
67
|
+
for (const [field, value] of Object.entries({name: cloneName, email: cloneEmail})) {
|
|
68
|
+
if (value === undefined) continue;
|
|
69
|
+
if (!is.string(value) || !value.isWellFormed() || value.includes('\0')) {
|
|
70
|
+
throw new TypeError(`cloneIdentity.${field} must be text representable as a native process argument.`);
|
|
71
|
+
}
|
|
72
|
+
cloneConfiguration.push('--config', `user.${field}=${value}`);
|
|
73
|
+
}
|
|
65
74
|
const execute = createGitIdentityRunner(run, gitIdentity);
|
|
66
75
|
const repositoryDirectory = path.resolve(selected);
|
|
67
76
|
const pending = new Set();
|
|
@@ -108,6 +117,7 @@ export function createRepositoryWorkspace(
|
|
|
108
117
|
if (process.platform === 'win32' && longPaths !== undefined) {
|
|
109
118
|
arguments_.push('--config', `core.longpaths=${longPaths}`);
|
|
110
119
|
}
|
|
120
|
+
arguments_.push(...cloneConfiguration);
|
|
111
121
|
if (branch !== undefined) arguments_.push('--branch', branch);
|
|
112
122
|
arguments_.push('--', remote, repositoryDirectory);
|
|
113
123
|
const result = await prepareRun(
|