diff --git a/.gitignore b/.gitignore
index 385fc940..c4d779a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,8 @@ lyrics/
/index.css
/startup_script.js
/worker_script.js
+/vendor
+
.DS_Store
target/
.yarn/cache/
@@ -19,3 +21,4 @@ target/
*.tsbuildinfo
.million/
.opencode
+AGENTS.md
diff --git a/packages/player/extension-window.html b/packages/player/extension-window.html
new file mode 100644
index 00000000..ec091303
--- /dev/null
+++ b/packages/player/extension-window.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+ AMLL Player Extension Window
+
+
+
+
+
+
+
diff --git a/packages/player/src-tauri/build.rs b/packages/player/src-tauri/build.rs
index 261851f6..b2fba3a2 100644
--- a/packages/player/src-tauri/build.rs
+++ b/packages/player/src-tauri/build.rs
@@ -1,3 +1,20 @@
fn main() {
- tauri_build::build();
+ let attrs =
+ tauri_build::Attributes::new().app_manifest(tauri_build::AppManifest::new().commands(&[
+ "extension_window_create",
+ "extension_window_get",
+ "extension_window_close",
+ "extension_window_close_all",
+ "extension_window_show",
+ "extension_window_hide",
+ "extension_window_focus",
+ "extension_window_center",
+ "extension_window_set_title",
+ "extension_window_set_size",
+ "extension_window_set_position",
+ "extension_window_mark_ready",
+ "extension_window_get_current",
+ "extension_window_get_current_extension_files",
+ ]));
+ tauri_build::try_build(attrs).expect("failed to run tauri build script");
}
diff --git a/packages/player/src-tauri/capabilities/extension-window.toml b/packages/player/src-tauri/capabilities/extension-window.toml
new file mode 100644
index 00000000..ee23c324
--- /dev/null
+++ b/packages/player/src-tauri/capabilities/extension-window.toml
@@ -0,0 +1,46 @@
+"$schema" = "../gen/schemas/desktop-schema.json"
+
+identifier = "extension-window"
+local = true
+windows = ["extension-window/*"]
+platforms = ["macOS", "windows", "linux"]
+
+[[permissions]]
+identifier = "core:event:allow-listen"
+[[permissions]]
+identifier = "core:event:allow-emit"
+[[permissions]]
+identifier = "core:event:allow-emit-to"
+[[permissions]]
+identifier = "core:event:allow-unlisten"
+
+[[permissions]]
+identifier = "allow-extension-window-get-current"
+
+[[permissions]]
+identifier = "allow-extension-window-get-current-extension-files"
+
+[[permissions]]
+identifier = "allow-extension-window-create"
+[[permissions]]
+identifier = "allow-extension-window-get"
+[[permissions]]
+identifier = "allow-extension-window-close"
+[[permissions]]
+identifier = "allow-extension-window-close-all"
+[[permissions]]
+identifier = "allow-extension-window-show"
+[[permissions]]
+identifier = "allow-extension-window-hide"
+[[permissions]]
+identifier = "allow-extension-window-focus"
+[[permissions]]
+identifier = "allow-extension-window-center"
+[[permissions]]
+identifier = "allow-extension-window-set-title"
+[[permissions]]
+identifier = "allow-extension-window-set-size"
+[[permissions]]
+identifier = "allow-extension-window-set-position"
+[[permissions]]
+identifier = "allow-extension-window-mark-ready"
diff --git a/packages/player/src-tauri/capabilities/migrated.toml b/packages/player/src-tauri/capabilities/migrated.toml
index 5436d781..618bc229 100644
--- a/packages/player/src-tauri/capabilities/migrated.toml
+++ b/packages/player/src-tauri/capabilities/migrated.toml
@@ -61,5 +61,28 @@ identifier = "fs:allow-stat"
identifier = "fs:read-app-specific-dirs-recursive"
[[permissions]]
identifier = "fs:create-app-specific-dirs"
-[[permissions]]
-identifier = "dialog:default"
+[[permissions]]
+identifier = "dialog:default"
+
+[[permissions]]
+identifier = "allow-extension-window-create"
+[[permissions]]
+identifier = "allow-extension-window-get"
+[[permissions]]
+identifier = "allow-extension-window-close"
+[[permissions]]
+identifier = "allow-extension-window-close-all"
+[[permissions]]
+identifier = "allow-extension-window-show"
+[[permissions]]
+identifier = "allow-extension-window-hide"
+[[permissions]]
+identifier = "allow-extension-window-focus"
+[[permissions]]
+identifier = "allow-extension-window-center"
+[[permissions]]
+identifier = "allow-extension-window-set-title"
+[[permissions]]
+identifier = "allow-extension-window-set-size"
+[[permissions]]
+identifier = "allow-extension-window-set-position"
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_center.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_center.toml
new file mode 100644
index 00000000..e34d11df
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_center.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-center"
+description = "Enables the extension_window_center command without any pre-configured scope."
+commands.allow = ["extension_window_center"]
+
+[[permission]]
+identifier = "deny-extension-window-center"
+description = "Denies the extension_window_center command without any pre-configured scope."
+commands.deny = ["extension_window_center"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_close.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_close.toml
new file mode 100644
index 00000000..b93b84d1
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_close.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-close"
+description = "Enables the extension_window_close command without any pre-configured scope."
+commands.allow = ["extension_window_close"]
+
+[[permission]]
+identifier = "deny-extension-window-close"
+description = "Denies the extension_window_close command without any pre-configured scope."
+commands.deny = ["extension_window_close"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_close_all.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_close_all.toml
new file mode 100644
index 00000000..0e2928f4
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_close_all.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-close-all"
+description = "Enables the extension_window_close_all command without any pre-configured scope."
+commands.allow = ["extension_window_close_all"]
+
+[[permission]]
+identifier = "deny-extension-window-close-all"
+description = "Denies the extension_window_close_all command without any pre-configured scope."
+commands.deny = ["extension_window_close_all"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_create.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_create.toml
new file mode 100644
index 00000000..09169f05
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_create.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-create"
+description = "Enables the extension_window_create command without any pre-configured scope."
+commands.allow = ["extension_window_create"]
+
+[[permission]]
+identifier = "deny-extension-window-create"
+description = "Denies the extension_window_create command without any pre-configured scope."
+commands.deny = ["extension_window_create"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_focus.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_focus.toml
new file mode 100644
index 00000000..a9562877
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_focus.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-focus"
+description = "Enables the extension_window_focus command without any pre-configured scope."
+commands.allow = ["extension_window_focus"]
+
+[[permission]]
+identifier = "deny-extension-window-focus"
+description = "Denies the extension_window_focus command without any pre-configured scope."
+commands.deny = ["extension_window_focus"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_get.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_get.toml
new file mode 100644
index 00000000..e2d08638
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_get.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-get"
+description = "Enables the extension_window_get command without any pre-configured scope."
+commands.allow = ["extension_window_get"]
+
+[[permission]]
+identifier = "deny-extension-window-get"
+description = "Denies the extension_window_get command without any pre-configured scope."
+commands.deny = ["extension_window_get"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_get_current.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_get_current.toml
new file mode 100644
index 00000000..b17ee0d0
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_get_current.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-get-current"
+description = "Enables the extension_window_get_current command without any pre-configured scope."
+commands.allow = ["extension_window_get_current"]
+
+[[permission]]
+identifier = "deny-extension-window-get-current"
+description = "Denies the extension_window_get_current command without any pre-configured scope."
+commands.deny = ["extension_window_get_current"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_get_current_extension_files.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_get_current_extension_files.toml
new file mode 100644
index 00000000..d772d9ee
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_get_current_extension_files.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-get-current-extension-files"
+description = "Enables the extension_window_get_current_extension_files command without any pre-configured scope."
+commands.allow = ["extension_window_get_current_extension_files"]
+
+[[permission]]
+identifier = "deny-extension-window-get-current-extension-files"
+description = "Denies the extension_window_get_current_extension_files command without any pre-configured scope."
+commands.deny = ["extension_window_get_current_extension_files"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_hide.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_hide.toml
new file mode 100644
index 00000000..f23ccf64
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_hide.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-hide"
+description = "Enables the extension_window_hide command without any pre-configured scope."
+commands.allow = ["extension_window_hide"]
+
+[[permission]]
+identifier = "deny-extension-window-hide"
+description = "Denies the extension_window_hide command without any pre-configured scope."
+commands.deny = ["extension_window_hide"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_mark_ready.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_mark_ready.toml
new file mode 100644
index 00000000..f6e188e1
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_mark_ready.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-mark-ready"
+description = "Enables the extension_window_mark_ready command without any pre-configured scope."
+commands.allow = ["extension_window_mark_ready"]
+
+[[permission]]
+identifier = "deny-extension-window-mark-ready"
+description = "Denies the extension_window_mark_ready command without any pre-configured scope."
+commands.deny = ["extension_window_mark_ready"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_set_position.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_set_position.toml
new file mode 100644
index 00000000..4793af7f
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_set_position.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-set-position"
+description = "Enables the extension_window_set_position command without any pre-configured scope."
+commands.allow = ["extension_window_set_position"]
+
+[[permission]]
+identifier = "deny-extension-window-set-position"
+description = "Denies the extension_window_set_position command without any pre-configured scope."
+commands.deny = ["extension_window_set_position"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_set_size.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_set_size.toml
new file mode 100644
index 00000000..46d18e85
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_set_size.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-set-size"
+description = "Enables the extension_window_set_size command without any pre-configured scope."
+commands.allow = ["extension_window_set_size"]
+
+[[permission]]
+identifier = "deny-extension-window-set-size"
+description = "Denies the extension_window_set_size command without any pre-configured scope."
+commands.deny = ["extension_window_set_size"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_set_title.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_set_title.toml
new file mode 100644
index 00000000..544b1e1a
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_set_title.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-set-title"
+description = "Enables the extension_window_set_title command without any pre-configured scope."
+commands.allow = ["extension_window_set_title"]
+
+[[permission]]
+identifier = "deny-extension-window-set-title"
+description = "Denies the extension_window_set_title command without any pre-configured scope."
+commands.deny = ["extension_window_set_title"]
diff --git a/packages/player/src-tauri/permissions/autogenerated/extension_window_show.toml b/packages/player/src-tauri/permissions/autogenerated/extension_window_show.toml
new file mode 100644
index 00000000..4ad20add
--- /dev/null
+++ b/packages/player/src-tauri/permissions/autogenerated/extension_window_show.toml
@@ -0,0 +1,11 @@
+# Automatically generated - DO NOT EDIT!
+
+[[permission]]
+identifier = "allow-extension-window-show"
+description = "Enables the extension_window_show command without any pre-configured scope."
+commands.allow = ["extension_window_show"]
+
+[[permission]]
+identifier = "deny-extension-window-show"
+description = "Denies the extension_window_show command without any pre-configured scope."
+commands.deny = ["extension_window_show"]
diff --git a/packages/player/src-tauri/src/extension_window.rs b/packages/player/src-tauri/src/extension_window.rs
new file mode 100644
index 00000000..7b360270
--- /dev/null
+++ b/packages/player/src-tauri/src/extension_window.rs
@@ -0,0 +1,931 @@
+use std::collections::{HashMap, HashSet};
+use std::fs;
+use std::sync::Mutex;
+
+use serde::{Deserialize, Serialize};
+use tauri::{
+ AppHandle, LogicalPosition, LogicalSize, LogicalUnit, Manager, PixelUnit, State, WebviewUrl,
+ WebviewWindow, WebviewWindowBuilder, WindowSizeConstraints, path::BaseDirectory,
+};
+
+const EXTENSION_WINDOW_LABEL_PREFIX: &str = "extension-window/";
+const EXTENSION_WINDOW_ENTRY: &str = "extension-window.html";
+const DEFAULT_WINDOW_WIDTH: f64 = 800.0;
+const DEFAULT_WINDOW_HEIGHT: f64 = 600.0;
+
+#[derive(Clone, Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ExtensionWindowInfo {
+ pub extension_id: String,
+ pub window_id: String,
+ pub label: String,
+}
+
+#[derive(Clone, Debug, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ExtensionScriptFile {
+ pub file_name: String,
+ pub script_data: String,
+}
+
+struct ExtensionScriptSource {
+ file_name: String,
+ script_data: String,
+ id: Option,
+ dependency: Vec,
+}
+
+#[derive(Clone, Debug, Default, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ExtensionWindowOptions {
+ pub title: Option,
+ pub width: Option,
+ pub height: Option,
+ pub x: Option,
+ pub y: Option,
+ pub center: Option,
+ pub resizable: Option,
+ pub decorations: Option,
+ pub visible: Option,
+ pub min_width: Option,
+ pub min_height: Option,
+ pub max_width: Option,
+ pub max_height: Option,
+}
+
+#[derive(Default)]
+pub struct ExtensionWindowState {
+ windows: Mutex,
+}
+
+#[derive(Default)]
+struct ExtensionWindowMaps {
+ by_label: HashMap,
+ by_extension: HashMap>,
+ visibility_by_label: HashMap,
+}
+
+#[derive(Clone, Debug)]
+struct ExtensionWindowVisibility {
+ ready: bool,
+ should_show: bool,
+ should_focus: bool,
+}
+
+impl ExtensionWindowVisibility {
+ fn ready() -> Self {
+ Self {
+ ready: true,
+ should_show: true,
+ should_focus: false,
+ }
+ }
+
+ fn pending(should_show: bool) -> Self {
+ Self {
+ ready: false,
+ should_show,
+ should_focus: should_show,
+ }
+ }
+}
+
+impl ExtensionWindowState {
+ fn insert(&self, info: ExtensionWindowInfo) {
+ self.insert_with_visibility(info, ExtensionWindowVisibility::ready());
+ }
+
+ fn insert_pending(&self, info: ExtensionWindowInfo, should_show: bool) {
+ self.insert_with_visibility(info, ExtensionWindowVisibility::pending(should_show));
+ }
+
+ fn insert_with_visibility(
+ &self,
+ info: ExtensionWindowInfo,
+ visibility: ExtensionWindowVisibility,
+ ) {
+ let mut maps = self.windows.lock().unwrap();
+ maps.by_extension
+ .entry(info.extension_id.clone())
+ .or_default()
+ .insert(info.label.clone());
+ maps.visibility_by_label
+ .insert(info.label.clone(), visibility);
+ maps.by_label.insert(info.label.clone(), info);
+ }
+
+ fn get_by_label(&self, label: &str) -> Option {
+ self.windows.lock().unwrap().by_label.get(label).cloned()
+ }
+
+ fn get_by_owner(
+ &self,
+ extension_id: &str,
+ window_id: &str,
+ ) -> Result