diff --git a/.github/workflows/marketplace-build.yml b/.github/workflows/marketplace-build.yml index e21552da..62b588bc 100644 --- a/.github/workflows/marketplace-build.yml +++ b/.github/workflows/marketplace-build.yml @@ -42,7 +42,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y cmake pkg-config libclang-dev wget libopenblas-dev zstd patchelf python3-yaml python3-tomli + sudo apt-get install -y cmake pkg-config libclang-dev libfontconfig1-dev wget libopenblas-dev zstd patchelf python3-yaml python3-tomli - name: Install minisign run: | diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 01450ab6..44538766 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -55,6 +55,10 @@ jobs: working-directory: plugins/native/nllb run: cargo fmt -- --check + - name: Check formatting - Slint + working-directory: plugins/native/slint + run: cargo fmt -- --check + # Lint plugins that can build without pre-installed native libraries lint-simple: name: Lint (Simple Plugins) @@ -65,7 +69,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y cmake pkg-config libclang-dev + sudo apt-get install -y cmake pkg-config libclang-dev libfontconfig1-dev - name: Install Rust toolchain uses: dtolnay/rust-toolchain@master @@ -79,12 +83,17 @@ jobs: with: workspaces: | plugins/native/vad + plugins/native/slint cache-on-failure: true - name: Clippy - VAD working-directory: plugins/native/vad run: cargo clippy -- -D warnings + - name: Clippy - Slint + working-directory: plugins/native/slint + run: cargo clippy -- -D warnings + # Lint Whisper plugin (builds whisper.cpp from source) lint-whisper: name: Lint (Whisper) diff --git a/crates/api/src/bin/generate_ts_types.rs b/crates/api/src/bin/generate_ts_types.rs index 91386f98..f3395d42 100644 --- a/crates/api/src/bin/generate_ts_types.rs +++ b/crates/api/src/bin/generate_ts_types.rs @@ -78,6 +78,8 @@ fn main() -> Result<(), Box> { format!("export {}", streamkit_api::yaml::OutputType::decl(&cfg)), format!("export {}", streamkit_api::yaml::FieldHint::decl(&cfg)), format!("export {}", streamkit_api::yaml::FieldType::decl(&cfg)), + format!("export {}", streamkit_api::yaml::ControlType::decl(&cfg)), + format!("export {}", streamkit_api::yaml::ControlConfig::decl(&cfg)), ]; let output = declarations.join("\n\n"); diff --git a/crates/api/src/yaml.rs b/crates/api/src/yaml.rs index 194faf01..bb00d737 100644 --- a/crates/api/src/yaml.rs +++ b/crates/api/src/yaml.rs @@ -119,6 +119,9 @@ pub struct ClientSection { pub input: Option, /// Output rendering configuration (oneshot pipelines). pub output: Option, + /// Declarative overlay controls for runtime node tuning (dynamic pipelines). + #[serde(default)] + pub controls: Option>, } /// Browser-side publish configuration for dynamic pipelines. @@ -322,6 +325,75 @@ pub struct FieldHint { pub placeholder: Option, } +// --------------------------------------------------------------------------- +// Declarative overlay controls — interactive widgets for runtime tuning +// --------------------------------------------------------------------------- + +/// The kind of interactive control widget rendered in the StreamView. +#[derive(Debug, Clone, Copy, Deserialize, Serialize, TS)] +#[ts(export)] +#[serde(rename_all = "snake_case")] +pub enum ControlType { + /// Boolean on/off switch. + Toggle, + /// Debounced text input. + Text, + /// Numeric slider with min/max/step. + Number, + /// Action button that sends a fixed value on click. + Button, +} + +/// A single declarative control entry in the `client.controls` array. +/// +/// Each control targets a specific node + property and renders as a widget +/// in the StreamView. On interaction the frontend sends a `TuneNodeAsync` +/// / `UpdateParams` message to the targeted node. +/// +/// The `property` field uses dot-notation paths (e.g. `"properties.home_score"`) +/// so the frontend can build the correct nested JSON payload. A flat path +/// like `"gain_db"` produces `{"gain_db": }`. +#[derive(Debug, Clone, Deserialize, Serialize, TS)] +#[ts(export)] +pub struct ControlConfig { + /// Human-readable label shown next to the widget. + pub label: String, + /// Widget type. + #[serde(rename = "type")] + pub control_type: ControlType, + /// Target node ID in the pipeline graph. + pub node: String, + /// Dot-notation property path, e.g. `"properties.home_score"`. + pub property: String, + /// Optional grouping label — controls with the same group are rendered + /// together under a shared heading. + #[serde(default)] + pub group: Option, + /// Initial value for the UI widget. This is a **UI-only hint** — it + /// seeds the local component state but is *not* sent to the server on + /// mount. Pipeline authors should ensure defaults here match the + /// node's own initial params to avoid a visual desync before the first + /// user interaction. + #[serde(default)] + #[ts(type = "unknown")] + pub default: Option, + // -- Number-only fields -- + /// Minimum value (number controls). + #[serde(default)] + pub min: Option, + /// Maximum value (number controls). + #[serde(default)] + pub max: Option, + /// Step increment (number controls). + #[serde(default)] + pub step: Option, + // -- Button-only field -- + /// Fixed value sent on click (button controls). Defaults to `true`. + #[serde(default)] + #[ts(type = "unknown")] + pub value: Option, +} + // --------------------------------------------------------------------------- // User-facing pipeline definition // --------------------------------------------------------------------------- @@ -686,7 +758,7 @@ pub struct ClientLintWarning { /// 1. **`mode-mismatch-dynamic`** — Dynamic pipeline declares oneshot-only /// fields (`input` / `output`). /// 2. **`mode-mismatch-oneshot`** — Oneshot pipeline declares dynamic-only -/// fields (`publish` / `watch` / `gateway_path` / `relay_url`). +/// fields (`publish` / `watch` / `gateway_path` / `relay_url` / `controls`). /// 3. **`missing-gateway`** — Dynamic pipeline has `publish` or `watch` /// but no `gateway_path` or `relay_url`. /// 4. **`publish-no-media`** — `publish` block sets both `audio` and @@ -720,7 +792,8 @@ pub fn lint_client_section(client: &ClientSection, mode: EngineMode) -> Vec Vec Vec { + /// The user-facing node ID (the key in the `nodes:` map). + pub name: &'a str, pub kind: &'a str, pub params: Option<&'a serde_json::Value>, } @@ -1076,6 +1152,10 @@ pub struct NodeInfo<'a> { /// `transport::moq::subscriber` node. /// 20. **`broadcast-mismatch`** — `publish.broadcast` or `watch.broadcast` /// does not match any broadcast name configured on MoQ transport nodes. +/// 21. **`control-unknown-node`** — a `controls` entry targets a `node` that +/// does not exist in the pipeline's `nodes` map. +/// 22. **`control-number-no-bounds`** — a `number` control is missing `min` +/// and/or `max`, so the slider will lack proper bounds. pub fn lint_client_against_nodes( client: &ClientSection, _mode: EngineMode, @@ -1305,6 +1385,50 @@ pub fn lint_client_against_nodes( } } + // Rule 21: control-unknown-node — control.node not in pipeline's nodes map + // Rule 22: control-number-no-bounds — number control without min/max + if let Some(ref controls) = client.controls { + let node_names: Vec<&str> = nodes.iter().map(|n| n.name).collect(); + + for control in controls { + if !node_names.iter().any(|n| *n == control.node) { + warnings.push(ClientLintWarning { + rule: "control-unknown-node", + message: format!( + "control `{}` targets node `{}` which does not exist in the pipeline. \ + Known nodes: {}.", + control.label, + control.node, + if node_names.is_empty() { + "(none)".to_string() + } else { + node_names.join(", ") + } + ), + }); + } + + if matches!(control.control_type, ControlType::Number) + && (control.min.is_none() || control.max.is_none()) + { + warnings.push(ClientLintWarning { + rule: "control-number-no-bounds", + message: format!( + "control `{}` is type `number` but is missing {} — the slider \ + will not have proper bounds.", + control.label, + match (control.min.is_none(), control.max.is_none()) { + (true, true) => "`min` and `max`", + (true, false) => "`min`", + (false, true) => "`max`", + _ => unreachable!(), + } + ), + }); + } + } + } + warnings } @@ -2075,6 +2199,7 @@ client: }), input: None, output: None, + ..Default::default() } } @@ -2093,6 +2218,7 @@ client: field_hints: None, }), output: Some(OutputConfig { output_type: OutputType::Audio }), + ..Default::default() } } @@ -2130,6 +2256,25 @@ client: assert!(warnings.iter().any(|w| w.rule == "mode-mismatch-oneshot")); } + #[test] + fn test_lint_mode_mismatch_oneshot_with_controls() { + let mut c = oneshot_client(); + c.controls = Some(vec![ControlConfig { + label: "Toggle".into(), + control_type: ControlType::Toggle, + node: "some_node".into(), + property: "enabled".into(), + group: None, + default: None, + min: None, + max: None, + step: None, + value: None, + }]); + let warnings = lint_client_section(&c, EngineMode::OneShot); + assert!(warnings.iter().any(|w| w.rule == "mode-mismatch-oneshot")); + } + #[test] fn test_lint_missing_gateway() { let c = ClientSection { @@ -2150,6 +2295,7 @@ client: watch: None, input: None, output: None, + ..Default::default() }; let warnings = lint_client_section(&c, EngineMode::Dynamic); assert!(warnings.iter().any(|w| w.rule == "missing-gateway")); @@ -2274,6 +2420,7 @@ client: field_hints: None, }), output: Some(OutputConfig { output_type: OutputType::Video }), + ..Default::default() }; let warnings = lint_client_section(&c, EngineMode::OneShot); assert!(warnings.iter().any(|w| w.rule == "input-none-with-accept")); @@ -2294,6 +2441,7 @@ client: field_hints: None, }), output: Some(OutputConfig { output_type: OutputType::Audio }), + ..Default::default() }; let warnings = lint_client_section(&c, EngineMode::OneShot); assert!(warnings.iter().any(|w| w.rule == "input-trigger-with-accept")); @@ -2319,6 +2467,7 @@ client: field_hints: Some(hints), }), output: Some(OutputConfig { output_type: OutputType::Video }), + ..Default::default() }; let warnings = lint_client_section(&c, EngineMode::OneShot); assert!(warnings.iter().any(|w| w.rule == "field-hints-no-input")); @@ -2339,6 +2488,7 @@ client: field_hints: None, }), output: Some(OutputConfig { output_type: OutputType::Audio }), + ..Default::default() }; let warnings = lint_client_section(&c, EngineMode::OneShot); assert!(warnings.iter().any(|w| w.rule == "asset-tags-no-input")); @@ -2359,6 +2509,7 @@ client: field_hints: None, }), output: Some(OutputConfig { output_type: OutputType::Audio }), + ..Default::default() }; let warnings = lint_client_section(&c, EngineMode::OneShot); assert!(warnings.iter().any(|w| w.rule == "text-no-placeholder")); @@ -2374,7 +2525,15 @@ client: } fn node<'a>(kind: &'a str, params: Option<&'a serde_json::Value>) -> NodeInfo<'a> { - NodeInfo { kind, params } + NodeInfo { name: kind, kind, params } + } + + fn named_node<'a>( + name: &'a str, + kind: &'a str, + params: Option<&'a serde_json::Value>, + ) -> NodeInfo<'a> { + NodeInfo { name, kind, params } } // Rule 13 — input-requires-http-input @@ -2777,6 +2936,112 @@ client: ); } + // ----------------------------------------------------------------------- + // Client-vs-nodes cross-validation tests (rules 21–22: controls) + // ----------------------------------------------------------------------- + + // Rule 21 — control-unknown-node + #[test] + fn test_lint_control_unknown_node() { + let c = ClientSection { + controls: Some(vec![ControlConfig { + label: "Show".into(), + control_type: ControlType::Toggle, + node: "nonexistent".into(), + property: "properties.show".into(), + group: None, + default: None, + min: None, + max: None, + step: None, + value: None, + }]), + ..Default::default() + }; + let nodes = vec![named_node("lower_third", "plugin::slint", None)]; + let warnings = lint_client_against_nodes(&c, EngineMode::Dynamic, &nodes); + assert!( + warnings.iter().any(|w| w.rule == "control-unknown-node"), + "Should warn when control targets unknown node: {warnings:?}" + ); + } + + #[test] + fn test_lint_control_known_node_clean() { + let c = ClientSection { + controls: Some(vec![ControlConfig { + label: "Show".into(), + control_type: ControlType::Toggle, + node: "lower_third".into(), + property: "properties.show".into(), + group: None, + default: None, + min: None, + max: None, + step: None, + value: None, + }]), + ..Default::default() + }; + let nodes = vec![named_node("lower_third", "plugin::slint", None)]; + let warnings = lint_client_against_nodes(&c, EngineMode::Dynamic, &nodes); + assert!( + !warnings.iter().any(|w| w.rule == "control-unknown-node"), + "Should not warn when control targets known node: {warnings:?}" + ); + } + + // Rule 22 — control-number-no-bounds + #[test] + fn test_lint_control_number_no_bounds() { + let c = ClientSection { + controls: Some(vec![ControlConfig { + label: "Score".into(), + control_type: ControlType::Number, + node: "scoreboard".into(), + property: "properties.home_score".into(), + group: None, + default: None, + min: None, + max: None, + step: None, + value: None, + }]), + ..Default::default() + }; + let nodes = vec![named_node("scoreboard", "plugin::slint", None)]; + let warnings = lint_client_against_nodes(&c, EngineMode::Dynamic, &nodes); + assert!( + warnings.iter().any(|w| w.rule == "control-number-no-bounds"), + "Should warn when number control has no min/max: {warnings:?}" + ); + } + + #[test] + fn test_lint_control_number_with_bounds_clean() { + let c = ClientSection { + controls: Some(vec![ControlConfig { + label: "Score".into(), + control_type: ControlType::Number, + node: "scoreboard".into(), + property: "properties.home_score".into(), + group: None, + default: None, + min: Some(0.0), + max: Some(99.0), + step: Some(1.0), + value: None, + }]), + ..Default::default() + }; + let nodes = vec![named_node("scoreboard", "plugin::slint", None)]; + let warnings = lint_client_against_nodes(&c, EngineMode::Dynamic, &nodes); + assert!( + !warnings.iter().any(|w| w.rule == "control-number-no-bounds"), + "Should not warn when number control has min and max: {warnings:?}" + ); + } + // ----------------------------------------------------------------------- // Tracks-based publish config tests // ----------------------------------------------------------------------- diff --git a/crates/nodes/src/video/compositor/mod.rs b/crates/nodes/src/video/compositor/mod.rs index 3ad6bef5..ea6a11ec 100644 --- a/crates/nodes/src/video/compositor/mod.rs +++ b/crates/nodes/src/video/compositor/mod.rs @@ -889,11 +889,33 @@ impl ProcessorNode for CompositorNode { // the channel and only the very last one would survive the // drain — producing a tiny handful of output frames instead // of the full expected count. + // + // Frame-aligned synchronization (oneshot only): we check + // readiness of **every** active (non-closed) slot *before* + // dequeuing, so a fast source isn't consumed ahead of a + // slower one. Without this, asymmetric drain causes the + // faster source's channel to close first, its slot to be + // removed, and the compositor to keep running with only the + // slower source — producing frames with missing layers. let mut any_new_frame = false; - for slot in &mut slots { - if is_oneshot { + + if is_oneshot { + // Pass 1: verify all active slots have at least one + // pending frame. Closed channels are considered ready + // (they may still have buffered data to drain). + let all_active_ready = slots.iter().all(|s| !s.rx.is_empty() || s.rx.is_closed()); + + if !all_active_ready { + if slots.iter().all(|s| s.rx.is_closed() && s.rx.is_empty()) { + stop_reason = "all_inputs_closed"; + break; + } + continue; // wait for slower sources to catch up + } + + // Pass 2: all active slots are ready — dequeue one from each. + for slot in &mut slots { if let Ok(Packet::Video(frame)) = slot.rx.try_recv() { - // Detect source dimension changes → trigger layout re-emission. let new_dims = (frame.width, frame.height); if slot.last_source_dims != Some(new_dims) { slot.last_source_dims = Some(new_dims); @@ -902,7 +924,9 @@ impl ProcessorNode for CompositorNode { slot.latest_frame = Some(frame); any_new_frame = true; } - } else { + } + } else { + for slot in &mut slots { let mut latest: Option = None; let mut dropped: u64 = 0; while let Ok(Packet::Video(frame)) = slot.rx.try_recv() { @@ -934,6 +958,8 @@ impl ProcessorNode for CompositorNode { // In oneshot mode, skip compositing when no slot received a // new frame this tick — avoids duplicating stale content. + // (The frame-aligned readiness check above already handles + // the case where active slots are waiting for data.) if is_oneshot && !any_new_frame { if slots.iter().all(|s| s.rx.is_closed() && s.rx.is_empty()) { stop_reason = "all_inputs_closed"; diff --git a/crates/plugin-native/src/wrapper.rs b/crates/plugin-native/src/wrapper.rs index 7989ea36..43d7d866 100644 --- a/crates/plugin-native/src/wrapper.rs +++ b/crates/plugin-native/src/wrapper.rs @@ -674,9 +674,26 @@ impl NativeNodeWrapper { warn!(error = %e, node = %node_name, "Failed to send running state"); } - // Clamp to at least 1µs to prevent panic in tokio::time::interval. - let tick_interval = std::time::Duration::from_micros(self.metadata.tick_interval_us.max(1)); - let max_ticks = self.metadata.max_ticks; + // Re-query source config from the live instance (created with actual + // params) so per-instance values like `max_ticks` (from `frame_count`) + // override the defaults obtained during the load-time probe. + let fallback = || { + let ti = std::time::Duration::from_micros(self.metadata.tick_interval_us.max(1)); + (ti, self.metadata.max_ticks) + }; + let (tick_interval, max_ticks) = self + .state + .api() + .get_source_config + .and_then(|get_source_config_fn| { + self.state.begin_call().map(|h| { + let cfg = get_source_config_fn(h); + self.state.finish_call(); + let ti = std::time::Duration::from_micros(cfg.tick_interval_us.max(1)); + (ti, cfg.max_ticks) + }) + }) + .unwrap_or_else(fallback); let mut tick_count: u64 = 0; let tick_fn = self.state.api().tick.ok_or_else(|| { diff --git a/docs/src/content/docs/reference/plugins/index.md b/docs/src/content/docs/reference/plugins/index.md index 4776d38b..db0cd6a6 100644 --- a/docs/src/content/docs/reference/plugins/index.md +++ b/docs/src/content/docs/reference/plugins/index.md @@ -14,7 +14,7 @@ curl http://localhost:4545/api/v1/plugins curl http://localhost:4545/api/v1/schema/nodes | jq '.[] | select(.kind | startswith("plugin::"))' ``` -## Official plugins (9) +## Official plugins (10) - [`plugin::native::helsinki`](./plugin-native-helsinki/) (original kind: `helsinki`) - [`plugin::native::kokoro`](./plugin-native-kokoro/) (original kind: `kokoro`) @@ -23,5 +23,6 @@ curl http://localhost:4545/api/v1/schema/nodes | jq '.[] | select(.kind | starts - [`plugin::native::piper`](./plugin-native-piper/) (original kind: `piper`) - [`plugin::native::pocket-tts`](./plugin-native-pocket-tts/) (original kind: `pocket-tts`) - [`plugin::native::sensevoice`](./plugin-native-sensevoice/) (original kind: `sensevoice`) +- [`plugin::native::slint`](./plugin-native-slint/) (original kind: `slint`) - [`plugin::native::vad`](./plugin-native-vad/) (original kind: `vad`) - [`plugin::native::whisper`](./plugin-native-whisper/) (original kind: `whisper`) diff --git a/docs/src/content/docs/reference/plugins/plugin-native-slint.md b/docs/src/content/docs/reference/plugins/plugin-native-slint.md new file mode 100644 index 00000000..355bfac9 --- /dev/null +++ b/docs/src/content/docs/reference/plugins/plugin-native-slint.md @@ -0,0 +1,197 @@ +--- +# SPDX-FileCopyrightText: © 2025 StreamKit Contributors +# SPDX-License-Identifier: MPL-2.0 +title: "plugin::native::slint" +description: "Slint UI rendering as a video source — render .slint files to RGBA8 frames at configurable resolution and frame rate." +--- + +`kind`: `plugin::native::slint` (original kind: `slint`) + +Slint UI rendering as a video source — render `.slint` files to RGBA8 frames at configurable resolution and frame rate. + +Source: `target/plugins/release/libslint.so` + +## Categories +- `video` +- `generators` + +## Pins +### Inputs +*(none — this is a source node)* + +### Outputs +- `out` produces `RawVideo(RawVideoFormat { width: None, height: None, pixel_format: Rgba8 })` (broadcast) + +## Parameters +| Name | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| `width` | `integer` | no | `640` | Output frame width in pixels
min: `1` | +| `height` | `integer` | no | `480` | Output frame height in pixels
min: `1` | +| `fps` | `integer` | no | `30` | Output frame rate
min: `1` | +| `slint_file` | `string` | yes | | Path to the `.slint` file | +| `component` | `string` | no | | Name of the exported component to instantiate (defaults to first) | +| `properties` | `object` | no | `{}` | Key-value map of Slint properties (strings, numbers, booleans) | +| `property_keyframes` | `array` | no | `[]` | List of property snapshots to cycle through over time | +| `keyframe_interval` | `integer` | no | `90` | Frames between keyframe switches
min: `1` | +| `frame_count` | `integer` | no | `0` | Total frames to generate (0 = infinite) | +| `static_ui` | `boolean` | no | `false` | Cache frames when properties haven't changed | + +## Example Pipeline + +Composites a Slint watermark overlay onto colorbars and streams as WebM: + +```yaml +name: Video Slint Watermark (Oneshot) +description: Composites colorbars with a Slint watermark overlay +mode: oneshot +client: + input: + type: none + output: + type: video + +nodes: + colorbars_bg: + kind: video::colorbars + params: + width: 1280 + height: 720 + fps: 30 + frame_count: 300 + pixel_format: rgba8 + + watermark: + kind: plugin::native::slint + params: + width: 180 + height: 44 + fps: 30 + frame_count: 300 + slint_file: samples/slint/watermark.slint + static_ui: true + properties: + channel: "StreamKit" + tagline: "LIVE" + + compositor: + kind: video::compositor + params: + width: 1280 + height: 720 + num_inputs: 2 + layers: + in_0: + opacity: 1.0 + z_index: 0 + in_1: + rect: + x: 1080 + y: 20 + width: 180 + height: 44 + opacity: 0.9 + z_index: 10 + needs: + - colorbars_bg + - watermark + + pixel_convert: + kind: video::pixel_convert + params: + output_format: nv12 + needs: compositor + + vp9_encoder: + kind: video::vp9::encoder + needs: pixel_convert + + webm_muxer: + kind: containers::webm::muxer + params: + video_width: 1280 + video_height: 720 + streaming_mode: live + needs: vp9_encoder + + pacer: + kind: core::pacer + needs: webm_muxer + + http_output: + kind: streamkit::http_output + params: + content_type: 'video/webm; codecs="vp9"' + needs: pacer +``` + + +
+Raw JSON Schema + +```json +{ + "properties": { + "width": { + "default": 640, + "description": "Output frame width in pixels", + "minimum": 1, + "type": "integer" + }, + "height": { + "default": 480, + "description": "Output frame height in pixels", + "minimum": 1, + "type": "integer" + }, + "fps": { + "default": 30, + "description": "Output frame rate", + "minimum": 1, + "type": "integer" + }, + "slint_file": { + "description": "Path to the .slint file", + "type": "string" + }, + "component": { + "description": "Name of the exported component to instantiate (defaults to first)", + "type": "string" + }, + "properties": { + "default": {}, + "description": "Key-value map of Slint properties (strings, numbers, booleans)", + "type": "object" + }, + "property_keyframes": { + "default": [], + "description": "List of property snapshots to cycle through over time", + "items": { + "type": "object" + }, + "type": "array" + }, + "keyframe_interval": { + "default": 90, + "description": "Frames between keyframe switches", + "minimum": 1, + "type": "integer" + }, + "frame_count": { + "default": 0, + "description": "Total frames to generate (0 = infinite)", + "type": "integer" + }, + "static_ui": { + "default": false, + "description": "Cache frames when properties haven't changed", + "type": "boolean" + } + }, + "required": [ + "slint_file" + ], + "type": "object" +} +``` + +
diff --git a/e2e/tests/overlay-controls.spec.ts b/e2e/tests/overlay-controls.spec.ts new file mode 100644 index 00000000..e39d85cd --- /dev/null +++ b/e2e/tests/overlay-controls.spec.ts @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +import { test, expect, request } from '@playwright/test'; + +import { ensureLoggedIn, getAuthHeaders } from './auth-helpers'; +import { type ConsoleErrorCollector, createConsoleErrorCollector } from './test-helpers'; + +/** + * E2E tests for the declarative overlay controls feature. + * + * Uses the "Test: Overlay Controls" sample pipeline which includes all four + * control types (toggle, text, number, button) and requires only core nodes + * (video::colorbars → core::sink) — no plugins or MoQ gateway needed. + */ +test.describe('Stream View - Overlay Controls', () => { + let collector: ConsoleErrorCollector; + let sessionId: string | null = null; + + // Captured WebSocket messages sent from the client. + let wsSentMessages: unknown[] = []; + + test.beforeEach(async ({ page }) => { + collector = createConsoleErrorCollector(page); + wsSentMessages = []; + + // Intercept outgoing WebSocket messages to verify control payloads. + page.on('websocket', (ws) => { + ws.on('framesent', (frame) => { + try { + const data = JSON.parse(frame.payload as string); + wsSentMessages.push(data); + } catch { + // Ignore non-JSON frames. + } + }); + }); + + await page.goto('/stream'); + await ensureLoggedIn(page); + if (!page.url().includes('/stream')) { + await page.goto('/stream'); + } + await expect(page.getByTestId('stream-view')).toBeVisible(); + }); + + test('renders all control types and sends correct UpdateParams on interaction', async ({ + page, + }) => { + test.setTimeout(60_000); + + // ── 1. Select the overlay-controls test pipeline ───────────────── + const templateCard = page.getByText('Test: Overlay Controls', { + exact: true, + }); + await expect(templateCard).toBeVisible({ timeout: 15_000 }); + await templateCard.click(); + + // ── 2. Create session ──────────────────────────────────────────── + const createButton = page.getByRole('button', { name: /Create Session/i }); + await expect(createButton).toBeEnabled({ timeout: 5_000 }); + await createButton.click(); + + const activeBadge = page.getByText('Session Active'); + await expect(activeBadge).toBeVisible({ timeout: 15_000 }); + + const sessionIdText = await page.getByText(/Session ID:/).textContent(); + sessionId = sessionIdText?.replace(/Session ID:\s*/, '').trim() ?? null; + + // ── 3. Verify "Pipeline Controls" section appears ──────────────── + const controls = page.getByTestId('overlay-controls'); + await expect(controls).toBeVisible({ timeout: 5_000 }); + await expect(controls.getByText('Pipeline Controls', { exact: true })).toBeVisible(); + + // ── 4. Verify all control labels are rendered ──────────────────── + // Scope all locators to the controls section to avoid collisions + // with the YAML editor that also displays control label strings. + // Use label locators to avoid collisions with button text. + const labels = controls.locator('label'); + await expect(labels.filter({ hasText: 'Draw Time' })).toBeVisible(); + await expect(labels.filter({ hasText: 'Label' })).toBeVisible(); + await expect(labels.filter({ hasText: 'Width' })).toBeVisible(); + await expect(labels.filter({ hasText: 'Height' })).toBeVisible(); + await expect(labels.filter({ hasText: 'Reset' })).toBeVisible(); + + // Verify group heading. + await expect(controls.getByText('Dimensions', { exact: true })).toBeVisible(); + + // ── 5. Exercise toggle control ─────────────────────────────────── + // The toggle defaults to true (checked). Click it to toggle off. + const toggleButton = controls.locator('button[aria-label="Draw Time"]'); + await expect(toggleButton).toBeVisible(); + await toggleButton.click(); + + // Wait for the WS message to be sent. + await page.waitForTimeout(200); + + // Find the TuneNodeAsync message for the toggle. + const toggleMsg = wsSentMessages.find( + (m: unknown) => + typeof m === 'object' && + m !== null && + (m as Record).type === 'request' && + ((m as Record>).payload?.action === 'tunenodeasync' || + (m as Record>).payload?.action === 'TuneNodeAsync') && + (m as Record>).payload?.node_id === 'colorbars' + ); + expect(toggleMsg, 'Expected a TuneNodeAsync message for the toggle control').toBeTruthy(); + + const togglePayload = (toggleMsg as Record>>) + .payload?.message?.UpdateParams; + expect(togglePayload, 'Toggle should send { draw_time: false }').toEqual({ + draw_time: false, + }); + + // ── 6. Exercise text control ───────────────────────────────────── + const textInput = controls.locator('input[placeholder="Label"]'); + await expect(textInput).toBeVisible(); + // Clear the default value and type a new one. + await textInput.fill('World'); + + // Text is debounced at 300ms — wait for it to fire. + await page.waitForTimeout(500); + + const textMsg = wsSentMessages.find( + (m: unknown) => + typeof m === 'object' && + m !== null && + (m as Record).type === 'request' && + ((m as Record>).payload?.action === 'tunenodeasync' || + (m as Record>).payload?.action === 'TuneNodeAsync') && + (m as Record>).payload?.node_id === 'colorbars' && + typeof ( + (m as Record>>).payload?.message + ?.UpdateParams as Record + )?.label === 'string' + ); + expect(textMsg, 'Expected a TuneNodeAsync message for the text control').toBeTruthy(); + + const textPayload = (textMsg as Record>>).payload + ?.message?.UpdateParams; + expect(textPayload, 'Text should send { label: "World" }').toEqual({ + label: 'World', + }); + + // ── 7. Exercise number/slider control ──────────────────────────── + // Clear previous messages to isolate slider messages. + wsSentMessages.length = 0; + + const slider = controls.locator('input[type="range"]').first(); + await expect(slider).toBeVisible(); + + // Set the slider to a specific value via fill (simulates user input). + await slider.fill('800'); + + // Throttled — wait for trailing edge. + await page.waitForTimeout(300); + + const sliderMsg = wsSentMessages.find( + (m: unknown) => + typeof m === 'object' && + m !== null && + (m as Record).type === 'request' && + ((m as Record>).payload?.action === 'tunenodeasync' || + (m as Record>).payload?.action === 'TuneNodeAsync') && + (m as Record>).payload?.node_id === 'colorbars' && + ( + (m as Record>>).payload?.message + ?.UpdateParams as Record + )?.properties !== undefined + ); + expect(sliderMsg, 'Expected a TuneNodeAsync message for the slider control').toBeTruthy(); + + const sliderPayload = (sliderMsg as Record>>) + .payload?.message?.UpdateParams; + // Width slider: dot-notation "properties.width" → nested { properties: { width: 800 } } + expect(sliderPayload).toHaveProperty('properties'); + expect((sliderPayload as Record>).properties).toHaveProperty( + 'width' + ); + + // ── 8. Exercise button control ─────────────────────────────────── + wsSentMessages.length = 0; + + const resetButton = controls.getByRole('button', { name: 'Reset' }); + await expect(resetButton).toBeVisible(); + await resetButton.click(); + + await page.waitForTimeout(200); + + const buttonMsg = wsSentMessages.find( + (m: unknown) => + typeof m === 'object' && + m !== null && + (m as Record).type === 'request' && + ((m as Record>).payload?.action === 'tunenodeasync' || + (m as Record>).payload?.action === 'TuneNodeAsync') && + (m as Record>).payload?.node_id === 'colorbars' && + ( + (m as Record>>).payload?.message + ?.UpdateParams as Record + )?.reset === true + ); + expect(buttonMsg, 'Expected a TuneNodeAsync message for the button control').toBeTruthy(); + + // ── 9. Assert no unexpected console errors ─────────────────────── + const unexpected = collector.getUnexpected(); + expect(unexpected, `Unexpected console errors: ${unexpected.join('; ')}`).toHaveLength(0); + collector.stop(); + + // ── 10. Destroy session ────────────────────────────────────────── + const destroyButton = page.getByRole('button', { + name: /Destroy Session/i, + }); + await expect(destroyButton).toBeVisible(); + await destroyButton.click(); + + const confirmModal = page.getByTestId('confirm-modal'); + await expect(confirmModal).toBeVisible(); + await confirmModal.getByRole('button', { name: /Destroy Session/i }).click(); + + await expect(createButton).toBeVisible({ timeout: 15_000 }); + sessionId = null; + }); + + // Safety-net cleanup. + test.afterEach(async ({ baseURL }) => { + if (sessionId) { + try { + const apiContext = await request.newContext({ + baseURL: baseURL!, + extraHTTPHeaders: getAuthHeaders(), + }); + await apiContext.delete(`/api/v1/sessions/${sessionId}`); + await apiContext.dispose(); + } catch { + // Best-effort cleanup; ignore errors. + } + sessionId = null; + } + }); +}); diff --git a/justfile b/justfile index 3a8433c4..a9923ecd 100644 --- a/justfile +++ b/justfile @@ -334,6 +334,7 @@ lint-plugins: @cd plugins/native/pocket-tts && cargo fmt -- --check && CARGO_TARGET_DIR={{plugins_target_dir}} cargo clippy -- -D warnings @cd plugins/native/nllb && cargo fmt -- --check && CMAKE_ARGS="-DCMAKE_INSTALL_PREFIX=$$(pwd)/target/cmake-install" CARGO_TARGET_DIR={{plugins_target_dir}} cargo clippy -- -D warnings @cd plugins/native/supertonic && cargo fmt -- --check && CARGO_TARGET_DIR={{plugins_target_dir}} cargo clippy -- -D warnings + @cd plugins/native/slint && cargo fmt -- --check && CARGO_TARGET_DIR={{plugins_target_dir}} cargo clippy -- -D warnings @echo "✓ All native plugins passed linting" # Auto-fix formatting and linting issues in native plugins @@ -348,6 +349,7 @@ fix-plugins: @cd plugins/native/pocket-tts && cargo fmt && CARGO_TARGET_DIR={{plugins_target_dir}} cargo clippy --fix --allow-dirty --allow-staged -- -D warnings @cd plugins/native/nllb && cargo fmt && CMAKE_ARGS="-DCMAKE_INSTALL_PREFIX=$$(pwd)/target/cmake-install" CARGO_TARGET_DIR={{plugins_target_dir}} cargo clippy --fix --allow-dirty --allow-staged -- -D warnings @cd plugins/native/supertonic && cargo fmt && CARGO_TARGET_DIR={{plugins_target_dir}} cargo clippy --fix --allow-dirty --allow-staged -- -D warnings + @cd plugins/native/slint && cargo fmt && CARGO_TARGET_DIR={{plugins_target_dir}} cargo clippy --fix --allow-dirty --allow-staged -- -D warnings @echo "✓ All native plugins fixed" # --- Profiling --- @@ -912,12 +914,25 @@ upload-supertonic-plugin: build-plugin-native-supertonic @curl -X POST -F "plugin=@{{plugins_target_dir}}/release/libsupertonic.so" \ http://127.0.0.1:4545/api/v1/plugins +# Build native Slint UI plugin +[working-directory: 'plugins/native/slint'] +build-plugin-native-slint: + @echo "Building native Slint UI plugin..." + @CARGO_TARGET_DIR={{plugins_target_dir}} cargo build --release + +# Upload Slint plugin to running server +[working-directory: 'plugins/native/slint'] +upload-slint-plugin: build-plugin-native-slint + @echo "Uploading Slint plugin to server..." + @curl -X POST -F "plugin=@{{plugins_target_dir}}/release/libslint.so" \ + http://127.0.0.1:4545/api/v1/plugins + # Build specific native plugin by name build-plugin-native name: @just build-plugin-native-{{name}} # Build all native plugin examples -build-plugins-native: build-plugin-native-gain build-plugin-native-whisper build-plugin-native-kokoro build-plugin-native-piper build-plugin-native-matcha build-plugin-native-pocket-tts build-plugin-native-sensevoice build-plugin-native-nllb build-plugin-native-vad build-plugin-native-helsinki build-plugin-native-supertonic +build-plugins-native: build-plugin-native-gain build-plugin-native-whisper build-plugin-native-kokoro build-plugin-native-piper build-plugin-native-matcha build-plugin-native-pocket-tts build-plugin-native-sensevoice build-plugin-native-nllb build-plugin-native-vad build-plugin-native-helsinki build-plugin-native-supertonic build-plugin-native-slint ## Combined @@ -953,7 +968,7 @@ copy-plugins-native: cp examples/plugins/gain-native/target/release/libgain_plugin_native.* .plugins/native/ 2>/dev/null || true # Official native plugins (shared target dir) - for name in whisper kokoro piper matcha vad sensevoice nllb helsinki supertonic; do + for name in whisper kokoro piper matcha vad sensevoice nllb helsinki supertonic slint; do for f in \ "$PLUGINS_TARGET"/release/lib"$name".so \ "$PLUGINS_TARGET"/release/lib"$name".so.* \ diff --git a/marketplace/official-plugins.json b/marketplace/official-plugins.json index 8e147e81..4e9892c1 100644 --- a/marketplace/official-plugins.json +++ b/marketplace/official-plugins.json @@ -245,6 +245,17 @@ } ] }, + { + "id": "slint", + "name": "Slint", + "version": "0.1.0", + "node_kind": "slint", + "kind": "native", + "entrypoint": "libslint.so", + "artifact": "target/plugins/release/libslint.so", + "description": "Slint UI rendering as a video source \u2014 render .slint files to RGBA8 frames at configurable resolution and frame rate", + "license": "MPL-2.0" + }, { "id": "supertonic", "name": "Supertonic", diff --git a/plugins/native/slint/Cargo.lock b/plugins/native/slint/Cargo.lock new file mode 100644 index 00000000..25663d24 --- /dev/null +++ b/plugins/native/slint/Cargo.lock @@ -0,0 +1,5949 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "accesskit" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eca13c82f9a5cd813120b2e9b6a5d10532c6e4cd140c295cebd1f770095c8a5" + +[[package]] +name = "accesskit_atspi_common" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb9cc46b7fb6987c4f891f0301b230b29d9e69b4854f060a0cf41fbc407ab77" +dependencies = [ + "accesskit", + "accesskit_consumer", + "atspi-common", + "serde", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d880a613f29621c90e801feec40f5dd61d837d7e20bf9b67676d45e7364a36" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0ddfc3fe3d457d11cc1c4989105986a03583a1d54d0c25053118944b62e100" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d552169ef018149966ed139bb0311c6947b3343e9140d1b9f88d69da9528fd" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d277279d0a3b0c0021dd110b55aa1fe326b09ee2cbc338df28f847c7daf94e25" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown 0.16.1", + "static_assertions", + "windows 0.61.3", + "windows-core 0.61.2", +] + +[[package]] +name = "accesskit_winit" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db08dff285306264a1de127ea07bb9e7a1ed71bd8593c168d0731caa782516c9" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.11.0", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.18", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "annotate-snippets" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fc7650eedcb2fee505aad48491529e408f0e854c2d9f63eb86c1361b9b3f93" +dependencies = [ + "anstyle", + "memchr", + "unicode-width", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.4", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.4", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + +[[package]] +name = "auto_enums" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65398a2893f41bce5c9259f6e1a4f03fbae40637c1bdc755b4f387f48c613b03" +dependencies = [ + "derive_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "serde", + "unty", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.11.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.2", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "bytes", + "cfg_aliases", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.11.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.11.0", + "polling", + "rustix 1.1.4", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.4", + "rustix 1.1.4", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "clru" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-field-offset" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fcde4ca1211b5a94b573083c472ee19e86b19a441913f66e1cc5c41daf0255" +dependencies = [ + "const-field-offset-macro", + "field-offset", +] + +[[package]] +name = "const-field-offset-macro" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5387f5bbc9e9e6c96436ea125afa12614cebf8ac67f49abc08c1e7a891466c90" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "copypasta" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6811e17f81fe246ef2bc553f76b6ee6ab41a694845df1d37e52a92b7bbd38a" +dependencies = [ + "clipboard-win", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "smithay-clipboard", + "x11-clipboard", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "countme" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" + +[[package]] +name = "cpp" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bcac3d8234c1fb813358e83d1bb6b0290a3d2b3b5efc6b88bfeaf9d8eec17" +dependencies = [ + "cpp_macros", +] + +[[package]] +name = "cpp_build" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f8638c97fbd79cc6fc80b616e0e74b49bac21014faed590bbc89b7e2676c90" +dependencies = [ + "cc", + "cpp_common", + "lazy_static", + "proc-macro2", + "regex", + "syn", + "unicode-xid", +] + +[[package]] +name = "cpp_common" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fcfea2ee05889597d35e986c2ad0169694320ae5cc8f6d2640a4bb8a884560" +dependencies = [ + "lazy_static", + "proc-macro2", + "syn", +] + +[[package]] +name = "cpp_macros" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d156158fe86e274820f5a53bc9edb0885a6e7113909497aa8d883b69dd171871" +dependencies = [ + "aho-corasick", + "byteorder", + "cpp_common", + "lazy_static", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "ctor-lite" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e162d0c2e2068eb736b71e5597eff0b9944e6b973cd9f37b6a288ab9bf20e300" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "derive_utils" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "362f47930db19fe7735f527e6595e4900316b893ebf6d48ad3d31be928d57dd6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "drm" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "libc", + "rustix 0.38.44", +] + +[[package]] +name = "drm-ffi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51a91c9b32ac4e8105dec255e849e0d66e27d7c34d184364fb93e469db08f690" +dependencies = [ + "drm-sys", + "rustix 1.1.4", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8e1361066d91f5ffccff060a3c3be9c3ecde15be2959c1937595f7a82a9f8" +dependencies = [ + "libc", + "linux-raw-sys 0.9.4", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "femtovg" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35695993a8264f5dfa8facc135c003965cea40d83705b49e0f8c3a3b632a2171" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "fnv", + "glow", + "image", + "imgref", + "itertools 0.14.0", + "log", + "rgb", + "slotmap", + "ttf-parser 0.25.1", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "font-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73829a7b5c91198af28a99159b7ae4afbb252fb906159ff7f189f3a2ceaa3df2" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "log", + "slotmap", + "tinyvec", + "ttf-parser 0.25.1", +] + +[[package]] +name = "fontdue" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e57e16b3fe8ff4364c0661fdaac543fb38b29ea9bc9c2f45612d90adf931d2b" +dependencies = [ + "hashbrown 0.15.5", + "ttf-parser 0.21.1", +] + +[[package]] +name = "fontique" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bbc252c93499b6d3635d692f892a637db0dbb130ce9b32bf20b28e0dcc470b" +dependencies = [ + "bytemuck", + "hashbrown 0.16.1", + "icu_locale_core", + "linebender_resource_handle", + "memmap2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-text", + "objc2-foundation 0.3.2", + "read-fonts 0.35.0", + "roxmltree", + "smallvec", + "windows 0.58.0", + "windows-core 0.58.0", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gbm" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a" +dependencies = [ + "bitflags 2.11.0", + "drm", + "drm-fourcc", + "gbm-sys", + "libc", +] + +[[package]] +name = "gbm-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f" +dependencies = [ + "libc", +] + +[[package]] +name = "generativity" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5881e4c3c2433fe4905bb19cfd2b5d49d4248274862b68c27c33d9ba4e13f9ec" + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link 0.2.1", +] + +[[package]] +name = "getopts" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gif" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.11.0", + "cfg_aliases", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "harfrust" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c020db12c71d8a12a3fe7607873cade3a01a6287e29d540c8723276221b9d8" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "core_maths", + "read-fonts 0.35.0", + "smallvec", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "htmlparser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ce8546b993eaf241d69ded33b1be6d205dd9857ec879d9d18bd05d3676e144" + +[[package]] +name = "i-slint-backend-linuxkms" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b8827952ecfbbf76c8cb5bc3388ca9124c34f2b4fe5dffcfe57800d2a484885" +dependencies = [ + "bytemuck", + "calloop 0.14.4", + "drm", + "gbm", + "glutin", + "i-slint-common", + "i-slint-core", + "i-slint-renderer-femtovg", + "i-slint-renderer-software", + "input", + "memmap2", + "nix", + "raw-window-handle", + "xkbcommon", +] + +[[package]] +name = "i-slint-backend-qt" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9d5db3221f453439ec5ad9b6ac3bb8d2b4825b2f8734f0cde4b67d7336c3da" +dependencies = [ + "const-field-offset", + "cpp", + "cpp_build", + "i-slint-common", + "i-slint-core", + "i-slint-core-macros", + "lyon_path", + "pin-project", + "pin-weak", + "qttypes", + "vtable", +] + +[[package]] +name = "i-slint-backend-selector" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5a7e591a7257096e1f3da1bbb9ad6a140c307d0eee74f008a0b412fdb20dec" +dependencies = [ + "cfg-if", + "i-slint-backend-linuxkms", + "i-slint-backend-qt", + "i-slint-backend-winit", + "i-slint-common", + "i-slint-core", + "i-slint-core-macros", + "i-slint-renderer-femtovg", +] + +[[package]] +name = "i-slint-backend-winit" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbf4789191740f939c9563b8850379122d7b5c1ceb09f9297b50ad53e408787" +dependencies = [ + "accesskit", + "accesskit_winit", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "copypasta", + "derive_more", + "futures", + "glutin", + "glutin-winit", + "i-slint-common", + "i-slint-core", + "i-slint-core-macros", + "i-slint-renderer-femtovg", + "i-slint-renderer-skia", + "i-slint-renderer-software", + "imgref", + "lyon_path", + "muda", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", + "objc2-ui-kit 0.3.2", + "pin-weak", + "raw-window-handle", + "rgb", + "scoped-tls-hkt", + "scopeguard", + "softbuffer", + "strum", + "vtable", + "wasm-bindgen", + "web-sys", + "windows 0.62.2", + "winit", + "zbus", +] + +[[package]] +name = "i-slint-common" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7659797fd28d4df3ed275ff95bf730bdf4a88d253f07e1ee8d0032d70138c3a" +dependencies = [ + "fontique", + "ttf-parser 0.25.1", +] + +[[package]] +name = "i-slint-compiler" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a6f358d0d5389869d67cd6ab6f5acf98fe31827264a696593e9687213cff682" +dependencies = [ + "annotate-snippets", + "by_address", + "derive_more", + "i-slint-common", + "itertools 0.14.0", + "linked_hash_set", + "lyon_extra", + "lyon_path", + "num_enum", + "proc-macro2", + "quote", + "rowan", + "smol_str 0.3.6", + "strum", + "typed-index-collections", + "url", +] + +[[package]] +name = "i-slint-core" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a95591ff85f8e2ff11c8d26ea8429768c2b77866e0c7e7fd49348f23ad108b5c" +dependencies = [ + "auto_enums", + "bitflags 2.11.0", + "cfg-if", + "chrono", + "clru", + "const-field-offset", + "derive_more", + "euclid", + "htmlparser", + "i-slint-common", + "i-slint-core-macros", + "image", + "lyon_algorithms", + "lyon_extra", + "lyon_geom", + "lyon_path", + "num-traits", + "once_cell", + "parley", + "pin-project", + "pin-weak", + "portable-atomic", + "pulldown-cmark", + "raw-window-handle", + "resvg", + "rgb", + "scoped-tls-hkt", + "scopeguard", + "skrifa 0.37.0", + "slab", + "strum", + "sys-locale", + "thiserror 2.0.18", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", + "vtable", + "wasm-bindgen", + "web-sys", + "web-time", +] + +[[package]] +name = "i-slint-core-macros" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc5f2f71682787dd5c6299555c0de635009eb269bbc54d6198e0d225b69fae4" +dependencies = [ + "quote", + "serde_json", + "syn", +] + +[[package]] +name = "i-slint-renderer-femtovg" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb6eccda447999bc6222988500b841b64c953988986af182334e7ba9a30f0edd" +dependencies = [ + "cfg-if", + "const-field-offset", + "derive_more", + "femtovg", + "glow", + "i-slint-common", + "i-slint-core", + "i-slint-core-macros", + "imgref", + "lyon_path", + "pin-weak", + "rgb", + "ttf-parser 0.25.1", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "i-slint-renderer-skia" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64546232c0370f291e65fc92a4f4fc777ea78d5f48467873cb968b1de52e9ab" +dependencies = [ + "bytemuck", + "cfg-if", + "cfg_aliases", + "const-field-offset", + "derive_more", + "glow", + "glutin", + "i-slint-common", + "i-slint-core", + "i-slint-core-macros", + "lyon_path", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "pin-weak", + "raw-window-handle", + "raw-window-metal", + "read-fonts 0.35.0", + "scoped-tls-hkt", + "skia-safe", + "softbuffer", + "unicode-segmentation", + "vtable", + "windows 0.62.2", + "write-fonts", +] + +[[package]] +name = "i-slint-renderer-software" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a59be6c34935c4f8e41aa67a63518d5c59219c8eeb1d07af420bed8334fa31d7" +dependencies = [ + "bytemuck", + "clru", + "derive_more", + "euclid", + "fontdue", + "i-slint-common", + "i-slint-core", + "integer-sqrt", + "lyon_path", + "num-traits", + "skrifa 0.37.0", + "zeno", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "serde", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png 0.18.1", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" + +[[package]] +name = "imgref" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "input" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbdc09524a91f9cacd26f16734ff63d7dc650daffadd2b6f84d17a285bd875a9" +dependencies = [ + "bitflags 2.11.0", + "input-sys", + "libc", + "log", + "udev", +] + +[[package]] +name = "input-sys" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd4f5b4d1c00331c5245163aacfe5f20be75b564c7112d45893d4ae038119eb0" + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.11.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce9729cc38c18d86123ab736fd2e7151763ba226ac2490ec092d1dd148825e32" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "kurbo" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linked_hash_set" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984fb35d06508d1e69fc91050cceba9c0b748f983e6739fa2c7a9237154c52c8" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lyon_algorithms" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9815fac08e6fd96733a11dce4f9d15a3f338e96a2e2311ee21e1b738efc2bc0f" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_extra" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7755f08423275157ad1680aaecc9ccb7e0cc633da3240fea2d1522935cc15c72" +dependencies = [ + "lyon_path", + "thiserror 2.0.18", +] + +[[package]] +name = "lyon_geom" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "keyboard-types", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "png 0.17.16", + "thiserror 2.0.18", + "windows-sys 0.60.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.0", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.0", + "block2 0.6.2", + "libc", + "objc2 0.6.4", + "objc2-cloud-kit 0.3.2", + "objc2-core-data 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image 0.3.2", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.0", + "dispatch2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.0", + "dispatch2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.0", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.11.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-cloud-kit 0.2.2", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core 0.2.2", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.11.0", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.11.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "orbclient" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser 0.25.1", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parley" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada5338c3a9794af7342e6f765b6e78740db37378aced034d7bf72c96b94ed94" +dependencies = [ + "fontique", + "harfrust", + "hashbrown 0.16.1", + "linebender_resource_handle", + "skrifa 0.37.0", + "swash", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pin-weak" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b330c9d1b92dfe68442ca20b009c717d5f0b1e3cf4965e62f704c3c6e95a1305" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +dependencies = [ + "critical-section", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad" +dependencies = [ + "bitflags 2.11.0", + "getopts", + "memchr", + "pulldown-cmark-escape", + "unicase", +] + +[[package]] +name = "pulldown-cmark-escape" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" + +[[package]] +name = "pxfm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" + +[[package]] +name = "qttypes" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7edf5b38c97ad8900ad2a8418ee44b4adceaa866a4a3405e2f1c909871d7ebd" +dependencies = [ + "cpp", + "cpp_build", + "semver", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "read-fonts" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +dependencies = [ + "bytemuck", + "core_maths", + "font-types 0.10.1", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "font-types 0.11.1", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "resvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" +dependencies = [ + "gif", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", + "zune-jpeg", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "rowan" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" +dependencies = [ + "countme", + "hashbrown 0.14.5", + "rustc-hash 1.1.0", + "text-size", +] + +[[package]] +name = "roxmltree" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +dependencies = [ + "memchr", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.11.0", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser 0.25.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scoped-tls-hkt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9603871ffe5df3ac39cb624790c296dbd47a400d202f56bf3e414045099524d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit 0.19.2", + "tiny-skia", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "skia-bindings" +version = "0.90.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6f96e00735f14a781aac8a6870c862b8cc831df6d8e4ad77ab78e11411b9af" +dependencies = [ + "bindgen", + "cc", + "flate2", + "heck", + "pkg-config", + "regex", + "serde_json", + "tar", + "toml", +] + +[[package]] +name = "skia-safe" +version = "0.90.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a71c01d325d40b1031dee67d251a5e0132e79e2a9ec272149a4f4a0d4b8b3be" +dependencies = [ + "bitflags 2.11.0", + "skia-bindings", + "windows 0.62.2", +] + +[[package]] +name = "skrifa" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" +dependencies = [ + "bytemuck", + "read-fonts 0.35.0", +] + +[[package]] +name = "skrifa" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts 0.37.0", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slint" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25b87d458205e79efb30545cae083aec2ccb1b192c46a55ae6d54403cdacb33" +dependencies = [ + "const-field-offset", + "i-slint-backend-selector", + "i-slint-common", + "i-slint-core", + "i-slint-core-macros", + "i-slint-renderer-software", + "num-traits", + "once_cell", + "pin-weak", + "slint-macros", + "unicode-segmentation", + "vtable", +] + +[[package]] +name = "slint-interpreter" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22df58eef76ecc29e0641bbd34f718b6c84ab49186f099298d6baa94f5493437" +dependencies = [ + "derive_more", + "generativity", + "i-slint-backend-qt", + "i-slint-backend-selector", + "i-slint-backend-winit", + "i-slint-common", + "i-slint-compiler", + "i-slint-core", + "i-slint-core-macros", + "itertools 0.14.0", + "lyon_path", + "once_cell", + "smol_str 0.3.6", + "unicode-segmentation", + "vtable", + "web-sys", +] + +[[package]] +name = "slint-macros" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ecc09bbc42c780d5b7ed7d41f7573dfd67343e11cdac27c07b88a8f933958e6" +dependencies = [ + "i-slint-compiler", + "proc-macro2", + "quote", + "spin_on", +] + +[[package]] +name = "slint-plugin-native" +version = "0.1.0" +dependencies = [ + "pollster", + "serde", + "serde_json", + "slint", + "slint-interpreter", + "streamkit-plugin-sdk-native", + "tracing", + "uuid", +] + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.11.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.11.0", + "calloop 0.14.4", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.4", + "thiserror 2.0.18", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "as-raw-xcb-connection", + "bytemuck", + "fastrand", + "js-sys", + "memmap2", + "ndk", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", + "raw-window-handle", + "redox_syscall 0.5.18", + "rustix 1.1.4", + "tiny-xlib", + "tracing", + "wasm-bindgen", + "wayland-backend", + "wayland-client", + "wayland-sys", + "web-sys", + "windows-sys 0.61.2", + "x11rb", +] + +[[package]] +name = "spin_on" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076e103ed41b9864aa838287efe5f4e3a7a0362dd00671ae62a212e5e4612da2" +dependencies = [ + "pin-utils", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "streamkit-core" +version = "0.2.0" +dependencies = [ + "async-trait", + "base64", + "bytes", + "schemars", + "serde", + "serde_json", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", + "ts-rs", +] + +[[package]] +name = "streamkit-plugin-sdk-native" +version = "0.2.0" +dependencies = [ + "async-trait", + "bytes", + "serde", + "serde_json", + "streamkit-core", + "tracing", +] + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "svgtypes" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" +dependencies = [ + "kurbo 0.13.0", + "siphasher", +] + +[[package]] +name = "swash" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842f3cd369c2ba38966204f983eaa5e54a8e84a7d7159ed36ade2b6c335aae64" +dependencies = [ + "skrifa 0.40.0", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "js-sys", + "libc", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "tar" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "text-size" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tiny-xlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" +dependencies = [ + "as-raw-xcb-connection", + "ctor-lite", + "libloading", + "pkg-config", + "tracing", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82418ca169e235e6c399a84e395ab6debeb3bc90edc959bf0f48647c6a32d1b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.1", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.1", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "ts-rs" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756050066659291d47a554a9f558125db17428b073c5ffce1daf5dcb0f7231d8" +dependencies = [ + "serde_json", + "thiserror 2.0.18", + "ts-rs-macros", +] + +[[package]] +name = "ts-rs-macros" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d90eea51bc7988ef9e674bf80a85ba6804739e535e9cab48e4bb34a8b652aa" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "termcolor", +] + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "typed-index-collections" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898160f1dfd383b4e92e17f0512a7d62f3c51c44937b23b6ffc3a1614a8eaccd" +dependencies = [ + "bincode", + "serde", +] + +[[package]] +name = "udev" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4e37e9ea4401fc841ff54b9ddfc9be1079b1e89434c1a6a865dd68980f7e9f" +dependencies = [ + "io-lifetimes", + "libc", + "libudev-sys", + "pkg-config", +] + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "usvg" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" +dependencies = [ + "base64", + "data-url", + "flate2", + "fontdb", + "imagesize", + "kurbo 0.13.0", + "log", + "pico-args", + "roxmltree", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vtable" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "753be81c38dff787d177b5939af1fa16f72f0d0d21a6b7d74ae56e29cd26f2a6" +dependencies = [ + "const-field-offset", + "portable-atomic", + "stable_deref_trait", + "vtable-macro", +] + +[[package]] +name = "vtable-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfcf6171aa2b0f85718ca5888ca32f6edf61d1849f8e4b3786ad890e5b68f68" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.11.0", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.11.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.11.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml 0.39.2", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.11.0", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases", + "concurrent-queue", + "core-foundation", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit 0.2.2", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit 0.19.2", + "smol_str 0.2.2", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "write-fonts" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886614b5ce857341226aa091f3c285e450683894acaaa7887f366c361efef79d" +dependencies = [ + "font-types 0.10.1", + "indexmap", + "kurbo 0.12.0", + "log", + "read-fonts 0.35.0", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "x11-clipboard" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix 1.1.4", +] + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a974f48060a14e95705c01f24ad9c3345022f4d97441b8a36beb7ed5c4a02d" +dependencies = [ + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.11.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 0.7.15", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" +dependencies = [ + "serde", + "winnow 0.7.15", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "441a0064125265655bccc3a6af6bef56814d9277ac83fce48b1cd7e160b80eac" +dependencies = [ + "quick-xml 0.38.4", + "serde", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + +[[package]] +name = "zvariant" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 0.7.15", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn", + "winnow 0.7.15", +] diff --git a/plugins/native/slint/Cargo.toml b/plugins/native/slint/Cargo.toml new file mode 100644 index 00000000..c0f9ffad --- /dev/null +++ b/plugins/native/slint/Cargo.toml @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: © 2025 StreamKit Contributors +# +# SPDX-License-Identifier: MPL-2.0 + +[package] +name = "slint-plugin-native" +version = "0.1.0" +edition = "2021" +license = "MPL-2.0" + +[lib] +name = "slint" +crate-type = ["cdylib"] + +[dependencies] +streamkit-plugin-sdk-native = { path = "../../../sdks/plugin-sdk/native" } +slint = { version = "1.15", default-features = false, features = ["renderer-software", "compat-1-2"] } +slint-interpreter = { version = "1.15" } +pollster = "0.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +uuid = { version = "1", features = ["v4"] } +tracing = "0.1" + +[lints.clippy] +# Categories +pedantic = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -1 } +# Safety +unwrap_used = "warn" +expect_used = "warn" +# Complexity +cognitive_complexity = "warn" +# Math +cast_possible_truncation = "warn" +cast_precision_loss = "warn" +cast_sign_loss = "warn" +# Allow-list (Noise reduction) +module_name_repetitions = "allow" +must_use_candidate = "allow" +doc_markdown = "allow" diff --git a/plugins/native/slint/README.md b/plugins/native/slint/README.md new file mode 100644 index 00000000..d9c6048d --- /dev/null +++ b/plugins/native/slint/README.md @@ -0,0 +1,122 @@ + + +# Slint Native Plugin + +Render `.slint` UI files as a video source node. Produces RGBA8 frames at a +configurable resolution and frame rate using the Slint software renderer. + +## Features + +- **Declarative UI overlays** — design overlays in the Slint markup language +- **Software rendering** — no GPU required, runs anywhere +- **Runtime property updates** — change text, scores, colors via `UpdateParams` +- **Keyframe cycling** — animate through property snapshots over time +- **Static UI caching** — skip re-renders when properties haven't changed + +## Setup + +### Build Plugin + +```bash +just build-plugin-native-slint +``` + +### Upload to Server + +```bash +just upload-slint-plugin +``` + +Or manually: +```bash +curl -X POST \ + -F plugin=@target/plugins/release/libslint.so \ + http://127.0.0.1:4545/api/v1/plugins +``` + +### Verify Loaded + +```bash +curl http://localhost:4545/api/v1/plugins +# Should show: plugin::native::slint +``` + +## Usage + +### Example Pipeline + +See `samples/pipelines/oneshot/video_slint_watermark.yml` for a static +watermark overlay example. + +### Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `width` | integer | 640 | Output frame width in pixels | +| `height` | integer | 480 | Output frame height in pixels | +| `fps` | integer | 30 | Output frame rate | +| `slint_file` | string | *required* | Path to `.slint` file | +| `component` | string | *(first)* | Exported component name to instantiate | +| `properties` | object | `{}` | Key-value map of Slint properties | +| `property_keyframes` | array | `[]` | List of property snapshots to cycle through | +| `keyframe_interval` | integer | 90 | Frames between keyframe switches | +| `frame_count` | integer | 0 | Total frames to generate (0 = infinite) | +| `static_ui` | boolean | false | Cache frames when properties haven't changed | + +### Property Types + +Properties are mapped from JSON to Slint values: +- JSON strings → `SharedString` +- JSON numbers → `f64` +- JSON booleans → `bool` + +### Static vs Dynamic UI + +- **`static_ui: false`** (default): Every frame is re-rendered. Use for UIs + with Slint `Timer` or `animate` directives. +- **`static_ui: true`**: Frames are cached and reused until properties change. + Use for static overlays (watermarks, scoreboards updated only via + `UpdateParams`). + +## Architecture + +### Threading Model + +Slint types are `!Send` (Rc-based) and `slint::platform::set_platform` is +process-global. All Slint operations are funnelled through a single dedicated +`std::thread` (lazily spawned). Each plugin instance communicates with this +shared thread via tagged work items and per-instance result channels. + +### Data Flow + +``` +Host tick loop → tick() → [Render work item] → Slint thread → render → [Frame result] → tick() → output.send() +``` + +## Technical Details + +### Dependencies + +- **slint** (1.15+): Slint runtime with software renderer +- **slint-interpreter**: Runtime `.slint` file compilation +- **streamkit-plugin-sdk-native**: StreamKit native plugin SDK + +### Video Output + +- **Pixel format**: RGBA8 (straight alpha) +- **Resolution**: Configurable (default 640x480) +- **Frame rate**: Configurable (default 30 fps) + +### No Models Required + +Unlike other StreamKit plugins, the Slint plugin has no ML models to download. +The `.slint` design files are provided as part of the pipeline configuration. + +## License + +- **Code**: MPL-2.0 (StreamKit Contributors) +- **Slint**: Dual-licensed under GPLv3 and Slint Commercial License diff --git a/plugins/native/slint/clippy.toml b/plugins/native/slint/clippy.toml new file mode 100644 index 00000000..8b2b18f1 --- /dev/null +++ b/plugins/native/slint/clippy.toml @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: © 2025 StreamKit Contributors +# +# SPDX-License-Identifier: MPL-2.0 + +# clippy.toml + +# 1. Complexity Limits +cognitive-complexity-threshold = 50 +too-many-lines-threshold = 300 + +# 2. Naming consistency +doc-valid-idents = [] + +# 3. Banned Types / Methods / Macros +disallowed-macros = [ + { path = "std::print", reason = "Use the logging crate instead of stdout" }, + { path = "std::println", reason = "Use the logging crate instead of stdout" }, + { path = "std::eprint", reason = "Use the logging crate instead of stderr" }, + { path = "std::eprintln", reason = "Use the logging crate instead of stderr" }, + { path = "std::dbg", reason = "Remove debugging macros before commit" }, +] + +# 4. Macro expansion setup +allowed-scripts = ["utf-8"] diff --git a/plugins/native/slint/plugin.yml b/plugins/native/slint/plugin.yml new file mode 100644 index 00000000..4a668595 --- /dev/null +++ b/plugins/native/slint/plugin.yml @@ -0,0 +1,9 @@ +id: slint +name: Slint +version: 0.1.0 +node_kind: slint +kind: native +entrypoint: libslint.so +artifact: target/plugins/release/libslint.so +description: Slint UI rendering as a video source — render .slint files to RGBA8 frames at configurable resolution and frame rate +license: MPL-2.0 diff --git a/plugins/native/slint/src/config.rs b/plugins/native/slint/src/config.rs new file mode 100644 index 00000000..433ef010 --- /dev/null +++ b/plugins/native/slint/src/config.rs @@ -0,0 +1,162 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +//! Configuration for the Slint video source plugin. + +use std::collections::HashMap; + +use serde::Deserialize; + +// ── Defaults ──────────────────────────────────────────────────────────────── + +/// Maximum allowed dimension (width or height) — 8K. +/// Guards against config typos that would attempt multi-GB buffer allocations. +const MAX_DIMENSION: u32 = 7680; + +const fn default_width() -> u32 { + 640 +} + +const fn default_height() -> u32 { + 480 +} + +const fn default_fps() -> u32 { + 30 +} + +const fn default_frame_count() -> u32 { + 0 +} + +const fn default_keyframe_interval() -> u32 { + 90 +} + +const fn default_static_ui() -> bool { + false +} + +// ── Configuration ─────────────────────────────────────────────────────────── + +/// Configuration for the Slint UI video source plugin. +/// +/// Produces RGBA8 frames by rendering a compiled `.slint` component via the +/// software renderer. Properties can be set at init and updated at runtime +/// via `UpdateParams`. +#[derive(Debug, Clone, Deserialize)] +#[serde(default)] +pub struct SlintConfig { + /// Output frame width in pixels. + #[serde(default = "default_width")] + pub width: u32, + /// Output frame height in pixels. + #[serde(default = "default_height")] + pub height: u32, + /// Output frame rate. + #[serde(default = "default_fps")] + pub fps: u32, + /// Path to the `.slint` file. + #[serde(default)] + pub slint_file: String, + /// Name of the exported component to instantiate. When omitted, the + /// first exported component in the file is used. + #[serde(default)] + pub component: Option, + /// Key-value map of Slint properties to set on the component instance. + /// Strings → `SharedString`, numbers → `f64`, booleans → `bool`. + #[serde(default)] + pub properties: HashMap, + /// Optional list of property snapshots to cycle through over time. + /// Each entry is a partial property map merged on top of `properties`. + #[serde(default)] + pub property_keyframes: Vec>, + /// Number of frames between keyframe switches (default: 90 ≈ 3 s at 30 fps). + #[serde(default = "default_keyframe_interval")] + pub keyframe_interval: u32, + /// Total frames to generate. 0 = infinite (real-time pacing). + #[serde(default = "default_frame_count")] + pub frame_count: u32, + /// When `true`, the rendered frame is cached and reused until properties + /// change (via `UpdateParams` or keyframe cycling). Suitable for overlays + /// with no Slint-internal `Timer` or `animate` directives. When `false` + /// (the default), every frame is re-rendered so that Slint timers and + /// animations advance correctly. + #[serde(default = "default_static_ui")] + pub static_ui: bool, +} + +impl Default for SlintConfig { + fn default() -> Self { + Self { + width: default_width(), + height: default_height(), + fps: default_fps(), + slint_file: String::new(), + component: None, + properties: HashMap::new(), + property_keyframes: Vec::new(), + keyframe_interval: default_keyframe_interval(), + frame_count: default_frame_count(), + static_ui: default_static_ui(), + } + } +} + +impl SlintConfig { + /// Validate configuration parameters. + /// + /// # Errors + /// + /// Returns an error string if dimensions are zero, fps is zero, or the + /// slint file path is invalid. + pub fn validate(&self) -> Result<(), String> { + if self.width == 0 || self.height == 0 { + return Err("width and height must be > 0".to_string()); + } + if self.width > MAX_DIMENSION || self.height > MAX_DIMENSION { + return Err(format!( + "width and height must be <= {MAX_DIMENSION} (8K), got {}x{}", + self.width, self.height + )); + } + if self.fps == 0 { + return Err("fps must be > 0".to_string()); + } + validate_slint_asset_path(&self.slint_file) + } + + /// Merge runtime property changes from an `UpdateParams` payload. + /// + /// Only `properties` values are merged (via `extend`) so that a partial + /// JSON like `{"properties": {"home_score": 4}}` updates the named keys + /// without dropping unmentioned ones. Init-time fields (`slint_file`, + /// `component`, `width`, `height`, `fps`, `frame_count`, + /// `property_keyframes`, `keyframe_interval`) are left unchanged because + /// serde defaults make it impossible to distinguish "user sent empty" from + /// "field was absent in the JSON". + pub fn merge_update(&mut self, update: &Self) { + self.properties.extend(update.properties.iter().map(|(k, v)| (k.clone(), v.clone()))); + } +} + +/// Validates that a Slint asset path is safe to read. +/// +/// Allows any relative path but forbids directory traversal sequences. +/// +/// # Errors +/// +/// Returns an error string if the path is empty or contains traversal sequences. +fn validate_slint_asset_path(path: &str) -> Result<(), String> { + if path.is_empty() { + return Err("slint_file must not be empty".to_string()); + } + if std::path::Path::new(path).is_absolute() { + return Err(format!("Invalid slint_file: absolute paths are not allowed: {path}")); + } + if path.contains("..") { + return Err(format!("Invalid slint_file: path must not contain '..': {path}")); + } + Ok(()) +} diff --git a/plugins/native/slint/src/lib.rs b/plugins/native/slint/src/lib.rs new file mode 100644 index 00000000..c70b2dfd --- /dev/null +++ b/plugins/native/slint/src/lib.rs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +mod config; +mod slint_node; +mod slint_thread; + +use slint_node::SlintSourcePlugin; +use streamkit_plugin_sdk_native::{native_source_plugin_entry, NativeSourceNode}; + +native_source_plugin_entry!(SlintSourcePlugin); diff --git a/plugins/native/slint/src/slint_node.rs b/plugins/native/slint/src/slint_node.rs new file mode 100644 index 00000000..8f89ae10 --- /dev/null +++ b/plugins/native/slint/src/slint_node.rs @@ -0,0 +1,240 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +//! `NativeSourceNode` implementation for the Slint video source plugin. + +use streamkit_plugin_sdk_native::prelude::*; +use streamkit_plugin_sdk_native::streamkit_core::types::{ + PacketMetadata, PixelFormat, RawVideoFormat, VideoFrame, +}; + +use crate::config::SlintConfig; +use crate::slint_thread::{send_work, NodeId, SlintThreadResult, SlintWorkItem}; + +/// Slint UI video source plugin. +/// +/// Renders `.slint` files to RGBA8 video frames at a configurable resolution +/// and frame rate. All Slint operations run on a shared dedicated thread; +/// this struct holds the channel handle and per-instance state. +pub struct SlintSourcePlugin { + config: SlintConfig, + node_id: NodeId, + result_rx: std::sync::mpsc::Receiver, + tick_count: u64, + duration_us: u64, + logger: Logger, +} + +impl NativeSourceNode for SlintSourcePlugin { + fn metadata() -> NodeMetadata { + NodeMetadata::builder("slint") + .output( + "out", + PacketType::RawVideo(RawVideoFormat { + width: None, + height: None, + pixel_format: PixelFormat::Rgba8, + }), + ) + .category("video") + .category("generators") + .description( + "Renders a Slint UI component into RGBA8 video frames. \ + Compiles a .slint file at init and produces frames at the \ + configured resolution and frame rate. Properties can be \ + updated at runtime via UpdateParams.", + ) + .param_schema(serde_json::json!({ + "type": "object", + "properties": { + "width": { + "type": "integer", + "default": 640, + "description": "Output frame width in pixels", + "minimum": 1 + }, + "height": { + "type": "integer", + "default": 480, + "description": "Output frame height in pixels", + "minimum": 1 + }, + "fps": { + "type": "integer", + "default": 30, + "description": "Output frame rate", + "minimum": 1 + }, + "slint_file": { + "type": "string", + "description": "Path to the .slint file" + }, + "component": { + "type": "string", + "description": "Name of the exported component to instantiate (defaults to first)" + }, + "properties": { + "type": "object", + "default": {}, + "description": "Key-value map of Slint properties (strings, numbers, booleans)" + }, + "property_keyframes": { + "type": "array", + "default": [], + "description": "List of property snapshots to cycle through over time", + "items": { "type": "object" } + }, + "keyframe_interval": { + "type": "integer", + "default": 90, + "description": "Frames between keyframe switches", + "minimum": 1 + }, + "frame_count": { + "type": "integer", + "default": 0, + "description": "Total frames to generate (0 = infinite)" + }, + "static_ui": { + "type": "boolean", + "default": false, + "description": "Cache frames when properties haven't changed" + } + }, + "required": ["slint_file"] + })) + .build() + } + + fn source_config(&self) -> SourceConfig { + let fps = self.config.fps.max(1); + if self.config.frame_count > 0 { + SourceConfig { + tick_interval_us: 1_000_000 / u64::from(fps), + max_ticks: u64::from(self.config.frame_count), + } + } else { + SourceConfig::from_fps(fps) + } + } + + fn new(params: Option, logger: Logger) -> Result { + let config: SlintConfig = if let Some(p) = params { + serde_json::from_value(p).map_err(|e| format!("Invalid config: {e}"))? + } else { + // Parameterless construction is used by the host to probe + // source_config(). Return a lightweight default instance + // without starting the Slint thread. + let config = SlintConfig::default(); + let (_tx, result_rx) = std::sync::mpsc::sync_channel(1); + return Ok(Self { + config, + node_id: uuid::Uuid::new_v4(), + result_rx, + tick_count: 0, + duration_us: 1_000_000 / 30, + logger, + }); + }; + + config.validate()?; + + let fps = config.fps.max(1); + let duration_us = 1_000_000 / u64::from(fps); + + plugin_info!( + logger, + "Initializing Slint plugin: {}x{} @ {} fps, slint_file='{}'", + config.width, + config.height, + fps, + config.slint_file + ); + + let node_id = uuid::Uuid::new_v4(); + + // Use a bounded channel with capacity 2 to allow one frame in-flight + // plus the init result, without unbounded buffering. + let (result_tx, result_rx) = std::sync::mpsc::sync_channel(2); + + // Register on the shared Slint thread. + send_work(SlintWorkItem::Register { node_id, config: config.clone(), result_tx })?; + + // Wait for init result. + match result_rx.recv() { + Ok(SlintThreadResult::InitOk) => { + plugin_info!(logger, "Slint instance registered: {node_id}"); + }, + Ok(SlintThreadResult::InitErr(e)) => { + return Err(format!("Slint instance creation failed: {e}")); + }, + Ok(SlintThreadResult::Frame { .. }) => { + return Err("Unexpected frame result during init".to_string()); + }, + Err(_) => { + return Err("Shared Slint thread channel closed during init".to_string()); + }, + } + + Ok(Self { config, node_id, result_rx, tick_count: 0, duration_us, logger }) + } + + fn tick(&mut self, output: &OutputSender) -> Result { + // Request a frame from the shared Slint thread. + send_work(SlintWorkItem::Render { node_id: self.node_id })?; + + // Wait for the rendered frame. + let rgba_data = match self.result_rx.recv() { + Ok(SlintThreadResult::Frame { rgba_data }) => rgba_data, + Ok(_) => { + plugin_warn!(self.logger, "Unexpected result from Slint thread"); + return Ok(false); + }, + Err(_) => { + return Err("Slint thread result channel closed".to_string()); + }, + }; + + let timestamp_us = self.tick_count * self.duration_us; + let metadata = Some(PacketMetadata { + timestamp_us: Some(timestamp_us), + duration_us: Some(self.duration_us), + sequence: Some(self.tick_count), + keyframe: Some(true), + }); + + let frame = VideoFrame::with_metadata( + self.config.width, + self.config.height, + PixelFormat::Rgba8, + rgba_data, + metadata, + ) + .map_err(|e| format!("Failed to create video frame: {e}"))?; + + output.send("out", &Packet::Video(frame))?; + + self.tick_count += 1; + Ok(false) + } + + fn update_params(&mut self, params: Option) -> Result<(), String> { + if let Some(p) = params { + let update: SlintConfig = + serde_json::from_value(p).map_err(|e| format!("Invalid params: {e}"))?; + self.config.merge_update(&update); + send_work(SlintWorkItem::UpdateConfig { + node_id: self.node_id, + config: self.config.clone(), + })?; + plugin_info!(self.logger, "Updated Slint properties"); + } + Ok(()) + } + + fn cleanup(&mut self) { + let _ = send_work(SlintWorkItem::Unregister { node_id: self.node_id }); + plugin_info!(self.logger, "Slint instance unregistered: {}", self.node_id); + } +} diff --git a/plugins/native/slint/src/slint_thread.rs b/plugins/native/slint/src/slint_thread.rs new file mode 100644 index 00000000..d20b8cc6 --- /dev/null +++ b/plugins/native/slint/src/slint_thread.rs @@ -0,0 +1,453 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +//! Shared Slint renderer thread. +//! +//! `slint::platform::set_platform` is process-global and the types it exposes +//! (`MinimalSoftwareWindow`, `ComponentInstance`) are `!Send` (`Rc`-based). +//! To support multiple plugin instances without UB, all Slint work is +//! funnelled through a single dedicated `std::thread`, lazily spawned on the +//! first instance's init. +//! +//! Each instance gets a unique `NodeId` (UUID) and communicates with the +//! shared thread via tagged work items. Results are sent back on per-node +//! `std::sync::mpsc` channels so `tick()` can block-receive without needing +//! a tokio runtime. + +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use std::sync::OnceLock; + +use slint::platform::software_renderer::{ + MinimalSoftwareWindow, PremultipliedRgbaColor, RepaintBufferType, +}; +use slint::platform::WindowAdapter; +use slint::{ComponentHandle, LogicalSize, SharedString}; +use slint_interpreter::{ComponentDefinition, ComponentInstance, Value}; + +use crate::config::SlintConfig; + +/// Opaque identifier for a plugin instance on the shared Slint thread. +pub type NodeId = uuid::Uuid; + +/// Work item sent from a plugin's `tick()` to the shared Slint thread. +pub enum SlintWorkItem { + /// Register a new instance: compile its `.slint` file and create a component. + /// The `result_tx` is stored by the shared thread for sending render + /// results and the init outcome back. + Register { + node_id: NodeId, + config: SlintConfig, + result_tx: std::sync::mpsc::SyncSender, + }, + /// Request a single rendered frame for the given instance. + Render { node_id: NodeId }, + /// Update the config (properties / keyframes) for subsequent renders. + UpdateConfig { node_id: NodeId, config: SlintConfig }, + /// Unregister an instance — drop its component and result channel. + Unregister { node_id: NodeId }, +} + +/// Result sent from the shared Slint thread back to a specific instance. +pub enum SlintThreadResult { + /// Init succeeded — the instance can start rendering. + InitOk, + /// Init failed with an error message. + InitErr(String), + /// A rendered frame. + Frame { rgba_data: Vec }, +} + +/// Handle to the shared Slint thread's work channel. +struct SlintThreadHandle { + work_tx: std::sync::mpsc::Sender, +} + +/// Get (or lazily spawn) the shared Slint thread. +/// +/// # Panics +/// +/// Panics if the OS fails to spawn the dedicated Slint renderer thread +/// (e.g. resource exhaustion). This is unrecoverable — the plugin cannot +/// render Slint UIs without this thread. +#[allow(clippy::expect_used)] +fn shared_slint_thread() -> &'static SlintThreadHandle { + static HANDLE: OnceLock = OnceLock::new(); + HANDLE.get_or_init(|| { + let (work_tx, work_rx) = std::sync::mpsc::channel::(); + std::thread::Builder::new() + .name("slint-plugin-renderer".to_string()) + .spawn(move || slint_thread_main(work_rx)) + .expect("Failed to spawn shared Slint renderer thread"); + SlintThreadHandle { work_tx } + }) +} + +/// Send a work item to the shared Slint thread. +/// +/// # Errors +/// +/// Returns an error if the shared thread has panicked or been dropped. +pub fn send_work(item: SlintWorkItem) -> Result<(), String> { + shared_slint_thread() + .work_tx + .send(item) + .map_err(|_| "Slint renderer thread is no longer running".to_string()) +} + +// ── Slint thread main loop ────────────────────────────────────────────────── + +/// Entry point for the shared Slint thread. +/// +/// Processes work items from all plugin instances. The platform backend is +/// set once on this thread; all `SlintInstance` values live here. +#[allow(clippy::needless_pass_by_value)] +fn slint_thread_main(work_rx: std::sync::mpsc::Receiver) { + /// Per-instance state living on the shared thread. + struct InstanceState { + instance: SlintInstance, + config: SlintConfig, + result_tx: std::sync::mpsc::SyncSender, + /// Cached straight-alpha RGBA8 output from the last render. + cached_frame: Option>, + /// Keyframe index that produced `cached_frame`. + cached_keyframe_idx: Option, + /// Set by `UpdateConfig` to force a re-render on the next frame. + dirty: bool, + } + + let mut instances: HashMap = HashMap::new(); + let mut platform_set = false; + + while let Ok(work) = work_rx.recv() { + match work { + SlintWorkItem::Register { node_id, config, result_tx } => { + match create_slint_instance(&config, &mut platform_set) { + Ok(instance) => { + tracing::info!( + node_id = %node_id, + slint_file = %config.slint_file, + "Created Slint instance", + ); + let _ = result_tx.send(SlintThreadResult::InitOk); + instances.insert( + node_id, + InstanceState { + instance, + config, + result_tx, + cached_frame: None, + cached_keyframe_idx: None, + dirty: true, + }, + ); + }, + Err(e) => { + tracing::error!( + node_id = %node_id, + error = %e, + "Failed to create Slint instance", + ); + let _ = result_tx.send(SlintThreadResult::InitErr(e)); + }, + } + }, + SlintWorkItem::Render { node_id } => { + if let Some(state) = instances.get_mut(&node_id) { + // Pump Slint timers/animations (process-global) so Timer + // callbacks and CSS-like transitions advance even when the + // frame is served from cache. This call is idempotent and + // wall-clock-based, so running it N times per tick cycle + // (once per instance) is harmless. + slint::platform::update_timers_and_animations(); + + let rgba_data = if state.config.static_ui { + // ── Static UI path: cache the rendered frame ──────── + let kf_idx = if state.config.property_keyframes.is_empty() { + None + } else { + let interval = state.config.keyframe_interval.max(1); + Some( + (state.instance.frame_counter / interval) as usize + % state.config.property_keyframes.len(), + ) + }; + + let need_render = state.dirty + || state.cached_keyframe_idx != kf_idx + || state.cached_frame.is_none(); + + if need_render { + let data = render_slint_frame(&mut state.instance, &state.config); + state.cached_frame = Some(data); + state.cached_keyframe_idx = kf_idx; + state.dirty = false; + } else { + // Advance frame counter so keyframe boundaries + // are detected at the right time. + state.instance.frame_counter = + state.instance.frame_counter.wrapping_add(1); + } + // Clone from cache — avoids a redundant allocation + // compared to cloning before storing. + state.cached_frame.clone().unwrap_or_default() + } else { + // ── Dynamic UI path: always re-render ─────────────── + render_slint_frame(&mut state.instance, &state.config) + }; + + // Use try_send to avoid blocking: if the consumer is slow, + // drop the frame rather than stalling the shared thread. + match state.result_tx.try_send(SlintThreadResult::Frame { rgba_data }) { + Ok(()) => {}, + Err(std::sync::mpsc::TrySendError::Full(_)) => { + tracing::debug!( + node_id = %node_id, + "Result channel full, dropping frame", + ); + }, + Err(std::sync::mpsc::TrySendError::Disconnected(_)) => { + instances.remove(&node_id); + }, + } + } + }, + SlintWorkItem::UpdateConfig { node_id, config } => { + if let Some(state) = instances.get_mut(&node_id) { + state.config = config; + state.dirty = true; + } + }, + SlintWorkItem::Unregister { node_id } => { + instances.remove(&node_id); + }, + } + } +} + +// ── Slint rendering internals ─────────────────────────────────────────────── + +/// Scope guard that clears the `CURRENT_WINDOW` thread-local on drop. +/// +/// Used by `create_slint_instance` to ensure the thread-local is cleaned up +/// even if `definition.create()` or `component.show()` fails via `?`. +struct ClearWindow; + +impl Drop for ClearWindow { + fn drop(&mut self) { + CURRENT_WINDOW.with(|cell| *cell.borrow_mut() = None); + } +} + +/// A compiled Slint component instance ready for per-frame rendering. +/// +/// Created once at init on the shared Slint thread. +/// `!Send` by design — must not leave that thread. +struct SlintInstance { + window: Rc, + component: ComponentInstance, + /// Kept alive to prevent the compiled Slint component definition from being dropped. + #[allow(dead_code)] + definition: ComponentDefinition, + buffer: Vec, + width: u32, + /// Frame counter for property keyframe cycling. + frame_counter: u32, +} + +/// Compile a `.slint` file and create a renderable instance. +/// +/// Must be called on the shared Slint thread (`!Send` types). +/// The `platform_set` flag tracks whether `set_platform` has already been +/// called — it must happen exactly once per process. +fn create_slint_instance( + config: &SlintConfig, + platform_set: &mut bool, +) -> Result { + let width = config.width; + let height = config.height; + + // Compile the .slint file. + let compiler = slint_interpreter::Compiler::default(); + let result = pollster::block_on(compiler.build_from_path(&config.slint_file)); + + // Check for compilation errors. + let diags: Vec<_> = result + .diagnostics() + .filter(|d| d.level() == slint_interpreter::DiagnosticLevel::Error) + .collect(); + if !diags.is_empty() { + let msgs: Vec = diags.iter().map(|d| d.message().to_string()).collect(); + return Err(format!( + "Slint compilation errors in '{}': {}", + config.slint_file, + msgs.join("; ") + )); + } + + // Get the component definition. + let definition = if let Some(ref name) = config.component { + result + .component(name) + .ok_or_else(|| format!("Component '{}' not found in '{}'", name, config.slint_file))? + } else { + // Use the first exported component. + result + .components() + .next() + .ok_or_else(|| format!("No exported components in '{}'", config.slint_file))? + }; + + // Create the minimal software window. + let window = MinimalSoftwareWindow::new(RepaintBufferType::NewBuffer); + #[allow(clippy::cast_precision_loss)] + window.set_size(LogicalSize::new((width as f32).max(1.0), (height as f32).max(1.0))); + + // Set the Slint platform backend exactly once per process. + if !*platform_set { + slint::platform::set_platform(Box::new(SlintBackend)) + .map_err(|e| format!("Failed to set Slint platform: {e}"))?; + *platform_set = true; + } + + // Swap in this instance's window so `create_window_adapter()` returns + // the correct one during `definition.create()` and `component.show()`. + // The `ClearWindow` guard ensures the thread-local is cleared even if + // either call fails via `?`, preventing a stale `Rc` + // from lingering until the next `Register`. + let window_adapter = window.clone() as Rc; + CURRENT_WINDOW.with(|cell| *cell.borrow_mut() = Some(window_adapter)); + let _guard = ClearWindow; + + // Instantiate the component. + let component = definition + .create() + .map_err(|e| format!("Failed to create Slint component instance: {e}"))?; + + // Set initial properties. + set_properties(&component, &config.properties); + + // Allocate pixel buffer. + let pixel_count = (width as usize) * (height as usize); + let buffer = vec![PremultipliedRgbaColor::default(); pixel_count]; + + // Show the component so it becomes visible for rendering. + component.show().map_err(|e| format!("Failed to show Slint component: {e}"))?; + + // _guard drops here, clearing CURRENT_WINDOW. + + Ok(SlintInstance { window, component, definition, buffer, width, frame_counter: 0 }) +} + +/// Render a single frame from the Slint instance, returning raw RGBA8 data. +/// +/// Applies property keyframe cycling. Timer/animation pumping is handled +/// by the caller (`slint_thread_main`) so it runs unconditionally. +fn render_slint_frame(instance: &mut SlintInstance, config: &SlintConfig) -> Vec { + // Build the effective property map: base properties merged with the + // current keyframe (if keyframes are configured). + let effective_props = if config.property_keyframes.is_empty() { + std::borrow::Cow::Borrowed(&config.properties) + } else { + let interval = config.keyframe_interval.max(1); + let idx = (instance.frame_counter / interval) as usize % config.property_keyframes.len(); + let mut merged = config.properties.clone(); + merged.extend(config.property_keyframes[idx].iter().map(|(k, v)| (k.clone(), v.clone()))); + std::borrow::Cow::Owned(merged) + }; + instance.frame_counter = instance.frame_counter.wrapping_add(1); + + // Push property updates into the component instance. + set_properties(&instance.component, &effective_props); + + // Force a full redraw every frame. + instance.window.request_redraw(); + + // Render into the pixel buffer. + let width = instance.width; + instance.window.draw_if_needed(|renderer| { + renderer.render(&mut instance.buffer, width as usize); + }); + + // Convert premultiplied buffer to straight-alpha RGBA8. + premultiplied_to_straight_rgba(&instance.buffer) +} + +// ── Private helpers ───────────────────────────────────────────────────────── + +/// Map JSON property values to Slint `Value` and set them on the component. +fn set_properties(component: &ComponentInstance, properties: &HashMap) { + for (key, json_val) in properties { + let slint_val = json_to_slint_value(json_val); + if let Err(e) = component.set_property(key, slint_val) { + tracing::warn!(property = %key, error = %e, "Failed to set Slint property"); + } + } +} + +/// Convert a JSON value to a Slint interpreter `Value`. +fn json_to_slint_value(json: &serde_json::Value) -> Value { + match json { + serde_json::Value::String(s) => Value::String(SharedString::from(s.as_str())), + serde_json::Value::Bool(b) => Value::Bool(*b), + // Slint's Value::Number takes f64. JSON integers arrive as i64; + // the i64→f64 cast may lose precision for values > 2^52, which is + // acceptable for UI property values (scores, counters, etc.). + #[allow(clippy::cast_precision_loss)] + serde_json::Value::Number(n) => n + .as_i64() + .map_or_else(|| Value::Number(n.as_f64().unwrap_or(0.0)), |i| Value::Number(i as f64)), + _ => Value::Void, + } +} + +/// Convert a slice of premultiplied-alpha pixels to straight-alpha RGBA8. +/// +/// The `as u8` casts below are safe: for premultiplied data the invariant +/// `channel <= alpha` holds, so `channel * 255 / alpha <= 255` — always +/// fits in a `u8`. +#[allow(clippy::cast_possible_truncation)] +fn premultiplied_to_straight_rgba(pixels: &[PremultipliedRgbaColor]) -> Vec { + let mut bytes = Vec::with_capacity(pixels.len() * 4); + for px in pixels { + if px.alpha == 0 { + bytes.extend_from_slice(&[0, 0, 0, 0]); + } else if px.alpha == 255 { + bytes.extend_from_slice(&[px.red, px.green, px.blue, 255]); + } else { + // Un-premultiply: channel = premultiplied * 255 / alpha + let a = u16::from(px.alpha); + let r = (u16::from(px.red) * 255 / a) as u8; + let g = (u16::from(px.green) * 255 / a) as u8; + let b = (u16::from(px.blue) * 255 / a) as u8; + bytes.extend_from_slice(&[r, g, b, px.alpha]); + } + } + bytes +} + +// ── Slint platform backend ────────────────────────────────────────────────── + +// Thread-local holding the window adapter that `SlintBackend::create_window_adapter()` +// should return. +thread_local! { + static CURRENT_WINDOW: RefCell>> = const { RefCell::new(None) }; +} + +/// Minimal Slint platform backend. +/// +/// Required by Slint's runtime to know where to render. Set exactly once +/// on the shared Slint thread. +struct SlintBackend; + +impl slint::platform::Platform for SlintBackend { + fn create_window_adapter(&self) -> Result, slint::PlatformError> { + CURRENT_WINDOW.with(|cell| { + cell.borrow() + .clone() + .ok_or_else(|| slint::PlatformError::Other("No current Slint window set".into())) + }) + } +} diff --git a/samples/pipelines/dynamic/test_overlay_controls.yml b/samples/pipelines/dynamic/test_overlay_controls.yml new file mode 100644 index 00000000..f5008a86 --- /dev/null +++ b/samples/pipelines/dynamic/test_overlay_controls.yml @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: © 2025 StreamKit Contributors +# +# SPDX-License-Identifier: MPL-2.0 + +# Minimal pipeline for E2E-testing overlay controls. +# Uses only core nodes (no plugins, no MoQ) so the session can be +# created in any environment. The controls section exercises all +# four control types: toggle, text, number, and button. + +name: "Test: Overlay Controls" +description: Colorbars-to-sink pipeline with overlay controls (E2E test fixture) +mode: dynamic +client: + controls: + - label: "Draw Time" + type: toggle + node: colorbars + property: draw_time + default: true + - label: "Label" + type: text + node: colorbars + property: label + default: "Hello" + - label: "Width" + type: number + node: colorbars + property: properties.width + group: Dimensions + default: 640 + min: 160 + max: 1920 + step: 10 + - label: "Height" + type: number + node: colorbars + property: properties.height + group: Dimensions + default: 480 + min: 120 + max: 1080 + step: 10 + - label: "Reset" + type: button + node: colorbars + property: reset + value: true + +nodes: + colorbars: + kind: video::colorbars + params: + width: 640 + height: 480 + fps: 30 + pixel_format: rgba8 + draw_time: true + + sink: + kind: core::sink + needs: colorbars diff --git a/samples/pipelines/dynamic/video_moq_slint_scoreboard.yml b/samples/pipelines/dynamic/video_moq_slint_scoreboard.yml new file mode 100644 index 00000000..873e920c --- /dev/null +++ b/samples/pipelines/dynamic/video_moq_slint_scoreboard.yml @@ -0,0 +1,166 @@ +# SPDX-FileCopyrightText: © 2025 StreamKit Contributors +# +# SPDX-License-Identifier: MPL-2.0 + +# Demonstrates Slint overlays (scoreboard + lower third) composited onto +# colorbars and streamed via MoQ, with runtime property updates. +# +# Each Slint overlay is a standalone plugin::native::slint source node whose output +# is wired into the compositor as a regular video layer. +# +# Requires: plugin::native::slint loaded +# just build-plugin-native-slint && just copy-plugins-native +# +# After starting the pipeline, update properties at runtime: +# +# curl -X POST http://localhost:4545/api/v1/sessions//nodes/scoreboard/update \ +# -H 'Content-Type: application/json' \ +# -d '{ +# "properties": { +# "home_score": 4, +# "clock_start": 2147, +# "period": "2ND" +# } +# }' +# +# NOTE: Only `properties` are mergeable at runtime via UpdateParams. +# Init-time fields (slint_file, width, height, fps, frame_count, +# keyframe_interval, property_keyframes, static_ui) are ignored in +# UpdateParams because serde defaults make it impossible to distinguish +# "user sent empty" from "field was absent". + +name: Video Slint Scoreboard + Lower Third (MoQ) +description: Composites colorbars with Slint scoreboard and lower-third overlays, streams via MoQ with runtime property updates +mode: dynamic +client: + gateway_path: /moq/video + watch: + broadcast: output + audio: false + video: true + controls: + - label: "Show Lower Third" + type: toggle + node: lower_third + property: properties.show + default: true + - label: "Player Name" + type: text + node: lower_third + property: properties.name + default: "Alex Johnson" + - label: "Subtitle" + type: text + node: lower_third + property: properties.subtitle + default: "Forward · #17" + - label: "Home Score" + type: number + node: scoreboard + property: properties.home_score + group: Scoreboard + default: 3 + min: 0 + max: 99 + step: 1 + - label: "Away Score" + type: number + node: scoreboard + property: properties.away_score + group: Scoreboard + default: 1 + min: 0 + max: 99 + step: 1 + - label: "Period" + type: text + node: scoreboard + property: properties.period + group: Scoreboard + default: "2ND" + +nodes: + colorbars_bg: + kind: video::colorbars + params: + width: 1280 + height: 720 + fps: 30 + pixel_format: rgba8 + draw_time: true + + scoreboard: + kind: plugin::native::slint + params: + width: 420 + height: 80 + fps: 30 + slint_file: samples/slint/scoreboard.slint + properties: + home_team: "EAGLES" + away_team: "HAWKS" + home_score: 3 + away_score: 1 + clock_start: 754 + period: "2ND" + + lower_third: + kind: plugin::native::slint + params: + width: 350 + height: 70 + fps: 30 + slint_file: samples/slint/lower_third.slint + properties: + name: "Alex Johnson" + subtitle: "Forward · #17" + show: true + + compositor: + kind: video::compositor + params: + width: 1280 + height: 720 + num_inputs: 3 + layers: + in_0: + opacity: 1.0 + z_index: 0 + in_1: + rect: + x: 430 + y: 20 + width: 420 + height: 80 + opacity: 1.0 + z_index: 10 + in_2: + rect: + x: 50 + y: 600 + width: 350 + height: 70 + opacity: 1.0 + z_index: 11 + needs: + - colorbars_bg + - scoreboard + - lower_third + + pixel_convert: + kind: video::pixel_convert + params: + output_format: nv12 + needs: compositor + + vp9_encoder: + kind: video::vp9::encoder + needs: pixel_convert + + moq_peer: + kind: transport::moq::peer + params: + gateway_path: /moq/video + output_broadcast: output + allow_reconnect: true + needs: vp9_encoder diff --git a/samples/pipelines/oneshot/video_slint_watermark.yml b/samples/pipelines/oneshot/video_slint_watermark.yml new file mode 100644 index 00000000..5960df3f --- /dev/null +++ b/samples/pipelines/oneshot/video_slint_watermark.yml @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: © 2025 StreamKit Contributors +# +# SPDX-License-Identifier: MPL-2.0 + +# Demonstrates a Slint watermark composited onto colorbars. +# +# The watermark is a standalone plugin::native::slint source node that renders +# RGBA8 frames via the slint-interpreter software renderer. Its output +# is wired into the compositor as a regular video layer — no special +# integration in the compositor itself. +# +# Requires: plugin::native::slint loaded +# just build-plugin-native-slint && just copy-plugins-native +# +# For runtime property updates see the dynamic/video_moq_slint_scoreboard.yml +# pipeline which uses the scoreboard overlay in real-time mode. + +name: Video Slint Watermark (Oneshot) +description: Composites colorbars with a Slint watermark overlay, encodes to VP9, and streams a WebM via http_output +mode: oneshot +client: + input: + type: none + output: + type: video + +nodes: + colorbars_bg: + kind: video::colorbars + params: + width: 1280 + height: 720 + fps: 30 + frame_count: 300 + pixel_format: rgba8 + draw_time: true + draw_time_use_pts: true + + watermark: + kind: plugin::native::slint + params: + width: 180 + height: 44 + fps: 30 + frame_count: 300 + slint_file: samples/slint/watermark.slint + static_ui: true + properties: + channel: "StreamKit" + tagline: "LIVE" + + compositor: + kind: video::compositor + params: + width: 1280 + height: 720 + num_inputs: 2 + layers: + in_0: + opacity: 1.0 + z_index: 0 + in_1: + rect: + x: 1080 + y: 20 + width: 180 + height: 44 + opacity: 0.9 + z_index: 10 + needs: + - colorbars_bg + - watermark + + pixel_convert: + kind: video::pixel_convert + params: + output_format: nv12 + needs: compositor + + vp9_encoder: + kind: video::vp9::encoder + needs: pixel_convert + + webm_muxer: + kind: containers::webm::muxer + params: + video_width: 1280 + video_height: 720 + streaming_mode: live + needs: vp9_encoder + + pacer: + kind: core::pacer + needs: webm_muxer + + http_output: + kind: streamkit::http_output + params: + content_type: 'video/webm; codecs="vp9"' + needs: pacer diff --git a/samples/slint/lower_third.slint b/samples/slint/lower_third.slint new file mode 100644 index 00000000..67739d41 --- /dev/null +++ b/samples/slint/lower_third.slint @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +// Broadcast lower-third overlay with a slide-in animation. +// +// Set `show` to `true` to slide in, `false` to slide out. +// Properties can be updated at runtime via UpdateParams JSON: +// name, subtitle, show + +export component LowerThird inherits Window { + in property name: "Player Name"; + in property subtitle: "Position"; + in property show: true; + + width: 350px; + height: 70px; + background: transparent; + + // Clip wrapper — `clip` is only valid on Rectangle, not Window. + Rectangle { + width: 100%; + height: 100%; + clip: true; + background: transparent; + + // Content container that slides in/out. + Rectangle { + x: root.show ? 0px : -360px; + y: 0px; + width: 350px; + height: 70px; + animate x { duration: 400ms; easing: ease-out; } + + // Accent bar (left edge). + Rectangle { + x: 0px; + y: 0px; + width: 4px; + height: 70px; + background: #e63946; + } + + // Thin bright highlight next to the accent bar. + Rectangle { + x: 4px; + y: 0px; + width: 1px; + height: 70px; + background: #ff6b7a; + } + + // Background panel. + Rectangle { + x: 5px; + y: 0px; + width: 345px; + height: 70px; + background: #0d1117ee; + + // Subtle top-edge highlight. + Rectangle { + x: 0px; + y: 0px; + width: 100%; + height: 1px; + background: #ffffff12; + } + + VerticalLayout { + padding-left: 14px; + padding-top: 10px; + padding-bottom: 10px; + spacing: 3px; + + Text { + text: root.name; + color: #ffffff; + font-size: 20px; + font-weight: 700; + } + + // Thin separator between name and subtitle. + Rectangle { + width: 40px; + height: 1px; + background: #ffffff18; + } + + Text { + text: root.subtitle; + color: #8b949e; + font-size: 13px; + font-weight: 500; + letter-spacing: 0.5px; + } + } + } + } + } +} diff --git a/samples/slint/scoreboard.slint b/samples/slint/scoreboard.slint new file mode 100644 index 00000000..fe486a42 --- /dev/null +++ b/samples/slint/scoreboard.slint @@ -0,0 +1,161 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +// Sports scoreboard overlay for the StreamKit compositor. +// +// Properties can be updated at runtime via UpdateParams JSON: +// home_team, away_team, home_score, away_score, clock_start, period +// +// The clock ticks automatically once per second from `clock_start` +// (total seconds). Set `clock_running` to false to freeze it. + +export component Scoreboard inherits Window { + in property home_team: "HOME"; + in property away_team: "AWAY"; + in property home_score: 0; + in property away_score: 0; + /// Starting time in total seconds (e.g. 754 = "12:34"). + in property clock_start: 0; + in property clock_running: true; + in property period: "1ST"; + + // Internal elapsed seconds counter driven by the Timer below. + property elapsed: 0; + property total_seconds: root.clock_start + root.elapsed; + property minutes: Math.floor(root.total_seconds / 60); + property secs: Math.mod(root.total_seconds, 60); + + // Format as MM:SS — Slint doesn't have zero-pad, so we build it + // with a ternary for the leading zero on each part. + property clock_display: + (root.minutes < 10 ? "0" : "") + root.minutes + + ":" + + (root.secs < 10 ? "0" : "") + root.secs; + + width: 420px; + height: 80px; + // Transparent window background so only the rounded rectangle is + // visible when composited onto the video frame. + background: transparent; + + // 1-second tick timer for the match clock. + Timer { + interval: 1s; + running: root.clock_running; + triggered() => { + root.elapsed += 1; + } + } + + Rectangle { + background: #0d1117e8; + border-radius: 10px; + width: 100%; + height: 100%; + clip: true; + + // Subtle top-edge highlight. + Rectangle { + x: 0px; + y: 0px; + width: 100%; + height: 1px; + background: #ffffff12; + } + + // Home team color indicator (left edge). + Rectangle { + x: 0px; + y: 0px; + width: 3px; + height: 100%; + background: #3b82f6; + } + + // Away team color indicator (right edge). + Rectangle { + x: parent.width - 3px; + y: 0px; + width: 3px; + height: 100%; + background: #ef4444; + } + + HorizontalLayout { + padding-left: 16px; + padding-right: 16px; + spacing: 0px; + + // Home team + VerticalLayout { + horizontal-stretch: 1; + alignment: center; + spacing: 1px; + + Text { + text: root.home_team; + color: #8b949e; + font-size: 11px; + font-weight: 600; + letter-spacing: 1px; + horizontal-alignment: center; + } + Text { + text: root.home_score; + color: #ffffff; + font-size: 30px; + font-weight: 700; + horizontal-alignment: center; + } + } + + // Center: clock + period + VerticalLayout { + horizontal-stretch: 0; + min-width: 100px; + alignment: center; + spacing: 3px; + + Text { + text: root.clock_display; + color: #fbbf24; + font-size: 20px; + font-weight: 700; + horizontal-alignment: center; + } + Text { + text: root.period; + color: #6e7681; + font-size: 10px; + font-weight: 600; + letter-spacing: 2px; + horizontal-alignment: center; + } + } + + // Away team + VerticalLayout { + horizontal-stretch: 1; + alignment: center; + spacing: 1px; + + Text { + text: root.away_team; + color: #8b949e; + font-size: 11px; + font-weight: 600; + letter-spacing: 1px; + horizontal-alignment: center; + } + Text { + text: root.away_score; + color: #ffffff; + font-size: 30px; + font-weight: 700; + horizontal-alignment: center; + } + } + } + } +} diff --git a/samples/slint/watermark.slint b/samples/slint/watermark.slint new file mode 100644 index 00000000..b776396b --- /dev/null +++ b/samples/slint/watermark.slint @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +// Semi-transparent channel watermark / bug overlay. +// +// A branded badge with a pulsing "live" indicator, suitable for +// compositing into a corner of the video frame. The pulse animates +// when rendered with `static_ui: false`; with `static_ui: true` the +// glow ring provides a static visual accent. +// +// Properties can be updated at runtime via UpdateParams JSON: +// channel, tagline + +export component Watermark inherits Window { + in property channel: "StreamKit"; + in property tagline: "LIVE"; + + // Toggle once per second — drives the live-indicator glow. + property pulse-state: true; + + width: 180px; + height: 44px; + background: transparent; + + Timer { + interval: 1s; + running: true; + triggered() => { + root.pulse-state = !root.pulse-state; + } + } + + Rectangle { + width: 100%; + height: 100%; + border-radius: 8px; + background: #000000b3; + + // Subtle top-edge highlight. + Rectangle { + x: 8px; + y: 0px; + width: parent.width - 16px; + height: 1px; + background: #ffffff15; + } + + HorizontalLayout { + padding-left: 12px; + padding-right: 14px; + spacing: 10px; + + // Live indicator with animated glow ring. + VerticalLayout { + alignment: center; + + Rectangle { + width: 14px; + height: 14px; + border-radius: 7px; + background: root.pulse-state ? #e6394640 : #e6394618; + animate background { duration: 600ms; easing: ease-in-out; } + + Rectangle { + x: 3px; + y: 3px; + width: 8px; + height: 8px; + border-radius: 4px; + background: root.pulse-state ? #e63946 : #e63946aa; + animate background { duration: 600ms; easing: ease-in-out; } + } + } + } + + VerticalLayout { + alignment: center; + spacing: 1px; + + Text { + text: root.channel; + color: #ffffff; + font-size: 14px; + font-weight: 700; + } + Text { + text: root.tagline; + color: #ffffffcc; + font-size: 9px; + font-weight: 700; + letter-spacing: 3px; + } + } + } + } +} diff --git a/ui/src/components/stream/OverlayControls.tsx b/ui/src/components/stream/OverlayControls.tsx new file mode 100644 index 00000000..4acd7ff6 --- /dev/null +++ b/ui/src/components/stream/OverlayControls.tsx @@ -0,0 +1,383 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +import styled from '@emotion/styled'; +import { throttle } from 'lodash-es'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; + +import { Section, SectionTitle } from '@/components/ui/ViewLayout'; +import { useTuneNode } from '@/hooks/useTuneNode'; +import type { ControlConfig } from '@/types/types'; +import { parseClientFromYaml } from '@/utils/clientSection'; +import { buildParamUpdate } from '@/utils/controlProps'; + +// --------------------------------------------------------------------------- +// Props +// --------------------------------------------------------------------------- + +interface OverlayControlsProps { + pipelineYaml: string; + sessionId: string; +} + +// --------------------------------------------------------------------------- +// Styled components +// --------------------------------------------------------------------------- + +const ControlsContainer = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +const GroupHeading = styled.h3` + font-size: 14px; + font-weight: 600; + color: var(--sk-text-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + margin: 0; +`; + +const ControlRow = styled.div` + display: flex; + align-items: center; + gap: 12px; + min-height: 36px; +`; + +const ControlLabel = styled.label` + font-size: 14px; + font-weight: 500; + color: var(--sk-text); + min-width: 140px; + flex-shrink: 0; +`; + +const ToggleTrack = styled.button<{ checked: boolean }>` + position: relative; + width: 44px; + height: 24px; + border-radius: 12px; + border: 1px solid ${(props) => (props.checked ? 'var(--sk-primary)' : 'var(--sk-border)')}; + background: ${(props) => (props.checked ? 'var(--sk-primary)' : 'var(--sk-bg)')}; + cursor: pointer; + padding: 0; + transition: + background 0.15s, + border-color 0.15s; + flex-shrink: 0; + + &::after { + content: ''; + position: absolute; + top: 2px; + left: ${(props) => (props.checked ? '21px' : '2px')}; + width: 18px; + height: 18px; + border-radius: 50%; + background: ${(props) => + props.checked ? 'var(--sk-primary-contrast)' : 'var(--sk-text-muted)'}; + transition: left 0.15s; + } +`; + +const TextInput = styled.input` + padding: 8px 12px; + font-size: 14px; + background: var(--sk-bg); + color: var(--sk-text); + border: 1px solid var(--sk-border); + border-radius: 6px; + font-family: inherit; + flex: 1; + min-width: 0; + + &:focus { + outline: none; + border-color: var(--sk-primary); + } + + &::placeholder { + color: var(--sk-text-muted); + } +`; + +const SliderWrapper = styled.div` + display: flex; + align-items: center; + gap: 8px; + flex: 1; + min-width: 0; +`; + +const Slider = styled.input` + flex: 1; + min-width: 80px; + accent-color: var(--sk-primary); +`; + +const SliderValue = styled.span` + font-size: 13px; + font-weight: 600; + color: var(--sk-text); + min-width: 36px; + text-align: right; + font-variant-numeric: tabular-nums; +`; + +const ActionButton = styled.button` + padding: 8px 16px; + font-size: 14px; + font-weight: 600; + color: var(--sk-text); + background: var(--sk-panel-bg); + border: 1px solid var(--sk-border); + border-radius: 6px; + cursor: pointer; + transition: none; + + &:hover { + background: var(--sk-hover-bg); + border-color: var(--sk-border-strong); + } + + &:active { + background: var(--sk-primary); + color: var(--sk-primary-contrast); + border-color: var(--sk-primary); + } +`; + +// --------------------------------------------------------------------------- +// Debounce delay for text inputs (ms) +// --------------------------------------------------------------------------- +const TEXT_DEBOUNCE_MS = 300; + +// Throttle delay for slider updates (ms) +const SLIDER_THROTTLE_MS = 100; + +// --------------------------------------------------------------------------- +// Individual control widgets +// --------------------------------------------------------------------------- + +const ToggleControl: React.FC<{ + control: ControlConfig; + onSend: (value: unknown) => void; +}> = ({ control, onSend }) => { + const [checked, setChecked] = useState(() => { + if (typeof control.default === 'boolean') return control.default; + return false; + }); + + // Ref pattern matching TextControl/NumberControl so rapid double-clicks + // always see the latest checked state and the latest onSend callback. + const onSendRef = useRef(onSend); + useEffect(() => { + onSendRef.current = onSend; + }, [onSend]); + + const handleToggle = useCallback(() => { + setChecked((prev) => { + const next = !prev; + onSendRef.current(next); + return next; + }); + }, []); + + return ; +}; + +const TextControl: React.FC<{ + control: ControlConfig; + onSend: (value: unknown) => void; +}> = ({ control, onSend }) => { + const [text, setText] = useState(() => { + if (typeof control.default === 'string') return control.default; + return ''; + }); + + // Store onSend in a ref so the debounce closure is stable across re-renders + // and pending timers always call the latest callback. + const onSendRef = useRef(onSend); + useEffect(() => { + onSendRef.current = onSend; + }, [onSend]); + + const timerRef = useRef>(undefined); + + const debouncedSend = useCallback((value: string) => { + clearTimeout(timerRef.current); + timerRef.current = setTimeout(() => onSendRef.current(value), TEXT_DEBOUNCE_MS); + }, []); + + // Clean up any pending timer on unmount. + useEffect(() => () => clearTimeout(timerRef.current), []); + + const handleChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + setText(value); + debouncedSend(value); + }, + [debouncedSend] + ); + + return ; +}; + +const NumberControl: React.FC<{ + control: ControlConfig; + onSend: (value: unknown) => void; +}> = ({ control, onSend }) => { + const min = control.min ?? 0; + const max = control.max ?? 100; + const step = control.step ?? 1; + const defaultValue = typeof control.default === 'number' ? control.default : min; + + const [localValue, setLocalValue] = useState(defaultValue); + + // Store onSend in a ref so the throttle closure is stable and always + // calls the latest callback without recreating the throttle function. + const onSendRef = useRef(onSend); + useEffect(() => { + onSendRef.current = onSend; + }, [onSend]); + + const throttledSend = useMemo( + () => + throttle((value: number) => onSendRef.current(value), SLIDER_THROTTLE_MS, { + leading: true, + trailing: true, + }), + [] + ); + + useEffect(() => () => throttledSend.cancel(), [throttledSend]); + + const handleChange = useCallback( + (e: React.ChangeEvent) => { + const raw = Number.parseFloat(e.target.value); + const clamped = Math.min(Math.max(Number.isFinite(raw) ? raw : min, min), max); + setLocalValue(clamped); + throttledSend(clamped); + }, + [min, max, throttledSend] + ); + + const handlePointerDown = useCallback((e: React.PointerEvent) => { + e.stopPropagation(); + e.currentTarget.setPointerCapture?.(e.pointerId); + }, []); + + const handlePointerUp = useCallback( + (e: React.PointerEvent) => { + e.stopPropagation(); + e.currentTarget.releasePointerCapture?.(e.pointerId); + throttledSend.flush?.(); + }, + [throttledSend] + ); + + // Format display: show integers without decimals, floats with up to 2. + const display = Number.isInteger(step) ? Math.round(localValue) : localValue.toFixed(2); + + return ( + + + {display} + + ); +}; + +const ButtonControl: React.FC<{ + control: ControlConfig; + onSend: (value: unknown) => void; +}> = ({ control, onSend }) => { + const handleClick = useCallback(() => { + onSend(control.value ?? true); + }, [control.value, onSend]); + + return {control.label}; +}; + +// --------------------------------------------------------------------------- +// Main component +// --------------------------------------------------------------------------- + +/** Groups controls by their `group` field. Ungrouped controls come first. */ +function groupControls(controls: ControlConfig[]): Map { + const groups = new Map(); + for (const c of controls) { + const key = c.group ?? null; + const list = groups.get(key); + if (list) { + list.push(c); + } else { + groups.set(key, [c]); + } + } + return groups; +} + +const OverlayControls: React.FC = ({ pipelineYaml, sessionId }) => { + const { tuneNodeConfig } = useTuneNode(sessionId); + + // Parse controls from the pipeline YAML's client section. + const controls: ControlConfig[] = useMemo( + () => parseClientFromYaml(pipelineYaml)?.controls ?? [], + [pipelineYaml] + ); + + // Build a send callback for a control. A new closure is created per + // render, but child controls absorb this via onSendRef so it is safe. + const makeSend = useCallback( + (control: ControlConfig) => (value: unknown) => { + const update = buildParamUpdate(control.property, value); + tuneNodeConfig(control.node, update); + }, + [tuneNodeConfig] + ); + + const grouped = useMemo(() => groupControls(controls), [controls]); + + if (controls.length === 0) return null; + + return ( +
+ Pipeline Controls + + {Array.from(grouped.entries()).map(([groupName, items]) => ( + + {groupName && {groupName}} + {items.map((control) => { + const key = `${control.node}:${control.property}`; + const send = makeSend(control); + return ( + + {control.label} + {control.type === 'toggle' && } + {control.type === 'text' && } + {control.type === 'number' && } + {control.type === 'button' && } + + ); + })} + + ))} + +
+ ); +}; + +export default OverlayControls; diff --git a/ui/src/hooks/useTuneNode.ts b/ui/src/hooks/useTuneNode.ts new file mode 100644 index 00000000..d54b323b --- /dev/null +++ b/ui/src/hooks/useTuneNode.ts @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +import { useCallback } from 'react'; +import { v4 as uuidv4 } from 'uuid'; + +import { getWebSocketService } from '@/services/websocket'; +import { sessionStore, nodeParamsAtom, nodeKey, writeNodeParams } from '@/stores/sessionAtoms'; +import type { Request, MessageType } from '@/types/types'; +import { deepMerge } from '@/utils/controlProps'; + +// Resolved once at module level — getWebSocketService returns a singleton, +// so hoisting it avoids a new reference on every render and keeps +// tuneNodeConfig's useCallback deps minimal. +const wsService = getWebSocketService(); + +/** + * Lightweight hook that only provides `tuneNodeConfig` without subscribing + * to pipeline or connection state. Use this in components that need to + * send `UpdateParams` messages but don't need to read session state (e.g. + * `OverlayControls`). This avoids unnecessary re-renders caused by the + * broader `useSession` hook's subscriptions. + * + * Unlike `useSession.tuneNodeConfig`, this deep-merges partial nested + * configs into the existing atom state so that sibling properties (e.g. + * `properties.home_score` and `properties.away_score`) are preserved. + */ +export function useTuneNode(sessionId: string | null) { + const tuneNodeConfig = useCallback( + (nodeId: string, config: Record) => { + if (!sessionId) return; + + // Deep-merge the partial update into the current atom value so + // sibling nested properties are preserved (e.g. updating + // properties.home_score doesn't clobber properties.away_score). + const k = nodeKey(sessionId, nodeId); + const current = sessionStore.get(nodeParamsAtom(k)); + const merged = deepMerge(current, config); + writeNodeParams(nodeId, merged, sessionId); + + const request: Request = { + type: 'request' as MessageType, + correlation_id: uuidv4(), + payload: { + action: 'tunenodeasync' as const, + session_id: sessionId, + node_id: nodeId, + message: { + UpdateParams: config, + }, + }, + }; + + wsService.sendFireAndForget(request); + }, + [sessionId] + ); + + return { tuneNodeConfig }; +} diff --git a/ui/src/types/generated/api-types.ts b/ui/src/types/generated/api-types.ts index 392ebbbe..7a58734e 100644 --- a/ui/src/types/generated/api-types.ts +++ b/ui/src/types/generated/api-types.ts @@ -501,7 +501,11 @@ input: InputConfig | null, /** * Output rendering configuration (oneshot pipelines). */ -output: OutputConfig | null, }; +output: OutputConfig | null, +/** + * Declarative overlay controls for runtime node tuning (dynamic pipelines). + */ +controls: Array | null, }; export type PublishConfig = { /** @@ -625,4 +629,49 @@ accept: string | null, */ placeholder: string | null, }; -export type FieldType = "file" | "text"; \ No newline at end of file +export type FieldType = "file" | "text"; + +export type ControlType = "toggle" | "text" | "number" | "button"; + +export type ControlConfig = { +/** + * Human-readable label shown next to the widget. + */ +label: string, +/** + * Widget type. + */ +type: ControlType, +/** + * Target node ID in the pipeline graph. + */ +node: string, +/** + * Dot-notation property path, e.g. `"properties.home_score"`. + */ +property: string, +/** + * Optional grouping label — controls with the same group are rendered + * together under a shared heading. + */ +group: string | null, +/** + * Default value sent on first render / reset. + */ +default: unknown, +/** + * Minimum value (number controls). + */ +min: number | null, +/** + * Maximum value (number controls). + */ +max: number | null, +/** + * Step increment (number controls). + */ +step: number | null, +/** + * Fixed value sent on click (button controls). Defaults to `true`. + */ +value: unknown, }; \ No newline at end of file diff --git a/ui/src/utils/clientSection.test.ts b/ui/src/utils/clientSection.test.ts index 22897685..bd5cf7ad 100644 --- a/ui/src/utils/clientSection.test.ts +++ b/ui/src/utils/clientSection.test.ts @@ -41,6 +41,7 @@ describe('extractClientFromParsed', () => { field_hints: null, }, output: { type: 'audio' }, + controls: null, }; const parsed = { nodes: {}, client }; expect(extractClientFromParsed(parsed)).toBe(client); @@ -69,6 +70,7 @@ describe('extractClientSection', () => { watch: null, input: null, output: null, + controls: null, }; const pipeline = { nodes: {}, connections: [], mode: 'live', client } as never; expect(extractClientSection(pipeline)).toBe(client); @@ -106,6 +108,7 @@ describe('deriveSettingsFromClient', () => { watch: { broadcast: 'composited-output', mse_path: null, audio: true, video: true }, input: null, output: null, + controls: null, }; const settings = deriveSettingsFromClient(client); @@ -167,6 +170,7 @@ describe('deriveSettingsFromClient', () => { watch: { broadcast: 'output', mse_path: null, audio: false, video: true }, input: null, output: null, + controls: null, }; const settings = deriveSettingsFromClient(client); @@ -206,6 +210,7 @@ describe('deriveSettingsFromClient', () => { watch: { broadcast: 'preview', mse_path: null, audio: false, video: true }, input: null, output: null, + controls: null, }; const settings = deriveSettingsFromClient(client); @@ -241,6 +246,7 @@ describe('deriveSettingsFromClient', () => { field_hints: null, }, output: { type: 'transcription' }, + controls: null, }; const settings = deriveSettingsFromClient(client); @@ -387,6 +393,7 @@ describe('deriveSettingsFromClient — monitor preview scenarios', () => { watch: { broadcast: 'output', mse_path: null, audio: true, video: false }, input: null, output: null, + controls: null, }; const settings = deriveSettingsFromClient(client); @@ -428,6 +435,7 @@ describe('deriveSettingsFromClient — monitor preview scenarios', () => { watch: { broadcast: 'composited', mse_path: null, audio: true, video: true }, input: null, output: null, + controls: null, }; const settings = deriveSettingsFromClient(client); @@ -467,6 +475,7 @@ describe('deriveSettingsFromClient — monitor preview scenarios', () => { watch: { broadcast: 'output', mse_path: null, audio: true, video: true }, input: null, output: null, + controls: null, }; const settings = deriveSettingsFromClient(client); @@ -515,6 +524,7 @@ describe('deriveSettingsFromClient — monitor preview scenarios', () => { watch: { broadcast: 'preview', mse_path: null, audio: false, video: true }, input: null, output: null, + controls: null, }; const settings = deriveSettingsFromClient(client); diff --git a/ui/src/utils/controlProps.test.ts b/ui/src/utils/controlProps.test.ts new file mode 100644 index 00000000..899f3375 --- /dev/null +++ b/ui/src/utils/controlProps.test.ts @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +import { describe, it, expect } from 'vitest'; + +import { buildParamUpdate, deepMerge } from './controlProps'; + +describe('buildParamUpdate', () => { + it('wraps a single-segment path as a flat key', () => { + expect(buildParamUpdate('gain_db', 1.5)).toEqual({ gain_db: 1.5 }); + }); + + it('nests a two-segment dot path', () => { + expect(buildParamUpdate('properties.home_score', 4)).toEqual({ + properties: { home_score: 4 }, + }); + }); + + it('nests a three-segment dot path', () => { + expect(buildParamUpdate('a.b.c', true)).toEqual({ a: { b: { c: true } } }); + }); + + it('handles string values', () => { + expect(buildParamUpdate('properties.name', 'Alex')).toEqual({ + properties: { name: 'Alex' }, + }); + }); + + it('handles null and undefined values', () => { + expect(buildParamUpdate('key', null)).toEqual({ key: null }); + expect(buildParamUpdate('key', undefined)).toEqual({ key: undefined }); + }); + + it('throws on an empty string path', () => { + expect(() => buildParamUpdate('', 1)).toThrow(/at least one non-empty segment/); + }); + + it('throws on a dot-only path', () => { + expect(() => buildParamUpdate('.', 1)).toThrow(/at least one non-empty segment/); + expect(() => buildParamUpdate('..', 1)).toThrow(/at least one non-empty segment/); + }); + + it('filters empty segments from malformed paths like "a..b"', () => { + expect(buildParamUpdate('a..b', 42)).toEqual({ a: { b: 42 } }); + }); + + it('filters leading/trailing dots', () => { + expect(buildParamUpdate('.foo.bar.', 'x')).toEqual({ foo: { bar: 'x' } }); + }); +}); + +describe('deepMerge', () => { + it('merges flat keys without clobbering siblings', () => { + const target = { a: 1, b: 2 }; + const source = { b: 3, c: 4 }; + expect(deepMerge(target, source)).toEqual({ a: 1, b: 3, c: 4 }); + }); + + it('recursively merges nested objects', () => { + const target = { properties: { home_score: 3, away_score: 1 } }; + const source = { properties: { home_score: 4 } }; + expect(deepMerge(target, source)).toEqual({ + properties: { home_score: 4, away_score: 1 }, + }); + }); + + it('preserves sibling nested properties across successive merges', () => { + const state1 = deepMerge({}, { properties: { home_score: 3 } }); + const state2 = deepMerge(state1, { properties: { away_score: 1 } }); + expect(state2).toEqual({ properties: { home_score: 3, away_score: 1 } }); + }); + + it('replaces non-object values wholesale', () => { + const target = { x: 'old' }; + const source = { x: 'new' }; + expect(deepMerge(target, source)).toEqual({ x: 'new' }); + }); + + it('replaces arrays instead of merging them', () => { + const target = { items: [1, 2, 3] }; + const source = { items: [4, 5] }; + expect(deepMerge(target, source)).toEqual({ items: [4, 5] }); + }); + + it('replaces an object with a primitive', () => { + const target = { nested: { a: 1 } }; + const source = { nested: 42 }; + expect(deepMerge(target, source)).toEqual({ nested: 42 }); + }); + + it('replaces a primitive with an object', () => { + const target = { nested: 42 }; + const source = { nested: { a: 1 } }; + expect(deepMerge(target, source)).toEqual({ nested: { a: 1 } }); + }); + + it('does not mutate the target', () => { + const target = { properties: { score: 1 } }; + const source = { properties: { score: 2 } }; + deepMerge(target, source); + expect(target).toEqual({ properties: { score: 1 } }); + }); +}); diff --git a/ui/src/utils/controlProps.ts b/ui/src/utils/controlProps.ts new file mode 100644 index 00000000..ece8c618 --- /dev/null +++ b/ui/src/utils/controlProps.ts @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: © 2025 StreamKit Contributors +// +// SPDX-License-Identifier: MPL-2.0 + +/** + * Builds a nested `UpdateParams` object from a dot-notation property path. + * + * For example: + * - `buildParamUpdate("properties.home_score", 4)` → `{ properties: { home_score: 4 } }` + * - `buildParamUpdate("gain_db", 1.5)` → `{ gain_db: 1.5 }` + * + * Empty or whitespace-only segments are discarded so that malformed paths + * like `""`, `"."`, or `"a..b"` degrade gracefully instead of producing + * keys with empty strings. + * + * @throws {Error} if the path resolves to zero valid segments. + */ +export function buildParamUpdate(path: string, value: unknown): Record { + const parts = path.split('.').filter(Boolean); + if (parts.length === 0) { + throw new Error( + `buildParamUpdate: path must contain at least one non-empty segment, got "${path}"` + ); + } + let result: unknown = value; + for (let i = parts.length - 1; i > 0; i--) { + result = { [parts[i]]: result }; + } + return { [parts[0]]: result }; +} + +function isPlainObject(v: unknown): v is Record { + return typeof v === 'object' && v !== null && !Array.isArray(v); +} + +/** + * Recursively deep-merges `source` into `target`, returning a new object. + * Only plain objects are merged recursively; arrays and other values are + * replaced wholesale (matching the semantics of `UpdateParams`). + */ +export function deepMerge( + target: Record, + source: Record +): Record { + const result: Record = { ...target }; + for (const key of Object.keys(source)) { + const srcVal = source[key]; + const tgtVal = result[key]; + if (isPlainObject(srcVal) && isPlainObject(tgtVal)) { + result[key] = deepMerge(tgtVal, srcVal); + } else { + result[key] = srcVal; + } + } + return result; +} diff --git a/ui/src/views/StreamView.tsx b/ui/src/views/StreamView.tsx index 9c7abd18..182509f4 100644 --- a/ui/src/views/StreamView.tsx +++ b/ui/src/views/StreamView.tsx @@ -11,6 +11,7 @@ import { useShallow } from 'zustand/shallow'; import ConfirmModal from '@/components/ConfirmModal'; import { NativeStreamPlayer } from '@/components/NativeStreamPlayer'; import { VolumeSlider } from '@/components/OutputPreviewPanel'; +import OverlayControls from '@/components/stream/OverlayControls'; import { PipelineSelectionSection } from '@/components/stream/PipelineSelectionSection'; import { TelemetryTimeline as TelemetryTimelineComponent } from '@/components/TelemetryTimeline'; import { @@ -1057,6 +1058,10 @@ const StreamView: React.FC = () => { )} + {activeSessionId && viewState.pipelineYaml && ( + + )} + {isStreaming && videoRenderer && !msePath && (
Video