@fluxlay/react 1.2.0 → 1.3.1

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 CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  SDK for building [Fluxlay](https://fluxlay.com) wallpapers with React.
4
4
 
5
+ - Documentation: <https://fluxlay.com/docs/developer/tutorials/getting-started>
6
+ - SDK reference: <https://fluxlay.com/docs/developer/reference/sdk/use-mouse-position>
7
+ - Examples: <https://github.com/fluxlay/examples>
8
+
5
9
  ## Installation
6
10
 
7
11
  ```sh
@@ -10,28 +14,169 @@ npm install @fluxlay/react
10
14
  bun add @fluxlay/react
11
15
  ```
12
16
 
13
- Also import the stylesheet in your entry file:
17
+ The SDK ships its own styles (xterm CSS is imported automatically by `useTerminal`); no additional stylesheet import is required.
14
18
 
15
- ```ts
16
- import "@fluxlay/react/dist/sdk.css";
17
- ```
19
+ ## Runtime constraints
20
+
21
+ Wallpapers run in an isolated `fluxlay://` origin with a minimal Content Security Policy enforced by the desktop app. As a result:
22
+
23
+ - **No external hosts.** `fetch` / `XMLHttpRequest` / `WebSocket` to arbitrary origins is blocked by `connect-src`. The only non-`'self'` endpoint allowed by default is the local Fluxlay API (`http://127.0.0.1:*`), which the SDK uses internally. To reach an external host, declare it under `network:` in `fluxlay.yaml` — its scheme + host[:port] is then injected into `connect-src` / `img-src` / `media-src` / `font-src` (passive resources only; never `script-src`).
24
+ - **No `eval` / `new Function`.** `script-src` is `'self'` only — no `'unsafe-eval'`, no `'unsafe-inline'`. Bundlers, template engines, and runtime-compiled code paths that rely on `eval` will not run. (Dev mode loosens this for HMR; production does not.)
25
+ - **No `__TAURI__.core.invoke`.** Wallpaper windows are excluded from every Tauri capability, so `invoke` is unavailable. All host interactions must go through SDK hooks, which talk to the local API over HTTP / WebSocket.
26
+ - **Inline styles are allowed** (`style-src 'self' 'unsafe-inline'`) so React `style={...}` and CSS-in-JS keep working.
18
27
 
19
28
  ## API
20
29
 
21
30
  ### `useMousePosition()`
22
31
 
23
- Subscribes to the global mouse position streamed from the Fluxlay backend. The Y axis is inverted so that positive values point upward (standard mathematical coordinates).
32
+ Subscribes to mouse position events for the current wallpaper window. Both axes are normalized to `[-1, 1]` (`x`: `-1` left → `1` right; `y`: `-1` bottom → `1` top — the Y axis is inverted to match a standard mathematical coordinate system, opposite to CSS). Returns `{ x: 0, y: 0 }` until the first event is received or when running outside the Fluxlay desktop app.
24
33
 
25
34
  ```tsx
26
35
  import { useMousePosition } from "@fluxlay/react";
27
36
 
28
37
  function Wallpaper() {
29
38
  const { x, y } = useMousePosition();
30
- return <div style={{ transform: `translate(${x}px, ${y}px)` }} />;
39
+ // Convert normalized coordinates to pixels, flipping Y back to CSS orientation.
40
+ const px = ((x + 1) / 2) * window.innerWidth;
41
+ const py = (1 - (y + 1) / 2) * window.innerHeight;
42
+ return <div style={{ transform: `translate(${px}px, ${py}px)` }} />;
43
+ }
44
+ ```
45
+
46
+ ### `useMouseEvents(handlers)`
47
+
48
+ Subscribes to mouse click and wheel events for the current wallpaper window. Coordinates are normalized to `[-1, 1]` with the Y axis inverted (positive Y points upward).
49
+
50
+ ```tsx
51
+ import { useMouseEvents } from "@fluxlay/react";
52
+
53
+ function Wallpaper() {
54
+ useMouseEvents({
55
+ onButton: event => {
56
+ if (event.pressed) console.log("pressed", event.button, event.x, event.y);
57
+ },
58
+ onWheel: event => {
59
+ console.log("scroll", event.deltaX, event.deltaY);
60
+ }
61
+ });
62
+ return <div />;
63
+ }
64
+ ```
65
+
66
+ On macOS, mouse events require the user to grant Fluxlay "Input Monitoring" permission. On Windows, events from windows running with elevated privileges are not delivered (UIPI).
67
+
68
+ ### `useKeyboard(handlers)`
69
+
70
+ Subscribes to global keyboard events. Events are delivered to every subscriber on every window while the user is anywhere on the desktop, which is useful for game-like wallpapers. `event.code` follows the Web `KeyboardEvent.code` convention (e.g. `"KeyA"`, `"Space"`, `"ArrowLeft"`, `"ShiftLeft"`) and identifies a physical key independent of layout.
71
+
72
+ Because this hook receives every keystroke the user types anywhere on the desktop — including text typed into other applications — it requires the wallpaper to declare the `keyboard` permission in `fluxlay.yaml`. Without the declaration, the backend rejects the subscription with HTTP 403 and no events are delivered.
73
+
74
+ ```yaml
75
+ # fluxlay.yaml
76
+ schemaVersion: 1
77
+ name: My Wallpaper
78
+ slug: my-wallpaper
79
+ version: 1.0.0
80
+ permissions:
81
+ - keyboard
82
+ ```
83
+
84
+ ```tsx
85
+ import { useKeyboard } from "@fluxlay/react";
86
+
87
+ function Wallpaper() {
88
+ useKeyboard({
89
+ onKeyDown: event => console.log("down", event.code, event.modifiers, event.repeat),
90
+ onKeyUp: event => console.log("up", event.code)
91
+ });
92
+ return <div />;
93
+ }
94
+ ```
95
+
96
+ Same platform requirements as `useMouseEvents` (macOS Input Monitoring permission; UIPI on Windows).
97
+
98
+ ### `useImeInput()`
99
+
100
+ Receives IME-composed text (Japanese / Chinese / Korean / etc.) for wallpapers that want to accept text input — for example a wallpaper-resident notepad, search box, or chat input.
101
+
102
+ Unlike `useKeyboard`, which sees raw pre-IME keystrokes from every window on the desktop, `useImeInput` only delivers text that the user explicitly directs at the wallpaper. Internally Fluxlay creates a tiny invisible proxy window per wallpaper; calling `activate()` (typically in response to the user clicking an in-wallpaper input field) gives that proxy keyboard focus so IME composition is routed through it. The wallpaper window itself never becomes a key window.
103
+
104
+ Wallpapers must declare the `ime-input` permission in `fluxlay.yaml`:
105
+
106
+ ```yaml
107
+ # fluxlay.yaml
108
+ permissions:
109
+ - ime-input
110
+ ```
111
+
112
+ ```tsx
113
+ import { useImeInput } from "@fluxlay/react";
114
+ import { useEffect, useState } from "react";
115
+
116
+ function NotePad() {
117
+ const ime = useImeInput();
118
+ const [text, setText] = useState("");
119
+
120
+ useEffect(() => {
121
+ return ime.onCommit(committed => setText(prev => prev + committed));
122
+ }, [ime]);
123
+
124
+ return (
125
+ <div onClick={ime.activate} onBlur={ime.deactivate}>
126
+ <span>{text}</span>
127
+ {ime.composition !== null && <span style={{ opacity: 0.5 }}>{ime.composition}</span>}
128
+ </div>
129
+ );
130
+ }
131
+ ```
132
+
133
+ The hook returns:
134
+
135
+ - `composition: string | null` — the in-progress composition text (e.g. `"か"` while typing `"漢字"`), or `null` when no composition is active
136
+ - `cursor: number` — caret position within `composition`, in code points
137
+ - `activate()` / `deactivate()` — start / stop receiving IME input. The hook automatically calls `deactivate()` on unmount
138
+ - `onCommit(handler)` — register a callback for finalized text. Returns a cleanup function
139
+
140
+ While `activate()` is in effect, `useKeyboard` events are paused on the same wallpaper to avoid IME candidate-window keys (arrows, Enter, Esc) double-firing as raw key events.
141
+
142
+ If the wallpaper does not declare `ime-input`, the hook becomes a no-op and emits one `console.warn` describing the missing declaration.
143
+
144
+ ### `useProperties<T>()`
145
+
146
+ Returns the current values of the custom properties declared under `properties:` in `fluxlay.yaml`. Updates in real time when the user adjusts settings in the Fluxlay app.
147
+
148
+ ```tsx
149
+ import { useProperties } from "@fluxlay/react";
150
+
151
+ type Props = {
152
+ particleCount: number;
153
+ themeColor: string;
154
+ showClock: boolean;
155
+ };
156
+
157
+ function Wallpaper() {
158
+ const { particleCount, themeColor, showClock } = useProperties<Props>();
159
+ return <div style={{ color: themeColor }}>{particleCount}</div>;
31
160
  }
32
161
  ```
33
162
 
34
- ### `useSystemMonitor()`
163
+ For `image` / `file` properties, the value is a local file path on the host OS (or `null` if the user has not selected a file). Pass it through `getPropertyFileUrl()` to obtain a URL the webview can load.
164
+
165
+ ### `getPropertyFileUrl(path)`
166
+
167
+ Builds a URL the wallpaper webview can use to load a local file referenced by an `image` or `file` property. Returns `null` when the path is missing.
168
+
169
+ ```tsx
170
+ import { useProperties, getPropertyFileUrl } from "@fluxlay/react";
171
+
172
+ function Wallpaper() {
173
+ const { customLogo } = useProperties<{ customLogo: string | null }>();
174
+ const url = getPropertyFileUrl(customLogo);
175
+ return url ? <img src={url} alt="logo" /> : null;
176
+ }
177
+ ```
178
+
179
+ ### `useSystemMonitor(options?)`
35
180
 
36
181
  Subscribes to real-time system and hardware metrics streamed from the Fluxlay backend. Returns CPU, memory, battery, network, disk, and other system information.
37
182
 
@@ -39,7 +184,11 @@ Subscribes to real-time system and hardware metrics streamed from the Fluxlay ba
39
184
  import { useSystemMonitor } from "@fluxlay/react";
40
185
 
41
186
  function Wallpaper() {
42
- const { cpuUsage, memoryUsage, batteryLevel } = useSystemMonitor();
187
+ const { cpuUsage, memoryUsage, batteryLevel } = useSystemMonitor({
188
+ cpuIntervalMs: 500,
189
+ memoryIntervalMs: 1000,
190
+ batteryIntervalMs: 10000
191
+ });
43
192
  return (
44
193
  <div style={{ fontFamily: "monospace" }}>
45
194
  <p>CPU: {cpuUsage.toFixed(1)}%</p>
@@ -50,25 +199,26 @@ function Wallpaper() {
50
199
  }
51
200
  ```
52
201
 
53
- Refresh intervals can be configured per-category in `fluxlay.yaml`:
202
+ | Option | Type | Default | Description |
203
+ | ----------------------- | -------- | ------- | ----------------------------------------- |
204
+ | `cpuIntervalMs` | `number` | `500` | CPU usage, per-core usage, and frequency. |
205
+ | `memoryIntervalMs` | `number` | `1000` | Memory and swap usage. |
206
+ | `networkIntervalMs` | `number` | `1000` | Network receive/transmit speed. |
207
+ | `diskIoIntervalMs` | `number` | `2000` | Disk read/write speed. |
208
+ | `diskSpaceIntervalMs` | `number` | `30000` | Disk capacity per mount point. |
209
+ | `batteryIntervalMs` | `number` | `10000` | Battery level and charging status. |
210
+ | `processIntervalMs` | `number` | `10000` | Running process count. |
211
+ | `loadAverageIntervalMs` | `number` | `5000` | System load average. |
54
212
 
55
- ```yaml
56
- sdk:
57
- system_monitor:
58
- cpu_interval_ms: 500
59
- memory_interval_ms: 1000
60
- battery_interval_ms: 10000
61
- ```
62
-
63
- ### `useAudio()`
213
+ ### `useAudio(options?)`
64
214
 
65
- Subscribes to real-time system audio information streamed from the Fluxlay backend. Returns RMS volume, peak level, and a 32-band frequency spectrum, useful for building audio visualizers.
215
+ Subscribes to real-time system audio information streamed from the Fluxlay backend. Returns RMS volume, peak level, and a logarithmically-scaled frequency spectrum useful for building audio visualizers.
66
216
 
67
217
  ```tsx
68
218
  import { useAudio } from "@fluxlay/react";
69
219
 
70
220
  function Wallpaper() {
71
- const { rms, peak, spectrum } = useAudio();
221
+ const { rms, peak, spectrum } = useAudio({ numBands: 64 });
72
222
  return (
73
223
  <div>
74
224
  <p>Volume: {(rms * 100).toFixed(0)}%</p>
@@ -82,13 +232,19 @@ function Wallpaper() {
82
232
  }
83
233
  ```
84
234
 
85
- | Field | Type | Description |
86
- | ---------- | ---------- | ------------------------------------------------------------------------------- |
87
- | `rms` | `number` | RMS (Root Mean Square) volume level (0.0 – 1.0). |
88
- | `peak` | `number` | Peak volume level (0.0 – 1.0). |
89
- | `spectrum` | `number[]` | Frequency spectrum split into 32 logarithmically-scaled bands (0.0 – 1.0 each). |
235
+ | Option | Type | Default | Description |
236
+ | ---------- | -------- | ------- | ----------------------------------- |
237
+ | `numBands` | `number` | `32` | Number of frequency spectrum bands. |
238
+
239
+ | Return field | Type | Description |
240
+ | ------------ | ---------- | -------------------------------------------------------------------------------------------- |
241
+ | `rms` | `number` | RMS (Root Mean Square) volume level (0.0 – 1.0). |
242
+ | `peak` | `number` | Peak volume level (0.0 – 1.0). |
243
+ | `spectrum` | `number[]` | Frequency spectrum bands on a logarithmic scale (0.0 – 1.0 each). Length matches `numBands`. |
244
+
245
+ On macOS, the Fluxlay desktop app captures system audio via the Core Audio Tap API and prompts the user for audio capture permission (`NSAudioCaptureUsageDescription`); macOS 14.2+ is required. The spectrum is A-weighted (IEC 61672) so the frequency balance matches human hearing.
90
246
 
91
- ### `useMediaMetadata()`
247
+ ### `useMediaMetadata(options?)`
92
248
 
93
249
  Subscribes to media metadata for the currently playing track from system media players (e.g. Spotify, Apple Music). Returns title, artist, album artwork, playback progress, and playback state.
94
250
 
@@ -96,7 +252,9 @@ Subscribes to media metadata for the currently playing track from system media p
96
252
  import { useMediaMetadata } from "@fluxlay/react";
97
253
 
98
254
  function Wallpaper() {
99
- const { title, artist, artwork, isPlaying, elapsedTime, duration } = useMediaMetadata();
255
+ const { title, artist, artwork, isPlaying, elapsedTime, duration } = useMediaMetadata({
256
+ intervalMs: 1000
257
+ });
100
258
  return (
101
259
  <div>
102
260
  {artwork && <img src={artwork} alt="Album art" width={200} />}
@@ -107,13 +265,9 @@ function Wallpaper() {
107
265
  }
108
266
  ```
109
267
 
110
- Polling interval can be configured in `fluxlay.yaml`:
111
-
112
- ```yaml
113
- sdk:
114
- media_metadata:
115
- interval_ms: 1000
116
- ```
268
+ | Option | Type | Default | Description |
269
+ | ------------ | -------- | ------- | --------------------------------- |
270
+ | `intervalMs` | `number` | `1000` | Polling interval in milliseconds. |
117
271
 
118
272
  | Field | Type | Description |
119
273
  | -------------- | ---------------- | ---------------------------------------------------- |
@@ -141,14 +295,14 @@ function Wallpaper() {
141
295
  }
142
296
  ```
143
297
 
144
- | Option | Type | Default | Description |
145
- | ----------------- | ------------------ | ------- | -------------------------------------------- |
146
- | `refreshInterval` | `number` | `30000` | Auto-refresh interval in ms. `0` to disable. |
147
- | `showStdout` | `boolean` | `true` | Write stdout to the linked terminal. |
148
- | `showStderr` | `boolean` | `false` | Write stderr to the linked terminal. |
149
- | `terminal` | `TerminalInstance` | — | Terminal instance from `useTerminal`. |
150
- | `columns` | `number` | — | Pseudo-terminal column width. |
151
- | `lines` | `number` | — | Pseudo-terminal row height. |
298
+ | Option | Type | Default | Description |
299
+ | ----------------- | -------------------------- | ------- | ------------------------------------------------------------------------------------------------------- |
300
+ | `refreshInterval` | `number` | `30000` | Auto-refresh interval in ms. `0` to disable. |
301
+ | `showStdout` | `boolean` | `true` | Write stdout to the linked terminal. |
302
+ | `showStderr` | `boolean` | `false` | Write stderr to the linked terminal. |
303
+ | `terminal` | `TerminalInstance \| null` | — | Terminal instance from `useTerminal`. When set, the terminal's dimensions override `columns` / `lines`. |
304
+ | `columns` | `number` | — | Pseudo-terminal column width. |
305
+ | `lines` | `number` | — | Pseudo-terminal line height. |
152
306
 
153
307
  Returns `{ execute, isRunning, error, result }`.
154
308