glimpse-sdk 0.5.0 → 0.7.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/README.md +1 -1
- package/dist/src/app.d.ts +16 -12
- package/dist/src/app.js +83 -7
- package/dist/src/events.d.ts +1 -1
- package/dist/src/events.js +1 -1
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.js +4 -4
- package/dist/src/protocol.d.ts +2 -15
- package/dist/src/protocol.js +1 -18
- package/dist/src/widgets.d.ts +415 -256
- package/dist/src/widgets.js +379 -457
- package/package.json +1 -1
- package/src/app.ts +94 -17
- package/src/events.ts +2 -2
- package/src/index.ts +39 -36
- package/src/protocol.ts +2 -23
- package/src/widgets.ts +334 -787
package/package.json
CHANGED
package/src/app.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
type CallbackEvent,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
parseInitEvent,
|
|
14
15
|
} from "./events.js";
|
|
15
16
|
import { StatusItem } from "./protocol.js";
|
|
16
|
-
import { type TreeNode } from "./widgets.js";
|
|
17
|
+
import { type TreeNode, InlineHandlerRegistry } from "./widgets.js";
|
|
17
18
|
|
|
18
19
|
type Handler<EventT> = (event: EventT) => void | Promise<void>;
|
|
19
20
|
|
|
@@ -23,18 +24,14 @@ interface OutgoingMessage {
|
|
|
23
24
|
line: string;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
interface
|
|
27
|
-
summary: string;
|
|
27
|
+
interface ShowNotificationOptions {
|
|
28
28
|
body?: string;
|
|
29
|
-
urgency?: string;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
interface
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
interface CopyToClipboardArgs {
|
|
37
|
-
text: string;
|
|
31
|
+
export interface CommandResult {
|
|
32
|
+
stdout: string;
|
|
33
|
+
stderr: string;
|
|
34
|
+
rc: number;
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
interface DismissNotificationArgs {
|
|
@@ -45,6 +42,7 @@ export abstract class Applet<State extends object> {
|
|
|
45
42
|
state: State;
|
|
46
43
|
|
|
47
44
|
private readonly handlerMap = new Map<string, Handler<CallbackEvent>>();
|
|
45
|
+
private inlineHandlerMap = new Map<string, (event: CallbackEvent) => void | Promise<void>>();
|
|
48
46
|
private readonly outgoing: OutgoingMessage[] = [];
|
|
49
47
|
private flushPromise: Promise<void> | null = null;
|
|
50
48
|
private renderQueued = false;
|
|
@@ -58,6 +56,10 @@ export abstract class Applet<State extends object> {
|
|
|
58
56
|
|
|
59
57
|
protected abstract initialState(): State;
|
|
60
58
|
|
|
59
|
+
protected cssClass(): string | null {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
61
63
|
protected async onStart(): Promise<void> {}
|
|
62
64
|
|
|
63
65
|
protected async onInit(_event: InitEvent): Promise<void> {}
|
|
@@ -97,20 +99,37 @@ export abstract class Applet<State extends object> {
|
|
|
97
99
|
this.register("toggle", id, handler as Handler<CallbackEvent>);
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
onPopover(handler: Handler<PopoverEvent>): void {
|
|
103
|
+
this.register("open", "popover", handler as Handler<CallbackEvent>);
|
|
104
|
+
this.register("close", "popover", handler as Handler<CallbackEvent>);
|
|
105
|
+
}
|
|
106
|
+
|
|
100
107
|
isPopoverOpen(): boolean {
|
|
101
108
|
return this.popoverOpen;
|
|
102
109
|
}
|
|
103
110
|
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
log(...args: unknown[]): void {
|
|
112
|
+
process.stderr.write(args.map(String).join(" ") + "\n");
|
|
106
113
|
}
|
|
107
114
|
|
|
108
|
-
protected
|
|
109
|
-
|
|
115
|
+
protected async showNotification(summary: string, options: ShowNotificationOptions = {}): Promise<void> {
|
|
116
|
+
const args = [summary];
|
|
117
|
+
if (options.body !== undefined) {
|
|
118
|
+
args.push(options.body);
|
|
119
|
+
}
|
|
120
|
+
await this.runDesktopCommand("notify-send", args);
|
|
110
121
|
}
|
|
111
122
|
|
|
112
|
-
protected
|
|
113
|
-
this.
|
|
123
|
+
protected async openUri(uri: string): Promise<void> {
|
|
124
|
+
await this.runDesktopCommand("xdg-open", [uri]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
protected async copyToClipboard(text: string): Promise<void> {
|
|
128
|
+
await this.runDesktopCommand("wl-copy", [], text);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected async runCommand(command: string[]): Promise<CommandResult> {
|
|
132
|
+
return await runCommand(command);
|
|
114
133
|
}
|
|
115
134
|
|
|
116
135
|
protected dismissNotification(args: DismissNotificationArgs): void {
|
|
@@ -121,6 +140,10 @@ export abstract class Applet<State extends object> {
|
|
|
121
140
|
this.emitAction("close_popover", {});
|
|
122
141
|
}
|
|
123
142
|
|
|
143
|
+
protected async runDesktopCommand(command: string, args: string[], input?: string): Promise<void> {
|
|
144
|
+
await runDesktopCommand([command, ...args], input);
|
|
145
|
+
}
|
|
146
|
+
|
|
124
147
|
async run(): Promise<void> {
|
|
125
148
|
process.stdout.on("error", (err: NodeJS.ErrnoException) => {
|
|
126
149
|
if (err.code === "EPIPE") {
|
|
@@ -129,6 +152,10 @@ export abstract class Applet<State extends object> {
|
|
|
129
152
|
});
|
|
130
153
|
|
|
131
154
|
await this.onStart();
|
|
155
|
+
const cssClass = this.cssClass();
|
|
156
|
+
if (cssClass !== null) {
|
|
157
|
+
process.stdout.write(`class ${cssClass}\n`);
|
|
158
|
+
}
|
|
132
159
|
await this.scheduleRender();
|
|
133
160
|
|
|
134
161
|
const rl = createInterface({
|
|
@@ -171,7 +198,13 @@ export abstract class Applet<State extends object> {
|
|
|
171
198
|
}
|
|
172
199
|
|
|
173
200
|
private async dispatchCallback(event: CallbackEvent): Promise<void> {
|
|
174
|
-
const
|
|
201
|
+
const key = `${event.event}:${event.id}`;
|
|
202
|
+
const inlineHandler = this.inlineHandlerMap.get(key);
|
|
203
|
+
if (inlineHandler !== undefined) {
|
|
204
|
+
await inlineHandler(event);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const handler = this.handlerMap.get(key);
|
|
175
208
|
if (handler !== undefined) {
|
|
176
209
|
await handler(event);
|
|
177
210
|
return;
|
|
@@ -221,6 +254,13 @@ export abstract class Applet<State extends object> {
|
|
|
221
254
|
}
|
|
222
255
|
|
|
223
256
|
const widget = await this.popover(this.state);
|
|
257
|
+
if (widget !== null) {
|
|
258
|
+
const registry = new InlineHandlerRegistry();
|
|
259
|
+
widget.bindHandlers?.(registry, []);
|
|
260
|
+
this.inlineHandlerMap = registry.handlers;
|
|
261
|
+
} else {
|
|
262
|
+
this.inlineHandlerMap = new Map();
|
|
263
|
+
}
|
|
224
264
|
const tree = { root: widget?.toProtocol() ?? null };
|
|
225
265
|
if (!deepEqual(tree, this.lastTree)) {
|
|
226
266
|
this.lastTree = tree;
|
|
@@ -268,3 +308,40 @@ function deepEqual(left: unknown, right: unknown): boolean {
|
|
|
268
308
|
function isPopoverEvent(event: CallbackEvent): event is PopoverEvent {
|
|
269
309
|
return event.event === "open" || event.event === "close";
|
|
270
310
|
}
|
|
311
|
+
|
|
312
|
+
export async function runCommand(command: string[]): Promise<CommandResult> {
|
|
313
|
+
return await runCommandWithInput(command);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function runDesktopCommand(command: string[], input?: string): Promise<void> {
|
|
317
|
+
const result = await runCommandWithInput(command, input);
|
|
318
|
+
if (result.rc !== 0) {
|
|
319
|
+
throw new Error(`${command[0]} exited with status ${result.rc}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function runCommandWithInput(command: string[], input?: string): Promise<CommandResult> {
|
|
324
|
+
if (command.length === 0) {
|
|
325
|
+
throw new Error("command must not be empty");
|
|
326
|
+
}
|
|
327
|
+
return await new Promise<CommandResult>((resolve, reject) => {
|
|
328
|
+
const child = spawn(command[0], command.slice(1), { stdio: ["pipe", "pipe", "pipe"] });
|
|
329
|
+
const stdout: Buffer[] = [];
|
|
330
|
+
const stderr: Buffer[] = [];
|
|
331
|
+
child.on("error", reject);
|
|
332
|
+
child.stdout.on("data", (chunk: Buffer) => stdout.push(chunk));
|
|
333
|
+
child.stderr.on("data", (chunk: Buffer) => stderr.push(chunk));
|
|
334
|
+
child.on("close", (code) => {
|
|
335
|
+
resolve({
|
|
336
|
+
stdout: Buffer.concat(stdout).toString("utf8"),
|
|
337
|
+
stderr: Buffer.concat(stderr).toString("utf8"),
|
|
338
|
+
rc: code ?? -1,
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
if (input !== undefined) {
|
|
342
|
+
child.stdin.end(input);
|
|
343
|
+
} else {
|
|
344
|
+
child.stdin.end();
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
package/src/events.ts
CHANGED
|
@@ -10,7 +10,7 @@ interface CallbackEventBase {
|
|
|
10
10
|
|
|
11
11
|
export interface ClickEvent extends CallbackEventBase {
|
|
12
12
|
event: "click";
|
|
13
|
-
button?: string;
|
|
13
|
+
button?: string | null;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface ScrollEvent extends CallbackEventBase {
|
|
@@ -57,7 +57,7 @@ export function parseCallbackEvent(payload: Record<string, unknown>): CallbackEv
|
|
|
57
57
|
const event = String(payload.type ?? payload.event ?? "");
|
|
58
58
|
const id = String(payload.id ?? "");
|
|
59
59
|
if (event === "click") {
|
|
60
|
-
return { id, event, button: payload.button === undefined ?
|
|
60
|
+
return { id, event, button: payload.button === undefined ? null : String(payload.button) };
|
|
61
61
|
}
|
|
62
62
|
if (event === "scroll") {
|
|
63
63
|
return {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { Applet } from "./app.js";
|
|
1
|
+
export { Applet, runCommand } from "./app.js";
|
|
2
2
|
export {
|
|
3
3
|
type CallbackEvent,
|
|
4
4
|
type ChangeEvent,
|
|
@@ -11,55 +11,58 @@ export {
|
|
|
11
11
|
parseCallbackEvent,
|
|
12
12
|
parseInitEvent,
|
|
13
13
|
} from "./events.js";
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
14
|
+
export { ipc, IpcError, Subscriber } from "./ipc.js";
|
|
15
|
+
export { StatusItem } from "./protocol.js";
|
|
16
16
|
export {
|
|
17
|
-
ActionItem,
|
|
18
|
-
type Align,
|
|
19
17
|
Badge,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
type BadgeKind,
|
|
19
|
+
BatteryHero,
|
|
20
|
+
BoxedList,
|
|
21
|
+
ButtonRow,
|
|
22
|
+
Calendar,
|
|
23
|
+
CameraIndicator,
|
|
24
|
+
Choice,
|
|
25
|
+
ChoiceList,
|
|
26
|
+
ChoiceTile,
|
|
27
|
+
CircleBox,
|
|
25
28
|
Column,
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
Container,
|
|
30
|
+
DateHero,
|
|
28
31
|
EmptyState,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
EventItem,
|
|
33
|
+
Events,
|
|
34
|
+
ExpanderTile,
|
|
35
|
+
Header,
|
|
32
36
|
Hero,
|
|
33
|
-
|
|
34
|
-
Item,
|
|
37
|
+
KeyValueGrid,
|
|
35
38
|
Label,
|
|
36
|
-
|
|
37
|
-
type LevelBarMode,
|
|
38
|
-
LinkButton,
|
|
39
|
-
ListBox,
|
|
40
|
-
MenuButton,
|
|
39
|
+
LocationIndicator,
|
|
41
40
|
Meter,
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
MicIndicator,
|
|
42
|
+
MutedIndicator,
|
|
44
43
|
type PagerAppearance,
|
|
45
44
|
PagerItem,
|
|
46
45
|
PagerStrip,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
type
|
|
50
|
-
PropertyList,
|
|
46
|
+
PanelIndicator,
|
|
47
|
+
PopoverShell,
|
|
48
|
+
type PopoverSize,
|
|
51
49
|
Row,
|
|
50
|
+
ScreenCastIndicator,
|
|
52
51
|
Scroll,
|
|
53
|
-
|
|
54
|
-
Select,
|
|
55
|
-
SelectOption,
|
|
52
|
+
SegmentedTile,
|
|
56
53
|
Separator,
|
|
57
|
-
|
|
54
|
+
SliderTile,
|
|
58
55
|
Spinner,
|
|
59
56
|
StatusDot,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
type StatusDotStatus,
|
|
58
|
+
SwitchTile,
|
|
59
|
+
Tile,
|
|
63
60
|
type TreeNode,
|
|
64
|
-
|
|
61
|
+
WeatherForecastItem,
|
|
62
|
+
WeatherForecastList,
|
|
63
|
+
WeatherHourlyItem,
|
|
64
|
+
WeatherHourlyStrip,
|
|
65
|
+
WidgetBase,
|
|
66
|
+
WorldClock,
|
|
67
|
+
WorldClockRow,
|
|
65
68
|
} from "./widgets.js";
|
package/src/protocol.ts
CHANGED
|
@@ -1,29 +1,8 @@
|
|
|
1
|
-
export type IconKind = "name" | "path";
|
|
2
|
-
|
|
3
|
-
export class Icon {
|
|
4
|
-
constructor(
|
|
5
|
-
public readonly kind: IconKind,
|
|
6
|
-
public readonly value: string,
|
|
7
|
-
) {}
|
|
8
|
-
|
|
9
|
-
static name(value: string): Icon {
|
|
10
|
-
return new Icon("name", value);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
static path(value: string): Icon {
|
|
14
|
-
return new Icon("path", value);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
toProtocol(): { name: string } | { path: string } {
|
|
18
|
-
return this.kind === "name" ? { name: this.value } : { path: this.value };
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
1
|
export class StatusItem {
|
|
23
2
|
constructor(
|
|
24
3
|
public readonly options: {
|
|
25
4
|
id?: string;
|
|
26
|
-
icon?:
|
|
5
|
+
icon?: string;
|
|
27
6
|
label?: string;
|
|
28
7
|
tooltip?: string;
|
|
29
8
|
} = {},
|
|
@@ -35,7 +14,7 @@ export class StatusItem {
|
|
|
35
14
|
payload.id = this.options.id;
|
|
36
15
|
}
|
|
37
16
|
if (this.options.icon !== undefined) {
|
|
38
|
-
payload.icon = this.options.icon
|
|
17
|
+
payload.icon = this.options.icon;
|
|
39
18
|
}
|
|
40
19
|
if (this.options.label !== undefined) {
|
|
41
20
|
payload.label = this.options.label;
|