From d969b70db59589cc632df375b7dc52235af670e5 Mon Sep 17 00:00:00 2001 From: edisdev Date: Mon, 24 Aug 2026 01:32:35 +0300 Subject: [PATCH 1/7] =?UTF-8?q?feat(watcher):=20reactive=20watch=20API=20?= =?UTF-8?q?=E2=80=94=20native=20macOS=20monitors=20+=20polling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a reactive watch API so consumers can subscribe to a kind of device information and receive a Tauri event whenever it changes, instead of polling a getter on a timer. - New `start_watching`/`stop_watching` commands (with matching permissions) backed by a reference-counted per-kind monitor registry. - Native, event-driven monitors on macOS: battery (IOKit), display (Core Graphics), and network (SystemConfiguration) — CPU stays idle between changes. Storage and device fall back to polling. - Change-detecting polling fallback with per-kind default/min intervals so expensive getters (e.g. `device` via system_profiler) are not polled aggressively; sleeps on a condvar and wakes immediately on stop. - macOS run-loop teardown is race-free (stop is signalled through the run loop itself) and stop() no longer holds the shared lock while joining; OS callbacks contain panics so they cannot unwind across the C ABI. Co-Authored-By: Claude Opus 4.8 (1M context) --- build.rs | 2 + .../src-tauri/gen/schemas/acl-manifests.json | 2 +- .../src-tauri/gen/schemas/desktop-schema.json | 88 +++- .../src-tauri/gen/schemas/macOS-schema.json | 88 +++- .../commands/start_watching.toml | 13 + .../autogenerated/commands/stop_watching.toml | 13 + permissions/autogenerated/reference.md | 54 ++ permissions/default.toml | 2 +- permissions/schemas/schema.json | 28 +- src/commands.rs | 14 + src/lib.rs | 6 +- src/watcher.rs | 225 ++++++++ src/watcher/native.rs | 39 ++ src/watcher/native/macos.rs | 489 ++++++++++++++++++ src/watcher/polling.rs | 104 ++++ 15 files changed, 1146 insertions(+), 21 deletions(-) create mode 100644 permissions/autogenerated/commands/start_watching.toml create mode 100644 permissions/autogenerated/commands/stop_watching.toml create mode 100644 src/watcher.rs create mode 100644 src/watcher/native.rs create mode 100644 src/watcher/native/macos.rs create mode 100644 src/watcher/polling.rs diff --git a/build.rs b/build.rs index c49362a..1f0c7d1 100644 --- a/build.rs +++ b/build.rs @@ -4,6 +4,8 @@ const COMMANDS: &[&str] = &[ "get_network_info", "get_storage_info", "get_display_info", + "start_watching", + "stop_watching", ]; fn main() { diff --git a/examples/tauri-app/src-tauri/gen/schemas/acl-manifests.json b/examples/tauri-app/src-tauri/gen/schemas/acl-manifests.json index 099b57f..899e8e9 100644 --- a/examples/tauri-app/src-tauri/gen/schemas/acl-manifests.json +++ b/examples/tauri-app/src-tauri/gen/schemas/acl-manifests.json @@ -1 +1 @@ -{"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},"device-info":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-get-device-info","allow-get-battery-info","allow-get-network-info","allow-get-storage-info","allow-get-display-info"]},"permissions":{"allow-get-battery-info":{"identifier":"allow-get-battery-info","description":"Enables the get_battery_info command without any pre-configured scope.","commands":{"allow":["get_battery_info"],"deny":[]}},"allow-get-device-info":{"identifier":"allow-get-device-info","description":"Enables the get_device_info command without any pre-configured scope.","commands":{"allow":["get_device_info"],"deny":[]}},"allow-get-display-info":{"identifier":"allow-get-display-info","description":"Enables the get_display_info command without any pre-configured scope.","commands":{"allow":["get_display_info"],"deny":[]}},"allow-get-network-info":{"identifier":"allow-get-network-info","description":"Enables the get_network_info command without any pre-configured scope.","commands":{"allow":["get_network_info"],"deny":[]}},"allow-get-storage-info":{"identifier":"allow-get-storage-info","description":"Enables the get_storage_info command without any pre-configured scope.","commands":{"allow":["get_storage_info"],"deny":[]}},"deny-get-battery-info":{"identifier":"deny-get-battery-info","description":"Denies the get_battery_info command without any pre-configured scope.","commands":{"allow":[],"deny":["get_battery_info"]}},"deny-get-device-info":{"identifier":"deny-get-device-info","description":"Denies the get_device_info command without any pre-configured scope.","commands":{"allow":[],"deny":["get_device_info"]}},"deny-get-display-info":{"identifier":"deny-get-display-info","description":"Denies the get_display_info command without any pre-configured scope.","commands":{"allow":[],"deny":["get_display_info"]}},"deny-get-network-info":{"identifier":"deny-get-network-info","description":"Denies the get_network_info command without any pre-configured scope.","commands":{"allow":[],"deny":["get_network_info"]}},"deny-get-storage-info":{"identifier":"deny-get-storage-info","description":"Denies the get_storage_info command without any pre-configured scope.","commands":{"allow":[],"deny":["get_storage_info"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file +{"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","allow-supports-multiple-windows"]},"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-supports-multiple-windows":{"identifier":"allow-supports-multiple-windows","description":"Enables the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":["supports_multiple_windows"],"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-supports-multiple-windows":{"identifier":"deny-supports-multiple-windows","description":"Denies the supports_multiple_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["supports_multiple_windows"]}},"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-icon-with-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-icon-with-as-template":{"identifier":"allow-set-icon-with-as-template","description":"Enables the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_with_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-icon-with-as-template":{"identifier":"deny-set-icon-with-as-template","description":"Denies the set_icon_with_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_with_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-activity-name","allow-scene-identifier","allow-internal-toggle-maximize"]},"permissions":{"allow-activity-name":{"identifier":"allow-activity-name","description":"Enables the activity_name command without any pre-configured scope.","commands":{"allow":["activity_name"],"deny":[]}},"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-scene-identifier":{"identifier":"allow-scene-identifier","description":"Enables the scene_identifier command without any pre-configured scope.","commands":{"allow":["scene_identifier"],"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-activity-name":{"identifier":"deny-activity-name","description":"Denies the activity_name command without any pre-configured scope.","commands":{"allow":[],"deny":["activity_name"]}},"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-scene-identifier":{"identifier":"deny-scene-identifier","description":"Denies the scene_identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["scene_identifier"]}},"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},"device-info":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin","permissions":["allow-get-device-info","allow-get-battery-info","allow-get-network-info","allow-get-storage-info","allow-get-display-info","allow-start-watching","allow-stop-watching"]},"permissions":{"allow-get-battery-info":{"identifier":"allow-get-battery-info","description":"Enables the get_battery_info command without any pre-configured scope.","commands":{"allow":["get_battery_info"],"deny":[]}},"allow-get-device-info":{"identifier":"allow-get-device-info","description":"Enables the get_device_info command without any pre-configured scope.","commands":{"allow":["get_device_info"],"deny":[]}},"allow-get-display-info":{"identifier":"allow-get-display-info","description":"Enables the get_display_info command without any pre-configured scope.","commands":{"allow":["get_display_info"],"deny":[]}},"allow-get-network-info":{"identifier":"allow-get-network-info","description":"Enables the get_network_info command without any pre-configured scope.","commands":{"allow":["get_network_info"],"deny":[]}},"allow-get-storage-info":{"identifier":"allow-get-storage-info","description":"Enables the get_storage_info command without any pre-configured scope.","commands":{"allow":["get_storage_info"],"deny":[]}},"allow-start-watching":{"identifier":"allow-start-watching","description":"Enables the start_watching command without any pre-configured scope.","commands":{"allow":["start_watching"],"deny":[]}},"allow-stop-watching":{"identifier":"allow-stop-watching","description":"Enables the stop_watching command without any pre-configured scope.","commands":{"allow":["stop_watching"],"deny":[]}},"deny-get-battery-info":{"identifier":"deny-get-battery-info","description":"Denies the get_battery_info command without any pre-configured scope.","commands":{"allow":[],"deny":["get_battery_info"]}},"deny-get-device-info":{"identifier":"deny-get-device-info","description":"Denies the get_device_info command without any pre-configured scope.","commands":{"allow":[],"deny":["get_device_info"]}},"deny-get-display-info":{"identifier":"deny-get-display-info","description":"Denies the get_display_info command without any pre-configured scope.","commands":{"allow":[],"deny":["get_display_info"]}},"deny-get-network-info":{"identifier":"deny-get-network-info","description":"Denies the get_network_info command without any pre-configured scope.","commands":{"allow":[],"deny":["get_network_info"]}},"deny-get-storage-info":{"identifier":"deny-get-storage-info","description":"Denies the get_storage_info command without any pre-configured scope.","commands":{"allow":[],"deny":["get_storage_info"]}},"deny-start-watching":{"identifier":"deny-start-watching","description":"Denies the start_watching command without any pre-configured scope.","commands":{"allow":[],"deny":["start_watching"]}},"deny-stop-watching":{"identifier":"deny-stop-watching","description":"Denies the stop_watching command without any pre-configured scope.","commands":{"allow":[],"deny":["stop_watching"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/examples/tauri-app/src-tauri/gen/schemas/desktop-schema.json b/examples/tauri-app/src-tauri/gen/schemas/desktop-schema.json index 37fe584..fa7587d 100644 --- a/examples/tauri-app/src-tauri/gen/schemas/desktop-schema.json +++ b/examples/tauri-app/src-tauri/gen/schemas/desktop-schema.json @@ -183,10 +183,10 @@ "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" }, { - "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", "type": "string", "const": "core:app:default", - "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`" + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" }, { "description": "Enables the app_hide command without any pre-configured scope.", @@ -260,6 +260,12 @@ "const": "core:app:allow-set-dock-visibility", "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." }, + { + "description": "Enables the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-supports-multiple-windows", + "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." + }, { "description": "Enables the tauri_version command without any pre-configured scope.", "type": "string", @@ -344,6 +350,12 @@ "const": "core:app:deny-set-dock-visibility", "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." }, + { + "description": "Denies the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-supports-multiple-windows", + "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." + }, { "description": "Denies the tauri_version command without any pre-configured scope.", "type": "string", @@ -867,10 +879,10 @@ "markdownDescription": "Denies the close command without any pre-configured scope." }, { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`", + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", "type": "string", "const": "core:tray:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`" + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" }, { "description": "Enables the get_by_id command without any pre-configured scope.", @@ -902,6 +914,12 @@ "const": "core:tray:allow-set-icon-as-template", "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." }, + { + "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-with-as-template", + "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." + }, { "description": "Enables the set_menu command without any pre-configured scope.", "type": "string", @@ -968,6 +986,12 @@ "const": "core:tray:deny-set-icon-as-template", "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." }, + { + "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-with-as-template", + "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." + }, { "description": "Denies the set_menu command without any pre-configured scope.", "type": "string", @@ -1227,10 +1251,16 @@ "markdownDescription": "Denies the webview_size command without any pre-configured scope." }, { - "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`", + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", "type": "string", "const": "core:window:default", - "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`" + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-activity-name", + "markdownDescription": "Enables the activity_name command without any pre-configured scope." }, { "description": "Enables the available_monitors command without any pre-configured scope.", @@ -1424,6 +1454,12 @@ "const": "core:window:allow-scale-factor", "markdownDescription": "Enables the scale_factor command without any pre-configured scope." }, + { + "description": "Enables the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scene-identifier", + "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." + }, { "description": "Enables the set_always_on_bottom command without any pre-configured scope.", "type": "string", @@ -1688,6 +1724,12 @@ "const": "core:window:allow-unminimize", "markdownDescription": "Enables the unminimize command without any pre-configured scope." }, + { + "description": "Denies the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-activity-name", + "markdownDescription": "Denies the activity_name command without any pre-configured scope." + }, { "description": "Denies the available_monitors command without any pre-configured scope.", "type": "string", @@ -1880,6 +1922,12 @@ "const": "core:window:deny-scale-factor", "markdownDescription": "Denies the scale_factor command without any pre-configured scope." }, + { + "description": "Denies the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scene-identifier", + "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." + }, { "description": "Denies the set_always_on_bottom command without any pre-configured scope.", "type": "string", @@ -2145,10 +2193,10 @@ "markdownDescription": "Denies the unminimize command without any pre-configured scope." }, { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`", + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`\n- `allow-start-watching`\n- `allow-stop-watching`", "type": "string", "const": "device-info:default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`" + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`\n- `allow-start-watching`\n- `allow-stop-watching`" }, { "description": "Enables the get_battery_info command without any pre-configured scope.", @@ -2180,6 +2228,18 @@ "const": "device-info:allow-get-storage-info", "markdownDescription": "Enables the get_storage_info command without any pre-configured scope." }, + { + "description": "Enables the start_watching command without any pre-configured scope.", + "type": "string", + "const": "device-info:allow-start-watching", + "markdownDescription": "Enables the start_watching command without any pre-configured scope." + }, + { + "description": "Enables the stop_watching command without any pre-configured scope.", + "type": "string", + "const": "device-info:allow-stop-watching", + "markdownDescription": "Enables the stop_watching command without any pre-configured scope." + }, { "description": "Denies the get_battery_info command without any pre-configured scope.", "type": "string", @@ -2209,6 +2269,18 @@ "type": "string", "const": "device-info:deny-get-storage-info", "markdownDescription": "Denies the get_storage_info command without any pre-configured scope." + }, + { + "description": "Denies the start_watching command without any pre-configured scope.", + "type": "string", + "const": "device-info:deny-start-watching", + "markdownDescription": "Denies the start_watching command without any pre-configured scope." + }, + { + "description": "Denies the stop_watching command without any pre-configured scope.", + "type": "string", + "const": "device-info:deny-stop-watching", + "markdownDescription": "Denies the stop_watching command without any pre-configured scope." } ] }, diff --git a/examples/tauri-app/src-tauri/gen/schemas/macOS-schema.json b/examples/tauri-app/src-tauri/gen/schemas/macOS-schema.json index 37fe584..fa7587d 100644 --- a/examples/tauri-app/src-tauri/gen/schemas/macOS-schema.json +++ b/examples/tauri-app/src-tauri/gen/schemas/macOS-schema.json @@ -183,10 +183,10 @@ "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" }, { - "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`", "type": "string", "const": "core:app:default", - "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`" + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`\n- `allow-supports-multiple-windows`" }, { "description": "Enables the app_hide command without any pre-configured scope.", @@ -260,6 +260,12 @@ "const": "core:app:allow-set-dock-visibility", "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." }, + { + "description": "Enables the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-supports-multiple-windows", + "markdownDescription": "Enables the supports_multiple_windows command without any pre-configured scope." + }, { "description": "Enables the tauri_version command without any pre-configured scope.", "type": "string", @@ -344,6 +350,12 @@ "const": "core:app:deny-set-dock-visibility", "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." }, + { + "description": "Denies the supports_multiple_windows command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-supports-multiple-windows", + "markdownDescription": "Denies the supports_multiple_windows command without any pre-configured scope." + }, { "description": "Denies the tauri_version command without any pre-configured scope.", "type": "string", @@ -867,10 +879,10 @@ "markdownDescription": "Denies the close command without any pre-configured scope." }, { - "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`", + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`", "type": "string", "const": "core:tray:default", - "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`" + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-icon-with-as-template`\n- `allow-set-show-menu-on-left-click`" }, { "description": "Enables the get_by_id command without any pre-configured scope.", @@ -902,6 +914,12 @@ "const": "core:tray:allow-set-icon-as-template", "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." }, + { + "description": "Enables the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-with-as-template", + "markdownDescription": "Enables the set_icon_with_as_template command without any pre-configured scope." + }, { "description": "Enables the set_menu command without any pre-configured scope.", "type": "string", @@ -968,6 +986,12 @@ "const": "core:tray:deny-set-icon-as-template", "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." }, + { + "description": "Denies the set_icon_with_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-with-as-template", + "markdownDescription": "Denies the set_icon_with_as_template command without any pre-configured scope." + }, { "description": "Denies the set_menu command without any pre-configured scope.", "type": "string", @@ -1227,10 +1251,16 @@ "markdownDescription": "Denies the webview_size command without any pre-configured scope." }, { - "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`", + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`", "type": "string", "const": "core:window:default", - "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`" + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-activity-name`\n- `allow-scene-identifier`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-activity-name", + "markdownDescription": "Enables the activity_name command without any pre-configured scope." }, { "description": "Enables the available_monitors command without any pre-configured scope.", @@ -1424,6 +1454,12 @@ "const": "core:window:allow-scale-factor", "markdownDescription": "Enables the scale_factor command without any pre-configured scope." }, + { + "description": "Enables the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scene-identifier", + "markdownDescription": "Enables the scene_identifier command without any pre-configured scope." + }, { "description": "Enables the set_always_on_bottom command without any pre-configured scope.", "type": "string", @@ -1688,6 +1724,12 @@ "const": "core:window:allow-unminimize", "markdownDescription": "Enables the unminimize command without any pre-configured scope." }, + { + "description": "Denies the activity_name command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-activity-name", + "markdownDescription": "Denies the activity_name command without any pre-configured scope." + }, { "description": "Denies the available_monitors command without any pre-configured scope.", "type": "string", @@ -1880,6 +1922,12 @@ "const": "core:window:deny-scale-factor", "markdownDescription": "Denies the scale_factor command without any pre-configured scope." }, + { + "description": "Denies the scene_identifier command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scene-identifier", + "markdownDescription": "Denies the scene_identifier command without any pre-configured scope." + }, { "description": "Denies the set_always_on_bottom command without any pre-configured scope.", "type": "string", @@ -2145,10 +2193,10 @@ "markdownDescription": "Denies the unminimize command without any pre-configured scope." }, { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`", + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`\n- `allow-start-watching`\n- `allow-stop-watching`", "type": "string", "const": "device-info:default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`" + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`\n- `allow-start-watching`\n- `allow-stop-watching`" }, { "description": "Enables the get_battery_info command without any pre-configured scope.", @@ -2180,6 +2228,18 @@ "const": "device-info:allow-get-storage-info", "markdownDescription": "Enables the get_storage_info command without any pre-configured scope." }, + { + "description": "Enables the start_watching command without any pre-configured scope.", + "type": "string", + "const": "device-info:allow-start-watching", + "markdownDescription": "Enables the start_watching command without any pre-configured scope." + }, + { + "description": "Enables the stop_watching command without any pre-configured scope.", + "type": "string", + "const": "device-info:allow-stop-watching", + "markdownDescription": "Enables the stop_watching command without any pre-configured scope." + }, { "description": "Denies the get_battery_info command without any pre-configured scope.", "type": "string", @@ -2209,6 +2269,18 @@ "type": "string", "const": "device-info:deny-get-storage-info", "markdownDescription": "Denies the get_storage_info command without any pre-configured scope." + }, + { + "description": "Denies the start_watching command without any pre-configured scope.", + "type": "string", + "const": "device-info:deny-start-watching", + "markdownDescription": "Denies the start_watching command without any pre-configured scope." + }, + { + "description": "Denies the stop_watching command without any pre-configured scope.", + "type": "string", + "const": "device-info:deny-stop-watching", + "markdownDescription": "Denies the stop_watching command without any pre-configured scope." } ] }, diff --git a/permissions/autogenerated/commands/start_watching.toml b/permissions/autogenerated/commands/start_watching.toml new file mode 100644 index 0000000..1e92b4c --- /dev/null +++ b/permissions/autogenerated/commands/start_watching.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-start-watching" +description = "Enables the start_watching command without any pre-configured scope." +commands.allow = ["start_watching"] + +[[permission]] +identifier = "deny-start-watching" +description = "Denies the start_watching command without any pre-configured scope." +commands.deny = ["start_watching"] diff --git a/permissions/autogenerated/commands/stop_watching.toml b/permissions/autogenerated/commands/stop_watching.toml new file mode 100644 index 0000000..6d7d27f --- /dev/null +++ b/permissions/autogenerated/commands/stop_watching.toml @@ -0,0 +1,13 @@ +# Automatically generated - DO NOT EDIT! + +"$schema" = "../../schemas/schema.json" + +[[permission]] +identifier = "allow-stop-watching" +description = "Enables the stop_watching command without any pre-configured scope." +commands.allow = ["stop_watching"] + +[[permission]] +identifier = "deny-stop-watching" +description = "Denies the stop_watching command without any pre-configured scope." +commands.deny = ["stop_watching"] diff --git a/permissions/autogenerated/reference.md b/permissions/autogenerated/reference.md index 179f913..8ba9387 100644 --- a/permissions/autogenerated/reference.md +++ b/permissions/autogenerated/reference.md @@ -9,6 +9,8 @@ Default permissions for the plugin - `allow-get-network-info` - `allow-get-storage-info` - `allow-get-display-info` +- `allow-start-watching` +- `allow-stop-watching` ## Permission Table @@ -146,6 +148,58 @@ Enables the get_storage_info command without any pre-configured scope. Denies the get_storage_info command without any pre-configured scope. + + + + + + +`device-info:allow-start-watching` + + + + +Enables the start_watching command without any pre-configured scope. + + + + + + + +`device-info:deny-start-watching` + + + + +Denies the start_watching command without any pre-configured scope. + + + + + + + +`device-info:allow-stop-watching` + + + + +Enables the stop_watching command without any pre-configured scope. + + + + + + + +`device-info:deny-stop-watching` + + + + +Denies the stop_watching command without any pre-configured scope. + diff --git a/permissions/default.toml b/permissions/default.toml index 1d3f618..c62a36c 100644 --- a/permissions/default.toml +++ b/permissions/default.toml @@ -1,3 +1,3 @@ [default] description = "Default permissions for the plugin" -permissions = ["allow-get-device-info", "allow-get-battery-info", "allow-get-network-info", "allow-get-storage-info", "allow-get-display-info"] +permissions = ["allow-get-device-info", "allow-get-battery-info", "allow-get-network-info", "allow-get-storage-info", "allow-get-display-info", "allow-start-watching", "allow-stop-watching"] diff --git a/permissions/schemas/schema.json b/permissions/schemas/schema.json index 4ac91ce..4df78cd 100644 --- a/permissions/schemas/schema.json +++ b/permissions/schemas/schema.json @@ -355,10 +355,34 @@ "markdownDescription": "Denies the get_storage_info command without any pre-configured scope." }, { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`", + "description": "Enables the start_watching command without any pre-configured scope.", + "type": "string", + "const": "allow-start-watching", + "markdownDescription": "Enables the start_watching command without any pre-configured scope." + }, + { + "description": "Denies the start_watching command without any pre-configured scope.", + "type": "string", + "const": "deny-start-watching", + "markdownDescription": "Denies the start_watching command without any pre-configured scope." + }, + { + "description": "Enables the stop_watching command without any pre-configured scope.", + "type": "string", + "const": "allow-stop-watching", + "markdownDescription": "Enables the stop_watching command without any pre-configured scope." + }, + { + "description": "Denies the stop_watching command without any pre-configured scope.", + "type": "string", + "const": "deny-stop-watching", + "markdownDescription": "Denies the stop_watching command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`\n- `allow-start-watching`\n- `allow-stop-watching`", "type": "string", "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`" + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-get-device-info`\n- `allow-get-battery-info`\n- `allow-get-network-info`\n- `allow-get-storage-info`\n- `allow-get-display-info`\n- `allow-start-watching`\n- `allow-stop-watching`" } ] } diff --git a/src/commands.rs b/src/commands.rs index b622687..39b92fa 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -28,3 +28,17 @@ pub(crate) async fn get_storage_info(app: AppHandle) -> Result(app: AppHandle) -> Result { app.device_info().get_display_info() } + +#[command] +pub(crate) async fn start_watching( + app: AppHandle, + kind: String, + interval_ms: Option, +) -> Result<()> { + crate::watcher::start(&app, &kind, interval_ms) +} + +#[command] +pub(crate) async fn stop_watching(app: AppHandle, kind: String) -> Result<()> { + crate::watcher::stop(&app, &kind) +} diff --git a/src/lib.rs b/src/lib.rs index 725f157..54e49fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ mod mobile; mod commands; mod error; mod models; +mod watcher; pub use error::{Error, Result}; @@ -88,7 +89,9 @@ pub fn init() -> TauriPlugin { commands::get_battery_info, commands::get_network_info, commands::get_storage_info, - commands::get_display_info + commands::get_display_info, + commands::start_watching, + commands::stop_watching ]) .setup(|app, api| { #[cfg(mobile)] @@ -96,6 +99,7 @@ pub fn init() -> TauriPlugin { #[cfg(desktop)] let device_info = desktop::init(app, api)?; app.manage(device_info); + app.manage(watcher::WatcherState::default()); Ok(()) }) .build() diff --git a/src/watcher.rs b/src/watcher.rs new file mode 100644 index 0000000..91196a4 --- /dev/null +++ b/src/watcher.rs @@ -0,0 +1,225 @@ +//! Reactive watch API for the device-info plugin. +//! +//! Consumers *subscribe* to a kind of device information and receive a Tauri +//! event whenever the value changes, instead of polling a getter on a timer. +//! +//! ## Architecture +//! +//! Each watch `kind` is backed by a [`MonitorHandle`]: +//! +//! - When the platform exposes a **native, event-driven** source for a kind +//! (e.g. IOKit power notifications on macOS), that is used — the CPU stays +//! idle between changes and updates are delivered the instant they happen. +//! - Otherwise we fall back to a **change-detecting poller** (see [`polling`]). +//! +//! Either way the public API and the emitted event names are identical, so the +//! engine can be upgraded per platform/kind without affecting consumers. +//! +//! Subscribers are reference-counted: the monitor for a kind starts on the +//! first subscriber and is torn down once the last one unsubscribes. + +use std::collections::HashMap; +use std::sync::Mutex; + +use tauri::{AppHandle, Emitter, Manager, Runtime}; + +use crate::DeviceInfoExt; + +mod native; +mod polling; + +/// The kinds of device information that can be watched. +pub(crate) const WATCH_KINDS: &[&str] = &["battery", "network", "storage", "display", "device"]; + +/// A running change-event source for one kind. +/// +/// `stop` must tear down whatever the monitor set up — OS callbacks, run loops, +/// D-Bus connections, or polling threads. +pub(crate) trait MonitorHandle: Send { + fn stop(self: Box); +} + +/// One active monitor plus its subscriber count. +struct Subscription { + handle: Box, + refs: usize, +} + +/// Shared state holding at most one monitor per active watch kind. +#[derive(Default)] +pub(crate) struct WatcherState { + subs: Mutex>, +} + +/// The event name emitted for a given watch kind, e.g. `device-info://battery-changed`. +pub(crate) fn event_name(kind: &str) -> String { + format!("device-info://{kind}-changed") +} + +/// Reads the current value for `kind` as a JSON value, for change comparison and emission. +/// +/// Shared by the poller and by native monitors (which read the fresh value when +/// the OS signals a change). +pub(crate) fn read_snapshot( + app: &AppHandle, + kind: &str, +) -> crate::Result { + let di = app.device_info(); + let value = match kind { + "battery" => serde_json::to_value(di.get_battery_info()?), + "network" => serde_json::to_value(di.get_network_info()?), + "storage" => serde_json::to_value(di.get_storage_info()?), + "display" => serde_json::to_value(di.get_display_info()?), + "device" => serde_json::to_value(di.get_device_info()?), + other => { + return Err(crate::Error::DeviceInfo(format!( + "unknown watch kind: {other}" + ))) + } + }; + value.map_err(|e| crate::Error::DeviceInfo(e.to_string())) +} + +/// Reads the current value for `kind` and emits `event` only if it differs from +/// `last`, updating `last` on emit. +/// +/// This is the single change-detection + emission path shared by the poller and +/// the native monitors, so both stay in lockstep. The payload is emitted by +/// reference to avoid a deep clone of the value also stored in `last`. +pub(crate) fn emit_if_changed( + app: &AppHandle, + event: &str, + kind: &str, + last: &mut Option, +) { + if let Ok(snapshot) = read_snapshot(app, kind) { + if last.as_ref() != Some(&snapshot) { + let _ = app.emit(event, &snapshot); + *last = Some(snapshot); + } + } +} + +/// Subscribes to a watch `kind`, starting a monitor if one isn't already running. +/// +/// A native event-driven monitor is preferred; if the platform has none for this +/// kind, a change-detecting poller is used (`interval_ms` applies only then, and +/// only for the first subscriber). +pub(crate) fn start( + app: &AppHandle, + kind: &str, + interval_ms: Option, +) -> crate::Result<()> { + if !WATCH_KINDS.contains(&kind) { + return Err(crate::Error::DeviceInfo(format!( + "unknown watch kind: {kind}" + ))); + } + + let state = app.state::(); + let mut subs = state.subs.lock().map_err(poisoned)?; + + // A monitor is already running for this kind: just add a subscriber. + if let Some(sub) = subs.get_mut(kind) { + sub.refs += 1; + return Ok(()); + } + + // Prefer a native event-driven monitor; fall back to the poller. + let handle = match native::try_spawn(app, kind)? { + Some(handle) => handle, + None => polling::spawn(app, kind, interval_ms), + }; + + subs.insert(kind.to_string(), Subscription { handle, refs: 1 }); + Ok(()) +} + +/// Unsubscribes from a watch `kind`, stopping the monitor once the last subscriber leaves. +pub(crate) fn stop(app: &AppHandle, kind: &str) -> crate::Result<()> { + let state = app.state::(); + + // Take the handle out from under the lock, then tear it down *after* releasing + // it: `handle.stop()` can block (it joins the monitor thread), and holding the + // subs mutex across that would serialize every other kind's subscribe/unsubscribe. + let handle = { + let mut subs = state.subs.lock().map_err(poisoned)?; + match subs.get_mut(kind) { + Some(sub) => { + sub.refs = sub.refs.saturating_sub(1); + if sub.refs == 0 { + subs.remove(kind).map(|sub| sub.handle) + } else { + None + } + } + None => None, + } + }; + + if let Some(handle) = handle { + handle.stop(); + } + Ok(()) +} + +/// Maps a poisoned-lock error into the plugin's error type. +fn poisoned(e: E) -> crate::Error { + crate::Error::DeviceInfo(format!("watcher state poisoned: {e}")) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn event_name_follows_convention() { + assert_eq!(event_name("battery"), "device-info://battery-changed"); + assert_eq!(event_name("network"), "device-info://network-changed"); + } + + #[test] + fn watch_kinds_cover_all_getters() { + for kind in ["battery", "network", "storage", "display", "device"] { + assert!(WATCH_KINDS.contains(&kind), "missing watch kind: {kind}"); + } + } + + #[test] + fn unknown_kind_is_rejected() { + let app = mock_app(); + let err = start(app.handle(), "gpu", None).unwrap_err(); + assert!(err.to_string().contains("unknown watch kind")); + } + + /// Exercises the full monitor lifecycle (spawn → emit → stop → join → free) + /// for every kind, including the native macOS event-driven paths. Catches + /// FFI signature mistakes, use-after-free, and stop deadlocks at runtime. + #[test] + fn start_and_stop_every_kind_does_not_crash() { + let app = mock_app(); + for kind in WATCH_KINDS { + start(app.handle(), kind, Some(250)).unwrap(); + std::thread::sleep(std::time::Duration::from_millis(60)); + stop(app.handle(), kind).unwrap(); + } + } + + #[test] + fn reference_counting_keeps_monitor_until_last_unsubscribe() { + let app = mock_app(); + start(app.handle(), "battery", None).unwrap(); + start(app.handle(), "battery", None).unwrap(); + stop(app.handle(), "battery").unwrap(); // one subscriber left + stop(app.handle(), "battery").unwrap(); // last subscriber → tears down + // Extra stop on an already-removed kind must be a no-op, not a panic. + stop(app.handle(), "battery").unwrap(); + } + + fn mock_app() -> tauri::App { + tauri::test::mock_builder() + .plugin(crate::init()) + .build(tauri::test::mock_context(tauri::test::noop_assets())) + .expect("failed to build mock app") + } +} diff --git a/src/watcher/native.rs b/src/watcher/native.rs new file mode 100644 index 0000000..9d37f20 --- /dev/null +++ b/src/watcher/native.rs @@ -0,0 +1,39 @@ +//! Native, OS-event-driven monitors. +//! +//! [`try_spawn`] returns `Ok(Some(handle))` when the current platform has a +//! native event source for `kind`, or `Ok(None)` to fall back to polling. +//! +//! ## Roadmap (each entry requires on-device verification) +//! +//! | Kind | macOS | Windows | Linux | +//! |---------|-----------------------------------------|--------------------------------------|--------------------------------| +//! | battery | IOKit `IOPSNotificationCreateRunLoopSource` ✅ | `RegisterPowerSettingNotification` | UPower / D-Bus | +//! | display | `CGDisplayRegisterReconfigurationCallback` ✅ | `WM_DISPLAYCHANGE` | XRandR | +//! | network | `SCNetworkReachability` | `NotifyUnicastIpAddressChange` | netlink / NetworkManager | +//! | storage | *(no OS event — always polled)* | *(polled)* | *(polled)* | +//! | device | *(rarely changes — polled)* | *(polled)* | *(polled)* | + +use tauri::{AppHandle, Runtime}; + +use super::MonitorHandle; + +#[cfg(target_os = "macos")] +mod macos; + +/// Returns a native event-driven monitor for `kind` if this platform supports +/// one, else `Ok(None)` so the caller falls back to polling. +pub(super) fn try_spawn( + app: &AppHandle, + kind: &str, +) -> crate::Result>> { + #[cfg(target_os = "macos")] + { + macos::try_spawn(app, kind) + } + + #[cfg(not(target_os = "macos"))] + { + let _ = (app, kind); + Ok(None) + } +} diff --git a/src/watcher/native/macos.rs b/src/watcher/native/macos.rs new file mode 100644 index 0000000..1c6cb7a --- /dev/null +++ b/src/watcher/native/macos.rs @@ -0,0 +1,489 @@ +//! macOS native monitors — event-driven via Core Foundation run loops. +//! +//! Each monitor runs a dedicated thread with its own `CFRunLoop` and registers +//! an OS notification source: +//! +//! - **battery** — IOKit `IOPSNotificationCreateRunLoopSource` +//! - **display** — Core Graphics `CGDisplayRegisterReconfigurationCallback` +//! - **network** — SystemConfiguration `SCNetworkReachability` +//! +//! The OS invokes our callback only when state changes, so the CPU stays idle in +//! between. A no-op keep-alive timer is added so `CFRunLoopRun` does not return +//! before `stop` asks it to. Storage and device have no OS change event and fall +//! back to polling. + +#![allow(non_snake_case, non_upper_case_globals, dead_code)] + +use std::ffi::c_void; +use std::ptr; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::JoinHandle; + +use tauri::{AppHandle, Runtime}; + +use crate::watcher::{emit_if_changed, event_name, MonitorHandle}; + +// ---- Core Foundation FFI ---------------------------------------------------- + +type CFRunLoopRef = *mut c_void; +type CFRunLoopSourceRef = *mut c_void; +type CFRunLoopTimerRef = *mut c_void; +type CFStringRef = *const c_void; +type CFAllocatorRef = *const c_void; + +type CFRunLoopTimerCallBack = unsafe extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void); +type CFRunLoopPerformCallBack = unsafe extern "C" fn(info: *mut c_void); + +/// Context for a version-0 `CFRunLoopSource`. Only `perform` is used; the other +/// callbacks are left null (matching how [`SCNetworkReachabilityContext`] is set up). +#[repr(C)] +struct CFRunLoopSourceContext { + version: isize, + info: *mut c_void, + retain: *const c_void, + release: *const c_void, + copyDescription: *const c_void, + equal: *const c_void, + hash: *const c_void, + schedule: *const c_void, + cancel: *const c_void, + perform: CFRunLoopPerformCallBack, +} + +#[link(name = "CoreFoundation", kind = "framework")] +extern "C" { + static kCFRunLoopDefaultMode: CFStringRef; + fn CFRunLoopGetCurrent() -> CFRunLoopRef; + fn CFRunLoopRun(); + fn CFRunLoopStop(rl: CFRunLoopRef); + fn CFRunLoopWakeUp(rl: CFRunLoopRef); + fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFStringRef); + fn CFRunLoopRemoveSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFStringRef); + fn CFRunLoopSourceCreate( + allocator: CFAllocatorRef, + order: isize, + context: *mut CFRunLoopSourceContext, + ) -> CFRunLoopSourceRef; + fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef); + fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFStringRef); + fn CFRunLoopTimerCreate( + allocator: CFAllocatorRef, + fireDate: f64, + interval: f64, + flags: usize, + order: isize, + callout: CFRunLoopTimerCallBack, + context: *mut c_void, + ) -> CFRunLoopTimerRef; + fn CFAbsoluteTimeGetCurrent() -> f64; + fn CFRelease(cf: *const c_void); +} + +// ---- IOKit (battery) FFI ---------------------------------------------------- + +type IOPSCallback = unsafe extern "C" fn(context: *mut c_void); + +#[link(name = "IOKit", kind = "framework")] +extern "C" { + fn IOPSNotificationCreateRunLoopSource( + callback: IOPSCallback, + context: *mut c_void, + ) -> CFRunLoopSourceRef; +} + +// ---- Core Graphics (display) FFI -------------------------------------------- + +type CGDirectDisplayID = u32; +type CGDisplayChangeSummaryFlags = u32; +type CGDisplayReconfigurationCallBack = unsafe extern "C" fn( + display: CGDirectDisplayID, + flags: CGDisplayChangeSummaryFlags, + userInfo: *mut c_void, +); + +#[link(name = "CoreGraphics", kind = "framework")] +extern "C" { + fn CGDisplayRegisterReconfigurationCallback( + callback: CGDisplayReconfigurationCallBack, + userInfo: *mut c_void, + ) -> i32; + fn CGDisplayRemoveReconfigurationCallback( + callback: CGDisplayReconfigurationCallBack, + userInfo: *mut c_void, + ) -> i32; +} + +// ---- SystemConfiguration (network) FFI -------------------------------------- + +type SCNetworkReachabilityRef = *mut c_void; +type SCNetworkReachabilityFlags = u32; +type SCNetworkReachabilityCallBack = unsafe extern "C" fn( + target: SCNetworkReachabilityRef, + flags: SCNetworkReachabilityFlags, + info: *mut c_void, +); + +#[repr(C)] +struct SCNetworkReachabilityContext { + version: isize, + info: *mut c_void, + retain: *const c_void, + release: *const c_void, + copyDescription: *const c_void, +} + +/// Minimal `sockaddr_in` for a zeroed address ("general internet reachability"). +#[repr(C)] +struct SockaddrIn { + sin_len: u8, + sin_family: u8, + sin_port: u16, + sin_addr: u32, + sin_zero: [u8; 8], +} + +#[link(name = "SystemConfiguration", kind = "framework")] +extern "C" { + fn SCNetworkReachabilityCreateWithAddress( + allocator: CFAllocatorRef, + address: *const c_void, + ) -> SCNetworkReachabilityRef; + fn SCNetworkReachabilitySetCallback( + target: SCNetworkReachabilityRef, + callout: SCNetworkReachabilityCallBack, + context: *mut SCNetworkReachabilityContext, + ) -> u8; + fn SCNetworkReachabilityScheduleWithRunLoop( + target: SCNetworkReachabilityRef, + runLoop: CFRunLoopRef, + runLoopMode: CFStringRef, + ) -> u8; + fn SCNetworkReachabilityUnscheduleFromRunLoop( + target: SCNetworkReachabilityRef, + runLoop: CFRunLoopRef, + runLoopMode: CFStringRef, + ) -> u8; +} + +/// Keep-alive timer firing far in the future, only to keep the run loop alive. +const KEEPALIVE_INTERVAL: f64 = 1.0e10; + +// ---- Type-erased emit context ----------------------------------------------- + +/// Type-erases the generic `AppHandle` so the context can travel through a C `void*`. +trait Fire: Send + Sync { + fn fire(&self); +} + +/// Reads the current value for a kind and emits an event only when it changed. +struct EmitCtx { + app: AppHandle, + kind: String, + event: String, + last: Mutex>, +} + +impl Fire for EmitCtx { + fn fire(&self) { + let Ok(mut last) = self.last.lock() else { + return; + }; + emit_if_changed(&self.app, &self.event, &self.kind, &mut last); + } +} + +/// Dereferences a C `void*` back into the boxed [`Fire`] context and fires it. +/// +/// # Safety +/// `context` must be the `*mut Box` handed to the OS during +/// registration; it stays valid until the monitor thread frees it after the run +/// loop stops. +unsafe fn fire_ctx(context: *mut c_void) { + let ctx = &*(context as *const Box); + // The platform callbacks below are `extern "C"`; a panic unwinding across that + // ABI boundary aborts the whole process, so contain it here. + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| ctx.fire())); +} + +unsafe extern "C" fn battery_callback(context: *mut c_void) { + fire_ctx(context); +} + +unsafe extern "C" fn display_callback( + _display: CGDirectDisplayID, + _flags: CGDisplayChangeSummaryFlags, + user_info: *mut c_void, +) { + fire_ctx(user_info); +} + +unsafe extern "C" fn network_callback( + _target: SCNetworkReachabilityRef, + _flags: SCNetworkReachabilityFlags, + info: *mut c_void, +) { + fire_ctx(info); +} + +unsafe extern "C" fn noop_timer(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} + +/// Performed on the monitor thread when `stop` signals the stop source. Because +/// it runs *inside* the run loop it can stop it race-free — even a signal that +/// arrives before `CFRunLoopRun` starts is honored the moment the loop runs. +unsafe extern "C" fn stop_perform(_info: *mut c_void) { + CFRunLoopStop(CFRunLoopGetCurrent()); +} + +// ---- Run-loop monitor handle ------------------------------------------------ + +/// Tracks the state of the monitor thread's run loop. +enum LoopState { + /// The thread has not finished setting up yet. + Pending, + /// The run loop is live; holds the `CFRunLoopRef` and the stop-source ref as + /// `usize`s (so they are `Send`) for `stop` to signal. + Running { run_loop: usize, stop_source: usize }, +} + +/// Handle to a macOS run-loop monitor. +struct MacRunLoopHandle { + state: Arc<(Mutex, Condvar)>, + join: Option>, +} + +impl MonitorHandle for MacRunLoopHandle { + fn stop(mut self: Box) { + let (lock, cvar) = &*self.state; + let mut guard = lock.lock().unwrap_or_else(|e| e.into_inner()); + while matches!(*guard, LoopState::Pending) { + guard = cvar.wait(guard).unwrap_or_else(|e| e.into_inner()); + } + if let LoopState::Running { + run_loop, + stop_source, + } = *guard + { + // SAFETY: signaling the stop source and waking the loop are thread-safe. + // The source performs on the monitor thread and calls `CFRunLoopStop` + // there, which is race-free even if the loop has not started running yet + // (a signaled version-0 source is serviced the moment the loop runs). + unsafe { + CFRunLoopSourceSignal(stop_source as CFRunLoopSourceRef); + CFRunLoopWakeUp(run_loop as CFRunLoopRef); + } + } + drop(guard); + + if let Some(join) = self.join.take() { + let _ = join.join(); + } + } +} + +// ---- Platform registration -------------------------------------------------- + +/// Undoes a registration; called on the monitor thread after the run loop stops. +type Teardown = Box; + +fn register_battery(run_loop: CFRunLoopRef, ctx: *mut c_void) -> Teardown { + // SAFETY: valid C callback and context; the source is removed and released in teardown. + let source = unsafe { IOPSNotificationCreateRunLoopSource(battery_callback, ctx) }; + if source.is_null() { + return Box::new(|| {}); + } + // SAFETY: adding our source to this thread's run loop in the default mode. + unsafe { CFRunLoopAddSource(run_loop, source, kCFRunLoopDefaultMode) }; + + let rl = run_loop as usize; + let src = source as usize; + Box::new(move || unsafe { + CFRunLoopRemoveSource( + rl as CFRunLoopRef, + src as CFRunLoopSourceRef, + kCFRunLoopDefaultMode, + ); + CFRelease(src as *const c_void); + }) +} + +fn register_display(_run_loop: CFRunLoopRef, ctx: *mut c_void) -> Teardown { + // SAFETY: registering a callback with our context; removed in teardown. + unsafe { CGDisplayRegisterReconfigurationCallback(display_callback, ctx) }; + let ctx_us = ctx as usize; + Box::new(move || unsafe { + CGDisplayRemoveReconfigurationCallback(display_callback, ctx_us as *mut c_void); + }) +} + +fn register_network(run_loop: CFRunLoopRef, ctx: *mut c_void) -> Teardown { + // Zeroed IPv4 address = general internet reachability; fires on route changes. + let addr = SockaddrIn { + sin_len: core::mem::size_of::() as u8, + sin_family: 2, // AF_INET + sin_port: 0, + sin_addr: 0, + sin_zero: [0; 8], + }; + + // SAFETY: passing a valid, stack-allocated sockaddr by const pointer. + let reach = unsafe { + SCNetworkReachabilityCreateWithAddress( + ptr::null(), + &addr as *const SockaddrIn as *const c_void, + ) + }; + if reach.is_null() { + eprintln!( + "device-info: SCNetworkReachabilityCreateWithAddress failed; \ + network watch will report the initial value only" + ); + return Box::new(|| {}); + } + + let mut context = SCNetworkReachabilityContext { + version: 0, + info: ctx, + retain: ptr::null(), + release: ptr::null(), + copyDescription: ptr::null(), + }; + + // SAFETY: `SetCallback` copies the context struct; scheduling on our run loop. + // Both return a boolean (0 = failure); a failed setup means no change events, + // so surface it rather than silently degrading to a one-shot value. + let ok = unsafe { + let set = SCNetworkReachabilitySetCallback(reach, network_callback, &mut context); + let sched = + SCNetworkReachabilityScheduleWithRunLoop(reach, run_loop, kCFRunLoopDefaultMode); + set != 0 && sched != 0 + }; + if !ok { + eprintln!( + "device-info: failed to schedule SCNetworkReachability callback; \ + network watch will report the initial value only" + ); + } + + let reach_us = reach as usize; + let rl = run_loop as usize; + Box::new(move || unsafe { + SCNetworkReachabilityUnscheduleFromRunLoop( + reach_us as SCNetworkReachabilityRef, + rl as CFRunLoopRef, + kCFRunLoopDefaultMode, + ); + CFRelease(reach_us as *const c_void); + }) +} + +// ---- Public entry point ----------------------------------------------------- + +/// Returns a native monitor for `kind` if macOS has one, else `Ok(None)` to poll. +pub(super) fn try_spawn( + app: &AppHandle, + kind: &str, +) -> crate::Result>> { + let register: fn(CFRunLoopRef, *mut c_void) -> Teardown = match kind { + "battery" => register_battery, + "display" => register_display, + "network" => register_network, + // storage and device have no OS change event; poll them. + _ => return Ok(None), + }; + Ok(Some(spawn_runloop(app, kind, register))) +} + +/// Spawns a dedicated run-loop thread, registers the OS source, and returns its handle. +fn spawn_runloop( + app: &AppHandle, + kind: &str, + register: fn(CFRunLoopRef, *mut c_void) -> Teardown, +) -> Box { + let ctx: Box = Box::new(EmitCtx { + app: app.clone(), + kind: kind.to_string(), + event: event_name(kind), + last: Mutex::new(None), + }); + // A thin pointer to the fat trait-object pointer, so it fits in a C `void*`. + let ctx_ptr = Box::into_raw(Box::new(ctx)) as usize; + + let state = Arc::new((Mutex::new(LoopState::Pending), Condvar::new())); + let state_thread = state.clone(); + + let join = std::thread::spawn(move || { + let ctx_void = ctx_ptr as *mut c_void; + let run_loop = unsafe { CFRunLoopGetCurrent() }; + + // Keep-alive timer so `CFRunLoopRun` does not exit when a source is idle. + // SAFETY: no-op callout, null context; timer is released after the run loop stops. + let timer = unsafe { + CFRunLoopTimerCreate( + ptr::null(), + CFAbsoluteTimeGetCurrent() + KEEPALIVE_INTERVAL, + KEEPALIVE_INTERVAL, + 0, + 0, + noop_timer, + ptr::null_mut(), + ) + }; + unsafe { CFRunLoopAddTimer(run_loop, timer, kCFRunLoopDefaultMode) }; + + // Stop source: `stop` signals this to make the loop stop *itself* from + // inside, which is race-free even if `stop` fires before `CFRunLoopRun`. + // SAFETY: version-0 source with a valid `perform`; removed and released below. + let mut src_ctx = CFRunLoopSourceContext { + version: 0, + info: ptr::null_mut(), + retain: ptr::null(), + release: ptr::null(), + copyDescription: ptr::null(), + equal: ptr::null(), + hash: ptr::null(), + schedule: ptr::null(), + cancel: ptr::null(), + perform: stop_perform, + }; + let stop_source = unsafe { CFRunLoopSourceCreate(ptr::null(), 0, &mut src_ctx) }; + unsafe { CFRunLoopAddSource(run_loop, stop_source, kCFRunLoopDefaultMode) }; + + // Platform-specific registration (returns its teardown). + let teardown = register(run_loop, ctx_void); + + // Publish the run loop + stop source so `stop` can wake it. + { + let (lock, cvar) = &*state_thread; + *lock.lock().unwrap_or_else(|e| e.into_inner()) = LoopState::Running { + run_loop: run_loop as usize, + stop_source: stop_source as usize, + }; + cvar.notify_all(); + } + + // Emit the current value immediately so a fresh subscriber gets initial state. + // SAFETY: `ctx_ptr` is valid until freed below, after the run loop stops. + unsafe { fire_ctx(ctx_void) }; + + // Blocks until the stop source performs and calls `CFRunLoopStop`. + unsafe { CFRunLoopRun() }; + + // Tear down OS registration, remove/release the stop source and timer, + // then free the context. + teardown(); + // SAFETY: the source and timer were created above and are no longer needed + // once the run loop has stopped. + unsafe { + CFRunLoopRemoveSource(run_loop, stop_source, kCFRunLoopDefaultMode); + CFRelease(stop_source); + CFRelease(timer); + }; + // SAFETY: `ctx_ptr` is no longer referenced by any OS callback after teardown. + unsafe { drop(Box::from_raw(ctx_ptr as *mut Box)) }; + }); + + Box::new(MacRunLoopHandle { + state, + join: Some(join), + }) +} diff --git a/src/watcher/polling.rs b/src/watcher/polling.rs new file mode 100644 index 0000000..6827764 --- /dev/null +++ b/src/watcher/polling.rs @@ -0,0 +1,104 @@ +//! Change-detecting poller. +//! +//! Reads the value on an interval and emits an event only when it differs from +//! the last emitted value. Used as the universal fallback for kinds/platforms +//! that have no native event source (storage always; anything not yet wired up +//! natively). +//! +//! The interval defaults are chosen **per kind**: some getters are far more +//! expensive than others (on macOS `device` shells out to `system_profiler`, +//! ~1-2s of CPU) and some values barely ever change. Polling those aggressively +//! would spawn a subprocess every couple of seconds for data that is effectively +//! static, so they default to — and are floored at — much longer intervals. + +use std::sync::{Arc, Condvar, Mutex}; +use std::time::Duration; + +use tauri::{AppHandle, Runtime}; + +use super::{emit_if_changed, event_name, MonitorHandle}; + +/// Default polling interval for a kind when the caller does not specify one. +/// +/// `device` almost never changes and is very expensive to read; `storage` +/// changes slowly. Everything else uses a responsive 2s default. +fn default_interval_ms(kind: &str) -> u64 { + match kind { + "device" => 60_000, + "storage" => 10_000, + _ => 2_000, + } +} + +/// Lower bound on the polling interval for a kind, so a caller cannot pin an +/// expensive getter to a tiny interval and peg a CPU core. +fn min_interval_ms(kind: &str) -> u64 { + match kind { + "device" => 10_000, + "storage" => 1_000, + _ => 250, + } +} + +/// Shared stop flag: the boolean guards the condition, the condvar lets `stop` +/// wake the sleeping poller immediately instead of it polling the flag. +type StopSignal = Arc<(Mutex, Condvar)>; + +/// Handle to a running poller thread. +pub(super) struct PollingHandle { + stop: StopSignal, +} + +impl MonitorHandle for PollingHandle { + fn stop(self: Box) { + let (lock, cvar) = &*self.stop; + *lock.lock().unwrap_or_else(|e| e.into_inner()) = true; + cvar.notify_all(); + } +} + +/// Spawns a poller thread for `kind` and returns its handle. +/// +/// The current value is emitted on the first iteration so a fresh subscriber +/// gets the initial state without waiting a full interval. +pub(super) fn spawn( + app: &AppHandle, + kind: &str, + interval_ms: Option, +) -> Box { + let interval = interval_ms + .unwrap_or_else(|| default_interval_ms(kind)) + .max(min_interval_ms(kind)); + let stop: StopSignal = Arc::new((Mutex::new(false), Condvar::new())); + + let app = app.clone(); + let kind = kind.to_string(); + let stop_flag = stop.clone(); + + std::thread::spawn(move || { + let event = event_name(&kind); + let interval = Duration::from_millis(interval); + let (lock, cvar) = &*stop_flag; + let mut last: Option = None; + + loop { + emit_if_changed(&app, &event, &kind, &mut last); + + // Sleep for the full interval, but wake the instant `stop` is called. + let guard = lock.lock().unwrap_or_else(|e| e.into_inner()); + if *guard { + break; + } + let (guard, _) = cvar + .wait_timeout(guard, interval) + .unwrap_or_else(|e| e.into_inner()); + let stopped = *guard; + drop(guard); + if stopped { + break; + } + } + }); + + Box::new(PollingHandle { stop }) +} From a77df687f545791cb418bf386b10cb52c92a91a1 Mon Sep 17 00:00:00 2001 From: edisdev Date: Mon, 24 Aug 2026 01:36:48 +0300 Subject: [PATCH 2/7] feat(js): add watch* bindings and WatchOptions/UnwatchFn types Expose the reactive watch API to JS: watchBattery, watchNetwork, watchStorage, watchDisplay, and watchDevice. Each listens on the per-kind change event, starts the backend monitor, and returns an async unwatch that stops it and removes the listener. If starting the backend fails, the event listener is removed so it does not leak. Add and export the WatchOptions (intervalMs) and UnwatchFn types. Co-Authored-By: Claude Opus 4.8 (1M context) --- guest-js/index.ts | 64 +++++++++++++++++++++++++++++++++++++++++++++-- guest-js/type.ts | 22 +++++++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/guest-js/index.ts b/guest-js/index.ts index 3deb235..e824e01 100644 --- a/guest-js/index.ts +++ b/guest-js/index.ts @@ -1,9 +1,10 @@ import { invoke } from '@tauri-apps/api/core' +import { listen } from '@tauri-apps/api/event' -import type { DeviceInfoResponse, BatteryInfo, NetworkInfo, StorageInfo, DisplayInfo } from './type' +import type { DeviceInfoResponse, BatteryInfo, NetworkInfo, StorageInfo, DisplayInfo, WatchOptions, UnwatchFn } from './type' // Re-export types for consumers -export type { DeviceInfoResponse, DeviceInfoResponse as DeviceInfo, BatteryInfo, NetworkInfo, StorageInfo, DisplayInfo } from './type' +export type { DeviceInfoResponse, DeviceInfoResponse as DeviceInfo, BatteryInfo, NetworkInfo, StorageInfo, DisplayInfo, WatchOptions, UnwatchFn } from './type' /** * Get comprehensive device information including UUID, manufacturer, model, etc. @@ -39,3 +40,62 @@ export async function getStorageInfo(): Promise { export async function getDisplayInfo(): Promise { return await invoke('plugin:device-info|get_display_info') } + +// ============================================================================ +// Reactive watch API +// ============================================================================ + +type WatchKind = 'battery' | 'network' | 'storage' | 'display' | 'device' + +/** + * Subscribes to a device-info kind and invokes `callback` whenever the value changes. + * The current value is delivered immediately, then on every change. + * Returns a function that stops watching and removes the listener. + */ +async function watch( + kind: WatchKind, + callback: (value: T) => void, + options?: WatchOptions +): Promise { + const unlisten = await listen(`device-info://${kind}-changed`, (event) => { + callback(event.payload) + }) + + try { + await invoke('plugin:device-info|start_watching', { kind, intervalMs: options?.intervalMs }) + } catch (error) { + // Don't leak the event listener if the backend failed to start the watcher. + unlisten() + throw error + } + + return async () => { + unlisten() + await invoke('plugin:device-info|stop_watching', { kind }) + } +} + +/** Watch battery status (level, charging state, health) for changes. */ +export function watchBattery(callback: (info: BatteryInfo) => void, options?: WatchOptions): Promise { + return watch('battery', callback, options) +} + +/** Watch network connectivity (IP, type, MAC) for changes — e.g. Wi-Fi ↔ offline. */ +export function watchNetwork(callback: (info: NetworkInfo) => void, options?: WatchOptions): Promise { + return watch('network', callback, options) +} + +/** Watch storage capacity (total/free space) for changes. */ +export function watchStorage(callback: (info: StorageInfo) => void, options?: WatchOptions): Promise { + return watch('storage', callback, options) +} + +/** Watch display properties (resolution, scale, refresh rate) for changes — e.g. plugging in a monitor. */ +export function watchDisplay(callback: (info: DisplayInfo) => void, options?: WatchOptions): Promise { + return watch('display', callback, options) +} + +/** Watch device identity fields for changes (rarely changes; useful on mobile). */ +export function watchDevice(callback: (info: DeviceInfoResponse) => void, options?: WatchOptions): Promise { + return watch('device', callback, options) +} diff --git a/guest-js/type.ts b/guest-js/type.ts index e397433..c6e2f25 100644 --- a/guest-js/type.ts +++ b/guest-js/type.ts @@ -30,4 +30,24 @@ export interface DisplayInfo { height?: number; scaleFactor?: number; refreshRate?: number; -} \ No newline at end of file +} + +/** Options for the reactive `watch*` APIs. */ +export interface WatchOptions { + /** + * Polling interval in milliseconds used to detect changes (only applies to + * kinds that fall back to polling; native event-driven kinds ignore it). + * + * Defaults and minimums are per kind, because some getters are expensive and + * some values rarely change: + * - `device`: defaults to 60000ms, floored at 10000ms (reads are costly and it barely changes) + * - `storage`: defaults to 10000ms, floored at 1000ms + * - others: default 2000ms, floored at 250ms + * + * Only the first subscriber for a given kind sets the interval. + */ + intervalMs?: number; +} + +/** Stops a watcher and removes its listener. Returned by every `watch*` function. */ +export type UnwatchFn = () => Promise; \ No newline at end of file From 1b031d9badc51b3b04c6dc3854f1484931490689 Mon Sep 17 00:00:00 2001 From: edisdev Date: Mon, 24 Aug 2026 01:38:14 +0300 Subject: [PATCH 3/7] feat(example): replace setInterval polling with reactive watchers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use watchBattery/watchNetwork/watchDisplay instead of a manual setInterval loop. The charging overlay now fires only on a real false → true transition (not on the initial value), and all watchers are unsubscribed on destroy. A failed start is surfaced as an error rather than left as an unhandled promise rejection. Co-Authored-By: Claude Opus 4.8 (1M context) --- examples/tauri-app/src/App.svelte | 49 ++++++++++++++++++------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/examples/tauri-app/src/App.svelte b/examples/tauri-app/src/App.svelte index 195e23d..28c5b7d 100644 --- a/examples/tauri-app/src/App.svelte +++ b/examples/tauri-app/src/App.svelte @@ -5,6 +5,9 @@ getNetworkInfo, getStorageInfo, getDisplayInfo, + watchBattery, + watchNetwork, + watchDisplay, } from "tauri-plugin-device-info-api"; import type { @@ -13,6 +16,7 @@ NetworkInfo, StorageInfo, DisplayInfo, + UnwatchFn, } from "tauri-plugin-device-info-api"; import { onMount, onDestroy } from "svelte"; @@ -34,7 +38,8 @@ let showChargingIcon = $state(false); let lastChargingState = false; - let pollingInterval: number | undefined; + let batteryInitialized = false; + let unwatchers: UnwatchFn[] = []; async function fetchData() { loading = true; @@ -45,11 +50,6 @@ networkInfo = await getNetworkInfo(); storageInfo = await getStorageInfo(); displayInfo = await getDisplayInfo(); - - // Initialize tracking state - if (batteryInfo) { - lastChargingState = batteryInfo.isCharging || false; - } } catch (e: any) { error = e.toString(); } finally { @@ -57,34 +57,41 @@ } } - function startBatteryPolling() { - pollingInterval = setInterval(async () => { - try { - const info = await getBatteryInfo(); - - // Check if charging started (transition from false to true) - if (info.isCharging && !lastChargingState) { + // Reactive watchers replace manual setInterval polling. + // On macOS these are native event-driven (IOKit / Core Graphics / + // SystemConfiguration); elsewhere they fall back to change-detecting polling. + async function startWatchers() { + unwatchers.push( + await watchBattery((info) => { + // Show the overlay only on a real false → true charging transition, + // never on the initial value delivered when the watcher starts. + if (batteryInitialized && info.isCharging && !lastChargingState) { showChargingIcon = true; setTimeout(() => { showChargingIcon = false; }, 5000); } - + batteryInitialized = true; lastChargingState = info.isCharging || false; - batteryInfo = info; // Update UI with latest info - } catch (e) { - console.error("Battery polling error:", e); - } - }, 1000); // Check every 1 second + batteryInfo = info; + }), + ); + unwatchers.push(await watchNetwork((info) => (networkInfo = info))); + unwatchers.push(await watchDisplay((info) => (displayInfo = info))); } onMount(() => { fetchData(); - startBatteryPolling(); + // startWatchers is async; surface a failed start instead of leaving an + // unhandled rejection (e.g. missing permission or backend error). + startWatchers().catch((e) => { + error = e?.toString?.() ?? String(e); + }); }); onDestroy(() => { - if (pollingInterval) clearInterval(pollingInterval); + unwatchers.forEach((unwatch) => unwatch()); + unwatchers = []; }); From 0255b9cffa472f12af0480769385ac8f84268c48 Mon Sep 17 00:00:00 2001 From: edisdev Date: Mon, 24 Aug 2026 01:39:11 +0300 Subject: [PATCH 4/7] docs: document the reactive watch API Add a dedicated Reactive Watch API reference page (functions, options, native-vs-polling behavior, per-kind intervals, permissions) and wire it into the sidebar and API overview. Update the README, getting-started guide, examples, and home page to cover watch* alongside the getters, and replace the manual setInterval example with a watch-based one. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 72 ++++++++++++++++++ docs/.vitepress/config.ts | 3 +- docs/api/index.md | 17 ++++- docs/api/watch.md | 154 ++++++++++++++++++++++++++++++++++++++ docs/examples.md | 34 ++++----- docs/getting-started.md | 24 ++++++ docs/index.md | 13 ++++ 7 files changed, 295 insertions(+), 22 deletions(-) create mode 100644 docs/api/watch.md diff --git a/README.md b/README.md index c63262f..eebe4d5 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ A comprehensive Tauri plugin to access device information including Battery, Net - 💾 **Storage** — total/free space and storage type - 🖥️ **Display** — resolution, scale factor, and refresh rate - 🆔 **Device** — manufacturer, model, serial, and device name +- 🔔 **Reactive** — `watch*` APIs push updates on change (native event-driven on macOS, polling fallback elsewhere) - 📱 **Truly cross-platform** — Windows, macOS, Linux, iOS, and Android - 🛡️ **Type-safe** — full TypeScript types and granular Tauri permissions - ✅ **Tested** — unit-tested core with CI across all desktop platforms @@ -193,6 +194,49 @@ console.log(display); } ``` +### Reactive Watch API + +Subscribe to a kind and get a callback whenever the value changes, instead of +polling a getter yourself. The current value is delivered immediately, then on +every change. On macOS these are native and event-driven (IOKit / Core Graphics +/ SystemConfiguration); elsewhere they fall back to change-detecting polling. + +```typescript +import { + watchBattery, + watchNetwork, + watchStorage, + watchDisplay, + watchDevice, +} from "tauri-plugin-device-info-api"; +import type { UnwatchFn } from "tauri-plugin-device-info-api"; + +// Called immediately with the current value, then on every change. +const unwatch = await watchBattery((battery) => { + console.log(`Battery: ${battery.level}% (charging: ${battery.isCharging})`); +}); + +// Stop watching and remove the listener when you're done. +await unwatch(); +``` + +Cleaning up several watchers at once (e.g. on component teardown): + +```typescript +const unwatchers: UnwatchFn[] = []; +unwatchers.push(await watchNetwork((info) => console.log(info.networkType))); +unwatchers.push(await watchDisplay((info) => console.log(info.width, info.height))); + +// Later: +await Promise.all(unwatchers.map((unwatch) => unwatch())); +``` + +`watch*` accepts an optional `{ intervalMs }` that applies **only to polled +kinds** (native kinds ignore it). Defaults and minimums are per kind: `device` +defaults to 60000 ms (floored at 10000 ms), `storage` to 10000 ms (floored at +1000 ms), and others to 2000 ms (floored at 250 ms). Subscribers to the same +kind share one monitor, so only the first subscriber's interval takes effect. + ## API Reference ### getDeviceInfo() @@ -249,6 +293,26 @@ Returns display/screen information. | `scaleFactor` | `number?` | Display scale factor (e.g., 2.0 for Retina) | | `refreshRate` | `number?` | Screen refresh rate in Hz | +### watch\*(callback, options?) + +Subscribes to a kind and invokes `callback` with the current value immediately, +then on every change. Returns a `Promise`; call the returned function +to stop watching and remove the listener. + +| Function | Callback payload | +| ----------------- | -------------------- | +| `watchDevice()` | `DeviceInfoResponse` | +| `watchBattery()` | `BatteryInfo` | +| `watchNetwork()` | `NetworkInfo` | +| `watchStorage()` | `StorageInfo` | +| `watchDisplay()` | `DisplayInfo` | + +**Options** + +| Field | Type | Description | +| ------------ | --------- | -------------------------------------------------------------------------------------------- | +| `intervalMs` | `number?` | Polling interval for polled kinds only (native kinds ignore it). Per-kind defaults/minimums. | + ## TypeScript Types All types are exported and can be imported: @@ -260,6 +324,8 @@ import type { NetworkInfo, StorageInfo, DisplayInfo, + WatchOptions, + UnwatchFn, } from "tauri-plugin-device-info-api"; ``` @@ -289,6 +355,8 @@ Add the required permissions in your `capabilities` configuration. | `device-info:allow-get-network-info` | Allows getting network details | | `device-info:allow-get-storage-info` | Allows getting storage info | | `device-info:allow-get-display-info` | Allows getting display info | +| `device-info:allow-start-watching` | Allows starting a reactive watch | +| `device-info:allow-stop-watching` | Allows stopping a reactive watch | ### Individual Permissions Example @@ -352,6 +420,10 @@ cargo clippy -- -D warnings yarn tsc --noEmit ``` +## Changelog + +See [CHANGELOG.md](CHANGELOG.md) for the release history and notable changes. + ## Contributing Contributions are welcome! Please read the [Contributing Guide](CONTRIBUTING.md) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 8268c84..a359e03 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -45,7 +45,8 @@ export default defineConfig({ { text: 'Battery Info', link: '/api/battery-info' }, { text: 'Network Info', link: '/api/network-info' }, { text: 'Storage Info', link: '/api/storage-info' }, - { text: 'Display Info', link: '/api/display-info' } + { text: 'Display Info', link: '/api/display-info' }, + { text: 'Reactive Watch', link: '/api/watch' } ] }, { diff --git a/docs/api/index.md b/docs/api/index.md index ad5e4d6..ea48eb9 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -1,6 +1,7 @@ # API Reference -This plugin provides 5 main functions to access device information. +This plugin provides 5 getters to read device information, plus a +[reactive watch API](/api/watch) to subscribe to changes. ## Functions Overview @@ -11,6 +12,7 @@ This plugin provides 5 main functions to access device information. | [`getNetworkInfo()`](/api/network-info) | Network connection details | | [`getStorageInfo()`](/api/storage-info) | Storage capacity information | | [`getDisplayInfo()`](/api/display-info) | Display properties and capabilities | +| [`watch*()`](/api/watch) | Subscribe to changes for any of the above kinds | ## Quick Reference @@ -20,7 +22,14 @@ import { getBatteryInfo, // → BatteryInfo getNetworkInfo, // → NetworkInfo getStorageInfo, // → StorageInfo - getDisplayInfo // → DisplayInfo + getDisplayInfo, // → DisplayInfo + + // Reactive watch API → Promise + watchDevice, + watchBattery, + watchNetwork, + watchStorage, + watchDisplay } from 'tauri-plugin-device-info-api'; ``` @@ -34,7 +43,9 @@ import type { BatteryInfo, NetworkInfo, StorageInfo, - DisplayInfo + DisplayInfo, + WatchOptions, + UnwatchFn } from 'tauri-plugin-device-info-api'; ``` diff --git a/docs/api/watch.md b/docs/api/watch.md new file mode 100644 index 0000000..0ad4394 --- /dev/null +++ b/docs/api/watch.md @@ -0,0 +1,154 @@ +# Reactive Watch API + +Instead of calling a getter on a timer, you can **subscribe** to a kind of +device information and receive a callback whenever the value changes. + +Each `watch*` function delivers the **current value immediately**, then invokes +your callback again on every subsequent change. It returns an async `unwatch` +function that stops watching and removes the listener. + +## Functions + +| Function | Delivers | Watches for | +|----------|----------|-------------| +| `watchBattery()` | `BatteryInfo` | Charge level, charging state, health | +| `watchNetwork()` | `NetworkInfo` | Connectivity changes (Wi-Fi ↔ offline, IP, MAC) | +| `watchStorage()` | `StorageInfo` | Total / free space | +| `watchDisplay()` | `DisplayInfo` | Resolution, scale, refresh rate (e.g. plugging in a monitor) | +| `watchDevice()` | `DeviceInfoResponse` | Device identity fields (rarely changes) | + +## Signatures + +```typescript +function watchBattery(callback: (info: BatteryInfo) => void, options?: WatchOptions): Promise +function watchNetwork(callback: (info: NetworkInfo) => void, options?: WatchOptions): Promise +function watchStorage(callback: (info: StorageInfo) => void, options?: WatchOptions): Promise +function watchDisplay(callback: (info: DisplayInfo) => void, options?: WatchOptions): Promise +function watchDevice(callback: (info: DeviceInfoResponse) => void, options?: WatchOptions): Promise +``` + +## Options & return type + +```typescript +interface WatchOptions { + /** + * Polling interval in milliseconds, used only for kinds that fall back to + * polling (native event-driven kinds ignore it). Only the first subscriber + * for a given kind sets the interval. + */ + intervalMs?: number; +} + +/** Stops a watcher and removes its listener. Returned by every `watch*` function. */ +type UnwatchFn = () => Promise; +``` + +## Basic usage + +```typescript +import { watchBattery } from 'tauri-plugin-device-info-api'; + +// Called immediately with the current value, then on every change. +const unwatch = await watchBattery((battery) => { + console.log(`Battery: ${battery.level}% (charging: ${battery.isCharging})`); +}); + +// Later, when you no longer need updates: +await unwatch(); +``` + +### Cleaning up multiple watchers + +Always stop watchers when the component/view is torn down, otherwise the +backend monitor stays alive. + +```typescript +import { watchBattery, watchNetwork, watchDisplay } from 'tauri-plugin-device-info-api'; +import type { UnwatchFn } from 'tauri-plugin-device-info-api'; + +const unwatchers: UnwatchFn[] = []; + +unwatchers.push(await watchBattery((info) => { /* ... */ })); +unwatchers.push(await watchNetwork((info) => { /* ... */ })); +unwatchers.push(await watchDisplay((info) => { /* ... */ })); + +// On teardown: +await Promise.all(unwatchers.map((unwatch) => unwatch())); +``` + +## How updates are detected + +The public API and the emitted events are identical across platforms, but the +engine behind each kind differs: + +- Where the OS exposes a **native, event-driven** source, updates are delivered + the instant the OS reports a change and the CPU stays idle in between. +- Otherwise a **change-detecting poller** reads the value on an interval and + emits only when it actually changed. + +| Kind | macOS | Windows / Linux | +|------|-------|-----------------| +| `battery` | Native — IOKit power notifications | Polled | +| `display` | Native — Core Graphics reconfiguration callback | Polled | +| `network` | Native — SystemConfiguration reachability | Polled | +| `storage` | Polled (no OS change event) | Polled | +| `device` | Polled (rarely changes) | Polled | + +### Polling intervals + +`intervalMs` only applies to polled kinds. Defaults and minimums are **per +kind**, because some getters are expensive and some values rarely change: + +| Kind | Default | Minimum | +|------|---------|---------| +| `device` | 60000 ms | 10000 ms | +| `storage` | 10000 ms | 1000 ms | +| others (when polled) | 2000 ms | 250 ms | + +```typescript +// Poll storage a little more eagerly (still floored at 1000 ms): +const unwatch = await watchStorage((info) => console.log(info.freeSpace), { + intervalMs: 2000, +}); +``` + +::: tip Reference counting +Subscribers to the same kind share a single monitor. It starts on the first +subscriber and is torn down once the last one calls `unwatch()`. Only the first +subscriber's `intervalMs` takes effect. +::: + +## Permissions + +The watch API requires the `start-watching` and `stop-watching` commands. +`device-info:default` already includes them; to grant them individually: + +```json +{ + "permissions": [ + "device-info:allow-start-watching", + "device-info:allow-stop-watching" + ] +} +``` + +## Example output + +`watchBattery` delivers the same shape as `getBatteryInfo`: + +```json +{ + "level": 85, + "isCharging": true, + "health": "Good" +} +``` + +## Notes + +- The current value is always delivered once, immediately, so you don't need a + separate `getBatteryInfo()` call to seed initial state. +- `unwatch()` is async — `await` it if you need the backend monitor fully torn + down before continuing. +- On a kind with a native source (e.g. `battery` on macOS), `intervalMs` is + ignored. diff --git a/docs/examples.md b/docs/examples.md index 58982f6..b28cb90 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -125,32 +125,30 @@ function formatBytes(bytes: number): string { {/if} ``` -## Polling for Updates +## Reactive Updates -For real-time battery monitoring: +Prefer the [`watch*` API](/api/watch) over manual polling — it pushes updates on +change (native event-driven on macOS, polling fallback elsewhere) and delivers +the current value immediately. ```typescript -import { getBatteryInfo } from 'tauri-plugin-device-info-api'; +import { watchBattery } from 'tauri-plugin-device-info-api'; -function startBatteryMonitor(callback: (level: number) => void) { - const interval = setInterval(async () => { - const battery = await getBatteryInfo(); - if (battery.level !== null) { - callback(battery.level); - } - }, 30000); // Update every 30 seconds - - return () => clearInterval(interval); -} - -// Usage -const stopMonitor = startBatteryMonitor((level) => { - console.log(`Battery level: ${level}%`); +// Called immediately with the current value, then on every change. +const stopMonitor = await watchBattery((battery) => { + console.log(`Battery level: ${battery.level}%`); }); -// Later: stopMonitor(); +// Later, when you no longer need updates: +await stopMonitor(); ``` +::: tip +Only reach for a manual `setInterval` + `getBatteryInfo()` loop if you need a +fixed cadence regardless of change. For "notify me when it changes", `watch*` is +both simpler and cheaper. +::: + ## Error Handling ```typescript diff --git a/docs/getting-started.md b/docs/getting-started.md index b9d072c..f3b33a6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -56,6 +56,8 @@ Or add individual permissions: - `device-info:allow-get-network-info` - `device-info:allow-get-storage-info` - `device-info:allow-get-display-info` +- `device-info:allow-start-watching` +- `device-info:allow-stop-watching` ## Basic Usage @@ -96,6 +98,26 @@ async function loadDeviceInfo() { } ``` +## Reactive Updates + +Instead of calling a getter on a timer, subscribe to a kind and get a callback +whenever the value changes. The current value is delivered immediately, then on +every change. On macOS these are native and event-driven; elsewhere they fall +back to change-detecting polling. + +```typescript +import { watchBattery } from "tauri-plugin-device-info-api"; + +const unwatch = await watchBattery((battery) => { + console.log(`Battery: ${battery.level}% (charging: ${battery.isCharging})`); +}); + +// Stop watching when you're done (e.g. on component teardown): +await unwatch(); +``` + +See the [Reactive Watch API](/api/watch) for all `watch*` functions and options. + ## TypeScript Types All types are exported and can be imported: @@ -107,6 +129,8 @@ import type { NetworkInfo, StorageInfo, DisplayInfo, + WatchOptions, + UnwatchFn, } from "tauri-plugin-device-info-api"; ``` diff --git a/docs/index.md b/docs/index.md index 89221a1..7f2d504 100644 --- a/docs/index.md +++ b/docs/index.md @@ -29,6 +29,9 @@ features: - icon: 🖥️ title: Display Info details: Resolution, scale factor, and refresh rate + - icon: 🔔 + title: Reactive Watch + details: Subscribe to changes — native event-driven on macOS, polling fallback elsewhere - icon: 🌍 title: Cross-platform details: Works on Windows, macOS, Linux, iOS, and Android @@ -77,3 +80,13 @@ console.log(`Battery: ${battery.level}%`); const network = await getNetworkInfo(); console.log(`IP: ${network.ipAddress}`); ``` + +Prefer push over polling? Subscribe to changes with the +[reactive watch API](/api/watch): + +```typescript +import { watchBattery } from 'tauri-plugin-device-info-api'; + +const unwatch = await watchBattery((b) => console.log(`Battery: ${b.level}%`)); +// await unwatch(); // stop when done +``` From 1ed054a9a6bc0bd9f39d1a5d7d54f46c43424d08 Mon Sep 17 00:00:00 2001 From: edisdev Date: Mon, 24 Aug 2026 01:43:02 +0300 Subject: [PATCH 5/7] chore(release): bump to 1.1.0 and add changelog Minor bump for the backwards-compatible reactive watch API. Add a dev-dependency on tauri's test feature for the watcher lifecycle tests, and introduce CHANGELOG.md documenting the 1.1.0 release. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 +++- package.json | 2 +- 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8d10b1c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,69 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.1.0] - 2026-08-24 + +### Added + +- **Reactive Watch API** — subscribe to a kind of device information and receive + a callback whenever the value changes, instead of polling a getter yourself. + The current value is delivered immediately, then on every change. + - `watchBattery`, `watchNetwork`, `watchStorage`, `watchDisplay`, and + `watchDevice`, each returning a `Promise`. + - `WatchOptions` (`intervalMs`) and `UnwatchFn` types, exported for consumers. + - `start_watching` / `stop_watching` commands, with matching + `allow-start-watching` and `allow-stop-watching` permissions (included in + `device-info:default`). +- **Native, event-driven monitors on macOS** — battery via IOKit power + notifications, display via Core Graphics reconfiguration callbacks, and + network via SystemConfiguration reachability. The CPU stays idle between + changes and updates arrive the instant the OS reports them. +- **Change-detecting polling fallback** for kinds/platforms without a native + event source (`storage` and `device` everywhere; all kinds off macOS). Events + are emitted only when the value actually changes. +- Reference-counted monitors: subscribers to the same kind share a single + monitor that starts on the first subscriber and is torn down on the last. +- Documentation for the watch API (API reference page, guide section, examples) + and unit tests covering the monitor lifecycle for every kind. + +### Performance + +- Per-kind polling defaults and floors so expensive getters are not polled + aggressively: `device` defaults to 60000 ms (min 10000 ms), `storage` to + 10000 ms (min 1000 ms), others to 2000 ms (min 250 ms). Notably, `device` + (which shells out to `system_profiler` on macOS and rarely changes) is no + longer read every couple of seconds. +- The poller now sleeps for the full interval on a condition variable and wakes + immediately on stop, instead of busy-waking every 100 ms — letting the CPU + idle and cooperating with power-saving. +- Change events are emitted by reference, avoiding a deep clone of each snapshot. + +### Fixed + +- Removed a run-loop teardown race on macOS that could hang `stop_watching` + indefinitely when a watcher was stopped just after starting; stop is now + signalled through the run loop itself and is race-free. +- `stop_watching` no longer holds the shared watcher lock while tearing down a + monitor, so a slow teardown can't block subscribe/unsubscribe for other kinds. +- Contained panics inside the macOS OS callbacks so they can no longer unwind + across the C ABI boundary and abort the process. +- The example app surfaces watcher start failures instead of leaving an + unhandled promise rejection. + +## [1.0.1] + +- Documentation updates, crates.io metadata, and a Tests CI badge. + +## [1.0.0] + +- Initial release: `getDeviceInfo`, `getBatteryInfo`, `getNetworkInfo`, + `getStorageInfo`, and `getDisplayInfo` across Windows, macOS, Linux, iOS, and + Android. + +[1.1.0]: https://github.com/edisdev/tauri-plugin-device-info/releases/tag/v1.1.0 +[1.0.1]: https://github.com/edisdev/tauri-plugin-device-info/releases/tag/v1.0.1 +[1.0.0]: https://github.com/edisdev/tauri-plugin-device-info/releases/tag/v1.0.0 diff --git a/Cargo.toml b/Cargo.toml index e56b20a..10bd739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-plugin-device-info" -version = "1.0.1" +version = "1.1.0" authors = ["edisdev "] description = "A Tauri plugin to access device information." license = "MIT" @@ -38,3 +38,6 @@ battery = "0.7.8" [build-dependencies] tauri-plugin = { version = "2.5.2", features = ["build"] } + +[dev-dependencies] +tauri = { version = "2.7.0", features = ["test"] } diff --git a/package.json b/package.json index 256b678..73608b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tauri-plugin-device-info-api", - "version": "1.0.1", + "version": "1.1.0", "author": "edisdev", "description": "A Tauri plugin to access device information.", "license": "MIT", From 9cc054541048385c38c2477cab39cee651e12c78 Mon Sep 17 00:00:00 2001 From: edisdev Date: Mon, 24 Aug 2026 02:05:10 +0300 Subject: [PATCH 6/7] style: satisfy cargo fmt in watcher tests Fixes the `cargo fmt -- --check` CI failure (comment indentation in the reference-counting test). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/watcher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/watcher.rs b/src/watcher.rs index 91196a4..f725424 100644 --- a/src/watcher.rs +++ b/src/watcher.rs @@ -212,7 +212,7 @@ mod tests { start(app.handle(), "battery", None).unwrap(); stop(app.handle(), "battery").unwrap(); // one subscriber left stop(app.handle(), "battery").unwrap(); // last subscriber → tears down - // Extra stop on an already-removed kind must be a no-op, not a panic. + // Extra stop on an already-removed kind must be a no-op, not a panic. stop(app.handle(), "battery").unwrap(); } From a0eb107989d0701853f46725e79439e2e74ca6d8 Mon Sep 17 00:00:00 2001 From: edisdev Date: Mon, 24 Aug 2026 02:14:28 +0300 Subject: [PATCH 7/7] fix(ci): don't link Tauri's test runtime on Windows The mock-runtime test binary crashed on load on windows-latest with STATUS_ENTRYPOINT_NOT_FOUND (0xc0000139) before any test ran. Enable Tauri's `test` feature only off Windows (target-gated dev-dependency) and cfg-gate the three mock-based watcher tests to match. The pure-logic tests still run on every platform. Co-Authored-By: Claude Opus 4.8 (1M context) --- Cargo.toml | 6 +++++- src/watcher.rs | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 10bd739..f17041e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,5 +39,9 @@ battery = "0.7.8" [build-dependencies] tauri-plugin = { version = "2.5.2", features = ["build"] } -[dev-dependencies] +# Tauri's `test` feature (the mock runtime) fails to load on Windows CI with +# STATUS_ENTRYPOINT_NOT_FOUND (0xc0000139), so enable it only off Windows. The +# mock-based watcher tests are gated to match; the pure-logic tests still run +# on every platform. +[target.'cfg(not(target_os = "windows"))'.dev-dependencies] tauri = { version = "2.7.0", features = ["test"] } diff --git a/src/watcher.rs b/src/watcher.rs index f725424..138c798 100644 --- a/src/watcher.rs +++ b/src/watcher.rs @@ -185,6 +185,10 @@ mod tests { } } + // The tests below use Tauri's mock runtime, which is only available off + // Windows here (see the target-gated dev-dependency in Cargo.toml). + + #[cfg(not(target_os = "windows"))] #[test] fn unknown_kind_is_rejected() { let app = mock_app(); @@ -195,6 +199,7 @@ mod tests { /// Exercises the full monitor lifecycle (spawn → emit → stop → join → free) /// for every kind, including the native macOS event-driven paths. Catches /// FFI signature mistakes, use-after-free, and stop deadlocks at runtime. + #[cfg(not(target_os = "windows"))] #[test] fn start_and_stop_every_kind_does_not_crash() { let app = mock_app(); @@ -205,6 +210,7 @@ mod tests { } } + #[cfg(not(target_os = "windows"))] #[test] fn reference_counting_keeps_monitor_until_last_unsubscribe() { let app = mock_app(); @@ -216,6 +222,7 @@ mod tests { stop(app.handle(), "battery").unwrap(); } + #[cfg(not(target_os = "windows"))] fn mock_app() -> tauri::App { tauri::test::mock_builder() .plugin(crate::init())