@oh-my-pi/pi-tui 18.2.11 → 18.3.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 +20 -0
- package/THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/types/apps/ps-data.d.ts +1 -1
- package/dist/types/apps/ps-top.d.ts +1 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +1 -1
- package/dist/types/overlays/annotation-overlay.d.ts +30 -0
- package/dist/types/overlays/annotation-types.d.ts +84 -0
- package/dist/types/overlays/copy-selector.d.ts +30 -2
- package/dist/types/overlays/copy-targets.d.ts +2 -0
- package/dist/types/overlays/model-hub.d.ts +2 -2
- package/dist/types/overlays/oauth-selector.d.ts +3 -6
- package/dist/types/overlays/usage-dashboard.d.ts +2 -0
- package/dist/types/prompt/image-source.d.ts +9 -8
- package/dist/types/status-line/context-usage.d.ts +3 -1
- package/dist/types/theme/mermaid-cache.d.ts +4 -6
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/daemon.d.ts +46 -0
- package/dist/types/tools/irc.d.ts +30 -0
- package/dist/types/tools/line-ranges.d.ts +1 -1
- package/dist/types/tools/proc-render.d.ts +34 -0
- package/dist/types/tools/read.d.ts +4 -1
- package/dist/types/tools/wait.d.ts +131 -0
- package/dist/types/tools/write.d.ts +4 -0
- package/dist/types/tools/xd-url.d.ts +8 -0
- package/dist/types/windows-altgr.d.ts +54 -0
- package/package.json +13 -10
- package/src/apps/debug/protocol-probe.ts +1 -1
- package/src/apps/ps-data.ts +1 -1
- package/src/apps/ps-top.ts +1 -1
- package/src/chat/chat-transcript-builder.ts +4 -4
- package/src/chat/tool-execution.ts +8 -8
- package/src/chat/transcript-render-helpers.ts +3 -3
- package/src/components/markdown.ts +1 -1
- package/src/desktop-notify.ts +1 -1
- package/src/overlays/annotation-overlay.ts +1145 -0
- package/src/overlays/annotation-types.ts +78 -0
- package/src/overlays/copy-selector.ts +45 -19
- package/src/overlays/copy-targets.ts +3 -3
- package/src/overlays/model-hub.ts +5 -4
- package/src/overlays/oauth-selector.ts +8 -12
- package/src/overlays/plan-review-overlay.ts +213 -40
- package/src/overlays/usage-dashboard.ts +22 -1
- package/src/prompt/image-source.ts +9 -8
- package/src/setup/scenes/sign-in.ts +1 -1
- package/src/status-line/context-usage.ts +15 -3
- package/src/terminal-capabilities.ts +2 -2
- package/src/terminal.ts +8 -1
- package/src/theme/mermaid-cache.ts +83 -25
- package/src/tools/bash.ts +14 -1
- package/src/tools/daemon.ts +51 -0
- package/src/tools/index.ts +2 -2
- package/src/tools/irc.ts +33 -0
- package/src/tools/line-ranges.ts +13 -3
- package/src/tools/proc-render.ts +294 -0
- package/src/tools/read.ts +22 -1
- package/src/tools/wait.ts +595 -0
- package/src/tools/write.ts +76 -0
- package/src/tools/xd-url.ts +9 -0
- package/src/windows-altgr.ts +227 -0
- package/dist/types/tools/hub.d.ts +0 -342
- package/src/tools/hub.ts +0 -1492
package/src/tools/write.ts
CHANGED
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
TRUNCATE_LENGTHS,
|
|
22
22
|
truncateToWidth,
|
|
23
23
|
} from "../render/render-utils";
|
|
24
|
+
import type { CoordinationDetails } from "./wait";
|
|
25
|
+
import { renderAgentWrite, renderProcWrite, type ProcWriteAction, type ProcWriteDetails } from "./proc-render";
|
|
24
26
|
import type { FileDiagnosticsResult } from "./lsp";
|
|
25
27
|
import type { OutputMeta } from "./output-meta";
|
|
26
28
|
import type { RenderResultOptions, ToolActivityContext, ToolActivitySummary, ToolRenderer } from "./renderer";
|
|
@@ -46,6 +48,8 @@ export interface WriteToolDetails {
|
|
|
46
48
|
resolvedPath?: string;
|
|
47
49
|
/** Set when the write dispatched an `xd://` tool device; drives renderer delegation. */
|
|
48
50
|
xdev?: XdevRenderDispatch;
|
|
51
|
+
message?: CoordinationDetails;
|
|
52
|
+
proc?: ProcWriteDetails;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
interface WriteRenderArgs {
|
|
@@ -314,6 +318,13 @@ export interface WriteRenderContext {
|
|
|
314
318
|
resolveXdevMounted?: (name: string) => XdevMountedRenderer | undefined;
|
|
315
319
|
}
|
|
316
320
|
|
|
321
|
+
function procWriteTarget(path: string): { id: string; action: ProcWriteAction } {
|
|
322
|
+
const target = path.slice("proc://".length);
|
|
323
|
+
if (target.endsWith("/kill")) return { id: target.slice(0, -5), action: "kill" };
|
|
324
|
+
if (target.endsWith("/mode")) return { id: target.slice(0, -5), action: "mode" };
|
|
325
|
+
return { id: target, action: "stdin" };
|
|
326
|
+
}
|
|
327
|
+
|
|
317
328
|
/** Render file writes and delegated tool-device calls. */
|
|
318
329
|
export const writeToolRenderer = {
|
|
319
330
|
/** Compact one-line activity: device writes read as the mounted tool (`LSP · references foo`), file writes as `Write · <path>`. */
|
|
@@ -326,6 +337,16 @@ export const writeToolRenderer = {
|
|
|
326
337
|
? writeArgs.path
|
|
327
338
|
: "";
|
|
328
339
|
if (!rawPath) return { label: "Write" };
|
|
340
|
+
if (/^agent:\/\//i.test(rawPath)) {
|
|
341
|
+
return {
|
|
342
|
+
label: "Message",
|
|
343
|
+
detail: rawPath.slice("agent://".length) === "all" ? "broadcast" : rawPath.slice("agent://".length),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
if (/^proc:\/\//i.test(rawPath)) {
|
|
347
|
+
const { id, action } = procWriteTarget(rawPath);
|
|
348
|
+
return { label: "Process", detail: `${action} ${shortenPath(id)}` };
|
|
349
|
+
}
|
|
329
350
|
const xdev = parseXdUrl(rawPath);
|
|
330
351
|
if (xdev?.name) {
|
|
331
352
|
const resolveMounted = (context.renderContext as WriteRenderContext | undefined)?.resolveXdevMounted;
|
|
@@ -347,6 +368,38 @@ export const writeToolRenderer = {
|
|
|
347
368
|
// A present-but-malformed path (array/object from a bad provider parse)
|
|
348
369
|
// is definitively not xd:// — fall through to the legacy frame.
|
|
349
370
|
if (args.path === undefined && args.file_path === undefined) return undefined;
|
|
371
|
+
const pathSettled = args.content !== undefined || options.argsComplete === true;
|
|
372
|
+
const hasStringPath = typeof args.file_path === "string" || typeof args.path === "string";
|
|
373
|
+
if (
|
|
374
|
+
hasStringPath &&
|
|
375
|
+
!pathSettled &&
|
|
376
|
+
("agent://".startsWith(rawPath.toLowerCase()) ||
|
|
377
|
+
"proc://".startsWith(rawPath.toLowerCase()) ||
|
|
378
|
+
/^(?:agent|proc):\/\//i.test(rawPath))
|
|
379
|
+
)
|
|
380
|
+
return undefined;
|
|
381
|
+
if (/^agent:\/\//i.test(rawPath)) {
|
|
382
|
+
return renderAgentWrite(
|
|
383
|
+
rawPath.slice("agent://".length),
|
|
384
|
+
typeof args.content === "string" ? args.content : "",
|
|
385
|
+
undefined,
|
|
386
|
+
undefined,
|
|
387
|
+
options,
|
|
388
|
+
uiTheme,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
if (/^proc:\/\//i.test(rawPath)) {
|
|
392
|
+
const { id, action } = procWriteTarget(rawPath);
|
|
393
|
+
return renderProcWrite(
|
|
394
|
+
id,
|
|
395
|
+
action,
|
|
396
|
+
typeof args.content === "string" ? args.content : undefined,
|
|
397
|
+
undefined,
|
|
398
|
+
undefined,
|
|
399
|
+
options,
|
|
400
|
+
uiTheme,
|
|
401
|
+
);
|
|
402
|
+
}
|
|
350
403
|
if (rawPath && couldBecomeXdUrl(rawPath)) {
|
|
351
404
|
const xdev = parseXdUrl(rawPath);
|
|
352
405
|
// The path string is settled once the content field started streaming.
|
|
@@ -412,6 +465,29 @@ export const writeToolRenderer = {
|
|
|
412
465
|
uiTheme: Theme,
|
|
413
466
|
args?: WriteRenderArgs,
|
|
414
467
|
): Component {
|
|
468
|
+
const messagePath = typeof args?.path === "string" ? args.path : args?.file_path;
|
|
469
|
+
if (typeof messagePath === "string" && /^agent:\/\//i.test(messagePath)) {
|
|
470
|
+
return renderAgentWrite(
|
|
471
|
+
messagePath.slice("agent://".length),
|
|
472
|
+
typeof args?.content === "string" ? args.content : "",
|
|
473
|
+
result,
|
|
474
|
+
result.details?.message,
|
|
475
|
+
options,
|
|
476
|
+
uiTheme,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
if (typeof messagePath === "string" && /^proc:\/\//i.test(messagePath)) {
|
|
480
|
+
const { id, action } = procWriteTarget(messagePath);
|
|
481
|
+
return renderProcWrite(
|
|
482
|
+
id,
|
|
483
|
+
action,
|
|
484
|
+
typeof args?.content === "string" ? args.content : undefined,
|
|
485
|
+
result,
|
|
486
|
+
result.details?.proc,
|
|
487
|
+
options,
|
|
488
|
+
uiTheme,
|
|
489
|
+
);
|
|
490
|
+
}
|
|
415
491
|
// xd:// dispatch results render as the mounted tool's own result.
|
|
416
492
|
const xdev = result.details?.xdev;
|
|
417
493
|
if (xdev) {
|
package/src/tools/xd-url.ts
CHANGED
|
@@ -19,6 +19,15 @@ export function parseXdUrl(input: string): { name: string | null } | null {
|
|
|
19
19
|
return { name };
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Parse a read-only `xd://<tool>/<topic>` documentation URL.
|
|
24
|
+
* Returns `null` for anything else, including bare device URLs.
|
|
25
|
+
*/
|
|
26
|
+
export function parseXdTopicUrl(input: string): { name: string; topic: string } | null {
|
|
27
|
+
const match = /^xd:\/\/([^/?#]+)\/([^/?#]+)$/i.exec(input.trim());
|
|
28
|
+
return match ? { name: match[1]!, topic: match[2]! } : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
/** Whether a streaming path prefix could still become an `xd://` URL. */
|
|
23
32
|
export function couldBecomeXdUrl(partialPath: string): boolean {
|
|
24
33
|
if (partialPath.length <= XD_URL_PREFIX.length) {
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recover AltGr text that Windows console hosts drop under the kitty keyboard protocol.
|
|
3
|
+
*
|
|
4
|
+
* The kitty encoder shipped in Windows Terminal / OpenConsole before
|
|
5
|
+
* microsoft/terminal#20052 reports AltGr+key as Alt (or Ctrl+Alt) plus the
|
|
6
|
+
* layout's *unmodified* base key, without the produced text: Hungarian
|
|
7
|
+
* AltGr+F arrives as `CSI 102;3u` instead of `[`. The byte stream is then
|
|
8
|
+
* indistinguishable from a real Alt+F shortcut, so the character is lost and
|
|
9
|
+
* the chord fires `alt+f` bindings instead.
|
|
10
|
+
*
|
|
11
|
+
* Recovery reads two pieces of local state the bytes lack:
|
|
12
|
+
* - whether Right Alt (AltGr) is physically held, via `GetAsyncKeyState`;
|
|
13
|
+
* - what the active keyboard layout's AltGr layer produces for the base key,
|
|
14
|
+
* via `ToUnicodeEx`.
|
|
15
|
+
*
|
|
16
|
+
* Layouts without an AltGr layer (US, UK, ...) resolve to an empty table once and
|
|
17
|
+
* short-circuit every later event. The per-layout table is built once and cached;
|
|
18
|
+
* it is rebuilt only when the foreground window reports a different layout handle.
|
|
19
|
+
*
|
|
20
|
+
* Known limitation: AltGr keys that produce a dead key on the active layout have no
|
|
21
|
+
* table entry (`ToUnicodeEx` reports them as pending composition, not text), so those
|
|
22
|
+
* chords keep reaching Alt bindings.
|
|
23
|
+
*/
|
|
24
|
+
import { dlopen, FFIType, type Library, type Pointer, ptr } from "bun:ffi";
|
|
25
|
+
import { parseKittySequence } from "./keys";
|
|
26
|
+
|
|
27
|
+
const KITTY_MOD_SHIFT = 1;
|
|
28
|
+
const KITTY_MOD_ALT = 2;
|
|
29
|
+
const KITTY_MOD_CTRL = 4;
|
|
30
|
+
const KITTY_LOCK_MASK = 64 | 128;
|
|
31
|
+
|
|
32
|
+
const VK_SHIFT = 0x10;
|
|
33
|
+
const VK_CONTROL = 0x11;
|
|
34
|
+
const VK_MENU = 0x12;
|
|
35
|
+
const VK_LSHIFT = 0xa0;
|
|
36
|
+
const VK_LCONTROL = 0xa2;
|
|
37
|
+
const VK_RMENU = 0xa5;
|
|
38
|
+
const MAPVK_VK_TO_VSC = 0;
|
|
39
|
+
/** `ToUnicodeEx` flag: do not change the kernel keyboard state (keeps pending dead keys intact). */
|
|
40
|
+
const TOUNICODE_NO_STATE_CHANGE = 0x4;
|
|
41
|
+
|
|
42
|
+
const USER32_SYMBOLS = {
|
|
43
|
+
GetForegroundWindow: { args: [], returns: FFIType.ptr },
|
|
44
|
+
GetWindowThreadProcessId: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.u32 },
|
|
45
|
+
GetKeyboardLayout: { args: [FFIType.u32], returns: FFIType.ptr },
|
|
46
|
+
GetAsyncKeyState: { args: [FFIType.i32], returns: FFIType.i16 },
|
|
47
|
+
MapVirtualKeyExW: { args: [FFIType.u32, FFIType.u32, FFIType.ptr], returns: FFIType.u32 },
|
|
48
|
+
ToUnicodeEx: {
|
|
49
|
+
args: [FFIType.u32, FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.i32, FFIType.u32, FFIType.ptr],
|
|
50
|
+
returns: FFIType.i32,
|
|
51
|
+
},
|
|
52
|
+
} as const;
|
|
53
|
+
|
|
54
|
+
type User32 = Library<typeof USER32_SYMBOLS>;
|
|
55
|
+
|
|
56
|
+
/** AltGr layer of one keyboard layout: `"<baseCodepoint>:<shift 0|1>"` → produced text. */
|
|
57
|
+
type AltGrTable = Map<string, string>;
|
|
58
|
+
/** Keyboard layout handle (`HKL`) as `bun:ffi` surfaces pointer-sized values. */
|
|
59
|
+
export type KeyboardLayoutHandle = Pointer | bigint;
|
|
60
|
+
|
|
61
|
+
let user32: User32 | null | undefined;
|
|
62
|
+
let cachedLayout: { hkl: KeyboardLayoutHandle | null; table: AltGrTable } | undefined;
|
|
63
|
+
|
|
64
|
+
function getUser32(): User32 | null {
|
|
65
|
+
if (user32 !== undefined) return user32;
|
|
66
|
+
try {
|
|
67
|
+
user32 = dlopen("user32.dll", USER32_SYMBOLS);
|
|
68
|
+
} catch {
|
|
69
|
+
user32 = null;
|
|
70
|
+
}
|
|
71
|
+
return user32;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function tableKey(baseCodepoint: number, shift: boolean): string {
|
|
75
|
+
return `${baseCodepoint}:${shift ? 1 : 0}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isPrintableText(text: string): boolean {
|
|
79
|
+
for (const ch of text) {
|
|
80
|
+
const cp = ch.codePointAt(0)!;
|
|
81
|
+
if (cp < 0x20 || cp === 0x7f || (cp >= 0x80 && cp <= 0x9f)) return false;
|
|
82
|
+
}
|
|
83
|
+
return text.length > 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Enumerate the layout's AltGr layer. Keys are the lowercased unmodified character
|
|
88
|
+
* of each virtual key — the same "base key" the kitty encoder reports.
|
|
89
|
+
*/
|
|
90
|
+
function buildAltGrTable(lib: User32, hkl: KeyboardLayoutHandle): AltGrTable {
|
|
91
|
+
const table: AltGrTable = new Map();
|
|
92
|
+
const keyState = new Uint8Array(256);
|
|
93
|
+
const buffer = new Uint16Array(8);
|
|
94
|
+
|
|
95
|
+
const translate = (vk: number, scan: number, modifiers: readonly number[]): string | undefined => {
|
|
96
|
+
keyState.fill(0);
|
|
97
|
+
for (const modifier of modifiers) keyState[modifier] = 0x80;
|
|
98
|
+
const count = lib.symbols.ToUnicodeEx(
|
|
99
|
+
vk,
|
|
100
|
+
scan,
|
|
101
|
+
// Fresh pointers per call: JSC may relocate small typed-array backing stores.
|
|
102
|
+
ptr(keyState),
|
|
103
|
+
ptr(buffer),
|
|
104
|
+
buffer.length,
|
|
105
|
+
TOUNICODE_NO_STATE_CHANGE,
|
|
106
|
+
hkl,
|
|
107
|
+
);
|
|
108
|
+
// Negative = dead key; zero = no translation.
|
|
109
|
+
if (count <= 0) return undefined;
|
|
110
|
+
const text = String.fromCharCode(...buffer.subarray(0, count));
|
|
111
|
+
return isPrintableText(text) ? text : undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const altGr = [VK_CONTROL, VK_LCONTROL, VK_MENU, VK_RMENU];
|
|
115
|
+
const altGrShift = [...altGr, VK_SHIFT, VK_LSHIFT];
|
|
116
|
+
for (let vk = 0x20; vk <= 0xfe; vk++) {
|
|
117
|
+
// Skip Windows/menu keys and the sided modifiers: they have no text layer.
|
|
118
|
+
if ((vk >= 0x5b && vk <= 0x5f) || (vk >= VK_LSHIFT && vk <= VK_RMENU)) continue;
|
|
119
|
+
const scan = lib.symbols.MapVirtualKeyExW(vk, MAPVK_VK_TO_VSC, hkl);
|
|
120
|
+
if (scan === 0) continue;
|
|
121
|
+
const base = translate(vk, scan, []);
|
|
122
|
+
if (base === undefined) continue;
|
|
123
|
+
const baseCodepoint = base.toLowerCase().codePointAt(0)!;
|
|
124
|
+
for (const shift of [false, true]) {
|
|
125
|
+
const key = tableKey(baseCodepoint, shift);
|
|
126
|
+
if (table.has(key)) continue;
|
|
127
|
+
const produced = translate(vk, scan, shift ? altGrShift : altGr);
|
|
128
|
+
if (produced !== undefined) table.set(key, produced);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return table;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function activeLayoutTable(lib: User32): AltGrTable {
|
|
135
|
+
// The console process has no window of its own; the layout that produced the
|
|
136
|
+
// keystroke belongs to the terminal window that currently holds focus.
|
|
137
|
+
const hwnd = lib.symbols.GetForegroundWindow();
|
|
138
|
+
const thread = hwnd ? lib.symbols.GetWindowThreadProcessId(hwnd, null) : 0;
|
|
139
|
+
const hkl = lib.symbols.GetKeyboardLayout(thread);
|
|
140
|
+
if (cachedLayout && cachedLayout.hkl === hkl) return cachedLayout.table;
|
|
141
|
+
const table: AltGrTable = hkl ? buildAltGrTable(lib, hkl) : new Map();
|
|
142
|
+
cachedLayout = { hkl, table };
|
|
143
|
+
return table;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* AltGr layer of an explicit layout handle (`HKL`), keyed `"<baseCodepoint>:<shift 0|1>"`.
|
|
148
|
+
* Test seam: lets a harness inspect a loaded layout without making it the active one.
|
|
149
|
+
*/
|
|
150
|
+
export function readAltGrLayer(hkl: KeyboardLayoutHandle): ReadonlyMap<string, string> {
|
|
151
|
+
const lib = getUser32();
|
|
152
|
+
return lib && hkl ? buildAltGrTable(lib, hkl) : new Map();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Local keyboard state the translator reads; injectable so tests need no live desktop. */
|
|
156
|
+
export interface AltGrHost {
|
|
157
|
+
/** AltGr layer of the layout that produced the keystroke, keyed like {@link readAltGrLayer}. */
|
|
158
|
+
activeLayer(): ReadonlyMap<string, string>;
|
|
159
|
+
/** Whether Right Alt (AltGr) is held, or was within the last {@link RIGHT_ALT_LATCH_MS}. */
|
|
160
|
+
isRightAltDown(): boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const EMPTY_LAYER: ReadonlyMap<string, string> = new Map();
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* How long an observed Right Alt press keeps counting as held. Key state is sampled
|
|
167
|
+
* at processing time, and one stdin read can carry several queued AltGr repeats that
|
|
168
|
+
* are dispatched synchronously after the key is released; the latch keeps that tail
|
|
169
|
+
* typing text. Far shorter than any release-then-Left-Alt chord a person can press.
|
|
170
|
+
*/
|
|
171
|
+
export const RIGHT_ALT_LATCH_MS = 30;
|
|
172
|
+
|
|
173
|
+
/** Wrap a raw Right Alt probe so a press stays observed for {@link RIGHT_ALT_LATCH_MS}. */
|
|
174
|
+
export function createRightAltLatch(isDownNow: () => boolean, now: () => number): () => boolean {
|
|
175
|
+
let lastSeenDown = Number.NEGATIVE_INFINITY;
|
|
176
|
+
return () => {
|
|
177
|
+
const at = now();
|
|
178
|
+
if (isDownNow()) {
|
|
179
|
+
lastSeenDown = at;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
return at - lastSeenDown <= RIGHT_ALT_LATCH_MS;
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const win32Host: AltGrHost = {
|
|
187
|
+
activeLayer() {
|
|
188
|
+
const lib = getUser32();
|
|
189
|
+
return lib ? activeLayoutTable(lib) : EMPTY_LAYER;
|
|
190
|
+
},
|
|
191
|
+
isRightAltDown: createRightAltLatch(() => {
|
|
192
|
+
const lib = getUser32();
|
|
193
|
+
return lib !== null && (lib.symbols.GetAsyncKeyState(VK_RMENU) & 0x8000) !== 0;
|
|
194
|
+
}, performance.now.bind(performance)),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Translate a kitty CSI-u Alt/Ctrl+Alt chord into the text AltGr produced, or
|
|
199
|
+
* `undefined` when the sequence is not an AltGr keystroke on the active layout.
|
|
200
|
+
*
|
|
201
|
+
* Only call this on native Windows ConPTY hosts with the kitty protocol active.
|
|
202
|
+
*/
|
|
203
|
+
export function translateWindowsAltGrSequence(data: string, host: AltGrHost = win32Host): string | undefined {
|
|
204
|
+
if (!data.endsWith("u")) return undefined;
|
|
205
|
+
const parsed = parseKittySequence(data);
|
|
206
|
+
if (!parsed || parsed.eventType === 3) return undefined;
|
|
207
|
+
const modifier = parsed.modifier & ~KITTY_LOCK_MASK;
|
|
208
|
+
// AltGr surfaces as Alt (fake LeftCtrl filtered) or Ctrl+Alt, optionally with Shift.
|
|
209
|
+
if ((modifier & KITTY_MOD_ALT) === 0) return undefined;
|
|
210
|
+
if ((modifier & ~(KITTY_MOD_ALT | KITTY_MOD_CTRL | KITTY_MOD_SHIFT)) !== 0) return undefined;
|
|
211
|
+
// Functional keys (kitty PUA range) and C0 controls never carry AltGr text.
|
|
212
|
+
if (parsed.codepoint < 0x20 || (parsed.codepoint >= 0xe000 && parsed.codepoint <= 0xf8ff)) return undefined;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
// Layouts without an AltGr layer (US, UK, ...) stop here: Alt chords stay shortcuts.
|
|
216
|
+
const layer = host.activeLayer();
|
|
217
|
+
if (layer.size === 0) return undefined;
|
|
218
|
+
// Table keys are lowercased base characters; normalize hosts that report uppercase.
|
|
219
|
+
const base = String.fromCodePoint(parsed.codepoint).toLowerCase().codePointAt(0)!;
|
|
220
|
+
const text = layer.get(tableKey(base, (modifier & KITTY_MOD_SHIFT) !== 0));
|
|
221
|
+
if (text === undefined) return undefined;
|
|
222
|
+
// Left Alt+key shares the exact bytes; only a held Right Alt means AltGr.
|
|
223
|
+
return host.isRightAltDown() ? text : undefined;
|
|
224
|
+
} catch {
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
}
|