From 3a13ee43510d00e3df7e679e6f674f76123685cf Mon Sep 17 00:00:00 2001 From: lee-sihun Date: Sat, 21 Feb 2026 01:12:21 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=B4=EB=93=9C=20?= =?UTF-8?q?=EC=97=94=EC=A7=84=20(windows)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.lock | 444 ++++++++++++++++++++++++++- src-tauri/Cargo.toml | 3 + src-tauri/src/app_state.rs | 31 ++ src-tauri/src/commands/key_sound.rs | 37 +++ src-tauri/src/commands/mod.rs | 1 + src-tauri/src/key_sound.rs | 458 ++++++++++++++++++++++++++++ src-tauri/src/lib.rs | 1 + src-tauri/src/main.rs | 6 + 8 files changed, 967 insertions(+), 14 deletions(-) create mode 100644 src-tauri/src/commands/key_sound.rs create mode 100644 src-tauri/src/key_sound.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 113b617..7dad203 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -43,6 +43,28 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.10.0", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -58,6 +80,12 @@ version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "ashpd" version = "0.8.1" @@ -282,6 +310,24 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.111", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -487,6 +533,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfb" version = "0.7.3" @@ -542,6 +597,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.9", +] + [[package]] name = "cocoa" version = "0.22.0" @@ -705,6 +771,49 @@ dependencies = [ "libc", ] +[[package]] +name = "coreaudio-rs" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys 0.8.7", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceec7a6067e62d6f931a2baf6f3a751f4a892595bcec1461a3c94ef9949864b6" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.7", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk 0.8.0", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.54.0", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -820,6 +929,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + [[package]] name = "deranged" version = "0.5.5" @@ -967,21 +1082,24 @@ dependencies = [ "rdev", "reqwest", "rfd", + "rodio", "self-replace", "semver", "serde", "serde_json", "sha2", + "symphonia", "tauri", "tauri-build", "tauri-plugin-single-instance", "tauri-runtime-wry", "tempfile", "thiserror 1.0.69", + "thread-priority", "uuid", "walkdir", "webp-animation", - "windows", + "windows 0.61.3", "zip", ] @@ -1021,6 +1139,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "embed-resource" version = "3.0.6" @@ -1041,6 +1165,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.1" @@ -1116,6 +1249,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + [[package]] name = "fastrand" version = "2.3.0" @@ -2085,6 +2224,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -2247,7 +2395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading", + "libloading 0.7.4", "once_cell", ] @@ -2267,6 +2415,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + [[package]] name = "libredox" version = "0.1.11" @@ -2330,6 +2488,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2391,6 +2558,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2445,6 +2618,20 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys 0.5.0+25.2.9519653", + "num_enum", + "thiserror 1.0.69", +] + [[package]] name = "ndk" version = "0.9.0" @@ -2454,7 +2641,7 @@ dependencies = [ "bitflags 2.10.0", "jni-sys", "log", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", "thiserror 1.0.69", @@ -2466,6 +2653,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -2500,6 +2696,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notify" version = "6.1.1" @@ -2536,6 +2742,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2808,6 +3025,29 @@ dependencies = [ "objc", ] +[[package]] +name = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni", + "ndk 0.8.0", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +dependencies = [ + "cc", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -3625,6 +3865,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rodio" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb" +dependencies = [ + "cpal", + "thiserror 1.0.69", +] + [[package]] name = "rustc-hash" version = "2.1.1" @@ -4060,7 +4310,7 @@ checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" dependencies = [ "bytemuck", "js-sys", - "ndk", + "ndk 0.9.0", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -4160,6 +4410,129 @@ dependencies = [ "serde_json", ] +[[package]] +name = "symphonia" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" +dependencies = [ + "lazy_static", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-isomp4", + "symphonia-format-ogg", + "symphonia-format-riff", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f" +dependencies = [ + "extended", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" version = "1.0.109" @@ -4236,9 +4609,9 @@ dependencies = [ "lazy_static", "libc", "log", - "ndk", + "ndk 0.9.0", "ndk-context", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "objc2", "objc2-app-kit", "objc2-foundation", @@ -4249,7 +4622,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", @@ -4321,7 +4694,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows", + "windows 0.61.3", ] [[package]] @@ -4424,7 +4797,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", ] [[package]] @@ -4450,7 +4823,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", "wry", ] @@ -4567,6 +4940,20 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "thread-priority" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe075d7053dae61ac5413a34ea7d4913b6e6207844fd726bdd858b37ff72bf5" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "libc", + "log", + "rustversion", + "winapi", +] + [[package]] name = "time" version = "0.3.44" @@ -5236,7 +5623,7 @@ checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-implement", "windows-interface", @@ -5260,7 +5647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ "thiserror 2.0.17", - "windows", + "windows 0.61.3", "windows-core 0.61.2", ] @@ -5316,6 +5703,16 @@ dependencies = [ "windows-version", ] +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core 0.54.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows" version = "0.61.3" @@ -5338,6 +5735,16 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -5419,6 +5826,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -5831,7 +6247,7 @@ dependencies = [ "jni", "kuchikiki", "libc", - "ndk", + "ndk 0.9.0", "objc2", "objc2-app-kit", "objc2-core-foundation", @@ -5849,7 +6265,7 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a21bdfc..86addcb 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,6 +33,8 @@ gif = "0.13.3" gif-dispose = "5.0.1" sha2 = "0.10" webp-animation = "0.9.0" +rodio = { version = "0.19", default-features = false } +symphonia = { version = "0.5", default-features = false, features = ["ogg", "vorbis", "wav", "mp3", "isomp4", "aac"] } [target."cfg(windows)".dependencies] windows = { version = "0.61.3", features = [ @@ -48,6 +50,7 @@ windows = { version = "0.61.3", features = [ "Win32_Security", "Win32_UI_Shell" ] } +thread-priority = "1.2" reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] } semver = "1" self-replace = "1.5" diff --git a/src-tauri/src/app_state.rs b/src-tauri/src/app_state.rs index 1b9aef5..5afe92a 100644 --- a/src-tauri/src/app_state.rs +++ b/src-tauri/src/app_state.rs @@ -23,6 +23,7 @@ use tauri::{ use tauri_runtime_wry::wry::dpi::{LogicalPosition, LogicalSize}; use crate::{ + key_sound::{KeySoundEngine, KeySoundStatus}, keyboard::KeyboardManager, models::{ overlay_resize_anchor_from_str, BootstrapOverlayState, BootstrapPayload, KeyCounters, @@ -52,6 +53,7 @@ pub struct AppState { active_keys: Arc>>, /// Raw input stream subscriber count - emit only when > 0 raw_input_subscribers: Arc, + key_sound: Arc, /// CSS 파일 핫리로딩 워처 css_watcher: RwLock>, } @@ -68,6 +70,7 @@ impl AppState { Self::sync_counters_with_keys_impl(&key_counters, &snapshot.keys); let key_counter_enabled = Arc::new(AtomicBool::new(snapshot.key_counter_enabled)); let active_keys = Arc::new(RwLock::new(HashSet::new())); + let key_sound = Arc::new(KeySoundEngine::new()); Ok(Self { store, @@ -80,6 +83,7 @@ impl AppState { key_counter_enabled, active_keys, raw_input_subscribers: Arc::new(std::sync::atomic::AtomicU32::new(0)), + key_sound, css_watcher: RwLock::new(None), }) } @@ -695,6 +699,11 @@ impl AppState { if !state_changed { continue; } + if message.device == crate::ipc::InputDeviceKind::Keyboard + && state == "DOWN" + { + app_state.key_sound.play_labels(&message.labels); + } let payload = json!({ "key": key_label, "state": state, "mode": mode }); let mut emitted = false; @@ -1185,6 +1194,28 @@ impl AppState { self.raw_input_subscribers.load(Ordering::Relaxed) } + pub fn key_sound_status(&self) -> KeySoundStatus { + self.key_sound.status() + } + + pub fn key_sound_set_enabled(&self, enabled: bool) -> KeySoundStatus { + self.key_sound.set_enabled(enabled) + } + + pub fn key_sound_set_volume(&self, volume: f32) -> KeySoundStatus { + self.key_sound.set_volume(volume) + } + + pub fn key_sound_load_soundpack(&self, soundpack_dir: &str) -> Result { + self.key_sound + .load_soundpack_dir(soundpack_dir) + .map_err(|err| err.to_string()) + } + + pub fn key_sound_unload_soundpack(&self) -> KeySoundStatus { + self.key_sound.unload_soundpack() + } + // ========== CSS 핫리로딩 관련 메서드 ========== /// CSS 워처 초기화 diff --git a/src-tauri/src/commands/key_sound.rs b/src-tauri/src/commands/key_sound.rs new file mode 100644 index 0000000..c3959f0 --- /dev/null +++ b/src-tauri/src/commands/key_sound.rs @@ -0,0 +1,37 @@ +use tauri::State; + +use crate::{app_state::AppState, key_sound::KeySoundStatus}; + +#[tauri::command(permission = "dmnote-allow-all")] +pub fn key_sound_get_status(state: State<'_, AppState>) -> Result { + Ok(state.key_sound_status()) +} + +#[tauri::command(permission = "dmnote-allow-all")] +pub fn key_sound_set_enabled( + state: State<'_, AppState>, + enabled: bool, +) -> Result { + Ok(state.key_sound_set_enabled(enabled)) +} + +#[tauri::command(permission = "dmnote-allow-all")] +pub fn key_sound_set_volume( + state: State<'_, AppState>, + volume: f32, +) -> Result { + Ok(state.key_sound_set_volume(volume)) +} + +#[tauri::command(permission = "dmnote-allow-all")] +pub fn key_sound_load_soundpack( + state: State<'_, AppState>, + soundpack_dir: String, +) -> Result { + state.key_sound_load_soundpack(&soundpack_dir) +} + +#[tauri::command(permission = "dmnote-allow-all")] +pub fn key_sound_unload_soundpack(state: State<'_, AppState>) -> Result { + Ok(state.key_sound_unload_soundpack()) +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 7645a5c..2a2b13a 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -5,6 +5,7 @@ pub mod font; pub mod graph_items; pub mod image; pub mod js; +pub mod key_sound; pub mod keys; pub mod overlay; pub mod plugin_storage; diff --git a/src-tauri/src/key_sound.rs b/src-tauri/src/key_sound.rs new file mode 100644 index 0000000..622a951 --- /dev/null +++ b/src-tauri/src/key_sound.rs @@ -0,0 +1,458 @@ +use std::{ + collections::HashMap, + fs::File, + path::Path, + sync::{ + mpsc::{self, Receiver, Sender}, + Arc, + }, + thread, + time::Duration, +}; + +use anyhow::{Context, Result}; +use parking_lot::RwLock; +use rodio::{OutputStream, PlayError, Sink, Source}; +use serde::{Deserialize, Serialize}; +use symphonia::{ + core::{ + audio::SampleBuffer, + codecs::Decoder, + formats::{FormatOptions, FormatReader, SeekMode, SeekTo}, + io::MediaSourceStream, + probe::Hint, + units::TimeBase, + }, + default::{get_codecs, get_probe}, +}; + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KeySoundStatus { + pub enabled: bool, + pub volume: f32, + pub loaded: bool, + pub soundpack_dir: Option, + pub mapped_labels: usize, +} + +impl Default for KeySoundStatus { + fn default() -> Self { + Self { + enabled: false, + volume: 1.0, + loaded: false, + soundpack_dir: None, + mapped_labels: 0, + } + } +} + +#[derive(Debug, Clone)] +struct KeySoundRuntimeState { + status: KeySoundStatus, + soundpack: Option>, +} + +enum AudioCommand { + PlayLabels(Vec), + SetEnabled(bool), + SetVolume(f32), + SetSoundpack(Option>), +} + +pub struct KeySoundEngine { + sender: Sender, + state: Arc>, +} + +impl Default for KeySoundEngine { + fn default() -> Self { + Self::new() + } +} + +impl KeySoundEngine { + pub fn new() -> Self { + let (sender, receiver) = mpsc::channel(); + let state = Arc::new(RwLock::new(KeySoundRuntimeState { + status: KeySoundStatus::default(), + soundpack: None, + })); + let state_for_thread = state.clone(); + + thread::spawn(move || audio_thread(receiver, state_for_thread)); + + Self { sender, state } + } + + pub fn status(&self) -> KeySoundStatus { + self.state.read().status.clone() + } + + pub fn set_enabled(&self, enabled: bool) -> KeySoundStatus { + { + let mut guard = self.state.write(); + guard.status.enabled = enabled; + } + let _ = self.sender.send(AudioCommand::SetEnabled(enabled)); + self.status() + } + + pub fn set_volume(&self, volume: f32) -> KeySoundStatus { + let volume = volume.clamp(0.0, 1.0); + { + let mut guard = self.state.write(); + guard.status.volume = volume; + } + let _ = self.sender.send(AudioCommand::SetVolume(volume)); + self.status() + } + + pub fn load_soundpack_dir

(&self, soundpack_dir: P) -> Result + where + P: AsRef, + { + let dir = soundpack_dir.as_ref().to_path_buf(); + let pack = Arc::new(LoadedSoundpack::from_dir(&dir)?); + + { + let mut guard = self.state.write(); + guard.status.loaded = true; + guard.status.soundpack_dir = Some(dir.to_string_lossy().to_string()); + guard.status.mapped_labels = pack.segments.len(); + guard.soundpack = Some(pack.clone()); + } + + let _ = self.sender.send(AudioCommand::SetSoundpack(Some(pack))); + Ok(self.status()) + } + + pub fn unload_soundpack(&self) -> KeySoundStatus { + { + let mut guard = self.state.write(); + guard.status.loaded = false; + guard.status.soundpack_dir = None; + guard.status.mapped_labels = 0; + guard.soundpack = None; + } + let _ = self.sender.send(AudioCommand::SetSoundpack(None)); + self.status() + } + + pub fn play_labels(&self, labels: &[String]) { + if labels.is_empty() { + return; + } + if !self.state.read().status.enabled { + return; + } + + let _ = self + .sender + .send(AudioCommand::PlayLabels(labels.to_vec())); + } +} + +fn audio_thread( + receiver: Receiver, + state: Arc>, +) { + #[cfg(target_os = "windows")] + { + use thread_priority::{set_current_thread_priority, ThreadPriority}; + let _ = set_current_thread_priority(ThreadPriority::Max); + } + + let mut enabled = state.read().status.enabled; + let mut volume = state.read().status.volume; + let mut soundpack = state.read().soundpack.clone(); + let mut stream_handler = OutputStream::try_default().ok(); + + while let Ok(command) = receiver.recv() { + match command { + AudioCommand::SetEnabled(value) => { + enabled = value; + } + AudioCommand::SetVolume(value) => { + volume = value; + } + AudioCommand::SetSoundpack(pack) => { + soundpack = pack; + } + AudioCommand::PlayLabels(labels) => { + if !enabled { + continue; + } + let Some(pack) = soundpack.as_ref() else { + continue; + }; + let Some(source) = pack.source_for_labels(&labels) else { + continue; + }; + + if stream_handler.is_none() { + stream_handler = OutputStream::try_default().ok(); + } + + let Some(sink) = build_sink(&mut stream_handler) else { + continue; + }; + + sink.set_volume(volume); + sink.append(source); + sink.detach(); + } + } + } +} + +fn build_sink( + stream_handler: &mut Option<(OutputStream, rodio::OutputStreamHandle)>, +) -> Option { + let handler = stream_handler.as_ref()?; + match Sink::try_new(&handler.1) { + Ok(sink) => Some(sink), + Err(PlayError::NoDevice) => { + *stream_handler = OutputStream::try_default().ok(); + stream_handler + .as_ref() + .and_then(|new_handler| Sink::try_new(&new_handler.1).ok()) + } + Err(_) => None, + } +} + +#[derive(Debug)] +struct LoadedSoundpack { + segments: HashMap>, + fallback: Option>, + channels: u16, + sample_rate: u32, +} + +impl LoadedSoundpack { + fn from_dir(soundpack_dir: &Path) -> Result { + let config_path = soundpack_dir.join("config.json"); + let config: SoundpackConfig = + serde_json::from_reader(File::open(&config_path).with_context(|| { + format!("failed to open soundpack config: {}", config_path.display()) + })?) + .context("failed to parse soundpack config.json")?; + + let audio_path = soundpack_dir.join(&config.audio_file); + let mut decoder = SoundDecoder::new(&audio_path)?; + let mut decoded_by_range: HashMap<(u64, u64), Arc<[i16]>> = HashMap::new(); + let mut segments = HashMap::new(); + + for (label, [start_ms, duration_ms]) in config.defines { + let cache_key = (start_ms, duration_ms); + let samples = if let Some(existing) = decoded_by_range.get(&cache_key) { + existing.clone() + } else { + let decoded = decoder.get_samples_buf(start_ms, duration_ms)?; + let shared: Arc<[i16]> = Arc::from(decoded.into_boxed_slice()); + decoded_by_range.insert(cache_key, shared.clone()); + shared + }; + segments.insert(normalize_label(&label), samples); + } + + let fallback = if let Some([start_ms, duration_ms]) = config.fallback { + let cache_key = (start_ms, duration_ms); + if let Some(existing) = decoded_by_range.get(&cache_key) { + Some(existing.clone()) + } else { + let decoded = decoder.get_samples_buf(start_ms, duration_ms)?; + let shared: Arc<[i16]> = Arc::from(decoded.into_boxed_slice()); + decoded_by_range.insert(cache_key, shared.clone()); + Some(shared) + } + } else { + None + }; + + Ok(Self { + segments, + fallback, + channels: decoder.channels, + sample_rate: decoder.sample_rate, + }) + } + + fn source_for_labels(&self, labels: &[String]) -> Option { + for label in labels { + let normalized = normalize_label(label); + if let Some(samples) = self.segments.get(&normalized) { + return Some(AudioSource::new( + samples.clone(), + self.channels, + self.sample_rate, + )); + } + } + + self.fallback + .as_ref() + .map(|samples| AudioSource::new(samples.clone(), self.channels, self.sample_rate)) + } +} + +fn normalize_label(label: &str) -> String { + label.trim().to_ascii_uppercase() +} + +#[derive(Debug, Deserialize)] +struct SoundpackConfig { + #[serde(default = "default_audio_file")] + audio_file: String, + defines: HashMap, + #[serde(default)] + fallback: Option<[u64; 2]>, +} + +fn default_audio_file() -> String { + "sound.ogg".to_string() +} + +#[derive(Clone, Debug)] +struct AudioSource { + samples: Arc<[i16]>, + channels: u16, + sample_rate: u32, + pos: usize, +} + +impl AudioSource { + fn new(samples: Arc<[i16]>, channels: u16, sample_rate: u32) -> Self { + Self { + samples, + channels, + sample_rate, + pos: 0, + } + } +} + +impl Iterator for AudioSource { + type Item = i16; + + fn next(&mut self) -> Option { + let value = self.samples.get(self.pos)?; + self.pos += 1; + Some(*value) + } +} + +impl Source for AudioSource { + fn current_frame_len(&self) -> Option { + None + } + + fn channels(&self) -> u16 { + self.channels + } + + fn sample_rate(&self) -> u32 { + self.sample_rate + } + + fn total_duration(&self) -> Option { + None + } +} + +struct SoundDecoder { + decoder: Box, + format: Box, + time_base: TimeBase, + sample_rate: u32, + channels: u16, +} + +impl SoundDecoder { + fn new(path: &Path) -> Result { + let file = File::open(path) + .with_context(|| format!("failed to open sound file: {}", path.display()))?; + let media_source = MediaSourceStream::new(Box::new(file), Default::default()); + + let mut hint = Hint::new(); + if let Some(ext) = path.extension().and_then(|value| value.to_str()) { + hint.with_extension(ext); + } + + let probe = get_probe() + .format( + &hint, + media_source, + &FormatOptions::default(), + &Default::default(), + ) + .context("failed to probe sound file format")?; + let format = probe.format; + let track = format.default_track().context("no default track in sound file")?; + let decoder = get_codecs() + .make(&track.codec_params, &Default::default()) + .context("failed to create audio decoder")?; + + let (sample_rate, channels, time_base) = { + let params = decoder.codec_params(); + ( + params + .sample_rate + .context("missing sample rate in sound file")?, + params + .channels + .map(|v| v.count() as u16) + .context("missing channels in sound file")?, + params.time_base.context("missing time base in sound file")?, + ) + }; + + Ok(Self { + decoder, + format, + time_base, + sample_rate, + channels, + }) + } + + fn get_samples_buf(&mut self, start_ms: u64, duration_ms: u64) -> Result> { + self.format + .seek( + SeekMode::Accurate, + SeekTo::Time { + track_id: None, + time: Duration::from_millis(start_ms).into(), + }, + ) + .context("failed to seek sound file")?; + self.decoder.reset(); + + let mut decoded_duration_ms = 0_u64; + let mut samples = Vec::new(); + + while decoded_duration_ms < duration_ms { + let packet = self + .format + .next_packet() + .context("failed to fetch audio packet")?; + + let packet_time = self.time_base.calc_time(packet.dur); + decoded_duration_ms += + ((packet_time.seconds as f64 + packet_time.frac) * 1000.0) as u64; + + let decoded = self + .decoder + .decode(&packet) + .context("failed to decode audio packet")?; + let mut sample_buffer = + SampleBuffer::::new(decoded.capacity() as u64, *decoded.spec()); + sample_buffer.copy_interleaved_ref(decoded); + samples.extend_from_slice(sample_buffer.samples()); + } + + Ok(samples) + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 64b42f7..d3d8b81 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4,6 +4,7 @@ pub mod cursor; pub mod defaults; pub mod keyboard; pub mod keyboard_daemon; +pub mod key_sound; #[cfg(target_os = "windows")] pub mod keyboard_labels; pub mod ipc; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c08b0d6..b1fec3f 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,6 +7,7 @@ mod defaults; mod ipc; mod keyboard; mod keyboard_daemon; +mod key_sound; #[cfg(target_os = "windows")] mod keyboard_labels; mod models; @@ -168,6 +169,11 @@ fn main() { commands::keys::keys_set_counters, commands::keys::raw_input_subscribe, commands::keys::raw_input_unsubscribe, + commands::key_sound::key_sound_get_status, + commands::key_sound::key_sound_set_enabled, + commands::key_sound::key_sound_set_volume, + commands::key_sound::key_sound_load_soundpack, + commands::key_sound::key_sound_unload_soundpack, commands::keys::custom_tabs_list, commands::keys::custom_tabs_create, commands::keys::custom_tabs_delete, From 0fe0f0bcf57715ce696763534b53655d5bf24775 Mon Sep 17 00:00:00 2001 From: lee-sihun Date: Sat, 21 Feb 2026 12:11:59 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=B4=EB=93=9C=20?= =?UTF-8?q?=EC=97=94=EC=A7=84=20=EB=A1=9C=EA=B9=85=20=EB=B0=8F=20api=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.lock | 46 ++ src-tauri/Cargo.toml | 2 +- src-tauri/gen/schemas/acl-manifests.json | 2 +- src-tauri/permissions/dmnote-allow-all.json | 7 + src-tauri/src/app_state.rs | 109 +++- src-tauri/src/commands/key_sound.rs | 11 + src-tauri/src/commands/mod.rs | 1 + src-tauri/src/commands/sound.rs | 56 ++ src-tauri/src/key_sound.rs | 511 +++++++++++++++++- src-tauri/src/main.rs | 2 + src-tauri/src/models.rs | 6 + src-tauri/src/store.rs | 32 +- src/renderer/api/dmnoteApi.ts | 5 + .../main/Grid/PropertiesPanel/types.ts | 1 + src/renderer/windows/overlay/App.tsx | 5 +- src/types/api.ts | 10 + src/types/keys.ts | 2 + 17 files changed, 793 insertions(+), 15 deletions(-) create mode 100644 src-tauri/src/commands/sound.rs diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 7dad203..4663dc3 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4417,8 +4417,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" dependencies = [ "lazy_static", + "symphonia-bundle-flac", "symphonia-bundle-mp3", "symphonia-codec-aac", + "symphonia-codec-adpcm", + "symphonia-codec-alac", + "symphonia-codec-pcm", "symphonia-codec-vorbis", "symphonia-core", "symphonia-format-isomp4", @@ -4427,6 +4431,18 @@ dependencies = [ "symphonia-metadata", ] +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + [[package]] name = "symphonia-bundle-mp3" version = "0.5.5" @@ -4450,6 +4466,36 @@ dependencies = [ "symphonia-core", ] +[[package]] +name = "symphonia-codec-adpcm" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dddc50e2bbea4cfe027441eece77c46b9f319748605ab8f3443350129ddd07f" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-alac" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8413fa754942ac16a73634c9dfd1500ed5c61430956b33728567f667fdd393ab" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95" +dependencies = [ + "log", + "symphonia-core", +] + [[package]] name = "symphonia-codec-vorbis" version = "0.5.5" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 86addcb..e460dda 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -34,7 +34,7 @@ gif-dispose = "5.0.1" sha2 = "0.10" webp-animation = "0.9.0" rodio = { version = "0.19", default-features = false } -symphonia = { version = "0.5", default-features = false, features = ["ogg", "vorbis", "wav", "mp3", "isomp4", "aac"] } +symphonia = { version = "0.5", default-features = false, features = ["ogg", "vorbis", "wav", "aiff", "mp3", "isomp4", "aac", "pcm", "adpcm", "flac", "alac"] } [target."cfg(windows)".dependencies] windows = { version = "0.61.3", features = [ diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json index a94963e..e271cf8 100644 --- a/src-tauri/gen/schemas/acl-manifests.json +++ b/src-tauri/gen/schemas/acl-manifests.json @@ -1 +1 @@ -{"__app-acl__":{"default_permission":null,"permissions":{"dmnote-allow-all":{"identifier":"dmnote-allow-all","description":"Full DM Note command access for renderer","commands":{"allow":["app_bootstrap","app_auto_update","app_open_external","app_restart","app_quit","window_minimize","window_close","window_show_main","window_open_devtools_all","get_cursor_settings","settings_get","settings_update","overlay_get","overlay_set_visible","overlay_set_lock","overlay_set_anchor","overlay_resize","css_get","css_get_use","css_toggle","css_reset","css_set_content","css_load","css_tab_get_all","css_tab_get","css_tab_load","css_tab_clear","css_tab_set","css_tab_toggle","font_load","image_load","js_get","js_get_use","js_toggle","js_reset","js_set_content","js_load","js_reload","js_remove_plugin","js_set_plugin_enabled","keys_get","keys_update","keys_set_mode","keys_reset_all","keys_reset_mode","keys_reset_counters","keys_reset_counters_mode","keys_reset_single_counter","keys_set_counters","raw_input_subscribe","raw_input_unsubscribe","positions_get","positions_update","custom_tabs_list","custom_tabs_create","custom_tabs_delete","custom_tabs_select","preset_save","preset_load","plugin_bridge_send","plugin_bridge_send_to","plugin_storage_get","plugin_storage_set","plugin_storage_remove","plugin_storage_clear","plugin_storage_keys","plugin_storage_has_data","plugin_storage_clear_by_prefix","stat_positions_get","stat_positions_update","graph_positions_get","graph_positions_update","layer_groups_get","layer_groups_update"],"deny":[]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file +{"__app-acl__":{"default_permission":null,"permissions":{"dmnote-allow-all":{"identifier":"dmnote-allow-all","description":"Full DM Note command access for renderer","commands":{"allow":["app_bootstrap","app_auto_update","app_open_external","app_restart","app_quit","window_minimize","window_close","window_show_main","window_open_devtools_all","get_cursor_settings","settings_get","settings_update","overlay_get","overlay_set_visible","overlay_set_lock","overlay_set_anchor","overlay_resize","css_get","css_get_use","css_toggle","css_reset","css_set_content","css_load","css_tab_get_all","css_tab_get","css_tab_load","css_tab_clear","css_tab_set","css_tab_toggle","font_load","image_load","sound_load","js_get","js_get_use","js_toggle","js_reset","js_set_content","js_load","js_reload","js_remove_plugin","js_set_plugin_enabled","keys_get","keys_update","keys_set_mode","keys_reset_all","keys_reset_mode","keys_reset_counters","keys_reset_counters_mode","keys_reset_single_counter","keys_set_counters","raw_input_subscribe","raw_input_unsubscribe","key_sound_get_status","key_sound_set_enabled","key_sound_set_volume","key_sound_load_soundpack","key_sound_unload_soundpack","key_sound_set_latency_logging","positions_get","positions_update","custom_tabs_list","custom_tabs_create","custom_tabs_delete","custom_tabs_select","preset_save","preset_load","plugin_bridge_send","plugin_bridge_send_to","plugin_storage_get","plugin_storage_set","plugin_storage_remove","plugin_storage_clear","plugin_storage_keys","plugin_storage_has_data","plugin_storage_clear_by_prefix","stat_positions_get","stat_positions_update","graph_positions_get","graph_positions_update","layer_groups_get","layer_groups_update"],"deny":[]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/src-tauri/permissions/dmnote-allow-all.json b/src-tauri/permissions/dmnote-allow-all.json index 7b75380..1e921b1 100644 --- a/src-tauri/permissions/dmnote-allow-all.json +++ b/src-tauri/permissions/dmnote-allow-all.json @@ -37,6 +37,7 @@ "css_tab_toggle", "font_load", "image_load", + "sound_load", "js_get", "js_get_use", "js_toggle", @@ -57,6 +58,12 @@ "keys_set_counters", "raw_input_subscribe", "raw_input_unsubscribe", + "key_sound_get_status", + "key_sound_set_enabled", + "key_sound_set_volume", + "key_sound_load_soundpack", + "key_sound_unload_soundpack", + "key_sound_set_latency_logging", "positions_get", "positions_update", "custom_tabs_list", diff --git a/src-tauri/src/app_state.rs b/src-tauri/src/app_state.rs index 5afe92a..0d62a9a 100644 --- a/src-tauri/src/app_state.rs +++ b/src-tauri/src/app_state.rs @@ -9,6 +9,8 @@ use std::{ thread::{self, JoinHandle}, time::Duration, }; +#[cfg(debug_assertions)] +use std::time::Instant; use anyhow::{anyhow, Context, Result}; use log::{error, warn}; @@ -32,6 +34,8 @@ use crate::{ services::{css_watcher::CssWatcher, settings::SettingsService}, store::AppStore, }; +#[cfg(debug_assertions)] +use crate::key_sound::KeySoundDispatchTrace; const OVERLAY_LABEL: &str = "overlay"; const TRAY_ICON_ID: &str = "background-tray"; @@ -702,7 +706,72 @@ impl AppState { if message.device == crate::ipc::InputDeviceKind::Keyboard && state == "DOWN" { - app_state.key_sound.play_labels(&message.labels); + if let Some((sound_path, per_key_volume)) = + app_state.resolve_key_sound_binding(&mode, &key_label) + { + #[cfg(debug_assertions)] + let key_sound_input_started_at = Instant::now(); + #[cfg(debug_assertions)] + let key_sound_dispatch_started_at = Instant::now(); + #[cfg(debug_assertions)] + let dispatch_ms = + key_sound_dispatch_started_at.elapsed().as_secs_f64() + * 1000.0; + #[cfg(debug_assertions)] + let trace = KeySoundDispatchTrace::new( + key_sound_input_started_at, + dispatch_ms, + ); + app_state.key_sound.play_file( + &sound_path, + per_key_volume, + #[cfg(debug_assertions)] + Some(trace), + #[cfg(not(debug_assertions))] + None, + ); + #[cfg(debug_assertions)] + if app_state.key_sound.latency_logging_enabled() { + log::debug!( + "[KeySound][Latency] route=dispatch dispatchMs={dispatch_ms:.3} mode={} key={} volume={:.3} path={}", + mode, + key_label, + per_key_volume, + sound_path + ); + } + } else { + #[cfg(debug_assertions)] + let key_sound_input_started_at = Instant::now(); + #[cfg(debug_assertions)] + let key_sound_dispatch_started_at = Instant::now(); + #[cfg(debug_assertions)] + let dispatch_ms = + key_sound_dispatch_started_at.elapsed().as_secs_f64() + * 1000.0; + #[cfg(debug_assertions)] + let trace = KeySoundDispatchTrace::new( + key_sound_input_started_at, + dispatch_ms, + ); + app_state + .key_sound + .play_labels( + &message.labels, + #[cfg(debug_assertions)] + Some(trace), + #[cfg(not(debug_assertions))] + None, + ); + #[cfg(debug_assertions)] + if app_state.key_sound.latency_logging_enabled() { + log::debug!( + "[KeySound][Latency] route=dispatch dispatchMs={dispatch_ms:.3} mode={} key={} source=soundpack", + mode, + key_label + ); + } + } } let payload = json!({ "key": key_label, "state": state, "mode": mode }); @@ -1206,6 +1275,14 @@ impl AppState { self.key_sound.set_volume(volume) } + pub fn key_sound_set_latency_logging(&self, enabled: bool) -> KeySoundStatus { + self.key_sound.set_latency_logging(enabled) + } + + pub fn key_sound_latency_logging_available(&self) -> bool { + self.key_sound.latency_logging_available() + } + pub fn key_sound_load_soundpack(&self, soundpack_dir: &str) -> Result { self.key_sound .load_soundpack_dir(soundpack_dir) @@ -1216,6 +1293,36 @@ impl AppState { self.key_sound.unload_soundpack() } + fn resolve_key_sound_binding(&self, mode: &str, key_label: &str) -> Option<(String, f32)> { + self.store.with_state(|state| { + let mappings = state.keys.get(mode)?; + let positions = state.key_positions.get(mode)?; + + for (index, mapped_key) in mappings.iter().enumerate() { + if mapped_key != key_label { + continue; + } + + let Some(position) = positions.get(index) else { + continue; + }; + let Some(sound_path) = position.sound_path.as_ref() else { + continue; + }; + let trimmed_path = sound_path.trim(); + if trimmed_path.is_empty() { + continue; + } + + let volume_percent = position.sound_volume.unwrap_or(100.0); + let per_key_volume = (volume_percent / 100.0).clamp(0.0, 1.0) as f32; + return Some((trimmed_path.to_string(), per_key_volume)); + } + + None + }) + } + // ========== CSS 핫리로딩 관련 메서드 ========== /// CSS 워처 초기화 diff --git a/src-tauri/src/commands/key_sound.rs b/src-tauri/src/commands/key_sound.rs index c3959f0..143a65d 100644 --- a/src-tauri/src/commands/key_sound.rs +++ b/src-tauri/src/commands/key_sound.rs @@ -35,3 +35,14 @@ pub fn key_sound_load_soundpack( pub fn key_sound_unload_soundpack(state: State<'_, AppState>) -> Result { Ok(state.key_sound_unload_soundpack()) } + +#[tauri::command(permission = "dmnote-allow-all")] +pub fn key_sound_set_latency_logging( + state: State<'_, AppState>, + enabled: bool, +) -> Result { + if enabled && !state.key_sound_latency_logging_available() { + return Err("Latency measurement is only available in tauri dev/debug builds".to_string()); + } + Ok(state.key_sound_set_latency_logging(enabled)) +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 2a2b13a..6737695 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -11,6 +11,7 @@ pub mod overlay; pub mod plugin_storage; pub mod preset; pub mod settings; +pub mod sound; pub mod stat_items; pub mod system; pub mod update; diff --git a/src-tauri/src/commands/sound.rs b/src-tauri/src/commands/sound.rs new file mode 100644 index 0000000..99b5d9a --- /dev/null +++ b/src-tauri/src/commands/sound.rs @@ -0,0 +1,56 @@ +use rfd::FileDialog; +use serde::Serialize; +use std::fs; +use tauri::Manager; +use uuid::Uuid; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SoundLoadResponse { + pub success: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sound_path: Option, +} + +/// 로컬 사운드 파일을 선택하고 appData/sounds 디렉토리로 복사한 뒤 경로 반환 +#[tauri::command(permission = "dmnote-allow-all")] +pub fn sound_load(app: tauri::AppHandle) -> Result { + let picked = FileDialog::new() + .add_filter( + "Audio", + &["wav", "mp3", "ogg", "flac", "m4a", "aac", "aif", "aiff"], + ) + .pick_file(); + + let Some(path) = picked else { + return Ok(SoundLoadResponse { + success: false, + error: None, + sound_path: None, + }); + }; + + let ext = path + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("wav") + .to_lowercase(); + + let data_dir = app + .path() + .app_data_dir() + .map_err(|e| format!("앱 데이터 디렉토리 확인 실패: {e}"))?; + let sounds_dir = data_dir.join("sounds"); + fs::create_dir_all(&sounds_dir).map_err(|e| format!("사운드 디렉토리 생성 실패: {e}"))?; + + let dest_path = sounds_dir.join(format!("{}.{}", Uuid::new_v4(), ext)); + fs::copy(&path, &dest_path).map_err(|e| format!("사운드 파일 복사 실패: {e}"))?; + + Ok(SoundLoadResponse { + success: true, + error: None, + sound_path: Some(dest_path.to_string_lossy().to_string()), + }) +} diff --git a/src-tauri/src/key_sound.rs b/src-tauri/src/key_sound.rs index 622a951..1e3fc0f 100644 --- a/src-tauri/src/key_sound.rs +++ b/src-tauri/src/key_sound.rs @@ -1,16 +1,20 @@ use std::{ collections::HashMap, fs::File, + io::ErrorKind, path::Path, sync::{ mpsc::{self, Receiver, Sender}, Arc, }, thread, - time::Duration, + time::{Duration, Instant}, }; use anyhow::{Context, Result}; +#[cfg(debug_assertions)] +use log::debug; +use log::warn; use parking_lot::RwLock; use rodio::{OutputStream, PlayError, Sink, Source}; use serde::{Deserialize, Serialize}; @@ -18,6 +22,7 @@ use symphonia::{ core::{ audio::SampleBuffer, codecs::Decoder, + errors::Error as SymphoniaError, formats::{FormatOptions, FormatReader, SeekMode, SeekTo}, io::MediaSourceStream, probe::Hint, @@ -26,6 +31,128 @@ use symphonia::{ default::{get_codecs, get_probe}, }; +#[cfg(debug_assertions)] +const LATENCY_SUMMARY_INTERVAL: u64 = 50; + +#[cfg(debug_assertions)] +fn latency_measurement_available() -> bool { + true +} + +#[cfg(not(debug_assertions))] +fn latency_measurement_available() -> bool { + false +} + +#[cfg(debug_assertions)] +#[derive(Debug, Clone, Copy)] +pub struct KeySoundDispatchTrace { + input_started_at: Instant, + dispatch_ms: f64, +} + +#[cfg(not(debug_assertions))] +#[derive(Debug, Clone, Copy, Default)] +pub struct KeySoundDispatchTrace; + +#[cfg(debug_assertions)] +impl KeySoundDispatchTrace { + pub fn new(input_started_at: Instant, dispatch_ms: f64) -> Self { + Self { + input_started_at, + dispatch_ms, + } + } + + fn total_elapsed_ms(self) -> f64 { + self.input_started_at.elapsed().as_secs_f64() * 1000.0 + } + + fn dispatch_ms(self) -> f64 { + self.dispatch_ms + } +} + +#[cfg_attr(not(debug_assertions), allow(dead_code))] +#[derive(Debug, Clone, Copy, Default)] +struct ClipLoadTrace { + cache_hit: bool, + decode_ms: f64, +} + +#[cfg(debug_assertions)] +#[derive(Debug, Clone, Copy, Default)] +struct LatencySample { + dispatch_ms: f64, + queue_ms: f64, + cache_lookup_ms: f64, + decode_ms: f64, + sink_ms: f64, + append_ms: f64, + thread_ms: f64, + total_ms: f64, +} + +#[cfg(debug_assertions)] +#[derive(Debug, Clone, Copy, Default)] +struct LatencySummary { + samples: u64, + cache_miss_samples: u64, + dispatch_sum: f64, + queue_sum: f64, + cache_lookup_sum: f64, + decode_sum: f64, + sink_sum: f64, + append_sum: f64, + thread_sum: f64, + total_sum: f64, + total_max: f64, +} + +#[cfg(debug_assertions)] +impl LatencySummary { + fn push(&mut self, sample: LatencySample, cache_miss: bool) { + self.samples += 1; + if cache_miss { + self.cache_miss_samples += 1; + } + self.dispatch_sum += sample.dispatch_ms; + self.queue_sum += sample.queue_ms; + self.cache_lookup_sum += sample.cache_lookup_ms; + self.decode_sum += sample.decode_ms; + self.sink_sum += sample.sink_ms; + self.append_sum += sample.append_ms; + self.thread_sum += sample.thread_ms; + self.total_sum += sample.total_ms; + self.total_max = self.total_max.max(sample.total_ms); + } + + fn should_emit_summary(&self) -> bool { + self.samples > 0 && self.samples % LATENCY_SUMMARY_INTERVAL == 0 + } + + fn emit_summary(&self) { + let count = self.samples as f64; + if count <= 0.0 { + return; + } + debug!( + "[KeySound][Latency][Summary] samples={} cacheMisses={} avgDispatchMs={:.3} avgQueueMs={:.3} avgCacheLookupMs={:.3} avgDecodeMs={:.3} avgSinkMs={:.3} avgAppendMs={:.3} avgThreadMs={:.3} avgTotalMs={:.3} maxTotalMs={:.3}", + self.samples, + self.cache_miss_samples, + self.dispatch_sum / count, + self.queue_sum / count, + self.cache_lookup_sum / count, + self.decode_sum / count, + self.sink_sum / count, + self.append_sum / count, + self.thread_sum / count, + self.total_sum / count, + self.total_max + ); + } +} + #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct KeySoundStatus { @@ -34,16 +161,18 @@ pub struct KeySoundStatus { pub loaded: bool, pub soundpack_dir: Option, pub mapped_labels: usize, + pub latency_logging: bool, } impl Default for KeySoundStatus { fn default() -> Self { Self { - enabled: false, + enabled: true, volume: 1.0, loaded: false, soundpack_dir: None, mapped_labels: 0, + latency_logging: false, } } } @@ -55,12 +184,30 @@ struct KeySoundRuntimeState { } enum AudioCommand { - PlayLabels(Vec), + PlayLabels { + labels: Vec, + queued_at: Instant, + trace: Option, + }, + PlayFile { + path: String, + per_key_volume: f32, + queued_at: Instant, + trace: Option, + }, SetEnabled(bool), SetVolume(f32), + SetLatencyLogging(bool), SetSoundpack(Option>), } +#[derive(Debug, Clone)] +struct CachedAudioClip { + samples: Arc<[i16]>, + channels: u16, + sample_rate: u32, +} + pub struct KeySoundEngine { sender: Sender, state: Arc>, @@ -109,6 +256,25 @@ impl KeySoundEngine { self.status() } + pub fn set_latency_logging(&self, enabled: bool) -> KeySoundStatus { + let enabled = enabled && latency_measurement_available(); + { + let mut guard = self.state.write(); + guard.status.latency_logging = enabled; + } + let _ = self.sender.send(AudioCommand::SetLatencyLogging(enabled)); + self.status() + } + + #[cfg(debug_assertions)] + pub fn latency_logging_enabled(&self) -> bool { + latency_measurement_available() && self.state.read().status.latency_logging + } + + pub fn latency_logging_available(&self) -> bool { + latency_measurement_available() + } + pub fn load_soundpack_dir

(&self, soundpack_dir: P) -> Result where P: AsRef, @@ -140,7 +306,11 @@ impl KeySoundEngine { self.status() } - pub fn play_labels(&self, labels: &[String]) { + pub fn play_labels( + &self, + labels: &[String], + trace: Option, + ) { if labels.is_empty() { return; } @@ -148,9 +318,33 @@ impl KeySoundEngine { return; } - let _ = self - .sender - .send(AudioCommand::PlayLabels(labels.to_vec())); + let _ = self.sender.send(AudioCommand::PlayLabels { + labels: labels.to_vec(), + queued_at: Instant::now(), + trace, + }); + } + + pub fn play_file( + &self, + path: &str, + per_key_volume: f32, + trace: Option, + ) { + let trimmed = path.trim(); + if trimmed.is_empty() { + return; + } + if !self.state.read().status.enabled { + return; + } + + let _ = self.sender.send(AudioCommand::PlayFile { + path: trimmed.to_string(), + per_key_volume: per_key_volume.clamp(0.0, 1.0), + queued_at: Instant::now(), + trace, + }); } } @@ -167,7 +361,12 @@ fn audio_thread( let mut enabled = state.read().status.enabled; let mut volume = state.read().status.volume; let mut soundpack = state.read().soundpack.clone(); + #[cfg(debug_assertions)] + let mut latency_logging = state.read().status.latency_logging; let mut stream_handler = OutputStream::try_default().ok(); + let mut file_cache: HashMap> = HashMap::new(); + #[cfg(debug_assertions)] + let mut latency_summary = LatencySummary::default(); while let Ok(command) = receiver.recv() { match command { @@ -177,30 +376,199 @@ fn audio_thread( AudioCommand::SetVolume(value) => { volume = value; } + AudioCommand::SetLatencyLogging(value) => { + #[cfg(debug_assertions)] + { + latency_logging = value; + } + #[cfg(not(debug_assertions))] + { + let _ = value; + } + } AudioCommand::SetSoundpack(pack) => { soundpack = pack; } - AudioCommand::PlayLabels(labels) => { + AudioCommand::PlayLabels { + labels, + queued_at, + trace, + } => { if !enabled { continue; } let Some(pack) = soundpack.as_ref() else { continue; }; + #[cfg(debug_assertions)] + let audio_started_at = latency_logging.then_some(Instant::now()); let Some(source) = pack.source_for_labels(&labels) else { continue; }; + #[cfg(not(debug_assertions))] + let _ = (queued_at, trace); if stream_handler.is_none() { stream_handler = OutputStream::try_default().ok(); } + #[cfg(debug_assertions)] + let sink_started_at = latency_logging.then_some(Instant::now()); let Some(sink) = build_sink(&mut stream_handler) else { continue; }; + #[cfg(debug_assertions)] + let sink_ms = sink_started_at + .map(|started| started.elapsed().as_secs_f64() * 1000.0) + .unwrap_or(0.0); sink.set_volume(volume); + #[cfg(debug_assertions)] + let append_started_at = latency_logging.then_some(Instant::now()); sink.append(source); + #[cfg(debug_assertions)] + { + let append_ms = append_started_at + .map(|started| started.elapsed().as_secs_f64() * 1000.0) + .unwrap_or(0.0); + if latency_logging { + let queue_ms = queued_at.elapsed().as_secs_f64() * 1000.0; + let dispatch_ms = + trace.map(KeySoundDispatchTrace::dispatch_ms).unwrap_or(0.0); + let thread_ms = audio_started_at + .map(|started| started.elapsed().as_secs_f64() * 1000.0) + .unwrap_or(0.0); + let total_ms = trace + .map(KeySoundDispatchTrace::total_elapsed_ms) + .unwrap_or(dispatch_ms + queue_ms + thread_ms); + debug!( + "[KeySound][Latency] route=soundpack dispatchMs={dispatch_ms:.3} queueMs={queue_ms:.3} sinkMs={sink_ms:.3} appendMs={append_ms:.3} threadMs={thread_ms:.3} totalMs={total_ms:.3} labels={labels:?}" + ); + latency_summary.push( + LatencySample { + dispatch_ms, + queue_ms, + sink_ms, + append_ms, + thread_ms, + total_ms, + ..Default::default() + }, + false, + ); + if latency_summary.should_emit_summary() { + latency_summary.emit_summary(); + } + } + } + sink.detach(); + } + AudioCommand::PlayFile { + path, + per_key_volume, + queued_at, + trace, + } => { + if !enabled { + continue; + } + #[cfg(debug_assertions)] + let audio_started_at = latency_logging.then_some(Instant::now()); + #[cfg(not(debug_assertions))] + let _ = (queued_at, trace); + + #[cfg(debug_assertions)] + let clip_lookup_started_at = latency_logging.then_some(Instant::now()); + let (clip, clip_load_trace) = match get_or_load_cached_clip( + &path, + &mut file_cache, + { + #[cfg(debug_assertions)] + { + latency_logging + } + #[cfg(not(debug_assertions))] + { + false + } + }, + ) { + Some(result) => result, + None => continue, + }; + #[cfg(debug_assertions)] + let cache_lookup_ms = clip_lookup_started_at + .map(|started| started.elapsed().as_secs_f64() * 1000.0) + .unwrap_or(0.0); + #[cfg(not(debug_assertions))] + let _ = clip_load_trace; + + if stream_handler.is_none() { + stream_handler = OutputStream::try_default().ok(); + } + + #[cfg(debug_assertions)] + let sink_started_at = latency_logging.then_some(Instant::now()); + let Some(sink) = build_sink(&mut stream_handler) else { + continue; + }; + #[cfg(debug_assertions)] + let sink_ms = sink_started_at + .map(|started| started.elapsed().as_secs_f64() * 1000.0) + .unwrap_or(0.0); + + let final_volume = (volume * per_key_volume).clamp(0.0, 1.0); + sink.set_volume(final_volume); + #[cfg(debug_assertions)] + let append_started_at = latency_logging.then_some(Instant::now()); + sink.append(AudioSource::new( + clip.samples.clone(), + clip.channels, + clip.sample_rate, + )); + #[cfg(debug_assertions)] + { + let append_ms = append_started_at + .map(|started| started.elapsed().as_secs_f64() * 1000.0) + .unwrap_or(0.0); + if latency_logging { + let queue_ms = queued_at.elapsed().as_secs_f64() * 1000.0; + let dispatch_ms = + trace.map(KeySoundDispatchTrace::dispatch_ms).unwrap_or(0.0); + let thread_ms = audio_started_at + .map(|started| started.elapsed().as_secs_f64() * 1000.0) + .unwrap_or(0.0); + let total_ms = trace + .map(KeySoundDispatchTrace::total_elapsed_ms) + .unwrap_or(dispatch_ms + queue_ms + thread_ms); + let cache_label = if clip_load_trace.cache_hit { + "hit" + } else { + "miss" + }; + debug!( + "[KeySound][Latency] route=key-file dispatchMs={dispatch_ms:.3} queueMs={queue_ms:.3} cacheLookupMs={cache_lookup_ms:.3} decodeMs={:.3} sinkMs={sink_ms:.3} appendMs={append_ms:.3} threadMs={thread_ms:.3} totalMs={total_ms:.3} cache={} volume={final_volume:.3} path={path}", + clip_load_trace.decode_ms, + cache_label + ); + latency_summary.push( + LatencySample { + dispatch_ms, + queue_ms, + cache_lookup_ms, + decode_ms: clip_load_trace.decode_ms, + sink_ms, + append_ms, + thread_ms, + total_ms, + }, + !clip_load_trace.cache_hit, + ); + if latency_summary.should_emit_summary() { + latency_summary.emit_summary(); + } + } + } sink.detach(); } } @@ -223,6 +591,133 @@ fn build_sink( } } +fn get_or_load_cached_clip( + path: &str, + cache: &mut HashMap>, + measure_decode_ms: bool, +) -> Option<(Arc, ClipLoadTrace)> { + if let Some(cached) = cache.get(path) { + return Some(( + cached.clone(), + ClipLoadTrace { + cache_hit: true, + decode_ms: 0.0, + }, + )); + } + + #[cfg(debug_assertions)] + let decode_started_at = measure_decode_ms.then_some(Instant::now()); + #[cfg(not(debug_assertions))] + let _ = measure_decode_ms; + match decode_audio_file_clip(path) { + Ok(clip) => { + let shared = Arc::new(clip); + cache.insert(path.to_string(), shared.clone()); + Some(( + shared, + ClipLoadTrace { + cache_hit: false, + #[cfg(debug_assertions)] + decode_ms: decode_started_at + .map(|started| started.elapsed().as_secs_f64() * 1000.0) + .unwrap_or(0.0), + #[cfg(not(debug_assertions))] + decode_ms: 0.0, + }, + )) + } + Err(error) => { + warn!("[KeySound] failed to decode key sound file '{}': {error:#}", path); + None + } + } +} + +fn decode_audio_file_clip(path: &str) -> Result { + let file = File::open(path) + .with_context(|| format!("failed to open key sound file: {}", path))?; + let media_source = MediaSourceStream::new(Box::new(file), Default::default()); + let path_ref = Path::new(path); + + let mut hint = Hint::new(); + if let Some(ext) = path_ref.extension().and_then(|value| value.to_str()) { + hint.with_extension(ext); + } + + let probe = get_probe() + .format( + &hint, + media_source, + &FormatOptions::default(), + &Default::default(), + ) + .context("failed to probe key sound file format")?; + let mut format = probe.format; + let track = format + .default_track() + .context("no default track in key sound file")?; + let track_id = track.id; + let mut decoder = get_codecs() + .make(&track.codec_params, &Default::default()) + .context("failed to create key sound decoder")?; + + let mut channels = track.codec_params.channels.map(|value| value.count() as u16); + let mut sample_rate = track.codec_params.sample_rate; + let mut samples: Vec = Vec::new(); + + loop { + let packet = match format.next_packet() { + Ok(packet) => packet, + Err(SymphoniaError::IoError(io_error)) + if io_error.kind() == ErrorKind::UnexpectedEof => + { + break; + } + Err(err) => { + return Err(anyhow::Error::new(err).context("failed to read key sound packet")); + } + }; + + if packet.track_id() != track_id { + continue; + } + + let decoded = match decoder.decode(&packet) { + Ok(decoded) => decoded, + Err(SymphoniaError::DecodeError(_)) => continue, + Err(SymphoniaError::ResetRequired) => { + decoder.reset(); + continue; + } + Err(err) => { + return Err(anyhow::Error::new(err).context("failed to decode key sound packet")); + } + }; + + channels.get_or_insert(decoded.spec().channels.count() as u16); + sample_rate.get_or_insert(decoded.spec().rate); + + let mut sample_buffer = + SampleBuffer::::new(decoded.capacity() as u64, *decoded.spec()); + sample_buffer.copy_interleaved_ref(decoded); + samples.extend_from_slice(sample_buffer.samples()); + } + + if samples.is_empty() { + anyhow::bail!("decoded sample buffer is empty"); + } + + let channels = channels.context("missing channel count in key sound file")?; + let sample_rate = sample_rate.context("missing sample rate in key sound file")?; + + Ok(CachedAudioClip { + samples: Arc::from(samples.into_boxed_slice()), + channels, + sample_rate, + }) +} + #[derive(Debug)] struct LoadedSoundpack { segments: HashMap>, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b1fec3f..bd7dabc 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -174,6 +174,7 @@ fn main() { commands::key_sound::key_sound_set_volume, commands::key_sound::key_sound_load_soundpack, commands::key_sound::key_sound_unload_soundpack, + commands::key_sound::key_sound_set_latency_logging, commands::keys::custom_tabs_list, commands::keys::custom_tabs_create, commands::keys::custom_tabs_delete, @@ -194,6 +195,7 @@ fn main() { commands::css::css_tab_toggle, commands::font::font_load, commands::image::image_load, + commands::sound::sound_load, commands::js::js_get, commands::js::js_get_use, commands::js::js_toggle, diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 88c9f21..3a3c4a7 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -123,6 +123,12 @@ pub struct KeyPosition { pub active_image: Option, #[serde(default)] pub inactive_image: Option, + /// 키 입력 시 재생할 로컬 사운드 파일 경로 + #[serde(default)] + pub sound_path: Option, + /// 키별 사운드 볼륨 (0~100, 기본값 100) + #[serde(default)] + pub sound_volume: Option, #[serde(default)] pub active_transparent: bool, #[serde(default)] diff --git a/src-tauri/src/store.rs b/src-tauri/src/store.rs index b57f647..ffd5e03 100644 --- a/src-tauri/src/store.rs +++ b/src-tauri/src/store.rs @@ -87,6 +87,11 @@ impl AppStore { self.state.read().clone() } + pub fn with_state(&self, reader: impl FnOnce(&AppStoreData) -> T) -> T { + let guard = self.state.read(); + reader(&guard) + } + pub fn settings_snapshot(&self) -> SettingsState { settings_from_store(&self.state.read()) } @@ -247,7 +252,7 @@ impl AppStore { } /// 앱 종료 시점에 한 번 호출하는 자원 정리. - /// 현재 store에서 참조하지 않는 appData/fonts, appData/images 파일을 삭제합니다. + /// 현재 store에서 참조하지 않는 appData/fonts, appData/images, appData/sounds 파일을 삭제 pub fn cleanup_orphan_assets_now(&self) -> Result<()> { let snapshot = self.state.read().clone(); let app_data_dir = self @@ -257,12 +262,15 @@ impl AppStore { let fonts_dir = app_data_dir.join("fonts"); let images_dir = app_data_dir.join("images"); + let sounds_dir = app_data_dir.join("sounds"); let referenced_font_keys = collect_local_font_path_keys(&snapshot); let referenced_image_keys = collect_local_image_path_keys(&snapshot); + let referenced_sound_keys = collect_local_sound_path_keys(&snapshot); sweep_unreferenced_asset_files("Fonts", &fonts_dir, &referenced_font_keys)?; sweep_unreferenced_asset_files("Images", &images_dir, &referenced_image_keys)?; + sweep_unreferenced_asset_files("Sounds", &sounds_dir, &referenced_sound_keys)?; Ok(()) } } @@ -623,6 +631,18 @@ fn collect_local_image_path_keys(data: &AppStoreData) -> HashSet { paths } +fn collect_local_sound_path_keys(data: &AppStoreData) -> HashSet { + let mut paths = HashSet::new(); + + for positions in data.key_positions.values() { + for position in positions { + collect_sound_path_from_option(&mut paths, position.sound_path.as_ref()); + } + } + + paths +} + fn collect_image_path_from_option(paths: &mut HashSet, value: Option<&String>) { let Some(path) = value else { return; @@ -633,6 +653,16 @@ fn collect_image_path_from_option(paths: &mut HashSet, value: Option<&St paths.insert(path_lookup_key(&normalized)); } +fn collect_sound_path_from_option(paths: &mut HashSet, value: Option<&String>) { + let Some(path) = value else { + return; + }; + let Some(normalized) = normalize_local_asset_path(path) else { + return; + }; + paths.insert(path_lookup_key(&normalized)); +} + fn normalize_local_asset_path(path: &str) -> Option { let trimmed = path.trim(); if trimmed.is_empty() { diff --git a/src/renderer/api/dmnoteApi.ts b/src/renderer/api/dmnoteApi.ts index cd003e2..adfe0ec 100644 --- a/src/renderer/api/dmnoteApi.ts +++ b/src/renderer/api/dmnoteApi.ts @@ -308,6 +308,11 @@ const api: DMNoteAPI = { image: { load: () => invoke("image_load"), }, + sound: { + load: () => invoke("sound_load"), + setLatencyLogging: (enabled: boolean) => + invoke("key_sound_set_latency_logging", { enabled }).then(() => undefined), + }, js: { get: () => invoke("js_get"), getUse: () => invoke("js_get_use"), diff --git a/src/renderer/components/main/Grid/PropertiesPanel/types.ts b/src/renderer/components/main/Grid/PropertiesPanel/types.ts index 20020cb..404a784 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel/types.ts +++ b/src/renderer/components/main/Grid/PropertiesPanel/types.ts @@ -185,6 +185,7 @@ export interface StyleTabContentProps { mappingLabel?: string; // 표시 텍스트 입력 숨김 (통계 요소는 statType이 displayText 역할) hideDisplayText?: boolean; + showSoundControls?: boolean; showImagePicker?: boolean; onToggleImagePicker?: () => void; imageButtonRef?: React.RefObject; diff --git a/src/renderer/windows/overlay/App.tsx b/src/renderer/windows/overlay/App.tsx index 261e041..b61ddf4 100644 --- a/src/renderer/windows/overlay/App.tsx +++ b/src/renderer/windows/overlay/App.tsx @@ -8,10 +8,11 @@ import React, { useCallback, } from "react"; import { + currentMonitor, getCurrentWindow, Window as TauriWindow, } from "@tauri-apps/api/window"; -import { LogicalPosition } from "@tauri-apps/api/dpi"; +import { LogicalPosition, PhysicalPosition } from "@tauri-apps/api/dpi"; import { Menu } from "@tauri-apps/api/menu"; import { isMac } from "@utils/platform"; import { Key } from "@components/Key"; @@ -276,8 +277,6 @@ export default function App() { const snapToNearestEdge = useCallback(async () => { try { const win = getCurrentWindow(); - const { currentMonitor } = await import("@tauri-apps/api/window"); - const { PhysicalPosition } = await import("@tauri-apps/api/dpi"); const [monitor, pos, size] = await Promise.all([ currentMonitor(), win.outerPosition(), diff --git a/src/types/api.ts b/src/types/api.ts index 8f79564..bd244d3 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -79,6 +79,12 @@ export type ImageLoadResult = { imagePath?: string; }; +export type SoundLoadResult = { + success: boolean; + error?: string; + soundPath?: string; +}; + // 탭별 CSS 타입 export type TabCssResponse = { tabId: string; @@ -718,6 +724,10 @@ export interface DMNoteAPI { image: { load(): Promise; }; + sound: { + load(): Promise; + setLatencyLogging(enabled: boolean): Promise; + }; js: { get(): Promise; getUse(): Promise; diff --git a/src/types/keys.ts b/src/types/keys.ts index cf61890..0b11568 100644 --- a/src/types/keys.ts +++ b/src/types/keys.ts @@ -208,6 +208,8 @@ export const keyPositionSchema = z.object({ hidden: z.boolean().optional().default(false), activeImage: z.string().optional().or(z.literal("")), inactiveImage: z.string().optional().or(z.literal("")), + soundPath: z.string().optional().or(z.literal("")), + soundVolume: z.number().min(0).max(100).optional(), activeTransparent: z.boolean().optional(), idleTransparent: z.boolean().optional(), count: z.number().int().nonnegative(), From 99fb4238b7379113207b7848a0e4db40c27cb637 Mon Sep 17 00:00:00 2001 From: lee-sihun Date: Sat, 21 Feb 2026 13:15:40 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=ED=82=A4=EC=9D=8C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20(?= =?UTF-8?q?=EC=9E=84=EC=8B=9C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/components/main/Grid/Grid.jsx | 4 + .../components/main/Grid/PropertiesPanel.tsx | 6 ++ .../PropertiesPanel/BatchStyleTabContent.tsx | 88 +++++++++++++++++++ .../Grid/PropertiesPanel/StyleTabContent.tsx | 82 +++++++++++++++++ src/renderer/hooks/useKeyManager.ts | 24 +++++ src/renderer/locales/en.json | 7 +- src/renderer/locales/ko.json | 7 +- src/renderer/locales/ru.json | 7 +- src/renderer/locales/zh-Hant.json | 9 +- src/renderer/locales/zh-cn.json | 7 +- 10 files changed, 235 insertions(+), 6 deletions(-) diff --git a/src/renderer/components/main/Grid/Grid.jsx b/src/renderer/components/main/Grid/Grid.jsx index cdf5a79..ede3fda 100644 --- a/src/renderer/components/main/Grid/Grid.jsx +++ b/src/renderer/components/main/Grid/Grid.jsx @@ -991,6 +991,8 @@ export default function Grid({ hidden: false, activeImage: "", inactiveImage: "", + soundPath: "", + soundVolume: 100, activeTransparent: false, idleTransparent: false, count: 0, @@ -1035,6 +1037,8 @@ export default function Grid({ hidden: false, activeImage: "", inactiveImage: "", + soundPath: "", + soundVolume: 100, activeTransparent: false, idleTransparent: false, count: 0, diff --git a/src/renderer/components/main/Grid/PropertiesPanel.tsx b/src/renderer/components/main/Grid/PropertiesPanel.tsx index 85ca91d..89bea11 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel.tsx +++ b/src/renderer/components/main/Grid/PropertiesPanel.tsx @@ -2796,6 +2796,10 @@ const PropertiesPanel: React.FC = ({

0 && + selectedKeyElements.length === selectedBatchStyleElements.length + } getMixedValue={styleMixedValueGetter} getSelectedKeysData={styleSelectedDataGetter} afterSizeContent={ @@ -3264,6 +3268,7 @@ const PropertiesPanel: React.FC = ({ selectedCount={selectedGraphElements.length} hideDisplayText hideFontControls + showSoundControls={false} afterSizeContent={ <> = ({ ? t("propertiesPanel.statType") || "Stat Type" : undefined } + showSoundControls={!isSingleStat} showImagePicker={showImagePicker} onToggleImagePicker={() => setShowImagePicker(!showImagePicker)} imageButtonRef={imageButtonRef} diff --git a/src/renderer/components/main/Grid/PropertiesPanel/BatchStyleTabContent.tsx b/src/renderer/components/main/Grid/PropertiesPanel/BatchStyleTabContent.tsx index 9cf38ab..8ad73dc 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel/BatchStyleTabContent.tsx +++ b/src/renderer/components/main/Grid/PropertiesPanel/BatchStyleTabContent.tsx @@ -27,6 +27,7 @@ interface BatchStyleTabContentProps { selectedCount: number; hideDisplayText?: boolean; hideFontControls?: boolean; + showSoundControls?: boolean; afterSizeContent?: React.ReactNode; // getMixedValue 함수 getMixedValue: ( @@ -70,6 +71,7 @@ const BatchStyleTabContent: React.FC = ({ selectedCount, hideDisplayText = false, hideFontControls = false, + showSoundControls = false, afterSizeContent, getMixedValue, getSelectedKeysData, @@ -90,6 +92,7 @@ const BatchStyleTabContent: React.FC = ({ }) => { const [colorState, setColorState] = useState<"idle" | "active">("idle"); const [showFontPicker, setShowFontPicker] = useState(false); + const [isLoadingSound, setIsLoadingSound] = useState(false); // 간격 입력 세션 동안 첫 변경만 히스토리를 남기고 이후는 skipHistory로 묶는다. const lastSpacingRef = useRef(null); @@ -188,6 +191,27 @@ const BatchStyleTabContent: React.FC = ({ return { isMixed, value: firstValue }; }; + const handleBatchSoundLoad = useCallback(async () => { + if (isLoadingSound) return; + setIsLoadingSound(true); + try { + const result = await window.api.sound.load(); + if (!result.success || !result.soundPath) return; + handleBatchStyleChangeComplete("soundPath", result.soundPath); + } catch (error) { + console.error("Failed to load key sound", error); + } finally { + setIsLoadingSound(false); + } + }, [handleBatchStyleChangeComplete, isLoadingSound]); + + const getSoundDisplayName = useCallback((soundPath?: string): string => { + if (!soundPath || !soundPath.trim()) return ""; + const separators = /[\\/]/g; + const segments = soundPath.split(separators); + return segments[segments.length - 1] || soundPath; + }, []); + return ( <> {/* 정렬 */} @@ -833,6 +857,70 @@ const BatchStyleTabContent: React.FC = ({ )} + {showSoundControls && ( + <> + + + + {getMixedValue((pos) => pos.soundPath, "").isMixed ? ( + Mixed + ) : null} +
+ + {getMixedValue((pos) => pos.soundPath, "").value ? ( + + ) : null} +
+
+ + {(() => { + const soundPathState = getMixedValue((pos) => pos.soundPath, ""); + if (soundPathState.isMixed || !soundPathState.value) return null; + return ( + + + {getSoundDisplayName(soundPathState.value)} + + + ); + })()} + + + {getMixedValue((pos) => pos.soundVolume, 100).isMixed ? ( + Mixed + ) : null} + pos.soundVolume, 100).value} + onChange={(value) => + handleBatchStyleChangeComplete( + "soundVolume", + Math.max(0, Math.min(100, value)), + ) + } + suffix="%" + min={0} + max={100} + isMixed={getMixedValue((pos) => pos.soundVolume, 100).isMixed} + /> + + + )} + {/* FontPicker */} {!hideFontControls && showFontPicker && ( = ({ mappingControlLayout, mappingLabel, hideDisplayText = false, + showSoundControls = true, showImagePicker = false, onToggleImagePicker, imageButtonRef, @@ -107,6 +108,7 @@ const StyleTabContent: React.FC = ({ const fontButtonRef = useRef(null); // 폰트 관리 모달 상태 const [showFontManager, setShowFontManager] = useState(false); + const [isLoadingSound, setIsLoadingSound] = useState(false); // 폰트 스토어 const { getAllFonts } = useFontStore(); const borderColorBtnRef = useRef(null); @@ -406,6 +408,33 @@ const StyleTabContent: React.FC = ({ [keyIndex, onKeyPreview, onKeyUpdate], ); + const handleSoundLoad = useCallback(async () => { + if (isLoadingSound) return; + setIsLoadingSound(true); + try { + const result = await window.api.sound.load(); + if (!result.success || !result.soundPath) return; + onKeyPreview?.(keyIndex, { soundPath: result.soundPath }); + onKeyUpdate({ index: keyIndex, soundPath: result.soundPath }); + } catch (error) { + console.error("Failed to load key sound", error); + } finally { + setIsLoadingSound(false); + } + }, [isLoadingSound, keyIndex, onKeyPreview, onKeyUpdate]); + + const handleSoundReset = useCallback(() => { + onKeyPreview?.(keyIndex, { soundPath: "" }); + onKeyUpdate({ index: keyIndex, soundPath: "" }); + }, [keyIndex, onKeyPreview, onKeyUpdate]); + + const getSoundDisplayName = useCallback((soundPath?: string): string => { + if (!soundPath || !soundPath.trim()) return ""; + const separators = /[\\/]/g; + const segments = soundPath.split(separators); + return segments[segments.length - 1] || soundPath; + }, []); + // 표시 텍스트 핸들러 const handleDisplayTextChange = useCallback( (value: string) => { @@ -726,6 +755,59 @@ const StyleTabContent: React.FC = ({ )} + {showSoundControls && ( + <> + + + +
+ + {keyPosition.soundPath ? ( + + ) : null} +
+
+ + {keyPosition.soundPath ? ( + + + {getSoundDisplayName(keyPosition.soundPath)} + + + ) : null} + + + + handleStyleChangeComplete( + "soundVolume", + Math.max(0, Math.min(100, value)), + ) + } + suffix="%" + min={0} + max={100} + /> + + + )} + {/* 이미지 픽커 팝업 - 단일 선택 모드에서만 */} {showImagePicker && onToggleImagePicker && imageButtonRef && ( Date: Sun, 22 Feb 2026 11:50:36 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=ED=82=A4=EC=9D=8C=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/gen/schemas/acl-manifests.json | 2 +- src-tauri/permissions/dmnote-allow-all.json | 4 + src-tauri/src/app_state.rs | 4 + src-tauri/src/commands/sound.rs | 457 +++++++++- src-tauri/src/main.rs | 4 + src-tauri/src/models.rs | 3 + src/renderer/api/dmnoteApi.ts | 17 + .../components/main/Grid/PropertiesPanel.tsx | 10 +- .../PropertiesPanel/BatchStyleTabContent.tsx | 165 ++-- .../Grid/PropertiesPanel/PropertyInputs.tsx | 2 +- .../Grid/PropertiesPanel/StyleTabContent.tsx | 107 +-- .../Grid/PropertiesPanel/useBatchHandlers.ts | 13 + .../Modal/content/CommonListPickerPopup.tsx | 249 ++++++ .../main/Modal/content/FontPicker.tsx | 246 +----- .../main/Modal/content/SoundManagerModal.tsx | 319 +++++++ .../main/Modal/content/SoundPicker.tsx | 138 +++ .../main/Modal/content/SoundTrimModal.tsx | 831 ++++++++++++++++++ src/renderer/components/main/Settings.jsx | 14 +- src/renderer/locales/en.json | 35 +- src/renderer/locales/ko.json | 35 +- src/renderer/locales/ru.json | 35 +- src/renderer/locales/zh-Hant.json | 139 +-- src/renderer/locales/zh-cn.json | 35 +- src/types/api.ts | 35 + src/types/keys.ts | 1 + 25 files changed, 2469 insertions(+), 431 deletions(-) create mode 100644 src/renderer/components/main/Modal/content/CommonListPickerPopup.tsx create mode 100644 src/renderer/components/main/Modal/content/SoundManagerModal.tsx create mode 100644 src/renderer/components/main/Modal/content/SoundPicker.tsx create mode 100644 src/renderer/components/main/Modal/content/SoundTrimModal.tsx diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json index e271cf8..c3033fd 100644 --- a/src-tauri/gen/schemas/acl-manifests.json +++ b/src-tauri/gen/schemas/acl-manifests.json @@ -1 +1 @@ -{"__app-acl__":{"default_permission":null,"permissions":{"dmnote-allow-all":{"identifier":"dmnote-allow-all","description":"Full DM Note command access for renderer","commands":{"allow":["app_bootstrap","app_auto_update","app_open_external","app_restart","app_quit","window_minimize","window_close","window_show_main","window_open_devtools_all","get_cursor_settings","settings_get","settings_update","overlay_get","overlay_set_visible","overlay_set_lock","overlay_set_anchor","overlay_resize","css_get","css_get_use","css_toggle","css_reset","css_set_content","css_load","css_tab_get_all","css_tab_get","css_tab_load","css_tab_clear","css_tab_set","css_tab_toggle","font_load","image_load","sound_load","js_get","js_get_use","js_toggle","js_reset","js_set_content","js_load","js_reload","js_remove_plugin","js_set_plugin_enabled","keys_get","keys_update","keys_set_mode","keys_reset_all","keys_reset_mode","keys_reset_counters","keys_reset_counters_mode","keys_reset_single_counter","keys_set_counters","raw_input_subscribe","raw_input_unsubscribe","key_sound_get_status","key_sound_set_enabled","key_sound_set_volume","key_sound_load_soundpack","key_sound_unload_soundpack","key_sound_set_latency_logging","positions_get","positions_update","custom_tabs_list","custom_tabs_create","custom_tabs_delete","custom_tabs_select","preset_save","preset_load","plugin_bridge_send","plugin_bridge_send_to","plugin_storage_get","plugin_storage_set","plugin_storage_remove","plugin_storage_clear","plugin_storage_keys","plugin_storage_has_data","plugin_storage_clear_by_prefix","stat_positions_get","stat_positions_update","graph_positions_get","graph_positions_update","layer_groups_get","layer_groups_update"],"deny":[]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file +{"__app-acl__":{"default_permission":null,"permissions":{"dmnote-allow-all":{"identifier":"dmnote-allow-all","description":"Full DM Note command access for renderer","commands":{"allow":["app_bootstrap","app_auto_update","app_open_external","app_restart","app_quit","window_minimize","window_close","window_show_main","window_open_devtools_all","get_cursor_settings","settings_get","settings_update","overlay_get","overlay_set_visible","overlay_set_lock","overlay_set_anchor","overlay_resize","css_get","css_get_use","css_toggle","css_reset","css_set_content","css_load","css_tab_get_all","css_tab_get","css_tab_load","css_tab_clear","css_tab_set","css_tab_toggle","font_load","image_load","sound_load","sound_list","sound_set_enabled","sound_delete","sound_save_processed_wav","js_get","js_get_use","js_toggle","js_reset","js_set_content","js_load","js_reload","js_remove_plugin","js_set_plugin_enabled","keys_get","keys_update","keys_set_mode","keys_reset_all","keys_reset_mode","keys_reset_counters","keys_reset_counters_mode","keys_reset_single_counter","keys_set_counters","raw_input_subscribe","raw_input_unsubscribe","key_sound_get_status","key_sound_set_enabled","key_sound_set_volume","key_sound_load_soundpack","key_sound_unload_soundpack","key_sound_set_latency_logging","positions_get","positions_update","custom_tabs_list","custom_tabs_create","custom_tabs_delete","custom_tabs_select","preset_save","preset_load","plugin_bridge_send","plugin_bridge_send_to","plugin_storage_get","plugin_storage_set","plugin_storage_remove","plugin_storage_clear","plugin_storage_keys","plugin_storage_has_data","plugin_storage_clear_by_prefix","stat_positions_get","stat_positions_update","graph_positions_get","graph_positions_update","layer_groups_get","layer_groups_update"],"deny":[]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/src-tauri/permissions/dmnote-allow-all.json b/src-tauri/permissions/dmnote-allow-all.json index 1e921b1..86ec57d 100644 --- a/src-tauri/permissions/dmnote-allow-all.json +++ b/src-tauri/permissions/dmnote-allow-all.json @@ -38,6 +38,10 @@ "font_load", "image_load", "sound_load", + "sound_list", + "sound_set_enabled", + "sound_delete", + "sound_save_processed_wav", "js_get", "js_get_use", "js_toggle", diff --git a/src-tauri/src/app_state.rs b/src-tauri/src/app_state.rs index 0d62a9a..ec5ecae 100644 --- a/src-tauri/src/app_state.rs +++ b/src-tauri/src/app_state.rs @@ -1306,6 +1306,10 @@ impl AppState { let Some(position) = positions.get(index) else { continue; }; + + if !position.sound_enabled.unwrap_or(false) { + continue; + } let Some(sound_path) = position.sound_path.as_ref() else { continue; }; diff --git a/src-tauri/src/commands/sound.rs b/src-tauri/src/commands/sound.rs index 99b5d9a..9cca15b 100644 --- a/src-tauri/src/commands/sound.rs +++ b/src-tauri/src/commands/sound.rs @@ -1,9 +1,54 @@ +use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _}; use rfd::FileDialog; -use serde::Serialize; -use std::fs; -use tauri::Manager; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{HashMap, HashSet}, + fs, + path::{Path, PathBuf}, + time::SystemTime, +}; +use tauri::{Emitter, Manager, State}; use uuid::Uuid; +use crate::app_state::AppState; + +const SUPPORTED_SOUND_EXTENSIONS: [&str; 8] = + ["wav", "mp3", "ogg", "flac", "m4a", "aac", "aif", "aiff"]; +const SOUND_LIBRARY_FILE_NAME: &str = ".sound-library.json"; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum SoundSource { + Builtin, + Local, +} + +fn default_sound_source() -> SoundSource { + SoundSource::Local +} + +fn default_enabled() -> bool { + true +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct SoundLibraryEntry { + #[serde(default = "default_enabled")] + enabled: bool, + #[serde(default = "default_sound_source")] + source: SoundSource, +} + +impl Default for SoundLibraryEntry { + fn default() -> Self { + Self { + enabled: true, + source: SoundSource::Local, + } + } +} + #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct SoundLoadResponse { @@ -14,14 +59,54 @@ pub struct SoundLoadResponse { pub sound_path: Option, } +#[derive(Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SoundListItem { + pub sound_path: String, + pub file_name: String, + pub size_bytes: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub modified_at_ms: Option, + pub enabled: bool, + pub source: SoundSource, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SoundSaveProcessedWavRequest { + pub wav_base64: String, + pub file_name: Option, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SoundSaveProcessedWavResponse { + pub success: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub sound_path: Option, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SoundSetEnabledResponse { + pub success: bool, + pub sound_path: String, + pub enabled: bool, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SoundDeleteResponse { + pub success: bool, +} + /// 로컬 사운드 파일을 선택하고 appData/sounds 디렉토리로 복사한 뒤 경로 반환 #[tauri::command(permission = "dmnote-allow-all")] pub fn sound_load(app: tauri::AppHandle) -> Result { let picked = FileDialog::new() - .add_filter( - "Audio", - &["wav", "mp3", "ogg", "flac", "m4a", "aac", "aif", "aiff"], - ) + .add_filter("Audio", &SUPPORTED_SOUND_EXTENSIONS) .pick_file(); let Some(path) = picked else { @@ -38,19 +123,361 @@ pub fn sound_load(app: tauri::AppHandle) -> Result { .unwrap_or("wav") .to_lowercase(); - let data_dir = app - .path() - .app_data_dir() - .map_err(|e| format!("앱 데이터 디렉토리 확인 실패: {e}"))?; - let sounds_dir = data_dir.join("sounds"); - fs::create_dir_all(&sounds_dir).map_err(|e| format!("사운드 디렉토리 생성 실패: {e}"))?; - + let sounds_dir = ensure_sounds_dir(&app)?; let dest_path = sounds_dir.join(format!("{}.{}", Uuid::new_v4(), ext)); fs::copy(&path, &dest_path).map_err(|e| format!("사운드 파일 복사 실패: {e}"))?; + let dest_path_str = normalize_path_string(&dest_path); + upsert_sound_library_entry( + &sounds_dir, + &dest_path_str, + SoundLibraryEntry { + enabled: true, + source: SoundSource::Local, + }, + )?; + Ok(SoundLoadResponse { success: true, error: None, - sound_path: Some(dest_path.to_string_lossy().to_string()), + sound_path: Some(dest_path_str), }) } + +#[tauri::command(permission = "dmnote-allow-all")] +pub fn sound_list(app: tauri::AppHandle) -> Result, String> { + let sounds_dir = ensure_sounds_dir(&app)?; + let mut items = Vec::new(); + let mut library = load_sound_library(&sounds_dir); + let mut seen_paths = HashSet::new(); + let mut library_mutated = false; + + let entries = + fs::read_dir(&sounds_dir).map_err(|e| format!("사운드 디렉토리 읽기 실패: {e}"))?; + + for entry_result in entries { + let Ok(entry) = entry_result else { + continue; + }; + + let path = entry.path(); + if !path.is_file() || !is_supported_sound_file(&path) { + continue; + } + + let Ok(metadata) = entry.metadata() else { + continue; + }; + + let file_name = path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default() + .to_string(); + if file_name.is_empty() { + continue; + } + + let path_key = normalize_path_string(&path); + seen_paths.insert(path_key.clone()); + + let entry_meta = library + .entry(path_key.clone()) + .or_insert_with(|| { + library_mutated = true; + SoundLibraryEntry::default() + }) + .clone(); + + let modified_at_ms = metadata.modified().ok().and_then(|modified| { + modified + .duration_since(SystemTime::UNIX_EPOCH) + .ok() + .map(|duration| duration.as_millis() as u64) + }); + + items.push(SoundListItem { + sound_path: path_key, + file_name, + size_bytes: metadata.len(), + modified_at_ms, + enabled: entry_meta.enabled, + source: entry_meta.source, + }); + } + + let stale_keys: Vec = library + .keys() + .filter(|key| !seen_paths.contains(*key)) + .cloned() + .collect(); + if !stale_keys.is_empty() { + for key in stale_keys { + library.remove(&key); + } + library_mutated = true; + } + + if library_mutated { + save_sound_library(&sounds_dir, &library)?; + } + + items.sort_by(|a, b| { + b.modified_at_ms + .unwrap_or_default() + .cmp(&a.modified_at_ms.unwrap_or_default()) + .then_with(|| a.file_name.cmp(&b.file_name)) + }); + + Ok(items) +} + +#[tauri::command(permission = "dmnote-allow-all")] +pub fn sound_set_enabled( + app: tauri::AppHandle, + sound_path: String, + enabled: bool, +) -> Result { + let sounds_dir = ensure_sounds_dir(&app)?; + let validated_path = validate_sound_path(&sounds_dir, &sound_path)?; + if !validated_path.exists() { + return Err("대상 사운드 파일이 존재하지 않습니다.".to_string()); + } + + let path_key = normalize_path_string(&validated_path); + upsert_sound_library_entry( + &sounds_dir, + &path_key, + SoundLibraryEntry { + enabled, + source: SoundSource::Local, + }, + )?; + + Ok(SoundSetEnabledResponse { + success: true, + sound_path: path_key, + enabled, + }) +} + +#[tauri::command(permission = "dmnote-allow-all")] +pub fn sound_delete( + app: tauri::AppHandle, + state: State<'_, AppState>, + sound_path: String, +) -> Result { + let sounds_dir = ensure_sounds_dir(&app)?; + let validated_path = validate_sound_path(&sounds_dir, &sound_path)?; + let path_key = normalize_path_string(&validated_path); + + if validated_path.exists() { + fs::remove_file(&validated_path).map_err(|e| format!("사운드 파일 삭제 실패: {e}"))?; + } + + let mut library = load_sound_library(&sounds_dir); + if library.remove(&path_key).is_some() { + save_sound_library(&sounds_dir, &library)?; + } + + clear_deleted_sound_references(&state, &app, &path_key)?; + + Ok(SoundDeleteResponse { success: true }) +} + +#[tauri::command(permission = "dmnote-allow-all")] +pub fn sound_save_processed_wav( + app: tauri::AppHandle, + request: SoundSaveProcessedWavRequest, +) -> Result { + let encoded = request.wav_base64.trim(); + if encoded.is_empty() { + return Ok(SoundSaveProcessedWavResponse { + success: false, + error: Some("사운드 데이터가 비어 있습니다.".to_string()), + sound_path: None, + }); + } + + let wav_bytes = BASE64_STANDARD + .decode(encoded) + .map_err(|e| format!("사운드 데이터 디코딩 실패: {e}"))?; + + let is_valid_wav = wav_bytes.len() >= 12 + && wav_bytes.get(0..4) == Some(b"RIFF") + && wav_bytes.get(8..12) == Some(b"WAVE"); + if !is_valid_wav { + return Ok(SoundSaveProcessedWavResponse { + success: false, + error: Some("유효한 WAV 데이터가 아닙니다.".to_string()), + sound_path: None, + }); + } + + let sounds_dir = ensure_sounds_dir(&app)?; + + let base_name = request + .file_name + .as_deref() + .map(|n| n.trim()) + .filter(|n| !n.is_empty()) + .map(|n| { + let sanitized: String = n + .chars() + .map(|c| if c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|' { '_' } else { c }) + .collect(); + sanitized + }) + .unwrap_or_else(|| Uuid::new_v4().to_string()); + + let mut dest_path = sounds_dir.join(format!("{}.wav", base_name)); + if dest_path.exists() { + dest_path = sounds_dir.join(format!("{}_{}.wav", base_name, Uuid::new_v4())); + } + fs::write(&dest_path, wav_bytes).map_err(|e| format!("편집된 사운드 저장 실패: {e}"))?; + + let dest_path_str = normalize_path_string(&dest_path); + upsert_sound_library_entry( + &sounds_dir, + &dest_path_str, + SoundLibraryEntry { + enabled: true, + source: SoundSource::Local, + }, + )?; + + Ok(SoundSaveProcessedWavResponse { + success: true, + error: None, + sound_path: Some(dest_path_str), + }) +} + +fn clear_deleted_sound_references( + state: &State<'_, AppState>, + app: &tauri::AppHandle, + deleted_path: &str, +) -> Result<(), String> { + let mut changed = false; + let updated = state + .store + .update(|store| { + for positions in store.key_positions.values_mut() { + for position in positions.iter_mut() { + if position.sound_path.as_deref() == Some(deleted_path) { + position.sound_path = None; + position.sound_enabled = Some(false); + changed = true; + } + } + } + + for positions in store.stat_positions.values_mut() { + for stat_position in positions.iter_mut() { + if stat_position.position.sound_path.as_deref() == Some(deleted_path) { + stat_position.position.sound_path = None; + stat_position.position.sound_enabled = Some(false); + changed = true; + } + } + } + + for positions in store.graph_positions.values_mut() { + for graph_position in positions.iter_mut() { + if graph_position.position.sound_path.as_deref() == Some(deleted_path) { + graph_position.position.sound_path = None; + graph_position.position.sound_enabled = Some(false); + changed = true; + } + } + } + }) + .map_err(|e| format!("사운드 참조 정리 실패: {e}"))?; + + if changed { + app.emit("positions:changed", &updated.key_positions) + .map_err(|e| format!("positions:changed emit 실패: {e}"))?; + app.emit("statPositions:changed", &updated.stat_positions) + .map_err(|e| format!("statPositions:changed emit 실패: {e}"))?; + app.emit("graphPositions:changed", &updated.graph_positions) + .map_err(|e| format!("graphPositions:changed emit 실패: {e}"))?; + } + + Ok(()) +} + +fn sound_library_path(sounds_dir: &Path) -> PathBuf { + sounds_dir.join(SOUND_LIBRARY_FILE_NAME) +} + +fn load_sound_library(sounds_dir: &Path) -> HashMap { + let path = sound_library_path(sounds_dir); + if !path.exists() { + return HashMap::new(); + } + + let raw = match fs::read_to_string(&path) { + Ok(value) => value, + Err(error) => { + log::warn!("[Sound] failed to read sound library file: {error}"); + return HashMap::new(); + } + }; + + serde_json::from_str::>(&raw).unwrap_or_else(|error| { + log::warn!("[Sound] failed to parse sound library file: {error}"); + HashMap::new() + }) +} + +fn save_sound_library(sounds_dir: &Path, library: &HashMap) -> Result<(), String> { + let path = sound_library_path(sounds_dir); + let serialized = + serde_json::to_string_pretty(library).map_err(|e| format!("사운드 라이브러리 직렬화 실패: {e}"))?; + fs::write(path, serialized).map_err(|e| format!("사운드 라이브러리 저장 실패: {e}"))?; + Ok(()) +} + +fn upsert_sound_library_entry( + sounds_dir: &Path, + sound_path: &str, + entry: SoundLibraryEntry, +) -> Result<(), String> { + let mut library = load_sound_library(sounds_dir); + library.insert(sound_path.to_string(), entry); + save_sound_library(sounds_dir, &library) +} + +fn validate_sound_path(sounds_dir: &Path, sound_path: &str) -> Result { + let path = PathBuf::from(sound_path); + if !path.is_absolute() { + return Err("절대 경로만 허용됩니다.".to_string()); + } + if !path.starts_with(sounds_dir) { + return Err("appData/sounds 외부 경로에는 접근할 수 없습니다.".to_string()); + } + Ok(path) +} + +fn normalize_path_string(path: &Path) -> String { + path.to_string_lossy().to_string() +} + +fn ensure_sounds_dir(app: &tauri::AppHandle) -> Result { + let data_dir = app + .path() + .app_data_dir() + .map_err(|e| format!("앱 데이터 디렉토리 확인 실패: {e}"))?; + let sounds_dir = data_dir.join("sounds"); + fs::create_dir_all(&sounds_dir).map_err(|e| format!("사운드 디렉토리 생성 실패: {e}"))?; + Ok(sounds_dir) +} + +fn is_supported_sound_file(path: &Path) -> bool { + let Some(ext) = path.extension().and_then(|ext| ext.to_str()) else { + return false; + }; + SUPPORTED_SOUND_EXTENSIONS + .iter() + .any(|allowed| ext.eq_ignore_ascii_case(allowed)) +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index bd7dabc..7ebc32b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -196,6 +196,10 @@ fn main() { commands::font::font_load, commands::image::image_load, commands::sound::sound_load, + commands::sound::sound_list, + commands::sound::sound_set_enabled, + commands::sound::sound_delete, + commands::sound::sound_save_processed_wav, commands::js::js_get, commands::js::js_get_use, commands::js::js_toggle, diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 3a3c4a7..e125916 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -123,6 +123,9 @@ pub struct KeyPosition { pub active_image: Option, #[serde(default)] pub inactive_image: Option, + /// 키별 사운드 활성화 여부 (기본값 false) + #[serde(default)] + pub sound_enabled: Option, /// 키 입력 시 재생할 로컬 사운드 파일 경로 #[serde(default)] pub sound_path: Option, diff --git a/src/renderer/api/dmnoteApi.ts b/src/renderer/api/dmnoteApi.ts index adfe0ec..07cfd0e 100644 --- a/src/renderer/api/dmnoteApi.ts +++ b/src/renderer/api/dmnoteApi.ts @@ -310,6 +310,23 @@ const api: DMNoteAPI = { }, sound: { load: () => invoke("sound_load"), + list: () => invoke("sound_list"), + setEnabled: (soundPath: string, enabled: boolean) => + invoke( + "sound_set_enabled", + { soundPath, enabled }, + ), + remove: (soundPath: string) => + invoke("sound_delete", { + soundPath, + }), + saveProcessedWav: (wavBase64: string, fileName?: string) => + invoke( + "sound_save_processed_wav", + { + request: { wavBase64, fileName }, + }, + ), setLatencyLogging: (enabled: boolean) => invoke("key_sound_set_latency_logging", { enabled }).then(() => undefined), }, diff --git a/src/renderer/components/main/Grid/PropertiesPanel.tsx b/src/renderer/components/main/Grid/PropertiesPanel.tsx index 89bea11..375ce6e 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel.tsx +++ b/src/renderer/components/main/Grid/PropertiesPanel.tsx @@ -1780,6 +1780,7 @@ const PropertiesPanel: React.FC = ({ const { handleBatchStyleChange, handleBatchStyleChangeComplete, + handleKeyOnlyStyleChangeComplete, handleBatchAlign, handleBatchDistribute, handleBatchSpacing, @@ -2796,10 +2797,7 @@ const PropertiesPanel: React.FC = ({
0 && - selectedKeyElements.length === selectedBatchStyleElements.length - } + showSoundControls={selectedKeyElements.length > 0} getMixedValue={styleMixedValueGetter} getSelectedKeysData={styleSelectedDataGetter} afterSizeContent={ @@ -2919,6 +2917,10 @@ const PropertiesPanel: React.FC = ({ handleBatchStyleChangeComplete={ handleBatchStyleChangeComplete } + getKeyOnlyMixedValue={getMixedValueKeysOnly} + handleKeyOnlyStyleChangeComplete={ + handleKeyOnlyStyleChangeComplete + } showBatchImagePicker={showBatchImagePicker} onToggleBatchImagePicker={() => setShowBatchImagePicker(!showBatchImagePicker) diff --git a/src/renderer/components/main/Grid/PropertiesPanel/BatchStyleTabContent.tsx b/src/renderer/components/main/Grid/PropertiesPanel/BatchStyleTabContent.tsx index 8ad73dc..4c72e3d 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel/BatchStyleTabContent.tsx +++ b/src/renderer/components/main/Grid/PropertiesPanel/BatchStyleTabContent.tsx @@ -11,6 +11,7 @@ import { import Checkbox from "@components/main/common/Checkbox"; import FontPicker from "@components/main/Modal/content/FontPicker"; import FontManagerModal from "@components/main/Modal/content/FontManagerModal"; +import SoundPicker from "@components/main/Modal/content/SoundPicker"; const SPACING_COMMIT_DEBOUNCE_MS = 80; const SPACING_COMMIT_EPSILON = 0.0001; @@ -57,6 +58,15 @@ interface BatchStyleTabContentProps { property: keyof KeyPosition, value: any, ) => void; + // 키 전용 (사운드 등) + getKeyOnlyMixedValue?: ( + getter: (pos: KeyPosition) => T | undefined, + defaultValue: T, + ) => { isMixed: boolean; value: T }; + handleKeyOnlyStyleChangeComplete?: ( + property: keyof KeyPosition, + value: any, + ) => void; // 이미지 피커 showBatchImagePicker: boolean; onToggleBatchImagePicker: () => void; @@ -83,6 +93,8 @@ const BatchStyleTabContent: React.FC = ({ handleBatchResize, handleBatchStyleChange, handleBatchStyleChangeComplete, + getKeyOnlyMixedValue, + handleKeyOnlyStyleChangeComplete, showBatchImagePicker, onToggleBatchImagePicker, batchImageButtonRef, @@ -92,7 +104,7 @@ const BatchStyleTabContent: React.FC = ({ }) => { const [colorState, setColorState] = useState<"idle" | "active">("idle"); const [showFontPicker, setShowFontPicker] = useState(false); - const [isLoadingSound, setIsLoadingSound] = useState(false); + const [showSoundPicker, setShowSoundPicker] = useState(false); // 간격 입력 세션 동안 첫 변경만 히스토리를 남기고 이후는 skipHistory로 묶는다. const lastSpacingRef = useRef(null); @@ -171,6 +183,7 @@ const BatchStyleTabContent: React.FC = ({ }, []); const [showFontManager, setShowFontManager] = useState(false); const fontButtonRef = useRef(null); + const soundButtonRef = useRef(null); // displayText의 실제 표시 값(displayText || keyInfo.displayName)을 기준으로 Mixed 판단 const getDisplayTextMixed = (): { isMixed: boolean; value: string } => { @@ -191,27 +204,6 @@ const BatchStyleTabContent: React.FC = ({ return { isMixed, value: firstValue }; }; - const handleBatchSoundLoad = useCallback(async () => { - if (isLoadingSound) return; - setIsLoadingSound(true); - try { - const result = await window.api.sound.load(); - if (!result.success || !result.soundPath) return; - handleBatchStyleChangeComplete("soundPath", result.soundPath); - } catch (error) { - console.error("Failed to load key sound", error); - } finally { - setIsLoadingSound(false); - } - }, [handleBatchStyleChangeComplete, isLoadingSound]); - - const getSoundDisplayName = useCallback((soundPath?: string): string => { - if (!soundPath || !soundPath.trim()) return ""; - const separators = /[\\/]/g; - const segments = soundPath.split(separators); - return segments[segments.length - 1] || soundPath; - }, []); - return ( <> {/* 정렬 */} @@ -857,69 +849,68 @@ const BatchStyleTabContent: React.FC = ({ )} - {showSoundControls && ( - <> - + {showSoundControls && (() => { + const soundMixedValue = getKeyOnlyMixedValue ?? getMixedValue; + const soundChangeComplete = handleKeyOnlyStyleChangeComplete ?? handleBatchStyleChangeComplete; + return ( + <> + - - {getMixedValue((pos) => pos.soundPath, "").isMixed ? ( - Mixed - ) : null} -
+ + {soundMixedValue((pos) => pos.soundEnabled, false).isMixed ? ( + Mixed + ) : null} + pos.soundEnabled, false).value} + onChange={() => { + const current = soundMixedValue( + (pos) => pos.soundEnabled, + false, + ).value; + soundChangeComplete("soundEnabled", !current); + }} + /> + + + + {soundMixedValue((pos) => pos.soundPath, "").isMixed ? ( + Mixed + ) : null} - {getMixedValue((pos) => pos.soundPath, "").value ? ( - - ) : null} -
-
+ - {(() => { - const soundPathState = getMixedValue((pos) => pos.soundPath, ""); - if (soundPathState.isMixed || !soundPathState.value) return null; - return ( - - - {getSoundDisplayName(soundPathState.value)} - - - ); - })()} - - - {getMixedValue((pos) => pos.soundVolume, 100).isMixed ? ( - Mixed - ) : null} - pos.soundVolume, 100).value} - onChange={(value) => - handleBatchStyleChangeComplete( - "soundVolume", - Math.max(0, Math.min(100, value)), - ) - } - suffix="%" - min={0} - max={100} - isMixed={getMixedValue((pos) => pos.soundVolume, 100).isMixed} - /> - - - )} + + {soundMixedValue((pos) => pos.soundVolume, 100).isMixed ? ( + Mixed + ) : null} + pos.soundVolume, 100).value} + onChange={(value) => + soundChangeComplete( + "soundVolume", + Math.max(0, Math.min(100, value)), + ) + } + suffix="%" + min={0} + max={100} + isMixed={soundMixedValue((pos) => pos.soundVolume, 100).isMixed} + /> + + + ); + })()} {/* FontPicker */} {!hideFontControls && showFontPicker && ( @@ -939,6 +930,22 @@ const BatchStyleTabContent: React.FC = ({ /> )} + {/* SoundPicker */} + {showSoundControls && showSoundPicker && ( + pos.soundPath, "").value || null} + onSoundSelect={(soundPath) => { + (handleKeyOnlyStyleChangeComplete ?? handleBatchStyleChangeComplete)("soundPath", soundPath || ""); + }} + onClose={() => setShowSoundPicker(false)} + interactiveRefs={[soundButtonRef]} + previewVolume={(getKeyOnlyMixedValue ?? getMixedValue)((pos) => pos.soundVolume, 100).value} + /> + )} + {/* FontManagerModal */} {!hideFontControls && showFontManager && ( = ({ label, children, }) => ( -
+

{label}

{children}
diff --git a/src/renderer/components/main/Grid/PropertiesPanel/StyleTabContent.tsx b/src/renderer/components/main/Grid/PropertiesPanel/StyleTabContent.tsx index e80b963..1d9522a 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel/StyleTabContent.tsx +++ b/src/renderer/components/main/Grid/PropertiesPanel/StyleTabContent.tsx @@ -18,8 +18,8 @@ import ImagePicker from "../../Modal/content/ImagePicker"; import ColorPicker from "../../Modal/content/ColorPicker"; import FontPicker from "../../Modal/content/FontPicker"; import FontManagerModal from "../../Modal/content/FontManagerModal"; +import SoundPicker from "../../Modal/content/SoundPicker"; import Checkbox from "../../common/Checkbox"; -import { useFontStore } from "@stores/useFontStore"; // 피커 타겟 타입 type PickerTarget = @@ -106,11 +106,10 @@ const StyleTabContent: React.FC = ({ const bgColorBtnRef = useRef(null); // 폰트 버튼 ref const fontButtonRef = useRef(null); + const soundButtonRef = useRef(null); // 폰트 관리 모달 상태 const [showFontManager, setShowFontManager] = useState(false); - const [isLoadingSound, setIsLoadingSound] = useState(false); - // 폰트 스토어 - const { getAllFonts } = useFontStore(); + const [showSoundPicker, setShowSoundPicker] = useState(false); const borderColorBtnRef = useRef(null); const fontColorBtnRef = useRef(null); const internalImageButtonRef = useRef(null); @@ -408,33 +407,6 @@ const StyleTabContent: React.FC = ({ [keyIndex, onKeyPreview, onKeyUpdate], ); - const handleSoundLoad = useCallback(async () => { - if (isLoadingSound) return; - setIsLoadingSound(true); - try { - const result = await window.api.sound.load(); - if (!result.success || !result.soundPath) return; - onKeyPreview?.(keyIndex, { soundPath: result.soundPath }); - onKeyUpdate({ index: keyIndex, soundPath: result.soundPath }); - } catch (error) { - console.error("Failed to load key sound", error); - } finally { - setIsLoadingSound(false); - } - }, [isLoadingSound, keyIndex, onKeyPreview, onKeyUpdate]); - - const handleSoundReset = useCallback(() => { - onKeyPreview?.(keyIndex, { soundPath: "" }); - onKeyUpdate({ index: keyIndex, soundPath: "" }); - }, [keyIndex, onKeyPreview, onKeyUpdate]); - - const getSoundDisplayName = useCallback((soundPath?: string): string => { - if (!soundPath || !soundPath.trim()) return ""; - const separators = /[\\/]/g; - const segments = soundPath.split(separators); - return segments[segments.length - 1] || soundPath; - }, []); - // 표시 텍스트 핸들러 const handleDisplayTextChange = useCallback( (value: string) => { @@ -759,37 +731,34 @@ const StyleTabContent: React.FC = ({ <> - -
- - {keyPosition.soundPath ? ( - - ) : null} -
+ + { + const nextEnabled = !(keyPosition.soundEnabled ?? false); + onKeyPreview?.(keyIndex, { soundEnabled: nextEnabled }); + onKeyUpdate({ index: keyIndex, soundEnabled: nextEnabled }); + }} + /> - {keyPosition.soundPath ? ( - - - {getSoundDisplayName(keyPosition.soundPath)} - - - ) : null} + + + = ({ /> )} + {/* SoundPicker */} + {showSoundControls && showSoundPicker && ( + { + const nextPath = soundPath || ""; + onKeyPreview?.(keyIndex, { soundPath: nextPath }); + onKeyUpdate({ index: keyIndex, soundPath: nextPath }); + }} + onClose={() => setShowSoundPicker(false)} + interactiveRefs={[soundButtonRef]} + previewVolume={keyPosition.soundVolume ?? 100} + /> + )} + {/* FontManagerModal */} {showFontManager && ( { + const keyUpdates = selectedKeys + .filter((el) => el.index !== undefined) + .map((el) => ({ index: el.index!, [property]: value })) as Array< + { index: number } & Partial + >; + dispatchKeyUpdates(keyUpdates, "commit"); + }, + [selectedKeys, dispatchKeyUpdates], + ); + return { handleBatchStyleChange, handleBatchStyleChangeComplete, + handleKeyOnlyStyleChangeComplete, handleBatchAlign, handleBatchDistribute, handleBatchSpacing, diff --git a/src/renderer/components/main/Modal/content/CommonListPickerPopup.tsx b/src/renderer/components/main/Modal/content/CommonListPickerPopup.tsx new file mode 100644 index 0000000..0541a7a --- /dev/null +++ b/src/renderer/components/main/Modal/content/CommonListPickerPopup.tsx @@ -0,0 +1,249 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import FloatingPopup from "../FloatingPopup"; +import { useLenis } from "@hooks/useLenis"; +import Dropdown from "@components/main/common/Dropdown"; + +const SCROLL_CONTENT_GUTTER = 4; + +type FilterOption = { + value: string; + label: string; +}; + +interface CommonListPickerPopupProps { + open: boolean; + referenceRef: React.RefObject; + panelElement?: HTMLElement | null; + onClose: () => void; + interactiveRefs?: Array>; + widthClass?: string; + estimatedWidth?: number; + estimatedHeight?: number; + searchQuery: string; + onSearchQueryChange: (value: string) => void; + searchPlaceholder: string; + filterOptions?: FilterOption[]; + filterValue?: string; + onFilterChange?: (value: string) => void; + items: T[]; + renderItem: (item: T) => React.ReactNode; + emptyText: string; + isLoading?: boolean; + loadingText?: string; + errorText?: string; + listHeightClass?: string; + onAdd: () => void; + addButtonContent: React.ReactNode; +} + +export default function CommonListPickerPopup({ + open, + referenceRef, + panelElement = null, + onClose, + interactiveRefs = [], + widthClass = "w-[156px]", + estimatedWidth = 164, + estimatedHeight = 280, + searchQuery, + onSearchQueryChange, + searchPlaceholder, + filterOptions, + filterValue, + onFilterChange, + items, + renderItem, + emptyText, + isLoading = false, + loadingText = "로딩...", + errorText = "", + listHeightClass = "min-h-[120px] h-[120px]", + onAdd, + addButtonContent, +}: CommonListPickerPopupProps) { + const containerRef = useRef(null); + const [fixedPosition, setFixedPosition] = useState<{ + x: number; + y: number; + } | null>(null); + const [hasOverflow, setHasOverflow] = useState(false); + + const { + scrollContainerRef: scrollRef, + wrapperElement, + lenisInstance, + scrollbarWidth, + } = useLenis(); + + useEffect(() => { + if (!open) { + setFixedPosition(null); + setHasOverflow(false); + return; + } + + if (!panelElement) { + setFixedPosition(null); + return; + } + + requestAnimationFrame(() => { + const panelRect = panelElement.getBoundingClientRect(); + const popupEl = containerRef.current; + const popupWidth = popupEl ? popupEl.offsetWidth : estimatedWidth; + const popupHeight = popupEl ? popupEl.offsetHeight : estimatedHeight; + + const gap = 5; + const padding = 5; + const panelBottomPadding = 20; + + let fixedX = panelRect.left - popupWidth - gap; + if (fixedX < padding) { + fixedX = padding; + } + + let fixedY = panelRect.bottom - panelBottomPadding - popupHeight; + if (fixedY < padding) { + fixedY = padding; + } + + setFixedPosition({ x: fixedX, y: fixedY }); + }); + }, [estimatedHeight, estimatedWidth, open, panelElement]); + + useEffect(() => { + if (!open) return; + const rafId = requestAnimationFrame(() => { + lenisInstance.current?.resize?.(); + }); + return () => cancelAnimationFrame(rafId); + }, [open, items.length, filterValue, searchQuery, lenisInstance]); + + useEffect(() => { + if (!open) { + setHasOverflow(false); + return; + } + + const wrapper = wrapperElement; + if (!wrapper) return; + + const updateOverflow = () => { + const nextHasOverflow = wrapper.scrollHeight > wrapper.clientHeight; + setHasOverflow((prev) => + prev === nextHasOverflow ? prev : nextHasOverflow, + ); + }; + + const rafId = requestAnimationFrame(updateOverflow); + const resizeObserver = new ResizeObserver(updateOverflow); + resizeObserver.observe(wrapper); + + const contentEl = wrapper.firstElementChild; + if (contentEl instanceof HTMLElement) { + resizeObserver.observe(contentEl); + } + + return () => { + cancelAnimationFrame(rafId); + resizeObserver.disconnect(); + }; + }, [open, wrapperElement, items.length]); + + const scrollbarCompensation = useMemo( + () => (hasOverflow ? scrollbarWidth + SCROLL_CONTENT_GUTTER : 0), + [hasOverflow, scrollbarWidth], + ); + + const effectiveOffsetY = fixedPosition ? 0 : -93; + + return ( + +
+ onSearchQueryChange(event.target.value)} + placeholder={searchPlaceholder} + className="w-full h-[23px] px-[8px] bg-[#2A2A30] rounded-[7px] border-[1px] border-[#3A3943] text-[#DBDEE8] text-style-2 placeholder-[#6F6E7A] focus:border-[#459BF8] outline-none" + /> + + {filterOptions && filterValue !== undefined && onFilterChange ? ( + + ) : null} + +
+ +
0 + ? `calc(100% + ${scrollbarCompensation}px)` + : undefined, + marginRight: + scrollbarCompensation > 0 + ? `-${scrollbarCompensation}px` + : undefined, + }} + > +
+ {items.length === 0 ? ( +
+ {emptyText} +
+ ) : ( + items.map((item) => renderItem(item)) + )} +
+
+ +
+ + + + {isLoading ? ( +

{loadingText}

+ ) : null} + + {errorText ? ( +

{errorText}

+ ) : null} +
+ + ); +} diff --git a/src/renderer/components/main/Modal/content/FontPicker.tsx b/src/renderer/components/main/Modal/content/FontPicker.tsx index 65b144e..f64bf42 100644 --- a/src/renderer/components/main/Modal/content/FontPicker.tsx +++ b/src/renderer/components/main/Modal/content/FontPicker.tsx @@ -1,18 +1,9 @@ -import React, { - useState, - useRef, - useLayoutEffect, - useEffect, - useMemo, - useCallback, -} from "react"; +import React, { useState, useMemo, useCallback } from "react"; import { useTranslation } from "@contexts/I18nContext"; -import { useLenis } from "@hooks/useLenis"; -import FloatingPopup from "../FloatingPopup"; -import Dropdown from "@components/main/common/Dropdown"; import { useFontStore } from "@stores/useFontStore"; -import type { CustomFont, FontType } from "@src/types/fonts"; +import type { CustomFont } from "@src/types/fonts"; import PlusIcon from "@assets/svgs/plus2.svg"; +import CommonListPickerPopup from "./CommonListPickerPopup"; interface FontPickerProps { open: boolean; @@ -26,7 +17,6 @@ interface FontPickerProps { } type FilterType = "all" | "builtin" | "local" | "web"; -const SCROLL_CONTENT_GUTTER = 4; export default function FontPicker({ open, @@ -41,23 +31,9 @@ export default function FontPicker({ const { t } = useTranslation(); const [searchQuery, setSearchQuery] = useState(""); const [filterType, setFilterType] = useState("all"); - const [hasOverflow, setHasOverflow] = useState(false); - const pickerContainerRef = useRef(null); - const [fixedPosition, setFixedPosition] = useState<{ - x: number; - y: number; - } | null>(null); const { builtinFonts, customFonts } = useFontStore(); - // Lenis smooth scroll 적용 - const { - scrollContainerRef: scrollRef, - wrapperElement, - lenisInstance, - scrollbarWidth, - } = useLenis(); - // 필터링된 폰트 목록 const filteredFonts = useMemo(() => { let fonts: CustomFont[] = [...builtinFonts, ...customFonts].filter( @@ -103,45 +79,6 @@ export default function FontPicker({ [t], ); - // 고정 위치 계산 (ImagePicker와 동일한 로직) - useLayoutEffect(() => { - if (!open) { - setFixedPosition(null); - return; - } - - if (panelElement) { - requestAnimationFrame(() => { - const panelRect = panelElement.getBoundingClientRect(); - const pickerEl = pickerContainerRef.current; - const pickerWidth = pickerEl ? pickerEl.offsetWidth : 164; - const pickerHeight = pickerEl ? pickerEl.offsetHeight : 280; - - const colorPickerSolidHeight = 264; - const gap = 5; - const padding = 5; - - let fixedX = panelRect.left - pickerWidth - gap; - if (fixedX < padding) { - fixedX = padding; - } - - const panelBottomPadding = 20; - const solidPickerBottom = panelRect.bottom - panelBottomPadding; - let fixedY = solidPickerBottom - pickerHeight; - if (fixedY < padding) { - fixedY = padding; - } - - setFixedPosition({ x: fixedX, y: fixedY }); - }); - } else { - setFixedPosition(null); - } - }, [open, panelElement]); - - const effectiveOffsetY = fixedPosition ? 0 : -93; - const handleFontClick = useCallback( (font: CustomFont) => { onFontSelect(font.name); @@ -149,152 +86,47 @@ export default function FontPicker({ [onFontSelect], ); - useEffect(() => { - if (!open) return; - const rafId = requestAnimationFrame(() => { - lenisInstance.current?.resize?.(); - }); - return () => cancelAnimationFrame(rafId); - }, [open, filteredFonts.length, filterType, searchQuery, lenisInstance]); - - useEffect(() => { - if (!open) { - setHasOverflow(false); - return; - } - - const wrapper = wrapperElement; - if (!wrapper) return; - - const updateOverflow = () => { - const nextHasOverflow = wrapper.scrollHeight > wrapper.clientHeight; - setHasOverflow((prev) => - prev === nextHasOverflow ? prev : nextHasOverflow, - ); - }; - - const rafId = requestAnimationFrame(updateOverflow); - const resizeObserver = new ResizeObserver(updateOverflow); - resizeObserver.observe(wrapper); - - const contentEl = wrapper.firstElementChild; - if (contentEl instanceof HTMLElement) { - resizeObserver.observe(contentEl); - } - - return () => { - cancelAnimationFrame(rafId); - resizeObserver.disconnect(); - }; - }, [open, wrapperElement, filteredFonts.length]); - - const scrollbarCompensation = hasOverflow - ? scrollbarWidth + SCROLL_CONTENT_GUTTER - : 0; - return ( - open={open} referenceRef={referenceRef} - fixedX={fixedPosition?.x} - fixedY={fixedPosition?.y} - placement="right-start" - offset={32} - offsetY={effectiveOffsetY} - className="z-50" + panelElement={panelElement} interactiveRefs={interactiveRefs} onClose={onClose} - autoClose={false} - > -
- {/* 검색 입력 */} - setSearchQuery(e.target.value)} - placeholder={t("fontPicker.searchPlaceholder") || "검색..."} - className="w-full h-[23px] px-[8px] bg-[#2A2A30] rounded-[7px] border-[1px] border-[#3A3943] text-[#DBDEE8] text-style-2 placeholder-[#6F6E7A] focus:border-[#459BF8] outline-none" - /> - - {/* 필터 드롭다운 */} - setFilterType(value as FilterType)} - fullWidth - /> - -
- - {/* 폰트 리스트 */} -
0 - ? `calc(100% + ${scrollbarCompensation}px)` - : undefined, - marginRight: - scrollbarCompensation > 0 - ? `-${scrollbarCompensation}px` - : undefined, - }} - > -
setFilterType(value as FilterType)} + items={filteredFonts} + renderItem={(font) => { + const isSelected = effectiveSelectedFont + ? effectiveSelectedFont === font.name + : font.name === "SUIT-Regular"; + return ( + - ); - }) - )} -
-
- - {/* 구분선 */} -
- - {/* 폰트 추가 버튼 (탭 추가 버튼 스타일) */} - -
- + {font.displayName} + + ); + }} + emptyText={t("fontPicker.noFonts") || "폰트 없음"} + onAdd={onOpenManager} + addButtonContent={} + /> ); } diff --git a/src/renderer/components/main/Modal/content/SoundManagerModal.tsx b/src/renderer/components/main/Modal/content/SoundManagerModal.tsx new file mode 100644 index 0000000..6165e2d --- /dev/null +++ b/src/renderer/components/main/Modal/content/SoundManagerModal.tsx @@ -0,0 +1,319 @@ +import React, { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; +import Modal from "../Modal"; +import { useTranslation } from "@contexts/I18nContext"; +import type { SoundListItem } from "@src/types/api"; +import { useLenis } from "@hooks/useLenis"; +import { getScrollShadowState } from "@utils/scrollShadow"; +import SoundTrimModal from "./SoundTrimModal"; +import Checkbox from "@components/main/common/Checkbox"; +import TrashIcon from "@assets/svgs/trash.svg"; + +interface SoundManagerModalProps { + isOpen: boolean; + selectedSound: string | null; + onSelectSound: (soundPath: string | null) => void; + onClose: () => void; + previewVolume?: number; +} + +const MAX_SCROLL_HEIGHT = 195; + +export default function SoundManagerModal({ + isOpen, + selectedSound, + onSelectSound, + onClose, + previewVolume, +}: SoundManagerModalProps) { + const { t } = useTranslation(); + const [sounds, setSounds] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [loadError, setLoadError] = useState(""); + const [showTrimModal, setShowTrimModal] = useState(false); + + const contentRef = useRef(null); + const [scrollState, setScrollState] = useState({ + hasTopShadow: false, + hasBottomShadow: false, + }); + const [skipShadowTransition, setSkipShadowTransition] = useState(true); + const [containerHeight, setContainerHeight] = useState(null); + const [isScrollable, setIsScrollable] = useState(false); + const isFirstRender = useRef(true); + + const normalizedSelectedSound = useMemo( + () => (selectedSound || "").trim(), + [selectedSound], + ); + + const loadSounds = useCallback(async () => { + setIsLoading(true); + setLoadError(""); + try { + const nextSounds = await window.api.sound.list(); + setSounds(nextSounds); + } catch (error) { + console.error("Failed to load sound list", error); + setLoadError( + t("soundManager.loadFailed") || "사운드 목록 로드 실패", + ); + } finally { + setIsLoading(false); + } + }, [t]); + + useEffect(() => { + if (!isOpen) return; + void loadSounds(); + }, [isOpen, loadSounds]); + + const updateScrollState = useCallback((el: HTMLElement | null) => { + if (!el) return; + const nextState = getScrollShadowState(el, contentRef.current); + setScrollState((prev) => + prev.hasTopShadow === nextState.hasTopShadow && + prev.hasBottomShadow === nextState.hasBottomShadow + ? prev + : nextState, + ); + }, []); + + const { scrollContainerRef: scrollRef, wrapperElement } = useLenis({ + onScroll: () => updateScrollState(wrapperElement), + }); + + useLayoutEffect(() => { + if (!isOpen) return; + + setSkipShadowTransition(true); + setScrollState({ hasTopShadow: false, hasBottomShadow: false }); + setIsScrollable(false); + + const el = wrapperElement; + const contentEl = contentRef.current; + if (!el) return; + + const updateHeight = () => { + if (contentEl) { + const contentHeight = contentEl.scrollHeight; + setContainerHeight(Math.min(contentHeight, MAX_SCROLL_HEIGHT)); + setIsScrollable(contentHeight > MAX_SCROLL_HEIGHT); + } + }; + + const resizeObserver = new ResizeObserver(() => { + updateScrollState(el); + updateHeight(); + }); + + if (contentEl) { + resizeObserver.observe(contentEl); + } + resizeObserver.observe(el); + + updateScrollState(el); + updateHeight(); + + const rafId = requestAnimationFrame(() => { + setSkipShadowTransition(false); + isFirstRender.current = false; + }); + + return () => { + resizeObserver.disconnect(); + cancelAnimationFrame(rafId); + }; + }, [isOpen, sounds, wrapperElement, updateScrollState]); + + const handleToggle = useCallback( + async (item: SoundListItem, nextEnabled: boolean) => { + if (isSaving) return; + setIsSaving(true); + setSounds((prev) => + prev.map((s) => + s.soundPath === item.soundPath ? { ...s, enabled: nextEnabled } : s, + ), + ); + try { + await window.api.sound.setEnabled(item.soundPath, nextEnabled); + } catch (error) { + console.error("Failed to toggle sound enabled state", error); + setSounds((prev) => + prev.map((s) => + s.soundPath === item.soundPath + ? { ...s, enabled: !nextEnabled } + : s, + ), + ); + setLoadError( + t("soundManager.stateChangeFailed") || "사운드 상태 변경 실패", + ); + } finally { + setIsSaving(false); + } + }, + [isSaving, t], + ); + + const handleDelete = useCallback( + async (item: SoundListItem) => { + if (isSaving) return; + setIsSaving(true); + setSounds((prev) => prev.filter((s) => s.soundPath !== item.soundPath)); + if (normalizedSelectedSound === item.soundPath) { + onSelectSound(null); + } + try { + await window.api.sound.remove(item.soundPath); + } catch (error) { + console.error("Failed to delete sound", error); + setLoadError(t("soundManager.deleteFailed") || "사운드 삭제 실패"); + await loadSounds(); + } finally { + setIsSaving(false); + } + }, + [isSaving, loadSounds, normalizedSelectedSound, onSelectSound, t], + ); + + if (!isOpen) return null; + + return ( + <> + +
event.stopPropagation()} + > +
+
+ +
+
+ {sounds.length === 0 && !isLoading ? ( +
+ {t("soundManager.noSounds") || "사운드 없음"} +
+ ) : ( + sounds.map((item) => { + const isSelected = item.soundPath === normalizedSelectedSound; + return ( + + + {item.fileName} + +
+
+ { + void handleToggle(item, !item.enabled); + }} + /> +
+ + ); + }) + )} +
+
+ +
+
+ +
+ +
+ + +
+ + {isLoading ? ( +

+ {t("propertiesPanel.loading") || "로딩..."} +

+ ) : null} + + {loadError ? ( +

{loadError}

+ ) : null} +
+ + + setShowTrimModal(false)} + onSaved={(soundPath) => { + onSelectSound(soundPath); + setShowTrimModal(false); + void loadSounds(); + }} + previewVolume={previewVolume} + /> + + ); +} diff --git a/src/renderer/components/main/Modal/content/SoundPicker.tsx b/src/renderer/components/main/Modal/content/SoundPicker.tsx new file mode 100644 index 0000000..62fbc98 --- /dev/null +++ b/src/renderer/components/main/Modal/content/SoundPicker.tsx @@ -0,0 +1,138 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "@contexts/I18nContext"; +import type { SoundListItem } from "@src/types/api"; +import PlusIcon from "@assets/svgs/plus2.svg"; +import CommonListPickerPopup from "./CommonListPickerPopup"; +import SoundManagerModal from "./SoundManagerModal"; + +interface SoundPickerProps { + open: boolean; + referenceRef: React.RefObject; + panelElement?: HTMLElement | null; + selectedSound: string | null; + onSoundSelect: (soundPath: string | null) => void; + onClose: () => void; + interactiveRefs?: Array>; + previewVolume?: number; +} + +export default function SoundPicker({ + open, + referenceRef, + panelElement = null, + selectedSound, + onSoundSelect, + onClose, + interactiveRefs = [], + previewVolume, +}: SoundPickerProps) { + const { t } = useTranslation(); + const [searchQuery, setSearchQuery] = useState(""); + const [filterType, setFilterType] = useState<"all" | "local">("all"); + const [sounds, setSounds] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [loadError, setLoadError] = useState(""); + const [showManager, setShowManager] = useState(false); + + const normalizedSelectedSound = useMemo(() => (selectedSound || "").trim(), [selectedSound]); + + const loadSounds = useCallback(async () => { + setIsLoading(true); + setLoadError(""); + try { + const nextSounds = await window.api.sound.list(); + setSounds(nextSounds); + } catch (error) { + console.error("Failed to load sound list", error); + setLoadError("사운드 목록 로드 실패"); + } finally { + setIsLoading(false); + } + }, []); + + useEffect(() => { + if (!open) return; + void loadSounds(); + }, [open, loadSounds]); + + const filterOptions = useMemo( + () => [ + { value: "all", label: t("soundPicker.filterAll") || "전체" }, + { value: "local", label: t("soundPicker.filterLocal") || "로컬 사운드" }, + ], + [t], + ); + + const filteredSounds = useMemo(() => { + const query = searchQuery.trim().toLowerCase(); + + return sounds.filter((item) => { + if (!item.enabled) return false; + if (filterType === "local" && item.source !== "local") { + return false; + } + if (!query) return true; + return item.fileName.toLowerCase().includes(query); + }); + }, [filterType, searchQuery, sounds]); + + return ( + <> + + open={open} + referenceRef={referenceRef} + panelElement={panelElement} + interactiveRefs={interactiveRefs} + onClose={onClose} + widthClass="w-[156px]" + estimatedWidth={164} + estimatedHeight={276} + searchQuery={searchQuery} + onSearchQueryChange={setSearchQuery} + searchPlaceholder={t("soundPicker.searchPlaceholder") || "검색"} + filterOptions={filterOptions} + filterValue={filterType} + onFilterChange={(value) => setFilterType(value as "all" | "local")} + items={filteredSounds} + renderItem={(item) => { + const isSelected = item.soundPath === normalizedSelectedSound; + return ( + + ); + }} + emptyText={t("soundPicker.noSounds") || "사운드 없음"} + isLoading={isLoading} + loadingText={t("propertiesPanel.loading") || "로딩..."} + errorText={loadError} + onAdd={() => setShowManager(true)} + addButtonContent={} + /> + + { + onSoundSelect(path); + void loadSounds(); + }} + onClose={() => { + setShowManager(false); + void loadSounds(); + }} + previewVolume={previewVolume} + /> + + ); +} diff --git a/src/renderer/components/main/Modal/content/SoundTrimModal.tsx b/src/renderer/components/main/Modal/content/SoundTrimModal.tsx new file mode 100644 index 0000000..c36ccd9 --- /dev/null +++ b/src/renderer/components/main/Modal/content/SoundTrimModal.tsx @@ -0,0 +1,831 @@ +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { useTranslation } from "@contexts/I18nContext"; +import Modal from "../Modal"; + +interface SoundTrimModalProps { + isOpen: boolean; + onClose: () => void; + onSaved: (soundPath: string) => void; + previewVolume?: number; +} + +type DragTarget = "start" | "end" | null; + +const WAVEFORM_PEAK_COUNT = 1600; +const WAVEFORM_PAD_X = 12; +const HANDLE_PICK_PX = 10; +const MIN_TRIM_MS = 0; + +function formatSecLabel(ms: number): string { + return `${(ms / 1000).toFixed(2)}s`; +} + +function clamp(value: number, min: number, max: number): number { + if (value < min) return min; + if (value > max) return max; + return value; +} + +function createAudioContext(): AudioContext { + const ctor = window.AudioContext || (window as any).webkitAudioContext; + return new ctor(); +} + +async function decodeAudioFile(file: File): Promise { + const bytes = await file.arrayBuffer(); + const context = createAudioContext(); + try { + return await context.decodeAudioData(bytes.slice(0)); + } finally { + void context.close(); + } +} + +function extractWaveformPeaks( + buffer: AudioBuffer, + peakCount = WAVEFORM_PEAK_COUNT, +): Float32Array { + const channelData = buffer.getChannelData(0); + if (channelData.length === 0) { + return new Float32Array(peakCount).fill(0); + } + + const peaks = new Float32Array(peakCount); + const blockSize = Math.max(1, Math.floor(channelData.length / peakCount)); + + for (let i = 0; i < peakCount; i += 1) { + const start = i * blockSize; + const end = Math.min(channelData.length, start + blockSize); + let max = 0; + + for (let j = start; j < end; j += 1) { + const sample = Math.abs(channelData[j]); + if (sample > max) { + max = sample; + } + } + + peaks[i] = max; + } + + return peaks; +} + +function drawWaveform( + canvas: HTMLCanvasElement, + peaks: Float32Array, + startRatio: number, + endRatio: number, + playbackRatio: number | null = null, +) { + const dpr = window.devicePixelRatio || 1; + const width = canvas.clientWidth; + const height = canvas.clientHeight; + const displayWidth = Math.max(1, Math.floor(width * dpr)); + const displayHeight = Math.max(1, Math.floor(height * dpr)); + + if (canvas.width !== displayWidth || canvas.height !== displayHeight) { + canvas.width = displayWidth; + canvas.height = displayHeight; + } + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + ctx.clearRect(0, 0, width, height); + + const padX = WAVEFORM_PAD_X; + const drawableW = width - padX * 2; + const minBarHeight = 1; + const centerY = height / 2; + const startX = padX + startRatio * drawableW; + const endX = padX + endRatio * drawableW; + + ctx.fillStyle = "rgba(69, 155, 248, 0.10)"; + ctx.fillRect(startX, 0, Math.max(1, endX - startX), height); + + for (let px = 0; px < drawableW; px += 1) { + const peakIndex = Math.floor( + (px / Math.max(1, drawableW - 1)) * (peaks.length - 1), + ); + const amplitude = peaks[peakIndex] ?? 0; + const barHeight = Math.max(minBarHeight, amplitude * (height - 4)); + const y = centerY - barHeight / 2; + const x = padX + px; + + const inRange = x >= startX && x <= endX; + ctx.fillStyle = inRange ? "#9EC4FF" : "#545868"; + ctx.fillRect(x, y, 1, barHeight); + } + + const handlePadY = 6; + const handleLineW = 3; + const handleLineH = height - handlePadY * 2; + const handleLineR = 1.5; + const gripW = 7; + const gripH = 22; + const gripR = 3; + + const drawHandle = (cx: number) => { + ctx.fillStyle = "#FFFFFF"; + roundRect(ctx, cx - handleLineW / 2, handlePadY, handleLineW, handleLineH, handleLineR); + ctx.fill(); + + roundRect(ctx, cx - gripW / 2, height / 2 - gripH / 2, gripW, gripH, gripR); + ctx.fill(); + }; + + drawHandle(startX); + drawHandle(endX); + + // Playback position indicator + if (playbackRatio !== null) { + const playX = padX + playbackRatio * drawableW; + ctx.fillStyle = "#FFFFFF"; + ctx.globalAlpha = 0.9; + ctx.fillRect(playX - 0.5, 0, 1, height); + ctx.globalAlpha = 1; + } +} + +function roundRect( + ctx: CanvasRenderingContext2D, + x: number, + y: number, + w: number, + h: number, + r: number, +) { + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.lineTo(x + w - r, y); + ctx.quadraticCurveTo(x + w, y, x + w, y + r); + ctx.lineTo(x + w, y + h - r); + ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); + ctx.lineTo(x + r, y + h); + ctx.quadraticCurveTo(x, y + h, x, y + h - r); + ctx.lineTo(x, y + r); + ctx.quadraticCurveTo(x, y, x + r, y); + ctx.closePath(); +} + +function encodeWavBase64( + source: AudioBuffer, + startFrame: number, + endFrame: number, +): string { + const channels = source.numberOfChannels; + const sampleRate = source.sampleRate; + const frameCount = Math.max(1, endFrame - startFrame); + const bytesPerSample = 2; + const blockAlign = channels * bytesPerSample; + const byteRate = sampleRate * blockAlign; + const dataSize = frameCount * blockAlign; + const buffer = new ArrayBuffer(44 + dataSize); + const view = new DataView(buffer); + + const writeAscii = (offset: number, value: string) => { + for (let i = 0; i < value.length; i += 1) { + view.setUint8(offset + i, value.charCodeAt(i)); + } + }; + + writeAscii(0, "RIFF"); + view.setUint32(4, 36 + dataSize, true); + writeAscii(8, "WAVE"); + writeAscii(12, "fmt "); + view.setUint32(16, 16, true); + view.setUint16(20, 1, true); + view.setUint16(22, channels, true); + view.setUint32(24, sampleRate, true); + view.setUint32(28, byteRate, true); + view.setUint16(32, blockAlign, true); + view.setUint16(34, 16, true); + writeAscii(36, "data"); + view.setUint32(40, dataSize, true); + + let offset = 44; + const channelData = new Array(channels) + .fill(null) + .map((_, channel) => source.getChannelData(channel)); + + for (let frame = 0; frame < frameCount; frame += 1) { + const sourceFrame = startFrame + frame; + for (let channel = 0; channel < channels; channel += 1) { + const sample = channelData[channel][sourceFrame] ?? 0; + const clamped = clamp(sample, -1, 1); + const pcm = clamped < 0 ? clamped * 0x8000 : clamped * 0x7fff; + view.setInt16(offset, pcm, true); + offset += 2; + } + } + + const bytes = new Uint8Array(buffer); + let binary = ""; + const chunkSize = 0x8000; + for (let i = 0; i < bytes.length; i += chunkSize) { + const chunk = bytes.subarray(i, i + chunkSize); + binary += String.fromCharCode(...chunk); + } + return btoa(binary); +} + +function stripExtension(name: string): string { + const lastDot = name.lastIndexOf("."); + return lastDot > 0 ? name.slice(0, lastDot) : name; +} + +export default function SoundTrimModal({ + isOpen, + onClose, + onSaved, + previewVolume = 100, +}: SoundTrimModalProps) { + const { t } = useTranslation(); + const fileInputRef = useRef(null); + const waveformRef = useRef(null); + const canvasRef = useRef(null); + const dragTargetRef = useRef(null); + + const [originalFileName, setOriginalFileName] = useState(""); + const [soundName, setSoundName] = useState(""); + const [audioBuffer, setAudioBuffer] = useState(null); + const [peaks, setPeaks] = useState(new Float32Array()); + const [startRatio, setStartRatio] = useState(0); + const [endRatio, setEndRatio] = useState(1); + const [isDecoding, setIsDecoding] = useState(false); + const [isSaving, setIsSaving] = useState(false); + const [isPlaying, setIsPlaying] = useState(false); + const [errorText, setErrorText] = useState(""); + + const playContextRef = useRef(null); + const playSourceRef = useRef(null); + const playStartCtxTimeRef = useRef(0); + const playDurationSecRef = useRef(0); + const playStartRatioRef = useRef(0); + const playEndRatioRef = useRef(1); + const animFrameRef = useRef(0); + const pausedAtRatioRef = useRef(null); + + const durationMs = useMemo( + () => (audioBuffer ? audioBuffer.duration * 1000 : 0), + [audioBuffer], + ); + const minRatioGap = useMemo( + () => (durationMs > 0 ? Math.min(1, MIN_TRIM_MS / durationMs) : 0), + [durationMs], + ); + const trimDurationMs = Math.max(0, durationMs * endRatio - durationMs * startRatio); + + const canSubmit = + !!audioBuffer && + !isDecoding && + !isSaving && + trimDurationMs >= MIN_TRIM_MS && + soundName.trim().length > 0; + + const peaksRef = useRef(peaks); + peaksRef.current = peaks; + const startRatioRef = useRef(startRatio); + startRatioRef.current = startRatio; + const endRatioRef = useRef(endRatio); + endRatioRef.current = endRatio; + + const teardownAudio = useCallback(() => { + if (animFrameRef.current) { + cancelAnimationFrame(animFrameRef.current); + animFrameRef.current = 0; + } + if (playSourceRef.current) { + playSourceRef.current.onended = null; + try { + playSourceRef.current.stop(); + } catch { + /* already stopped */ + } + playSourceRef.current = null; + } + if (playContextRef.current) { + void playContextRef.current.close(); + playContextRef.current = null; + } + setIsPlaying(false); + }, []); + + const redrawWaveformStatic = useCallback((pausedRatio?: number | null) => { + const canvas = canvasRef.current; + const currentPeaks = peaksRef.current; + if (canvas && currentPeaks.length > 0) { + drawWaveform( + canvas, + currentPeaks, + startRatioRef.current, + endRatioRef.current, + pausedRatio ?? null, + ); + } + }, []); + + // Pause: save current position and show indicator at paused position + const pausePlayback = useCallback(() => { + const playCtx = playContextRef.current; + if (playCtx) { + const elapsed = playCtx.currentTime - playStartCtxTimeRef.current; + const totalDur = playDurationSecRef.current; + const progress = totalDur > 0 ? clamp(elapsed / totalDur, 0, 1) : 0; + const sR = playStartRatioRef.current; + const eR = playEndRatioRef.current; + pausedAtRatioRef.current = sR + progress * (eR - sR); + } + teardownAudio(); + redrawWaveformStatic(pausedAtRatioRef.current); + }, [teardownAudio, redrawWaveformStatic]); + + // Full stop: reset position to start of range + const stopPlayback = useCallback(() => { + pausedAtRatioRef.current = null; + teardownAudio(); + redrawWaveformStatic(); + }, [teardownAudio, redrawWaveformStatic]); + + const handlePlay = useCallback(() => { + if (!audioBuffer) return; + + // Currently playing → pause + if (playSourceRef.current) { + pausePlayback(); + return; + } + + const ctx = createAudioContext(); + const gainNode = ctx.createGain(); + gainNode.gain.value = clamp(previewVolume / 100, 0, 1); + gainNode.connect(ctx.destination); + const source = ctx.createBufferSource(); + source.buffer = audioBuffer; + source.connect(gainNode); + + const sR = startRatioRef.current; + const eR = endRatioRef.current; + + // Resume from paused position, or start from range beginning + const resumeRatio = pausedAtRatioRef.current ?? sR; + const offsetSec = audioBuffer.duration * resumeRatio; + const remainingSec = audioBuffer.duration * (eR - resumeRatio); + pausedAtRatioRef.current = null; + + source.onended = () => { + // Natural end → reset position + stopPlayback(); + }; + + playContextRef.current = ctx; + playSourceRef.current = source; + playStartCtxTimeRef.current = ctx.currentTime; + playDurationSecRef.current = remainingSec; + playStartRatioRef.current = resumeRatio; + playEndRatioRef.current = eR; + setIsPlaying(true); + + source.start(0, offsetSec, remainingSec); + + // Start animation loop for playback indicator + const animate = () => { + const playCtx = playContextRef.current; + const canvas = canvasRef.current; + const currentPeaks = peaksRef.current; + if (!playCtx || !canvas || currentPeaks.length === 0) return; + + const elapsed = playCtx.currentTime - playStartCtxTimeRef.current; + const totalDur = playDurationSecRef.current; + const progress = totalDur > 0 ? clamp(elapsed / totalDur, 0, 1) : 0; + const animSR = playStartRatioRef.current; + const animER = playEndRatioRef.current; + const playbackRatio = animSR + progress * (animER - animSR); + + drawWaveform(canvas, currentPeaks, startRatioRef.current, endRatioRef.current, playbackRatio); + + if (progress < 1) { + animFrameRef.current = requestAnimationFrame(animate); + } + }; + animFrameRef.current = requestAnimationFrame(animate); + }, [audioBuffer, previewVolume, pausePlayback, stopPlayback]); + + const resetState = useCallback(() => { + pausedAtRatioRef.current = null; + stopPlayback(); + setOriginalFileName(""); + setSoundName(""); + setAudioBuffer(null); + setPeaks(new Float32Array()); + setStartRatio(0); + setEndRatio(1); + setIsDecoding(false); + setIsSaving(false); + setErrorText(""); + dragTargetRef.current = null; + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + }, [stopPlayback]); + + const closeModal = useCallback(() => { + resetState(); + onClose(); + }, [onClose, resetState]); + + useEffect(() => { + if (!isOpen) { + resetState(); + } + }, [isOpen, resetState]); + + useEffect(() => { + if (!isOpen) return; + if (isPlaying) return; // animation loop handles drawing during playback + const canvas = canvasRef.current; + if (!canvas || peaks.length === 0) return; + drawWaveform(canvas, peaks, startRatio, endRatio, pausedAtRatioRef.current); + }, [isOpen, isPlaying, peaks, startRatio, endRatio]); + + useEffect(() => { + if (!isOpen) return; + const node = waveformRef.current; + if (!node) return; + + const observer = new ResizeObserver(() => { + if (isPlaying) return; // animation loop handles drawing during playback + const canvas = canvasRef.current; + if (!canvas || peaks.length === 0) return; + drawWaveform(canvas, peaks, startRatio, endRatio, pausedAtRatioRef.current); + }); + + observer.observe(node); + return () => observer.disconnect(); + }, [isOpen, isPlaying, peaks, startRatio, endRatio]); + + const selectFile = useCallback(() => { + fileInputRef.current?.click(); + }, []); + + const handleFileChange = useCallback( + async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + + stopPlayback(); + setErrorText(""); + setIsDecoding(true); + setOriginalFileName(file.name); + setSoundName(stripExtension(file.name)); + + try { + const decoded = await decodeAudioFile(file); + setAudioBuffer(decoded); + setPeaks(extractWaveformPeaks(decoded)); + setStartRatio(0); + setEndRatio(1); + } catch (error) { + console.error("Failed to decode audio file:", error); + setAudioBuffer(null); + setPeaks(new Float32Array()); + setOriginalFileName(""); + setSoundName(""); + setErrorText(t("soundTrimModal.decodeError")); + } finally { + setIsDecoding(false); + } + }, + [stopPlayback], + ); + + const updateFromClientX = useCallback( + (clientX: number, target: DragTarget) => { + const host = waveformRef.current; + if (!host || !target) return; + const rect = host.getBoundingClientRect(); + const drawableW = rect.width - WAVEFORM_PAD_X * 2; + const ratio = clamp( + (clientX - rect.left - WAVEFORM_PAD_X) / Math.max(1, drawableW), + 0, + 1, + ); + + if (target === "start") { + const maxStart = Math.max(0, endRatio - minRatioGap); + setStartRatio(clamp(ratio, 0, maxStart)); + } else if (target === "end") { + const minEnd = Math.min(1, startRatio + minRatioGap); + setEndRatio(clamp(ratio, minEnd, 1)); + } + }, + [endRatio, minRatioGap, startRatio], + ); + + const handlePointerMove = useCallback( + (event: PointerEvent) => { + const target = dragTargetRef.current; + if (!target) return; + updateFromClientX(event.clientX, target); + }, + [updateFromClientX], + ); + + const handlePointerUp = useCallback(() => { + dragTargetRef.current = null; + window.removeEventListener("pointermove", handlePointerMove); + window.removeEventListener("pointerup", handlePointerUp); + }, [handlePointerMove]); + + const handleWaveformPointerDown = useCallback( + (event: React.PointerEvent) => { + if (!audioBuffer) return; + const host = waveformRef.current; + if (!host) return; + + // Stop playback and reset pause position when dragging handles + if (playSourceRef.current) { + teardownAudio(); + } + pausedAtRatioRef.current = null; + + const rect = host.getBoundingClientRect(); + const drawableW = rect.width - WAVEFORM_PAD_X * 2; + const x = clamp(event.clientX - rect.left, 0, rect.width); + const startX = WAVEFORM_PAD_X + startRatio * drawableW; + const endX = WAVEFORM_PAD_X + endRatio * drawableW; + + const pickStart = Math.abs(x - startX) <= HANDLE_PICK_PX; + const pickEnd = Math.abs(x - endX) <= HANDLE_PICK_PX; + + let nextTarget: DragTarget = null; + if (pickStart && pickEnd) { + nextTarget = x < (startX + endX) / 2 ? "start" : "end"; + } else if (pickStart) { + nextTarget = "start"; + } else if (pickEnd) { + nextTarget = "end"; + } else { + nextTarget = + Math.abs(x - startX) < Math.abs(x - endX) ? "start" : "end"; + } + + dragTargetRef.current = nextTarget; + updateFromClientX(event.clientX, nextTarget); + + window.addEventListener("pointermove", handlePointerMove); + window.addEventListener("pointerup", handlePointerUp); + }, + [ + audioBuffer, + endRatio, + handlePointerMove, + handlePointerUp, + startRatio, + teardownAudio, + updateFromClientX, + ], + ); + + const handleSave = useCallback(async () => { + if (!audioBuffer || isSaving || isDecoding) return; + stopPlayback(); + setErrorText(""); + setIsSaving(true); + + try { + const startFrame = Math.floor(audioBuffer.length * startRatio); + const endFrame = Math.max( + startFrame + 1, + Math.floor(audioBuffer.length * endRatio), + ); + const wavBase64 = encodeWavBase64(audioBuffer, startFrame, endFrame); + const trimmedName = soundName.trim() || undefined; + const response = await window.api.sound.saveProcessedWav( + wavBase64, + trimmedName, + ); + + if (!response.success || !response.soundPath) { + throw new Error( + response.error || t("soundTrimModal.saveErrorDefault"), + ); + } + + onSaved(response.soundPath); + resetState(); + } catch (error) { + console.error("Failed to save processed sound:", error); + setErrorText(t("soundTrimModal.saveErrorFailed")); + } finally { + setIsSaving(false); + } + }, [ + audioBuffer, + endRatio, + isDecoding, + isSaving, + onSaved, + resetState, + soundName, + startRatio, + stopPlayback, + ]); + + const headerLabel = useMemo(() => { + if (!audioBuffer) { + if (isDecoding) return t("soundTrimModal.statusDecoding"); + return t("soundTrimModal.statusWaiting"); + } + return t("soundTrimModal.statusReady"); + }, [audioBuffer, isDecoding, t]); + + if (!isOpen) return null; + + return ( + +
event.stopPropagation()} + > + {/* Header bar */} +
+
+ + Sound + + + {originalFileName || t("soundTrimModal.defaultTitle")} + +
+ + {headerLabel} + +
+ + {/* Content area */} +
+ {/* Name input */} +
+ + setSoundName(e.target.value)} + placeholder={t("soundTrimModal.namePlaceholder")} + className="w-full h-[30px] px-[10px] rounded-[7px] border border-[#3A3943] bg-[#1E1E1E] text-[12px] leading-[16px] text-[#DBDEE8] placeholder-[#6F6E7A] outline-none focus:border-[#459BF8] transition-colors" + disabled={isSaving} + /> +
+ + {/* Waveform section */} +
+
+ {/* Play button */} +
+ + + {audioBuffer ? formatSecLabel(trimDurationMs) : "--"} + +
+ + {/* Waveform canvas */} +
+ {audioBuffer ? ( + + ) : ( +
+ {isDecoding + ? t("soundTrimModal.decodingMessage") + : t("soundTrimModal.emptyMessage")} +
+ )} +
+
+
+ + {errorText ? ( +

+ {errorText} +

+ ) : null} +
+ + {/* Hint bar */} +
+ +

+ {t("soundTrimModal.dragHint")} +

+
+ + + + {/* Footer */} +
+ + +
+
+
+ ); +} diff --git a/src/renderer/components/main/Settings.jsx b/src/renderer/components/main/Settings.jsx index 696a6ce..45f17bc 100644 --- a/src/renderer/components/main/Settings.jsx +++ b/src/renderer/components/main/Settings.jsx @@ -765,8 +765,8 @@ export default function Settings({ showAlert, showConfirm }) {
{/* 기타 설정 */} -
-
+
+

{t("settings.language")}

@@ -777,7 +777,7 @@ export default function Settings({ showAlert, showConfirm }) { placeholder={t("settings.selectLanguage")} />
-
+

{t("settings.shortcuts")}

@@ -788,7 +788,7 @@ export default function Settings({ showAlert, showConfirm }) { {t("settings.configure")}
-
+

{t("settings.graphicsOption")}

@@ -801,7 +801,7 @@ export default function Settings({ showAlert, showConfirm }) { />

@@ -813,7 +813,7 @@ export default function Settings({ showAlert, showConfirm }) { />

@@ -825,7 +825,7 @@ export default function Settings({ showAlert, showConfirm }) { />

{/* 버전 및 설정 초기화 */} -
+

Ver {__APP_VERSION__}

diff --git a/src/renderer/locales/en.json b/src/renderer/locales/en.json index 6878053..9f913fb 100644 --- a/src/renderer/locales/en.json +++ b/src/renderer/locales/en.json @@ -457,7 +457,8 @@ "hideLayer": "Hide", "showLayer": "Show", "font": "Font", - "keySound": "Key Sound", + "keySound": "Sound Settings", + "keySoundEnabled": "Enable Key Sound", "selectedSound": "Selected Sound", "soundVolume": "Sound Volume", "import": "Import", @@ -471,6 +472,38 @@ "filterWeb": "Web", "noFonts": "No fonts" }, + "soundPicker": { + "searchPlaceholder": "Search...", + "filterAll": "All", + "filterLocal": "Local Sounds", + "noSounds": "No sounds" + }, + "soundManager": { + "title": "Sounds", + "noSounds": "No sounds", + "addSound": "Add", + "loadFailed": "Failed to load sound list", + "stateChangeFailed": "Failed to change sound state", + "deleteFailed": "Failed to delete sound" + }, + "soundTrimModal": { + "defaultTitle": "Add Sound", + "statusDecoding": "Decoding...", + "statusWaiting": "Waiting for file", + "statusReady": "Ready", + "nameLabel": "Sound name", + "namePlaceholder": "Sound name", + "decodeError": "Unable to decode the audio file.", + "decodingMessage": "Decoding audio...", + "emptyMessage": "Select a file to display the waveform", + "loadFile": "Load file", + "dragHint": "Drag to edit range", + "saving": "Saving...", + "submit": "Add", + "cancel": "Cancel", + "saveErrorDefault": "Failed to save the processed sound.", + "saveErrorFailed": "Failed to save the processed sound." + }, "fontManager": { "localTab": "Local Fonts", "webTab": "Web Fonts", diff --git a/src/renderer/locales/ko.json b/src/renderer/locales/ko.json index aeedd5f..abb3a46 100644 --- a/src/renderer/locales/ko.json +++ b/src/renderer/locales/ko.json @@ -458,7 +458,8 @@ "hideLayer": "숨기기", "showLayer": "표시하기", "font": "폰트", - "keySound": "키 사운드", + "keySound": "사운드 설정", + "keySoundEnabled": "키 사운드 사용", "selectedSound": "선택 파일", "soundVolume": "사운드 볼륨", "import": "가져오기", @@ -472,6 +473,38 @@ "filterWeb": "웹 폰트", "noFonts": "폰트 없음" }, + "soundPicker": { + "searchPlaceholder": "검색", + "filterAll": "전체", + "filterLocal": "로컬 사운드", + "noSounds": "사운드 없음" + }, + "soundManager": { + "title": "사운드", + "noSounds": "사운드가 없습니다", + "addSound": "추가", + "loadFailed": "사운드 목록 로드 실패", + "stateChangeFailed": "사운드 상태 변경 실패", + "deleteFailed": "사운드 삭제 실패" + }, + "soundTrimModal": { + "defaultTitle": "사운드 추가", + "statusDecoding": "해석 중...", + "statusWaiting": "파일 대기", + "statusReady": "사용 가능", + "nameLabel": "사운드 이름", + "namePlaceholder": "사운드 이름", + "decodeError": "오디오 파일을 해석할 수 없습니다.", + "decodingMessage": "오디오 해석 중...", + "emptyMessage": "파일을 선택하면 파형이 표시됩니다", + "loadFile": "파일 불러오기", + "dragHint": "드래그하여 범위 편집", + "saving": "저장 중...", + "submit": "추가하기", + "cancel": "취소", + "saveErrorDefault": "편집된 사운드를 저장하지 못했습니다.", + "saveErrorFailed": "편집된 사운드 저장에 실패했습니다." + }, "fontManager": { "localTab": "로컬 폰트", "webTab": "웹 폰트", diff --git a/src/renderer/locales/ru.json b/src/renderer/locales/ru.json index 55048c3..629db8f 100644 --- a/src/renderer/locales/ru.json +++ b/src/renderer/locales/ru.json @@ -445,7 +445,8 @@ "hideLayer": "Скрыть", "showLayer": "Показать", "font": "Шрифт", - "keySound": "Звук клавиши", + "keySound": "Настройки звука", + "keySoundEnabled": "Включить звук клавиши", "selectedSound": "Выбранный звук", "soundVolume": "Громкость", "import": "Импорт", @@ -459,6 +460,38 @@ "filterWeb": "Веб", "noFonts": "Нет шрифтов" }, + "soundPicker": { + "searchPlaceholder": "Поиск...", + "filterAll": "Все", + "filterLocal": "Локальные звуки", + "noSounds": "Звуков нет" + }, + "soundManager": { + "title": "Звуки", + "noSounds": "Звуков нет", + "addSound": "Добавить", + "loadFailed": "Не удалось загрузить список звуков", + "stateChangeFailed": "Не удалось изменить состояние звука", + "deleteFailed": "Не удалось удалить звук" + }, + "soundTrimModal": { + "defaultTitle": "Добавить звук", + "statusDecoding": "Декодирование...", + "statusWaiting": "Ожидание файла", + "statusReady": "Готово", + "nameLabel": "Название звука", + "namePlaceholder": "Название звука", + "decodeError": "Не удалось декодировать аудиофайл.", + "decodingMessage": "Декодирование аудио...", + "emptyMessage": "Выберите файл для отображения формы волны", + "loadFile": "Загрузить файл", + "dragHint": "Перетащите для редактирования диапазона", + "saving": "Сохранение...", + "submit": "Добавить", + "cancel": "Отмена", + "saveErrorDefault": "Не удалось сохранить обработанный звук.", + "saveErrorFailed": "Не удалось сохранить обработанный звук." + }, "fontManager": { "localTab": "Локальные", "webTab": "Веб-шрифты", diff --git a/src/renderer/locales/zh-Hant.json b/src/renderer/locales/zh-Hant.json index 157ae62..1e94fca 100644 --- a/src/renderer/locales/zh-Hant.json +++ b/src/renderer/locales/zh-Hant.json @@ -59,12 +59,12 @@ "selectAnchor": "選擇錨點", "overlayLockDesc": "鎖定懸浮窗, 使其無法移動.", "alwaysOnTopDesc": "使懸浮窗始終顯示在其他視窗之上.", - "noteEffectDesc": "按下按鍵時顯示鍵雨.", - "laboratoryDesc": "嘗試實驗性功能.", - "trayEnabledDesc": "關閉主視窗時不退出程式,而是隱藏到系統匣.", - "autoUpdate": "自動更新", - "autoUpdateDesc": "在新版本提示彈窗中執行自動更新.", - "developerMode": "啟用開發者模式", + "noteEffectDesc": "按下按鍵時顯示鍵雨.", + "laboratoryDesc": "嘗試實驗性功能.", + "trayEnabledDesc": "關閉主視窗時不退出程式,而是隱藏到系統匣.", + "autoUpdate": "自動更新", + "autoUpdateDesc": "在新版本提示彈窗中執行自動更新.", + "developerMode": "啟用開發者模式", "developerModeDesc": "即使在生產版本中也允許打開開發者工具 (DevTools).", "customCSSDesc": "載入自定義 CSS 檔案以調整懸浮窗樣式.", "customJSDesc": "運行 JS 插件以實現進階行為.", @@ -166,13 +166,13 @@ "ok": "確定", "save": "儲存" }, - "tooltip": { - "github": "GitHub", - "issue": "報告問題", - "move": "移動", - "delete": "刪除", - "add": "添加", - "palette": "畫布", + "tooltip": { + "github": "GitHub", + "issue": "報告問題", + "move": "移動", + "delete": "刪除", + "add": "添加", + "palette": "畫布", "resetCurrentTab": "重置", "exportPreset": "導出預設", "importExport": "導入/導出", @@ -185,13 +185,13 @@ "laboratory": "實驗性功能", "gridSettings": "網格設定" }, - "toolbar": { - "resetTab": "重置標籤頁", - "resetCounters": "重置計數器", - "addKey": "按鍵", - "addStat": "統計", - "addGraph": "圖表" - }, + "toolbar": { + "resetTab": "重置標籤頁", + "resetCounters": "重置計數器", + "addKey": "按鍵", + "addStat": "統計", + "addGraph": "圖表" + }, "preset": { "import": "導入", "export": "導出", @@ -280,27 +280,27 @@ "sendToBack": "置於底層", "bringForward": "上移一層", "sendBackward": "下移一層", - "tabCssSetting": "標籤頁 CSS 設定", - "addKey": "新增按鍵", - "addStat": "新增統計", + "tabCssSetting": "標籤頁 CSS 設定", + "addKey": "新增按鍵", + "addStat": "新增統計", "addGraph": "新增圖表", "rename": "重新命名" }, "tabCssSetting": "標籤頁 CSS 設定", "addStat": "新增統計", - "quitApp": "退出程式", - "snapToEdge": "貼靠螢幕邊緣", - "selectTab": "選擇標籤頁", - "groupSelected": "群組", - "renameGroup": "重新命名", - "ungroup": "取消分組", - "removeFromGroup": "從群組中移除" - }, - "layerGroup": { - "defaultName": "群組", - "newGroup": "新群組" - }, - "noteColor": { + "quitApp": "退出程式", + "snapToEdge": "貼靠螢幕邊緣", + "selectTab": "選擇標籤頁", + "groupSelected": "群組", + "renameGroup": "重新命名", + "ungroup": "取消分組", + "removeFromGroup": "從群組中移除" + }, + "layerGroup": { + "defaultName": "群組", + "newGroup": "新群組" + }, + "noteColor": { "color": "顏色", "solid": "純色", "gradient": "漸變", @@ -369,12 +369,12 @@ "currentVersion": "目前版本", "latestVersion": "最新版本", "releaseNotes": "發布說明", - "skipVersion": "跳過此版本", - "goToRelease": "前往發布頁面", - "autoUpdate": "自動更新", - "autoUpdating": "更新中...", - "autoUpdateFailed": "自動更新失敗.", - "later": "稍後", + "skipVersion": "跳過此版本", + "goToRelease": "前往發布頁面", + "autoUpdate": "自動更新", + "autoUpdating": "更新中...", + "autoUpdateFailed": "自動更新失敗.", + "later": "稍後", "checkUpdate": "檢查更新", "checking": "檢查中...", "latestAlready": "已是最新版本." @@ -432,10 +432,10 @@ "graphShape": "圖表形狀", "graphShapeLine": "折線", "graphShapeBar": "柱狀", - "graphShowAverageLine": "顯示平均線", - "graphSpeed": "圖表速度", - "graphColor": "圖表顏色", - "graphAnimation": "圖表動畫", + "graphShowAverageLine": "顯示平均線", + "graphSpeed": "圖表速度", + "graphColor": "圖表顏色", + "graphAnimation": "圖表動畫", "pressAnyKey": "按下任意鍵", "clickToSet": "單擊設定", "customImage": "自定義圖像", @@ -472,13 +472,14 @@ "delete": "刪除", "hideLayer": "隱藏", "showLayer": "顯示", - "font": "字體", - "keySound": "按鍵音效", - "selectedSound": "已選音檔", - "soundVolume": "音量", - "import": "匯入", - "loading": "載入中..." - }, + "font": "字體", + "keySound": "聲音設定", + "keySoundEnabled": "啟用按鍵音效", + "selectedSound": "已選音檔", + "soundVolume": "音量", + "import": "匯入", + "loading": "載入中..." + }, "fontPicker": { "searchPlaceholder": "搜尋", "filterAll": "全部", @@ -487,6 +488,38 @@ "filterWeb": "網路字體", "noFonts": "無字體" }, + "soundPicker": { + "searchPlaceholder": "搜尋", + "filterAll": "全部", + "filterLocal": "本地音效", + "noSounds": "無音效" + }, + "soundManager": { + "title": "音效", + "noSounds": "無音效", + "addSound": "添加", + "loadFailed": "音效清單載入失敗", + "stateChangeFailed": "音效狀態變更失敗", + "deleteFailed": "音效刪除失敗" + }, + "soundTrimModal": { + "defaultTitle": "新增音效", + "statusDecoding": "解析中...", + "statusWaiting": "等待檔案", + "statusReady": "可使用", + "nameLabel": "音效名稱", + "namePlaceholder": "音效名稱", + "decodeError": "無法解析音訊檔案。", + "decodingMessage": "正在解析音訊...", + "emptyMessage": "選擇檔案後將顯示波形", + "loadFile": "載入檔案", + "dragHint": "拖曳以編輯範圍", + "saving": "儲存中...", + "submit": "新增", + "cancel": "取消", + "saveErrorDefault": "無法儲存處理後的音效。", + "saveErrorFailed": "儲存處理後的音效失敗。" + }, "fontManager": { "localTab": "本地字體", "webTab": "網路字體", diff --git a/src/renderer/locales/zh-cn.json b/src/renderer/locales/zh-cn.json index d010b68..221a509 100644 --- a/src/renderer/locales/zh-cn.json +++ b/src/renderer/locales/zh-cn.json @@ -460,7 +460,8 @@ "hideLayer": "隐藏", "showLayer": "显示", "font": "字体", - "keySound": "按键声音", + "keySound": "声音设置", + "keySoundEnabled": "启用按键声音", "selectedSound": "已选音频", "soundVolume": "音量", "import": "导入", @@ -474,6 +475,38 @@ "filterWeb": "网络字体", "noFonts": "无字体" }, + "soundPicker": { + "searchPlaceholder": "搜索", + "filterAll": "全部", + "filterLocal": "本地音频", + "noSounds": "无音频" + }, + "soundManager": { + "title": "音频", + "noSounds": "无音频", + "addSound": "添加", + "loadFailed": "音频列表加载失败", + "stateChangeFailed": "音频状态变更失败", + "deleteFailed": "音频删除失败" + }, + "soundTrimModal": { + "defaultTitle": "添加音频", + "statusDecoding": "解析中...", + "statusWaiting": "等待文件", + "statusReady": "可用", + "nameLabel": "音频名称", + "namePlaceholder": "音频名称", + "decodeError": "无法解析音频文件。", + "decodingMessage": "正在解析音频...", + "emptyMessage": "选择文件后将显示波形", + "loadFile": "加载文件", + "dragHint": "拖动以编辑范围", + "saving": "保存中...", + "submit": "添加", + "cancel": "取消", + "saveErrorDefault": "无法保存处理后的音频。", + "saveErrorFailed": "保存处理后的音频失败。" + }, "fontManager": { "localTab": "本地字体", "webTab": "网络字体", diff --git a/src/types/api.ts b/src/types/api.ts index bd244d3..dd0ff65 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -85,6 +85,31 @@ export type SoundLoadResult = { soundPath?: string; }; +export type SoundListItem = { + soundPath: string; + fileName: string; + sizeBytes: number; + modifiedAtMs?: number; + enabled: boolean; + source: "local" | "builtin"; +}; + +export type SoundSaveProcessedWavResult = { + success: boolean; + error?: string; + soundPath?: string; +}; + +export type SoundSetEnabledResult = { + success: boolean; + soundPath: string; + enabled: boolean; +}; + +export type SoundDeleteResult = { + success: boolean; +}; + // 탭별 CSS 타입 export type TabCssResponse = { tabId: string; @@ -726,6 +751,16 @@ export interface DMNoteAPI { }; sound: { load(): Promise; + list(): Promise; + setEnabled( + soundPath: string, + enabled: boolean, + ): Promise; + remove(soundPath: string): Promise; + saveProcessedWav( + wavBase64: string, + fileName?: string, + ): Promise; setLatencyLogging(enabled: boolean): Promise; }; js: { diff --git a/src/types/keys.ts b/src/types/keys.ts index 0b11568..ff98c8f 100644 --- a/src/types/keys.ts +++ b/src/types/keys.ts @@ -208,6 +208,7 @@ export const keyPositionSchema = z.object({ hidden: z.boolean().optional().default(false), activeImage: z.string().optional().or(z.literal("")), inactiveImage: z.string().optional().or(z.literal("")), + soundEnabled: z.boolean().optional(), soundPath: z.string().optional().or(z.literal("")), soundVolume: z.number().min(0).max(100).optional(), activeTransparent: z.boolean().optional(), From c379ba05f31efe43b2831ac37854ce1d9abce453 Mon Sep 17 00:00:00 2001 From: lee-sihun Date: Sun, 22 Feb 2026 15:12:50 +0900 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20=ED=82=A4=EC=9D=8C=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EA=B2=BD=EB=A1=9C=20=EB=B0=8F=20=EB=94=94=ED=85=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/gen/schemas/acl-manifests.json | 2 +- src-tauri/permissions/dmnote-allow-all.json | 2 + src-tauri/src/app_state.rs | 4 + src-tauri/src/commands/sound.rs | 418 ++++++++++++------ src-tauri/src/key_sound.rs | 10 + src-tauri/src/main.rs | 2 + src-tauri/src/models.rs | 49 ++ src-tauri/src/store.rs | 8 + src/renderer/api/dmnoteApi.ts | 42 +- .../main/Modal/content/SoundManagerModal.tsx | 98 +++- .../main/Modal/content/SoundTrimModal.tsx | 214 +++++++-- src/renderer/locales/en.json | 7 +- src/renderer/locales/ko.json | 7 +- src/renderer/locales/ru.json | 7 +- src/renderer/locales/zh-Hant.json | 7 +- src/renderer/locales/zh-cn.json | 7 +- src/types/api.ts | 28 ++ 17 files changed, 705 insertions(+), 207 deletions(-) diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json index c3033fd..2352206 100644 --- a/src-tauri/gen/schemas/acl-manifests.json +++ b/src-tauri/gen/schemas/acl-manifests.json @@ -1 +1 @@ -{"__app-acl__":{"default_permission":null,"permissions":{"dmnote-allow-all":{"identifier":"dmnote-allow-all","description":"Full DM Note command access for renderer","commands":{"allow":["app_bootstrap","app_auto_update","app_open_external","app_restart","app_quit","window_minimize","window_close","window_show_main","window_open_devtools_all","get_cursor_settings","settings_get","settings_update","overlay_get","overlay_set_visible","overlay_set_lock","overlay_set_anchor","overlay_resize","css_get","css_get_use","css_toggle","css_reset","css_set_content","css_load","css_tab_get_all","css_tab_get","css_tab_load","css_tab_clear","css_tab_set","css_tab_toggle","font_load","image_load","sound_load","sound_list","sound_set_enabled","sound_delete","sound_save_processed_wav","js_get","js_get_use","js_toggle","js_reset","js_set_content","js_load","js_reload","js_remove_plugin","js_set_plugin_enabled","keys_get","keys_update","keys_set_mode","keys_reset_all","keys_reset_mode","keys_reset_counters","keys_reset_counters_mode","keys_reset_single_counter","keys_set_counters","raw_input_subscribe","raw_input_unsubscribe","key_sound_get_status","key_sound_set_enabled","key_sound_set_volume","key_sound_load_soundpack","key_sound_unload_soundpack","key_sound_set_latency_logging","positions_get","positions_update","custom_tabs_list","custom_tabs_create","custom_tabs_delete","custom_tabs_select","preset_save","preset_load","plugin_bridge_send","plugin_bridge_send_to","plugin_storage_get","plugin_storage_set","plugin_storage_remove","plugin_storage_clear","plugin_storage_keys","plugin_storage_has_data","plugin_storage_clear_by_prefix","stat_positions_get","stat_positions_update","graph_positions_get","graph_positions_update","layer_groups_get","layer_groups_update"],"deny":[]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file +{"__app-acl__":{"default_permission":null,"permissions":{"dmnote-allow-all":{"identifier":"dmnote-allow-all","description":"Full DM Note command access for renderer","commands":{"allow":["app_bootstrap","app_auto_update","app_open_external","app_restart","app_quit","window_minimize","window_close","window_show_main","window_open_devtools_all","get_cursor_settings","settings_get","settings_update","overlay_get","overlay_set_visible","overlay_set_lock","overlay_set_anchor","overlay_resize","css_get","css_get_use","css_toggle","css_reset","css_set_content","css_load","css_tab_get_all","css_tab_get","css_tab_load","css_tab_clear","css_tab_set","css_tab_toggle","font_load","image_load","sound_load","sound_list","sound_set_enabled","sound_delete","sound_save_processed_wav","sound_load_original","sound_update_processed_wav","js_get","js_get_use","js_toggle","js_reset","js_set_content","js_load","js_reload","js_remove_plugin","js_set_plugin_enabled","keys_get","keys_update","keys_set_mode","keys_reset_all","keys_reset_mode","keys_reset_counters","keys_reset_counters_mode","keys_reset_single_counter","keys_set_counters","raw_input_subscribe","raw_input_unsubscribe","key_sound_get_status","key_sound_set_enabled","key_sound_set_volume","key_sound_load_soundpack","key_sound_unload_soundpack","key_sound_set_latency_logging","positions_get","positions_update","custom_tabs_list","custom_tabs_create","custom_tabs_delete","custom_tabs_select","preset_save","preset_load","plugin_bridge_send","plugin_bridge_send_to","plugin_storage_get","plugin_storage_set","plugin_storage_remove","plugin_storage_clear","plugin_storage_keys","plugin_storage_has_data","plugin_storage_clear_by_prefix","stat_positions_get","stat_positions_update","graph_positions_get","graph_positions_update","layer_groups_get","layer_groups_update"],"deny":[]}}},"permission_sets":{},"global_scope_schema":null},"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/src-tauri/permissions/dmnote-allow-all.json b/src-tauri/permissions/dmnote-allow-all.json index 86ec57d..baa06f9 100644 --- a/src-tauri/permissions/dmnote-allow-all.json +++ b/src-tauri/permissions/dmnote-allow-all.json @@ -42,6 +42,8 @@ "sound_set_enabled", "sound_delete", "sound_save_processed_wav", + "sound_load_original", + "sound_update_processed_wav", "js_get", "js_get_use", "js_toggle", diff --git a/src-tauri/src/app_state.rs b/src-tauri/src/app_state.rs index ec5ecae..933c9a8 100644 --- a/src-tauri/src/app_state.rs +++ b/src-tauri/src/app_state.rs @@ -1293,6 +1293,10 @@ impl AppState { self.key_sound.unload_soundpack() } + pub fn key_sound_invalidate_file_cache(&self, path: &str) { + self.key_sound.invalidate_file_cache(path); + } + fn resolve_key_sound_binding(&self, mode: &str, key_label: &str) -> Option<(String, f32)> { self.store.with_state(|state| { let mappings = state.keys.get(mode)?; diff --git a/src-tauri/src/commands/sound.rs b/src-tauri/src/commands/sound.rs index 9cca15b..db886a7 100644 --- a/src-tauri/src/commands/sound.rs +++ b/src-tauri/src/commands/sound.rs @@ -2,7 +2,7 @@ use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _}; use rfd::FileDialog; use serde::{Deserialize, Serialize}; use std::{ - collections::{HashMap, HashSet}, + collections::HashSet, fs, path::{Path, PathBuf}, time::SystemTime, @@ -11,43 +11,10 @@ use tauri::{Emitter, Manager, State}; use uuid::Uuid; use crate::app_state::AppState; +use crate::models::{SoundLibraryEntry, SoundSource}; const SUPPORTED_SOUND_EXTENSIONS: [&str; 8] = ["wav", "mp3", "ogg", "flac", "m4a", "aac", "aif", "aiff"]; -const SOUND_LIBRARY_FILE_NAME: &str = ".sound-library.json"; - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] -pub enum SoundSource { - Builtin, - Local, -} - -fn default_sound_source() -> SoundSource { - SoundSource::Local -} - -fn default_enabled() -> bool { - true -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -struct SoundLibraryEntry { - #[serde(default = "default_enabled")] - enabled: bool, - #[serde(default = "default_sound_source")] - source: SoundSource, -} - -impl Default for SoundLibraryEntry { - fn default() -> Self { - Self { - enabled: true, - source: SoundSource::Local, - } - } -} #[derive(Serialize)] #[serde(rename_all = "camelCase")] @@ -69,6 +36,14 @@ pub struct SoundListItem { pub modified_at_ms: Option, pub enabled: bool, pub source: SoundSource, + #[serde(skip_serializing_if = "Option::is_none")] + pub original_path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub trim_start_ratio: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub trim_end_ratio: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub display_name: Option, } #[derive(Deserialize)] @@ -76,6 +51,10 @@ pub struct SoundListItem { pub struct SoundSaveProcessedWavRequest { pub wav_base64: String, pub file_name: Option, + pub original_base64: Option, + pub original_extension: Option, + pub trim_start_ratio: Option, + pub trim_end_ratio: Option, } #[derive(Serialize)] @@ -104,7 +83,10 @@ pub struct SoundDeleteResponse { /// 로컬 사운드 파일을 선택하고 appData/sounds 디렉토리로 복사한 뒤 경로 반환 #[tauri::command(permission = "dmnote-allow-all")] -pub fn sound_load(app: tauri::AppHandle) -> Result { +pub fn sound_load( + app: tauri::AppHandle, + state: State<'_, AppState>, +) -> Result { let picked = FileDialog::new() .add_filter("Audio", &SUPPORTED_SOUND_EXTENSIONS) .pick_file(); @@ -128,14 +110,19 @@ pub fn sound_load(app: tauri::AppHandle) -> Result { fs::copy(&path, &dest_path).map_err(|e| format!("사운드 파일 복사 실패: {e}"))?; let dest_path_str = normalize_path_string(&dest_path); - upsert_sound_library_entry( - &sounds_dir, - &dest_path_str, - SoundLibraryEntry { - enabled: true, - source: SoundSource::Local, - }, - )?; + state + .store + .update(|s| { + s.sound_library.insert( + dest_path_str.clone(), + SoundLibraryEntry { + enabled: true, + source: SoundSource::Local, + ..Default::default() + }, + ); + }) + .map_err(|e| format!("사운드 라이브러리 저장 실패: {e}"))?; Ok(SoundLoadResponse { success: true, @@ -145,10 +132,13 @@ pub fn sound_load(app: tauri::AppHandle) -> Result { } #[tauri::command(permission = "dmnote-allow-all")] -pub fn sound_list(app: tauri::AppHandle) -> Result, String> { +pub fn sound_list( + app: tauri::AppHandle, + state: State<'_, AppState>, +) -> Result, String> { let sounds_dir = ensure_sounds_dir(&app)?; let mut items = Vec::new(); - let mut library = load_sound_library(&sounds_dir); + let mut library = state.store.with_state(|s| s.sound_library.clone()); let mut seen_paths = HashSet::new(); let mut library_mutated = false; @@ -203,6 +193,10 @@ pub fn sound_list(app: tauri::AppHandle) -> Result, String> { modified_at_ms, enabled: entry_meta.enabled, source: entry_meta.source, + original_path: entry_meta.original_path, + trim_start_ratio: entry_meta.trim_start_ratio, + trim_end_ratio: entry_meta.trim_end_ratio, + display_name: entry_meta.display_name, }); } @@ -219,7 +213,12 @@ pub fn sound_list(app: tauri::AppHandle) -> Result, String> { } if library_mutated { - save_sound_library(&sounds_dir, &library)?; + state + .store + .update(|s| { + s.sound_library = library.clone(); + }) + .map_err(|e| format!("사운드 라이브러리 저장 실패: {e}"))?; } items.sort_by(|a, b| { @@ -235,6 +234,7 @@ pub fn sound_list(app: tauri::AppHandle) -> Result, String> { #[tauri::command(permission = "dmnote-allow-all")] pub fn sound_set_enabled( app: tauri::AppHandle, + state: State<'_, AppState>, sound_path: String, enabled: bool, ) -> Result { @@ -245,14 +245,15 @@ pub fn sound_set_enabled( } let path_key = normalize_path_string(&validated_path); - upsert_sound_library_entry( - &sounds_dir, - &path_key, - SoundLibraryEntry { - enabled, - source: SoundSource::Local, - }, - )?; + state + .store + .update(|s| { + s.sound_library + .entry(path_key.clone()) + .or_default() + .enabled = enabled; + }) + .map_err(|e| format!("사운드 라이브러리 저장 실패: {e}"))?; Ok(SoundSetEnabledResponse { success: true, @@ -271,16 +272,74 @@ pub fn sound_delete( let validated_path = validate_sound_path(&sounds_dir, &sound_path)?; let path_key = normalize_path_string(&validated_path); + // 라이브러리에서 원본 경로를 먼저 조회 + let original_rel_path = state.store.with_state(|s| { + s.sound_library + .get(&path_key) + .and_then(|entry| entry.original_path.clone()) + }); + if validated_path.exists() { fs::remove_file(&validated_path).map_err(|e| format!("사운드 파일 삭제 실패: {e}"))?; } - let mut library = load_sound_library(&sounds_dir); - if library.remove(&path_key).is_some() { - save_sound_library(&sounds_dir, &library)?; + // 원본 파일도 삭제 + if let Some(ref orig_rel) = original_rel_path { + let orig_abs = sounds_dir.join(orig_rel); + if orig_abs.exists() && orig_abs.starts_with(&sounds_dir) { + if let Err(e) = fs::remove_file(&orig_abs) { + log::warn!("[Sound] 원본 사운드 파일 삭제 실패: {e}"); + } + } } - clear_deleted_sound_references(&state, &app, &path_key)?; + // 라이브러리 엔트리 제거 + 키 참조 정리를 하나의 트랜잭션으로 + let mut references_changed = false; + let updated = state + .store + .update(|store| { + store.sound_library.remove(&path_key); + + for positions in store.key_positions.values_mut() { + for position in positions.iter_mut() { + if position.sound_path.as_deref() == Some(&path_key) { + position.sound_path = None; + position.sound_enabled = Some(false); + references_changed = true; + } + } + } + + for positions in store.stat_positions.values_mut() { + for stat_position in positions.iter_mut() { + if stat_position.position.sound_path.as_deref() == Some(&path_key) { + stat_position.position.sound_path = None; + stat_position.position.sound_enabled = Some(false); + references_changed = true; + } + } + } + + for positions in store.graph_positions.values_mut() { + for graph_position in positions.iter_mut() { + if graph_position.position.sound_path.as_deref() == Some(&path_key) { + graph_position.position.sound_path = None; + graph_position.position.sound_enabled = Some(false); + references_changed = true; + } + } + } + }) + .map_err(|e| format!("사운드 삭제 처리 실패: {e}"))?; + + if references_changed { + app.emit("positions:changed", &updated.key_positions) + .map_err(|e| format!("positions:changed emit 실패: {e}"))?; + app.emit("statPositions:changed", &updated.stat_positions) + .map_err(|e| format!("statPositions:changed emit 실패: {e}"))?; + app.emit("graphPositions:changed", &updated.graph_positions) + .map_err(|e| format!("graphPositions:changed emit 실패: {e}"))?; + } Ok(SoundDeleteResponse { success: true }) } @@ -288,6 +347,7 @@ pub fn sound_delete( #[tauri::command(permission = "dmnote-allow-all")] pub fn sound_save_processed_wav( app: tauri::AppHandle, + state: State<'_, AppState>, request: SoundSaveProcessedWavRequest, ) -> Result { let encoded = request.wav_base64.trim(); @@ -336,15 +396,45 @@ pub fn sound_save_processed_wav( } fs::write(&dest_path, wav_bytes).map_err(|e| format!("편집된 사운드 저장 실패: {e}"))?; + // 원본 파일 저장 + let mut original_rel_path: Option = None; + if let Some(ref orig_b64) = request.original_base64 { + let trimmed_orig = orig_b64.trim(); + if !trimmed_orig.is_empty() { + let orig_bytes = BASE64_STANDARD + .decode(trimmed_orig) + .map_err(|e| format!("원본 사운드 데이터 디코딩 실패: {e}"))?; + let orig_ext = request + .original_extension + .as_deref() + .unwrap_or("wav") + .to_lowercase(); + let originals_dir = ensure_originals_dir(&app)?; + let orig_filename = format!("{}.{}", Uuid::new_v4(), orig_ext); + let orig_path = originals_dir.join(&orig_filename); + fs::write(&orig_path, orig_bytes) + .map_err(|e| format!("원본 사운드 저장 실패: {e}"))?; + original_rel_path = Some(format!("originals/{}", orig_filename)); + } + } + let dest_path_str = normalize_path_string(&dest_path); - upsert_sound_library_entry( - &sounds_dir, - &dest_path_str, - SoundLibraryEntry { - enabled: true, - source: SoundSource::Local, - }, - )?; + state + .store + .update(|s| { + s.sound_library.insert( + dest_path_str.clone(), + SoundLibraryEntry { + enabled: true, + source: SoundSource::Local, + original_path: original_rel_path.clone(), + trim_start_ratio: request.trim_start_ratio, + trim_end_ratio: request.trim_end_ratio, + display_name: request.file_name.clone(), + }, + ); + }) + .map_err(|e| format!("사운드 라이브러리 저장 실패: {e}"))?; Ok(SoundSaveProcessedWavResponse { success: true, @@ -353,99 +443,129 @@ pub fn sound_save_processed_wav( }) } -fn clear_deleted_sound_references( - state: &State<'_, AppState>, - app: &tauri::AppHandle, - deleted_path: &str, -) -> Result<(), String> { - let mut changed = false; - let updated = state - .store - .update(|store| { - for positions in store.key_positions.values_mut() { - for position in positions.iter_mut() { - if position.sound_path.as_deref() == Some(deleted_path) { - position.sound_path = None; - position.sound_enabled = Some(false); - changed = true; - } - } - } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SoundLoadOriginalResponse { + pub success: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub audio_base64: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub original_extension: Option, +} - for positions in store.stat_positions.values_mut() { - for stat_position in positions.iter_mut() { - if stat_position.position.sound_path.as_deref() == Some(deleted_path) { - stat_position.position.sound_path = None; - stat_position.position.sound_enabled = Some(false); - changed = true; - } - } - } +/// 편집을 위해 원본 사운드 파일을 base64로 반환 +#[tauri::command(permission = "dmnote-allow-all")] +pub fn sound_load_original( + app: tauri::AppHandle, + state: State<'_, AppState>, + sound_path: String, +) -> Result { + let sounds_dir = ensure_sounds_dir(&app)?; + let validated_path = validate_sound_path(&sounds_dir, &sound_path)?; + let path_key = normalize_path_string(&validated_path); - for positions in store.graph_positions.values_mut() { - for graph_position in positions.iter_mut() { - if graph_position.position.sound_path.as_deref() == Some(deleted_path) { - graph_position.position.sound_path = None; - graph_position.position.sound_enabled = Some(false); - changed = true; - } - } - } + let original_rel = state + .store + .with_state(|s| { + s.sound_library + .get(&path_key) + .and_then(|e| e.original_path.clone()) }) - .map_err(|e| format!("사운드 참조 정리 실패: {e}"))?; + .ok_or("원본 파일 정보가 없습니다.")?; - if changed { - app.emit("positions:changed", &updated.key_positions) - .map_err(|e| format!("positions:changed emit 실패: {e}"))?; - app.emit("statPositions:changed", &updated.stat_positions) - .map_err(|e| format!("statPositions:changed emit 실패: {e}"))?; - app.emit("graphPositions:changed", &updated.graph_positions) - .map_err(|e| format!("graphPositions:changed emit 실패: {e}"))?; + let original_abs = sounds_dir.join(&original_rel); + if !original_abs.starts_with(&sounds_dir) { + return Err("잘못된 원본 경로입니다.".to_string()); } + if !original_abs.exists() { + return Err("원본 파일이 존재하지 않습니다.".to_string()); + } + + let bytes = + fs::read(&original_abs).map_err(|e| format!("원본 사운드 파일 읽기 실패: {e}"))?; + let encoded = BASE64_STANDARD.encode(&bytes); + + let ext = original_abs + .extension() + .and_then(|e| e.to_str()) + .map(|s| s.to_lowercase()); + + Ok(SoundLoadOriginalResponse { + success: true, + error: None, + audio_base64: Some(encoded), + original_extension: ext, + }) +} - Ok(()) +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SoundUpdateProcessedWavRequest { + pub sound_path: String, + pub wav_base64: String, + pub trim_start_ratio: Option, + pub trim_end_ratio: Option, + pub display_name: Option, } -fn sound_library_path(sounds_dir: &Path) -> PathBuf { - sounds_dir.join(SOUND_LIBRARY_FILE_NAME) +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SoundUpdateProcessedWavResponse { + pub success: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, } -fn load_sound_library(sounds_dir: &Path) -> HashMap { - let path = sound_library_path(sounds_dir); - if !path.exists() { - return HashMap::new(); +/// 기존 트리밍 파일을 새 WAV로 덮어쓰고 메타데이터 갱신 +#[tauri::command(permission = "dmnote-allow-all")] +pub fn sound_update_processed_wav( + app: tauri::AppHandle, + state: State<'_, AppState>, + request: SoundUpdateProcessedWavRequest, +) -> Result { + let sounds_dir = ensure_sounds_dir(&app)?; + let validated_path = validate_sound_path(&sounds_dir, &request.sound_path)?; + let path_key = normalize_path_string(&validated_path); + + let wav_bytes = BASE64_STANDARD + .decode(request.wav_base64.trim()) + .map_err(|e| format!("사운드 데이터 디코딩 실패: {e}"))?; + + let is_valid_wav = wav_bytes.len() >= 12 + && wav_bytes.get(0..4) == Some(b"RIFF") + && wav_bytes.get(8..12) == Some(b"WAVE"); + if !is_valid_wav { + return Ok(SoundUpdateProcessedWavResponse { + success: false, + error: Some("유효한 WAV 데이터가 아닙니다.".to_string()), + }); } - let raw = match fs::read_to_string(&path) { - Ok(value) => value, - Err(error) => { - log::warn!("[Sound] failed to read sound library file: {error}"); - return HashMap::new(); - } - }; + fs::write(&validated_path, wav_bytes) + .map_err(|e| format!("편집된 사운드 저장 실패: {e}"))?; - serde_json::from_str::>(&raw).unwrap_or_else(|error| { - log::warn!("[Sound] failed to parse sound library file: {error}"); - HashMap::new() - }) -} + state + .store + .update(|s| { + if let Some(entry) = s.sound_library.get_mut(&path_key) { + entry.trim_start_ratio = request.trim_start_ratio; + entry.trim_end_ratio = request.trim_end_ratio; + if let Some(ref name) = request.display_name { + entry.display_name = Some(name.clone()); + } + } + }) + .map_err(|e| format!("사운드 라이브러리 저장 실패: {e}"))?; -fn save_sound_library(sounds_dir: &Path, library: &HashMap) -> Result<(), String> { - let path = sound_library_path(sounds_dir); - let serialized = - serde_json::to_string_pretty(library).map_err(|e| format!("사운드 라이브러리 직렬화 실패: {e}"))?; - fs::write(path, serialized).map_err(|e| format!("사운드 라이브러리 저장 실패: {e}"))?; - Ok(()) -} + // 키음 엔진 캐시에서 이전 디코딩 결과 무효화 + state.key_sound_invalidate_file_cache(&path_key); -fn upsert_sound_library_entry( - sounds_dir: &Path, - sound_path: &str, - entry: SoundLibraryEntry, -) -> Result<(), String> { - let mut library = load_sound_library(sounds_dir); - library.insert(sound_path.to_string(), entry); - save_sound_library(sounds_dir, &library) + Ok(SoundUpdateProcessedWavResponse { + success: true, + error: None, + }) } fn validate_sound_path(sounds_dir: &Path, sound_path: &str) -> Result { @@ -473,6 +593,14 @@ fn ensure_sounds_dir(app: &tauri::AppHandle) -> Result { Ok(sounds_dir) } +fn ensure_originals_dir(app: &tauri::AppHandle) -> Result { + let sounds_dir = ensure_sounds_dir(app)?; + let originals_dir = sounds_dir.join("originals"); + fs::create_dir_all(&originals_dir) + .map_err(|e| format!("원본 사운드 디렉토리 생성 실패: {e}"))?; + Ok(originals_dir) +} + fn is_supported_sound_file(path: &Path) -> bool { let Some(ext) = path.extension().and_then(|ext| ext.to_str()) else { return false; diff --git a/src-tauri/src/key_sound.rs b/src-tauri/src/key_sound.rs index 1e3fc0f..db2ee7b 100644 --- a/src-tauri/src/key_sound.rs +++ b/src-tauri/src/key_sound.rs @@ -199,6 +199,7 @@ enum AudioCommand { SetVolume(f32), SetLatencyLogging(bool), SetSoundpack(Option>), + InvalidateFileCache { path: String }, } #[derive(Debug, Clone)] @@ -325,6 +326,12 @@ impl KeySoundEngine { }); } + pub fn invalidate_file_cache(&self, path: &str) { + let _ = self.sender.send(AudioCommand::InvalidateFileCache { + path: path.to_string(), + }); + } + pub fn play_file( &self, path: &str, @@ -389,6 +396,9 @@ fn audio_thread( AudioCommand::SetSoundpack(pack) => { soundpack = pack; } + AudioCommand::InvalidateFileCache { path } => { + file_cache.remove(&path); + } AudioCommand::PlayLabels { labels, queued_at, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 7ebc32b..01e0294 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -200,6 +200,8 @@ fn main() { commands::sound::sound_set_enabled, commands::sound::sound_delete, commands::sound::sound_save_processed_wav, + commands::sound::sound_load_original, + commands::sound::sound_update_processed_wav, commands::js::js_get, commands::js::js_get_use, commands::js::js_toggle, diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index e125916..a6660e4 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -55,6 +55,51 @@ impl Default for FontSettings { } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum SoundSource { + Builtin, + Local, +} + +fn default_sound_source() -> SoundSource { + SoundSource::Local +} + +fn default_sound_enabled() -> bool { + true +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct SoundLibraryEntry { + #[serde(default = "default_sound_enabled")] + pub enabled: bool, + #[serde(default = "default_sound_source")] + pub source: SoundSource, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub original_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub trim_start_ratio: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub trim_end_ratio: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub display_name: Option, +} + +impl Default for SoundLibraryEntry { + fn default() -> Self { + Self { + enabled: true, + source: SoundSource::Local, + original_path: None, + trim_start_ratio: None, + trim_end_ratio: None, + display_name: None, + } + } +} + // Serialize as: // - Solid: JSON string (e.g., "#FF00FF") // - Gradient: object with explicit type { type: "gradient", top, bottom } @@ -794,6 +839,9 @@ pub struct AppStoreData { /// 단축키 설정 #[serde(default)] pub shortcuts: ShortcutsState, + /// 사운드 라이브러리 메타데이터 (키: 절대 경로, 값: 메타데이터) + #[serde(default)] + pub sound_library: HashMap, /// 플러그인 데이터 저장소 (plugin_data_* 키로 저장) #[serde(default, flatten)] pub plugin_data: HashMap, @@ -840,6 +888,7 @@ impl Default for AppStoreData { key_counter_enabled: false, grid_settings: GridSettings::default(), shortcuts: ShortcutsState::default(), + sound_library: HashMap::new(), plugin_data: HashMap::new(), } } diff --git a/src-tauri/src/store.rs b/src-tauri/src/store.rs index ffd5e03..bbf71aa 100644 --- a/src-tauri/src/store.rs +++ b/src-tauri/src/store.rs @@ -640,6 +640,14 @@ fn collect_local_sound_path_keys(data: &AppStoreData) -> HashSet { } } + // 사운드 라이브러리에 등록된 파일도 보호 (키에 할당 안 되어도 유지) + for key in data.sound_library.keys() { + let normalized = PathBuf::from(key); + if normalized.is_absolute() { + paths.insert(path_lookup_key(&normalized)); + } + } + paths } diff --git a/src/renderer/api/dmnoteApi.ts b/src/renderer/api/dmnoteApi.ts index 07cfd0e..cda00d5 100644 --- a/src/renderer/api/dmnoteApi.ts +++ b/src/renderer/api/dmnoteApi.ts @@ -320,11 +320,49 @@ const api: DMNoteAPI = { invoke("sound_delete", { soundPath, }), - saveProcessedWav: (wavBase64: string, fileName?: string) => + saveProcessedWav: ( + wavBase64: string, + fileName?: string, + originalBase64?: string, + originalExtension?: string, + trimStartRatio?: number, + trimEndRatio?: number, + ) => invoke( "sound_save_processed_wav", { - request: { wavBase64, fileName }, + request: { + wavBase64, + fileName, + originalBase64, + originalExtension, + trimStartRatio, + trimEndRatio, + }, + }, + ), + loadOriginal: (soundPath: string) => + invoke( + "sound_load_original", + { soundPath }, + ), + updateProcessedWav: ( + soundPath: string, + wavBase64: string, + trimStartRatio?: number, + trimEndRatio?: number, + displayName?: string, + ) => + invoke( + "sound_update_processed_wav", + { + request: { + soundPath, + wavBase64, + trimStartRatio, + trimEndRatio, + displayName, + }, }, ), setLatencyLogging: (enabled: boolean) => diff --git a/src/renderer/components/main/Modal/content/SoundManagerModal.tsx b/src/renderer/components/main/Modal/content/SoundManagerModal.tsx index 6165e2d..a3e4ac7 100644 --- a/src/renderer/components/main/Modal/content/SoundManagerModal.tsx +++ b/src/renderer/components/main/Modal/content/SoundManagerModal.tsx @@ -38,6 +38,7 @@ export default function SoundManagerModal({ const [isSaving, setIsSaving] = useState(false); const [loadError, setLoadError] = useState(""); const [showTrimModal, setShowTrimModal] = useState(false); + const [editingSoundPath, setEditingSoundPath] = useState(null); const contentRef = useRef(null); const [scrollState, setScrollState] = useState({ @@ -48,12 +49,21 @@ export default function SoundManagerModal({ const [containerHeight, setContainerHeight] = useState(null); const [isScrollable, setIsScrollable] = useState(false); const isFirstRender = useRef(true); + const hasLoadedRef = useRef(false); const normalizedSelectedSound = useMemo( () => (selectedSound || "").trim(), [selectedSound], ); + const editingSoundItem = useMemo( + () => + editingSoundPath + ? sounds.find((s) => s.soundPath === editingSoundPath) ?? null + : null, + [editingSoundPath, sounds], + ); + const loadSounds = useCallback(async () => { setIsLoading(true); setLoadError(""); @@ -66,6 +76,7 @@ export default function SoundManagerModal({ t("soundManager.loadFailed") || "사운드 목록 로드 실패", ); } finally { + hasLoadedRef.current = true; setIsLoading(false); } }, [t]); @@ -91,7 +102,11 @@ export default function SoundManagerModal({ }); useLayoutEffect(() => { - if (!isOpen) return; + if (!isOpen) { + isFirstRender.current = true; + hasLoadedRef.current = false; + return; + } setSkipShadowTransition(true); setScrollState({ hasTopShadow: false, hasBottomShadow: false }); @@ -124,7 +139,9 @@ export default function SoundManagerModal({ const rafId = requestAnimationFrame(() => { setSkipShadowTransition(false); - isFirstRender.current = false; + if (hasLoadedRef.current) { + isFirstRender.current = false; + } }); return () => { @@ -184,6 +201,27 @@ export default function SoundManagerModal({ [isSaving, loadSounds, normalizedSelectedSound, onSelectSound, t], ); + const handleEditSound = useCallback((item: SoundListItem) => { + if (!item.originalPath) return; + setEditingSoundPath(item.soundPath); + setShowTrimModal(true); + }, []); + + const handleCloseTrimModal = useCallback(() => { + setShowTrimModal(false); + setEditingSoundPath(null); + }, []); + + const handleTrimSaved = useCallback( + (soundPath: string) => { + onSelectSound(soundPath); + setShowTrimModal(false); + setEditingSoundPath(null); + void loadSounds(); + }, + [loadSounds, onSelectSound], + ); + if (!isOpen) return null; return ( @@ -220,9 +258,7 @@ export default function SoundManagerModal({ {t("soundManager.noSounds") || "사운드 없음"}
) : ( - sounds.map((item) => { - const isSelected = item.soundPath === normalizedSelectedSound; - return ( + sounds.map((item) => ( - - {item.fileName} - + {item.originalPath ? ( + + ) : ( + + {item.displayName || item.fileName} + + )}
- ); - }) + )) )}
@@ -279,7 +328,10 @@ export default function SoundManagerModal({ @@ -306,13 +358,13 @@ export default function SoundManagerModal({ setShowTrimModal(false)} - onSaved={(soundPath) => { - onSelectSound(soundPath); - setShowTrimModal(false); - void loadSounds(); - }} + onClose={handleCloseTrimModal} + onSaved={handleTrimSaved} previewVolume={previewVolume} + editingSoundPath={editingSoundPath} + editingTrimStartRatio={editingSoundItem?.trimStartRatio} + editingTrimEndRatio={editingSoundItem?.trimEndRatio} + editingDisplayName={editingSoundItem?.displayName} /> ); diff --git a/src/renderer/components/main/Modal/content/SoundTrimModal.tsx b/src/renderer/components/main/Modal/content/SoundTrimModal.tsx index c36ccd9..fd2fca6 100644 --- a/src/renderer/components/main/Modal/content/SoundTrimModal.tsx +++ b/src/renderer/components/main/Modal/content/SoundTrimModal.tsx @@ -13,6 +13,10 @@ interface SoundTrimModalProps { onClose: () => void; onSaved: (soundPath: string) => void; previewVolume?: number; + editingSoundPath?: string | null; + editingTrimStartRatio?: number; + editingTrimEndRatio?: number; + editingDisplayName?: string; } type DragTarget = "start" | "end" | null; @@ -47,6 +51,17 @@ async function decodeAudioFile(file: File): Promise { } } +async function decodeAudioFromArrayBuffer( + buffer: ArrayBuffer, +): Promise { + const context = createAudioContext(); + try { + return await context.decodeAudioData(buffer.slice(0)); + } finally { + void context.close(); + } +} + function extractWaveformPeaks( buffer: AudioBuffer, peakCount = WAVEFORM_PEAK_COUNT, @@ -237,6 +252,26 @@ function encodeWavBase64( return btoa(binary); } +function arrayBufferToBase64(buffer: ArrayBuffer): string { + const bytes = new Uint8Array(buffer); + let binary = ""; + const chunkSize = 0x8000; + for (let i = 0; i < bytes.length; i += chunkSize) { + const chunk = bytes.subarray(i, i + chunkSize); + binary += String.fromCharCode(...chunk); + } + return btoa(binary); +} + +function base64ToArrayBuffer(base64: string): ArrayBuffer { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i += 1) { + bytes[i] = binary.charCodeAt(i); + } + return bytes.buffer; +} + function stripExtension(name: string): string { const lastDot = name.lastIndexOf("."); return lastDot > 0 ? name.slice(0, lastDot) : name; @@ -247,6 +282,10 @@ export default function SoundTrimModal({ onClose, onSaved, previewVolume = 100, + editingSoundPath, + editingTrimStartRatio, + editingTrimEndRatio, + editingDisplayName, }: SoundTrimModalProps) { const { t } = useTranslation(); const fileInputRef = useRef(null); @@ -254,6 +293,8 @@ export default function SoundTrimModal({ const canvasRef = useRef(null); const dragTargetRef = useRef(null); + const isEditMode = !!editingSoundPath; + const [originalFileName, setOriginalFileName] = useState(""); const [soundName, setSoundName] = useState(""); const [audioBuffer, setAudioBuffer] = useState(null); @@ -265,6 +306,11 @@ export default function SoundTrimModal({ const [isPlaying, setIsPlaying] = useState(false); const [errorText, setErrorText] = useState(""); + const originalFileDataRef = useRef<{ + base64: string; + extension: string; + } | null>(null); + const playContextRef = useRef(null); const playSourceRef = useRef(null); const playStartCtxTimeRef = useRef(0); @@ -421,6 +467,7 @@ export default function SoundTrimModal({ const resetState = useCallback(() => { pausedAtRatioRef.current = null; + originalFileDataRef.current = null; stopPlayback(); setOriginalFileName(""); setSoundName(""); @@ -448,6 +495,52 @@ export default function SoundTrimModal({ } }, [isOpen, resetState]); + // Edit mode: load original audio from backend + useEffect(() => { + if (!isOpen || !editingSoundPath) return; + + let cancelled = false; + setIsDecoding(true); + setErrorText(""); + setSoundName(editingDisplayName || ""); + setOriginalFileName(editingDisplayName || ""); + + (async () => { + try { + const result = await window.api.sound.loadOriginal(editingSoundPath); + if (cancelled) return; + + if (!result.success || !result.audioBase64) { + throw new Error( + result.error || "Failed to load original audio", + ); + } + + const arrayBuffer = base64ToArrayBuffer(result.audioBase64); + const decoded = await decodeAudioFromArrayBuffer(arrayBuffer); + if (cancelled) return; + + setAudioBuffer(decoded); + setPeaks(extractWaveformPeaks(decoded)); + setStartRatio(editingTrimStartRatio ?? 0); + setEndRatio(editingTrimEndRatio ?? 1); + } catch (error) { + if (!cancelled) { + console.error("Failed to load original audio:", error); + setErrorText(t("soundTrimModal.loadOriginalError")); + } + } finally { + if (!cancelled) { + setIsDecoding(false); + } + } + })(); + + return () => { + cancelled = true; + }; + }, [isOpen, editingSoundPath, editingTrimStartRatio, editingTrimEndRatio, editingDisplayName, t]); + useEffect(() => { if (!isOpen) return; if (isPlaying) return; // animation loop handles drawing during playback @@ -488,6 +581,14 @@ export default function SoundTrimModal({ setSoundName(stripExtension(file.name)); try { + // Capture original file data for backend storage + const arrayBuffer = await file.arrayBuffer(); + const ext = file.name.split(".").pop()?.toLowerCase() || "wav"; + originalFileDataRef.current = { + base64: arrayBufferToBase64(arrayBuffer), + extension: ext, + }; + const decoded = await decodeAudioFile(file); setAudioBuffer(decoded); setPeaks(extractWaveformPeaks(decoded)); @@ -499,12 +600,13 @@ export default function SoundTrimModal({ setPeaks(new Float32Array()); setOriginalFileName(""); setSoundName(""); + originalFileDataRef.current = null; setErrorText(t("soundTrimModal.decodeError")); } finally { setIsDecoding(false); } }, - [stopPlayback], + [stopPlayback, t], ); const updateFromClientX = useCallback( @@ -609,19 +711,46 @@ export default function SoundTrimModal({ ); const wavBase64 = encodeWavBase64(audioBuffer, startFrame, endFrame); const trimmedName = soundName.trim() || undefined; - const response = await window.api.sound.saveProcessedWav( - wavBase64, - trimmedName, - ); - if (!response.success || !response.soundPath) { - throw new Error( - response.error || t("soundTrimModal.saveErrorDefault"), + if (isEditMode && editingSoundPath) { + // Edit mode: update existing sound + const response = await window.api.sound.updateProcessedWav( + editingSoundPath, + wavBase64, + startRatio, + endRatio, + trimmedName, ); - } - onSaved(response.soundPath); - resetState(); + if (!response.success) { + throw new Error( + response.error || t("soundTrimModal.saveErrorDefault"), + ); + } + + onSaved(editingSoundPath); + resetState(); + } else { + // Create mode: save new sound + original + const origData = originalFileDataRef.current; + const response = await window.api.sound.saveProcessedWav( + wavBase64, + trimmedName, + origData?.base64, + origData?.extension, + startRatio, + endRatio, + ); + + if (!response.success || !response.soundPath) { + throw new Error( + response.error || t("soundTrimModal.saveErrorDefault"), + ); + } + + onSaved(response.soundPath); + resetState(); + } } catch (error) { console.error("Failed to save processed sound:", error); setErrorText(t("soundTrimModal.saveErrorFailed")); @@ -630,23 +759,34 @@ export default function SoundTrimModal({ } }, [ audioBuffer, + editingSoundPath, endRatio, isDecoding, + isEditMode, isSaving, onSaved, resetState, soundName, startRatio, stopPlayback, + t, ]); const headerLabel = useMemo(() => { if (!audioBuffer) { - if (isDecoding) return t("soundTrimModal.statusDecoding"); + if (isDecoding) { + return isEditMode + ? t("soundTrimModal.statusLoading") + : t("soundTrimModal.statusDecoding"); + } return t("soundTrimModal.statusWaiting"); } return t("soundTrimModal.statusReady"); - }, [audioBuffer, isDecoding, t]); + }, [audioBuffer, isDecoding, isEditMode, t]); + + const headerTitle = isEditMode + ? editingDisplayName || t("soundTrimModal.editTitle") + : originalFileName || t("soundTrimModal.defaultTitle"); if (!isOpen) return null; @@ -663,7 +803,7 @@ export default function SoundTrimModal({ Sound - {originalFileName || t("soundTrimModal.defaultTitle")} + {headerTitle}
@@ -762,7 +902,9 @@ export default function SoundTrimModal({ ) : (
{isDecoding - ? t("soundTrimModal.decodingMessage") + ? isEditMode + ? t("soundTrimModal.statusLoading") + : t("soundTrimModal.decodingMessage") : t("soundTrimModal.emptyMessage")}
)} @@ -779,26 +921,32 @@ export default function SoundTrimModal({ {/* Hint bar */}
- + {!isEditMode ? ( + + ) : ( + + )}

{t("soundTrimModal.dragHint")}

- + {!isEditMode ? ( + + ) : null} {/* Footer */}
@@ -814,7 +962,11 @@ export default function SoundTrimModal({ }} disabled={!canSubmit} > - {isSaving ? t("soundTrimModal.saving") : t("soundTrimModal.submit")} + {isSaving + ? t("soundTrimModal.saving") + : isEditMode + ? t("soundTrimModal.submitEdit") + : t("soundTrimModal.submit")}