diff --git a/CHANGELOG.md b/CHANGELOG.md index cb450c3..4dfda32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -136,3 +136,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `useClipboardMonitoring` React hook with server-confirmed state updates - Vitest test infrastructure: jsdom environment, setup file, Tauri API mocks - Frontend tests for ClipboardIndicator (4 tests) and useClipboardMonitoring (4 tests) +- Settings View (Task 23) + - Expanded `AppConfig` domain model from 9 to 32 fields across 6 categories + - `ConfigPatch` with `apply_patch()` utility for partial config updates + - `TomlConfigStore` adapter: read/write `~/.config/vortex/config.toml` with atomic writes and auto-defaults + - `UpdateConfigCommand` handler with input validation (proxy_type, theme, port bounds, limits) + - `SettingsUpdated` domain event with Tauri bridge forwarding + - Settings IPC: `settings_get` query + `settings_update` command with camelCase DTO serialization + - SettingsView with 6-tab sidebar layout (General, Downloads, Network, Remote Access, Browser, Appearance) + - Shared `SettingToggle` and `SettingNumberInput` field components + - GeneralSection: download directory, 7 toggle settings + - DownloadsSection: 5 numeric settings with MB/s speed limit conversion, 2 toggles + - NetworkSection: proxy type selector with conditional URL, user-agent, DoH, timeout + - RemoteAccessSection: security warning, web interface/REST API/WebSocket toggles, API key display with show/hide/copy/regenerate + - BrowserSection: min file size, excluded domains/extensions via comma-separated textarea + - AppearanceSection: theme selector, 6 accent color presets, compact mode, language selector + - Event-based cache invalidation for settings changes from external sources + - 35 frontend tests (SettingsView + 6 sections + settingsStore) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ed24fab..6ac764b 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -6665,6 +6665,21 @@ dependencies = [ "url", ] +[[package]] +name = "tauri-plugin-pilot" +version = "0.3.0" +source = "git+https://github.com/mpiton/tauri-pilot#571761e3a053daa5322ce60c296c4bedfce988c2" +dependencies = [ + "libc", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "tokio", + "tracing", +] + [[package]] name = "tauri-runtime" version = "2.10.1" @@ -7537,6 +7552,7 @@ dependencies = [ "tauri-build", "tauri-plugin-clipboard-manager", "tauri-plugin-notification", + "tauri-plugin-pilot", "tempfile", "thiserror 2.0.18", "tokio", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index d66b6b2..100a6a9 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -39,6 +39,9 @@ tauri-plugin-clipboard-manager = "2" tauri-plugin-notification = "2" regex = "1" +[target.'cfg(unix)'.dependencies] +tauri-plugin-pilot = { git = "https://github.com/mpiton/tauri-pilot", version = "0.3.0" } + [dev-dependencies] tempfile = "3.27.0" wiremock = "0.6.5" diff --git a/src-tauri/capabilities/dev-pilot.json b/src-tauri/capabilities/dev-pilot.json new file mode 100644 index 0000000..880a074 --- /dev/null +++ b/src-tauri/capabilities/dev-pilot.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "identifier": "dev-pilot", + "description": "tauri-pilot testing plugin (Unix debug builds only)", + "windows": ["main"], + "platforms": ["linux", "macOS"], + "permissions": [ + "pilot:default" + ] +} diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json index 3a3597e..776e5f1 100644 --- a/src-tauri/gen/schemas/acl-manifests.json +++ b/src-tauri/gen/schemas/acl-manifests.json @@ -1 +1 @@ -{"clipboard-manager":{"default_permission":{"identifier":"default","description":"No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n","permissions":[]},"permissions":{"allow-clear":{"identifier":"allow-clear","description":"Enables the clear command without any pre-configured scope.","commands":{"allow":["clear"],"deny":[]}},"allow-read-image":{"identifier":"allow-read-image","description":"Enables the read_image command without any pre-configured scope.","commands":{"allow":["read_image"],"deny":[]}},"allow-read-text":{"identifier":"allow-read-text","description":"Enables the read_text command without any pre-configured scope.","commands":{"allow":["read_text"],"deny":[]}},"allow-write-html":{"identifier":"allow-write-html","description":"Enables the write_html command without any pre-configured scope.","commands":{"allow":["write_html"],"deny":[]}},"allow-write-image":{"identifier":"allow-write-image","description":"Enables the write_image command without any pre-configured scope.","commands":{"allow":["write_image"],"deny":[]}},"allow-write-text":{"identifier":"allow-write-text","description":"Enables the write_text command without any pre-configured scope.","commands":{"allow":["write_text"],"deny":[]}},"deny-clear":{"identifier":"deny-clear","description":"Denies the clear command without any pre-configured scope.","commands":{"allow":[],"deny":["clear"]}},"deny-read-image":{"identifier":"deny-read-image","description":"Denies the read_image command without any pre-configured scope.","commands":{"allow":[],"deny":["read_image"]}},"deny-read-text":{"identifier":"deny-read-text","description":"Denies the read_text command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text"]}},"deny-write-html":{"identifier":"deny-write-html","description":"Denies the write_html command without any pre-configured scope.","commands":{"allow":[],"deny":["write_html"]}},"deny-write-image":{"identifier":"deny-write-image","description":"Denies the write_image command without any pre-configured scope.","commands":{"allow":[],"deny":["write_image"]}},"deny-write-text":{"identifier":"deny-write-text","description":"Denies the write_text command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text"]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"notification":{"default_permission":{"identifier":"default","description":"This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n","permissions":["allow-is-permission-granted","allow-request-permission","allow-notify","allow-register-action-types","allow-register-listener","allow-cancel","allow-get-pending","allow-remove-active","allow-get-active","allow-check-permissions","allow-show","allow-batch","allow-list-channels","allow-delete-channel","allow-create-channel","allow-permission-state"]},"permissions":{"allow-batch":{"identifier":"allow-batch","description":"Enables the batch command without any pre-configured scope.","commands":{"allow":["batch"],"deny":[]}},"allow-cancel":{"identifier":"allow-cancel","description":"Enables the cancel command without any pre-configured scope.","commands":{"allow":["cancel"],"deny":[]}},"allow-check-permissions":{"identifier":"allow-check-permissions","description":"Enables the check_permissions command without any pre-configured scope.","commands":{"allow":["check_permissions"],"deny":[]}},"allow-create-channel":{"identifier":"allow-create-channel","description":"Enables the create_channel command without any pre-configured scope.","commands":{"allow":["create_channel"],"deny":[]}},"allow-delete-channel":{"identifier":"allow-delete-channel","description":"Enables the delete_channel command without any pre-configured scope.","commands":{"allow":["delete_channel"],"deny":[]}},"allow-get-active":{"identifier":"allow-get-active","description":"Enables the get_active command without any pre-configured scope.","commands":{"allow":["get_active"],"deny":[]}},"allow-get-pending":{"identifier":"allow-get-pending","description":"Enables the get_pending command without any pre-configured scope.","commands":{"allow":["get_pending"],"deny":[]}},"allow-is-permission-granted":{"identifier":"allow-is-permission-granted","description":"Enables the is_permission_granted command without any pre-configured scope.","commands":{"allow":["is_permission_granted"],"deny":[]}},"allow-list-channels":{"identifier":"allow-list-channels","description":"Enables the list_channels command without any pre-configured scope.","commands":{"allow":["list_channels"],"deny":[]}},"allow-notify":{"identifier":"allow-notify","description":"Enables the notify command without any pre-configured scope.","commands":{"allow":["notify"],"deny":[]}},"allow-permission-state":{"identifier":"allow-permission-state","description":"Enables the permission_state command without any pre-configured scope.","commands":{"allow":["permission_state"],"deny":[]}},"allow-register-action-types":{"identifier":"allow-register-action-types","description":"Enables the register_action_types command without any pre-configured scope.","commands":{"allow":["register_action_types"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-active":{"identifier":"allow-remove-active","description":"Enables the remove_active command without any pre-configured scope.","commands":{"allow":["remove_active"],"deny":[]}},"allow-request-permission":{"identifier":"allow-request-permission","description":"Enables the request_permission command without any pre-configured scope.","commands":{"allow":["request_permission"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"deny-batch":{"identifier":"deny-batch","description":"Denies the batch command without any pre-configured scope.","commands":{"allow":[],"deny":["batch"]}},"deny-cancel":{"identifier":"deny-cancel","description":"Denies the cancel command without any pre-configured scope.","commands":{"allow":[],"deny":["cancel"]}},"deny-check-permissions":{"identifier":"deny-check-permissions","description":"Denies the check_permissions command without any pre-configured scope.","commands":{"allow":[],"deny":["check_permissions"]}},"deny-create-channel":{"identifier":"deny-create-channel","description":"Denies the create_channel command without any pre-configured scope.","commands":{"allow":[],"deny":["create_channel"]}},"deny-delete-channel":{"identifier":"deny-delete-channel","description":"Denies the delete_channel command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_channel"]}},"deny-get-active":{"identifier":"deny-get-active","description":"Denies the get_active command without any pre-configured scope.","commands":{"allow":[],"deny":["get_active"]}},"deny-get-pending":{"identifier":"deny-get-pending","description":"Denies the get_pending command without any pre-configured scope.","commands":{"allow":[],"deny":["get_pending"]}},"deny-is-permission-granted":{"identifier":"deny-is-permission-granted","description":"Denies the is_permission_granted command without any pre-configured scope.","commands":{"allow":[],"deny":["is_permission_granted"]}},"deny-list-channels":{"identifier":"deny-list-channels","description":"Denies the list_channels command without any pre-configured scope.","commands":{"allow":[],"deny":["list_channels"]}},"deny-notify":{"identifier":"deny-notify","description":"Denies the notify command without any pre-configured scope.","commands":{"allow":[],"deny":["notify"]}},"deny-permission-state":{"identifier":"deny-permission-state","description":"Denies the permission_state command without any pre-configured scope.","commands":{"allow":[],"deny":["permission_state"]}},"deny-register-action-types":{"identifier":"deny-register-action-types","description":"Denies the register_action_types command without any pre-configured scope.","commands":{"allow":[],"deny":["register_action_types"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-active":{"identifier":"deny-remove-active","description":"Denies the remove_active command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_active"]}},"deny-request-permission":{"identifier":"deny-request-permission","description":"Denies the request_permission command without any pre-configured scope.","commands":{"allow":[],"deny":["request_permission"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file +{"clipboard-manager":{"default_permission":{"identifier":"default","description":"No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n","permissions":[]},"permissions":{"allow-clear":{"identifier":"allow-clear","description":"Enables the clear command without any pre-configured scope.","commands":{"allow":["clear"],"deny":[]}},"allow-read-image":{"identifier":"allow-read-image","description":"Enables the read_image command without any pre-configured scope.","commands":{"allow":["read_image"],"deny":[]}},"allow-read-text":{"identifier":"allow-read-text","description":"Enables the read_text command without any pre-configured scope.","commands":{"allow":["read_text"],"deny":[]}},"allow-write-html":{"identifier":"allow-write-html","description":"Enables the write_html command without any pre-configured scope.","commands":{"allow":["write_html"],"deny":[]}},"allow-write-image":{"identifier":"allow-write-image","description":"Enables the write_image command without any pre-configured scope.","commands":{"allow":["write_image"],"deny":[]}},"allow-write-text":{"identifier":"allow-write-text","description":"Enables the write_text command without any pre-configured scope.","commands":{"allow":["write_text"],"deny":[]}},"deny-clear":{"identifier":"deny-clear","description":"Denies the clear command without any pre-configured scope.","commands":{"allow":[],"deny":["clear"]}},"deny-read-image":{"identifier":"deny-read-image","description":"Denies the read_image command without any pre-configured scope.","commands":{"allow":[],"deny":["read_image"]}},"deny-read-text":{"identifier":"deny-read-text","description":"Denies the read_text command without any pre-configured scope.","commands":{"allow":[],"deny":["read_text"]}},"deny-write-html":{"identifier":"deny-write-html","description":"Denies the write_html command without any pre-configured scope.","commands":{"allow":[],"deny":["write_html"]}},"deny-write-image":{"identifier":"deny-write-image","description":"Denies the write_image command without any pre-configured scope.","commands":{"allow":[],"deny":["write_image"]}},"deny-write-text":{"identifier":"deny-write-text","description":"Denies the write_text command without any pre-configured scope.","commands":{"allow":[],"deny":["write_text"]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"notification":{"default_permission":{"identifier":"default","description":"This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n","permissions":["allow-is-permission-granted","allow-request-permission","allow-notify","allow-register-action-types","allow-register-listener","allow-cancel","allow-get-pending","allow-remove-active","allow-get-active","allow-check-permissions","allow-show","allow-batch","allow-list-channels","allow-delete-channel","allow-create-channel","allow-permission-state"]},"permissions":{"allow-batch":{"identifier":"allow-batch","description":"Enables the batch command without any pre-configured scope.","commands":{"allow":["batch"],"deny":[]}},"allow-cancel":{"identifier":"allow-cancel","description":"Enables the cancel command without any pre-configured scope.","commands":{"allow":["cancel"],"deny":[]}},"allow-check-permissions":{"identifier":"allow-check-permissions","description":"Enables the check_permissions command without any pre-configured scope.","commands":{"allow":["check_permissions"],"deny":[]}},"allow-create-channel":{"identifier":"allow-create-channel","description":"Enables the create_channel command without any pre-configured scope.","commands":{"allow":["create_channel"],"deny":[]}},"allow-delete-channel":{"identifier":"allow-delete-channel","description":"Enables the delete_channel command without any pre-configured scope.","commands":{"allow":["delete_channel"],"deny":[]}},"allow-get-active":{"identifier":"allow-get-active","description":"Enables the get_active command without any pre-configured scope.","commands":{"allow":["get_active"],"deny":[]}},"allow-get-pending":{"identifier":"allow-get-pending","description":"Enables the get_pending command without any pre-configured scope.","commands":{"allow":["get_pending"],"deny":[]}},"allow-is-permission-granted":{"identifier":"allow-is-permission-granted","description":"Enables the is_permission_granted command without any pre-configured scope.","commands":{"allow":["is_permission_granted"],"deny":[]}},"allow-list-channels":{"identifier":"allow-list-channels","description":"Enables the list_channels command without any pre-configured scope.","commands":{"allow":["list_channels"],"deny":[]}},"allow-notify":{"identifier":"allow-notify","description":"Enables the notify command without any pre-configured scope.","commands":{"allow":["notify"],"deny":[]}},"allow-permission-state":{"identifier":"allow-permission-state","description":"Enables the permission_state command without any pre-configured scope.","commands":{"allow":["permission_state"],"deny":[]}},"allow-register-action-types":{"identifier":"allow-register-action-types","description":"Enables the register_action_types command without any pre-configured scope.","commands":{"allow":["register_action_types"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-active":{"identifier":"allow-remove-active","description":"Enables the remove_active command without any pre-configured scope.","commands":{"allow":["remove_active"],"deny":[]}},"allow-request-permission":{"identifier":"allow-request-permission","description":"Enables the request_permission command without any pre-configured scope.","commands":{"allow":["request_permission"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"deny-batch":{"identifier":"deny-batch","description":"Denies the batch command without any pre-configured scope.","commands":{"allow":[],"deny":["batch"]}},"deny-cancel":{"identifier":"deny-cancel","description":"Denies the cancel command without any pre-configured scope.","commands":{"allow":[],"deny":["cancel"]}},"deny-check-permissions":{"identifier":"deny-check-permissions","description":"Denies the check_permissions command without any pre-configured scope.","commands":{"allow":[],"deny":["check_permissions"]}},"deny-create-channel":{"identifier":"deny-create-channel","description":"Denies the create_channel command without any pre-configured scope.","commands":{"allow":[],"deny":["create_channel"]}},"deny-delete-channel":{"identifier":"deny-delete-channel","description":"Denies the delete_channel command without any pre-configured scope.","commands":{"allow":[],"deny":["delete_channel"]}},"deny-get-active":{"identifier":"deny-get-active","description":"Denies the get_active command without any pre-configured scope.","commands":{"allow":[],"deny":["get_active"]}},"deny-get-pending":{"identifier":"deny-get-pending","description":"Denies the get_pending command without any pre-configured scope.","commands":{"allow":[],"deny":["get_pending"]}},"deny-is-permission-granted":{"identifier":"deny-is-permission-granted","description":"Denies the is_permission_granted command without any pre-configured scope.","commands":{"allow":[],"deny":["is_permission_granted"]}},"deny-list-channels":{"identifier":"deny-list-channels","description":"Denies the list_channels command without any pre-configured scope.","commands":{"allow":[],"deny":["list_channels"]}},"deny-notify":{"identifier":"deny-notify","description":"Denies the notify command without any pre-configured scope.","commands":{"allow":[],"deny":["notify"]}},"deny-permission-state":{"identifier":"deny-permission-state","description":"Denies the permission_state command without any pre-configured scope.","commands":{"allow":[],"deny":["permission_state"]}},"deny-register-action-types":{"identifier":"deny-register-action-types","description":"Denies the register_action_types command without any pre-configured scope.","commands":{"allow":[],"deny":["register_action_types"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-active":{"identifier":"deny-remove-active","description":"Denies the remove_active command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_active"]}},"deny-request-permission":{"identifier":"deny-request-permission","description":"Denies the request_permission command without any pre-configured scope.","commands":{"allow":[],"deny":["request_permission"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}}},"permission_sets":{},"global_scope_schema":null},"pilot":{"default_permission":{"identifier":"default","description":"Default permissions for tauri-plugin-pilot (allows eval callback)","permissions":["allow-callback"]},"permissions":{"allow---callback":{"identifier":"allow---callback","description":"Enables the __callback command without any pre-configured scope.","commands":{"allow":["__callback"],"deny":[]}},"allow-callback":{"identifier":"allow-callback","description":"Allow the internal __callback IPC command used by the eval engine","commands":{"allow":["__callback"],"deny":[]}},"deny---callback":{"identifier":"deny---callback","description":"Denies the __callback command without any pre-configured scope.","commands":{"allow":[],"deny":["__callback"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json index 9a92061..257cdc2 100644 --- a/src-tauri/gen/schemas/capabilities.json +++ b/src-tauri/gen/schemas/capabilities.json @@ -1 +1 @@ -{"default":{"identifier":"default","description":"Default capabilities for Vortex","local":true,"windows":["main"],"permissions":["core:default","core:tray:default","notification:allow-notify","notification:allow-request-permission","notification:allow-is-permission-granted"]}} \ No newline at end of file +{"default":{"identifier":"default","description":"Default capabilities for Vortex","local":true,"windows":["main"],"permissions":["core:default","core:tray:default","notification:allow-notify","notification:allow-request-permission","notification:allow-is-permission-granted"]},"dev-pilot":{"identifier":"dev-pilot","description":"tauri-pilot testing plugin (Unix debug builds only)","local":true,"windows":["main"],"permissions":["pilot:default"],"platforms":["linux","macOS"]}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/desktop-schema.json b/src-tauri/gen/schemas/desktop-schema.json index 8a15b22..211e53f 100644 --- a/src-tauri/gen/schemas/desktop-schema.json +++ b/src-tauri/gen/schemas/desktop-schema.json @@ -2419,6 +2419,30 @@ "type": "string", "const": "notification:deny-show", "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Default permissions for tauri-plugin-pilot (allows eval callback)\n#### This default permission set includes:\n\n- `allow-callback`", + "type": "string", + "const": "pilot:default", + "markdownDescription": "Default permissions for tauri-plugin-pilot (allows eval callback)\n#### This default permission set includes:\n\n- `allow-callback`" + }, + { + "description": "Enables the __callback command without any pre-configured scope.", + "type": "string", + "const": "pilot:allow---callback", + "markdownDescription": "Enables the __callback command without any pre-configured scope." + }, + { + "description": "Allow the internal __callback IPC command used by the eval engine", + "type": "string", + "const": "pilot:allow-callback", + "markdownDescription": "Allow the internal __callback IPC command used by the eval engine" + }, + { + "description": "Denies the __callback command without any pre-configured scope.", + "type": "string", + "const": "pilot:deny---callback", + "markdownDescription": "Denies the __callback command without any pre-configured scope." } ] }, diff --git a/src-tauri/gen/schemas/linux-schema.json b/src-tauri/gen/schemas/linux-schema.json index 8a15b22..211e53f 100644 --- a/src-tauri/gen/schemas/linux-schema.json +++ b/src-tauri/gen/schemas/linux-schema.json @@ -2419,6 +2419,30 @@ "type": "string", "const": "notification:deny-show", "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Default permissions for tauri-plugin-pilot (allows eval callback)\n#### This default permission set includes:\n\n- `allow-callback`", + "type": "string", + "const": "pilot:default", + "markdownDescription": "Default permissions for tauri-plugin-pilot (allows eval callback)\n#### This default permission set includes:\n\n- `allow-callback`" + }, + { + "description": "Enables the __callback command without any pre-configured scope.", + "type": "string", + "const": "pilot:allow---callback", + "markdownDescription": "Enables the __callback command without any pre-configured scope." + }, + { + "description": "Allow the internal __callback IPC command used by the eval engine", + "type": "string", + "const": "pilot:allow-callback", + "markdownDescription": "Allow the internal __callback IPC command used by the eval engine" + }, + { + "description": "Denies the __callback command without any pre-configured scope.", + "type": "string", + "const": "pilot:deny---callback", + "markdownDescription": "Denies the __callback command without any pre-configured scope." } ] }, diff --git a/src-tauri/src/adapters/driven/config/mod.rs b/src-tauri/src/adapters/driven/config/mod.rs new file mode 100644 index 0000000..245ec17 --- /dev/null +++ b/src-tauri/src/adapters/driven/config/mod.rs @@ -0,0 +1,3 @@ +mod toml_config_store; + +pub use toml_config_store::TomlConfigStore; diff --git a/src-tauri/src/adapters/driven/config/toml_config_store.rs b/src-tauri/src/adapters/driven/config/toml_config_store.rs new file mode 100644 index 0000000..8110b9d --- /dev/null +++ b/src-tauri/src/adapters/driven/config/toml_config_store.rs @@ -0,0 +1,363 @@ +//! TOML-backed implementation of the `ConfigStore` port. +//! +//! Reads and writes `config.toml` with atomic file operations +//! (write to `.tmp` then rename) and automatic parent directory creation. + +use std::path::PathBuf; +use std::sync::Mutex; + +use crate::domain::error::DomainError; +use crate::domain::model::config::{AppConfig, ConfigPatch, apply_patch}; +use crate::domain::ports::driven::ConfigStore; + +/// Persists application configuration as a TOML file. +pub struct TomlConfigStore { + path: PathBuf, + lock: Mutex<()>, +} + +impl TomlConfigStore { + pub fn new(path: PathBuf) -> Self { + Self { + path, + lock: Mutex::new(()), + } + } + + fn read_or_default(&self) -> Result { + if !self.path.exists() { + return Ok(AppConfig::default()); + } + let content = std::fs::read_to_string(&self.path) + .map_err(|e| DomainError::StorageError(format!("failed to read config: {e}")))?; + let dto: ConfigDto = toml::from_str(&content) + .map_err(|e| DomainError::StorageError(format!("failed to parse config: {e}")))?; + Ok(AppConfig::from(dto)) + } + + fn write_config(&self, config: &AppConfig) -> Result<(), DomainError> { + if let Some(parent) = self.path.parent() { + std::fs::create_dir_all(parent).map_err(|e| { + DomainError::StorageError(format!("failed to create config directory: {e}")) + })?; + } + let dto = ConfigDto::from(config.clone()); + let content = toml::to_string_pretty(&dto) + .map_err(|e| DomainError::StorageError(format!("failed to serialize config: {e}")))?; + + let tmp_path = self.path.with_extension("tmp"); + { + use std::io::Write; + let mut file = std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&tmp_path) + .map_err(|e| { + DomainError::StorageError(format!("failed to create config tmp file: {e}")) + })?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + file.set_permissions(std::fs::Permissions::from_mode(0o600)) + .map_err(|e| { + DomainError::StorageError(format!( + "failed to set config file permissions: {e}" + )) + })?; + } + file.write_all(content.as_bytes()).map_err(|e| { + DomainError::StorageError(format!("failed to write config tmp file: {e}")) + })?; + file.sync_all().map_err(|e| { + DomainError::StorageError(format!("failed to sync config tmp file: {e}")) + })?; + } + std::fs::rename(&tmp_path, &self.path) + .map_err(|e| DomainError::StorageError(format!("failed to rename config file: {e}")))?; + Ok(()) + } +} + +impl ConfigStore for TomlConfigStore { + fn get_config(&self) -> Result { + let _guard = self + .lock + .lock() + .map_err(|e| DomainError::StorageError(format!("config lock poisoned: {e}")))?; + let config = self.read_or_default()?; + if !self.path.exists() { + self.write_config(&config)?; + } + Ok(config) + } + + fn update_config(&self, patch: ConfigPatch) -> Result { + let _guard = self + .lock + .lock() + .map_err(|e| DomainError::StorageError(format!("config lock poisoned: {e}")))?; + let mut config = self.read_or_default()?; + apply_patch(&mut config, &patch); + self.write_config(&config)?; + Ok(config) + } +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(default, rename_all = "snake_case")] +struct ConfigDto { + // General + download_dir: Option, + start_minimized: bool, + notifications_enabled: bool, + auto_extract: bool, + clipboard_monitoring: bool, + sound_enabled: bool, + confirm_delete: bool, + subfolder_per_package: bool, + + // Downloads + max_concurrent_downloads: u32, + max_segments_per_download: u32, + speed_limit_bytes_per_sec: Option, + max_retries: u32, + retry_delay_seconds: u32, + verify_checksums: bool, + pre_allocate_space: bool, + + // Network + proxy_type: String, + proxy_url: Option, + user_agent: String, + dns_over_https: bool, + connection_timeout_seconds: u32, + + // Remote Access + web_interface_enabled: bool, + web_interface_port: u16, + rest_api_enabled: bool, + api_key: String, + websocket_enabled: bool, + + // Browser Integration + min_file_size_mb: f64, + excluded_domains: Vec, + excluded_extensions: Vec, + + // Appearance + theme: String, + accent_color: String, + compact_mode: bool, + locale: String, +} + +impl Default for ConfigDto { + fn default() -> Self { + let defaults = AppConfig::default(); + Self::from(defaults) + } +} + +impl From for ConfigDto { + fn from(c: AppConfig) -> Self { + Self { + download_dir: c.download_dir, + start_minimized: c.start_minimized, + notifications_enabled: c.notifications_enabled, + auto_extract: c.auto_extract, + clipboard_monitoring: c.clipboard_monitoring, + sound_enabled: c.sound_enabled, + confirm_delete: c.confirm_delete, + subfolder_per_package: c.subfolder_per_package, + max_concurrent_downloads: c.max_concurrent_downloads, + max_segments_per_download: c.max_segments_per_download, + speed_limit_bytes_per_sec: c.speed_limit_bytes_per_sec, + max_retries: c.max_retries, + retry_delay_seconds: c.retry_delay_seconds, + verify_checksums: c.verify_checksums, + pre_allocate_space: c.pre_allocate_space, + proxy_type: c.proxy_type, + proxy_url: c.proxy_url, + user_agent: c.user_agent, + dns_over_https: c.dns_over_https, + connection_timeout_seconds: c.connection_timeout_seconds, + web_interface_enabled: c.web_interface_enabled, + web_interface_port: c.web_interface_port, + rest_api_enabled: c.rest_api_enabled, + api_key: c.api_key, + websocket_enabled: c.websocket_enabled, + min_file_size_mb: c.min_file_size_mb, + excluded_domains: c.excluded_domains, + excluded_extensions: c.excluded_extensions, + theme: c.theme, + accent_color: c.accent_color, + compact_mode: c.compact_mode, + locale: c.locale, + } + } +} + +impl From for AppConfig { + fn from(d: ConfigDto) -> Self { + Self { + download_dir: d.download_dir, + start_minimized: d.start_minimized, + notifications_enabled: d.notifications_enabled, + auto_extract: d.auto_extract, + clipboard_monitoring: d.clipboard_monitoring, + sound_enabled: d.sound_enabled, + confirm_delete: d.confirm_delete, + subfolder_per_package: d.subfolder_per_package, + max_concurrent_downloads: d.max_concurrent_downloads, + max_segments_per_download: d.max_segments_per_download, + speed_limit_bytes_per_sec: d.speed_limit_bytes_per_sec, + max_retries: d.max_retries, + retry_delay_seconds: d.retry_delay_seconds, + verify_checksums: d.verify_checksums, + pre_allocate_space: d.pre_allocate_space, + proxy_type: d.proxy_type, + proxy_url: d.proxy_url, + user_agent: d.user_agent, + dns_over_https: d.dns_over_https, + connection_timeout_seconds: d.connection_timeout_seconds, + web_interface_enabled: d.web_interface_enabled, + web_interface_port: d.web_interface_port, + rest_api_enabled: d.rest_api_enabled, + api_key: d.api_key, + websocket_enabled: d.websocket_enabled, + min_file_size_mb: d.min_file_size_mb, + excluded_domains: d.excluded_domains, + excluded_extensions: d.excluded_extensions, + theme: d.theme, + accent_color: d.accent_color, + compact_mode: d.compact_mode, + locale: d.locale, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_config_returns_defaults_when_file_missing() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("config.toml"); + let store = TomlConfigStore::new(path.clone()); + + let config = store.get_config().unwrap(); + assert_eq!(config, AppConfig::default()); + // File should be created with defaults + assert!(path.exists()); + } + + #[test] + fn test_save_load_roundtrip() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("config.toml"); + let store = TomlConfigStore::new(path); + + let patch = ConfigPatch { + max_concurrent_downloads: Some(10), + theme: Some("dark".to_string()), + locale: Some("fr".to_string()), + ..Default::default() + }; + + let updated = store.update_config(patch).unwrap(); + assert_eq!(updated.max_concurrent_downloads, 10); + assert_eq!(updated.theme, "dark"); + assert_eq!(updated.locale, "fr"); + + // Reload from file + let reloaded = store.get_config().unwrap(); + assert_eq!(reloaded, updated); + } + + #[test] + fn test_partial_patch_preserves_other_fields() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("config.toml"); + let store = TomlConfigStore::new(path); + + // Set some values + store + .update_config(ConfigPatch { + max_retries: Some(5), + sound_enabled: Some(true), + ..Default::default() + }) + .unwrap(); + + // Patch only one field + let updated = store + .update_config(ConfigPatch { + max_retries: Some(10), + ..Default::default() + }) + .unwrap(); + + assert_eq!(updated.max_retries, 10); + assert!(updated.sound_enabled); // preserved + } + + #[test] + fn test_creates_parent_directories() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("nested").join("deep").join("config.toml"); + let store = TomlConfigStore::new(path.clone()); + + let config = store.get_config().unwrap(); + assert_eq!(config, AppConfig::default()); + assert!(path.exists()); + } + + #[test] + fn test_missing_fields_in_toml_use_defaults() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("config.toml"); + // Write a partial TOML file + std::fs::write(&path, "theme = \"dark\"\n").unwrap(); + + let store = TomlConfigStore::new(path); + let config = store.get_config().unwrap(); + + assert_eq!(config.theme, "dark"); + // All other fields should be defaults + assert_eq!(config.max_concurrent_downloads, 3); + assert!(config.notifications_enabled); + } + + #[test] + fn test_nullable_fields_roundtrip() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("config.toml"); + let store = TomlConfigStore::new(path); + + // Set a nullable field + let updated = store + .update_config(ConfigPatch { + speed_limit_bytes_per_sec: Some(Some(1024)), + proxy_url: Some(Some("http://proxy:8080".to_string())), + ..Default::default() + }) + .unwrap(); + + assert_eq!(updated.speed_limit_bytes_per_sec, Some(1024)); + assert_eq!(updated.proxy_url, Some("http://proxy:8080".to_string())); + + // Clear the nullable field + let cleared = store + .update_config(ConfigPatch { + speed_limit_bytes_per_sec: Some(None), + ..Default::default() + }) + .unwrap(); + + assert_eq!(cleared.speed_limit_bytes_per_sec, None); + // proxy_url should still be set + assert_eq!(cleared.proxy_url, Some("http://proxy:8080".to_string())); + } +} diff --git a/src-tauri/src/adapters/driven/event/tauri_bridge.rs b/src-tauri/src/adapters/driven/event/tauri_bridge.rs index 14add8c..bd4d7f5 100644 --- a/src-tauri/src/adapters/driven/event/tauri_bridge.rs +++ b/src-tauri/src/adapters/driven/event/tauri_bridge.rs @@ -34,6 +34,7 @@ fn event_name(event: &DomainEvent) -> &'static str { DomainEvent::PluginUnloaded { .. } => "plugin-unloaded", DomainEvent::PackageCreated { .. } => "package-created", DomainEvent::ClipboardUrlDetected { .. } => "clipboard-url-detected", + DomainEvent::SettingsUpdated => "settings-updated", } } @@ -86,6 +87,7 @@ fn event_payload(event: &DomainEvent) -> serde_json::Value { DomainEvent::PluginUnloaded { name } => json!({ "name": name }), DomainEvent::PackageCreated { id, name } => json!({ "id": id.to_string(), "name": name }), DomainEvent::ClipboardUrlDetected { urls } => json!({ "urls": urls }), + DomainEvent::SettingsUpdated => json!({}), } } @@ -248,6 +250,14 @@ mod tests { ); } + #[test] + fn test_settings_updated_event_bridge_mapping() { + let event = DomainEvent::SettingsUpdated; + let (name, payload) = to_tauri_event(&event); + assert_eq!(name, "settings-updated"); + assert_eq!(payload, serde_json::json!({})); + } + #[test] fn test_event_payload_clipboard_url_detected() { let event = DomainEvent::ClipboardUrlDetected { diff --git a/src-tauri/src/adapters/driven/mod.rs b/src-tauri/src/adapters/driven/mod.rs index 38c611b..ef52fed 100644 --- a/src-tauri/src/adapters/driven/mod.rs +++ b/src-tauri/src/adapters/driven/mod.rs @@ -1,6 +1,7 @@ //! Driven adapters — implementations of domain port traits. pub mod clipboard; +pub mod config; pub mod event; pub mod filesystem; pub mod network; diff --git a/src-tauri/src/adapters/driving/tauri_ipc.rs b/src-tauri/src/adapters/driving/tauri_ipc.rs index 39032e8..79f30c4 100644 --- a/src-tauri/src/adapters/driving/tauri_ipc.rs +++ b/src-tauri/src/adapters/driving/tauri_ipc.rs @@ -14,7 +14,7 @@ use crate::application::commands::{ CancelDownloadCommand, DisablePluginCommand, EnablePluginCommand, InstallPluginCommand, PauseAllDownloadsCommand, PauseDownloadCommand, RemoveDownloadCommand, ResolveLinksCommand, ResolvedLinkDto, ResumeAllDownloadsCommand, ResumeDownloadCommand, RetryDownloadCommand, - SetPriorityCommand, StartDownloadCommand, UninstallPluginCommand, + SetPriorityCommand, StartDownloadCommand, UninstallPluginCommand, UpdateConfigCommand, }; use crate::application::error::AppError; use crate::application::queries::{ @@ -24,6 +24,7 @@ use crate::application::query_bus::QueryBus; use crate::application::read_models::download_detail_view::DownloadDetailViewDto; use crate::application::read_models::download_view::DownloadViewDto; use crate::application::read_models::plugin_view::PluginViewDto; +use crate::domain::model::config::{AppConfig, ConfigPatch}; use crate::domain::model::download::{DownloadId, DownloadState}; use crate::domain::model::views::{DownloadFilter, SortDirection, SortField, SortOrder}; @@ -342,6 +343,211 @@ pub async fn clipboard_state(state: State<'_, AppState>) -> Result Ok(config.clipboard_monitoring) } +// ── Settings DTOs ──────────────────────────────────────────────────── + +#[derive(Debug, Clone, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SettingsDto { + // General + pub download_dir: Option, + pub start_minimized: bool, + pub notifications_enabled: bool, + pub auto_extract: bool, + pub clipboard_monitoring: bool, + pub sound_enabled: bool, + pub confirm_delete: bool, + pub subfolder_per_package: bool, + + // Downloads + pub max_concurrent_downloads: u32, + pub max_segments_per_download: u32, + pub speed_limit_bytes_per_sec: Option, + pub max_retries: u32, + pub retry_delay_seconds: u32, + pub verify_checksums: bool, + pub pre_allocate_space: bool, + + // Network + pub proxy_type: String, + pub proxy_url: Option, + pub user_agent: String, + pub dns_over_https: bool, + pub connection_timeout_seconds: u32, + + // Remote Access + pub web_interface_enabled: bool, + pub web_interface_port: u16, + pub rest_api_enabled: bool, + pub api_key: String, + pub websocket_enabled: bool, + + // Browser Integration + pub min_file_size_mb: f64, + pub excluded_domains: Vec, + pub excluded_extensions: Vec, + + // Appearance + pub theme: String, + pub accent_color: String, + pub compact_mode: bool, + pub locale: String, +} + +impl From for SettingsDto { + fn from(c: AppConfig) -> Self { + Self { + download_dir: c.download_dir, + start_minimized: c.start_minimized, + notifications_enabled: c.notifications_enabled, + auto_extract: c.auto_extract, + clipboard_monitoring: c.clipboard_monitoring, + sound_enabled: c.sound_enabled, + confirm_delete: c.confirm_delete, + subfolder_per_package: c.subfolder_per_package, + max_concurrent_downloads: c.max_concurrent_downloads, + max_segments_per_download: c.max_segments_per_download, + speed_limit_bytes_per_sec: c.speed_limit_bytes_per_sec, + max_retries: c.max_retries, + retry_delay_seconds: c.retry_delay_seconds, + verify_checksums: c.verify_checksums, + pre_allocate_space: c.pre_allocate_space, + proxy_type: c.proxy_type, + proxy_url: c.proxy_url, + user_agent: c.user_agent, + dns_over_https: c.dns_over_https, + connection_timeout_seconds: c.connection_timeout_seconds, + web_interface_enabled: c.web_interface_enabled, + web_interface_port: c.web_interface_port, + rest_api_enabled: c.rest_api_enabled, + api_key: c.api_key, + websocket_enabled: c.websocket_enabled, + min_file_size_mb: c.min_file_size_mb, + excluded_domains: c.excluded_domains, + excluded_extensions: c.excluded_extensions, + theme: c.theme, + accent_color: c.accent_color, + compact_mode: c.compact_mode, + locale: c.locale, + } + } +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ConfigPatchDto { + // General + pub download_dir: Option>, + pub start_minimized: Option, + pub notifications_enabled: Option, + pub auto_extract: Option, + pub clipboard_monitoring: Option, + pub sound_enabled: Option, + pub confirm_delete: Option, + pub subfolder_per_package: Option, + + // Downloads + pub max_concurrent_downloads: Option, + pub max_segments_per_download: Option, + pub speed_limit_bytes_per_sec: Option>, + pub max_retries: Option, + pub retry_delay_seconds: Option, + pub verify_checksums: Option, + pub pre_allocate_space: Option, + + // Network + pub proxy_type: Option, + pub proxy_url: Option>, + pub user_agent: Option, + pub dns_over_https: Option, + pub connection_timeout_seconds: Option, + + // Remote Access + pub web_interface_enabled: Option, + pub web_interface_port: Option, + pub rest_api_enabled: Option, + pub api_key: Option, + pub websocket_enabled: Option, + + // Browser Integration + pub min_file_size_mb: Option, + pub excluded_domains: Option>, + pub excluded_extensions: Option>, + + // Appearance + pub theme: Option, + pub accent_color: Option, + pub compact_mode: Option, + pub locale: Option, +} + +impl From for ConfigPatch { + fn from(d: ConfigPatchDto) -> Self { + Self { + download_dir: d.download_dir, + start_minimized: d.start_minimized, + notifications_enabled: d.notifications_enabled, + auto_extract: d.auto_extract, + clipboard_monitoring: d.clipboard_monitoring, + sound_enabled: d.sound_enabled, + confirm_delete: d.confirm_delete, + subfolder_per_package: d.subfolder_per_package, + max_concurrent_downloads: d.max_concurrent_downloads, + max_segments_per_download: d.max_segments_per_download, + speed_limit_bytes_per_sec: d.speed_limit_bytes_per_sec, + max_retries: d.max_retries, + retry_delay_seconds: d.retry_delay_seconds, + verify_checksums: d.verify_checksums, + pre_allocate_space: d.pre_allocate_space, + proxy_type: d.proxy_type, + proxy_url: d.proxy_url, + user_agent: d.user_agent, + dns_over_https: d.dns_over_https, + connection_timeout_seconds: d.connection_timeout_seconds, + web_interface_enabled: d.web_interface_enabled, + web_interface_port: d.web_interface_port, + rest_api_enabled: d.rest_api_enabled, + api_key: d.api_key, + websocket_enabled: d.websocket_enabled, + min_file_size_mb: d.min_file_size_mb, + excluded_domains: d.excluded_domains, + excluded_extensions: d.excluded_extensions, + theme: d.theme, + accent_color: d.accent_color, + compact_mode: d.compact_mode, + locale: d.locale, + } + } +} + +// ── Settings IPC Commands ──────────────────────────────────────────── + +#[tauri::command] +pub async fn settings_get(state: State<'_, AppState>) -> Result { + state + .command_bus + .config_store() + .get_config() + .map(SettingsDto::from) + .map_err(|e| e.to_string()) +} + +#[tauri::command] +pub async fn settings_update( + state: State<'_, AppState>, + patch: ConfigPatchDto, +) -> Result { + let cmd = UpdateConfigCommand { + patch: patch.into(), + }; + state + .command_bus + .handle_update_config(cmd) + .map(SettingsDto::from) + .map_err(|e| e.to_string()) +} + +// ── Helpers ────────────────────────────────────────────────────────── + fn parse_download_state(s: &str) -> Option { match s.to_lowercase().as_str() { "queued" => Some(DownloadState::Queued), diff --git a/src-tauri/src/application/command_bus.rs b/src-tauri/src/application/command_bus.rs index 3f3a398..36d1c68 100644 --- a/src-tauri/src/application/command_bus.rs +++ b/src-tauri/src/application/command_bus.rs @@ -347,33 +347,7 @@ mod tests { fn update_config(&self, patch: ConfigPatch) -> Result { let mut config = self.config.lock().unwrap(); - if let Some(dir) = patch.download_dir { - config.download_dir = dir; - } - if let Some(max) = patch.max_concurrent_downloads { - config.max_concurrent_downloads = max; - } - if let Some(max) = patch.max_segments_per_download { - config.max_segments_per_download = max; - } - if let Some(limit) = patch.speed_limit_bytes_per_sec { - config.speed_limit_bytes_per_sec = limit; - } - if let Some(auto) = patch.auto_extract { - config.auto_extract = auto; - } - if let Some(theme) = patch.theme { - config.theme = theme; - } - if let Some(locale) = patch.locale { - config.locale = locale; - } - if let Some(monitoring) = patch.clipboard_monitoring { - config.clipboard_monitoring = monitoring; - } - if let Some(minimize) = patch.minimize_to_tray { - config.minimize_to_tray = minimize; - } + crate::domain::model::config::apply_patch(&mut config, &patch); Ok(config.clone()) } } diff --git a/src-tauri/src/application/commands/mod.rs b/src-tauri/src/application/commands/mod.rs index 0049327..beee49d 100644 --- a/src-tauri/src/application/commands/mod.rs +++ b/src-tauri/src/application/commands/mod.rs @@ -17,6 +17,7 @@ mod start_download; mod toggle_clipboard; mod toggle_plugin; mod uninstall_plugin; +mod update_config; use std::path::PathBuf; @@ -115,7 +116,6 @@ pub use resolve_links::ResolvedLinkDto; // Handler: task 23 (settings) #[derive(Debug)] -#[expect(dead_code)] pub struct UpdateConfigCommand { pub patch: ConfigPatch, } diff --git a/src-tauri/src/application/commands/toggle_clipboard.rs b/src-tauri/src/application/commands/toggle_clipboard.rs index a217772..678cc23 100644 --- a/src-tauri/src/application/commands/toggle_clipboard.rs +++ b/src-tauri/src/application/commands/toggle_clipboard.rs @@ -285,33 +285,7 @@ mod tests { fn update_config(&self, patch: ConfigPatch) -> Result { let mut config = self.config.lock().unwrap(); - if let Some(dir) = patch.download_dir { - config.download_dir = dir; - } - if let Some(max) = patch.max_concurrent_downloads { - config.max_concurrent_downloads = max; - } - if let Some(max) = patch.max_segments_per_download { - config.max_segments_per_download = max; - } - if let Some(limit) = patch.speed_limit_bytes_per_sec { - config.speed_limit_bytes_per_sec = limit; - } - if let Some(auto) = patch.auto_extract { - config.auto_extract = auto; - } - if let Some(theme) = patch.theme { - config.theme = theme; - } - if let Some(locale) = patch.locale { - config.locale = locale; - } - if let Some(monitoring) = patch.clipboard_monitoring { - config.clipboard_monitoring = monitoring; - } - if let Some(minimize) = patch.minimize_to_tray { - config.minimize_to_tray = minimize; - } + crate::domain::model::config::apply_patch(&mut config, &patch); Ok(config.clone()) } } diff --git a/src-tauri/src/application/commands/update_config.rs b/src-tauri/src/application/commands/update_config.rs new file mode 100644 index 0000000..b029abb --- /dev/null +++ b/src-tauri/src/application/commands/update_config.rs @@ -0,0 +1,379 @@ +//! Handler for `UpdateConfigCommand`. + +use crate::application::command_bus::CommandBus; +use crate::application::error::AppError; +use crate::domain::event::DomainEvent; +use crate::domain::model::config::AppConfig; + +/// Validate the merged configuration after applying a patch. +/// Returns `Err(AppError::Validation)` on invalid combined state. +fn validate_config(config: &AppConfig) -> Result<(), AppError> { + if !matches!(config.proxy_type.as_str(), "none" | "http" | "socks5") { + return Err(AppError::Validation(format!( + "proxy_type must be none, http, or socks5, got '{}'", + config.proxy_type + ))); + } + if !matches!(config.theme.as_str(), "light" | "dark" | "auto") { + return Err(AppError::Validation(format!( + "theme must be light, dark, or auto, got '{}'", + config.theme + ))); + } + if config.web_interface_port < 1024 { + return Err(AppError::Validation(format!( + "web_interface_port must be >= 1024, got {}", + config.web_interface_port + ))); + } + if !(1..=100).contains(&config.max_concurrent_downloads) { + return Err(AppError::Validation(format!( + "max_concurrent_downloads must be 1-100, got {}", + config.max_concurrent_downloads + ))); + } + if !(1..=32).contains(&config.max_segments_per_download) { + return Err(AppError::Validation(format!( + "max_segments_per_download must be 1-32, got {}", + config.max_segments_per_download + ))); + } + if !(5..=300).contains(&config.connection_timeout_seconds) { + return Err(AppError::Validation(format!( + "connection_timeout_seconds must be 5-300, got {}", + config.connection_timeout_seconds + ))); + } + // Cross-field: proxy URL required when proxy is enabled + if config.proxy_type != "none" && config.proxy_url.as_ref().is_none_or(|u| u.is_empty()) { + return Err(AppError::Validation( + "proxy_url is required when proxy_type is not 'none'".to_string(), + )); + } + Ok(()) +} + +impl CommandBus { + pub fn handle_update_config( + &self, + cmd: super::UpdateConfigCommand, + ) -> Result { + // Merge patch with current config and validate the result + let current = self.config_store().get_config()?; + let mut merged = current; + crate::domain::model::config::apply_patch(&mut merged, &cmd.patch); + validate_config(&merged)?; + + let updated = self.config_store().update_config(cmd.patch)?; + self.event_bus().publish(DomainEvent::SettingsUpdated); + Ok(updated) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::application::commands::UpdateConfigCommand; + use crate::domain::model::config::ConfigPatch; + + // Re-use the test helper from command_bus tests + fn make_command_bus() -> CommandBus { + use std::collections::HashMap; + use std::sync::{Arc, Mutex}; + + use crate::domain::error::DomainError; + use crate::domain::model::config::apply_patch; + use crate::domain::model::credential::Credential; + use crate::domain::model::download::{Download, DownloadId, DownloadState}; + use crate::domain::model::http::HttpResponse; + use crate::domain::model::meta::DownloadMeta; + use crate::domain::model::plugin::{PluginInfo, PluginManifest}; + use crate::domain::ports::driven::{ + ClipboardObserver, ConfigStore, CredentialStore, DownloadEngine, DownloadRepository, + EventBus, FileStorage, HttpClient, PluginLoader, + }; + + struct StubRepo; + impl DownloadRepository for StubRepo { + fn find_by_id(&self, _: DownloadId) -> Result, DomainError> { + Ok(None) + } + fn save(&self, _: &Download) -> Result<(), DomainError> { + Ok(()) + } + fn delete(&self, _: DownloadId) -> Result<(), DomainError> { + Ok(()) + } + fn find_by_state(&self, _: DownloadState) -> Result, DomainError> { + Ok(vec![]) + } + } + + struct StubEngine; + impl DownloadEngine for StubEngine { + fn start(&self, _: &Download) -> Result<(), DomainError> { + Ok(()) + } + fn pause(&self, _: DownloadId) -> Result<(), DomainError> { + Ok(()) + } + fn resume(&self, _: DownloadId) -> Result<(), DomainError> { + Ok(()) + } + fn cancel(&self, _: DownloadId) -> Result<(), DomainError> { + Ok(()) + } + } + + struct CollectingEventBus { + events: Mutex>, + } + impl EventBus for CollectingEventBus { + fn publish(&self, event: DomainEvent) { + self.events.lock().unwrap().push(event); + } + fn subscribe(&self, _: Box) {} + } + + struct StubFs; + impl FileStorage for StubFs { + fn create_file(&self, _: &std::path::Path, _: u64) -> Result<(), DomainError> { + Ok(()) + } + fn write_segment( + &self, + _: &std::path::Path, + _: u64, + _: &[u8], + ) -> Result<(), DomainError> { + Ok(()) + } + fn read_meta(&self, _: &std::path::Path) -> Result, DomainError> { + Ok(None) + } + fn write_meta(&self, _: &std::path::Path, _: &DownloadMeta) -> Result<(), DomainError> { + Ok(()) + } + fn delete_meta(&self, _: &std::path::Path) -> Result<(), DomainError> { + Ok(()) + } + } + + struct StubHttp; + impl HttpClient for StubHttp { + fn head(&self, _: &str) -> Result { + Ok(HttpResponse { + status_code: 200, + headers: HashMap::new(), + body: vec![], + }) + } + fn get_range(&self, _: &str, _: u64, _: u64) -> Result, DomainError> { + Ok(vec![]) + } + fn supports_range(&self, _: &str) -> Result { + Ok(true) + } + } + + struct StubPluginLoader; + impl PluginLoader for StubPluginLoader { + fn load(&self, _: &PluginManifest) -> Result<(), DomainError> { + Ok(()) + } + fn unload(&self, _: &str) -> Result<(), DomainError> { + Ok(()) + } + fn resolve_url(&self, _: &str) -> Result, DomainError> { + Ok(None) + } + fn list_loaded(&self) -> Result, DomainError> { + Ok(vec![]) + } + fn set_enabled(&self, _: &str, _: bool) -> Result<(), DomainError> { + Ok(()) + } + } + + struct InMemoryConfigStore { + config: Mutex, + } + impl ConfigStore for InMemoryConfigStore { + fn get_config(&self) -> Result { + Ok(self.config.lock().unwrap().clone()) + } + fn update_config(&self, patch: ConfigPatch) -> Result { + let mut config = self.config.lock().unwrap(); + apply_patch(&mut config, &patch); + Ok(config.clone()) + } + } + + struct StubCredStore; + impl CredentialStore for StubCredStore { + fn get(&self, _: &str) -> Result, DomainError> { + Ok(None) + } + fn store(&self, _: &str, _: &Credential) -> Result<(), DomainError> { + Ok(()) + } + fn delete(&self, _: &str) -> Result<(), DomainError> { + Ok(()) + } + } + + struct StubClipboard; + impl ClipboardObserver for StubClipboard { + fn start(&self) -> Result<(), DomainError> { + Ok(()) + } + fn stop(&self) -> Result<(), DomainError> { + Ok(()) + } + fn get_urls(&self) -> Result, DomainError> { + Ok(vec![]) + } + } + + CommandBus::new( + Arc::new(StubRepo), + Arc::new(StubEngine), + Arc::new(CollectingEventBus { + events: Mutex::new(Vec::new()), + }), + Arc::new(StubFs), + Arc::new(StubHttp), + Arc::new(StubPluginLoader), + Arc::new(InMemoryConfigStore { + config: Mutex::new(AppConfig::default()), + }), + Arc::new(StubCredStore), + Arc::new(StubClipboard), + ) + } + + #[test] + fn test_handle_update_config_returns_updated_config() { + let bus = make_command_bus(); + let cmd = UpdateConfigCommand { + patch: ConfigPatch { + theme: Some("dark".to_string()), + max_concurrent_downloads: Some(5), + ..Default::default() + }, + }; + + let result = bus.handle_update_config(cmd).unwrap(); + assert_eq!(result.theme, "dark"); + assert_eq!(result.max_concurrent_downloads, 5); + // Other fields should remain at defaults + assert_eq!(result.locale, "en"); + } + + #[test] + fn test_handle_update_config_empty_patch_returns_defaults() { + let bus = make_command_bus(); + let cmd = UpdateConfigCommand { + patch: ConfigPatch::default(), + }; + + let result = bus.handle_update_config(cmd).unwrap(); + assert_eq!(result, AppConfig::default()); + } + + #[test] + fn test_handle_update_config_rejects_invalid_proxy_type() { + let bus = make_command_bus(); + let cmd = UpdateConfigCommand { + patch: ConfigPatch { + proxy_type: Some("invalid".to_string()), + ..Default::default() + }, + }; + + let result = bus.handle_update_config(cmd); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("proxy_type")); + } + + #[test] + fn test_handle_update_config_rejects_invalid_theme() { + let bus = make_command_bus(); + let cmd = UpdateConfigCommand { + patch: ConfigPatch { + theme: Some("neon".to_string()), + ..Default::default() + }, + }; + + let result = bus.handle_update_config(cmd); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("theme")); + } + + #[test] + fn test_handle_update_config_rejects_low_port() { + let bus = make_command_bus(); + let cmd = UpdateConfigCommand { + patch: ConfigPatch { + web_interface_port: Some(80), + ..Default::default() + }, + }; + + let result = bus.handle_update_config(cmd); + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .to_string() + .contains("web_interface_port") + ); + } + + #[test] + fn test_handle_update_config_rejects_zero_concurrent() { + let bus = make_command_bus(); + let cmd = UpdateConfigCommand { + patch: ConfigPatch { + max_concurrent_downloads: Some(0), + ..Default::default() + }, + }; + + let result = bus.handle_update_config(cmd); + assert!(result.is_err()); + } + + #[test] + fn test_handle_update_config_rejects_proxy_without_url() { + let bus = make_command_bus(); + let cmd = UpdateConfigCommand { + patch: ConfigPatch { + proxy_type: Some("http".to_string()), + // proxy_url not set → merged config has None + ..Default::default() + }, + }; + + let result = bus.handle_update_config(cmd); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("proxy_url")); + } + + #[test] + fn test_handle_update_config_accepts_proxy_with_url() { + let bus = make_command_bus(); + let cmd = UpdateConfigCommand { + patch: ConfigPatch { + proxy_type: Some("http".to_string()), + proxy_url: Some(Some("http://proxy:8080".to_string())), + ..Default::default() + }, + }; + + let result = bus.handle_update_config(cmd).unwrap(); + assert_eq!(result.proxy_type, "http"); + assert_eq!(result.proxy_url, Some("http://proxy:8080".to_string())); + } +} diff --git a/src-tauri/src/domain/event.rs b/src-tauri/src/domain/event.rs index 3ebb81f..644de42 100644 --- a/src-tauri/src/domain/event.rs +++ b/src-tauri/src/domain/event.rs @@ -81,6 +81,9 @@ pub enum DomainEvent { ClipboardUrlDetected { urls: Vec, }, + + // Settings + SettingsUpdated, } #[cfg(test)] diff --git a/src-tauri/src/domain/model/config.rs b/src-tauri/src/domain/model/config.rs index c5d757a..309869e 100644 --- a/src-tauri/src/domain/model/config.rs +++ b/src-tauri/src/domain/model/config.rs @@ -9,31 +9,104 @@ /// Adapters serialize/deserialize this to the actual file format. #[derive(Debug, Clone, PartialEq)] pub struct AppConfig { + // ── General ────────────────────────────────────────────────────── /// Download directory path. `None` means the adapter must resolve /// a platform-specific default (e.g., `~/Downloads`) before use. pub download_dir: Option, + pub start_minimized: bool, + pub notifications_enabled: bool, + pub auto_extract: bool, + pub clipboard_monitoring: bool, + pub sound_enabled: bool, + pub confirm_delete: bool, + pub subfolder_per_package: bool, + + // ── Downloads ──────────────────────────────────────────────────── pub max_concurrent_downloads: u32, pub max_segments_per_download: u32, + /// `None` means unlimited. pub speed_limit_bytes_per_sec: Option, - pub auto_extract: bool, + pub max_retries: u32, + pub retry_delay_seconds: u32, + pub verify_checksums: bool, + pub pre_allocate_space: bool, + + // ── Network ────────────────────────────────────────────────────── + /// `"none"`, `"http"`, or `"socks5"`. + pub proxy_type: String, + pub proxy_url: Option, + pub user_agent: String, + pub dns_over_https: bool, + pub connection_timeout_seconds: u32, + + // ── Remote Access ──────────────────────────────────────────────── + pub web_interface_enabled: bool, + pub web_interface_port: u16, + pub rest_api_enabled: bool, + pub api_key: String, + pub websocket_enabled: bool, + + // ── Browser Integration ────────────────────────────────────────── + /// Minimum file size in megabytes. `0.0` means capture all. + pub min_file_size_mb: f64, + pub excluded_domains: Vec, + pub excluded_extensions: Vec, + + // ── Appearance ─────────────────────────────────────────────────── + /// `"light"`, `"dark"`, or `"auto"`. pub theme: String, + /// Hex color string (e.g. `"#4F46E5"`). + pub accent_color: String, + pub compact_mode: bool, pub locale: String, - pub clipboard_monitoring: bool, - pub minimize_to_tray: bool, } impl Default for AppConfig { fn default() -> Self { Self { + // General download_dir: None, + start_minimized: false, + notifications_enabled: true, + auto_extract: false, + clipboard_monitoring: true, + sound_enabled: false, + confirm_delete: true, + subfolder_per_package: false, + + // Downloads max_concurrent_downloads: 3, max_segments_per_download: 8, speed_limit_bytes_per_sec: None, - auto_extract: false, - theme: "system".to_string(), + max_retries: 3, + retry_delay_seconds: 5, + verify_checksums: false, + pre_allocate_space: true, + + // Network + proxy_type: "none".to_string(), + proxy_url: None, + user_agent: "Vortex/1.0".to_string(), + dns_over_https: false, + connection_timeout_seconds: 30, + + // Remote Access + web_interface_enabled: false, + web_interface_port: 9666, + rest_api_enabled: false, + api_key: String::new(), + websocket_enabled: false, + + // Browser Integration + min_file_size_mb: 0.0, + excluded_domains: Vec::new(), + excluded_extensions: Vec::new(), + + // Appearance + theme: "auto".to_string(), + accent_color: "#4F46E5".to_string(), + compact_mode: false, locale: "en".to_string(), - clipboard_monitoring: true, - minimize_to_tray: true, } } } @@ -43,17 +116,164 @@ impl Default for AppConfig { /// Only the fields set to `Some(...)` will be applied. /// This avoids overwriting unchanged settings. /// -/// For `download_dir`: `None` = no change, `Some(None)` = reset to -/// platform default, `Some(Some(path))` = set to custom path. +/// For nullable fields (`download_dir`, `speed_limit_bytes_per_sec`, +/// `proxy_url`): `None` = no change, `Some(None)` = reset to default, +/// `Some(Some(value))` = set. #[derive(Debug, Clone, PartialEq, Default)] pub struct ConfigPatch { + // General pub download_dir: Option>, + pub start_minimized: Option, + pub notifications_enabled: Option, + pub auto_extract: Option, + pub clipboard_monitoring: Option, + pub sound_enabled: Option, + pub confirm_delete: Option, + pub subfolder_per_package: Option, + + // Downloads pub max_concurrent_downloads: Option, pub max_segments_per_download: Option, pub speed_limit_bytes_per_sec: Option>, - pub auto_extract: Option, + pub max_retries: Option, + pub retry_delay_seconds: Option, + pub verify_checksums: Option, + pub pre_allocate_space: Option, + + // Network + pub proxy_type: Option, + pub proxy_url: Option>, + pub user_agent: Option, + pub dns_over_https: Option, + pub connection_timeout_seconds: Option, + + // Remote Access + pub web_interface_enabled: Option, + pub web_interface_port: Option, + pub rest_api_enabled: Option, + pub api_key: Option, + pub websocket_enabled: Option, + + // Browser Integration + pub min_file_size_mb: Option, + pub excluded_domains: Option>, + pub excluded_extensions: Option>, + + // Appearance pub theme: Option, + pub accent_color: Option, + pub compact_mode: Option, pub locale: Option, - pub clipboard_monitoring: Option, - pub minimize_to_tray: Option, +} + +/// Apply a `ConfigPatch` to an `AppConfig`, updating only fields +/// that are `Some(...)`. Pure function — no I/O. +pub fn apply_patch(config: &mut AppConfig, patch: &ConfigPatch) { + // General + if let Some(ref dir) = patch.download_dir { + config.download_dir = dir.clone(); + } + if let Some(v) = patch.start_minimized { + config.start_minimized = v; + } + if let Some(v) = patch.notifications_enabled { + config.notifications_enabled = v; + } + if let Some(v) = patch.auto_extract { + config.auto_extract = v; + } + if let Some(v) = patch.clipboard_monitoring { + config.clipboard_monitoring = v; + } + if let Some(v) = patch.sound_enabled { + config.sound_enabled = v; + } + if let Some(v) = patch.confirm_delete { + config.confirm_delete = v; + } + if let Some(v) = patch.subfolder_per_package { + config.subfolder_per_package = v; + } + + // Downloads + if let Some(v) = patch.max_concurrent_downloads { + config.max_concurrent_downloads = v; + } + if let Some(v) = patch.max_segments_per_download { + config.max_segments_per_download = v; + } + if let Some(ref limit) = patch.speed_limit_bytes_per_sec { + config.speed_limit_bytes_per_sec = *limit; + } + if let Some(v) = patch.max_retries { + config.max_retries = v; + } + if let Some(v) = patch.retry_delay_seconds { + config.retry_delay_seconds = v; + } + if let Some(v) = patch.verify_checksums { + config.verify_checksums = v; + } + if let Some(v) = patch.pre_allocate_space { + config.pre_allocate_space = v; + } + + // Network + if let Some(ref v) = patch.proxy_type { + config.proxy_type = v.clone(); + } + if let Some(ref v) = patch.proxy_url { + config.proxy_url = v.clone(); + } + if let Some(ref v) = patch.user_agent { + config.user_agent = v.clone(); + } + if let Some(v) = patch.dns_over_https { + config.dns_over_https = v; + } + if let Some(v) = patch.connection_timeout_seconds { + config.connection_timeout_seconds = v; + } + + // Remote Access + if let Some(v) = patch.web_interface_enabled { + config.web_interface_enabled = v; + } + if let Some(v) = patch.web_interface_port { + config.web_interface_port = v; + } + if let Some(v) = patch.rest_api_enabled { + config.rest_api_enabled = v; + } + if let Some(ref v) = patch.api_key { + config.api_key = v.clone(); + } + if let Some(v) = patch.websocket_enabled { + config.websocket_enabled = v; + } + + // Browser Integration + if let Some(v) = patch.min_file_size_mb { + config.min_file_size_mb = v; + } + if let Some(ref v) = patch.excluded_domains { + config.excluded_domains = v.clone(); + } + if let Some(ref v) = patch.excluded_extensions { + config.excluded_extensions = v.clone(); + } + + // Appearance + if let Some(ref v) = patch.theme { + config.theme = v.clone(); + } + if let Some(ref v) = patch.accent_color { + config.accent_color = v.clone(); + } + if let Some(v) = patch.compact_mode { + config.compact_mode = v; + } + if let Some(ref v) = patch.locale { + config.locale = v.clone(); + } } diff --git a/src-tauri/src/domain/ports/driven/tests.rs b/src-tauri/src/domain/ports/driven/tests.rs index c166ede..fa7162e 100644 --- a/src-tauri/src/domain/ports/driven/tests.rs +++ b/src-tauri/src/domain/ports/driven/tests.rs @@ -260,33 +260,7 @@ impl ConfigStore for InMemoryConfigStore { fn update_config(&self, patch: ConfigPatch) -> Result { let mut config = self.config.lock().unwrap(); - if let Some(dir) = patch.download_dir { - config.download_dir = dir; - } - if let Some(max) = patch.max_concurrent_downloads { - config.max_concurrent_downloads = max; - } - if let Some(max) = patch.max_segments_per_download { - config.max_segments_per_download = max; - } - if let Some(limit) = patch.speed_limit_bytes_per_sec { - config.speed_limit_bytes_per_sec = limit; - } - if let Some(auto) = patch.auto_extract { - config.auto_extract = auto; - } - if let Some(theme) = patch.theme { - config.theme = theme; - } - if let Some(locale) = patch.locale { - config.locale = locale; - } - if let Some(monitoring) = patch.clipboard_monitoring { - config.clipboard_monitoring = monitoring; - } - if let Some(minimize) = patch.minimize_to_tray { - config.minimize_to_tray = minimize; - } + crate::domain::model::config::apply_patch(&mut config, &patch); Ok(config.clone()) } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 12603b3..c86e474 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -27,6 +27,7 @@ pub use application::read_models::{ pub use application::services::QueueManager; pub use adapters::driven::clipboard::TauriClipboardObserver; +pub use adapters::driven::config::TomlConfigStore; pub use adapters::driven::plugin::builtin::HttpModule; pub use adapters::driven::plugin::capabilities::SharedHostResources; pub use adapters::driven::plugin::{ExtismPluginLoader, PluginRegistry, PluginWatcher}; @@ -35,13 +36,19 @@ pub use adapters::driving::tauri_ipc::{ download_detail, download_list, download_pause, download_pause_all, download_remove, download_resume, download_resume_all, download_retry, download_set_priority, download_start, link_resolve, plugin_disable, plugin_enable, plugin_install, plugin_list, plugin_uninstall, + settings_get, settings_update, }; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - tauri::Builder::default() + let mut builder = tauri::Builder::default() .plugin(tauri_plugin_clipboard_manager::init()) - .plugin(tauri_plugin_notification::init()) + .plugin(tauri_plugin_notification::init()); + #[cfg(all(debug_assertions, unix))] + { + builder = builder.plugin(tauri_plugin_pilot::init()); + } + builder .setup(|app| { // System tray — default to clipboard_enabled=false since the // observer starts disabled and AppState isn't wired yet. @@ -77,6 +84,8 @@ pub fn run() { link_resolve, clipboard_toggle, clipboard_state, + settings_get, + settings_update, ]) .run(tauri::generate_context!()) // Tauri's run() has no meaningful recovery path — panic is intentional here diff --git a/src/components/__tests__/ClipboardIndicator.test.tsx b/src/components/__tests__/ClipboardIndicator.test.tsx index d11dd18..558c86f 100644 --- a/src/components/__tests__/ClipboardIndicator.test.tsx +++ b/src/components/__tests__/ClipboardIndicator.test.tsx @@ -10,6 +10,42 @@ vi.mock("@tauri-apps/api/event", () => ({ listen })); import { ClipboardIndicator } from "../ClipboardIndicator"; import { useSettingsStore } from "@/stores/settingsStore"; +import type { AppConfig } from "@/types/settings"; + +const baseConfig: AppConfig = { + downloadDir: null, + maxConcurrentDownloads: 3, + maxSegmentsPerDownload: 8, + speedLimitBytesPerSec: null, + autoExtract: false, + theme: "dark", + locale: "en", + clipboardMonitoring: false, + startMinimized: false, + notificationsEnabled: true, + soundEnabled: false, + confirmDelete: true, + subfolderPerPackage: false, + maxRetries: 3, + retryDelaySeconds: 5, + verifyChecksums: true, + preAllocateSpace: false, + proxyType: "none", + proxyUrl: null, + userAgent: "Vortex/1.0", + dnsOverHttps: false, + connectionTimeoutSeconds: 30, + webInterfaceEnabled: false, + webInterfacePort: 9666, + restApiEnabled: false, + apiKey: "", + websocketEnabled: false, + minFileSizeMb: 1, + excludedDomains: [], + excludedExtensions: [], + accentColor: "#4F46E5", + compactMode: false, +}; function wrapper({ children }: { children: React.ReactNode }) { const client = new QueryClient({ @@ -32,17 +68,7 @@ describe("ClipboardIndicator", () => { it("should show grey dot when disabled", () => { useSettingsStore.setState({ - config: { - downloadDir: null, - maxConcurrentDownloads: 3, - maxSegmentsPerDownload: 8, - speedLimitBytesPerSec: null, - autoExtract: false, - theme: "system", - locale: "en", - clipboardMonitoring: false, - minimizeToTray: true, - }, + config: { ...baseConfig, clipboardMonitoring: false }, }); render(, { wrapper }); @@ -55,17 +81,7 @@ describe("ClipboardIndicator", () => { it("should show green dot when enabled", () => { useSettingsStore.setState({ - config: { - downloadDir: null, - maxConcurrentDownloads: 3, - maxSegmentsPerDownload: 8, - speedLimitBytesPerSec: null, - autoExtract: false, - theme: "system", - locale: "en", - clipboardMonitoring: true, - minimizeToTray: true, - }, + config: { ...baseConfig, clipboardMonitoring: true }, }); render(, { wrapper }); diff --git a/src/stores/__tests__/settingsStore.test.ts b/src/stores/__tests__/settingsStore.test.ts index c9eb650..3d06f8c 100644 --- a/src/stores/__tests__/settingsStore.test.ts +++ b/src/stores/__tests__/settingsStore.test.ts @@ -8,7 +8,9 @@ vi.mock('@/api/client', () => ({ import { tauriInvoke } from '@/api/client'; -const baseConfig = { +import type { AppConfig } from '@/types/settings'; + +const baseConfig: AppConfig = { downloadDir: '/tmp', maxConcurrentDownloads: 3, maxSegmentsPerDownload: 8, @@ -17,7 +19,30 @@ const baseConfig = { theme: 'light', locale: 'en', clipboardMonitoring: true, - minimizeToTray: false, + startMinimized: false, + notificationsEnabled: true, + soundEnabled: false, + confirmDelete: true, + subfolderPerPackage: false, + maxRetries: 3, + retryDelaySeconds: 5, + verifyChecksums: true, + preAllocateSpace: false, + proxyType: 'none', + proxyUrl: null, + userAgent: 'Vortex/1.0', + dnsOverHttps: false, + connectionTimeoutSeconds: 30, + webInterfaceEnabled: false, + webInterfacePort: 9666, + restApiEnabled: false, + apiKey: '', + websocketEnabled: false, + minFileSizeMb: 1, + excludedDomains: [], + excludedExtensions: [], + accentColor: '#4F46E5', + compactMode: false, }; beforeEach(() => { @@ -37,7 +62,7 @@ describe('useSettingsStore — updateConfig', () => { vi.mocked(tauriInvoke).mockResolvedValueOnce(null); useSettingsStore.setState({ config: baseConfig, isLoading: false }); await useSettingsStore.getState().updateConfig({ theme: 'dark' }); - expect(tauriInvoke).toHaveBeenCalledWith('settings_update', { theme: 'dark' }); + expect(tauriInvoke).toHaveBeenCalledWith('settings_update', { patch: { theme: 'dark' } }); }); it('should merge partial config on success', async () => { diff --git a/src/stores/settingsStore.ts b/src/stores/settingsStore.ts index e7fee9d..5724999 100644 --- a/src/stores/settingsStore.ts +++ b/src/stores/settingsStore.ts @@ -1,19 +1,6 @@ import { create } from 'zustand'; import { tauriInvoke } from '@/api/client'; - -interface AppConfig { - downloadDir: string | null; - maxConcurrentDownloads: number; - maxSegmentsPerDownload: number; - speedLimitBytesPerSec: number | null; - autoExtract: boolean; - theme: string; - locale: string; - clipboardMonitoring: boolean; - minimizeToTray: boolean; -} - -type AppConfigPatch = Partial; +import type { AppConfig, AppConfigPatch } from '@/types/settings'; interface SettingsStoreState { config: AppConfig | null; @@ -38,7 +25,7 @@ export const useSettingsStore = create((set) => ({ config: s.config ? { ...s.config, ...partial } : s.config, })); try { - await tauriInvoke('settings_update', partial); + await tauriInvoke('settings_update', { patch: partial }); } catch (err) { const message = err instanceof Error ? err.message : String(err); set({ error: message }); diff --git a/src/types/settings.ts b/src/types/settings.ts new file mode 100644 index 0000000..e002687 --- /dev/null +++ b/src/types/settings.ts @@ -0,0 +1,60 @@ +export type ProxyType = 'none' | 'http' | 'socks5'; +export type ThemeMode = 'light' | 'dark' | 'auto'; +export type SettingTab = 'general' | 'downloads' | 'network' | 'remote' | 'browser' | 'appearance'; + +export interface AppConfig { + // General + downloadDir: string | null; + startMinimized: boolean; + notificationsEnabled: boolean; + autoExtract: boolean; + clipboardMonitoring: boolean; + soundEnabled: boolean; + confirmDelete: boolean; + subfolderPerPackage: boolean; + + // Downloads + maxConcurrentDownloads: number; + maxSegmentsPerDownload: number; + speedLimitBytesPerSec: number | null; + maxRetries: number; + retryDelaySeconds: number; + verifyChecksums: boolean; + preAllocateSpace: boolean; + + // Network + proxyType: ProxyType; + proxyUrl: string | null; + userAgent: string; + dnsOverHttps: boolean; + connectionTimeoutSeconds: number; + + // Remote Access + webInterfaceEnabled: boolean; + webInterfacePort: number; + restApiEnabled: boolean; + apiKey: string; + websocketEnabled: boolean; + + // Browser Integration + minFileSizeMb: number; + excludedDomains: string[]; + excludedExtensions: string[]; + + // Appearance + theme: ThemeMode; + accentColor: string; + compactMode: boolean; + locale: string; +} + +export type AppConfigPatch = Partial; + +export const ACCENT_PRESETS = [ + { name: 'Indigo', value: '#4F46E5' }, + { name: 'Blue', value: '#0EA5E9' }, + { name: 'Purple', value: '#A855F7' }, + { name: 'Pink', value: '#EC4899' }, + { name: 'Red', value: '#EF4444' }, + { name: 'Green', value: '#10B981' }, +] as const; diff --git a/src/views/SettingsView.tsx b/src/views/SettingsView.tsx deleted file mode 100644 index 05274cc..0000000 --- a/src/views/SettingsView.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Settings } from "lucide-react"; - -export function SettingsView() { - return ( -
- -
-

Settings

-

Coming soon

-
-
- ); -} diff --git a/src/views/SettingsView/AppearanceSection.tsx b/src/views/SettingsView/AppearanceSection.tsx new file mode 100644 index 0000000..a471312 --- /dev/null +++ b/src/views/SettingsView/AppearanceSection.tsx @@ -0,0 +1,109 @@ +import { useTauriMutation } from '@/api/hooks'; +import type { AppConfig, AppConfigPatch, ThemeMode } from '@/types/settings'; +import { ACCENT_PRESETS } from '@/types/settings'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { SettingToggle } from './SettingField'; + +interface AppearanceSectionProps { + config: AppConfig; +} + +const LOCALES = [ + { value: 'en', label: 'English' }, + { value: 'fr', label: 'Français' }, + { value: 'de', label: 'Deutsch' }, + { value: 'es', label: 'Español' }, + { value: 'ja', label: '日本語' }, + { value: 'zh', label: '中文' }, +] as const; + +export function AppearanceSection({ config }: AppearanceSectionProps) { + const { mutate } = useTauriMutation('settings_update', { + invalidateKeys: [['settings_get']], + }); + + const handleChange = (key: K, value: AppConfig[K]) => { + mutate({ patch: { [key]: value } as AppConfigPatch }); + }; + + return ( +
+
+

Appearance

+

Theme and display preferences

+
+ +
+
+

Theme

+ +
+ +
+

Accent color

+
+ {ACCENT_PRESETS.map((preset) => ( +
+
+ + handleChange('compactMode', v)} + /> + +
+

Language

+ +
+
+
+ ); +} diff --git a/src/views/SettingsView/BrowserSection.tsx b/src/views/SettingsView/BrowserSection.tsx new file mode 100644 index 0000000..f240375 --- /dev/null +++ b/src/views/SettingsView/BrowserSection.tsx @@ -0,0 +1,78 @@ +import { useState, useEffect } from 'react'; +import { useTauriMutation } from '@/api/hooks'; +import type { AppConfig, AppConfigPatch } from '@/types/settings'; +import { SettingNumberInput } from './SettingField'; + +interface BrowserSectionProps { + config: AppConfig; +} + +export function BrowserSection({ config }: BrowserSectionProps) { + const { mutate } = useTauriMutation('settings_update', { + invalidateKeys: [['settings_get']], + }); + + const handleChange = (key: K, value: AppConfig[K]) => { + mutate({ patch: { [key]: value } as AppConfigPatch }); + }; + + const [domainsDraft, setDomainsDraft] = useState(config.excludedDomains.join(', ')); + const [extensionsDraft, setExtensionsDraft] = useState(config.excludedExtensions.join(', ')); + + useEffect(() => { setDomainsDraft(config.excludedDomains.join(', ')); }, [config.excludedDomains]); + useEffect(() => { setExtensionsDraft(config.excludedExtensions.join(', ')); }, [config.excludedExtensions]); + + return ( +
+
+

Browser Integration

+

Browser extension capture settings

+
+ +
+ handleChange('minFileSizeMb', v)} + min={0} + step={0.1} + /> + +
+

Excluded domains

+

Comma-separated list of domains to ignore

+