diff --git a/.dockerignore b/.dockerignore deleted file mode 100755 index 0b1e1e7..0000000 --- a/.dockerignore +++ /dev/null @@ -1,27 +0,0 @@ -**/__pycache__ -**/.venv -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/bin -**/charts -**/docker-compose* -**/compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -LICENSE -README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d29ef1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +node_modules/ +dist/ +src-tauri/target/ +target/ +.DS_Store +npm-debug.log* +yarn-debug.log* +yarn-error.log* +Fbrowser.7z +baseline_zip.zip +python-src/Dockerfile +python-src/docker-compose.yml +python-src/docker-compose.debug.yml +migrations/0001_init.sql +__pycache__/ +test.png +paq9a.cpp +src-paq-next/output.paqg++ +output_v2.paq +output.paq +pypaqtest.paq +src-paq-next/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100755 index f3d8430..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "configurations": [ - { - "name": "Docker: Python - General", - "type": "docker", - "request": "launch", - "preLaunchTask": "docker-run: debug", - "python": { - "pathMappings": [ - { - "localRoot": "${workspaceFolder}", - "remoteRoot": "/app" - } - ], - "projectType": "general" - } - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 39c0c4f..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "github.gitAuthentication": false -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100755 index 37fc95f..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "type": "docker-build", - "label": "docker-build", - "platform": "python", - "dockerBuild": { - "tag": "fbrowser:latest", - "dockerfile": "${workspaceFolder}/Dockerfile", - "context": "${workspaceFolder}", - "pull": true - } - }, - { - "type": "docker-run", - "label": "docker-run: debug", - "dependsOn": [ - "docker-build" - ], - "python": { - "file": "Fbrowser.py" - } - } - ] -} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..01e8c54 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,6739 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.11.0", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c88dbbce13b232b26250e1e2e6ac18b6a891a646b8148285036ebce260ac5c3" +dependencies = [ + "alsa-sys", + "bitflags 2.11.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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.11.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "coreaudio-rs" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aae284fbaf7d27aa0e292f7677dfbe26503b0d555026f702940805a630eac17" +dependencies = [ + "bitflags 1.3.2", + "libc", + "objc2-audio-toolbox", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", +] + +[[package]] +name = "coremidi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964eb3e10ea8b0d29c797086aab3ca730f75e06dced0cb980642fd274a5cca30" +dependencies = [ + "block", + "core-foundation 0.9.4", + "core-foundation-sys", + "coremidi-sys", +] + +[[package]] +name = "coremidi-sys" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9504310988d938e49fff1b5f1e56e3dafe39bb1bae580c19660b58b83a191e" +dependencies = [ + "core-foundation-sys", +] + +[[package]] +name = "cpal" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1f9c7312f19fc2fa12fd7acaf38de54e8320ba10d1a02dcbe21038def51ccb" +dependencies = [ + "alsa 0.10.0", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "objc2", + "objc2-audio-toolbox", + "objc2-avf-audio", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.61.3", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf 0.13.1", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.11.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser 0.36.0", + "foldhash 0.2.0", + "html5ever 0.38.0", + "precomputed-hash", + "selectors 0.36.1", + "tendril 0.5.0", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "embed-resource" +version = "3.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63a1d0de4f2249aa0ff5884d7080814f446bb241a559af6c170a41e878ed2d45" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.9.12+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +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 = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fbrowser-archive" +version = "0.1.0" +dependencies = [ + "anyhow", + "flate2", + "serde", + "tar", + "tokio", + "zip", +] + +[[package]] +name = "fbrowser-audio" +version = "0.1.0" +dependencies = [ + "anyhow", + "rodio", + "serde", + "symphonia", + "zip", +] + +[[package]] +name = "fbrowser-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "ignore", + "lofty", + "rayon", + "serde", + "serde_json", + "sqlx", + "tokio", + "walkdir", +] + +[[package]] +name = "fbrowser-desktop" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "fbrowser-archive", + "fbrowser-audio", + "fbrowser-core", + "fbrowser-midi", + "serde", + "serde_json", + "sqlx", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "tokio", +] + +[[package]] +name = "fbrowser-midi" +version = "0.1.0" +dependencies = [ + "anyhow", + "fbrowser-audio", + "midir", + "midly", + "serde", +] + +[[package]] +name = "fbrowser-plugin-core" +version = "0.1.0" +dependencies = [ + "serde", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.11.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever 0.14.1", + "match_token", +] + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever 0.38.0", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "js-sys" +version = "0.3.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.11.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser 0.29.6", + "html5ever 0.29.1", + "indexmap 2.13.0", + "selectors 0.24.0", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "lofty" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca260c51a9c71f823fbfd2e6fbc8eb2ee09834b98c00763d877ca8bfa85cde3e" +dependencies = [ + "byteorder", + "data-encoding", + "flate2", + "lofty_attr", + "log", + "ogg_pager", + "paste", +] + +[[package]] +name = "lofty_attr" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9983e64b2358522f745c1251924e3ab7252d55637e80f6a0a3de642d6a9efc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache 0.8.9", + "string_cache_codegen 0.5.4", + "tendril 0.4.3", +] + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril 0.5.0", + "web_atoms", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "midir" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73f8737248ad37b88291a2108d9df5f991dc8555103597d586b5a29d4d703c0" +dependencies = [ + "alsa 0.9.1", + "bitflags 1.3.2", + "coremidi", + "js-sys", + "libc", + "parking_lot", + "wasm-bindgen", + "web-sys", + "windows 0.56.0", +] + +[[package]] +name = "midly" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207d755f4cb882d20c4da58d707ca9130a0c9bc5061f657a4f299b8e36362b7a" +dependencies = [ + "rayon", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.18", + "windows-sys 0.60.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.0", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[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.117", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-audio-toolbox" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" +dependencies = [ + "bitflags 2.11.0", + "libc", + "objc2", + "objc2-core-audio", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-avf-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-audio" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" +dependencies = [ + "dispatch2", + "objc2", + "objc2-core-audio-types", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-audio-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" +dependencies = [ + "bitflags 2.11.0", + "objc2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.0", + "block2", + "dispatch2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.0", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.0", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "ogg_pager" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6d1ca8364b84e0cf725eed06b1460c44671e6c0fb28765f5262de3ece07fdc" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros 0.13.1", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.2", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher 1.0.2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap 2.13.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.8+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rand_distr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" +dependencies = [ + "num-traits", + "rand 0.10.0", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rodio" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a536bb79db59098ef71a4dd4246c02eb87b316deceb1b68e0cde7167ec01eb" +dependencies = [ + "cpal", + "dasp_sample", + "num-rational", + "rand 0.10.0", + "rand_distr", + "rtrb", + "symphonia", + "thiserror 2.0.18", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rtrb" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7204ed6420f698836b76d4d5c2ec5dec7585fd5c3a788fd1cde855d1de598239" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser 0.29.6", + "derive_more 0.99.20", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc 0.2.0", + "smallvec", +] + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.11.0", + "cssparser 0.36.0", + "derive_more 2.1.1", + "log", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen 0.13.1", + "precomputed-hash", + "rustc-hash", + "servo_arc 0.4.3", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall 0.5.18", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap 2.13.0", + "log", + "memchr", + "once_cell", + "percent-encoding", + "rustls", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.117", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.117", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.11.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.11.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.18", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.13.1", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", +] + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "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-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-adpcm", + "symphonia-codec-alac", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-isomp4", + "symphonia-format-mkv", + "symphonia-format-ogg", + "symphonia-format-riff", + "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" +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-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" +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-mkv" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122d786d2c43a49beb6f397551b4a050d8229eaa54c7ddf9ee4b98899b8742d0" +dependencies = [ + "lazy_static", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.34.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9103edf55f2da3c82aea4c7fab7c4241032bfeea0e71fa557d98e00e7ce7cc20" +dependencies = [ + "bitflags 2.11.0", + "block2", + "core-foundation 0.10.1", + "core-graphics", + "crossbeam-channel", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "parking_lot", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tar" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da77cc00fb9028caf5b5d4650f75e31f1ef3693459dfca7f7e506d1ecef0ba2d" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.18", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows 0.61.3", +] + +[[package]] +name = "tauri-build" +version = "2.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bbc990d1dbf57a8e1c7fa2327f2a614d8b757805603c1b9ba5c81bade09fd4d" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a24476afd977c5d5d169f72425868613d82747916dd29e0a357c84c4bd6d29" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.117", + "tauri-utils", + "thiserror 2.0.18", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39b349a98dadaffebb73f0a40dcd1f23c999211e5a2e744403db384d0c33de7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddde7d51c907b940fb573006cdda9a642d6a7c8153657e88f8a5c3c9290cd4aa" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", + "url", +] + +[[package]] +name = "tauri-runtime" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2826d79a3297ed08cd6ea7f412644ef58e32969504bc4fbd8d7dbeabc4445ea2" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.61.3", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11ea2e6f801d275fdd890d6c9603736012742a1c33b96d0db788c9cdebf7f9e" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.61.3", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219a1f983a2af3653f75b5747f76733b0da7ff03069c7a41901a5eb3ace4557d" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever 0.29.1", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" +dependencies = [ + "dunce", + "embed-resource", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "tendril" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4790fc369d5a530f4b544b094e31388b9b3a37c0f4652ade4505945f5660d24" +dependencies = [ + "new_debug_unreachable", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned 1.1.0", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.13.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 1.1.0+spec-1.1.0", + "toml_parser", + "winnow 1.0.0", +] + +[[package]] +name = "toml_parser" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +dependencies = [ + "winnow 1.0.0", +] + +[[package]] +name = "toml_writer" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.18", + "windows-sys 0.60.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d1faf851e778dfa54db7cd438b70758eba9755cb47403f3496edd7c8fc212f0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cde8507f4d7cfcb1185b8cb5890c494ffea65edbe1ba82cfd63661c805ed94" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a9779e9f04d2ac1ce317aee707aa2f6b773afba7b931222bff6983843b1576" +dependencies = [ + "phf 0.13.1", + "phf_codegen 0.13.1", + "string_cache 0.9.0", + "string_cache_codegen 0.6.1", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-implement 0.60.2", + "windows-interface 0.59.3", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.18", + "windows 0.61.3", + "windows-core 0.61.2", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" +dependencies = [ + "windows-core 0.56.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +dependencies = [ + "windows-implement 0.56.0", + "windows-interface 0.56.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wry" +version = "0.54.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a8135d8676225e5744de000d4dff5a082501bf7db6a1c1495034f8c314edbc" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.13.0", + "memchr", + "thiserror 2.0.18", + "zopfli", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..619854b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,36 @@ +[workspace] +members = [ + "crates/fbrowser-core", + "crates/fbrowser-audio", + "crates/fbrowser-midi", + "crates/fbrowser-archive", + "crates/fbrowser-plugin-core", + "src-tauri" +] +resolver = "2" + +[workspace.package] +edition = "2021" +license = "MIT" +version = "0.1.0" + +[workspace.dependencies] +anyhow = "1.0.98" +chrono = { version = "0.4.41", features = ["serde"] } +flate2 = "1.1.1" +ignore = "0.4.23" +lofty = "0.22.4" +midir = "0.10.1" +midly = "0.5.3" +rayon = "1.10.0" +rodio = { version = "0.22.2", default-features = true } +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +sqlx = { version = "0.8.4", features = ["sqlite", "runtime-tokio-rustls", "chrono"] } +symphonia = { version = "0.5.4", features = ["aac", "aiff", "alac", "flac", "isomp4", "mp3", "ogg", "pcm", "vorbis", "wav"] } +tar = "0.4.44" +tauri = { version = "2.5.1", features = [] } +tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread", "sync", "time"] } +uuid = { version = "1.17.0", features = ["serde", "v4"] } +walkdir = "2.5.0" +zip = { version = "2.4.1", default-features = false, features = ["deflate"] } diff --git a/Dockerfile b/Dockerfile deleted file mode 100755 index 109f18f..0000000 --- a/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# For more information, please refer to https://aka.ms/vscode-docker-python -FROM python:3-slim - -# Keeps Python from generating .pyc files in the container -ENV PYTHONDONTWRITEBYTECODE=1 - -# Turns off buffering for easier container logging -ENV PYTHONUNBUFFERED=1 - -# Install pip requirements -COPY requirements.txt . -RUN python -m pip install -r requirements.txt - -WORKDIR /app -COPY . /app - -# Creates a non-root user with an explicit UID and adds permission to access the /app folder -# For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers -RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -R appuser /app -USER appuser - -# During debugging, this entry point will be overridden. For more information, please refer to https://aka.ms/vscode-docker-python-debug -CMD ["python", "Fbrowser.py"] diff --git a/Fbroswer.tar.gz b/Fbroswer.tar.gz deleted file mode 100644 index ba23803..0000000 Binary files a/Fbroswer.tar.gz and /dev/null differ diff --git a/Fbrowser.zip b/Fbrowser.zip deleted file mode 100755 index a09a7bf..0000000 Binary files a/Fbrowser.zip and /dev/null differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd22236 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Fbrowser + +Fbrowser is being migrated from a Python/PyQt desktop app to a Rust + Tauri + React desktop application. + +## Repository Layout + +- `src/`: React frontend for the new desktop app. +- `src-tauri/`: Tauri host application and IPC command layer. +- `crates/`: Shared Rust workspace crates for catalog, audio, MIDI, archives, and future plugin-facing APIs. +- `migrations/`: SQLite schema migrations for the new local catalog. +- `python-src/`: Legacy Python implementation kept as a feature reference during migration. + +## Current Status + +- The new Tauri/Rust/React application builds successfully. +- The legacy Python code has been moved out of the repository root to keep the migration boundary clear. +- The desktop shell, catalog model, scan pipeline, archive utilities, timer, waveform generation, and transport scaffolding are in place. +- Some areas are still scaffold-level rather than production-complete, especially MIDI playback internals and broader archive-format support. + +## Verification + +- Frontend production build: `npm run build` +- Rust workspace checks/tests: `cargo check --manifest-path src-tauri/Cargo.toml` and `cargo test --workspace` + +## Notes + +- The Python code under `python-src/` is no longer the primary application entrypoint. +- The new root-level app is the active migration target. diff --git a/__pycache__/MidPlay.cpython-310.pyc b/__pycache__/MidPlay.cpython-310.pyc deleted file mode 100755 index 00219d1..0000000 Binary files a/__pycache__/MidPlay.cpython-310.pyc and /dev/null differ diff --git a/__pycache__/ScanOrg.cpython-310.pyc b/__pycache__/ScanOrg.cpython-310.pyc deleted file mode 100755 index be7279a..0000000 Binary files a/__pycache__/ScanOrg.cpython-310.pyc and /dev/null differ diff --git a/crates/fbrowser-archive/Cargo.toml b/crates/fbrowser-archive/Cargo.toml new file mode 100644 index 0000000..90747e8 --- /dev/null +++ b/crates/fbrowser-archive/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "fbrowser-archive" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +anyhow.workspace = true +flate2.workspace = true +serde.workspace = true +tar.workspace = true +tokio.workspace = true +zip.workspace = true diff --git a/crates/fbrowser-archive/src/lib.rs b/crates/fbrowser-archive/src/lib.rs new file mode 100644 index 0000000..4524804 --- /dev/null +++ b/crates/fbrowser-archive/src/lib.rs @@ -0,0 +1,345 @@ +use std::fs::{self, File}; +use std::io::{self, Read}; +use std::path::{Path, PathBuf}; + +use anyhow::{bail, Result}; +use flate2::read::GzDecoder; +use flate2::write::GzEncoder; +use flate2::Compression; +use serde::{Deserialize, Serialize}; +use tar::{Archive as TarArchive, Builder as TarBuilder}; +use zip::write::SimpleFileOptions; +use zip::{CompressionMethod, ZipArchive, ZipWriter}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ArchiveJobSpec { + pub source: String, + pub destination: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ArchiveJobResult { + pub output_path: String, + pub processed_entries: usize, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ArchiveFormat { + Zip, + Tar, + TarGz, +} + +pub fn extract(spec: &ArchiveJobSpec) -> Result { + let source = Path::new(&spec.source); + let destination = Path::new(&spec.destination); + fs::create_dir_all(destination)?; + + match detect_archive_format(source) { + Some(ArchiveFormat::Zip) => extract_zip(source, destination), + Some(ArchiveFormat::Tar) => extract_tar(File::open(source)?, destination), + Some(ArchiveFormat::TarGz) => extract_tar(GzDecoder::new(File::open(source)?), destination), + None => bail!( + "unsupported archive format for extract: {} (supported: .zip, .tar, .tar.gz, .tgz)", + source.display() + ), + } +} + +pub fn compress(spec: &ArchiveJobSpec) -> Result { + let source = Path::new(&spec.source); + let destination = Path::new(&spec.destination); + if let Some(parent) = destination.parent() { + fs::create_dir_all(parent)?; + } + + match detect_archive_format(destination) { + Some(ArchiveFormat::Zip) => compress_zip(source, destination), + Some(ArchiveFormat::Tar) => compress_tar(source, destination, false), + Some(ArchiveFormat::TarGz) => compress_tar(source, destination, true), + None => bail!( + "unsupported archive format for compress: {} (supported: .zip, .tar, .tar.gz, .tgz)", + destination.display() + ), + } +} + +fn detect_archive_format(path: &Path) -> Option { + let lower_name = path.file_name()?.to_str()?.to_ascii_lowercase(); + if lower_name.ends_with(".tar.gz") || lower_name.ends_with(".tgz") { + Some(ArchiveFormat::TarGz) + } else if lower_name.ends_with(".tar") { + Some(ArchiveFormat::Tar) + } else if lower_name.ends_with(".zip") { + Some(ArchiveFormat::Zip) + } else { + None + } +} + +fn compress_zip(source: &Path, destination: &Path) -> Result { + let file = File::create(destination)?; + let mut zip = ZipWriter::new(file); + let options = SimpleFileOptions::default().compression_method(CompressionMethod::Deflated); + let mut processed = 0_usize; + + if source.is_file() { + let name = source + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("item") + .to_string(); + add_file_to_zip(&mut zip, source, &name, options)?; + processed += 1; + } else { + for entry in walk(source)? { + let relative = entry.strip_prefix(source)?.to_string_lossy().replace('\\', "/"); + add_file_to_zip(&mut zip, &entry, &relative, options)?; + processed += 1; + } + } + + zip.finish()?; + Ok(ArchiveJobResult { + output_path: destination.display().to_string(), + processed_entries: processed, + }) +} + +fn compress_tar(source: &Path, destination: &Path, gzip: bool) -> Result { + let mut processed = 0_usize; + + if gzip { + let file = File::create(destination)?; + let writer = GzEncoder::new(file, Compression::default()); + let mut builder = TarBuilder::new(writer); + append_to_tar(&mut builder, source, &mut processed)?; + builder.finish()?; + } else { + let file = File::create(destination)?; + let mut builder = TarBuilder::new(file); + append_to_tar(&mut builder, source, &mut processed)?; + builder.finish()?; + } + + Ok(ArchiveJobResult { + output_path: destination.display().to_string(), + processed_entries: processed, + }) +} + +fn extract_zip(source: &Path, destination: &Path) -> Result { + let file = File::open(source)?; + let mut archive = ZipArchive::new(file)?; + let mut processed = 0_usize; + + for index in 0..archive.len() { + let mut entry = archive.by_index(index)?; + let Some(relative_path) = entry.enclosed_name().map(|path| path.to_owned()) else { + continue; + }; + let out_path = destination.join(relative_path); + if entry.is_dir() { + fs::create_dir_all(&out_path)?; + continue; + } + if let Some(parent) = out_path.parent() { + fs::create_dir_all(parent)?; + } + let mut out = File::create(&out_path)?; + io::copy(&mut entry, &mut out)?; + processed += 1; + } + + Ok(ArchiveJobResult { + output_path: destination.display().to_string(), + processed_entries: processed, + }) +} + +fn extract_tar(reader: R, destination: &Path) -> Result { + let mut archive = TarArchive::new(reader); + let mut processed = 0_usize; + + for entry in archive.entries()? { + let mut entry = entry?; + if entry.header().entry_type().is_dir() { + entry.unpack_in(destination)?; + continue; + } + entry.unpack_in(destination)?; + processed += 1; + } + + Ok(ArchiveJobResult { + output_path: destination.display().to_string(), + processed_entries: processed, + }) +} + +fn add_file_to_zip( + zip: &mut ZipWriter, + source: &Path, + archive_path: &str, + options: SimpleFileOptions, +) -> Result<()> { + let mut file = File::open(source)?; + zip.start_file(archive_path, options)?; + io::copy(&mut file, zip)?; + Ok(()) +} + +fn append_to_tar( + builder: &mut TarBuilder, + source: &Path, + processed: &mut usize, +) -> Result<()> { + if source.is_file() { + let name = source + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("item") + .to_string(); + builder.append_path_with_name(source, name)?; + *processed += 1; + return Ok(()); + } + + let root_name = source + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("archive-root") + .to_string(); + for entry in walk(source)? { + let relative = entry.strip_prefix(source)?.to_path_buf(); + let archive_path = Path::new(&root_name).join(relative); + builder.append_path_with_name(&entry, archive_path)?; + *processed += 1; + } + Ok(()) +} + +fn walk(root: &Path) -> Result> { + let mut files = Vec::new(); + for entry in fs::read_dir(root)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + files.extend(walk(&path)?); + } else if path.is_file() { + files.push(path); + } + } + Ok(files) +} + +#[cfg(test)] +mod tests { + use super::{compress, extract, ArchiveJobSpec}; + use std::fs; + use std::path::PathBuf; + use std::time::{SystemTime, UNIX_EPOCH}; + + fn temp_path(name: &str) -> PathBuf { + let suffix = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("time went backwards") + .as_nanos(); + std::env::temp_dir().join(format!("fbrowser-{name}-{suffix}")) + } + + #[test] + fn zip_roundtrip_preserves_nested_file_contents() { + let source_dir = temp_path("archive-source"); + let nested_dir = source_dir.join("nested"); + let archive_path = temp_path("archive-output").with_extension("zip"); + let extract_dir = temp_path("archive-extract"); + + fs::create_dir_all(&nested_dir).expect("create source dir"); + fs::write(source_dir.join("root.txt"), "root-data").expect("write root file"); + fs::write(nested_dir.join("child.txt"), "nested-data").expect("write nested file"); + + let compress_result = compress(&ArchiveJobSpec { + source: source_dir.display().to_string(), + destination: archive_path.display().to_string(), + }) + .expect("compress directory"); + assert_eq!(compress_result.processed_entries, 2); + assert!(archive_path.exists()); + + let extract_result = extract(&ArchiveJobSpec { + source: archive_path.display().to_string(), + destination: extract_dir.display().to_string(), + }) + .expect("extract archive"); + assert_eq!(extract_result.processed_entries, 2); + assert_eq!( + fs::read_to_string(extract_dir.join("root.txt")).expect("read extracted root file"), + "root-data" + ); + assert_eq!( + fs::read_to_string(extract_dir.join("nested").join("child.txt")).expect("read extracted nested file"), + "nested-data" + ); + + let _ = fs::remove_dir_all(&source_dir); + let _ = fs::remove_file(&archive_path); + let _ = fs::remove_dir_all(&extract_dir); + } + + #[test] + fn extract_rejects_unsupported_archive_types() { + let source_path = temp_path("archive-unsupported").with_extension("rar"); + let destination = temp_path("archive-unsupported-out"); + fs::write(&source_path, b"not-a-real-rar").expect("write unsupported source"); + + let error = extract(&ArchiveJobSpec { + source: source_path.display().to_string(), + destination: destination.display().to_string(), + }) + .expect_err("unsupported format should fail"); + assert!(error.to_string().contains("unsupported archive format")); + + let _ = fs::remove_file(&source_path); + } + + #[test] + fn tar_gz_roundtrip_preserves_directory_structure() { + let source_dir = temp_path("archive-source-targz"); + let nested_dir = source_dir.join("drums").join("kicks"); + let archive_path = temp_path("archive-output-targz").join("samples.tar.gz"); + let extract_dir = temp_path("archive-extract-targz"); + + fs::create_dir_all(&nested_dir).expect("create nested source dir"); + fs::write(nested_dir.join("kick.txt"), "four-on-the-floor").expect("write nested file"); + + let compress_result = compress(&ArchiveJobSpec { + source: source_dir.display().to_string(), + destination: archive_path.display().to_string(), + }) + .expect("compress tar.gz directory"); + assert_eq!(compress_result.processed_entries, 1); + assert!(archive_path.exists()); + + let extract_result = extract(&ArchiveJobSpec { + source: archive_path.display().to_string(), + destination: extract_dir.display().to_string(), + }) + .expect("extract tar.gz archive"); + assert_eq!(extract_result.processed_entries, 1); + + let extracted_file = extract_dir + .join(source_dir.file_name().expect("source dir name")) + .join("drums") + .join("kicks") + .join("kick.txt"); + assert_eq!( + fs::read_to_string(extracted_file).expect("read extracted tar.gz file"), + "four-on-the-floor" + ); + + let _ = fs::remove_dir_all(&source_dir); + let _ = fs::remove_file(&archive_path); + let _ = fs::remove_dir_all(&extract_dir); + } +} diff --git a/crates/fbrowser-audio/Cargo.toml b/crates/fbrowser-audio/Cargo.toml new file mode 100644 index 0000000..a62a7fe --- /dev/null +++ b/crates/fbrowser-audio/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "fbrowser-audio" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +anyhow.workspace = true +rodio.workspace = true +serde.workspace = true +symphonia.workspace = true +zip.workspace = true diff --git a/crates/fbrowser-audio/src/lib.rs b/crates/fbrowser-audio/src/lib.rs new file mode 100644 index 0000000..b6dcab0 --- /dev/null +++ b/crates/fbrowser-audio/src/lib.rs @@ -0,0 +1,287 @@ +use std::fs::{self, File}; +use std::io::{self, BufReader, Write}; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use anyhow::{bail, Context, Result}; +use rodio::{Decoder, DeviceSinkBuilder, MixerDeviceSink, Player, Source}; +use serde::{Deserialize, Serialize}; +use symphonia::core::audio::SampleBuffer; +use symphonia::core::codecs::DecoderOptions; +use symphonia::core::formats::FormatOptions; +use symphonia::core::io::MediaSourceStream; +use symphonia::core::meta::MetadataOptions; +use symphonia::default::{get_codecs, get_probe}; +use zip::ZipArchive; + +const PREVIEWABLE_ARCHIVE_EXTENSIONS: &[&str] = &[ + "wav", "wave", "mp3", "flac", "aif", "aiff", "aifc", "ogg", "m4a", "aac", +]; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct LoopRegion { + pub start_ms: u64, + pub end_ms: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PlaybackState { + pub loaded_path: Option, + pub is_playing: bool, + pub volume: f32, + pub position_ms: u64, + pub duration_ms: u64, + pub loop_region: Option, + pub output_device: Option, + pub media_kind: String, +} + +impl Default for PlaybackState { + fn default() -> Self { + Self { + loaded_path: None, + is_playing: false, + volume: 0.8, + position_ms: 0, + duration_ms: 0, + loop_region: None, + output_device: Some("Default".into()), + media_kind: "audio".into(), + } + } +} + +struct PlaybackInner { + _stream: MixerDeviceSink, + player: Player, + state: PlaybackState, + temp_preview_path: Option, +} + +#[derive(Clone)] +pub struct AudioEngine { + inner: Arc>, +} + +impl AudioEngine { + pub fn new() -> Result { + let stream = DeviceSinkBuilder::open_default_sink()?; + let player = Player::connect_new(&stream.mixer()); + player.set_volume(0.8); + Ok(Self { + inner: Arc::new(Mutex::new(PlaybackInner { + _stream: stream, + player, + state: PlaybackState::default(), + temp_preview_path: None, + })), + }) + } + + pub fn load(&self, path: &str, media_kind: &str) -> Result { + let (source_path, display_path, temp_preview_path) = prepare_playback_source(path, media_kind)?; + let file = BufReader::new(File::open(&source_path)?); + let decoder = Decoder::try_from(file)?; + let duration_ms = decoder + .total_duration() + .map(|duration| duration.as_millis() as u64) + .unwrap_or(0); + + let mut inner = self.inner.lock().expect("audio engine poisoned"); + cleanup_temp_preview(inner.temp_preview_path.take()); + inner.player.stop(); + inner.player.clear(); + inner.player.append(decoder); + inner.player.pause(); + inner.player.set_volume(inner.state.volume); + inner.temp_preview_path = temp_preview_path; + inner.state.loaded_path = Some(display_path); + inner.state.duration_ms = duration_ms; + inner.state.position_ms = 0; + inner.state.media_kind = media_kind.to_string(); + inner.state.is_playing = false; + Ok(inner.state.clone()) + } + + pub fn play(&self) -> PlaybackState { + let mut inner = self.inner.lock().expect("audio engine poisoned"); + inner.player.play(); + inner.state.is_playing = true; + inner.state.clone() + } + + pub fn pause(&self) -> PlaybackState { + let mut inner = self.inner.lock().expect("audio engine poisoned"); + inner.state.position_ms = current_position_locked(&inner); + inner.player.pause(); + inner.state.is_playing = false; + inner.state.clone() + } + + pub fn stop(&self) -> PlaybackState { + let mut inner = self.inner.lock().expect("audio engine poisoned"); + inner.player.stop(); + inner.player.clear(); + inner.state.position_ms = 0; + inner.state.is_playing = false; + inner.state.clone() + } + + pub fn seek(&self, position_ms: u64) -> Result { + let mut inner = self.inner.lock().expect("audio engine poisoned"); + inner + .player + .try_seek(Duration::from_millis(position_ms)) + .map_err(|err| anyhow::anyhow!(err.to_string()))?; + inner.state.position_ms = position_ms; + Ok(inner.state.clone()) + } + + pub fn set_volume(&self, volume: f32) -> PlaybackState { + let mut inner = self.inner.lock().expect("audio engine poisoned"); + inner.player.set_volume(volume); + inner.state.volume = volume; + inner.state.clone() + } + + pub fn set_loop_region(&self, region: Option) -> PlaybackState { + let mut inner = self.inner.lock().expect("audio engine poisoned"); + inner.state.loop_region = region; + inner.state.clone() + } + + pub fn state(&self) -> PlaybackState { + let mut inner = self.inner.lock().expect("audio engine poisoned"); + let position = current_position_locked(&inner); + inner.state.position_ms = position; + if let Some(region) = inner.state.loop_region.clone() { + if inner.state.is_playing && position >= region.end_ms { + let _ = inner.player.try_seek(Duration::from_millis(region.start_ms)); + inner.state.position_ms = region.start_ms; + } + } + inner.state.clone() + } +} + +pub fn generate_waveform(path: &str, bars: usize) -> Result> { + let file = File::open(Path::new(path))?; + let mss = MediaSourceStream::new(Box::new(file), Default::default()); + let probe = get_probe().format( + &Default::default(), + mss, + &FormatOptions::default(), + &MetadataOptions::default(), + )?; + let mut format = probe.format; + let track = format + .default_track() + .ok_or_else(|| anyhow::anyhow!("no default audio track"))?; + let mut decoder = get_codecs().make(&track.codec_params, &DecoderOptions::default())?; + let mut peaks = Vec::new(); + + while let Ok(packet) = format.next_packet() { + let decoded = decoder.decode(&packet)?; + let channels = decoded.spec().channels.count(); + let frames = decoded.frames(); + let mut samples = SampleBuffer::::new(decoded.capacity() as u64, *decoded.spec()); + samples.copy_interleaved_ref(decoded); + let chunk = samples.samples(); + if chunk.is_empty() { + continue; + } + let stride = channels.max(1); + for frame in 0..frames { + let idx = frame * stride; + if let Some(sample) = chunk.get(idx) { + peaks.push(sample.abs()); + } + } + } + + if peaks.is_empty() { + return Ok(vec![0.0; bars.max(1)]); + } + + let bucket_size = (peaks.len() / bars.max(1)).max(1); + let mut output = Vec::with_capacity(bars.max(1)); + for chunk in peaks.chunks(bucket_size).take(bars.max(1)) { + output.push(chunk.iter().copied().fold(0.0_f32, f32::max)); + } + while output.len() < bars.max(1) { + output.push(0.0); + } + Ok(output) +} + +fn prepare_playback_source(path: &str, media_kind: &str) -> Result<(PathBuf, String, Option)> { + if media_kind != "archive" { + let source_path = PathBuf::from(path); + return Ok((source_path, path.to_string(), None)); + } + + let archive_path = Path::new(path); + let extension = archive_path + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext.to_ascii_lowercase()) + .unwrap_or_default(); + if extension != "zip" { + bail!("archive preview is currently supported for .zip files only"); + } + + let (temp_path, entry_name) = extract_preview_from_zip(archive_path)?; + Ok(( + temp_path.clone(), + format!("{} :: {}", archive_path.display(), entry_name), + Some(temp_path), + )) +} + +fn extract_preview_from_zip(archive_path: &Path) -> Result<(PathBuf, String)> { + let file = File::open(archive_path).with_context(|| format!("failed to open archive {}", archive_path.display()))?; + let mut archive = ZipArchive::new(file)?; + + for index in 0..archive.len() { + let mut entry = archive.by_index(index)?; + if entry.is_dir() { + continue; + } + let entry_name = entry.name().to_string(); + let extension = Path::new(&entry_name) + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext.to_ascii_lowercase()) + .unwrap_or_default(); + if !PREVIEWABLE_ARCHIVE_EXTENSIONS.contains(&extension.as_str()) { + continue; + } + + let temp_path = unique_preview_path(&extension); + let mut output = File::create(&temp_path)?; + io::copy(&mut entry, &mut output)?; + output.flush()?; + return Ok((temp_path, entry_name)); + } + + bail!("no previewable audio files found inside {}", archive_path.display()) +} + +fn unique_preview_path(extension: &str) -> PathBuf { + let stamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos(); + std::env::temp_dir().join(format!("fbrowser-preview-{stamp}.{extension}")) +} + +fn cleanup_temp_preview(path: Option) { + if let Some(path) = path { + let _ = fs::remove_file(path); + } +} + +fn current_position_locked(inner: &PlaybackInner) -> u64 { + inner.player.get_pos().as_millis() as u64 +} diff --git a/crates/fbrowser-core/Cargo.toml b/crates/fbrowser-core/Cargo.toml new file mode 100644 index 0000000..b09d25f --- /dev/null +++ b/crates/fbrowser-core/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "fbrowser-core" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +anyhow.workspace = true +chrono.workspace = true +ignore.workspace = true +lofty.workspace = true +rayon.workspace = true +serde.workspace = true +serde_json.workspace = true +sqlx.workspace = true +tokio.workspace = true +walkdir.workspace = true diff --git a/crates/fbrowser-core/src/db.rs b/crates/fbrowser-core/src/db.rs new file mode 100644 index 0000000..cc5d365 --- /dev/null +++ b/crates/fbrowser-core/src/db.rs @@ -0,0 +1,558 @@ +use std::str::FromStr; + +use anyhow::{Context, Result}; +use chrono::Utc; +use lofty::config::WriteOptions; +use lofty::file::{AudioFile, TaggedFileExt}; +use lofty::probe::Probe; +use lofty::tag::{Accessor, ItemKey, ItemValue, Tag, TagItem}; +use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; +use sqlx::{QueryBuilder, Row, Sqlite, SqlitePool}; + +use crate::models::{ + AnnotationUpdate, CollectionItemsMutation, CollectionMutation, CollectionRecord, LibraryRoot, + MediaItemDetail, MediaItemSummary, MetadataPatch, NewMediaItem, ReorderCollectionMutation, + SearchRequest, SearchResponse, +}; + +#[derive(Clone)] +pub struct AppDatabase { + pool: SqlitePool, +} + +impl AppDatabase { + pub async fn connect(database_path: &str) -> Result { + let options = SqliteConnectOptions::from_str(database_path)? + .create_if_missing(true) + .foreign_keys(true); + let pool = SqlitePoolOptions::new() + .max_connections(8) + .connect_with(options) + .await?; + Ok(Self { pool }) + } + + pub fn pool(&self) -> &SqlitePool { + &self.pool + } + + pub async fn list_roots(&self) -> Result> { + let roots = sqlx::query_as::<_, LibraryRoot>( + "SELECT id, path, enabled, platform, created_at, updated_at, item_count FROM library_roots ORDER BY path ASC", + ) + .fetch_all(&self.pool) + .await?; + Ok(roots) + } + + pub async fn add_root(&self, path: &str) -> Result { + let now = Utc::now().to_rfc3339(); + sqlx::query( + r#" + INSERT INTO library_roots(path, enabled, platform, created_at, updated_at, item_count) + VALUES(?1, 1, ?2, ?3, ?4, 0) + ON CONFLICT(path) DO UPDATE SET updated_at = excluded.updated_at + "#, + ) + .bind(path) + .bind(std::env::consts::OS) + .bind(&now) + .bind(&now) + .execute(&self.pool) + .await?; + + let root = sqlx::query_as::<_, LibraryRoot>( + "SELECT id, path, enabled, platform, created_at, updated_at, item_count FROM library_roots WHERE path = ?1", + ) + .bind(path) + .fetch_one(&self.pool) + .await?; + Ok(root) + } + + pub async fn remove_root(&self, root_id: i64) -> Result<()> { + sqlx::query("DELETE FROM library_roots WHERE id = ?1") + .bind(root_id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn clear_root_media(&self, root_id: i64) -> Result<()> { + sqlx::query("DELETE FROM media_items WHERE root_id = ?1") + .bind(root_id) + .execute(&self.pool) + .await?; + sqlx::query("UPDATE library_roots SET item_count = 0, updated_at = ?2 WHERE id = ?1") + .bind(root_id) + .bind(Utc::now().to_rfc3339()) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn insert_media_item(&self, item: &NewMediaItem) -> Result<()> { + sqlx::query( + r#" + INSERT INTO media_items( + root_id, absolute_path, file_name, extension, media_kind, size_bytes, mtime_unix, + duration_ms, sample_rate, channels, bpm, musical_key, waveform_cache_key, + title, artist, album, genre, year, comment, embedded_bpm + ) + VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20) + ON CONFLICT(absolute_path) DO UPDATE SET + root_id=excluded.root_id, + file_name=excluded.file_name, + extension=excluded.extension, + media_kind=excluded.media_kind, + size_bytes=excluded.size_bytes, + mtime_unix=excluded.mtime_unix, + duration_ms=excluded.duration_ms, + sample_rate=excluded.sample_rate, + channels=excluded.channels, + bpm=excluded.bpm, + musical_key=excluded.musical_key, + waveform_cache_key=excluded.waveform_cache_key, + title=excluded.title, + artist=excluded.artist, + album=excluded.album, + genre=excluded.genre, + year=excluded.year, + comment=excluded.comment, + embedded_bpm=excluded.embedded_bpm + "#, + ) + .bind(item.root_id) + .bind(&item.absolute_path) + .bind(&item.file_name) + .bind(&item.extension) + .bind(&item.media_kind) + .bind(item.size_bytes) + .bind(item.mtime_unix) + .bind(item.duration_ms) + .bind(item.sample_rate) + .bind(item.channels) + .bind(item.bpm) + .bind(&item.musical_key) + .bind(&item.waveform_cache_key) + .bind(&item.title) + .bind(&item.artist) + .bind(&item.album) + .bind(&item.genre) + .bind(&item.year) + .bind(&item.comment) + .bind(item.embedded_bpm) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn finalize_root_scan(&self, root_id: i64) -> Result<()> { + sqlx::query( + r#" + UPDATE library_roots + SET item_count = (SELECT COUNT(*) FROM media_items WHERE root_id = ?1), + updated_at = ?2 + WHERE id = ?1 + "#, + ) + .bind(root_id) + .bind(Utc::now().to_rfc3339()) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn search_library(&self, req: SearchRequest) -> Result { + let mut select = QueryBuilder::::new( + r#" + SELECT + mi.id, mi.root_id, mi.absolute_path, mi.file_name, mi.extension, mi.media_kind, + mi.size_bytes, mi.mtime_unix, mi.duration_ms, mi.sample_rate, mi.channels, + mi.bpm, mi.musical_key, mi.title, mi.artist, mi.album, mi.genre, mi.year, + mi.comment, mi.embedded_bpm, + COALESCE(ua.favorite, 0) AS favorite, + ua.rating, + ua.note, + ua.custom_tags_json, + ua.color, + (SELECT MAX(ph.played_at) FROM play_history ph WHERE ph.media_item_id = mi.id) AS last_played_at + FROM media_items mi + LEFT JOIN user_annotations ua ON ua.media_item_id = mi.id + "#, + ); + self.apply_filters(&mut select, &req); + self.apply_sort(&mut select, req.sort.as_deref()); + let offset = (req.page.saturating_mul(req.page_size)) as i64; + select.push(" LIMIT ").push_bind(req.page_size as i64); + select.push(" OFFSET ").push_bind(offset); + let items = select + .build_query_as::() + .fetch_all(&self.pool) + .await?; + + let mut count = QueryBuilder::::new( + "SELECT COUNT(*) AS count FROM media_items mi LEFT JOIN user_annotations ua ON ua.media_item_id = mi.id ", + ); + self.apply_filters(&mut count, &req); + let total = count + .build() + .fetch_one(&self.pool) + .await? + .get::("count"); + + Ok(SearchResponse { + items, + total, + page: req.page, + page_size: req.page_size, + }) + } + + pub async fn get_item(&self, item_id: i64) -> Result { + let summary = sqlx::query_as::<_, MediaItemSummary>( + r#" + SELECT + mi.id, mi.root_id, mi.absolute_path, mi.file_name, mi.extension, mi.media_kind, + mi.size_bytes, mi.mtime_unix, mi.duration_ms, mi.sample_rate, mi.channels, + mi.bpm, mi.musical_key, mi.title, mi.artist, mi.album, mi.genre, mi.year, + mi.comment, mi.embedded_bpm, + COALESCE(ua.favorite, 0) AS favorite, + ua.rating, + ua.note, + ua.custom_tags_json, + ua.color, + (SELECT MAX(ph.played_at) FROM play_history ph WHERE ph.media_item_id = mi.id) AS last_played_at + FROM media_items mi + LEFT JOIN user_annotations ua ON ua.media_item_id = mi.id + WHERE mi.id = ?1 + "#, + ) + .bind(item_id) + .fetch_one(&self.pool) + .await?; + + let custom_tags = summary + .custom_tags_json + .as_ref() + .and_then(|raw| serde_json::from_str::>(raw).ok()) + .unwrap_or_default(); + Ok(MediaItemDetail { summary, custom_tags }) + } + + pub async fn update_annotations(&self, update: AnnotationUpdate) -> Result { + let existing = sqlx::query( + "SELECT favorite, rating, note, custom_tags_json, color FROM user_annotations WHERE media_item_id = ?1", + ) + .bind(update.item_id) + .fetch_optional(&self.pool) + .await?; + + let favorite = update.favorite.unwrap_or_else(|| { + existing + .as_ref() + .and_then(|row| row.try_get::("favorite").ok()) + .unwrap_or(0) + != 0 + }); + let rating = update + .rating + .or_else(|| existing.as_ref().and_then(|row| row.try_get::, _>("rating").ok().flatten())); + let note = update + .note + .or_else(|| existing.as_ref().and_then(|row| row.try_get::, _>("note").ok().flatten())); + let custom_tags_json = update + .custom_tags + .map(|tags| serde_json::to_string(&tags)) + .transpose()? + .or_else(|| existing.as_ref().and_then(|row| row.try_get::, _>("custom_tags_json").ok().flatten())); + let color = update + .color + .or_else(|| existing.as_ref().and_then(|row| row.try_get::, _>("color").ok().flatten())); + + sqlx::query( + r#" + INSERT INTO user_annotations(media_item_id, favorite, rating, note, custom_tags_json, color, updated_at) + VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7) + ON CONFLICT(media_item_id) DO UPDATE SET + favorite=excluded.favorite, + rating=excluded.rating, + note=excluded.note, + custom_tags_json=excluded.custom_tags_json, + color=excluded.color, + updated_at=excluded.updated_at + "#, + ) + .bind(update.item_id) + .bind(if favorite { 1 } else { 0 }) + .bind(rating) + .bind(note) + .bind(custom_tags_json) + .bind(color) + .bind(Utc::now().to_rfc3339()) + .execute(&self.pool) + .await?; + self.get_item(update.item_id).await + } + + pub async fn record_play_history(&self, item_id: i64, source_context: &str) -> Result<()> { + sqlx::query("INSERT INTO play_history(media_item_id, played_at, source_context) VALUES(?1, ?2, ?3)") + .bind(item_id) + .bind(Utc::now().to_rfc3339()) + .bind(source_context) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn find_item_id_by_path(&self, path: &str) -> Result> { + let row = sqlx::query("SELECT id FROM media_items WHERE absolute_path = ?1") + .bind(path) + .fetch_optional(&self.pool) + .await?; + Ok(row.map(|row| row.get::("id"))) + } + + pub async fn get_path_for_item(&self, item_id: i64) -> Result { + let row = sqlx::query("SELECT absolute_path FROM media_items WHERE id = ?1") + .bind(item_id) + .fetch_one(&self.pool) + .await?; + Ok(row.get::("absolute_path")) + } + + pub async fn write_metadata(&self, item_id: i64, patch: MetadataPatch) -> Result { + let path = self.get_path_for_item(item_id).await?; + let mut tagged = Probe::open(&path) + .with_context(|| format!("failed to open media file for metadata write: {path}"))? + .read() + .with_context(|| format!("failed to read media file for metadata write: {path}"))?; + + let primary = tagged.primary_tag_type(); + if tagged.primary_tag_mut().is_none() { + tagged.insert_tag(Tag::new(primary)); + } + let tag = if let Some(tag) = tagged.primary_tag_mut() { + tag + } else { + tagged + .first_tag_mut() + .context("no writable metadata tag available")? + }; + if let Some(value) = &patch.title { + tag.set_title(value.clone()); + } + if let Some(value) = &patch.artist { + tag.set_artist(value.clone()); + } + if let Some(value) = &patch.album { + tag.set_album(value.clone()); + } + if let Some(value) = &patch.genre { + tag.set_genre(value.clone()); + } + if let Some(value) = &patch.year { + tag.insert(TagItem::new(ItemKey::RecordingDate, ItemValue::Text(value.clone()))); + } + if let Some(value) = &patch.comment { + tag.insert(TagItem::new(ItemKey::Comment, ItemValue::Text(value.clone()))); + } + tagged.save_to_path(&path, WriteOptions::default())?; + + sqlx::query( + r#" + UPDATE media_items + SET title = COALESCE(?2, title), + artist = COALESCE(?3, artist), + album = COALESCE(?4, album), + genre = COALESCE(?5, genre), + year = COALESCE(?6, year), + comment = COALESCE(?7, comment) + WHERE id = ?1 + "#, + ) + .bind(item_id) + .bind(patch.title) + .bind(patch.artist) + .bind(patch.album) + .bind(patch.genre) + .bind(patch.year) + .bind(patch.comment) + .execute(&self.pool) + .await?; + self.get_item(item_id).await + } + + pub async fn list_collections(&self) -> Result> { + let collections = sqlx::query_as::<_, CollectionRecord>( + "SELECT id, name, kind, rules_json, created_at FROM collections ORDER BY created_at DESC", + ) + .fetch_all(&self.pool) + .await?; + Ok(collections) + } + + pub async fn create_collection(&self, payload: CollectionMutation) -> Result { + let now = Utc::now().to_rfc3339(); + let result = + sqlx::query("INSERT INTO collections(name, kind, rules_json, created_at) VALUES(?1, ?2, ?3, ?4)") + .bind(payload.name) + .bind(payload.kind) + .bind(payload.rules_json) + .bind(&now) + .execute(&self.pool) + .await?; + let id = result.last_insert_rowid(); + let collection = sqlx::query_as::<_, CollectionRecord>( + "SELECT id, name, kind, rules_json, created_at FROM collections WHERE id = ?1", + ) + .bind(id) + .fetch_one(&self.pool) + .await?; + Ok(collection) + } + + pub async fn update_collection(&self, id: i64, payload: CollectionMutation) -> Result { + sqlx::query("UPDATE collections SET name = ?2, kind = ?3, rules_json = ?4 WHERE id = ?1") + .bind(id) + .bind(payload.name) + .bind(payload.kind) + .bind(payload.rules_json) + .execute(&self.pool) + .await?; + let collection = sqlx::query_as::<_, CollectionRecord>( + "SELECT id, name, kind, rules_json, created_at FROM collections WHERE id = ?1", + ) + .bind(id) + .fetch_one(&self.pool) + .await?; + Ok(collection) + } + + pub async fn delete_collection(&self, id: i64) -> Result<()> { + sqlx::query("DELETE FROM collections WHERE id = ?1") + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } + + pub async fn add_items_to_collection(&self, payload: CollectionItemsMutation) -> Result<()> { + for (position, item_id) in payload.item_ids.into_iter().enumerate() { + sqlx::query( + r#" + INSERT INTO collection_items(collection_id, media_item_id, position) + VALUES(?1, ?2, ?3) + ON CONFLICT(collection_id, media_item_id) DO UPDATE SET position = excluded.position + "#, + ) + .bind(payload.collection_id) + .bind(item_id) + .bind(position as i64) + .execute(&self.pool) + .await?; + } + Ok(()) + } + + pub async fn remove_items_from_collection(&self, payload: CollectionItemsMutation) -> Result<()> { + for item_id in payload.item_ids { + sqlx::query("DELETE FROM collection_items WHERE collection_id = ?1 AND media_item_id = ?2") + .bind(payload.collection_id) + .bind(item_id) + .execute(&self.pool) + .await?; + } + Ok(()) + } + + pub async fn reorder_collection(&self, payload: ReorderCollectionMutation) -> Result<()> { + for (position, item_id) in payload.item_ids.into_iter().enumerate() { + sqlx::query( + "UPDATE collection_items SET position = ?3 WHERE collection_id = ?1 AND media_item_id = ?2", + ) + .bind(payload.collection_id) + .bind(item_id) + .bind(position as i64) + .execute(&self.pool) + .await?; + } + Ok(()) + } + + pub async fn get_setting_json(&self, key: &str) -> Result> { + let row = sqlx::query("SELECT value FROM settings WHERE key = ?1") + .bind(key) + .fetch_optional(&self.pool) + .await?; + if let Some(row) = row { + let value = row.get::("value"); + Ok(Some(serde_json::from_str(&value)?)) + } else { + Ok(None) + } + } + + pub async fn set_setting_json(&self, key: &str, value: &T) -> Result<()> { + let raw = serde_json::to_string(value)?; + sqlx::query( + r#" + INSERT INTO settings(key, value) + VALUES(?1, ?2) + ON CONFLICT(key) DO UPDATE SET value = excluded.value + "#, + ) + .bind(key) + .bind(raw) + .execute(&self.pool) + .await?; + Ok(()) + } + + fn apply_filters<'a>(&self, builder: &mut QueryBuilder<'a, Sqlite>, req: &SearchRequest) { + builder.push(" WHERE 1=1 "); + if let Some(root_id) = req.root_id { + builder.push(" AND mi.root_id = ").push_bind(root_id); + } + if let Some(media_kind) = req.media_kind.as_deref() { + builder.push(" AND mi.media_kind = ").push_bind(media_kind.to_string()); + } + if let Some(query) = req.query.as_deref() { + let like = format!("%{}%", query.trim()); + builder.push(" AND (mi.file_name LIKE "); + builder.push_bind(like.clone()); + builder.push(" OR COALESCE(mi.title, '') LIKE "); + builder.push_bind(like.clone()); + builder.push(" OR COALESCE(mi.artist, '') LIKE "); + builder.push_bind(like.clone()); + builder.push(" OR COALESCE(mi.album, '') LIKE "); + builder.push_bind(like); + builder.push(")"); + } + if let Some(section) = req.section.as_deref() { + match section { + "favorites" => { + builder.push(" AND COALESCE(ua.favorite, 0) = 1"); + } + "recent" => { + builder.push(" AND EXISTS (SELECT 1 FROM play_history ph WHERE ph.media_item_id = mi.id)"); + } + _ => {} + } + } + if let Some(collection_id) = req.collection_id { + builder.push(" AND EXISTS (SELECT 1 FROM collection_items ci WHERE ci.collection_id = "); + builder.push_bind(collection_id); + builder.push(" AND ci.media_item_id = mi.id)"); + } + } + + fn apply_sort<'a>(&self, builder: &mut QueryBuilder<'a, Sqlite>, sort: Option<&str>) { + match sort.unwrap_or("name") { + "recent" => builder.push(" ORDER BY last_played_at DESC NULLS LAST, mi.file_name ASC "), + "rating" => builder.push(" ORDER BY ua.rating DESC NULLS LAST, mi.file_name ASC "), + "duration" => builder.push(" ORDER BY mi.duration_ms DESC NULLS LAST, mi.file_name ASC "), + _ => builder.push(" ORDER BY mi.file_name COLLATE NOCASE ASC "), + }; + } +} diff --git a/crates/fbrowser-core/src/lib.rs b/crates/fbrowser-core/src/lib.rs new file mode 100644 index 0000000..7efd2e1 --- /dev/null +++ b/crates/fbrowser-core/src/lib.rs @@ -0,0 +1,5 @@ +pub mod db; +pub mod models; +pub mod scanner; + +pub use db::AppDatabase; diff --git a/crates/fbrowser-core/src/models.rs b/crates/fbrowser-core/src/models.rs new file mode 100644 index 0000000..756ee1b --- /dev/null +++ b/crates/fbrowser-core/src/models.rs @@ -0,0 +1,151 @@ +use serde::{Deserialize, Serialize}; +use sqlx::FromRow; + +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] +pub struct LibraryRoot { + pub id: i64, + pub path: String, + pub enabled: bool, + pub platform: String, + pub created_at: String, + pub updated_at: String, + pub item_count: i64, +} + +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] +pub struct MediaItemSummary { + pub id: i64, + pub root_id: i64, + pub absolute_path: String, + pub file_name: String, + pub extension: String, + pub media_kind: String, + pub size_bytes: i64, + pub mtime_unix: i64, + pub duration_ms: Option, + pub sample_rate: Option, + pub channels: Option, + pub bpm: Option, + pub musical_key: Option, + pub title: Option, + pub artist: Option, + pub album: Option, + pub genre: Option, + pub year: Option, + pub comment: Option, + pub embedded_bpm: Option, + pub favorite: bool, + pub rating: Option, + pub note: Option, + pub custom_tags_json: Option, + pub color: Option, + pub last_played_at: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MediaItemDetail { + pub summary: MediaItemSummary, + pub custom_tags: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SearchRequest { + pub query: Option, + pub section: Option, + pub sort: Option, + pub page: u32, + pub page_size: u32, + pub root_id: Option, + pub collection_id: Option, + pub media_kind: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SearchResponse { + pub items: Vec, + pub total: i64, + pub page: u32, + pub page_size: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AnnotationUpdate { + pub item_id: i64, + pub favorite: Option, + pub rating: Option, + pub note: Option, + pub custom_tags: Option>, + pub color: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MetadataPatch { + pub title: Option, + pub artist: Option, + pub album: Option, + pub genre: Option, + pub year: Option, + pub comment: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, FromRow)] +pub struct CollectionRecord { + pub id: i64, + pub name: String, + pub kind: String, + pub rules_json: Option, + pub created_at: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CollectionMutation { + pub name: String, + pub kind: String, + pub rules_json: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CollectionItemsMutation { + pub collection_id: i64, + pub item_ids: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ReorderCollectionMutation { + pub collection_id: i64, + pub item_ids: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct ScanStatus { + pub active: bool, + pub current_root: Option, + pub indexed: u64, + pub discovered: u64, + pub last_error: Option, + pub roots: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NewMediaItem { + pub root_id: i64, + pub absolute_path: String, + pub file_name: String, + pub extension: String, + pub media_kind: String, + pub size_bytes: i64, + pub mtime_unix: i64, + pub duration_ms: Option, + pub sample_rate: Option, + pub channels: Option, + pub bpm: Option, + pub musical_key: Option, + pub waveform_cache_key: Option, + pub title: Option, + pub artist: Option, + pub album: Option, + pub genre: Option, + pub year: Option, + pub comment: Option, + pub embedded_bpm: Option, +} diff --git a/crates/fbrowser-core/src/scanner.rs b/crates/fbrowser-core/src/scanner.rs new file mode 100644 index 0000000..7405b6d --- /dev/null +++ b/crates/fbrowser-core/src/scanner.rs @@ -0,0 +1,173 @@ +use std::fs; +use std::path::{Path, PathBuf}; + +use anyhow::Result; +use ignore::WalkBuilder; +use lofty::file::{AudioFile, TaggedFileExt}; +use lofty::probe::Probe; +use lofty::tag::Accessor; +use rayon::prelude::*; + +use crate::db::AppDatabase; +use crate::models::{LibraryRoot, NewMediaItem}; + +const AUDIO_EXTENSIONS: &[&str] = &[ + "wav", "wave", "mp3", "flac", "aif", "aiff", "aifc", "ogg", "m4a", "aac", "wma", +]; +const MIDI_EXTENSIONS: &[&str] = &["mid", "midi"]; +const ARCHIVE_EXTENSIONS: &[&str] = &["zip", "tar", "gz", "tgz", "7z"]; + +const DISCOVERY_PROGRESS_INTERVAL: usize = 250; +const INDEX_PROGRESS_INTERVAL: usize = 100; + +#[derive(Debug, Clone)] +pub struct ScanProgress { + pub discovered: u64, + pub indexed: u64, + pub current_path: Option, +} + +pub async fn scan_root(db: &AppDatabase, root: &LibraryRoot, mut on_progress: F) -> Result +where + F: FnMut(ScanProgress) + Send, +{ + db.clear_root_media(root.id).await?; + + let mut candidate_paths = Vec::<(PathBuf, String)>::new(); + let walker = WalkBuilder::new(&root.path) + .hidden(false) + .git_ignore(true) + .git_exclude(true) + .build(); + + for entry in walker { + let entry = match entry { + Ok(entry) => entry, + Err(_) => continue, + }; + if !entry.file_type().map(|file_type| file_type.is_file()).unwrap_or(false) { + continue; + } + + let path = entry.into_path(); + let extension = path + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext.to_ascii_lowercase()) + .unwrap_or_default(); + if !AUDIO_EXTENSIONS.contains(&extension.as_str()) + && !MIDI_EXTENSIONS.contains(&extension.as_str()) + && !ARCHIVE_EXTENSIONS.contains(&extension.as_str()) + { + continue; + } + + candidate_paths.push((path.clone(), extension)); + if candidate_paths.len() == 1 || candidate_paths.len() % DISCOVERY_PROGRESS_INTERVAL == 0 { + on_progress(ScanProgress { + discovered: candidate_paths.len() as u64, + indexed: 0, + current_path: Some(path.display().to_string()), + }); + } + } + + let discovered = candidate_paths.len() as u64; + let items = candidate_paths + .par_iter() + .filter_map(|(path, extension)| match build_item(root.id, path, extension) { + Ok(item) => item, + Err(_) => None, + }) + .collect::>(); + + let mut indexed = 0_u64; + for item in items { + db.insert_media_item(&item).await?; + indexed += 1; + if indexed == 1 || indexed % INDEX_PROGRESS_INTERVAL as u64 == 0 { + on_progress(ScanProgress { + discovered, + indexed, + current_path: Some(item.absolute_path.clone()), + }); + } + } + + db.finalize_root_scan(root.id).await?; + on_progress(ScanProgress { + discovered, + indexed, + current_path: None, + }); + + Ok(indexed) +} + +fn build_item(root_id: i64, path: &Path, extension: &str) -> Result> { + let metadata = fs::metadata(path)?; + let file_name = path + .file_name() + .and_then(|name| name.to_str()) + .map(ToOwned::to_owned) + .unwrap_or_else(|| path.display().to_string()); + let absolute_path = path.canonicalize()?.display().to_string(); + let mtime_unix = metadata + .modified()? + .duration_since(std::time::UNIX_EPOCH)? + .as_secs() as i64; + + let media_kind = if AUDIO_EXTENSIONS.contains(&extension) { + "audio" + } else if MIDI_EXTENSIONS.contains(&extension) { + "midi" + } else { + "archive" + }; + + let mut item = NewMediaItem { + root_id, + absolute_path, + file_name, + extension: extension.to_string(), + media_kind: media_kind.to_string(), + size_bytes: metadata.len() as i64, + mtime_unix, + duration_ms: None, + sample_rate: None, + channels: None, + bpm: None, + musical_key: None, + waveform_cache_key: None, + title: None, + artist: None, + album: None, + genre: None, + year: None, + comment: None, + embedded_bpm: None, + }; + + if media_kind == "audio" { + if let Ok(tagged) = Probe::open(path)?.read() { + item.duration_ms = tagged + .properties() + .duration() + .as_millis() + .try_into() + .ok(); + item.sample_rate = tagged.properties().sample_rate().map(i64::from); + item.channels = tagged.properties().channels().map(i64::from); + if let Some(tag) = tagged.primary_tag().or_else(|| tagged.first_tag()) { + item.title = tag.title().map(|value| value.into_owned()); + item.artist = tag.artist().map(|value| value.into_owned()); + item.album = tag.album().map(|value| value.into_owned()); + item.genre = tag.genre().map(|value| value.into_owned()); + item.comment = tag.comment().map(|value| value.into_owned()); + item.year = tag.year().map(|value: u32| value.to_string()); + } + } + } + + Ok(Some(item)) +} diff --git a/crates/fbrowser-midi/Cargo.toml b/crates/fbrowser-midi/Cargo.toml new file mode 100644 index 0000000..81fa3db --- /dev/null +++ b/crates/fbrowser-midi/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "fbrowser-midi" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +anyhow.workspace = true +fbrowser-audio = { path = "../fbrowser-audio" } +midir.workspace = true +midly.workspace = true +serde.workspace = true diff --git a/crates/fbrowser-midi/src/lib.rs b/crates/fbrowser-midi/src/lib.rs new file mode 100644 index 0000000..45368aa --- /dev/null +++ b/crates/fbrowser-midi/src/lib.rs @@ -0,0 +1,366 @@ +use std::fs; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use std::thread::{self, JoinHandle}; +use std::time::{Duration, Instant}; + +use anyhow::{bail, Context, Result}; +use fbrowser_audio::{LoopRegion, PlaybackState}; +use midir::MidiOutput; +use midly::{MetaMessage, MidiMessage, Smf, Timing, TrackEventKind}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MidiBackendInfo { + pub id: String, + pub label: String, + pub supports_soundfont: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MidiBackendConfig { + pub backend_id: String, + pub soundfont_path: Option, +} + +impl Default for MidiBackendConfig { + fn default() -> Self { + Self { + backend_id: "system".into(), + soundfont_path: None, + } + } +} + +#[derive(Clone)] +pub struct MidiEngine { + inner: Arc>, +} + +struct MidiPlaybackInner { + state: PlaybackState, + events: Vec, + task: Option, + play_started_at: Option, + paused_position_ms: u64, +} + +struct MidiPlaybackTask { + stop: Arc, + handle: JoinHandle<()>, +} + +#[derive(Debug, Clone)] +struct ScheduledMidiEvent { + timestamp_ms: u64, + data: Vec, +} + +impl MidiEngine { + pub fn new() -> Self { + Self { + inner: Arc::new(Mutex::new(MidiPlaybackInner { + state: PlaybackState { + media_kind: "midi".into(), + ..PlaybackState::default() + }, + events: Vec::new(), + task: None, + play_started_at: None, + paused_position_ms: 0, + })), + } + } + + pub fn load(&self, path: &str, config: &MidiBackendConfig) -> Result { + ensure_supported_backend(config)?; + let bytes = fs::read(path).with_context(|| format!("failed to read MIDI file {path}"))?; + let (events, duration_ms) = parse_midi_events(&bytes)?; + + let mut inner = self.inner.lock().expect("midi engine poisoned"); + stop_task_locked(&mut inner); + inner.events = events; + inner.paused_position_ms = 0; + inner.play_started_at = None; + inner.state.loaded_path = Some(path.to_string()); + inner.state.duration_ms = duration_ms; + inner.state.position_ms = 0; + inner.state.volume = 0.8; + inner.state.loop_region = None; + inner.state.output_device = Some("System MIDI".into()); + inner.state.media_kind = "midi".into(); + inner.state.is_playing = false; + Ok(inner.state.clone()) + } + + pub fn play(&self, config: &MidiBackendConfig) -> Result { + ensure_supported_backend(config)?; + let mut inner = self.inner.lock().expect("midi engine poisoned"); + if inner.state.loaded_path.is_none() { + bail!("no MIDI file loaded"); + } + + stop_task_locked(&mut inner); + let stop = Arc::new(AtomicBool::new(false)); + let stop_for_thread = stop.clone(); + let events = inner.events.clone(); + let start_offset_ms = inner.paused_position_ms; + let state = self.inner.clone(); + + let handle = thread::spawn(move || { + let midi_out = match MidiOutput::new("Fbrowser MIDI") { + Ok(output) => output, + Err(_) => return, + }; + let ports = midi_out.ports(); + let Some(port) = ports.first() else { + return; + }; + let mut connection = match midi_out.connect(port, "fbrowser-system-midi") { + Ok(connection) => connection, + Err(_) => return, + }; + + let start = Instant::now(); + for event in events.into_iter().filter(|event| event.timestamp_ms >= start_offset_ms) { + let target_offset = event.timestamp_ms.saturating_sub(start_offset_ms); + while !stop_for_thread.load(Ordering::Relaxed) { + let elapsed = start.elapsed().as_millis() as u64; + if elapsed >= target_offset { + break; + } + thread::sleep(Duration::from_millis(2)); + } + + if stop_for_thread.load(Ordering::Relaxed) { + break; + } + + let _ = connection.send(&event.data); + if let Ok(mut inner) = state.lock() { + inner.state.position_ms = event.timestamp_ms; + } + } + + if let Ok(mut inner) = state.lock() { + if !stop_for_thread.load(Ordering::Relaxed) { + inner.state.position_ms = inner.state.duration_ms; + inner.paused_position_ms = inner.state.duration_ms; + } + inner.state.is_playing = false; + inner.play_started_at = None; + inner.task = None; + } + }); + + inner.task = Some(MidiPlaybackTask { stop, handle }); + inner.play_started_at = Some(Instant::now()); + inner.state.is_playing = true; + Ok(inner.state.clone()) + } + + pub fn pause(&self) -> PlaybackState { + let mut inner = self.inner.lock().expect("midi engine poisoned"); + inner.paused_position_ms = current_position_locked(&inner); + stop_task_locked(&mut inner); + inner.state.position_ms = inner.paused_position_ms; + inner.state.is_playing = false; + inner.state.clone() + } + + pub fn stop(&self) -> PlaybackState { + let mut inner = self.inner.lock().expect("midi engine poisoned"); + stop_task_locked(&mut inner); + inner.paused_position_ms = 0; + inner.state.position_ms = 0; + inner.state.is_playing = false; + inner.state.clone() + } + + pub fn seek(&self, position_ms: u64, config: &MidiBackendConfig) -> Result { + ensure_supported_backend(config)?; + let was_playing = { + let mut inner = self.inner.lock().expect("midi engine poisoned"); + let was_playing = inner.state.is_playing; + stop_task_locked(&mut inner); + let clamped = position_ms.min(inner.state.duration_ms); + inner.paused_position_ms = clamped; + inner.state.position_ms = clamped; + inner.state.is_playing = false; + was_playing + }; + + if was_playing { + return self.play(config); + } + + Ok(self.state()) + } + + pub fn set_volume(&self, volume: f32) -> PlaybackState { + let mut inner = self.inner.lock().expect("midi engine poisoned"); + inner.state.volume = volume; + inner.state.clone() + } + + pub fn set_loop_region(&self, region: Option) -> PlaybackState { + let mut inner = self.inner.lock().expect("midi engine poisoned"); + inner.state.loop_region = region; + inner.state.clone() + } + + pub fn state(&self) -> PlaybackState { + let mut inner = self.inner.lock().expect("midi engine poisoned"); + inner.state.position_ms = current_position_locked(&inner); + inner.state.clone() + } +} + +pub fn available_backends() -> Vec { + vec![ + MidiBackendInfo { + id: "system".into(), + label: "System MIDI Output".into(), + supports_soundfont: false, + }, + MidiBackendInfo { + id: "soundfont".into(), + label: "User SoundFont".into(), + supports_soundfont: true, + }, + ] +} + +fn ensure_supported_backend(config: &MidiBackendConfig) -> Result<()> { + match config.backend_id.as_str() { + "system" => Ok(()), + "soundfont" => bail!("SoundFont MIDI playback backend is not implemented yet"), + other => bail!("unsupported MIDI backend: {other}"), + } +} + +fn parse_midi_events(bytes: &[u8]) -> Result<(Vec, u64)> { + let smf = Smf::parse(bytes)?; + let ticks_per_beat = match smf.header.timing { + Timing::Metrical(ticks) => u64::from(ticks.as_int()), + Timing::Timecode(_, _) => bail!("timecode-based MIDI timing is not supported yet"), + }; + + let mut raw_events = Vec::<(u64, RawMidiEvent)>::new(); + for track in &smf.tracks { + let mut tick_position = 0_u64; + for event in track { + tick_position += u64::from(event.delta.as_int()); + match event.kind { + TrackEventKind::Midi { channel, message } => { + if let Some(data) = midi_message_to_bytes(channel.as_int(), message) { + raw_events.push((tick_position, RawMidiEvent::Message(data))); + } + } + TrackEventKind::Meta(MetaMessage::Tempo(tempo)) => { + raw_events.push((tick_position, RawMidiEvent::Tempo(tempo.as_int()))); + } + _ => {} + } + } + } + + raw_events.sort_by_key(|(tick, _)| *tick); + + let mut events = Vec::new(); + let mut current_tempo_us_per_beat = 500_000_u64; + let mut previous_tick = 0_u64; + let mut elapsed_us = 0_u64; + + for (tick, raw_event) in raw_events { + let delta_ticks = tick.saturating_sub(previous_tick); + elapsed_us = elapsed_us.saturating_add( + delta_ticks + .saturating_mul(current_tempo_us_per_beat) + .checked_div(ticks_per_beat) + .unwrap_or(0), + ); + previous_tick = tick; + + match raw_event { + RawMidiEvent::Message(data) => events.push(ScheduledMidiEvent { + timestamp_ms: elapsed_us / 1000, + data, + }), + RawMidiEvent::Tempo(next_tempo) => { + current_tempo_us_per_beat = u64::from(next_tempo); + } + } + } + + let duration_ms = events.last().map(|event| event.timestamp_ms).unwrap_or(0); + Ok((events, duration_ms)) +} + +fn midi_message_to_bytes(channel: u8, message: MidiMessage) -> Option> { + let status_base = match message { + MidiMessage::NoteOff { .. } => 0x80, + MidiMessage::NoteOn { .. } => 0x90, + MidiMessage::Aftertouch { .. } => 0xA0, + MidiMessage::Controller { .. } => 0xB0, + MidiMessage::ProgramChange { .. } => 0xC0, + MidiMessage::ChannelAftertouch { .. } => 0xD0, + MidiMessage::PitchBend { .. } => 0xE0, + }; + let status = status_base | (channel & 0x0F); + + Some(match message { + MidiMessage::NoteOff { key, vel } => vec![status, key.as_int(), vel.as_int()], + MidiMessage::NoteOn { key, vel } => vec![status, key.as_int(), vel.as_int()], + MidiMessage::Aftertouch { key, vel } => vec![status, key.as_int(), vel.as_int()], + MidiMessage::Controller { controller, value } => vec![status, controller.as_int(), value.as_int()], + MidiMessage::ProgramChange { program } => vec![status, program.as_int()], + MidiMessage::ChannelAftertouch { vel } => vec![status, vel.as_int()], + MidiMessage::PitchBend { bend } => { + let value = bend.as_int(); + vec![status, (value & 0x7F) as u8, ((value >> 7) & 0x7F) as u8] + } + }) +} + +fn stop_task_locked(inner: &mut MidiPlaybackInner) { + if let Some(task) = inner.task.take() { + task.stop.store(true, Ordering::Relaxed); + let _ = task.handle.join(); + } + inner.play_started_at = None; +} + +fn current_position_locked(inner: &MidiPlaybackInner) -> u64 { + if inner.state.is_playing { + if let Some(started_at) = inner.play_started_at { + return (inner.paused_position_ms + started_at.elapsed().as_millis() as u64) + .min(inner.state.duration_ms); + } + } + inner.paused_position_ms.min(inner.state.duration_ms) +} + +enum RawMidiEvent { + Message(Vec), + Tempo(u32), +} + +#[cfg(test)] +mod tests { + use super::{available_backends, MidiBackendConfig}; + + #[test] + fn default_config_uses_system_backend_without_soundfont() { + let config = MidiBackendConfig::default(); + assert_eq!(config.backend_id, "system"); + assert_eq!(config.soundfont_path, None); + } + + #[test] + fn available_backends_include_system_and_soundfont_modes() { + let backends = available_backends(); + assert!(backends.iter().any(|backend| backend.id == "system" && !backend.supports_soundfont)); + assert!(backends.iter().any(|backend| backend.id == "soundfont" && backend.supports_soundfont)); + } +} diff --git a/crates/fbrowser-plugin-core/Cargo.toml b/crates/fbrowser-plugin-core/Cargo.toml new file mode 100644 index 0000000..dae55a3 --- /dev/null +++ b/crates/fbrowser-plugin-core/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "fbrowser-plugin-core" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +serde.workspace = true diff --git a/crates/fbrowser-plugin-core/src/lib.rs b/crates/fbrowser-plugin-core/src/lib.rs new file mode 100644 index 0000000..9d4cfa7 --- /dev/null +++ b/crates/fbrowser-plugin-core/src/lib.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PluginPreviewRequest { + pub path: String, + pub start_ms: u64, + pub end_ms: Option, +} + +pub trait PreviewHost { + fn preview(&self, request: PluginPreviewRequest); +} diff --git a/docker-compose.debug.yml b/docker-compose.debug.yml deleted file mode 100755 index bf8c4d0..0000000 --- a/docker-compose.debug.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: '3.4' - -services: - fbrowser: - image: fbrowser - build: - context: . - dockerfile: ./Dockerfile - command: ["sh", "-c", "pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 Fbrowser.py "] - ports: - - 5678:5678 diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100755 index a84be70..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: '3.4' - -services: - fbrowser: - image: fbrowser - build: - context: . - dockerfile: ./Dockerfile diff --git a/index.html b/index.html new file mode 100644 index 0000000..fae57f1 --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + + Fbrowser + + + + + + +
+ + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b6698e5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3596 @@ +{ + "name": "fbrowser-desktop", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fbrowser-desktop", + "version": "0.1.0", + "dependencies": { + "@tanstack/react-query": "^5.76.1", + "@tanstack/react-virtual": "^3.13.5", + "@tauri-apps/api": "^2.5.0", + "@tauri-apps/plugin-dialog": "^2.3.1", + "clsx": "^2.1.1", + "framer-motion": "^12.9.2", + "lucide-react": "^0.511.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "zustand": "^5.0.3" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.5.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.3", + "tailwindcss": "^3.4.17", + "typescript": "~5.8.3", + "vite": "^6.2.5", + "vitest": "^3.1.1" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/query-core": { + "version": "5.95.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.95.2.tgz", + "integrity": "sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.95.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.95.2.tgz", + "integrity": "sha512-/wGkvLj/st5Ud1Q76KF1uFxScV7WeqN1slQx5280ycwAyYkIPGaRZAEgHxe3bjirSd5Zpwkj6zNcR4cqYni/ZA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.95.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.23", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.23.tgz", + "integrity": "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.23" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.23", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.23.tgz", + "integrity": "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", + "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz", + "integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.1", + "@tauri-apps/cli-darwin-x64": "2.10.1", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", + "@tauri-apps/cli-linux-arm64-musl": "2.10.1", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-musl": "2.10.1", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", + "@tauri-apps/cli-win32-x64-msvc": "2.10.1" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz", + "integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz", + "integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz", + "integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz", + "integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz", + "integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz", + "integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz", + "integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz", + "integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz", + "integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz", + "integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz", + "integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.6.0.tgz", + "integrity": "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.12.tgz", + "integrity": "sha512-qyq26DxfY4awP2gIRXhhLWfwzwI+N5Nxk6iQi8EFizIaWIjqicQTE4sLnZZVdeKPRcVNoJOkkpfzoIYuvCKaIQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001782", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001782.tgz", + "integrity": "sha512-dZcaJLJeDMh4rELYFw1tvSn1bhZWYFOt468FcbHHxx/Z/dFidd1I6ciyFdi3iwfQCyOjqo9upF6lGQYtMiJWxw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.328", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", + "integrity": "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz", + "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.38.0", + "motion-utils": "^12.36.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.511.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz", + "integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/motion-dom": { + "version": "12.38.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz", + "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.36.0" + } + }, + "node_modules/motion-utils": { + "version": "12.36.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz", + "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b9b4ec8 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "fbrowser-desktop", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0 --port 1420", + "build": "tsc && vite build", + "preview": "vite preview", + "test": "vitest run", + "tauri": "tauri" + }, + "dependencies": { + "@tanstack/react-query": "^5.76.1", + "@tanstack/react-virtual": "^3.13.5", + "@tauri-apps/api": "^2.5.0", + "@tauri-apps/plugin-dialog": "^2.3.1", + "clsx": "^2.1.1", + "framer-motion": "^12.9.2", + "lucide-react": "^0.511.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "zustand": "^5.0.3" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.5.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.3", + "tailwindcss": "^3.4.17", + "typescript": "~5.8.3", + "vite": "^6.2.5", + "vitest": "^3.1.1" + } +} diff --git a/paq-8l_intel.exe b/paq-8l_intel.exe deleted file mode 100755 index f6f3684..0000000 Binary files a/paq-8l_intel.exe and /dev/null differ diff --git a/paq7asm-x86_64.asm b/paq7asm-x86_64.asm deleted file mode 100755 index a0754a6..0000000 --- a/paq7asm-x86_64.asm +++ /dev/null @@ -1,102 +0,0 @@ -; YASM x86-64 assembly language code for PAQ7/8 ver. 2, Jan 18, 2007 -; -; (C) 2005-2007, Matt Mahoney, Matthew Fite. -; This is free software under GPL, http://www.gnu.org/licenses/gpl.txt -; -; This code was tested on an Athlon-64 under Ubuntu Linux 2.6.15.27.amd64-generic -; with paq8f and paq8jd. It should work with any PAQ version since paq7, -; because all versions use the same paq7asm.asm code for 32 bit Windows/Linux -; versions. To compile e.g. paq8jd in Linux: -; -; yasm paq7asm-x86_64.asm -f elf -m amd64 -; g++ -O3 -s -fomit-frame-pointer -DUNIX paq8jd.cpp paq7asm-x86_64.o -o paq8jd -; -; This code has not been tested in Windows. (You would need XP Professional -; 64 bit edition and a 64 bit compiler). - -section .text - -BITS 64 - -; Vector product a*b of n signed words, returning signed dword scaled -; down by 8 bits. n is rounded up to a multiple of 8. - - global dot_product ; (short* a, short* b, int n) - align 16 -dot_product: - mov rcx, rdx ; n - mov rax, rdi ; a - mov rdx, rsi ; b - add rcx, 7 ; n rounding up - and rcx, -8 - jz .done - sub rax, 16 - sub rdx, 16 - pxor xmm0, xmm0 ; sum = 0 -.loop: ; each loop sums 4 products - movdqa xmm1, [rax+rcx*2] ; put parital sums of vector product in xmm1 - pmaddwd xmm1, [rdx+rcx*2] - psrad xmm1, 8 - paddd xmm0, xmm1 - sub rcx, 8 - ja .loop - movdqa xmm1, xmm0 ; add 4 parts of xmm0 and return in eax - psrldq xmm1, 8 - paddd xmm0, xmm1 - movdqa xmm1, xmm0 - psrldq xmm1, 4 - paddd xmm0, xmm1 - movd rax, xmm0 -.done - ret - -; Train n neural network weights w[n] on inputs t[n] and err. -; w[i] += (t[i]*err*2 >> 16)+1 >> 1 bounded to +- 32K. -; n is rounded up to a multiple of 8. - -;1st arg rdi -> *t -;2nd arg rsi -> *w -;3rd arg rdx -> n -;4th arg rcx -> err (signed 16 bits) - - global train ; (short* t, short* w, int n, int err) - BITS 64 - align 16 -train: - mov rax, rcx ; err - and rax, 0xffff ; put 8 copies of err in xmm0 - movd xmm0, rax - movd xmm1, rax - pslldq xmm1, 2 - por xmm0, xmm1 - movdqa xmm1, xmm0 - pslldq xmm1, 4 - por xmm0, xmm1 - movdqa xmm1, xmm0 - pslldq xmm1, 8 - por xmm0, xmm1; - pcmpeqb xmm1, xmm1 ; 8 copies of 1 in xmm1 - psrlw xmm1, 15 - mov rcx, rdx ; n - mov rax, rdi ; t - mov rdx, rsi ; w - add rcx, 7 ; n/8 rounding up - and rcx, -8 - sub rax, 16 - sub rdx, 16 - jz .done - align 16 -.loop: ; each iteration adjusts 8 weights - movdqa xmm2, [rdx+rcx*2] ; w[i] - movdqa xmm3, [rax+rcx*2] ; t[i] - paddsw xmm3, xmm3 ; t[i]*2 - pmulhw xmm3, xmm0 ; t[i]*err*2 >> 16 - paddsw xmm3, xmm1 ; (t[i]*err*2 >> 16)+1 - psraw xmm3, 1 ; (t[i]*err*2 >> 16)+1 >> 1 - paddsw xmm2, xmm3 ; w[i] + xmm3 - movdqa [rdx+rcx*2], xmm2 - sub rcx, 8 - ja .loop -.done: - ret - diff --git a/paq7asm.asm b/paq7asm.asm deleted file mode 100755 index 82d55a7..0000000 --- a/paq7asm.asm +++ /dev/null @@ -1,140 +0,0 @@ -; NASM assembly language code for PAQ7. -; (C) 2005, Matt Mahoney. -; This is free software under GPL, http://www.gnu.org/licenses/gpl.txt -; -; MINGW g++: nasm paq7asm.asm -f win32 --prefix _ -; DJGPP g++: nasm paq7asm.asm -f coff --prefix _ -; Borland, Mars: nasm paq7asm.asm -f obj --prefix _ -; Linux: nasm paq7asm.asm -f elf -; -; For other Windows compilers try -f win32 or -f obj. Some old versions -; of Linux should use -f aout instead of -f elf. -; -; This code will only work on a Pentium-MMX or higher. It doesn't -; use extended (Katmai/SSE) instructions. It won't work -; in 64-bit mode. - -section .text use32 class=CODE - -; Reset after MMX -global do_emms -do_emms: - emms - ret - -; Vector product a*b of n signed words, returning signed dword scaled -; down by 8 bits. n is rounded up to a multiple of 8. - -global dot_product ; (short* a, short* b, int n) -align 16 -dot_product: - mov eax, [esp+4] ; a - mov edx, [esp+8] ; b - mov ecx, [esp+12] ; n - add ecx, 7 ; n rounding up - and ecx, -8 - jz .done - sub eax, 8 - sub edx, 8 - pxor mm0, mm0 ; sum = 0 -.loop: ; each loop sums 4 products - movq mm1, [eax+ecx*2] ; put halves of vector product in mm0 - pmaddwd mm1, [edx+ecx*2] - movq mm2, [eax+ecx*2-8] - pmaddwd mm2, [edx+ecx*2-8] - psrad mm1, 8 - psrad mm2, 8 - paddd mm0, mm1 - paddd mm0, mm2 - sub ecx, 8 - ja .loop - movq mm1, mm0 ; add 2 halves of mm0 and return in eax - psrlq mm1, 32 - paddd mm0, mm1 - movd eax, mm0 - emms -.done - ret - -; This should work on a Pentium 4 or higher in 32-bit mode, -; but it isn't much faster than the MMX version so I don't use it. - -global dot_product_sse2 ; (short* a, short* b, int n) -align 16 -dot_product_sse2: - mov eax, [esp+4] ; a - mov edx, [esp+8] ; b - mov ecx, [esp+12] ; n - add ecx, 7 ; n rounding up - and ecx, -8 - jz .done - sub eax, 16 - sub edx, 16 - pxor xmm0, xmm0 ; sum = 0 -.loop: ; each loop sums 4 products - movdqa xmm1, [eax+ecx*2] ; put parital sums of vector product in xmm0 - pmaddwd xmm1, [edx+ecx*2] - psrad xmm1, 8 - paddd xmm0, xmm1 - sub ecx, 8 - ja .loop - movdqa xmm1, xmm0 ; add 4 parts of xmm0 and return in eax - psrldq xmm1, 8 - paddd xmm0, xmm1 - movdqa xmm1, xmm0 - psrldq xmm1, 4 - paddd xmm0, xmm1 - movd eax, xmm0 -.done - ret - - -; Train n neural network weights w[n] on inputs t[n] and err. -; w[i] += t[i]*err*2+1 >> 17 bounded to +- 32K. -; n is rounded up to a multiple of 8. - -global train ; (short* t, short* w, int n, int err) -align 16 -train: - mov eax, [esp+16] ; err - and eax, 0xffff ; put 4 copies of err in mm0 - movd mm0, eax - movd mm1, eax - psllq mm1, 16 - por mm0, mm1 - movq mm1, mm0 - psllq mm1, 32 - por mm0, mm1 - pcmpeqb mm1, mm1 ; 4 copies of 1 in mm1 - psrlw mm1, 15 - mov eax, [esp+4] ; t - mov edx, [esp+8] ; w - mov ecx, [esp+12] ; n - add ecx, 7 ; n/8 rounding up - and ecx, -8 - sub eax, 8 - sub edx, 8 - jz .done -.loop: ; each iteration adjusts 8 weights - movq mm2, [edx+ecx*2] ; w[i] - movq mm3, [eax+ecx*2] ; t[i] - movq mm4, [edx+ecx*2-8] ; w[i] - movq mm5, [eax+ecx*2-8] ; t[i] - paddsw mm3, mm3 - paddsw mm5, mm5 - pmulhw mm3, mm0 - pmulhw mm5, mm0 - paddsw mm3, mm1 - paddsw mm5, mm1 - psraw mm3, 1 - psraw mm5, 1 - paddsw mm2, mm3 - paddsw mm4, mm5 - movq [edx+ecx*2], mm2 - movq [edx+ecx*2-8], mm4 - sub ecx, 8 - ja .loop -.done: - emms - ret - diff --git a/paq7asmsse.asm b/paq7asmsse.asm deleted file mode 100755 index 98ff613..0000000 --- a/paq7asmsse.asm +++ /dev/null @@ -1,93 +0,0 @@ -; NASM assembly language code for PAQ7. -; (C) 2005, Matt Mahoney. -; train - written by wowtiger, Jan. 30, 2007 -; -; This is free software under GPL, http://www.gnu.org/licenses/gpl.txt -; -; This code is a replacement for paq7asm.asm for newer processors -; supporting SSE2 instructions. It is about 1% faster than the -; equivalent MMX code. It can be linked with any version of paq7* -; or paq8*. Assemble as below, then link following the instructions -; in the C++ source code, replacing paq7asm.obj with paq7asmsse.obj. -; No C++ code changes are needed. -; -; MINGW g++: nasm paq7asmsse.asm -f win32 --prefix _ -; DJGPP g++: nasm paq7asmsse.asm -f coff --prefix _ -; Borland, Mars: nasm paq7asmsse.asm -f obj --prefix _ -; Linux: nasm paq7asmsse.asm -f elf -; - -section .text use32 class=CODE - -; Vector product a*b of n signed words, returning signed dword scaled -; down by 8 bits. n is rounded up to a multiple of 8. - -global dot_product ; (short* a, short* b, int n) -align 16 -dot_product: - mov eax, [esp+4] ; a - mov edx, [esp+8] ; b - mov ecx, [esp+12] ; n - add ecx, 7 ; n rounding up - and ecx, -8 - jz .done - sub eax, 16 - sub edx, 16 - pxor xmm0, xmm0 ; sum = 0 -.loop: ; each loop sums 4 products - movdqa xmm1, [eax+ecx*2] ; put parital sums of vector product in xmm0 - pmaddwd xmm1, [edx+ecx*2] - psrad xmm1, 8 - paddd xmm0, xmm1 - sub ecx, 8 - ja .loop - movdqa xmm1, xmm0 ; add 4 parts of xmm0 and return in eax - psrldq xmm1, 8 - paddd xmm0, xmm1 - movdqa xmm1, xmm0 - psrldq xmm1, 4 - paddd xmm0, xmm1 - movd eax, xmm0 -.done - ret - - -; Train n neural network weights w[n] on inputs t[n] and err. -; w[i] += t[i]*err*2+1 >> 17 bounded to +- 32K. -; n is rounded up to a multiple of 8. - -; Train for SSE2 -; Use this code to get some performance... - -global train ; (short* t, short* w, int n, int err) -align 16 -train: - mov eax, [esp+4] ; t - mov edx, [esp+8] ; w - mov ecx, [esp+12] ; n - add ecx, 7 ; n/8 rounding up - and ecx, -8 - jz .done - sub eax, 16 - sub edx, 16 - movd xmm0, [esp+16] - pshuflw xmm0,xmm0,0 - punpcklqdq xmm0,xmm0 -.loop: ; each iteration adjusts 8 weights - movdqa xmm3, [eax+ecx*2] ; t[i] - movdqa xmm2, [edx+ecx*2] ; w[i] - paddsw xmm3, xmm3 ; t[i]*2 - pmulhw xmm3, xmm0 ; t[i]*err*2 >> 16 - paddsw xmm3, [_mask] ; (t[i]*err*2 >> 16)+1 - psraw xmm3, 1 ; (t[i]*err*2 >> 16)+1 >> 1 - paddsw xmm2, xmm3 ; w[i] + xmm3 - movdqa [edx+ecx*2], xmm2 - sub ecx, 8 - ja .loop -.done: - ret - -align 16 -_mask dd 10001h,10001h,10001h,10001h ; 8 copies of 1 in xmm1 - - diff --git a/paq8l.cpp b/paq8l.cpp deleted file mode 100755 index 3f69df8..0000000 --- a/paq8l.cpp +++ /dev/null @@ -1,3575 +0,0 @@ -/* paq8l file compressor/archiver. Release by Matt Mahoney, Mar. 8, 2007. - Updated Apr. 15, 2007 (no change to paq8l.exe). - - Copyright (C) 2006 Matt Mahoney, Serge Osnach, Alexander Ratushnyak, - Bill Pettis, Przemyslaw Skibinski, Matthew Fite, wowtiger, Andrew Paterson, - - - LICENSE - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details at - Visit . - -To install and use in Windows: - -- To install, put paq8l.exe or a shortcut to it on your desktop. -- To compress a file or folder, drop it on the paq8l icon. -- To decompress, drop a .paq8l file on the icon. - -A .paq8l extension is added for compression, removed for decompression. -The output will go in the same folder as the input. - -While paq8l is working, a command window will appear and report -progress. When it is done you can close the window by pressing -ENTER or clicking [X]. - - -COMMAND LINE INTERFACE - -- To install, put paq8l.exe somewhere in your PATH. -- To compress: paq8l [-N] file1 [file2...] -- To decompress: paq8l [-d] file1.paq8l [dir2] -- To view contents: more < file1.paq8l - -The compressed output file is named by adding ".paq8l" extension to -the first named file (file1.paq8l). Each file that exists will be -added to the archive and its name will be stored without a path. -The option -N specifies a compression level ranging from -0 -(fastest) to -9 (smallest). The default is -5. If there is -no option and only one file, then the program will pause when -finished until you press the ENTER key (to support drag and drop). -If file1.paq8l exists then it is overwritten. - -If the first named file ends in ".paq8l" then it is assumed to be -an archive and the files within are extracted to the same directory -as the archive unless a different directory (dir2) is specified. -The -d option forces extraction even if there is not a ".paq8l" -extension. If any output file already exists, then it is compared -with the archive content and the first byte that differs is reported. -No files are overwritten or deleted. If there is only one argument -(no -d or dir2) then the program will pause when finished until -you press ENTER. - -For compression, if any named file is actually a directory, then all -files and subdirectories are compressed, preserving the directory -structure, except that empty directories are not stored, and file -attributes (timestamps, permissions, etc.) are not preserved. -During extraction, directories are created as needed. For example: - - paq8l -4 c:\tmp\foo bar - -compresses foo and bar (if they exist) to c:\tmp\foo.paq8l at level 4. - - paq8l -d c:\tmp\foo.paq8l . - -extracts foo and compares bar in the current directory. If foo and bar -are directories then their contents are extracted/compared. - -There are no commands to update an existing archive or to extract -part of an archive. Files and archives larger than 2GB are not -supported (but might work on 64-bit machines, not tested). -File names with nonprintable characters are not supported (spaces -are OK). - - -TO COMPILE - -There are 2 files: paq8l.cpp (C++) and paq7asm.asm (NASM/YASM). -paq7asm.asm is the same as in paq7 and paq8x. paq8l.cpp recognizes the -following compiler options: - - -DWINDOWS (to compile in Windows) - -DUNIX (to compile in Unix, Linux, Solairs, MacOS/Darwin, etc) - -DNOASM (to replace paq7asm.asm with equivalent C++) - -DDEFAULT_OPTION=N (to change the default compression level from 5 to N). - -If you compile without -DWINDOWS or -DUNIX, you can still compress files, -but you cannot compress directories or create them during extraction. -You can extract directories if you manually create the empty directories -first. - -Use -DEFAULT_OPTION=N to change the default compression level to support -drag and drop on machines with less than 256 MB of memory. Use --DDEFAULT_OPTION=4 for 128 MB, 3 for 64 MB, 2 for 32 MB, etc. - -Use -DNOASM for non x86-32 machines, or older than a Pentium-MMX (about -1997), or if you don't have NASM or YASM to assemble paq7asm.asm. The -program will still work but it will be slower. For NASM in Windows, -use the options "--prefix _" and either "-f win32" or "-f obj" depending -on your C++ compiler. In Linux, use "-f elf". - -Recommended compiler commands and optimizations: - - MINGW g++: - nasm paq7asm.asm -f win32 --prefix _ - g++ paq8l.cpp -DWINDOWS -O2 -Os -s -march=pentiumpro -fomit-frame-pointer -o paq8l.exe paq7asm.obj - - Borland: - nasm paq7asm.asm -f obj --prefix _ - bcc32 -DWINDOWS -O -w-8027 paq8l.cpp paq7asm.obj - - Mars: - nasm paq7asm.asm -f obj --prefix _ - dmc -DWINDOWS -Ae -O paq8l.cpp paq7asm.obj - - UNIX/Linux (PC): - nasm -f elf paq7asm.asm - g++ paq8l.cpp -DUNIX -O2 -Os -s -march=pentiumpro -fomit-frame-pointer -o paq8l paq7asm.o - - Non PC (e.g. PowerPC under MacOS X) - g++ paq8l.cpp -O2 -DUNIX -DNOASM -s -o paq8l - -MinGW produces faster executables than Borland or Mars, but Intel 9 -is about 4% faster than MinGW). - - -ARCHIVE FILE FORMAT - -An archive has the following format. It is intended to be both -human and machine readable. The header ends with CTRL-Z (Windows EOF) -so that the binary compressed data is not displayed on the screen. - - paq8l -N CR LF - size TAB filename CR LF - size TAB filename CR LF - ... - CTRL-Z - compressed binary data - --N is the option (-0 to -9), even if a default was used. -Plain file names are stored without a path. Files in compressed -directories are stored with path relative to the compressed directory -(using UNIX style forward slashes "/"). For example, given these files: - - 123 C:\dir1\file1.txt - 456 C:\dir2\file2.txt - -Then - - paq8l archive \dir1\file1.txt \dir2 - -will create archive.paq8l with the header: - - paq8l -5 - 123 file1.txt - 456 dir2/file2.txt - -The command: - - paq8l archive.paq8l C:\dir3 - -will create the files: - - C:\dir3\file1.txt - C:\dir3\dir2\file2.txt - -Decompression will fail if the first 7 bytes are not "paq8l -". Sizes -are stored as decimal numbers. CR, LF, TAB, CTRL-Z are ASCII codes -13, 10, 9, 26 respectively. - - -ARITHMETIC CODING - -The binary data is arithmetic coded as the shortest base 256 fixed point -number x = SUM_i x_i 256^-1-i such that p(= 16. - - The primaty output is t_i := stretch(sm(n0,n1,h)), where sm(.) is - a stationary map with K = 1/256, initiaized to - sm(n0,n1,h) = (n1+(1/64))/(n+2/64). Four additional inputs are also - be computed to improve compression slightly: - - p1_i = sm(n0,n1,h) - p0_i = 1 - p1_i - t_i := stretch(p_1) - t_i+1 := K1 (p1_i - p0_i) - t_i+2 := K2 stretch(p1) if n0 = 0, -K2 stretch(p1) if n1 = 0, else 0 - t_i+3 := K3 (-p0_i if n1 = 0, p1_i if n0 = 0, else 0) - t_i+4 := K3 (-p0_i if n0 = 0, p1_i if n1 = 0, else 0) - - where K1..K4 are ad-hoc constants. - - h is updated as follows: - If n < 4, append y_j to h. - Else if n <= 16, set h := y_j. - Else h = 0. - - The update rule is biased toward newer data in a way that allows - n0 or n1, but not both, to grow large by discarding counts of the - opposite bit. Large counts are incremented probabilistically. - Specifically, when y_j = 0 then the update rule is: - - n0 := n0 + 1, n < 29 - n0 + 1 with probability 2^(27-n0)/2 else n0, 29 <= n0 < 41 - n0, n = 41. - n1 := n1, n1 <= 5 - round(8/3 lg n1), if n1 > 5 - - swapping (n0,n1) when y_j = 1. - - Furthermore, to allow an 8 bit representation for (n0,n1,h), states - exceeding the following values of n0 or n1 are replaced with the - state with the closest ratio n0:n1 obtained by decrementing the - smaller count: (41,0,h), (40,1,h), (12,2,h), (5,3,h), (4,4,h), - (3,5,h), (2,12,h), (1,40,h), (0,41,h). For example: - (12,2,1) 0-> (7,1,0) because there is no state (13,2,0). - -- Match Model. The state is (c,b), initially (0,0), where c is 1 if - the context was previously seen, else 0, and b is the next bit in - this context. The prediction is: - - t_i := (2b - 1)Kc log(m + 1) - - where m is the length of the context. The update rule is c := 1, - b := y_j. A match model can be implemented efficiently by storing - input in a buffer and storing pointers into the buffer into a hash - table indexed by context. Then c is indicated by a hash table entry - and b can be retrieved from the buffer. - - -CONTEXTS - -High compression is achieved by combining a large number of contexts. -Most (not all) contexts start on a byte boundary and end on the bit -immediately preceding the predicted bit. The contexts below are -modeled with both a run map and a nonstationary map unless indicated. - -- Order n. The last n bytes, up to about 16. For general purpose data. - Most of the compression occurs here for orders up to about 6. - An order 0 context includes only the 0-7 bits of the partially coded - byte and the number of these bits (255 possible values). - -- Sparse. Usually 1 or 2 of the last 8 bytes preceding the byte containing - the predicted bit, e.g (2), (3),..., (8), (1,3), (1,4), (1,5), (1,6), - (2,3), (2,4), (3,6), (4,8). The ordinary order 1 and 2 context, (1) - or (1,2) are included above. Useful for binary data. - -- Text. Contexts consists of whole words (a-z, converted to lower case - and skipping other values). Contexts may be sparse, e.g (0,2) meaning - the current (partially coded) word and the second word preceding the - current one. Useful contexts are (0), (0,1), (0,1,2), (0,2), (0,3), - (0,4). The preceding byte may or may not be included as context in the - current word. - -- Formatted text. The column number (determined by the position of - the last linefeed) is combined with other contexts: the charater to - the left and the character above it. - -- Fixed record length. The record length is determined by searching for - byte sequences with a uniform stride length. Once this is found, then - the record length is combined with the context of the bytes immediately - preceding it and the corresponding byte locations in the previous - one or two records (as with formatted text). - -- Context gap. The distance to the previous occurrence of the order 1 - or order 2 context is combined with other low order (1-2) contexts. - -- FAX. For 2-level bitmapped images. Contexts are the surrounding - pixels already seen. Image width is assumed to be 1728 bits (as - in calgary/pic). - -- Image. For uncompressed 24-bit color BMP and TIFF images. Contexts - are the high order bits of the surrounding pixels and linear - combinations of those pixels, including other color planes. The - image width is detected from the file header. When an image is - detected, other models are turned off to improve speed. - -- JPEG. Files are further compressed by partially uncompressing back - to the DCT coefficients to provide context for the next Huffman code. - Only baseline DCT-Huffman coded files are modeled. (This ia about - 90% of images, the others are usually progresssive coded). JPEG images - embedded in other files (quite common) are detected by headers. The - baseline JPEG coding process is: - - Convert to grayscale and 2 chroma colorspace. - - Sometimes downsample the chroma images 2:1 or 4:1 in X and/or Y. - - Divide each of the 3 images into 8x8 blocks. - - Convert using 2-D discrete cosine transform (DCT) to 64 12-bit signed - coefficients. - - Quantize the coefficients by integer division (lossy). - - Split the image into horizontal slices coded independently, separated - by restart codes. - - Scan each block starting with the DC (0,0) coefficient in zigzag order - to the (7,7) coefficient, interleaving the 3 color components in - order to scan the whole image left to right starting at the top. - - Subtract the previous DC component from the current in each color. - - Code the coefficients using RS codes, where R is a run of R zeros (0-15) - and S indicates 0-11 bits of a signed value to follow. (There is a - special RS code (EOB) to indicate the rest of the 64 coefficients are 0). - - Huffman code the RS symbol, followed by S literal bits. - The most useful contexts are the current partially coded Huffman code - (including S following bits) combined with the coefficient position - (0-63), color (0-2), and last few RS codes. - -- Match. When a context match of 400 bytes or longer is detected, - the next bit of the match is predicted and other models are turned - off to improve speed. - -- Exe. When a x86 file (.exe, .obj, .dll) is detected, sparse contexts - with gaps of 1-12 selecting only the prefix, opcode, and the bits - of the modR/M byte that are relevant to parsing are selected. - This model is turned off otherwise. - -- Indirect. The history of the last 1-3 bytes in the context of the - last 1-2 bytes is combined with this 1-2 byte context. - -- DMC. A bitwise n-th order context is built from a state machine using - DMC, described in http://plg.uwaterloo.ca/~ftp/dmc/dmc.c - The effect is to extend a single context, one bit at a time and predict - the next bit based on the history in this context. The model here differs - in that two predictors are used. One is a pair of counts as in the original - DMC. The second predictor is a bit history state mapped adaptively to - a probability as as in a Nonstationary Map. - -ARCHITECTURE - -The context models are mixed by several of several hundred neural networks -selected by a low-order context. The outputs of these networks are -combined using a second neural network, then fed through several stages of -adaptive probability maps (APM) before arithmetic coding. - -For images, only one neural network is used and its context is fixed. - -An APM is a stationary map combining a context and an input probability. -The input probability is stretched and divided into 32 segments to -combine with other contexts. The output is interpolated between two -adjacent quantized values of stretch(p1). There are 2 APM stages in series: - - p1 := (p1 + 3 APM(order 0, p1)) / 4. - p1 := (APM(order 1, p1) + 2 APM(order 2, p1) + APM(order 3, p1)) / 4. - -PREPROCESSING - -paq8l uses preprocessing transforms on certain data types to improve -compression. To improve reliability, the decoding transform is -tested during compression to ensure that the input file can be -restored. If the decoder output is not identical to the input file -due to a bug, then the transform is abandoned and the data is compressed -without a transform so that it will still decompress correctly. - -The input is split into blocks with the format -where is 1 byte (0 = no transform), is the size -of the data after decoding, which may be different than the size of . -Blocks do not span file boundaries, and have a maximum size of 4MB to -2GB depending on compression level. Large files are split into blocks -of this size. The preprocessor has 3 parts: - -- Detector. Splits the input into smaller blocks depending on data type. - -- Coder. Input is a block to be compressed. Output is a temporary - file. The coder determines whether a transform is to be applied - based on file type, and if so, which one. A coder may use lots - of resources (memory, time) and make multiple passes through the - input file. The file type is stored (as one byte) during compression. - -- Decoder. Performs the inverse transform of the coder. It uses few - resorces (fast, low memory) and runs in a single pass (stream oriented). - It takes input either from a file or the arithmetic decoder. Each call - to the decoder returns a single decoded byte. - -The following transforms are used: - -- EXE: CALL (0xE8) and JMP (0xE9) address operands are converted from - relative to absolute address. The transform is to replace the sequence - E8/E9 xx xx xx 00/FF by adding file offset modulo 2^25 (signed range, - little-endian format). Data to transform is identified by trying the - transform and applying a crude compression test: testing whether the - byte following the E8/E8 (LSB of the address) occurred more recently - in the transformed data than the original and within 4KB 4 times in - a row. The block ends when this does not happen for 4KB. - -- JPEG: detected by SOI and SOF and ending with EOI or any nondecodable - data. No transform is applied. The purpose is to separate images - embedded in execuables to block the EXE transform, and for a future - place to insert a transform. - - -IMPLEMENTATION - -Hash tables are designed to minimize cache misses, which consume most -of the CPU time. - -Most of the memory is used by the nonstationary context models. -Contexts are represented by 32 bits, possibly a hash. These are -mapped to a bit history, represented by 1 byte. The hash table is -organized into 64-byte buckets on cache line boundaries. Each bucket -contains 7 x 7 bit histories, 7 16-bit checksums, and a 2 element LRU -queue packed into one byte. Each 7 byte element represents 7 histories -for a context ending on a 3-bit boundary plus 0-2 more bits. One -element (for bits 0-1, which have 4 unused bytes) also contains a run model -consisting of the last byte seen and a count (as 1 byte each). - -Run models use 4 byte hash elements consisting of a 2 byte checksum, a -repeat count (0-255) and the byte value. The count also serves as -a priority. - -Stationary models are most appropriate for small contexts, so the -context is used as a direct table lookup without hashing. - -The match model maintains a pointer to the last match until a mismatching -bit is found. At the start of the next byte, the hash table is referenced -to find another match. The hash table of pointers is updated after each -whole byte. There is no checksum. Collisions are detected by comparing -the current and matched context in a rotating buffer. - -The inner loops of the neural network prediction (1) and training (2) -algorithms are implemented in MMX assembler, which computes 4 elements -at a time. Using assembler is 8 times faster than C++ for this code -and 1/3 faster overall. (However I found that SSE2 code on an AMD-64, -which computes 8 elements at a time, is not any faster). - - -DIFFERENCES FROM PAQ7 - -An .exe model and filter are added. Context maps are improved using 16-bit -checksums to reduce collisions. The state table uses probabilistic updates -for large counts, more states that remember the last bit, and decreased -discounting of the opposite count. It is implemented as a fixed table. -There are also many minor changes. - -DIFFERENCES FROM PAQ8A - -The user interface supports directory compression and drag and drop. -The preprocessor segments the input into blocks and uses more robust -EXE detection. An indirect context model was added. There is no -dictionary preprocesor like PAQ8B/C/D/E. - -DIFFERENCES FROM PAQ8F - -Different models, usually from paq8hp*. Also changed rate from 8 to 7. A bug -in Array was fixed that caused the program to silently crash upon exit. - -DIFFERENCES FROM PAQ8J - -1) Slightly improved sparse model. -2) Added new family of sparse contexts. Each byte mapped to 3-bit value, where -different values corresponds to different byte classes. For example, input -byte 0x00 transformed into 0, all bytes that less then 16 -- into 5, all -punctuation marks (ispunct(c)!=0) -- into 2 etc. Then this flags from 11 -previous bytes combined into 32-bit pseudo-context. - -All this improvements gives only 62 byte on BOOK1, but on binaries archive size -reduced on 1-2%. - -DIFFERENCES FROM PAQ8JA - -Introduced distance model. Distance model uses distance to last occurence -of some anchor char ( 0x00, space, newline, 0xff ), combined with previous -charactes as context. This slightly improves compression of files with -variable-width record data. - -DIFFERENCES FROM PAQ8JB - -Restored recordModel(), broken in paq8hp*. Slightly tuned indirectModel(). - -DIFFERENCES FROM PAQ8JC - -Changed the APMs in the Predictor. Up to a 0.2% improvement for some files. - -DIFFERENCES FROM PAQ8JD - -Added DMCModel. Removed some redundant models from SparseModel and other -minor tuneups. Changes introduced in PAQ8K were not carried over. - -PAQ8L v.2 - -Changed Mixer::p() to p() to fix a compiler error in Linux -(patched by Indrek Kruusa, Apr. 15, 2007). - -*/ - -#define PROGNAME "paq8l" // Please change this if you change the program. - -#include -#include -#include -#include -#include -#include -#define NDEBUG // remove for debugging (turns on Array bound checks) -#include - -#ifdef UNIX -#include -#include -#include -#include -#endif - -#ifdef WINDOWS -#include -#endif - -#ifndef DEFAULT_OPTION -#define DEFAULT_OPTION 5 -#endif - -// 8, 16, 32 bit unsigned types (adjust as appropriate) -typedef unsigned char U8; -typedef unsigned short U16; -typedef unsigned int U32; - -// min, max functions -#ifndef WINDOWS -inline int min(int a, int b) {return a='A'&&c1<='Z') c1+='a'-'A'; - int c2=*b; - if (c2>='A'&&c2<='Z') c2+='a'-'A'; - if (c1!=c2) return 0; - ++a; - ++b; - } - return *a==*b; -} - -//////////////////////// Program Checker ///////////////////// - -// Track time and memory used -class ProgramChecker { - int memused; // bytes allocated by Array now - int maxmem; // most bytes allocated ever - clock_t start_time; // in ticks -public: - void alloc(int n) { // report memory allocated, may be negative - memused+=n; - if (memused>maxmem) maxmem=memused; - } - ProgramChecker(): memused(0), maxmem(0) { - start_time=clock(); - assert(sizeof(U8)==1); - assert(sizeof(U16)==2); - assert(sizeof(U32)==4); - assert(sizeof(short)==2); - assert(sizeof(int)==4); - } - void print() const { // print time and memory used - printf("Time %1.2f sec, used %d bytes of memory\n", - double(clock()-start_time)/CLOCKS_PER_SEC, maxmem); - } -} programChecker; - -//////////////////////////// Array //////////////////////////// - -// Array a(n); creates n elements of T initialized to 0 bits. -// Constructors for T are not called. -// Indexing is bounds checked if assertions are on. -// a.size() returns n. -// a.resize(n) changes size to n, padding with 0 bits or truncating. -// a.push_back(x) appends x and increases size by 1, reserving up to size*2. -// a.pop_back() decreases size by 1, does not free memory. -// Copy and assignment are not supported. -// Memory is aligned on a ALIGN byte boundary (power of 2), default is none. - -template class Array { -private: - int n; // user size - int reserved; // actual size - char *ptr; // allocated memory, zeroed - T* data; // start of n elements of aligned data - void create(int i); // create with size i -public: - explicit Array(int i=0) {create(i);} - ~Array(); - T& operator[](int i) { -#ifndef NDEBUG - if (i<0 || i>=n) fprintf(stderr, "%d out of bounds %d\n", i, n), quit(); -#endif - return data[i]; - } - const T& operator[](int i) const { -#ifndef NDEBUG - if (i<0 || i>=n) fprintf(stderr, "%d out of bounds %d\n", i, n), quit(); -#endif - return data[i]; - } - int size() const {return n;} - void resize(int i); // change size to i - void pop_back() {if (n>0) --n;} // decrement size - void push_back(const T& x); // increment size, append x -private: - Array(const Array&); // no copy or assignment - Array& operator=(const Array&); -}; - -template void Array::resize(int i) { - if (i<=reserved) { - n=i; - return; - } - char *saveptr=ptr; - T *savedata=data; - int saven=n; - create(i); - if (saveptr) { - if (savedata) { - memcpy(data, savedata, sizeof(T)*min(i, saven)); - programChecker.alloc(-ALIGN-n*sizeof(T)); - } - free(saveptr); - } -} - -template void Array::create(int i) { - n=reserved=i; - if (i<=0) { - data=0; - ptr=0; - return; - } - const int sz=ALIGN+n*sizeof(T); - programChecker.alloc(sz); - ptr = (char*)calloc(sz, 1); - if (!ptr) quit("Out of memory"); - data = (ALIGN ? (T*)(ptr+ALIGN-(((long)ptr)&(ALIGN-1))) : (T*)ptr); - assert((char*)data>=ptr && (char*)data<=ptr+ALIGN); -} - -template Array::~Array() { - programChecker.alloc(-ALIGN-n*sizeof(T)); - free(ptr); -} - -template void Array::push_back(const T& x) { - if (n==reserved) { - int saven=n; - resize(max(1, n*2)); - n=saven; - } - data[n++]=x; -} - -/////////////////////////// String ///////////////////////////// - -// A tiny subset of std::string -// size() includes NUL terminator. - -class String: public Array { -public: - const char* c_str() const {return &(*this)[0];} - void operator=(const char* s) { - resize(strlen(s)+1); - strcpy(&(*this)[0], s); - } - void operator+=(const char* s) { - assert(s); - pop_back(); - while (*s) push_back(*s++); - push_back(0); - } - String(const char* s=""): Array(1) { - (*this)+=s; - } -}; - - -//////////////////////////// rnd /////////////////////////////// - -// 32-bit pseudo random number generator -class Random{ - Array table; - int i; -public: - Random(): table(64) { - table[0]=123456789; - table[1]=987654321; - for(int j=0; j<62; j++) table[j+2]=table[j+1]*11+table[j]*23/16; - i=0; - } - U32 operator()() { - return ++i, table[i&63]=table[i-24&63]^table[i-55&63]; - } -} rnd; - -////////////////////////////// Buf ///////////////////////////// - -// Buf(n) buf; creates an array of n bytes (must be a power of 2). -// buf[i] returns a reference to the i'th byte with wrap (no out of bounds). -// buf(i) returns i'th byte back from pos (i > 0) -// buf.size() returns n. - -int pos; // Number of input bytes in buf (not wrapped) - -class Buf { - Array b; -public: - Buf(int i=0): b(i) {} - void setsize(int i) { - if (!i) return; - assert(i>0 && (i&(i-1))==0); - b.resize(i); - } - U8& operator[](int i) { - return b[i&b.size()-1]; - } - int operator()(int i) const { - assert(i>0); - return b[pos-i&b.size()-1]; - } - int size() const { - return b.size(); - } -}; - -/////////////////////// Global context ///////////////////////// - -int level=DEFAULT_OPTION; // Compression level 0 to 9 -#define MEM (0x10000< t; -public: - int operator()(U16 x) const {return t[x];} - Ilog(); -} ilog; - -// Compute lookup table by numerical integration of 1/x -Ilog::Ilog(): t(65536) { - U32 x=14155776; - for (int i=2; i<65536; ++i) { - x+=774541002/(i*2-1); // numerator is 2^29/ln 2 - t[i]=x>>24; - } -} - -// llog(x) accepts 32 bits -inline int llog(U32 x) { - if (x>=0x1000000) - return 256+ilog(x>>16); - else if (x>=0x10000) - return 128+ilog(x>>8); - else - return ilog(x); -} - -///////////////////////// state table //////////////////////// - -// State table: -// nex(state, 0) = next state if bit y is 0, 0 <= state < 256 -// nex(state, 1) = next state if bit y is 1 -// nex(state, 2) = number of zeros in bit history represented by state -// nex(state, 3) = number of ones represented -// -// States represent a bit history within some context. -// State 0 is the starting state (no bits seen). -// States 1-30 represent all possible sequences of 1-4 bits. -// States 31-252 represent a pair of counts, (n0,n1), the number -// of 0 and 1 bits respectively. If n0+n1 < 16 then there are -// two states for each pair, depending on if a 0 or 1 was the last -// bit seen. -// If n0 and n1 are too large, then there is no state to represent this -// pair, so another state with about the same ratio of n0/n1 is substituted. -// Also, when a bit is observed and the count of the opposite bit is large, -// then part of this count is discarded to favor newer data over old. - -#if 1 // change to #if 0 to generate this table at run time (4% slower) -static const U8 State_table[256][4]={ - { 1, 2, 0, 0},{ 3, 5, 1, 0},{ 4, 6, 0, 1},{ 7, 10, 2, 0}, // 0-3 - { 8, 12, 1, 1},{ 9, 13, 1, 1},{ 11, 14, 0, 2},{ 15, 19, 3, 0}, // 4-7 - { 16, 23, 2, 1},{ 17, 24, 2, 1},{ 18, 25, 2, 1},{ 20, 27, 1, 2}, // 8-11 - { 21, 28, 1, 2},{ 22, 29, 1, 2},{ 26, 30, 0, 3},{ 31, 33, 4, 0}, // 12-15 - { 32, 35, 3, 1},{ 32, 35, 3, 1},{ 32, 35, 3, 1},{ 32, 35, 3, 1}, // 16-19 - { 34, 37, 2, 2},{ 34, 37, 2, 2},{ 34, 37, 2, 2},{ 34, 37, 2, 2}, // 20-23 - { 34, 37, 2, 2},{ 34, 37, 2, 2},{ 36, 39, 1, 3},{ 36, 39, 1, 3}, // 24-27 - { 36, 39, 1, 3},{ 36, 39, 1, 3},{ 38, 40, 0, 4},{ 41, 43, 5, 0}, // 28-31 - { 42, 45, 4, 1},{ 42, 45, 4, 1},{ 44, 47, 3, 2},{ 44, 47, 3, 2}, // 32-35 - { 46, 49, 2, 3},{ 46, 49, 2, 3},{ 48, 51, 1, 4},{ 48, 51, 1, 4}, // 36-39 - { 50, 52, 0, 5},{ 53, 43, 6, 0},{ 54, 57, 5, 1},{ 54, 57, 5, 1}, // 40-43 - { 56, 59, 4, 2},{ 56, 59, 4, 2},{ 58, 61, 3, 3},{ 58, 61, 3, 3}, // 44-47 - { 60, 63, 2, 4},{ 60, 63, 2, 4},{ 62, 65, 1, 5},{ 62, 65, 1, 5}, // 48-51 - { 50, 66, 0, 6},{ 67, 55, 7, 0},{ 68, 57, 6, 1},{ 68, 57, 6, 1}, // 52-55 - { 70, 73, 5, 2},{ 70, 73, 5, 2},{ 72, 75, 4, 3},{ 72, 75, 4, 3}, // 56-59 - { 74, 77, 3, 4},{ 74, 77, 3, 4},{ 76, 79, 2, 5},{ 76, 79, 2, 5}, // 60-63 - { 62, 81, 1, 6},{ 62, 81, 1, 6},{ 64, 82, 0, 7},{ 83, 69, 8, 0}, // 64-67 - { 84, 71, 7, 1},{ 84, 71, 7, 1},{ 86, 73, 6, 2},{ 86, 73, 6, 2}, // 68-71 - { 44, 59, 5, 3},{ 44, 59, 5, 3},{ 58, 61, 4, 4},{ 58, 61, 4, 4}, // 72-75 - { 60, 49, 3, 5},{ 60, 49, 3, 5},{ 76, 89, 2, 6},{ 76, 89, 2, 6}, // 76-79 - { 78, 91, 1, 7},{ 78, 91, 1, 7},{ 80, 92, 0, 8},{ 93, 69, 9, 0}, // 80-83 - { 94, 87, 8, 1},{ 94, 87, 8, 1},{ 96, 45, 7, 2},{ 96, 45, 7, 2}, // 84-87 - { 48, 99, 2, 7},{ 48, 99, 2, 7},{ 88,101, 1, 8},{ 88,101, 1, 8}, // 88-91 - { 80,102, 0, 9},{103, 69,10, 0},{104, 87, 9, 1},{104, 87, 9, 1}, // 92-95 - {106, 57, 8, 2},{106, 57, 8, 2},{ 62,109, 2, 8},{ 62,109, 2, 8}, // 96-99 - { 88,111, 1, 9},{ 88,111, 1, 9},{ 80,112, 0,10},{113, 85,11, 0}, // 100-103 - {114, 87,10, 1},{114, 87,10, 1},{116, 57, 9, 2},{116, 57, 9, 2}, // 104-107 - { 62,119, 2, 9},{ 62,119, 2, 9},{ 88,121, 1,10},{ 88,121, 1,10}, // 108-111 - { 90,122, 0,11},{123, 85,12, 0},{124, 97,11, 1},{124, 97,11, 1}, // 112-115 - {126, 57,10, 2},{126, 57,10, 2},{ 62,129, 2,10},{ 62,129, 2,10}, // 116-119 - { 98,131, 1,11},{ 98,131, 1,11},{ 90,132, 0,12},{133, 85,13, 0}, // 120-123 - {134, 97,12, 1},{134, 97,12, 1},{136, 57,11, 2},{136, 57,11, 2}, // 124-127 - { 62,139, 2,11},{ 62,139, 2,11},{ 98,141, 1,12},{ 98,141, 1,12}, // 128-131 - { 90,142, 0,13},{143, 95,14, 0},{144, 97,13, 1},{144, 97,13, 1}, // 132-135 - { 68, 57,12, 2},{ 68, 57,12, 2},{ 62, 81, 2,12},{ 62, 81, 2,12}, // 136-139 - { 98,147, 1,13},{ 98,147, 1,13},{100,148, 0,14},{149, 95,15, 0}, // 140-143 - {150,107,14, 1},{150,107,14, 1},{108,151, 1,14},{108,151, 1,14}, // 144-147 - {100,152, 0,15},{153, 95,16, 0},{154,107,15, 1},{108,155, 1,15}, // 148-151 - {100,156, 0,16},{157, 95,17, 0},{158,107,16, 1},{108,159, 1,16}, // 152-155 - {100,160, 0,17},{161,105,18, 0},{162,107,17, 1},{108,163, 1,17}, // 156-159 - {110,164, 0,18},{165,105,19, 0},{166,117,18, 1},{118,167, 1,18}, // 160-163 - {110,168, 0,19},{169,105,20, 0},{170,117,19, 1},{118,171, 1,19}, // 164-167 - {110,172, 0,20},{173,105,21, 0},{174,117,20, 1},{118,175, 1,20}, // 168-171 - {110,176, 0,21},{177,105,22, 0},{178,117,21, 1},{118,179, 1,21}, // 172-175 - {110,180, 0,22},{181,115,23, 0},{182,117,22, 1},{118,183, 1,22}, // 176-179 - {120,184, 0,23},{185,115,24, 0},{186,127,23, 1},{128,187, 1,23}, // 180-183 - {120,188, 0,24},{189,115,25, 0},{190,127,24, 1},{128,191, 1,24}, // 184-187 - {120,192, 0,25},{193,115,26, 0},{194,127,25, 1},{128,195, 1,25}, // 188-191 - {120,196, 0,26},{197,115,27, 0},{198,127,26, 1},{128,199, 1,26}, // 192-195 - {120,200, 0,27},{201,115,28, 0},{202,127,27, 1},{128,203, 1,27}, // 196-199 - {120,204, 0,28},{205,115,29, 0},{206,127,28, 1},{128,207, 1,28}, // 200-203 - {120,208, 0,29},{209,125,30, 0},{210,127,29, 1},{128,211, 1,29}, // 204-207 - {130,212, 0,30},{213,125,31, 0},{214,137,30, 1},{138,215, 1,30}, // 208-211 - {130,216, 0,31},{217,125,32, 0},{218,137,31, 1},{138,219, 1,31}, // 212-215 - {130,220, 0,32},{221,125,33, 0},{222,137,32, 1},{138,223, 1,32}, // 216-219 - {130,224, 0,33},{225,125,34, 0},{226,137,33, 1},{138,227, 1,33}, // 220-223 - {130,228, 0,34},{229,125,35, 0},{230,137,34, 1},{138,231, 1,34}, // 224-227 - {130,232, 0,35},{233,125,36, 0},{234,137,35, 1},{138,235, 1,35}, // 228-231 - {130,236, 0,36},{237,125,37, 0},{238,137,36, 1},{138,239, 1,36}, // 232-235 - {130,240, 0,37},{241,125,38, 0},{242,137,37, 1},{138,243, 1,37}, // 236-239 - {130,244, 0,38},{245,135,39, 0},{246,137,38, 1},{138,247, 1,38}, // 240-243 - {140,248, 0,39},{249,135,40, 0},{250, 69,39, 1},{ 80,251, 1,39}, // 244-247 - {140,252, 0,40},{249,135,41, 0},{250, 69,40, 1},{ 80,251, 1,40}, // 248-251 - {140,252, 0,41}}; // 252, 253-255 are reserved - -#define nex(state,sel) State_table[state][sel] - -// The code used to generate the above table at run time (4% slower). -// To print the table, uncomment the 4 lines of print statements below. -// In this code x,y = n0,n1 is the number of 0,1 bits represented by a state. -#else - -class StateTable { - Array ns; // state*4 -> next state if 0, if 1, n0, n1 - enum {B=5, N=64}; // sizes of b, t - static const int b[B]; // x -> max y, y -> max x - static U8 t[N][N][2]; // x,y -> state number, number of states - int num_states(int x, int y); // compute t[x][y][1] - void discount(int& x); // set new value of x after 1 or y after 0 - void next_state(int& x, int& y, int b); // new (x,y) after bit b -public: - int operator()(int state, int sel) {return ns[state*4+sel];} - StateTable(); -} nex; - -const int StateTable::b[B]={42,41,13,6,5}; // x -> max y, y -> max x -U8 StateTable::t[N][N][2]; - -int StateTable::num_states(int x, int y) { - if (x=N || y>=N || y>=B || x>=b[y]) return 0; - - // States 0-30 are a history of the last 0-4 bits - if (x+y<=4) { // x+y choose x = (x+y)!/x!y! - int r=1; - for (int i=x+1; i<=x+y; ++i) r*=i; - for (int i=2; i<=y; ++i) r/=i; - return r; - } - - // States 31-255 represent a 0,1 count and possibly the last bit - // if the state is reachable by either a 0 or 1. - else - return 1+(y>0 && x+y<16); -} - -// New value of count x if the opposite bit is observed -void StateTable::discount(int& x) { - if (x>2) x=ilog(x)/6-1; -} - -// compute next x,y (0 to N) given input b (0 or 1) -void StateTable::next_state(int& x, int& y, int b) { - if (x next if 0, next if 1, x, y -StateTable::StateTable(): ns(1024) { - - // Assign states - int state=0; - for (int i=0; i<256; ++i) { - for (int y=0; y<=i; ++y) { - int x=i-y; - int n=num_states(x, y); - if (n) { - t[x][y][0]=state; - t[x][y][1]=n; - state+=n; - } - } - } - - // Print/generate next state table - state=0; - for (int i=0; i0) ns1+=t[x-1][y+1][1]; - ns[state*4]=ns0; - ns[state*4+1]=ns1; - ns[state*4+2]=x; - ns[state*4+3]=y; - } - else if (t[x][y][1]) { - next_state(x0, y0, 0); - next_state(x1, y1, 1); - ns[state*4]=ns0=t[x0][y0][0]; - ns[state*4+1]=ns1=t[x1][y1][0]+(t[x1][y1][1]>1); - ns[state*4+2]=x; - ns[state*4+3]=y; - } - // uncomment to print table above -// printf("{%3d,%3d,%2d,%2d},", ns[state*4], ns[state*4+1], -// ns[state*4+2], ns[state*4+3]); -// if (state%4==3) printf(" // %d-%d\n ", state-3, state); - assert(state>=0 && state<256); - assert(t[x][y][1]>0); - assert(t[x][y][0]<=state); - assert(t[x][y][0]+t[x][y][1]>state); - assert(t[x][y][1]<=6); - assert(t[x0][y0][1]>0); - assert(t[x1][y1][1]>0); - assert(ns0-t[x0][y0][0]=0); - assert(ns1-t[x1][y1][0]=0); - ++state; - } - } - } -// printf("%d states\n", state); exit(0); // uncomment to print table above -} - -#endif - -///////////////////////////// Squash ////////////////////////////// - -// return p = 1/(1 + exp(-d)), d scaled by 8 bits, p scaled by 12 bits -int squash(int d) { - static const int t[33]={ - 1,2,3,6,10,16,27,45,73,120,194,310,488,747,1101, - 1546,2047,2549,2994,3348,3607,3785,3901,3975,4022, - 4050,4068,4079,4085,4089,4092,4093,4094}; - if (d>2047) return 4095; - if (d<-2047) return 0; - int w=d&127; - d=(d>>7)+16; - return (t[d]*(128-w)+t[(d+1)]*w+64) >> 7; -} - -//////////////////////////// Stretch /////////////////////////////// - -// Inverse of squash. d = ln(p/(1-p)), d scaled by 8 bits, p by 12 bits. -// d has range -2047 to 2047 representing -8 to 8. p has range 0 to 4095. - -class Stretch { - Array t; -public: - Stretch(); - int operator()(int p) const { - assert(p>=0 && p<4096); - return t[p]; - } -} stretch; - -Stretch::Stretch(): t(4096) { - int pi=0; - for (int x=-2047; x<=2047; ++x) { // invert squash() - int i=squash(x); - for (int j=pi; j<=i; ++j) - t[j]=x; - pi=i+1; - } - t[4095]=2047; -} - -//////////////////////////// Mixer ///////////////////////////// - -// Mixer m(N, M, S=1, w=0) combines models using M neural networks with -// N inputs each, of which up to S may be selected. If S > 1 then -// the outputs of these neural networks are combined using another -// neural network (with parameters S, 1, 1). If S = 1 then the -// output is direct. The weights are initially w (+-32K). -// It is used as follows: -// m.update() trains the network where the expected output is the -// last bit (in the global variable y). -// m.add(stretch(p)) inputs prediction from one of N models. The -// prediction should be positive to predict a 1 bit, negative for 0, -// nominally +-256 to +-2K. The maximum allowed value is +-32K but -// using such large values may cause overflow if N is large. -// m.set(cxt, range) selects cxt as one of 'range' neural networks to -// use. 0 <= cxt < range. Should be called up to S times such -// that the total of the ranges is <= M. -// m.p() returns the output prediction that the next bit is 1 as a -// 12 bit number (0 to 4095). - -// dot_product returns dot product t*w of n elements. n is rounded -// up to a multiple of 8. Result is scaled down by 8 bits. -#ifdef NOASM // no assembly language -int dot_product(short *t, short *w, int n) { - int sum=0; - n=(n+7)&-8; - for (int i=0; i> 8; - return sum; -} -#else // The NASM version uses MMX and is about 8 times faster. -extern "C" int dot_product(short *t, short *w, int n); // in NASM -#endif - -// Train neural network weights w[n] given inputs t[n] and err. -// w[i] += t[i]*err, i=0..n-1. t, w, err are signed 16 bits (+- 32K). -// err is scaled 16 bits (representing +- 1/2). w[i] is clamped to +- 32K -// and rounded. n is rounded up to a multiple of 8. -#ifdef NOASM -void train(short *t, short *w, int n, int err) { - n=(n+7)&-8; - for (int i=0; i>16)+1>>1); - if (wt<-32768) wt=-32768; - if (wt>32767) wt=32767; - w[i]=wt; - } -} -#else -extern "C" void train(short *t, short *w, int n, int err); // in NASM -#endif - -class Mixer { - const int N, M, S; // max inputs, max contexts, max context sets - Array tx; // N inputs from add() - Array wx; // N*M weights - Array cxt; // S contexts - int ncxt; // number of contexts (0 to S) - int base; // offset of next context - int nx; // Number of inputs in tx, 0 to N - Array pr; // last result (scaled 12 bits) - Mixer* mp; // points to a Mixer to combine results -public: - Mixer(int n, int m, int s=1, int w=0); - - // Adjust weights to minimize coding cost of last prediction - void update() { - for (int i=0; i=-32768 && err<32768); - train(&tx[0], &wx[cxt[i]*N], nx, err); - } - nx=base=ncxt=0; - } - - // Input x (call up to N times) - void add(int x) { - assert(nx=0); - assert(ncxt=0); - assert(base+cxupdate(); - for (int i=0; i>5); - mp->add(stretch(pr[i])); - } - mp->set(0, 1); - return mp->p(); - } - else { // S=1 context - return pr[0]=squash(dot_product(&tx[0], &wx[0], nx)>>8); - } - } - ~Mixer(); -}; - -Mixer::~Mixer() { - delete mp; -} - - -Mixer::Mixer(int n, int m, int s, int w): - N((n+7)&-8), M(m), S(s), tx(N), wx(N*M), - cxt(S), ncxt(0), base(0), nx(0), pr(S), mp(0) { - assert(n>0 && N>0 && (N&7)==0 && M>0); - for (int i=0; i1) mp=new Mixer(S, 1, 1, 0x7fff); -} - -//////////////////////////// APM ////////////////////////////// - -// APM maps a probability and a context into a new probability -// that bit y will next be 1. After each guess it updates -// its state to improve future guesses. Methods: -// -// APM a(N) creates with N contexts, uses 66*N bytes memory. -// a.p(pr, cx, rate=7) returned adjusted probability in context cx (0 to -// N-1). rate determines the learning rate (smaller = faster, default 7). -// Probabilities are scaled 12 bits (0-4095). - -class APM { - int index; // last p, context - const int N; // number of contexts - Array t; // [N][33]: p, context -> p -public: - APM(int n); - int p(int pr=2048, int cxt=0, int rate=7) { - assert(pr>=0 && pr<4096 && cxt>=0 && cxt0 && rate<32); - pr=stretch(pr); - int g=(y<<16)+(y<> rate; - t[index+1] += g-t[index+1] >> rate; - const int w=pr&127; // interpolation weight (33 points) - index=(pr+2048>>7)+cxt*33; - return t[index]*(128-w)+t[index+1]*w >> 11; - } -}; - -// maps p, cxt -> p initially -APM::APM(int n): index(0), N(n), t(n*33) { - for (int i=0; i probability * 4096 -class StateMap { -protected: - int cxt; // context - Array t; // 256 states -> probability * 64K -public: - StateMap(); - int p(int cx) { - assert(cx>=0 && cx> 8; - return t[cxt=cx] >> 4; - } -}; - -StateMap::StateMap(): cxt(0), t(256) { - for (int i=0; i<256; ++i) { - int n0=nex(i,2); - int n1=nex(i,3); - if (n0==0) n1*=64; - if (n1==0) n0*=64; - t[i] = 65536*(n1+1)/(n0+n1+2); - } -} - -//////////////////////////// hash ////////////////////////////// - -// Hash 2-5 ints. -inline U32 hash(U32 a, U32 b, U32 c=0xffffffff, U32 d=0xffffffff, - U32 e=0xffffffff) { - U32 h=a*200002979u+b*30005491u+c*50004239u+d*70004807u+e*110002499u; - return h^h>>9^a>>2^b>>3^c>>4^d>>5^e>>6; -} - -///////////////////////////// BH //////////////////////////////// - -// A BH maps a 32 bit hash to an array of B bytes (checksum and B-2 values) -// -// BH bh(N); creates N element table with B bytes each. -// N must be a power of 2. The first byte of each element is -// reserved for a checksum to detect collisions. The remaining -// B-1 bytes are values, prioritized by the first value. This -// byte is 0 to mark an unused element. -// -// bh[i] returns a pointer to the i'th element, such that -// bh[i][0] is a checksum of i, bh[i][1] is the priority, and -// bh[i][2..B-1] are other values (0-255). -// The low lg(n) bits as an index into the table. -// If a collision is detected, up to M nearby locations in the same -// cache line are tested and the first matching checksum or -// empty element is returned. -// If no match or empty element is found, then the lowest priority -// element is replaced. - -// 2 byte checksum with LRU replacement (except last 2 by priority) -template class BH { - enum {M=8}; // search limit - Array t; // elements - U32 n; // size-1 -public: - BH(int i): t(i*B), n(i-1) { - assert(B>=2 && i>0 && (i&(i-1))==0); // size a power of 2? - } - U8* operator[](U32 i); -}; - -template -inline U8* BH::operator[](U32 i) { - int chk=(i>>16^i)&0xffff; - i=i*M&n; - U8 *p; - U16 *cp; - int j; - for (j=0; j2 && t[(i+j)*B+2]>t[(i+j-1)*B+2]) --j; - } - else memcpy(tmp, cp, B); - memmove(&t[(i+1)*B], &t[i*B], j*B); - memcpy(&t[i*B], tmp, B); - return &t[i*B+1]; -} - -/////////////////////////// ContextMap ///////////////////////// -// -// A ContextMap maps contexts to a bit histories and makes predictions -// to a Mixer. Methods common to all classes: -// -// ContextMap cm(M, C); creates using about M bytes of memory (a power -// of 2) for C contexts. -// cm.set(cx); sets the next context to cx, called up to C times -// cx is an arbitrary 32 bit value that identifies the context. -// It should be called before predicting the first bit of each byte. -// cm.mix(m) updates Mixer m with the next prediction. Returns 1 -// if context cx is found, else 0. Then it extends all the contexts with -// global bit y. It should be called for every bit: -// -// if (bpos==0) -// for (int i=0; i= 1. Context need not be hashed. - -// Predict to mixer m from bit history state s, using sm to map s to -// a probability. -inline int mix2(Mixer& m, int s, StateMap& sm) { - int p1=sm.p(s); - int n0=-!nex(s,2); - int n1=-!nex(s,3); - int st=stretch(p1)>>2; - m.add(st); - p1>>=4; - int p0=255-p1; - m.add(p1-p0); - m.add(st*(n1-n0)); - m.add((p1&n0)-(p0&n1)); - m.add((p1&n1)-(p0&n0)); - return s>0; -} - -// A RunContextMap maps a context into the next byte and a repeat -// count up to M. Size should be a power of 2. Memory usage is 3M/4. -class RunContextMap { - BH<4> t; - U8* cp; -public: - RunContextMap(int m): t(m/4) {cp=t[0]+1;} - void set(U32 cx) { // update count - if (cp[0]==0 || cp[1]!=buf(1)) cp[0]=1, cp[1]=buf(1); - else if (cp[0]<255) ++cp[0]; - cp=t[cx]+1; - } - int p() { // predict next bit - if (cp[1]+256>>8-bpos==c0) - return ((cp[1]>>7-bpos&1)*2-1)*ilog(cp[0]+1)*8; - else - return 0; - } - int mix(Mixer& m) { // return run length - m.add(p()); - return cp[0]!=0; - } -}; - -// Context is looked up directly. m=size is power of 2 in bytes. -// Context should be < m/512. High bits are discarded. -class SmallStationaryContextMap { - Array t; - int cxt; - U16 *cp; -public: - SmallStationaryContextMap(int m): t(m/2), cxt(0) { - assert((m/2&m/2-1)==0); // power of 2? - for (int i=0; i> rate; - cp=&t[cxt+c0]; - m.add(stretch(*cp>>4)); - } -}; - -// Context map for large contexts. Most modeling uses this type of context -// map. It includes a built in RunContextMap to predict the last byte seen -// in the same context, and also bit-level contexts that map to a bit -// history state. -// -// Bit histories are stored in a hash table. The table is organized into -// 64-byte buckets alinged on cache page boundaries. Each bucket contains -// a hash chain of 7 elements, plus a 2 element queue (packed into 1 byte) -// of the last 2 elements accessed for LRU replacement. Each element has -// a 2 byte checksum for detecting collisions, and an array of 7 bit history -// states indexed by the last 0 to 2 bits of context. The buckets are indexed -// by a context ending after 0, 2, or 5 bits of the current byte. Thus, each -// byte modeled results in 3 main memory accesses per context, with all other -// accesses to cache. -// -// On bits 0, 2 and 5, the context is updated and a new bucket is selected. -// The most recently accessed element is tried first, by comparing the -// 16 bit checksum, then the 7 elements are searched linearly. If no match -// is found, then the element with the lowest priority among the 5 elements -// not in the LRU queue is replaced. After a replacement, the queue is -// emptied (so that consecutive misses favor a LFU replacement policy). -// In all cases, the found/replaced element is put in the front of the queue. -// -// The priority is the state number of the first element (the one with 0 -// additional bits of context). The states are sorted by increasing n0+n1 -// (number of bits seen), implementing a LFU replacement policy. -// -// When the context ends on a byte boundary (bit 0), only 3 of the 7 bit -// history states are used. The remaining 4 bytes implement a run model -// as follows: where is the last byte -// seen, possibly repeated. is a 7 bit count and a 1 bit -// flag (represented by count * 2 + d). If d=0 then = 1..127 is the -// number of repeats of and no other bytes have been seen. If d is 1 then -// other byte values have been seen in this context prior to the last -// copies of . -// -// As an optimization, the last two hash elements of each byte (representing -// contexts with 2-7 bits) are not updated until a context is seen for -// a second time. This is indicated by = <1,0> (2). After update, -// is updated to <2,0> or <1,1> (4 or 3). - -class ContextMap { - const int C; // max number of contexts - class E { // hash element, 64 bytes - U16 chk[7]; // byte context checksums - U8 last; // last 2 accesses (0-6) in low, high nibble - public: - U8 bh[7][7]; // byte context, 3-bit context -> bit history state - // bh[][0] = 1st bit, bh[][1,2] = 2nd bit, bh[][3..6] = 3rd bit - // bh[][0] is also a replacement priority, 0 = empty - U8* get(U16 chk); // Find element (0-6) matching checksum. - // If not found, insert or replace lowest priority (not last). - }; - Array t; // bit histories for bits 0-1, 2-4, 5-7 - // For 0-1, also contains a run count in bh[][4] and value in bh[][5] - // and pending update count in bh[7] - Array cp; // C pointers to current bit history - Array cp0; // First element of 7 element array containing cp[i] - Array cxt; // C whole byte contexts (hashes) - Array runp; // C [0..3] = count, value, unused, unused - StateMap *sm; // C maps of state -> p - int cn; // Next context to set by set() - void update(U32 cx, int c); // train model that context cx predicts c - int mix1(Mixer& m, int cc, int bp, int c1, int y1); - // mix() with global context passed as arguments to improve speed. -public: - ContextMap(int m, int c=1); // m = memory in bytes, a power of 2, C = c - ~ContextMap(); - void set(U32 cx, int next=-1); // set next whole byte context to cx - // if next is 0 then set order does not matter - int mix(Mixer& m) {return mix1(m, c0, bpos, buf(1), y);} -}; - -// Find or create hash element matching checksum ch -inline U8* ContextMap::E::get(U16 ch) { - if (chk[last&15]==ch) return &bh[last&15][0]; - int b=0xffff, bi=0; - for (int i=0; i<7; ++i) { - if (chk[i]==ch) return last=last<<4|i, &bh[i][0]; - int pri=bh[i][0]; - if ((last&15)!=i && last>>4!=i && pri>6), cp(c), cp0(c), - cxt(c), runp(c), cn(0) { - assert(m>=64 && (m&m-1)==0); // power of 2? - assert(sizeof(E)==64); - sm=new StateMap[C]; - for (int i=0; i=0 && i>16; - cxt[i]=cx*123456791+i; -} - -// Update the model with bit y1, and predict next bit to mixer m. -// Context: cc=c0, bp=bpos, c1=buf(1), y1=y. -int ContextMap::mix1(Mixer& m, int cc, int bp, int c1, int y1) { - - // Update model with y - int result=0; - for (int i=0; i=&t[0].bh[0][0] && cp[i]<=&t[t.size()-1].bh[6][6]); - assert((long(cp[i])&63)>=15); - int ns=nex(*cp[i], y1); - if (ns>=204 && rnd() << (452-ns>>3)) ns-=4; // probabilistic increment - *cp[i]=ns; - } - - // Update context pointers - if (bpos>1 && runp[i][0]==0) - cp[i]=0; - else if (bpos==1||bpos==3||bpos==6) - cp[i]=cp0[i]+1+(cc&1); - else if (bpos==4||bpos==7) - cp[i]=cp0[i]+3+(cc&3); - else { - cp0[i]=cp[i]=t[cxt[i]+cc&t.size()-1].get(cxt[i]>>16); - - // Update pending bit histories for bits 2-7 - if (bpos==0) { - if (cp0[i][3]==2) { - const int c=cp0[i][4]+256; - U8 *p=t[cxt[i]+(c>>6)&t.size()-1].get(cxt[i]>>16); - p[0]=1+((c>>5)&1); - p[1+((c>>5)&1)]=1+((c>>4)&1); - p[3+((c>>4)&3)]=1+((c>>3)&1); - p=t[cxt[i]+(c>>3)&t.size()-1].get(cxt[i]>>16); - p[0]=1+((c>>2)&1); - p[1+((c>>2)&1)]=1+((c>>1)&1); - p[3+((c>>1)&3)]=1+(c&1); - cp0[i][6]=0; - } - // Update run count of previous context - if (runp[i][0]==0) // new context - runp[i][0]=2, runp[i][1]=c1; - else if (runp[i][1]!=c1) // different byte in context - runp[i][0]=1, runp[i][1]=c1; - else if (runp[i][0]<254) // same byte in context - runp[i][0]+=2; - else if (runp[i][0]==255) - runp[i][0]=128; - runp[i]=cp0[i]+3; - } - } - - // predict from last byte in context - int rc=runp[i][0]; // count*2, +1 if 2 different bytes seen - if (runp[i][1]+256>>8-bp==cc) { - int b=(runp[i][1]>>7-bp&1)*2-1; // predicted bit + for 1, - for 0 - int c=ilog(rc+1)<<2+(~rc&1); - m.add(b*c); - } - else - m.add(0); - - // predict from bit context - result+=mix2(m, cp[i] ? *cp[i] : 0, sm[i]); - } - if (bp==7) cn=0; - return result; -} - -//////////////////////////// Models ////////////////////////////// - -// All of the models below take a Mixer as a parameter and write -// predictions to it. - -//////////////////////////// matchModel /////////////////////////// - -// matchModel() finds the longest matching context and returns its length - -int matchModel(Mixer& m) { - const int MAXLEN=65534; // longest allowed match + 1 - static Array t(MEM); // hash table of pointers to contexts - static int h=0; // hash of last 7 bytes - static int ptr=0; // points to next byte of match if any - static int len=0; // length of match, or 0 if no match - static int result=0; - - static SmallStationaryContextMap scm1(0x20000); - - if (!bpos) { - h=h*997*8+buf(1)+1&t.size()-1; // update context hash - if (len) ++len, ++ptr; - else { // find match - ptr=t[h]; - if (ptr && pos-ptr0 && !(result&0xfff)) printf("pos=%d len=%d ptr=%d\n", pos, len, ptr); - scm1.set(pos); - } - - // predict - if (len>MAXLEN) len=MAXLEN; - int sgn; - if (len && buf(1)==buf[ptr-1] && c0==buf[ptr]+256>>8-bpos) { - if (buf[ptr]>>7-bpos&1) sgn=1; - else sgn=-1; - } - else sgn=len=0; - m.add(sgn*4*ilog(len)); - m.add(sgn*64*min(len, 32)); - scm1.mix(m); - return result; -} - -//////////////////////////// picModel ////////////////////////// - -// Model a 1728 by 2376 2-color CCITT bitmap image, left to right scan, -// MSB first (216 bytes per row, 513216 bytes total). Insert predictions -// into m. - -void picModel(Mixer& m) { - static U32 r0, r1, r2, r3; // last 4 rows, bit 8 is over current pixel - static Array t(0x10200); // model: cxt -> state - const int N=3; // number of contexts - static int cxt[N]; // contexts - static StateMap sm[N]; - - // update the model - for (int i=0; i>(7-bpos))&1); - r2+=r2+((buf(431)>>(7-bpos))&1); - r3+=r3+((buf(647)>>(7-bpos))&1); - cxt[0]=r0&0x7|r1>>4&0x38|r2>>3&0xc0; - cxt[1]=0x100+(r0&1|r1>>4&0x3e|r2>>2&0x40|r3>>1&0x80); - cxt[2]=0x200+(r0&0x3f^r1&0x3ffe^r2<<2&0x7f00^r3<<5&0xf800); - - // predict - for (int i=0; i='A' && c<='Z') - c+='a'-'A'; - if (c>='a' && c<='z' || c>=128) { - word0=word0*263*32+c; - text0=text0*997*16+c; - } - else if (word0) { - word5=word4*23; - word4=word3*19; - word3=word2*17; - word2=word1*13; - word1=word0*11; - word0=0; - } - if (c==10) nl1=nl, nl=pos-1; - int col=min(255, pos-nl), above=buf[nl1+col]; // text column context - U32 h=word0*271+buf(1); - - cm.set(h); - cm.set(word0); - cm.set(h+word1); - cm.set(word0+word1*31); - cm.set(h+word1+word2*29); - cm.set(text0&0xffffff); - cm.set(text0&0xfffff); - - cm.set(h+word2); - cm.set(h+word3); - cm.set(h+word4); - cm.set(h+word5); - cm.set(buf(1)|buf(3)<<8|buf(5)<<16); - cm.set(buf(2)|buf(4)<<8|buf(6)<<16); - - cm.set(h+word1+word3); - cm.set(h+word2+word3); - - // Text column models - cm.set(col<<16|buf(1)<<8|above); - cm.set(buf(1)<<8|above); - cm.set(col<<8|buf(1)); - cm.set(col); - } - cm.mix(m); -} - -//////////////////////////// recordModel /////////////////////// - -// Model 2-D data with fixed record length. Also order 1-2 models -// that include the distance to the last match. - -void recordModel(Mixer& m) { - static int cpos1[256] , cpos2[256], cpos3[256], cpos4[256]; - static int wpos1[0x10000]; // buf(1..2) -> last position - static int rlen=2, rlen1=3, rlen2=4; // run length and 2 candidates - static int rcount1=0, rcount2=0; // candidate counts - static ContextMap cm(32768, 3), cn(32768/2, 3), co(32768*2, 3), cp(MEM, 3); - - // Find record length - if (!bpos) { - int w=c4&0xffff, c=w&255, d=w>>8; -#if 1 - int r=pos-cpos1[c]; - if (r>1 && r==cpos1[c]-cpos2[c] - && r==cpos2[c]-cpos3[c] && r==cpos3[c]-cpos4[c] - && (r>15 || (c==buf(r*5+1)) && c==buf(r*6+1))) { - if (r==rlen1) ++rcount1; - else if (r==rlen2) ++rcount2; - else if (rcount1>rcount2) rlen2=r, rcount2=1; - else rlen1=r, rcount1=1; - } - if (rcount1>15 && rlen!=rlen1) rlen=rlen1, rcount1=rcount2=0; - if (rcount2>15 && rlen!=rlen2) rlen=rlen2, rcount1=rcount2=0; - - // Set 2 dimensional contexts - assert(rlen>0); -#endif - cm.set(c<<8| (min(255, pos-cpos1[c])/4) ); - cm.set(w<<9| llog(pos-wpos1[w])>>2); - - cm.set(rlen|buf(rlen)<<10|buf(rlen*2)<<18); - cn.set(w|rlen<<8); - cn.set(d|rlen<<16); - cn.set(c|rlen<<8); - - co.set(buf(1)<<8|min(255, pos-cpos1[buf(1)])); - co.set(buf(1)<<17|buf(2)<<9|llog(pos-wpos1[w])>>2); - int col=pos%rlen; - co.set(buf(1)<<8|buf(rlen)); - - //cp.set(w*16); - //cp.set(d*32); - //cp.set(c*64); - cp.set(rlen|buf(rlen)<<10|col<<18); - cp.set(rlen|buf(1)<<10|col<<18); - cp.set(col|rlen<<12); - - // update last context positions - cpos4[c]=cpos3[c]; - cpos3[c]=cpos2[c]; - cpos2[c]=cpos1[c]; - cpos1[c]=pos; - wpos1[w]=pos; - } - cm.mix(m); - cn.mix(m); - co.mix(m); - cp.mix(m); -} - - -//////////////////////////// sparseModel /////////////////////// - -// Model order 1-2 contexts with gaps. - -void sparseModel(Mixer& m, int seenbefore, int howmany) { - static ContextMap cm(MEM*2, 48); - static int mask = 0; - - if (bpos==0) { - - cm.set( c4&0x00f0f0f0); - cm.set((c4&0xf0f0f0f0)+1); - cm.set((c4&0x00f8f8f8)+2); - cm.set((c4&0xf8f8f8f8)+3); - cm.set((c4&0x00e0e0e0)+4); - cm.set((c4&0xe0e0e0e0)+5); - cm.set((c4&0x00f0f0ff)+6); - - cm.set(seenbefore); - cm.set(howmany); - cm.set(c4&0x00ff00ff); - cm.set(c4&0xff0000ff); - cm.set(buf(1)|buf(5)<<8); - cm.set(buf(1)|buf(6)<<8); - cm.set(buf(3)|buf(6)<<8); - cm.set(buf(4)|buf(8)<<8); - - for (int i=1; i<8; ++i) { - cm.set((buf(i+1)<<8)|buf(i+2)); - cm.set((buf(i+1)<<8)|buf(i+3)); - cm.set(seenbefore|buf(i)<<8); - } - - int fl = 0; - if( c4&0xff != 0 ){ - if( isalpha( c4&0xff ) ) fl = 1; - else if( ispunct( c4&0xff ) ) fl = 2; - else if( isspace( c4&0xff ) ) fl = 3; - else if( c4&0xff == 0xff ) fl = 4; - else if( c4&0xff < 16 ) fl = 5; - else if( c4&0xff < 64 ) fl = 6; - else fl = 7; - } - mask = (mask<<3)|fl; - cm.set(mask); - cm.set(mask<<8|buf(1)); - cm.set(mask<<17|buf(2)<<8|buf(3)); - cm.set(mask&0x1ff|((c4&0xf0f0f0f0)<<9)); - } - cm.mix(m); -} - -//////////////////////////// distanceModel /////////////////////// - -// Model for modelling distances between symbols - -void distanceModel(Mixer& m) { - static ContextMap cr(MEM, 3); - if( bpos == 0 ){ - static int pos00=0,pos20=0,posnl=0; - int c=c4&0xff; - if(c==0x00)pos00=pos; - if(c==0x20)pos20=pos; - if(c==0xff||c=='\r'||c=='\n')posnl=pos; - cr.set(min(pos-pos00,255)|(c<<8)); - cr.set(min(pos-pos20,255)|(c<<8)); - cr.set(min(pos-posnl,255)|(c<<8)+234567); - } - cr.mix(m); -} - -//////////////////////////// bmpModel ///////////////////////////////// - -// Model a 24-bit color uncompressed .bmp or .tif file. Return -// width in pixels if an image file is detected, else 0. - -// 32-bit little endian number at buf(i)..buf(i-3) -inline U32 i4(int i) { - assert(i>3); - return buf(i)+256*buf(i-1)+65536*buf(i-2)+16777216*buf(i-3); -} - -// 16-bit -inline int i2(int i) { - assert(i>1); - return buf(i)+256*buf(i-1); -} - -// Square buf(i) -inline int sqrbuf(int i) { - assert(i>0); - return buf(i)*buf(i); -} - -int bmpModel(Mixer& m) { - static int w=0; // width of image in bytes (pixels * 3) - static int eoi=0; // end of image - static U32 tiff=0; // offset of tif header - const int SC=0x20000; - static SmallStationaryContextMap scm1(SC), scm2(SC), - scm3(SC), scm4(SC), scm5(SC), scm6(SC*2); - static ContextMap cm(MEM*4, 8); - - // Detect .bmp file header (24 bit color, not compressed) - if (!bpos && buf(54)=='B' && buf(53)=='M' - && i4(44)==54 && i4(40)==40 && i4(24)==0) { - w=(i4(36)+3&-4)*3; // image width - const int height=i4(32); - eoi=pos; - if (w<0x30000 && height<0x10000) { - eoi=pos+w*height; // image size in bytes - printf("BMP %dx%d ", w/3, height); - } - else - eoi=pos; - } - - // Detect .tif file header (24 bit color, not compressed). - // Parsing is crude, won't work with weird formats. - if (!bpos) { - if (c4==0x49492a00) tiff=pos; // Intel format only - if (pos-tiff==4 && c4!=0x08000000) tiff=0; // 8=normal offset to directory - if (tiff && pos-tiff==200) { // most of directory should be read by now - int dirsize=i2(pos-tiff-4); // number of 12-byte directory entries - w=0; - int bpp=0, compression=0, width=0, height=0; - for (int i=tiff+6; i0; i+=12) { - int tag=i2(pos-i); // 256=width, 257==height, 259: 1=no compression - // 277=3 samples/pixel - int tagfmt=i2(pos-i-2); // 3=short, 4=long - int taglen=i4(pos-i-4); // number of elements in tagval - int tagval=i4(pos-i-8); // 1 long, 1-2 short, or points to array - if ((tagfmt==3||tagfmt==4) && taglen==1) { - if (tag==256) width=tagval; - if (tag==257) height=tagval; - if (tag==259) compression=tagval; // 1 = no compression - if (tag==277) bpp=tagval; // should be 3 - } - } - if (width>0 && height>0 && width*height>50 && compression==1 - && (bpp==1||bpp==3)) - eoi=tiff+width*height*bpp, w=width*bpp; - if (eoi>pos) - printf("TIFF %dx%dx%d ", width, height, bpp); - else - tiff=w=0; - } - } - if (pos>eoi) return w=0; - - // Select nearby pixels as context - if (!bpos) { - assert(w>3); - int color=pos%3; - int mean=buf(3)+buf(w-3)+buf(w)+buf(w+3); - const int var=sqrbuf(3)+sqrbuf(w-3)+sqrbuf(w)+sqrbuf(w+3)-mean*mean/4>>2; - mean>>=2; - const int logvar=ilog(var); - int i=0; - cm.set(hash(++i, buf(3)>>2, buf(w)>>2, color)); - cm.set(hash(++i, buf(3)>>2, buf(1)>>2, color)); - cm.set(hash(++i, buf(3)>>2, buf(2)>>2, color)); - cm.set(hash(++i, buf(w)>>2, buf(1)>>2, color)); - cm.set(hash(++i, buf(w)>>2, buf(2)>>2, color)); - cm.set(hash(++i, buf(3)+buf(w)>>1, color)); - cm.set(hash(++i, buf(3)+buf(w)>>3, buf(1)>>5, buf(2)>>5, color)); - cm.set(hash(++i, mean, logvar>>5, color)); - scm1.set(buf(3)+buf(w)>>1); - scm2.set(buf(3)+buf(w)-buf(w+3)>>1); - scm3.set(buf(3)*2-buf(6)>>1); - scm4.set(buf(w)*2-buf(w*2)>>1); - scm5.set(buf(3)+buf(w)-buf(w-3)>>1); - scm6.set(mean>>1|logvar<<1&0x180); - } - - // Predict next bit - scm1.mix(m); - scm2.mix(m); - scm3.mix(m); - scm4.mix(m); - scm5.mix(m); - scm6.mix(m); - cm.mix(m); - return w; -} - -//////////////////////////// jpegModel ///////////////////////// - -// Model JPEG. Return 1 if a JPEG file is detected or else 0. -// Only the baseline and 8 bit extended Huffman coded DCT modes are -// supported. The model partially decodes the JPEG image to provide -// context for the Huffman coded symbols. - -// Print a JPEG segment at buf[p...] for debugging -void dump(const char* msg, int p) { - printf("%s:", msg); - int len=buf[p+2]*256+buf[p+3]; - for (int i=0; i ht(8); // pointers to Huffman table headers - static int htsize=0; // number of pointers in ht - - // Huffman decode state - static U32 huffcode=0; // Current Huffman code including extra bits - static int huffbits=0; // Number of valid bits in huffcode - static int huffsize=0; // Number of bits without extra bits - static int rs=-1; // Decoded huffcode without extra bits. It represents - // 2 packed 4-bit numbers, r=run of zeros, s=number of extra bits for - // first nonzero code. huffcode is complete when rs >= 0. - // rs is -1 prior to decoding incomplete huffcode. - static int mcupos=0; // position in MCU (0-639). The low 6 bits mark - // the coefficient in zigzag scan order (0=DC, 1-63=AC). The high - // bits mark the block within the MCU, used to select Huffman tables. - - // Decoding tables - static Array huf(128); // Tc*64+Th*16+m -> min, max, val - static int mcusize=0; // number of coefficients in an MCU - static int linesize=0; // width of image in MCU - static int hufsel[2][10]; // DC/AC, mcupos/64 -> huf decode table - static Array hbuf(2048); // Tc*1024+Th*256+hufcode -> RS - - // Image state - static Array color(10); // block -> component (0-3) - static Array pred(4); // component -> last DC value - static int dc=0; // DC value of the current block - static int width=0; // Image width in MCU - static int row=0, column=0; // in MCU (column 0 to width-1) - static Buf cbuf(0x20000); // Rotating buffer of coefficients, coded as: - // DC: level shifted absolute value, low 4 bits discarded, i.e. - // [-1023...1024] -> [0...255]. - // AC: as an RS code: a run of R (0-15) zeros followed by an S (0-15) - // bit number, or 00 for end of block (in zigzag order). - // However if R=0, then the format is ssss11xx where ssss is S, - // xx is the first 2 extra bits, and the last 2 bits are 1 (since - // this never occurs in a valid RS code). - static int cpos=0; // position in cbuf - static U32 huff1=0, huff2=0, huff3=0, huff4=0; // hashes of last codes - static int rs1, rs2, rs3, rs4; // last 4 RS codes - static int ssum=0, ssum1=0, ssum2=0, ssum3=0, ssum4=0; - // sum of S in RS codes in block and last 4 values - - // Be sure to quit on a byte boundary - if (!bpos) next_jpeg=jpeg>1; - if (bpos && !jpeg) return next_jpeg; - if (!bpos && app>0) --app; - if (app>0) return next_jpeg; - if (!bpos) { - - // Parse. Baseline DCT-Huffman JPEG syntax is: - // SOI APPx... misc... SOF0 DHT... SOS data EOI - // SOI (= FF D8) start of image. - // APPx (= FF Ex) len ... where len is always a 2 byte big-endian length - // including the length itself but not the 2 byte preceding code. - // Application data is ignored. There may be more than one APPx. - // misc codes are DQT, DNL, DRI, COM (ignored). - // SOF0 (= FF C0) len 08 height width Nf [C HV Tq]... - // where len, height, width (in pixels) are 2 bytes, Nf is the repeat - // count (1 byte) of [C HV Tq], where C is a component identifier - // (color, 0-3), HV is the horizontal and vertical dimensions - // of the MCU (high, low bits, packed), and Tq is the quantization - // table ID (not used). An MCU (minimum compression unit) consists - // of 64*H*V DCT coefficients for each color. - // DHT (= FF C4) len [TcTh L1...L16 V1,1..V1,L1 ... V16,1..V16,L16]... - // defines Huffman table Th (1-4) for Tc (0=DC (first coefficient) - // 1=AC (next 63 coefficients)). L1..L16 are the number of codes - // of length 1-16 (in ascending order) and Vx,y are the 8-bit values. - // A V code of RS means a run of R (0-15) zeros followed by S (0-15) - // additional bits to specify the next nonzero value, negative if - // the first additional bit is 0 (e.g. code x63 followed by the - // 3 bits 1,0,1 specify 7 coefficients: 0, 0, 0, 0, 0, 0, 5. - // Code 00 means end of block (remainder of 63 AC coefficients is 0). - // SOS (= FF DA) len Ns [Cs TdTa]... 0 3F 00 - // Start of scan. TdTa specifies DC/AC Huffman tables (0-3, packed - // into one byte) for component Cs matching C in SOF0, repeated - // Ns (1-4) times. - // EOI (= FF D9) is end of image. - // Huffman coded data is between SOI and EOI. Codes may be embedded: - // RST0-RST7 (= FF D0 to FF D7) mark the start of an independently - // compressed region. - // DNL (= FF DC) 04 00 height - // might appear at the end of the scan (ignored). - // FF 00 is interpreted as FF (to distinguish from RSTx, DNL, EOI). - - // Detect JPEG (SOI, APPx) - if (!jpeg && buf(4)==FF && buf(3)==SOI && buf(2)==FF && buf(1)>>4==0xe) { - jpeg=1; - app=sos=sof=htsize=data=mcusize=linesize=0; - huffcode=huffbits=huffsize=mcupos=cpos=0, rs=-1; - memset(&huf[0], 0, huf.size()*sizeof(HUF)); - memset(&pred[0], 0, pred.size()*sizeof(int)); - } - - // Detect end of JPEG when data contains a marker other than RSTx - // or byte stuff (00). - if (jpeg && data && buf(2)==FF && buf(1) && (buf(1)&0xf8)!=RST0) { - jassert(buf(1)==EOI); - jpeg=0; - } - if (!jpeg) return next_jpeg; - - // Detect APPx or COM field - if (!data && !app && buf(4)==FF && (buf(3)>>4==0xe || buf(3)==COM)) - app=buf(2)*256+buf(1)+2; - - // Save pointers to sof, ht, sos, data, - if (buf(5)==FF && buf(4)==SOS) { - int len=buf(3)*256+buf(2); - if (len==6+2*buf(1) && buf(1) && buf(1)<=4) // buf(1) is Ns - sos=pos-5, data=sos+len+2, jpeg=2; - } - if (buf(4)==FF && buf(3)==DHT && htsize<8) ht[htsize++]=pos-4; - if (buf(4)==FF && buf(3)==SOF0) sof=pos-4; - - // Restart - if (buf(2)==FF && (buf(1)&0xf8)==RST0) { - huffcode=huffbits=huffsize=mcupos=0, rs=-1; - memset(&pred[0], 0, pred.size()*sizeof(int)); - } - } - - { - // Build Huffman tables - // huf[Tc][Th][m] = min, max+1 codes of length m, pointer to byte values - if (pos==data && bpos==1) { - jassert(htsize>0); - for (int i=0; i>4, th=buf[p]&15; - if (tc>=2 || th>=4) break; - jassert(tc>=0 && tc<2 && th>=0 && th<4); - HUF* h=&huf[tc*64+th*16]; // [tc][th][0]; - int val=p+17; // pointer to values - int hval=tc*1024+th*256; // pointer to RS values in hbuf - for (int j=0; j<256; ++j) // copy RS codes - hbuf[hval+j]=buf[val+j]; - int code=0; - for (int j=0; j<16; ++j) { - h[j].min=code; - h[j].max=code+=buf[p+j+1]; - h[j].val=hval; - val+=buf[p+j+1]; - hval+=buf[p+j+1]; - code*=2; - } - p=val; - jassert(hval>=0 && hval<2048); - } - jassert(p==end); - } - huffcode=huffbits=huffsize=0, rs=-1; - - // Build Huffman table selection table (indexed by mcupos). - // Get image width. - if (!sof && sos) return next_jpeg; - int ns=buf[sos+4]; - int nf=buf[sof+9]; - jassert(ns<=4 && nf<=4); - mcusize=0; // blocks per MCU - int hmax=0; // MCU horizontal dimension - for (int i=0; i>4>hmax) hmax=hv>>4; - hv=(hv&15)*(hv>>4); // number of blocks in component C - jassert(hv>=1 && hv+mcusize<=10); - while (hv) { - jassert(mcusize<10); - hufsel[0][mcusize]=buf[sos+2*i+6]>>4&15; - hufsel[1][mcusize]=buf[sos+2*i+6]&15; - jassert (hufsel[0][mcusize]<4 && hufsel[1][mcusize]<4); - color[mcusize]=i; - --hv; - ++mcusize; - } - } - } - } - jassert(hmax>=1 && hmax<=10); - width=buf[sof+7]*256+buf[sof+8]; // in pixels - int height=buf[sof+5]*256+buf[sof+6]; - printf("JPEG %dx%d ", width, height); - width=(width-1)/(hmax*8)+1; // in MCU - jassert(width>0); - mcusize*=64; // coefficients per MCU - row=column=0; - } - } - - - // Decode Huffman - { - if (mcusize && buf(1+(!bpos))!=FF) { // skip stuffed byte - jassert(huffbits<=32); - huffcode+=huffcode+y; - ++huffbits; - if (rs<0) { - jassert(huffbits>=1 && huffbits<=16); - const int ac=(mcupos&63)>0; - jassert(mcupos>=0 && (mcupos>>6)<10); - jassert(ac==0 || ac==1); - const int sel=hufsel[ac][mcupos>>6]; - jassert(sel>=0 && sel<4); - const int i=huffbits-1; - jassert(i>=0 && i<16); - const HUF *h=&huf[ac*64+sel*16]; // [ac][sel]; - jassert(h[i].min<=h[i].max && h[i].val<2048 && huffbits>0); - if (huffcode=h[i].min); - int k=h[i].val+huffcode-h[i].min; - jassert(k>=0 && k<2048); - rs=hbuf[k]; - huffsize=huffbits; - } - } - if (rs>=0) { - if (huffsize+(rs&15)==huffbits) { // done decoding - huff4=huff3; - huff3=huff2; - huff2=huff1; - huff1=hash(huffcode, huffbits); - rs4=rs3; - rs3=rs2; - rs2=rs1; - rs1=rs; - int x=0; // decoded extra bits - if (mcupos&63) { // AC - if (rs==0) { // EOB - mcupos=mcupos+63&-64; - jassert(mcupos>=0 && mcupos<=mcusize && mcupos<=640); - while (cpos&63) cbuf[cpos++]=0; - } - else { // rs = r zeros + s extra bits for the next nonzero value - // If first extra bit is 0 then value is negative. - jassert((rs&15)<=10); - const int r=rs>>4; - const int s=rs&15; - jassert(mcupos>>6==mcupos+r>>6); - mcupos+=r+1; - x=huffcode&(1<>s-1)) x-=(1<=1; --i) cbuf[cpos++]=i<<4|s; - cbuf[cpos++]=s<<4|huffcode<<2>>s&3|12; - ssum+=s; - } - } - else { // DC: rs = 0S, s<12 - jassert(rs<12); - ++mcupos; - x=huffcode&(1<>rs-1)) x-=(1<=0 && mcupos>>6<10); - const int comp=color[mcupos>>6]; - jassert(comp>=0 && comp<4); - dc=pred[comp]+=x; - jassert((cpos&63)==0); - cbuf[cpos++]=dc+1023>>3; - ssum4=ssum3; - ssum3=ssum2; - ssum2=ssum1; - ssum1=ssum; - ssum=rs; - } - jassert(mcupos>=0 && mcupos<=mcusize); - if (mcupos>=mcusize) { - mcupos=0; - if (++column==width) column=0, ++row; - } - huffcode=huffsize=huffbits=0, rs=-1; - } - } - } - } - - // Estimate next bit probability - if (!jpeg || !data) return next_jpeg; - - // Context model - const int N=19; // size of t, number of contexts - static BH<9> t(MEM); // context hash -> bit history - // As a cache optimization, the context does not include the last 1-2 - // bits of huffcode if the length (huffbits) is not a multiple of 3. - // The 7 mapped values are for context+{"", 0, 00, 01, 1, 10, 11}. - static Array cxt(N); // context hashes - static Array cp(N); // context pointers - static StateMap sm[N]; - static Mixer m1(32, 800, 4); - static APM a1(1024), a2(0x10000); - const static U8 zzu[64]={ // zigzag coef -> u,v - 0,1,0,0,1,2,3,2,1,0,0,1,2,3,4,5,4,3,2,1,0,0,1,2,3,4,5,6,7,6,5,4, - 3,2,1,0,1,2,3,4,5,6,7,7,6,5,4,3,2,3,4,5,6,7,7,6,5,4,5,6,7,7,6,7}; - const static U8 zzv[64]={ - 0,0,1,2,1,0,0,1,2,3,4,3,2,1,0,0,1,2,3,4,5,6,5,4,3,2,1,0,0,1,2,3, - 4,5,6,7,7,6,5,4,3,2,1,2,3,4,5,6,7,7,6,5,4,3,4,5,6,7,7,6,5,6,7,7}; - - - // Update model - if (cp[N-1]) { - for (int i=0; i>6]; - const int coef=(mcupos&63)|comp<<6; - const int hc=huffcode|1<2 || huffbits==0) hbcount=0; - jassert(coef>=0 && coef<256); - const int zu=zzu[mcupos&63], zv=zzv[mcupos&63]; - if (hbcount==0) { - const int mpos=mcupos>>4|!(mcupos&-64)<<7; - int n=0; - cxt[0]=hash(++n, hc, mcupos>>2, min(3, mcupos&63)); - cxt[1]=hash(++n, hc, mpos>>4, cbuf[cpos-mcusize]); - cxt[2]=hash(++n, hc, mpos>>4, cbuf[cpos-width*mcusize]); - cxt[3]=hash(++n, hc, ilog(ssum3), coef); - cxt[4]=hash(++n, hc, coef, column>>3); - cxt[5]=hash(++n, hc, coef, column>>1); - cxt[6]=hash(++n, hc, rs1, mpos); - cxt[7]=hash(++n, hc, rs1, rs2); - cxt[8]=hash(++n, hc, rs1, rs2, rs3); - cxt[9]=hash(++n, hc, ssum>>4, mcupos); - cxt[10]=hash(++n, hc, mpos, cbuf[cpos-1]); - cxt[11]=hash(++n, hc, dc); - cxt[12]=hash(++n, hc, rs1, coef); - cxt[13]=hash(++n, hc, rs1, rs2, coef); - cxt[14]=hash(++n, hc, mcupos>>3, ssum3>>3); - cxt[15]=hash(++n, hc, huff1); - cxt[16]=hash(++n, hc, coef, huff1); - cxt[17]=hash(++n, hc, zu, comp); - cxt[18]=hash(++n, hc, zv, comp); - } - - // Predict next bit - m1.add(128); - assert(hbcount<=2); - for (int i=0; i4))); - } - cm.mix(m); -} - -//////////////////////////// indirectModel ///////////////////// - -// The context is a byte string history that occurs within a -// 1 or 2 byte context. - -void indirectModel(Mixer& m) { - static ContextMap cm(MEM, 6); - static U32 t1[256]; - static U16 t2[0x10000]; - - if (!bpos) { - U32 d=c4&0xffff, c=d&255; - U32& r1=t1[d>>8]; - r1=r1<<8|c; - U16& r2=t2[c4>>8&0xffff]; - r2=r2<<8|c; - U32 t=c|t1[c]<<8; - cm.set(t&0xffff); - cm.set(t&0xffffff); - cm.set(t); - cm.set(t&0xff00); - t=d|t2[d]<<16; - cm.set(t&0xffffff); - cm.set(t); - - } - cm.mix(m); -} - -//////////////////////////// dmcModel ////////////////////////// - -// Model using DMC. The bitwise context is represented by a state graph, -// initilaized to a bytewise order 1 model as in -// http://plg.uwaterloo.ca/~ftp/dmc/dmc.c but with the following difference: -// - It uses integer arithmetic. -// - The threshold for cloning a state increases as memory is used up. -// - Each state maintains both a 0,1 count and a bit history (as in a -// context model). The 0,1 count is best for stationary data, and the -// bit history for nonstationary data. The bit history is mapped to -// a probability adaptively using a StateMap. The two computed probabilities -// are combined. -// - When memory is used up the state graph is reinitialized to a bytewise -// order 1 context as in the original DMC. However, the bit histories -// are not cleared. - -struct DMCNode { // 12 bytes - unsigned int nx[2]; // next pointers - U8 state; // bit history - unsigned int c0:12, c1:12; // counts * 256 -}; - -void dmcModel(Mixer& m) { - static int top=0, curr=0; // allocated, current node - static Array t(MEM*2); // state graph - static StateMap sm; - static int threshold=256; - - // clone next state - if (top>0 && top=threshold*2 && nn-n>=threshold*3) { - int r=n*4096/nn; - assert(r>=0 && r<=4096); - t[next].c0 -= t[top].c0 = t[next].c0*r>>12; - t[next].c1 -= t[top].c1 = t[next].c1*r>>12; - t[top].nx[0]=t[next].nx[0]; - t[top].nx[1]=t[next].nx[1]; - t[top].state=t[next].state; - t[curr].nx[y]=top; - ++top; - if (top==MEM*2) threshold=512; - if (top==MEM*3) threshold=768; - } - } - - // Initialize to a bytewise order 1 model at startup or when flushing memory - if (top==t.size() && bpos==1) top=0; - if (top==0) { - assert(t.size()>=65536); - for (int i=0; i<256; ++i) { - for (int j=0; j<256; ++j) { - if (i<127) { - t[j*256+i].nx[0]=j*256+i*2+1; - t[j*256+i].nx[1]=j*256+i*2+2; - } - else { - t[j*256+i].nx[0]=(i-127)*256; - t[j*256+i].nx[1]=(i+1)*256; - } - t[j*256+i].c0=128; - t[j*256+i].c1=128; - } - } - top=65536; - curr=0; - threshold=256; - } - - // update count, state - if (y) { - if (t[curr].c1<3800) t[curr].c1+=256; - } - else if (t[curr].c0<3800) t[curr].c0+=256; - t[curr].state=nex(t[curr].state, y); - curr=t[curr].nx[y]; - - // predict - const int pr1=sm.p(t[curr].state); - const int n1=t[curr].c1; - const int n0=t[curr].c0; - const int pr2=(n1+5)*4096/(n0+n1+10); - m.add(stretch(pr1)); - m.add(stretch(pr2)); -} - -//////////////////////////// contextModel ////////////////////// - -typedef enum {DEFAULT, JPEG, EXE, TEXT} Filetype; - -// This combines all the context models with a Mixer. - -int contextModel2() { - static ContextMap cm(MEM*32, 9); - static RunContextMap rcm7(MEM), rcm9(MEM), rcm10(MEM); - static Mixer m(800, 3088, 7, 128); - static U32 cxt[16]; // order 0-11 contexts - static Filetype filetype=DEFAULT; - static int size=0; // bytes remaining in block -// static const char* typenames[4]={"", "jpeg ", "exe ", "text "}; - - // Parse filetype and size - if (bpos==0) { - --size; - if (size==-1) filetype=(Filetype)buf(1); - if (size==-5) { - size=buf(4)<<24|buf(3)<<16|buf(2)<<8|buf(1); -// if (filetype<=3) printf("(%s%d)", typenames[filetype], size); - if (filetype==EXE) size+=8; - } - } - - m.update(); - m.add(256); - - // Test for special file types - int isjpeg=jpegModel(m); // 1 if JPEG is detected, else 0 - int ismatch=ilog(matchModel(m)); // Length of longest matching context - int isbmp=bmpModel(m); // Image width (bytes) if BMP or TIFF detected, or 0 - - if (isjpeg) { - m.set(1, 8); - m.set(c0, 256); - m.set(buf(1), 256); - return m.p(); - } - else if (isbmp>0) { - static int col=0; - if (++col>=24) col=0; - m.set(2, 8); - m.set(col, 24); - m.set(buf(isbmp)+buf(3)>>4, 32); - m.set(c0, 256); - return m.p(); - } - - - // Normal model - if (bpos==0) { - for (int i=15; i>0; --i) // update order 0-11 context hashes - cxt[i]=cxt[i-1]*257+(c4&255)+1; - for (int i=0; i<7; ++i) - cm.set(cxt[i]); - rcm7.set(cxt[7]); - cm.set(cxt[8]); - rcm9.set(cxt[10]); - rcm10.set(cxt[12]); - cm.set(cxt[14]); - } - int order=cm.mix(m); - - rcm7.mix(m); - rcm9.mix(m); - rcm10.mix(m); - - if (level>=4) { - sparseModel(m,ismatch,order); - distanceModel(m); - picModel(m); - recordModel(m); - wordModel(m); - indirectModel(m); - dmcModel(m); - if (filetype==EXE) exeModel(m); - } - - - - order = order-2; - if(order<0) order=0; - - U32 c1=buf(1), c2=buf(2), c3=buf(3), c; - - m.set(c1+8, 264); - m.set(c0, 256); - m.set(order+8*(c4>>5&7)+64*(c1==c2)+128*(filetype==EXE), 256); - m.set(c2, 256); - m.set(c3, 256); - m.set(ismatch, 256); - - if(bpos) - { - c=c0<<(8-bpos); if(bpos==1)c+=c3/2; - c=(min(bpos,5))*256+c1/32+8*(c2/32)+(c&192); - } - else c=c3/128+(c4>>31)*2+4*(c2/64)+(c1&240); - m.set(c, 1536); - int pr=m.p(); - return pr; -} - - -//////////////////////////// Predictor ///////////////////////// - -// A Predictor estimates the probability that the next bit of -// uncompressed data is 1. Methods: -// p() returns P(1) as a 12 bit number (0-4095). -// update(y) trains the predictor with the actual bit (0 or 1). - -class Predictor { - int pr; // next prediction -public: - Predictor(); - int p() const {assert(pr>=0 && pr<4096); return pr;} - void update(); -}; - -Predictor::Predictor(): pr(2048) {} - -void Predictor::update() { - static APM a(256), a1(0x10000), a2(0x10000), a3(0x10000), - a4(0x10000), a5(0x10000), a6(0x10000); - - // Update global context: pos, bpos, c0, c4, buf - c0+=c0+y; - if (c0>=256) { - buf[pos++]=c0; - c4=(c4<<8)+c0-256; - c0=1; - } - bpos=(bpos+1)&7; - - // Filter the context model with APMs - int pr0=contextModel2(); - - pr=a.p(pr0, c0); - - int pr1=a1.p(pr0, c0+256*buf(1)); - int pr2=a2.p(pr0, c0^hash(buf(1), buf(2))&0xffff); - int pr3=a3.p(pr0, c0^hash(buf(1), buf(2), buf(3))&0xffff); - pr0=pr0+pr1+pr2+pr3+2>>2; - - pr1=a4.p(pr, c0+256*buf(1)); - pr2=a5.p(pr, c0^hash(buf(1), buf(2))&0xffff); - pr3=a6.p(pr, c0^hash(buf(1), buf(2), buf(3))&0xffff); - pr=pr+pr1+pr2+pr3+2>>2; - - pr=pr+pr0+1>>1; -} - -//////////////////////////// Encoder //////////////////////////// - -// An Encoder does arithmetic encoding. Methods: -// Encoder(COMPRESS, f) creates encoder for compression to archive f, which -// must be open past any header for writing in binary mode. -// Encoder(DECOMPRESS, f) creates encoder for decompression from archive f, -// which must be open past any header for reading in binary mode. -// code(i) in COMPRESS mode compresses bit i (0 or 1) to file f. -// code() in DECOMPRESS mode returns the next decompressed bit from file f. -// Global y is set to the last bit coded or decoded by code(). -// compress(c) in COMPRESS mode compresses one byte. -// decompress() in DECOMPRESS mode decompresses and returns one byte. -// flush() should be called exactly once after compression is done and -// before closing f. It does nothing in DECOMPRESS mode. -// size() returns current length of archive -// setFile(f) sets alternate source to FILE* f for decompress() in COMPRESS -// mode (for testing transforms). -// If level (global) is 0, then data is stored without arithmetic coding. - -typedef enum {COMPRESS, DECOMPRESS} Mode; -class Encoder { -private: - Predictor predictor; - const Mode mode; // Compress or decompress? - FILE* archive; // Compressed data file - U32 x1, x2; // Range, initially [0, 1), scaled by 2^32 - U32 x; // Decompress mode: last 4 input bytes of archive - FILE *alt; // decompress() source in COMPRESS mode - - // Compress bit y or return decompressed bit - int code(int i=0) { - int p=predictor.p(); - assert(p>=0 && p<4096); - p+=p<2048; - U32 xmid=x1 + (x2-x1>>12)*p + ((x2-x1&0xfff)*p>>12); - assert(xmid>=x1 && xmid>24, archive); - x1<<=8; - x2=(x2<<8)+255; - if (mode==DECOMPRESS) x=(x<<8)+(getc(archive)&255); // EOF is OK - } - return y; - } - -public: - Encoder(Mode m, FILE* f); - Mode getMode() const {return mode;} - long size() const {return ftell(archive);} // length of archive so far - void flush(); // call this when compression is finished - void setFile(FILE* f) {alt=f;} - - // Compress one byte - void compress(int c) { - assert(mode==COMPRESS); - if (level==0) - putc(c, archive); - else - for (int i=7; i>=0; --i) - code((c>>i)&1); - } - - // Decompress and return one byte - int decompress() { - if (mode==COMPRESS) { - assert(alt); - return getc(alt); - } - else if (level==0) - return getc(archive); - else { - int c=0; - for (int i=0; i<8; ++i) - c+=c+code(); - return c; - } - } -}; - -Encoder::Encoder(Mode m, FILE* f): - mode(m), archive(f), x1(0), x2(0xffffffff), x(0), alt(0) { - if (level>0 && mode==DECOMPRESS) { // x = first 4 bytes of archive - for (int i=0; i<4; ++i) - x=(x<<8)+(getc(archive)&255); - } -} - -void Encoder::flush() { - if (mode==COMPRESS && level>0) - putc(x1>>24, archive); // Flush first unequal byte of range -} - -/////////////////////////// Filters ///////////////////////////////// -// -// Before compression, data is encoded in blocks with the following format: -// -// -// -// Type is 1 byte (type Filetype): DEFAULT=0, JPEG, EXE -// Size is 4 bytes in big-endian format. -// Encoded-data decodes to bytes. The encoded size might be -// different. Encoded data is designed to be more compressible. -// -// void encode(FILE* in, FILE* out, int n); -// -// Reads n bytes of in (open in "rb" mode) and encodes one or -// more blocks to temporary file out (open in "wb+" mode). -// The file pointer of in is advanced n bytes. The file pointer of -// out is positioned after the last byte written. -// -// en.setFile(FILE* out); -// int decode(Encoder& en); -// -// Decodes and returns one byte. Input is from en.decompress(), which -// reads from out if in COMPRESS mode. During compression, n calls -// to decode() must exactly match n bytes of in, or else it is compressed -// as type 0 without encoding. -// -// Filetype detect(FILE* in, int n, Filetype type); -// -// Reads n bytes of in, and detects when the type changes to -// something else. If it does, then the file pointer is repositioned -// to the start of the change and the new type is returned. If the type -// does not change, then it repositions the file pointer n bytes ahead -// and returns the old type. -// -// For each type X there are the following 2 functions: -// -// void encode_X(FILE* in, FILE* out, int n, ...); -// -// encodes n bytes from in to out. -// -// int decode_X(Encoder& en); -// -// decodes one byte from en and returns it. decode() and decode_X() -// maintain state information using static variables. - -// Detect EXE or JPEG data -Filetype detect(FILE* in, int n, Filetype type) { - U32 buf1=0, buf0=0; // last 8 bytes - long start=ftell(in); - - // For EXE detection - Array abspos(256), // CALL/JMP abs. addr. low byte -> last offset - relpos(256); // CALL/JMP relative addr. low byte -> last offset - int e8e9count=0; // number of consecutive CALL/JMPs - int e8e9pos=0; // offset of first CALL or JMP instruction - int e8e9last=0; // offset of most recent CALL or JMP - - // For JPEG detection - int soi=0, sof=0, sos=0; // position where found - - for (int i=0; i>24; - buf0=buf0<<8|c; - - // Detect JPEG by code SOI APPx (FF D8 FF Ex) followed by - // SOF0 (FF C0 xx xx 08) and SOS (FF DA) within a reasonable distance. - // Detect end by any code other than RST0-RST7 (FF D9-D7) or - // a byte stuff (FF 00). - - if (i>=3 && (buf0&0xfffffff0)==0xffd8ffe0) soi=i; - if (soi && i-soi<0x10000 && (buf1&0xff)==0xff - && (buf0&0xff0000ff)==0xc0000008) - sof=i; - if (soi && sof && sof>soi && i-soi<0x10000 && i-sof<0x1000 - && (buf0&0xffff)==0xffda) { - sos=i; - if (type!=JPEG) return fseek(in, start+soi-3, SEEK_SET), JPEG; - } - if (type==JPEG && sos && i>sos && (buf0&0xff00)==0xff00 - && (buf0&0xff)!=0 && (buf0&0xf8)!=0xd0) - return DEFAULT; - - // Detect EXE if the low order byte (little-endian) XX is more - // recently seen (and within 4K) if a relative to absolute address - // conversion is done in the context CALL/JMP (E8/E9) XX xx xx 00/FF - // 4 times in a row. Detect end of EXE at the last - // place this happens when it does not happen for 64KB. - - if ((buf1&0xfe)==0xe8 && (buf0+1&0xfe)==0) { - int r=buf0>>24; // relative address low 8 bits - int a=(buf0>>24)+i&0xff; // absolute address low 8 bits - int rdist=i-relpos[r]; - int adist=i-abspos[a]; - if (adist5) { - e8e9last=i; - ++e8e9count; - if (e8e9pos==0 || e8e9pos>abspos[a]) e8e9pos=abspos[a]; - } - else e8e9count=0; - if (type!=EXE && e8e9count>=4 && e8e9pos>5) - return fseek(in, start+e8e9pos-5, SEEK_SET), EXE; - abspos[a]=i; - relpos[r]=i; - } - if (type==EXE && i-e8e9last>0x1000) - return fseek(in, start+e8e9last, SEEK_SET), DEFAULT; - } - return type; -} - -// Default encoding as self -void encode_default(FILE* in, FILE* out, int len) { - while (len--) putc(getc(in), out); -} - -int decode_default(Encoder& en) { - return en.decompress(); -} - -// JPEG encode as self. The purpose is to shield jpegs from exe transform. -void encode_jpeg(FILE* in, FILE* out, int len) { - while (len--) putc(getc(in), out); -} - -int decode_jpeg(Encoder& en) { - return en.decompress(); -} - -// EXE transform: ... -// Encoded-size is 4 bytes, MSB first. -// begin is the offset of the start of the input file, 4 bytes, MSB first. -// Each block applies the e8e9 transform to strings falling entirely -// within the block starting from the end and working backwards. -// The 5 byte pattern is E8/E9 xx xx xx 00/FF (x86 CALL/JMP xxxxxxxx) -// where xxxxxxxx is a relative address LSB first. The address is -// converted to an absolute address by adding the offset mod 2^25 -// (in range +-2^24). - -void encode_exe(FILE* in, FILE* out, int len, int begin) { - const int BLOCK=0x10000; - Array blk(BLOCK); - fprintf(out, "%c%c%c%c", len>>24, len>>16, len>>8, len); // size, MSB first - fprintf(out, "%c%c%c%c", begin>>24, begin>>16, begin>>8, begin); - - // Transform - for (int offset=0; offset=4; --i) { - if ((blk[i-4]==0xe8||blk[i-4]==0xe9) && (blk[i]==0||blk[i]==0xff)) { - int a=(blk[i-3]|blk[i-2]<<8|blk[i-1]<<16|blk[i]<<24)+offset+begin+i+1; - a<<=7; - a>>=7; - blk[i]=a>>24; - blk[i-1]=a>>16; - blk[i-2]=a>>8; - blk[i-3]=a; - } - } - fwrite(&blk[0], 1, bytesRead, out); - } -} - -int decode_exe(Encoder& en) { - const int BLOCK=0x10000; // block size - static int offset=0, q=0; // decode state: file offset, queue size - static int size=0; // where to stop coding - static int begin=0; // offset in file - static U8 c[5]; // queue of last 5 bytes, c[0] at front - - // Read size from first 4 bytes, MSB first - while (offset==size && q==0) { - offset=0; - size=en.decompress()<<24; - size|=en.decompress()<<16; - size|=en.decompress()<<8; - size|=en.decompress(); - begin=en.decompress()<<24; - begin|=en.decompress()<<16; - begin|=en.decompress()<<8; - begin|=en.decompress(); - } - - // Fill queue - while (offset subtract location from x - if (q==5 && (c[4]==0xe8||c[4]==0xe9) && (c[0]==0||c[0]==0xff) - && ((offset-1^offset-5)&-BLOCK)==0) { // not crossing block boundary - int a=(c[3]|c[2]<<8|c[1]<<16|c[0]<<24)-offset-begin; - a<<=7; - a>>=7; - c[3]=a; - c[2]=a>>8; - c[1]=a>>16; - c[0]=a>>24; - } - - // return oldest byte in queue - assert(q>0 && q<=5); - return c[--q]; -} - - - -// Split n bytes into blocks by type. For each block, output -// and call encode_X to convert to type X. -void encode(FILE* in, FILE* out, int n) { - Filetype type=DEFAULT; - long begin=ftell(in); - while (n>0) { - Filetype nextType=detect(in, n, type); - long end=ftell(in); - fseek(in, begin, SEEK_SET); - int len=int(end-begin); - if (len>0) { - fprintf(out, "%c%c%c%c%c", type, len>>24, len>>16, len>>8, len); - switch(type) { - case JPEG: encode_jpeg(in, out, len); break; - case EXE: encode_exe(in, out, len, begin); break; - default: encode_default(in, out, len); break; - } - } - n-=len; - type=nextType; - begin=end; - } -} - -// Decode ... -int decode(Encoder& en) { - static Filetype type=DEFAULT; - static int len=0; - while (len==0) { - type=(Filetype)en.decompress(); - len=en.decompress()<<24; - len|=en.decompress()<<16; - len|=en.decompress()<<8; - len|=en.decompress(); - if (len<0) len=1; - } - --len; - switch (type) { - case JPEG: return decode_jpeg(en); - case EXE: return decode_exe(en); - default: return decode_default(en); - } -} - -//////////////////// Compress, Decompress //////////////////////////// - -// Print progress: n is the number of bytes compressed or decompressed -void printStatus(int n) { - if (n>0 && !(n&0x0fff)) - printf("%12d\b\b\b\b\b\b\b\b\b\b\b\b", n), fflush(stdout); -} - -// Compress a file -void compress(const char* filename, long filesize, Encoder& en) { - assert(en.getMode()==COMPRESS); - assert(filename && filename[0]); - FILE *f=fopen(filename, "rb"); - if (!f) perror(filename), quit(); - long start=en.size(); - printf("%s %ld -> ", filename, filesize); - - // Transform and test in blocks - const int BLOCK=MEM*64; - for (int i=0; filesize>0; i+=BLOCK) { - int size=BLOCK; - if (size>filesize) size=filesize; - FILE* tmp=tmpfile(); - if (!tmp) perror("tmpfile"), quit(); - long savepos=ftell(f); - encode(f, tmp, size); - - // Test transform - rewind(tmp); - en.setFile(tmp); - fseek(f, savepos, SEEK_SET); - long j; - int c1=0, c2=0; - for (j=0; j>24); - en.compress(size>>16); - en.compress(size>>8); - en.compress(size); - fseek(f, savepos, SEEK_SET); - for (int j=0; j ", filename, filesize); - bool found=false; // mismatch? - for (int i=0; i ", filename, filesize); - for (int i=0; i ", filename, filesize); - for (int i=0; i=s.size()) s.resize(len*2+1); - if (c!='\r') s[len++]=c; - } - if (len>=s.size()) s.resize(len+1); - s[len]=0; - if (c==EOF || c==26) - return 0; - else - return s.c_str(); -} - -// int expand(String& archive, String& s, const char* fname, int base) { -// Given file name fname, print its length and base name (beginning -// at fname+base) to archive in format "%ld\t%s\r\n" and append the -// full name (including path) to String s in format "%s\n". If fname -// is a directory then substitute all of its regular files and recursively -// expand any subdirectories. Base initially points to the first -// character after the last / in fname, but in subdirectories includes -// the path from the topmost directory. Return the number of files -// whose names are appended to s and archive. - -// Same as expand() except fname is an ordinary file -int putsize(String& archive, String& s, const char* fname, int base) { - int result=0; - FILE *f=fopen(fname, "rb"); - if (f) { - fseek(f, 0, SEEK_END); - long len=ftell(f); - if (len>=0) { - static char blk[24]; - sprintf(blk, "%ld\t", len); - archive+=blk; - archive+=(fname+base); - archive+="\r\n"; - s+=fname; - s+="\n"; - ++result; - } - fclose(f); - } - return result; -} - -#ifdef WINDOWS - -int expand(String& archive, String& s, const char* fname, int base) { - int result=0; - DWORD attr=GetFileAttributes(fname); - if ((attr != 0xFFFFFFFF) && (attr & FILE_ATTRIBUTE_DIRECTORY)) { - WIN32_FIND_DATA ffd; - String fdir(fname); - fdir+="/*"; - HANDLE h=FindFirstFile(fdir.c_str(), &ffd); - while (h!=INVALID_HANDLE_VALUE) { - if (!equals(ffd.cFileName, ".") && !equals(ffd.cFileName, "..")) { - String d(fname); - d+="/"; - d+=ffd.cFileName; - result+=expand(archive, s, d.c_str(), base); - } - if (FindNextFile(h, &ffd)!=TRUE) break; - } - FindClose(h); - } - else // ordinary file - result=putsize(archive, s, fname, base); - return result; -} - -#else -#ifdef UNIX - -int expand(String& archive, String& s, const char* fname, int base) { - int result=0; - struct stat sb; - if (stat(fname, &sb)<0) return 0; - - // If a regular file and readable, get file size - if (sb.st_mode & S_IFREG && sb.st_mode & 0400) - result+=putsize(archive, s, fname, base); - - // If a directory with read and execute permission, traverse it - else if (sb.st_mode & S_IFDIR && sb.st_mode & 0400 && sb.st_mode & 0100) { - DIR *dirp=opendir(fname); - if (!dirp) { - perror("opendir"); - return result; - } - dirent *dp; - while(errno=0, (dp=readdir(dirp))!=0) { - if (!equals(dp->d_name, ".") && !equals(dp->d_name, "..")) { - String d(fname); - d+="/"; - d+=dp->d_name; - result+=expand(archive, s, d.c_str(), base); - } - } - if (errno) perror("readdir"); - closedir(dirp); - } - else printf("%s is not a readable file or directory\n", fname); - return result; -} - -#else // Not WINDOWS or UNIX, ignore directories - -int expand(String& archive, String& s, const char* fname, int base) { - return putsize(archive, s, fname, base); -} - -#endif -#endif - - -// To compress to file1.paq8l: paq8l [-n] file1 [file2...] -// To decompress: paq8l file1.paq8l [output_dir] -int main(int argc, char** argv) { - bool pause=argc<=2; // Pause when done? - try { - - // Get option - bool doExtract=false; // -d option - if (argc>1 && argv[1][0]=='-' && argv[1][1] && !argv[1][2]) { - if (argv[1][1]>='0' && argv[1][1]<='9') - level=argv[1][1]-'0'; - else if (argv[1][1]=='d') - doExtract=true; - else - quit("Valid options are -0 through -9 or -d\n"); - --argc; - ++argv; - pause=false; - } - - // Print help message - if (argc<2) { - printf(PROGNAME " archiver (C) 2006, Matt Mahoney et al.\n" - "Free under GPL, http://www.gnu.org/licenses/gpl.txt\n\n" -#ifdef WINDOWS - "To compress or extract, drop a file or folder on the " - PROGNAME " icon.\n" - "The output will be put in the same folder as the input.\n" - "\n" - "Or from a command window: " -#endif - "To compress:\n" - " " PROGNAME " -level file (compresses to file." PROGNAME ")\n" - " " PROGNAME " -level archive files... (creates archive." PROGNAME ")\n" - " " PROGNAME " file (level -%d, pause when done)\n" - "level: -0 = store, -1 -2 -3 = faster (uses 35, 48, 59 MB)\n" - "-4 -5 -6 -7 -8 = smaller (uses 133, 233, 435, 837, 1643 MB)\n" -#if defined(WINDOWS) || defined (UNIX) - "You may also compress directories.\n" -#endif - "\n" - "To extract or compare:\n" - " " PROGNAME " -d dir1/archive." PROGNAME " (extract to dir1)\n" - " " PROGNAME " -d dir1/archive." PROGNAME " dir2 (extract to dir2)\n" - " " PROGNAME " archive." PROGNAME " (extract, pause when done)\n" - "\n" - "To view contents: more < archive." PROGNAME "\n" - "\n", - DEFAULT_OPTION); - quit(); - } - - FILE* archive=0; // compressed file - int files=0; // number of files to compress/decompress - Array fname(1); // file names (resized to files) - Array fsize(1); // file lengths (resized to files) - - // Compress or decompress? Get archive name - Mode mode=COMPRESS; - String archiveName(argv[1]); - { - const int prognamesize=strlen(PROGNAME); - const int arg1size=strlen(argv[1]); - if (arg1size>prognamesize+1 && argv[1][arg1size-prognamesize-1]=='.' - && equals(PROGNAME, argv[1]+arg1size-prognamesize)) { - mode=DECOMPRESS; - } - else if (doExtract) - mode=DECOMPRESS; - else { - archiveName+="."; - archiveName+=PROGNAME; - } - } - - // Compress: write archive header, get file names and sizes - String filenames; - if (mode==COMPRESS) { - - // Expand filenames to read later. Write their base names and sizes - // to archive. - String header_string; - for (int i=1; i0 && name[len-1]=='/') // remove trailing / - name[--len]=0; - int base=len-1; - while (base>=0 && name[base]!='/') --base; // find last / - ++base; - if (base==0 && len>=2 && name[1]==':') base=2; // chop "C:" - int expanded=expand(header_string, filenames, name.c_str(), base); - if (!expanded && (i>1||argc==2)) - printf("%s: not found, skipping...\n", name.c_str()); - files+=expanded; - } - - // If archive doesn't exist and there is at least one file to compress - // then create the archive header. - if (files<1) quit("Nothing to compress\n"); -// archive=fopen(archiveName.c_str(), "rb"); -// if (archive) -// printf("%s already exists\n", archiveName.c_str()), quit(); - archive=fopen(archiveName.c_str(), "wb+"); - if (!archive) perror(archiveName.c_str()), quit(); - fprintf(archive, PROGNAME " -%d\r\n%s\x1A", - level, header_string.c_str()); - printf("Creating archive %s with %d file(s)...\n", - archiveName.c_str(), files); - - // Fill fname[files], fsize[files] with input filenames and sizes - fname.resize(files); - fsize.resize(files); - char *p=&filenames[0]; - rewind(archive); - getline(archive); - for (int i=0; i=0); - fname[i]=p; - while (*p!='\n') ++p; - assert(p-filenames.c_str()9) level=DEFAULT_OPTION; - - // Fill fname[files], fsize[files] with output file names and sizes - while (getline(archive)) ++files; // count files - printf("Extracting %d file(s) from %s -%d\n", files, - archiveName.c_str(), level); - long header_size=ftell(archive); - filenames.resize(header_size+4); // copy of header - rewind(archive); - fread(&filenames[0], 1, header_size, archive); - fname.resize(files); - fsize.resize(files); - char* p=&filenames[0]; - while (*p && *p!='\r') ++p; // skip first line - ++p; - for (int i=0; i=0 && level<=9); - buf.setsize(MEM*8); - - // Compress or decompress files - assert(fname.size()==files); - assert(fsize.size()==files); - long total_size=0; // sum of file sizes - for (int i=0; i %ld\n", total_size, en.size()); - } - - // Decompress files to dir2: paq8l -d dir1/archive.paq8l dir2 - // If there is no dir2, then extract to dir1 - // If there is no dir1, then extract to . - else { - assert(argc>=2); - String dir(argc>2?argv[2]:argv[1]); - if (argc==2) { // chop "/archive.paq8l" - int i; - for (i=dir.size()-2; i>=0; --i) { - if (dir[i]=='/' || dir[i]=='\\') { - dir[i]=0; - break; - } - if (i==1 && dir[i]==':') { // leave "C:" - dir[i+1]=0; - break; - } - } - if (i==-1) dir="."; // "/" not found - } - dir=dir.c_str(); - if (dir[0] && (dir.size()!=3 || dir[1]!=':')) dir+="/"; - for (int i=0; i. - -paq9a is an experimental file compressor and archiver. Usage: - - paq9a {a|x|l} archive [[-opt] files...]... - -Commands: - - a = create archive and compress named files. - x = extract from archive. - l = list contents. - -Archives are "solid". You can only create new archives. You cannot -modify existing archives. File names are stored and extracted exactly as -named when the archive is created, but you have the option to rename them -during extraction. Files are never clobbered. - -The "a" command creates a new archive and adds the named files. -Wildcards are permitted if compiled with g++. Options -and filenames may be in any order. Options apply only to filenames -after the option, and override previous options. Options are: - - -s = store without compression. - -c = compress (default). - -1 through -9 selects memory level from 18 MB to 1.5 GB Default is -7 - using 405 MB. The memory option must be set before the first file. - Decompression requires the same amount of memory. - -For example: - - paq9a a foo.paq9a a.txt -3 -s b.txt -c c.txt tmp/d.txt /tmp/e.txt - -creates the archive foo.paq9a with 5 files. The file b.txt is -stored without compression. The other 4 files are compressed -at memory level 3. Extraction requires the same memory as compression. - -If any named file does not exist, then it is omitted from the archive -with a warning and the remaining files are added. An existing -archive cannot be overwritten. There must be at least one filename on -the command line. - -The "x" command extracts the archive contents, creating files exactly -as named when the archive was created. Files cannot be overwritten. -If a file already exists or cannot be created, then it is skipped. -For example, "tmp/d.txt" would be skipped if either the current -directory does not have a subdirectory tmp, or tmp is write -protected, or tmp/d.txt already exists. - -If "x" is followed by one or more file names, then the output files -are renamed in the order they were added to the archive and any remaining -contents are extracted without renaming. For example: - - paq9a x foo.paq9a x.txt y.txt - -would extract a.txt to x.txt and b.txt to y.txt, then extract c.txt, -tmp/d.txt and /tmp/e.txt. If the command line has more filenames than -the archive then the extra arguments are ignored. Options are not -allowed. - -The "l" (letter l) command lists the contents. Any extra arguments -are ignored. - -Any other command, or no command, displays a help message. - - -ARCHIVE FORMAT - - "lPq" 1 mem [filename {'\0' mode usize csize contents}...]... - -The first 4 bytes are "lPq\x01" (1 is the version number). - -mem is a digit '1' through '9', where '9' uses the most memory (1.5 GB). - -A file is stored as one or more blocks. The filename is stored -only in the first block as a NUL terminated string. Subsequent -blocks start with a 0. - -The mode is 's' if the block is stored and 'c' if compressed. - -usize = uncompressed size as a 4 byte big-endian number (MSB first). - -csize = compressed size as a 4 byte big-endian number. - -The contents is copied from the file itself if mode is 's' or the -compressed contents otherwise. Its length is exactly csize bytes. - - -COMPRESSED FORMAT - -Files are preprocessed with LZP and then compressed with a context -mixing compressor and arithmetic coded one bit at a time. Model -contents are maintained across files. - -The LZP stage predicts the next byte by matching the current context -(order 12 or higher) to a rotating buffer. If a match is found -then the next byte after the match is predicted. If the next byte -matches the prediction, then a 1 bit is coded and the context is extended. -Otherwise a 0 is coded followed by 8 bits of the actual byte in MSB to -LSB order. - -A 1 bit is modeled using the match length as context, then refined -in 3 stages using sucessively longer contexts. The predictions are -adjusted by 2 input neurons selected by a context hash with the second -input fixed. - -If the LZP prediction is missed, then the literal is coded using a chain -of predicions which are mixed using neurons, where one input is the -previous prediction and the second input is the prediction given the -current context. The current context is mapped to an 8 bit state -representing the bit history, the sequence of bits previously observed -in that context. The bit history is used both to select the neuron -and is mapped to a prediction that provides the second input. In addition, -if the known bits of the current byte match the LZP incorrectly predicted -byte, then this fact is used to select one of 2 sets of neurons (512 total). - -The contexts, in order, are sparse order-1 with gaps of 3, 2, and 1 -byte, then orders 1 through 6, then word orders 0 and 1, where a word -is a sequenece of case insensitive letters (useful for compressing text). -Contexts longer than 1 are hashed. Order-n contexts consist of a hash -of the last n bytes plus the 0 to 7 known bits of the current byte. -The order 6 context and the word order 0 and 1 contexts also include -the LZP predicted byte. - -All mixing is in the logistic or "stretched" domain: stretch(p) = ln(p/(1-p)), -then "squashed" by the inverse function: squash(p) = 1/(1 + exp(-p)) before -arithmetic coding. A 2 input neuron has 2 weights (w0 and w1) -selected by context. Given inputs x0 and x1 (2 predictions, or one -prediction and a constant), the output prediction is computed: -p = w0*x0 + w1*x1. If the actual bit is y, then the weights are updated -to minimize its coding cost: - - error = y - squash(p) - w0 += x0 * error * L - w1 += x1 * error * L - -where L is the learning rate, normally 1/256, but increased by a factor -of 4 an 2 for the first 2 training cycles (using the 2 low bits -of w0 as a counter). In the implementation, p is represented by a fixed -point number with a 12 bit fractional part in the linear domain (0..4095) -and 8 bits in the logistic domain (-2047..2047 representing -8..8). -Weights are scaled by 24 bits. Both weights are initialized to 1/2, -expecting 2 probabilities, weighted equally). However, when one input -(x0) is fixed, its weight (w0) is initialized to 0. - -A bit history represents the sequence of 0 and 1 bits observed in a given -context. An 8 bit state represents all possible sequences up to 4 bits -long. Longer sequences are represented by a count of 0 and 1 bits, plus -an indicator of the most recent bit. If counts grow too large, then the -next state represents a pair of smaller counts with about the same ratio. -The state table is the same as used in PAQ8 (all versions) and LPAQ1. - -A state is mapped to a prediction by using a table. A table entry -contains 2 values, p, initialized to 1/2, and n, initialized to 0. -The output prediciton is p (in the linear domain, not stretched). -If the actual bit is y, then the entry is updated: - - error = y - p - p += error/(n + 1.5) - if n < limit then n += 1 - -In practice, p is scaled by 22 bits, and n is 10 bits, packed into -one 32 bit integer. The limit is 255. - -Every 4 bits, contexts are mapped to arrays of 15 states using a -hash table. The first element is the bit history for the current -context ending on a half byte boundary, followed by all possible -contexts formed by appending up to 3 more bits. - -A hash table accepts a 32 bit context, which must be a hash if -longer than 4 bytes. The input is further hashed and divided into -an index (depending on the table size, a power of 2), and an 8 bit -checksum which is stored in the table and used to detect collisions -(not perfectly). A lookup tests 3 adjacent locations within a single -64 byte cache line, and if a matching checksum is not found, then the -entry with the smallest value in the first data element is replaced -(LFU replacement policy). This element represents a bit history -for a context ending on a half byte boundary. The states are ordered -so that larger values represent larger total bit counts, which -estimates the likelihood of future use. The initial state is 0. - -Memory is allocated from MEM = pow(2, opt+22) bytes, where opt is 1 through -9 (user selected). Of this, MEM/2 is for the hash table for storing literal -context states, MEM/8 for the rotating LZP buffer, and MEM/8 for a -hash table of pointers into the buffer, plus 12 MB for miscellaneous data. -Total memory usage is 0.75*MEM + 12 MB. - - -ARITHMETIC CODING - -The arithmetic coder codes a bit with probability p using log2(1/p) bits. -Given input string y, the output is a binary fraction x such that -P(< y) <= x < P(<= y) where P(< y) means the total probability of all inputs -lexicographically less than y and P(<= y) = P(< y) + P(y). Note that one -can always find x with length at most log2(P(y)) + 1 bits. - -x can be computed efficiently by maintaining a range, low <= x < high -(initially 0..1) and expressing P(y) as a product of predictions: -P(y) = P(y1) P(y2|y1) P(y3|y1y2) P(y4|y1y2y3) ... P(yn|y1y2...yn-1) -where the term P(yi|y0y1...yi-1) means the probability that yi is 1 -given the context y1...yi-1, the previous i-1 bits of y. For each -prediction p, the range is split in proportion to the probabilities -of 0 and 1, then updated by taking the half corresponding to the actual -bit y as the new range, i.e. - - mid = low + (high - low) * p(y = 1) - if y = 0 then (low, high) := (mid, high) - if y = 1 then (low, high) := (low, mid) - -As low and high approach each other, the high order bits of x become -known (because they are the same throughout the range) and can be -output immediately. - -For decoding, the range is split as before and the range is updated -to the half containing x. The corresponding bit y is used to update -the model. Thus, the model has the same knowledge for coding and -decoding. - -*/ - -#include -#include -#include -#include -#include -#define NDEBUG // remove for debugging -#include - -int allocated=0; // Total memory allocated by alloc() - -// Create an array p of n elements of type T -template void alloc(T*&p, int n) { - p=(T*)calloc(n, sizeof(T)); - if (!p) printf("Out of memory\n"), exit(1); - allocated+=n*sizeof(T); -} - -// 8, 16, 32 bit unsigned types (adjust as appropriate) -typedef unsigned char U8; -typedef unsigned short U16; -typedef unsigned int U32; - -///////////////////////////// Squash ////////////////////////////// - -// return p = 1/(1 + exp(-d)), d scaled by 8 bits, p scaled by 12 bits -class Squash { - short tab[4096]; -public: - Squash(); - int operator()(int d) { - d+=2048; - if (d<0) return 0; - else if (d>4095) return 4095; - else return tab[d]; - } -} squash; - -Squash::Squash() { - static const int t[33]={ - 1,2,3,6,10,16,27,45,73,120,194,310,488,747,1101, - 1546,2047,2549,2994,3348,3607,3785,3901,3975,4022, - 4050,4068,4079,4085,4089,4092,4093,4094}; - for (int i=-2048; i<2048; ++i) { - int w=i&127; - int d=(i>>7)+16; - tab[i+2048]=(t[d]*(128-w)+t[(d+1)]*w+64) >> 7; - } -} - -//////////////////////////// Stretch /////////////////////////////// - -// Inverse of squash. stretch(d) returns ln(p/(1-p)), d scaled by 8 bits, -// p by 12 bits. d has range -2047 to 2047 representing -8 to 8. -// p has range 0 to 4095 representing 0 to 1. - -class Stretch { - short t[4096]; -public: - Stretch(); - int operator()(int p) const { - assert(p>=0 && p<4096); - return t[p]; - } -} stretch; - -Stretch::Stretch() { - int pi=0; - for (int x=-2047; x<=2047; ++x) { // invert squash() - int i=squash(x); - for (int j=pi; j<=i; ++j) - t[j]=x; - pi=i+1; - } - t[4095]=2047; -} - -///////////////////////////// ilog ////////////////////////////// - -// ilog(x) = round(log2(x) * 16), 0 <= x < 64K -class Ilog { - U8* t; -public: - int operator()(U16 x) const {return t[x];} - Ilog(); -} ilog; - -// Compute lookup table by numerical integration of 1/x -Ilog::Ilog() { - alloc(t, 65536); - U32 x=14155776; - for (int i=2; i<65536; ++i) { - x+=774541002/(i*2-1); // numerator is 2^29/ln 2 - t[i]=x>>24; - } -} - -// llog(x) accepts 32 bits -inline int llog(U32 x) { - if (x>=0x1000000) - return 256+ilog(x>>16); - else if (x>=0x10000) - return 128+ilog(x>>8); - else - return ilog(x); -} - -///////////////////////// state table //////////////////////// - -// State table: -// nex(state, 0) = next state if bit y is 0, 0 <= state < 256 -// nex(state, 1) = next state if bit y is 1 -// -// States represent a bit history within some context. -// State 0 is the starting state (no bits seen). -// States 1-30 represent all possible sequences of 1-4 bits. -// States 31-252 represent a pair of counts, (n0,n1), the number -// of 0 and 1 bits respectively. If n0+n1 < 16 then there are -// two states for each pair, depending on if a 0 or 1 was the last -// bit seen. -// If n0 and n1 are too large, then there is no state to represent this -// pair, so another state with about the same ratio of n0/n1 is substituted. -// Also, when a bit is observed and the count of the opposite bit is large, -// then part of this count is discarded to favor newer data over old. - -static const U8 State_table[256][2]={ -{ 1, 2},{ 3, 5},{ 4, 6},{ 7, 10},{ 8, 12},{ 9, 13},{ 11, 14}, // 0 -{ 15, 19},{ 16, 23},{ 17, 24},{ 18, 25},{ 20, 27},{ 21, 28},{ 22, 29}, // 7 -{ 26, 30},{ 31, 33},{ 32, 35},{ 32, 35},{ 32, 35},{ 32, 35},{ 34, 37}, // 14 -{ 34, 37},{ 34, 37},{ 34, 37},{ 34, 37},{ 34, 37},{ 36, 39},{ 36, 39}, // 21 -{ 36, 39},{ 36, 39},{ 38, 40},{ 41, 43},{ 42, 45},{ 42, 45},{ 44, 47}, // 28 -{ 44, 47},{ 46, 49},{ 46, 49},{ 48, 51},{ 48, 51},{ 50, 52},{ 53, 43}, // 35 -{ 54, 57},{ 54, 57},{ 56, 59},{ 56, 59},{ 58, 61},{ 58, 61},{ 60, 63}, // 42 -{ 60, 63},{ 62, 65},{ 62, 65},{ 50, 66},{ 67, 55},{ 68, 57},{ 68, 57}, // 49 -{ 70, 73},{ 70, 73},{ 72, 75},{ 72, 75},{ 74, 77},{ 74, 77},{ 76, 79}, // 56 -{ 76, 79},{ 62, 81},{ 62, 81},{ 64, 82},{ 83, 69},{ 84, 71},{ 84, 71}, // 63 -{ 86, 73},{ 86, 73},{ 44, 59},{ 44, 59},{ 58, 61},{ 58, 61},{ 60, 49}, // 70 -{ 60, 49},{ 76, 89},{ 76, 89},{ 78, 91},{ 78, 91},{ 80, 92},{ 93, 69}, // 77 -{ 94, 87},{ 94, 87},{ 96, 45},{ 96, 45},{ 48, 99},{ 48, 99},{ 88,101}, // 84 -{ 88,101},{ 80,102},{103, 69},{104, 87},{104, 87},{106, 57},{106, 57}, // 91 -{ 62,109},{ 62,109},{ 88,111},{ 88,111},{ 80,112},{113, 85},{114, 87}, // 98 -{114, 87},{116, 57},{116, 57},{ 62,119},{ 62,119},{ 88,121},{ 88,121}, // 105 -{ 90,122},{123, 85},{124, 97},{124, 97},{126, 57},{126, 57},{ 62,129}, // 112 -{ 62,129},{ 98,131},{ 98,131},{ 90,132},{133, 85},{134, 97},{134, 97}, // 119 -{136, 57},{136, 57},{ 62,139},{ 62,139},{ 98,141},{ 98,141},{ 90,142}, // 126 -{143, 95},{144, 97},{144, 97},{ 68, 57},{ 68, 57},{ 62, 81},{ 62, 81}, // 133 -{ 98,147},{ 98,147},{100,148},{149, 95},{150,107},{150,107},{108,151}, // 140 -{108,151},{100,152},{153, 95},{154,107},{108,155},{100,156},{157, 95}, // 147 -{158,107},{108,159},{100,160},{161,105},{162,107},{108,163},{110,164}, // 154 -{165,105},{166,117},{118,167},{110,168},{169,105},{170,117},{118,171}, // 161 -{110,172},{173,105},{174,117},{118,175},{110,176},{177,105},{178,117}, // 168 -{118,179},{110,180},{181,115},{182,117},{118,183},{120,184},{185,115}, // 175 -{186,127},{128,187},{120,188},{189,115},{190,127},{128,191},{120,192}, // 182 -{193,115},{194,127},{128,195},{120,196},{197,115},{198,127},{128,199}, // 189 -{120,200},{201,115},{202,127},{128,203},{120,204},{205,115},{206,127}, // 196 -{128,207},{120,208},{209,125},{210,127},{128,211},{130,212},{213,125}, // 203 -{214,137},{138,215},{130,216},{217,125},{218,137},{138,219},{130,220}, // 210 -{221,125},{222,137},{138,223},{130,224},{225,125},{226,137},{138,227}, // 217 -{130,228},{229,125},{230,137},{138,231},{130,232},{233,125},{234,137}, // 224 -{138,235},{130,236},{237,125},{238,137},{138,239},{130,240},{241,125}, // 231 -{242,137},{138,243},{130,244},{245,135},{246,137},{138,247},{140,248}, // 238 -{249,135},{250, 69},{ 80,251},{140,252},{249,135},{250, 69},{ 80,251}, // 245 -{140,252},{ 0, 0},{ 0, 0},{ 0, 0}}; // 252 -#define nex(state,sel) State_table[state][sel] - -//////////////////////////// StateMap ////////////////////////// - -// A StateMap maps a context to a probability. Methods: -// -// Statemap sm(n) creates a StateMap with n contexts using 4*n bytes memory. -// sm.p(cx, limit) converts state cx (0..n-1) to a probability (0..4095) -// that the next updated bit y=1. -// limit (1..1023, default 255) is the maximum count for computing a -// prediction. Larger values are better for stationary sources. -// sm.update(y) updates the model with actual bit y (0..1). - -class StateMap { -protected: - const int N; // Number of contexts - int cxt; // Context of last prediction - U32 *t; // cxt -> prediction in high 22 bits, count in low 10 bits - static int dt[1024]; // i -> 16K/(i+3) -public: - StateMap(int n=256); - - // update bit y (0..1) - void update(int y, int limit=255) { - assert(cxt>=0 && cxt>10; // count, prediction - if (n>3)*dt[n]&0xfffffc00; - } - - // predict next bit in context cx - int p(int cx) { - assert(cx>=0 && cx>20; - } -}; - -int StateMap::dt[1024]={0}; - -StateMap::StateMap(int n): N(n), cxt(0) { - alloc(t, N); - for (int i=0; i=0 && cx>16)+(x2=p2)*(wt[cxt+1]>>16)+128>>8; - } - void update(int y) { - assert(y==0 || y==1); - int err=((y<<12)-squash(pr)); - if ((wt[cxt]&3)<3) - err*=4-(++wt[cxt]&3); - err=err+8>>4; - wt[cxt]+=x1*err&-4; - wt[cxt+1]+=x2*err; - } -}; - -Mix::Mix(int n): N(n), x1(0), x2(0), cxt(0), pr(0) { - alloc(wt, n*2); - for (int i=0; i h(n) - create using n bytes n and B must be -// powers of 2 with n >= B*4, and B >= 2. -// h[i] returns array [1..B-1] of bytes indexed by i, creating and -// replacing another element if needed. Element 0 is the -// checksum and should not be modified. - -template -class HashTable { - U8* t; // table: 1 element = B bytes: checksum priority data data - const U32 N; // size in bytes -public: - HashTable(int n); - ~HashTable(); - U8* operator[](U32 i); -}; - -template -HashTable::HashTable(int n): t(0), N(n) { - assert(B>=2 && (B&B-1)==0); - assert(N>=B*4 && (N&N-1)==0); - alloc(t, N+B*4+64); - t+=64-int(((long)t)&63); // align on cache line boundary -} - -template -inline U8* HashTable::operator[](U32 i) { - i*=123456791; - i=i<<16|i>>16; - i*=234567891; - int chk=i>>24; - i=i*B&N-B; - if (t[i]==chk) return t+i; - if (t[i^B]==chk) return t+(i^B); - if (t[i^B*2]==chk) return t+(i^B*2); - if (t[i+1]>t[i+1^B] || t[i+1]>t[i+1^B*2]) i^=B; - if (t[i+1]>t[i+1^B^B*2]) i^=B^B*2; - memset(t+i, 0, B); - t[i]=chk; - return t+i; -} - -template -HashTable::~HashTable() { - int c=0, c0=0; - for (U32 i=0; i %1.4f%% full, %1.4f%% utilized of %d KiB\n", - B, 100.0*c0*B/N, 100.0*c/N, N>>10); -} - -////////////////////////// LZP ///////////////////////// - -U32 MEM=1<<29; // Global memory limit, 1 << 22+(memory option) - -// LZP predicts the next byte and maintains context. Methods: -// c() returns the predicted byte for the next update, or -1 if none. -// p() returns the 12 bit probability (0..4095) that c() is next. -// update(ch) updates the model with actual byte ch (0..255). -// c(i) returns the i'th prior byte of context, i > 0. -// c4() returns the order 4 context, shifted into the LSB. -// c8() returns a hash of the order 8 context, shifted 4 bits into LSB. -// word0, word1 are hashes of the current and previous word (a-z). - -class LZP { -private: - const int N, H; // buf, t sizes - enum {MINLEN=12}; // minimum match length - U8* buf; // Rotating buffer of size N - U32* t; // hash table of pointers in high 24 bits, state in low 8 bits - int match; // start of match - int len; // length of match - int pos; // position of next ch to write to buf - U32 h; // context hash - U32 h1; // hash of last 8 byte updates, shifting 4 bits to MSB - U32 h2; // last 4 updates, shifting 8 bits to MSB - StateMap sm1; // len+offset -> p - APM a1, a2, a3; // p, context -> p - int literals, matches; // statistics -public: - U32 word0, word1; // hashes of last 2 words (case insensitive a-z) - LZP(); - ~LZP(); - int c(); // predicted char - int c(int i);// context - int c4() {return h2;} // order 4 context, c(1) in LSB - int c8() {return h1;} // hashed order 8 context - int p(); // probability that next char is c() * 4096 - void update(int ch); // update model with actual char ch -}; - -// Initialize -LZP::LZP(): N(MEM/8), H(MEM/32), - match(-1), len(0), pos(0), h(0), h1(0), h2(0), - sm1(0x200), a1(0x10000), a2(0x40000), a3(0x100000), - literals(0), matches(0), word0(0), word1(0) { - assert(MEM>0); - assert(H>0); - alloc(buf, N); - alloc(t, H); -} - -// Print statistics -LZP::~LZP() { - int c=0; - for (int i=0; i>8, pos>10); - printf("LZP %d literals, %d matches (%1.4f%% matched)\n", - literals, matches, - literals+matches>0?100.0*matches/(literals+matches):0.0); -} - -// Predicted next byte, or -1 for no prediction -inline int LZP::c() { - return len>=MINLEN ? buf[match&N-1] : -1; -} - -// Return i'th byte of context (i > 0) -inline int LZP::c(int i) { - assert(i>0); - return buf[pos-i&N-1]; -} - -// Return prediction that c() will be the next byte (0..4095) -int LZP::p() { - if (len28) cxt=28+(len>=32)+(len>=64)+(len>=128); - int pc=c(); - int pr=sm1.p(cxt); - pr=stretch(pr); - pr=a1.pp(2048, pr*2, h2*256+pc&0xffff)*3+pr>>2; - pr=a2.pp(2048, pr*2, h1*(11<<6)+pc&0x3ffff)*3+pr>>2; - pr=a3.pp(2048, pr*2, h1*(7<<4)+pc&0xfffff)*3+pr>>2; - pr=squash(pr); - return pr; -} - -// Update model with predicted byte ch (0..255) -void LZP::update(int ch) { - int y=c()==ch; // 1 if prediction of ch was right, else 0 - h1=h1*(3<<4)+ch+1; // update context hashes - h2=h2<<8|ch; - h=h*(5<<2)+ch+1&H-1; - if (len>=MINLEN) { - sm1.update(y); - a1.update(y); - a2.update(y); - a3.update(y); - } - if (isalpha(ch)) - word0=word0*(29<<2)+tolower(ch); - else if (word0) - word1=word0, word0=0; - buf[pos&N-1]=ch; // update buf - ++pos; - if (y) { // extend match - ++len; - ++match; - ++matches; - } - else { // find new match, try order 6 context first - ++literals; - y=0; - len=1; - match=t[h]; - if (!((match^pos)&N-1)) --match; - while (len<=128 && buf[match-len&N-1]==buf[pos-len&N-1]) ++len; - --len; - } - t[h]=pos; -} - -LZP* lzp=0; - -//////////////////////////// Predictor ///////////////////////// - -// A Predictor estimates the probability that the next bit of -// uncompressed data is 1. Methods: -// Predictor() creates. -// p() returns P(1) as a 12 bit number (0-4095). -// update(y) trains the predictor with the actual bit (0 or 1). - -class Predictor { - enum {N=11}; // number of contexts - int c0; // last 0-7 bits with leading 1, 0 before LZP flag - int nibble; // last 0-3 bits with leading 1 (1..15) - int bcount; // number of bits in c0 (0..7) - HashTable<16> t; // context -> state - StateMap sm[N]; // state -> prediction - U8* cp[N]; // i -> state array of bit histories for i'th context - U8* sp[N]; // i -> pointer to bit history for i'th context - Mix m[N-1]; // combines 2 predictions given a context - APM a1, a2, a3; // adjusts a prediction given a context - U8* t2; // order 1 contexts -> state - -public: - Predictor(); - int p(); - void update(int y); -}; - -// Initialize -Predictor::Predictor(): - c0(0), nibble(1), bcount(0), t(MEM/2), - a1(0x10000), a2(0x10000), a3(0x10000) { - alloc(t2, 0x40000); - for (int i=0; i=0 && bcount<8); - assert(c0>=0 && c0<256); - assert(nibble>=1 && nibble<=15); - if (c0==0) - c0=1-y; - else { - *sp[0]=nex(*sp[0], y); - sm[0].update(y); - for (int i=1; i=16) nibble=1; - a1.update(y); - a2.update(y); - a3.update(y); - } -} - -// Predict next bit -int Predictor::p() { - assert(lzp); - if (c0==0) - return lzp->p(); - else { - - // Set context pointers - int pc=lzp->c(); // mispredicted byte - int r=pc+256>>8-bcount==c0; // c0 consistent with mispredicted byte? - U32 c4=lzp->c4(); // last 4 whole context bytes, shifted into LSB - U32 c8=(lzp->c8()<<4)-1; // hash of last 7 bytes with 4 trailing 1 bits - if ((bcount&3)==0) { // nibble boundary? Update context pointers - pc&=-r; - U32 c4p=c4<<8; - if (bcount==0) { // byte boundary? Update order-1 context pointers - cp[0]=t2+(c4>>16&0xff00); - cp[1]=t2+(c4>>8 &0xff00)+0x10000; - cp[2]=t2+(c4 &0xff00)+0x20000; - cp[3]=t2+(c4<<8 &0xff00)+0x30000; - } - cp[4]=t[(c4p&0xffff00)-c0]; - cp[5]=t[(c4p&0xffffff00)*3+c0]; - cp[6]=t[c4*7+c0]; - cp[7]=t[(c8*5&0xfffffc)+c0]; - cp[8]=t[(c8*11&0xffffff0)+c0+pc*13]; - cp[9]=t[lzp->word0*5+c0+pc*17]; - cp[10]=t[lzp->word1*7+lzp->word0*11+c0+pc*37]; - } - - // Mix predictions - r<<=8; - sp[0]=&cp[0][c0]; - int pr=stretch(sm[0].p(*sp[0])); - for (int i=1; i>2; - } - pr=a1.pp(512, pr*2, c0+pc*256&0xffff)*3+pr>>2; // Adjust prediction - pr=a2.pp(512, pr*2, c4<<8&0xff00|c0)*3+pr>>2; - pr=a3.pp(512, pr*2, c4*3+c0&0xffff)*3+pr>>2; - return squash(pr); - } -} - -Predictor* predictor=0; - -/////////////////////////// get4, put4 ////////////////////////// - -// Read/write a 4 byte big-endian number -int get4(FILE* in) { - int r=getc(in); - r=r*256+getc(in); - r=r*256+getc(in); - r=r*256+getc(in); - return r; -} - -void put4(U32 c, FILE* out) { - fprintf(out, "%c%c%c%c", c>>24, c>>16, c>>8, c); -} - -//////////////////////////// Encoder //////////////////////////// - -// An Encoder arithmetic codes in blocks of size BUFSIZE. Methods: -// Encoder(COMPRESS, f) creates encoder for compression to archive f, which -// must be open past any header for writing in binary mode. -// Encoder(DECOMPRESS, f) creates encoder for decompression from archive f, -// which must be open past any header for reading in binary mode. -// code(i) in COMPRESS mode compresses bit i (0 or 1) to file f. -// code() in DECOMPRESS mode returns the next decompressed bit from file f. -// count() should be called after each byte is compressed. -// flush() should be called after compression is done. It is also called -// automatically when a block is written. - -typedef enum {COMPRESS, DECOMPRESS} Mode; -class Encoder { -private: - const Mode mode; // Compress or decompress? - FILE* archive; // Compressed data file - U32 x1, x2; // Range, initially [0, 1), scaled by 2^32 - U32 x; // Decompress mode: last 4 input bytes of archive - enum {BUFSIZE=0x20000}; - static unsigned char* buf; // Compression output buffer, size BUFSIZE - int usize, csize; // Buffered uncompressed and compressed sizes - double usum, csum; // Total of usize, csize - -public: - Encoder(Mode m, FILE* f); - void flush(); // call this when compression is finished - - // Compress bit y or return decompressed bit - int code(int y=0) { - assert(predictor); - int p=predictor->p(); - assert(p>=0 && p<4096); - p+=p<2048; - U32 xmid=x1 + (x2-x1>>12)*p + ((x2-x1&0xfff)*p>>12); - assert(xmid>=x1 && xmidupdate(y); - while (((x1^x2)&0xff000000)==0) { // pass equal leading bytes of range - if (mode==COMPRESS) buf[csize++]=x2>>24; - x1<<=8; - x2=(x2<<8)+255; - if (mode==DECOMPRESS) x=(x<<8)+getc(archive); - } - return y; - } - - // Count one byte - void count() { - assert(mode==COMPRESS); - ++usize; - if (csize>BUFSIZE-256) - flush(); - } -}; -unsigned char* Encoder::buf=0; - -// Create in mode m (COMPRESS or DECOMPRESS) with f opened as the archive. -Encoder::Encoder(Mode m, FILE* f): - mode(m), archive(f), x1(0), x2(0xffffffff), x(0), - usize(0), csize(0), usum(0), csum(0) { - if (mode==DECOMPRESS) { // x = first 4 bytes of archive - for (int i=0; i<4; ++i) - x=(x<<8)+(getc(archive)&255); - csize=4; - } - else if (!buf) - alloc(buf, BUFSIZE); -} - -// Write a compressed block and reinitialize the encoder. The format is: -// uncompressed size (usize, 4 byte, MSB first) -// compressed size (csize, 4 bytes, MSB first) -// compressed data (csize bytes) -void Encoder::flush() { - if (mode==COMPRESS) { - buf[csize++]=x1>>24; - buf[csize++]=255; - buf[csize++]=255; - buf[csize++]=255; - putc(0, archive); - putc('c', archive); - put4(usize, archive); - put4(csize, archive); - fwrite(buf, 1, csize, archive); - usum+=usize; - csum+=csize+10; - printf("%15.0f -> %15.0f" - "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", - usum, csum); - x1=x=usize=csize=0; - x2=0xffffffff; - } -} - -/////////////////////////// paq9a //////////////////////////////// - -// Compress or decompress from in to out, depending on whether mode -// is COMPRESS or DECOMPRESS. A byte c is encoded as a 1 bit if it -// is predicted by LZP, otherwise a 0 followed by 8 bits from MSB to LSB. -void paq9a(FILE* in, FILE* out, Mode mode) { - if (!lzp && !predictor) { - lzp=new LZP; - predictor=new Predictor; - printf("%8d KiB\b\b\b\b\b\b\b\b\b\b\b\b", allocated>>10); - } - if (mode==COMPRESS) { - Encoder e(COMPRESS, out); - int c; - while ((c=getc(in))!=EOF) { - int cp=lzp->c(); - if (c==cp) - e.code(1); - else - for (int i=8; i>=0; --i) - e.code(c>>i&1); - e.count(); - lzp->update(c); - } - e.flush(); - } - else { // DECOMPRESS - int usize=get4(in); - get4(in); // csize - Encoder e(DECOMPRESS, in); - while (usize--) { - int c=lzp->c(); - if (e.code()==0) { - c=1; - while (c<256) c+=c+e.code(); - c&=255; - } - if (out) putc(c, out); - lzp->update(c); - } - } -} - - -///////////////////////////// store /////////////////////////// - -// Store a file in blocks as: {'\0' mode usize csize contents}... -void store(FILE* in, FILE* out) { - assert(in); - assert(out); - - // Store in blocks - const int BLOCKSIZE=0x100000; - static char* buf=0; - if (!buf) alloc(buf, BLOCKSIZE); - bool first=true; - while (true) { - int n=fread(buf, 1, BLOCKSIZE, in); - if (!first && n<=0) break; - fprintf(out, "%c%c", 0, 's'); - put4(n, out); // usize - put4(n, out); // csize - fwrite(buf, 1, n, out); - first=false; - } - - // Close file - fclose(in); -} - -// Write usize == csize bytes of an uncompressed block from in to out -void unstore(FILE* in, FILE* out) { - assert(in); - int usize=get4(in); - int csize=get4(in); - if (usize!=csize) - printf("Bad archive format: usize=%d csize=%d\n", usize, csize); - static char* buf=0; - const int BUFSIZE=0x1000; - if (!buf) alloc(buf, BUFSIZE); - while (csize>0) { - usize=csize; - if (usize>BUFSIZE) usize=BUFSIZE; - if (int(fread(buf, 1, usize, in))!=usize) - printf("Unexpected end of archive\n"), exit(1); - if (out) fwrite(buf, 1, usize, out); - csize-=usize; - } -} - -//////////////////////// Archiving functions //////////////////////// - -const int MAXNAMELEN=1023; // max filename length - -// Return true if the first 4 bytes of in are a valid archive -bool check_archive(FILE* in) { - return getc(in)=='p' && getc(in)=='Q' && getc(in)=='9' && getc(in)==1; -} - -// Open archive and check for valid archive header, exit if bad. -// Set MEM to memory option '1' through '9' -FILE* open_archive(const char* filename) { - FILE* in=fopen(filename, "rb"); - if (!in) - printf("Cannot find archive %s\n", filename), exit(1); - if (!check_archive(in) || (MEM=getc(in))<'1' || MEM>'9') { - fclose(in); - printf("%s: Not a paq9a archive\n", filename); - exit(1); - } - return in; -} - -// Compress filename to out. option is 'c' to compress or 's' to store. -void compress(const char* filename, FILE* out, int option) { - - // Open input file - FILE* in=fopen(filename, "rb"); - if (!in) { - printf("File not found: %s\n", filename); - return; - } - fprintf(out, "%s", filename); - printf("%-40s ", filename); - - // Compress depending on option - if (option=='s') - store(in, out); - else if (option=='c') - paq9a(in, out, COMPRESS); - printf("\n"); -} - -// List archive contents -void list(const char* archive) { - double usum=0, csum=0; // uncompressed and compressed size per file - double utotal=0, ctotal=4; // total size in archive - static char filename[MAXNAMELEN+1]; - int mode=0; - - FILE* in=open_archive(archive); - printf("\npaq9a -%c\n", MEM); - while (true) { - - // Get filename, mode - int c=getc(in); - if (c==EOF) break; - if (c) { // start of new file? Print previous file - if (mode) - printf("%10.0f -> %10.0f %c %s\n", usum, csum, mode, filename); - int len=0; - filename[len++]=c; - while ((c=getc(in))!=EOF && c) - if (lenBUFSIZE) - csize-=fread(buf, 1, BUFSIZE, in); - fread(buf, 1, csize, in); - } - printf("%10.0f -> %10.0f %c %s\n", usum, csum, mode, filename); - utotal+=usum; - ctotal+=csum; - printf("%10.0f -> %10.0f total\n", utotal, ctotal); - fclose(in); -} - -// Extract files given command line arguments -// Input format is: [filename {'\0' mode usize csize contents}...]... -void extract(int argc, char** argv) { - assert(argc>2); - assert(argv[1][0]=='x'); - static char filename[MAXNAMELEN+1]; // filename from archive - - // Open archive - FILE* in=open_archive(argv[2]); - MEM=1<<22+MEM-'0'; - - // Extract files - argc-=3; - argv+=3; - FILE* out=0; - while (true) { // for each block - - // Get filename - int c; - for (int i=0;; ++i) { - c=getc(in); - if (c==EOF) break; - if (i0) fn=argv[0], --argc, ++argv; - if (out) fclose(out); - out=fopen(fn, "rb"); - if (out) { - printf("\nCannot overwrite file, skipping: %s ", fn); - fclose(out); - out=0; - } - else { - out=fopen(fn, "wb"); - if (!out) printf("\nCannot create file: %s ", fn); - } - if (out) { - if (fn==filename) printf("\n%s ", filename); - else printf("\n%s -> %s ", filename, fn); - } - } - - // Extract block - int mode=getc(in); - if (mode=='s') - unstore(in, out); - else if (mode=='c') - paq9a(in, out, DECOMPRESS); - else - printf("\nUnsupported compression mode %c %d at %ld\n", - mode, mode, ftell(in)), exit(1); - } - printf("\n"); - if (out) fclose(out); -} - -// Command line is: paq9a {a|x|l} archive [[-option] files...]... -int main(int argc, char** argv) { - clock_t start=clock(); - - // Check command line arguments - if (argc<3 || argv[1][1] || (argv[1][0]!='a' && argv[1][0]!='x' - && argv[1][0]!='l') || (argv[1][0]=='a' && argc<4) || argv[2][0]=='-') - { - printf("paq9a archiver (C) 2007, Matt Mahoney\n" - "Free software under GPL, http://www.gnu.org/copyleft/gpl.html\n" - "\n" - "To create archive: paq9a a archive [-1..-9] [[-s|-c] files...]...\n" - " -1..-9 = use 18 to 1585 MiB memory (default -7 = 408 MiB)\n" - " -s = store, -c = compress (default)\n" - "To extract files: paq9a x archive [files...]\n" - "To list contents: paq9a l archive\n"); - exit(1); - } - - // Create archive - if (argv[1][0]=='a') { - int option = 'c'; // -c or -s - FILE* out=fopen(argv[2], "rb"); - if (out) printf("Cannot overwrite archive %s\n", argv[2]), exit(1); - out=fopen(argv[2], "wb"); - if (!out) printf("Cannot create archive %s\n", argv[2]), exit(1); - fprintf(out, "pQ9%c", 1); - int i=3; - if (argc>3 && argv[3][0]=='-' && argv[3][1]>='1' && argv[3][1]<='9' - && argv[3][2]==0) { - putc(argv[3][1], out); - MEM=1<<22+argv[3][1]-'0'; - ++i; - } - else - putc('7', out); - for (; i %ld in %1.2f sec\n", ftell(out), - double(clock()-start)/CLOCKS_PER_SEC); - } - - // List archive contents - else if (argv[1][0]=='l') - list(argv[2]); - - // Extract from archive - else if (argv[1][0]=='x') { - extract(argc, argv); - printf("%1.2f sec\n", double(clock()-start)/CLOCKS_PER_SEC); - } - - // Report statistics - delete predictor; - delete lzp; - printf("Used %d KiB memory\n", allocated>>10); - return 0; -} diff --git a/paq9a.exe b/paq9a.exe deleted file mode 100755 index ee938e2..0000000 Binary files a/paq9a.exe and /dev/null differ diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/pypaqtest.paq b/pypaqtest.paq old mode 100755 new mode 100644 diff --git a/python-src/.python-version b/python-src/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/python-src/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/Fbrowser.py b/python-src/Fbrowser.py old mode 100755 new mode 100644 similarity index 97% rename from Fbrowser.py rename to python-src/Fbrowser.py index ad6c2c5..ff21e4e --- a/Fbrowser.py +++ b/python-src/Fbrowser.py @@ -1,216 +1,216 @@ -# Path: Fbrowser.py -# Sample Music Browser & Ogranizer: Main.py - -# Importing Libraries -import sys -import os -from ScanOrg import organizer, file_scanner, DirectoryFilterProxyModel, FileFilterProxyModel - -from PyQt5.QtGui import QStandardItem , QStandardItemModel -from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QTreeView, QMessageBox, QSlider, QWidget, QFileSystemModel, QSplitter, QHBoxLayout, QFileDialog -from PyQt5.QtMultimedia import QMediaPlaylist, QMediaPlayer, QMediaContent, QAudioFormat, QAudioDeviceInfo, QAudio -from PyQt5.QtCore import QDir, QSortFilterProxyModel, Qt, QUrl #QAbstractItemModel, QAbstractProxyModel, QModelIndex, QItemSelectionModel, QItemSelection, QItemSelectionRange, QItemSelectionModel, QItemSelection, QItemSelectionRange - - -""" -# Audio Format -audio_format = QAudioFormat() -audio_format.setSampleRate(44100) -audio_format.setChannelCount(2) -audio_format.setSampleSize(16) -audio_format.setCodec('audio/pcm') -audio_format.setByteOrder(QAudioFormat.LittleEndian) -audio_format.setSampleType(QAudioFormat.SignedInt) -# Audio Device Info -device_info = QAudioDeviceInfo.defaultOutputDevice() -if not device_info.isFormatSupported(audio_format): - print('Raw audio format not supported by backend, cannot play audio.') - audio_format = device_info.nearestFormat(audio_format) - -""" - - -# Sample Music Browser Main Class -class SampleMusicBrowser(QWidget): - def __init__(self): - super().__init__() - self.organizer = organizer() - self.file_model = QStandardItemModel() - self.player = QMediaPlayer() - self.playlist = QMediaPlaylist() - self.player.setPlaylist(self.playlist) - self.tree_model = QFileSystemModel() - - self.init_ui() - - self.folder_contents_view.setEditTriggers(QTreeView.NoEditTriggers) - self.player.error.connect(self.player_error) - self.player.mediaStatusChanged.connect(self.player_media_status_changed) - self.player.setAudioRole(QAudio.MusicRole) - - def player_error(self, error): - if error == QMediaPlayer.NoError: - return - print('Error: ' + self.player.errorString()) - - def player_media_status_changed(self, status): - if status == QMediaPlayer.NoMedia: - return - print('Media Status: ' + str(status)) - - def init_ui(self): - layout = QHBoxLayout() - label = QLabel('Sample Music Browser') - layout.addWidget(label) - button = QPushButton('Exit') - button.clicked.connect(self.show_exit_popup) - layout.addWidget(button) - self.file_tree = QTreeView() - self.file_tree.setHeaderHidden(True) - self.file_tree.clicked.connect(self.change_directory) - self.folder_contents_view = QTreeView() - self.folder_contents_view.setHeaderHidden(False) - self.folder_contents_view.setRootIsDecorated(False) - self.folder_contents_view.setSortingEnabled(True) - splitter = QSplitter() - splitter.addWidget(self.file_tree) - splitter.addWidget(self.folder_contents_view) - layout.addWidget(splitter) - self.current_dir_label = QLabel() - layout.addWidget(self.current_dir_label) - up_dir_button = QPushButton('Up Directory') - up_dir_button.clicked.connect(self.go_up_directory) - layout.addWidget(up_dir_button) - back_button = QPushButton('Back') - back_button.clicked.connect(self.go_back_directory) - layout.addWidget(back_button) - forward_button = QPushButton('Forward') - forward_button.clicked.connect(self.go_forward_directory) - layout.addWidget(forward_button) - self.setLayout(layout) - self.setWindowTitle('Samples are life!') - path = QFileDialog.getExistingDirectory(self, 'Select Directory') - if path: - self.populate_file_tree(path) - play_button = QPushButton('Play') - play_button.clicked.connect(self.player.play) - layout.addWidget(play_button) - pause_button = QPushButton('Pause') - pause_button.clicked.connect(self.player.pause) - layout.addWidget(pause_button) - stop_button = QPushButton('Stop') - stop_button.clicked.connect(self.player.stop) - layout.addWidget(stop_button) - self.player.stateChanged.connect(self.player_state_changed) - self.player.positionChanged.connect(self.player_position_changed) - self.player.durationChanged.connect(self.player_duration_changed) - self.player.setVolume(50) - volume_slider = QSlider(Qt.Horizontal) - volume_slider.setRange(0, 100) - volume_slider.setValue(50) - volume_slider.valueChanged.connect(self.player.setVolume) - layout.addWidget(volume_slider) - self.playlist.currentIndexChanged.connect(self.playlist_current_index_changed) - self.playlist.currentMediaChanged.connect(self.playlist_current_media_changed) - self.playlist.mediaInserted.connect(self.playlist_media_inserted) - self.playlist.mediaRemoved.connect(self.playlist_media_removed) - self.playlist.setPlaybackMode(QMediaPlaylist.Loop) - self.folder_contents_view.doubleClicked.connect(self.play_file) - - def directory_loaded(self, path): - self.file_tree.setRootIndex(self.directory_model.mapFromSource(self.model.index(path))) - self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(self.list_model.index(path))) - - def populate_file_tree(self, path): - try: - self.tree_model.setRootPath(path) - self.file_tree.setModel(self.tree_model) - self.directory_model = DirectoryFilterProxyModel() - self.directory_model.setSourceModel(self.tree_model) - self.file_tree.setModel(self.directory_model) - self.file_tree.setRootIndex(self.directory_model.mapFromSource(self.tree_model.index(path))) - self.list_model = QFileSystemModel() - self.list_model.setRootPath(path) - self.file_filter_model = FileFilterProxyModel() - self.file_filter_model.setSourceModel(self.list_model) - self.folder_contents_view.setModel(self.file_filter_model) - self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(self.list_model.index(path))) - self.current_dir_label.setText(path) - except Exception as e: - print(f"Error Populating File Tree: {e}") - - - def show_exit_popup(self): - reply = QMessageBox.question(self, 'Exit', 'Are you sure you want to exit?', - QMessageBox.Yes | QMessageBox.No, QMessageBox.No) - if reply == QMessageBox.Yes: - sys.exit() - - def play_file(self, index): - index = self.file_filter_model.mapToSource(index) - file_path = self.list_model.filePath(index) - media = QMediaContent(QUrl.fromLocalFile(file_path)) - self.playlist.addMedia(media) - self.player.play() - - def player_state_changed(self, state): - if state == QMediaPlayer.StoppedState: - self.playlist.setCurrentIndex(0) - - def player_position_changed(self, position): - pass - def player_duration_changed(self, duration): - pass - def playlist_current_index_changed(self, index): - pass - def playlist_current_media_changed(self, media): - pass - def playlist_media_inserted(self, start, end): - pass - def playlist_media_removed(self, start, end): - pass - - def change_directory(self, index): - index = self.directory_model.mapToSource(index) - try: - file_path = self.tree_model.filePath(index) - self.list_model.setRootPath(file_path) - self.current_dir_label.setText(file_path) - self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(self.list_model.index(file_path))) - except Exception as e: - print(f"Error Changing Dirs.: {e}") - - def go_up_directory(self): - index = self.folder_contents_view.rootIndex() - index = self.file_filter_model.mapToSource(index) - index = self.file_model.index(index) - index = index.parent() - index = self.file_filter_model.mapFromSource(index) - self.folder_contents_view.setRootIndex(index) - self.current_dir_label.setText(self.model.filePath(index)) - - def go_back_directory(self): - index = self.folder_contents_view.rootIndex() - index = self.file_filter_model.mapToSource(index) - index = self.file_model.index(index) - index = index.parent() - index = self.file_filter_model.mapFromSource(index) - self.folder_contents_view.setRootIndex(index) - self.current_dir_label.setText(self.model.filePath(index)) - - def go_forward_directory(self): - index = self.folder_contents_view.rootIndex() - index = self.file_filter_model.mapToSource(index) - index = self.file_model.index(index) - index = index.parent() - index = self.file_filter_model.mapFromSource(index) - self.folder_contents_view.setRootIndex(index) - self.current_dir_label.setText(self.model.filePath(index)) - - - -if __name__ == '__main__': - app = QApplication(sys.argv) - sampleMusicBrowser = SampleMusicBrowser() - sampleMusicBrowser.show() +# Path: Fbrowser.py +# Sample Music Browser & Ogranizer: Main.py + +# Importing Libraries +import sys +import os +from ScanOrg import organizer, file_scanner, DirectoryFilterProxyModel, FileFilterProxyModel + +from PyQt5.QtGui import QStandardItem , QStandardItemModel +from PyQt5.QtWidgets import QApplication, QLabel, QPushButton, QTreeView, QMessageBox, QSlider, QWidget, QFileSystemModel, QSplitter, QHBoxLayout, QFileDialog +from PyQt5.QtMultimedia import QMediaPlaylist, QMediaPlayer, QMediaContent, QAudioFormat, QAudioDeviceInfo, QAudio +from PyQt5.QtCore import QDir, QSortFilterProxyModel, Qt, QUrl #QAbstractItemModel, QAbstractProxyModel, QModelIndex, QItemSelectionModel, QItemSelection, QItemSelectionRange, QItemSelectionModel, QItemSelection, QItemSelectionRange + + +""" +# Audio Format +audio_format = QAudioFormat() +audio_format.setSampleRate(44100) +audio_format.setChannelCount(2) +audio_format.setSampleSize(16) +audio_format.setCodec('audio/pcm') +audio_format.setByteOrder(QAudioFormat.LittleEndian) +audio_format.setSampleType(QAudioFormat.SignedInt) +# Audio Device Info +device_info = QAudioDeviceInfo.defaultOutputDevice() +if not device_info.isFormatSupported(audio_format): + print('Raw audio format not supported by backend, cannot play audio.') + audio_format = device_info.nearestFormat(audio_format) + +""" + + +# Sample Music Browser Main Class +class SampleMusicBrowser(QWidget): + def __init__(self): + super().__init__() + self.organizer = organizer() + self.file_model = QStandardItemModel() + self.player = QMediaPlayer() + self.playlist = QMediaPlaylist() + self.player.setPlaylist(self.playlist) + self.tree_model = QFileSystemModel() + + self.init_ui() + + self.folder_contents_view.setEditTriggers(QTreeView.NoEditTriggers) + self.player.error.connect(self.player_error) + self.player.mediaStatusChanged.connect(self.player_media_status_changed) + self.player.setAudioRole(QAudio.MusicRole) + + def player_error(self, error): + if error == QMediaPlayer.NoError: + return + print('Error: ' + self.player.errorString()) + + def player_media_status_changed(self, status): + if status == QMediaPlayer.NoMedia: + return + print('Media Status: ' + str(status)) + + def init_ui(self): + layout = QHBoxLayout() + label = QLabel('Sample Music Browser') + layout.addWidget(label) + button = QPushButton('Exit') + button.clicked.connect(self.show_exit_popup) + layout.addWidget(button) + self.file_tree = QTreeView() + self.file_tree.setHeaderHidden(True) + self.file_tree.clicked.connect(self.change_directory) + self.folder_contents_view = QTreeView() + self.folder_contents_view.setHeaderHidden(False) + self.folder_contents_view.setRootIsDecorated(False) + self.folder_contents_view.setSortingEnabled(True) + splitter = QSplitter() + splitter.addWidget(self.file_tree) + splitter.addWidget(self.folder_contents_view) + layout.addWidget(splitter) + self.current_dir_label = QLabel() + layout.addWidget(self.current_dir_label) + up_dir_button = QPushButton('Up Directory') + up_dir_button.clicked.connect(self.go_up_directory) + layout.addWidget(up_dir_button) + back_button = QPushButton('Back') + back_button.clicked.connect(self.go_back_directory) + layout.addWidget(back_button) + forward_button = QPushButton('Forward') + forward_button.clicked.connect(self.go_forward_directory) + layout.addWidget(forward_button) + self.setLayout(layout) + self.setWindowTitle('Samples are life!') + path = QFileDialog.getExistingDirectory(self, 'Select Directory') + if path: + self.populate_file_tree(path) + play_button = QPushButton('Play') + play_button.clicked.connect(self.player.play) + layout.addWidget(play_button) + pause_button = QPushButton('Pause') + pause_button.clicked.connect(self.player.pause) + layout.addWidget(pause_button) + stop_button = QPushButton('Stop') + stop_button.clicked.connect(self.player.stop) + layout.addWidget(stop_button) + self.player.stateChanged.connect(self.player_state_changed) + self.player.positionChanged.connect(self.player_position_changed) + self.player.durationChanged.connect(self.player_duration_changed) + self.player.setVolume(50) + volume_slider = QSlider(Qt.Horizontal) + volume_slider.setRange(0, 100) + volume_slider.setValue(50) + volume_slider.valueChanged.connect(self.player.setVolume) + layout.addWidget(volume_slider) + self.playlist.currentIndexChanged.connect(self.playlist_current_index_changed) + self.playlist.currentMediaChanged.connect(self.playlist_current_media_changed) + self.playlist.mediaInserted.connect(self.playlist_media_inserted) + self.playlist.mediaRemoved.connect(self.playlist_media_removed) + self.playlist.setPlaybackMode(QMediaPlaylist.Loop) + self.folder_contents_view.doubleClicked.connect(self.play_file) + + def directory_loaded(self, path): + self.file_tree.setRootIndex(self.directory_model.mapFromSource(self.model.index(path))) + self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(self.list_model.index(path))) + + def populate_file_tree(self, path): + try: + self.tree_model.setRootPath(path) + self.file_tree.setModel(self.tree_model) + self.directory_model = DirectoryFilterProxyModel() + self.directory_model.setSourceModel(self.tree_model) + self.file_tree.setModel(self.directory_model) + self.file_tree.setRootIndex(self.directory_model.mapFromSource(self.tree_model.index(path))) + self.list_model = QFileSystemModel() + self.list_model.setRootPath(path) + self.file_filter_model = FileFilterProxyModel() + self.file_filter_model.setSourceModel(self.list_model) + self.folder_contents_view.setModel(self.file_filter_model) + self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(self.list_model.index(path))) + self.current_dir_label.setText(path) + except Exception as e: + print(f"Error Populating File Tree: {e}") + + + def show_exit_popup(self): + reply = QMessageBox.question(self, 'Exit', 'Are you sure you want to exit?', + QMessageBox.Yes | QMessageBox.No, QMessageBox.No) + if reply == QMessageBox.Yes: + sys.exit() + + def play_file(self, index): + index = self.file_filter_model.mapToSource(index) + file_path = self.list_model.filePath(index) + media = QMediaContent(QUrl.fromLocalFile(file_path)) + self.playlist.addMedia(media) + self.player.play() + + def player_state_changed(self, state): + if state == QMediaPlayer.StoppedState: + self.playlist.setCurrentIndex(0) + + def player_position_changed(self, position): + pass + def player_duration_changed(self, duration): + pass + def playlist_current_index_changed(self, index): + pass + def playlist_current_media_changed(self, media): + pass + def playlist_media_inserted(self, start, end): + pass + def playlist_media_removed(self, start, end): + pass + + def change_directory(self, index): + index = self.directory_model.mapToSource(index) + try: + file_path = self.tree_model.filePath(index) + self.list_model.setRootPath(file_path) + self.current_dir_label.setText(file_path) + self.folder_contents_view.setRootIndex(self.file_filter_model.mapFromSource(self.list_model.index(file_path))) + except Exception as e: + print(f"Error Changing Dirs.: {e}") + + def go_up_directory(self): + index = self.folder_contents_view.rootIndex() + index = self.file_filter_model.mapToSource(index) + index = self.file_model.index(index) + index = index.parent() + index = self.file_filter_model.mapFromSource(index) + self.folder_contents_view.setRootIndex(index) + self.current_dir_label.setText(self.model.filePath(index)) + + def go_back_directory(self): + index = self.folder_contents_view.rootIndex() + index = self.file_filter_model.mapToSource(index) + index = self.file_model.index(index) + index = index.parent() + index = self.file_filter_model.mapFromSource(index) + self.folder_contents_view.setRootIndex(index) + self.current_dir_label.setText(self.model.filePath(index)) + + def go_forward_directory(self): + index = self.folder_contents_view.rootIndex() + index = self.file_filter_model.mapToSource(index) + index = self.file_model.index(index) + index = index.parent() + index = self.file_filter_model.mapFromSource(index) + self.folder_contents_view.setRootIndex(index) + self.current_dir_label.setText(self.model.filePath(index)) + + + +if __name__ == '__main__': + app = QApplication(sys.argv) + sampleMusicBrowser = SampleMusicBrowser() + sampleMusicBrowser.show() sys.exit(app.exec_()) \ No newline at end of file diff --git a/MidPlay.py b/python-src/MidPlay.py old mode 100755 new mode 100644 similarity index 96% rename from MidPlay.py rename to python-src/MidPlay.py index b01388f..211199b --- a/MidPlay.py +++ b/python-src/MidPlay.py @@ -1,261 +1,261 @@ -#Path: MidPlay.py -# Description: A class to play MIDI files and a class to view MIDI files - -import pygame -# Imports -import mido -import fluidsynth -import os -from PyQt5.QtWidgets import (QApplication, QLabel, QListWidget, QFileDialog, QMessageBox, QWidget, QPushButton, QHBoxLayout, - QVBoxLayout, - QProgressBar, - QSlider) # structured for readability and to avoid long lines and it annoys my friend XD -from PyQt5.QtCore import QTimer, Qt -import threading -import cProfile # profiler remove for production - -#profiler remove for production -def start_profiling(): - global pr - pr = cProfile.Profile() - pr.enable() - -def stop_profiling(): - pr.disable() - pr.dump_stats('midi_profile.out') - -# The pygame.mixer.init() call is necessary to initialize the mixer module -# before any sound can be played. The pygame.init() call is necessary maybe. -pygame.mixer.init() -pygame.init() - -class MidPlayGUI(QWidget): - def __init__(self): - super().__init__() - self.player = MidPlay() - self.current_midi_label = QLabel() - self.playlist_widget = QListWidget() - self.setWindowTitle("MidPlay - Midi Player") - self.init_ui() - self.timer = QTimer() - self.timer.timeout.connect(self.handle_song_end) - self.timer.start(1000) - - def set_volume(self, value): - volume = value / 100 - pygame.mixer.music.set_volume(volume) - - def update_progress(self): - if self.player.current_midi: - current_time = pygame.mixer.music.get_pos() / 1000 # get_pos returns time in milliseconds NOT SECONDS! - total_time = self.calculate_midi_duration(self.player.current_midi) - progress = current_time / total_time * 100 - self.progress_bar.setValue(int(progress)) - - def calculate_midi_duration(self, midi_file): - total_duration = 0 - for track in midi_file.tracks: - track_duration = max([msg.time for msg in track]) if track else 0 - total_duration = max(total_duration, track_duration) - return total_duration - - def handle_song_end(self): - if self.player.playing and not pygame.mixer.music.get_busy(): - self.player.next_song() - if self.player.playlist: - self.player.current_index %= len(self.player.playlist) - filepath = self.player.playlist[self.player.current_index] - filename = os.path.basename(filepath) - self.current_midi_label.setText(f"Current MIDI: {filename}") - self.update_progress() - - - def init_ui(self): - #label = QLabel("MidPlay - Midi player") - #label.setStyleSheet("font-size: 20px; font-weight: bold;") - - self.progress_bar = QProgressBar() - self.volume_slider = QSlider(Qt.Horizontal) - self.volume_slider.setMinimum(0) - self.volume_slider.setMaximum(100) - self.volume_slider.setValue(100) - self.volume_slider.valueChanged.connect(self.set_volume) - self.current_midi_label.setText("Current MIDI: None") - - - self.playlist_widget.itemDoubleClicked.connect(self.play_selected_song) - pygame.mixer.music.set_endevent(pygame.USEREVENT) - - # Buttons - play_button = QPushButton("Play") - play_button.clicked.connect(self.player.play_midi) - - pause_button = QPushButton("Pause") - pause_button.clicked.connect(self.player.pause) - - stop_button = QPushButton("Stop") - stop_button.clicked.connect(self.player.stop) - - next_button = QPushButton("Next") - next_button.clicked.connect(self.player.next_song) - - back_button = QPushButton("Back") - back_button.clicked.connect(self.previous_song) - - add_button = QPushButton("Add to Playlist") - add_button.clicked.connect(self.load_midi_file) - - add_folder_button = QPushButton("Add Folder to Playlist") - add_folder_button.clicked.connect(self.load_folder) - - clear_button = QPushButton("Clear Playlist") - clear_button.clicked.connect(self.clear_playlist) - - # Window layout - layout = QVBoxLayout() - layout.addWidget(self.current_midi_label) - layout.addWidget(self.playlist_widget) - layout.addWidget(self.progress_bar) - layout.addWidget(self.volume_slider) - layout.addWidget(play_button) - layout.addWidget(pause_button) - layout.addWidget(stop_button) - layout.addWidget(next_button) - layout.addWidget(back_button) - layout.addWidget(add_button) - layout.addWidget(add_folder_button) - layout.addWidget(clear_button) - - progress_volume_layout = QHBoxLayout() - progress_volume_layout.addWidget(self.progress_bar) - progress_volume_layout.addWidget(self.volume_slider) - layout.addLayout(progress_volume_layout) - self.setLayout(layout) - - # Event handlers - def play_selected_song(self, item): - index = self.playlist_widget.row(item) - self.current_index = index - filepath = self.player.playlist[self.current_index] - self.player.load_midi(filepath) - self.player.play_midi() - pygame.mixer.music.set_endevent(pygame.USEREVENT) - filename = os.path.basename(filepath) - self.current_midi_label.setText(f"Current MIDI: {filename}") - - def load_midi_file(self): - filepath, _ = QFileDialog.getOpenFileName(self, "Select MIDI File", filter="MIDI files (*.mid *.midi)") - if filepath: - filename = os.path.basename(filepath) - self.player.load_midi(filepath) - self.current_midi_label.setText(f"Current MIDI: {filename}") - self.player.play_midi() - self.playlist_widget.addItem(filename) - self.player.add_to_playlist(filepath) - - def load_folder(self): - folder = QFileDialog.getExistingDirectory(self, "Select Folder") - if folder: - for file in os.listdir(folder): - if file.endswith((".midi", ".mid")): - filepath = os.path.join(folder, file) - self.playlist_widget.addItem(file) - self.player.add_to_playlist(filepath) # Only add to playlist, don't load immediately!!!!!!!!!!!!!!! - - #probably should be in the MidPlay class - - - def previous_song(self): - if self.player.playlist: - filepath = self.player.playlist[self.current_index] - filename = os.path.basename(filepath) - self.player.current_index = (self.player.current_index - 1) % len(self.player.playlist) - self.current_midi_label.setText(f"Current MIDI: {filename}") - self.player.play_midi() - - def clear_playlist(self): - self.player.clear_playlist() - self.playlist_widget.clear() - - def closeEvent(self, event): - confirmation = QMessageBox.question(self, "Exit Confirmation", "Are you sure you want to exit?", QMessageBox.Yes | QMessageBox.No) - if confirmation == QMessageBox.Yes: - pygame.mixer.quit() - pygame.quit() - event.accept() - else: - event.ignore() - -class MidPlay: - """The Heart of Midi Playback""" - - def __init__(self): - self.playlist = [] - self.current_midi = None - self.playing = False - self.current_index = 0 - - def load_midi(self, filepath: str) -> None: - def load(): - try: - self.current_midi = mido.MidiFile(filepath) - pygame.mixer.music.load(filepath) - except Exception as e: - print(f"Error loading MIDI: {e}") - threading.Thread(target=load).start() - - def add_to_playlist(self, filepath: str) -> None: - self.playlist.append(filepath) - - def clear_playlist(self) -> None: - self.playlist = [] - - def play_midi(self) -> None: - def play(): - if self.current_midi: - self.current_midi.instruments[0].synthesize() - pygame.mixer.music.play() - self.playing = True - pygame.mixer.music.set_endevent(pygame.USEREVENT) - else: - print("No MIDI file loaded") - threading.Thread(target=play).start() - - def pause(self) -> None: - pygame.mixer.music.pause() - self.playing = False - - def stop(self) -> None: - pygame.mixer.music.stop() - self.playing = False - - - def next_song(self) -> None: - #print("Debug: next_song() called", self.playlist) debug line - if self.playlist: - self.current_index = (self.current_index + 1) % len(self.playlist) - filepath = self.playlist[self.current_index] - - # If a new MIDI was loaded before the last one ended, respect that as the new playlist start - if self.current_midi and self.playing: - # print("Debug: New MIDI loaded before last one ended") # debug line - self.load_midi(filepath) - self.play_midi() - # print("Debug: Filepath:", filepath) # debug line - # print("Debug: Current MIDI:", self.current_midi) # debug line - -""" -if __name__ == '__main__': - app = QApplication([]) - player_gui = MidPlayGUI() - player_gui.show() - running = True - while True: - for event in pygame.event.get(): - if event.type == pygame.USEREVENT: - player_gui.player.next_song() - if event.type == pygame.QUIT: - running = False - break - app.exec_() +#Path: MidPlay.py +# Description: A class to play MIDI files and a class to view MIDI files + +import pygame +# Imports +import mido +import fluidsynth +import os +from PyQt6.QtWidgets import (QApplication, QLabel, QListWidget, QFileDialog, QMessageBox, QWidget, QPushButton, QHBoxLayout, + QVBoxLayout, + QProgressBar, + QSlider) # structured for readability and to avoid long lines and it annoys my friend XD +from PyQt6.QtCore import QTimer, Qt +import threading +import cProfile # profiler remove for production + +#profiler remove for production +def start_profiling(): + global pr + pr = cProfile.Profile() + pr.enable() + +def stop_profiling(): + pr.disable() + pr.dump_stats('midi_profile.out') + +# The pygame.mixer.init() call is necessary to initialize the mixer module +# before any sound can be played. The pygame.init() call is necessary maybe. +pygame.mixer.init() +pygame.init() + +class MidPlayGUI(QWidget): + def __init__(self): + super().__init__() + self.player = MidPlay() + self.current_midi_label = QLabel() + self.playlist_widget = QListWidget() + self.setWindowTitle("MidPlay - Midi Player") + self.init_ui() + self.timer = QTimer() + self.timer.timeout.connect(self.handle_song_end) + self.timer.start(1000) + + def set_volume(self, value): + volume = value / 100 + pygame.mixer.music.set_volume(volume) + + def update_progress(self): + if self.player.current_midi: + current_time = pygame.mixer.music.get_pos() / 1000 # get_pos returns time in milliseconds NOT SECONDS! + total_time = self.calculate_midi_duration(self.player.current_midi) + progress = current_time / total_time * 100 + self.progress_bar.setValue(int(progress)) + + def calculate_midi_duration(self, midi_file): + total_duration = 0 + for track in midi_file.tracks: + track_duration = max([msg.time for msg in track]) if track else 0 + total_duration = max(total_duration, track_duration) + return total_duration + + def handle_song_end(self): + if self.player.playing and not pygame.mixer.music.get_busy(): + self.player.next_song() + if self.player.playlist: + self.player.current_index %= len(self.player.playlist) + filepath = self.player.playlist[self.player.current_index] + filename = os.path.basename(filepath) + self.current_midi_label.setText(f"Current MIDI: {filename}") + self.update_progress() + + + def init_ui(self): + #label = QLabel("MidPlay - Midi player") + #label.setStyleSheet("font-size: 20px; font-weight: bold;") + + self.progress_bar = QProgressBar() + self.volume_slider = QSlider(Qt.Horizontal) + self.volume_slider.setMinimum(0) + self.volume_slider.setMaximum(100) + self.volume_slider.setValue(100) + self.volume_slider.valueChanged.connect(self.set_volume) + self.current_midi_label.setText("Current MIDI: None") + + + self.playlist_widget.itemDoubleClicked.connect(self.play_selected_song) + pygame.mixer.music.set_endevent(pygame.USEREVENT) + + # Buttons + play_button = QPushButton("Play") + play_button.clicked.connect(self.player.play_midi) + + pause_button = QPushButton("Pause") + pause_button.clicked.connect(self.player.pause) + + stop_button = QPushButton("Stop") + stop_button.clicked.connect(self.player.stop) + + next_button = QPushButton("Next") + next_button.clicked.connect(self.player.next_song) + + back_button = QPushButton("Back") + back_button.clicked.connect(self.previous_song) + + add_button = QPushButton("Add to Playlist") + add_button.clicked.connect(self.load_midi_file) + + add_folder_button = QPushButton("Add Folder to Playlist") + add_folder_button.clicked.connect(self.load_folder) + + clear_button = QPushButton("Clear Playlist") + clear_button.clicked.connect(self.clear_playlist) + + # Window layout + layout = QVBoxLayout() + layout.addWidget(self.current_midi_label) + layout.addWidget(self.playlist_widget) + layout.addWidget(self.progress_bar) + layout.addWidget(self.volume_slider) + layout.addWidget(play_button) + layout.addWidget(pause_button) + layout.addWidget(stop_button) + layout.addWidget(next_button) + layout.addWidget(back_button) + layout.addWidget(add_button) + layout.addWidget(add_folder_button) + layout.addWidget(clear_button) + + progress_volume_layout = QHBoxLayout() + progress_volume_layout.addWidget(self.progress_bar) + progress_volume_layout.addWidget(self.volume_slider) + layout.addLayout(progress_volume_layout) + self.setLayout(layout) + + # Event handlers + def play_selected_song(self, item): + index = self.playlist_widget.row(item) + self.current_index = index + filepath = self.player.playlist[self.current_index] + self.player.load_midi(filepath) + self.player.play_midi() + pygame.mixer.music.set_endevent(pygame.USEREVENT) + filename = os.path.basename(filepath) + self.current_midi_label.setText(f"Current MIDI: {filename}") + + def load_midi_file(self): + filepath, _ = QFileDialog.getOpenFileName(self, "Select MIDI File", filter="MIDI files (*.mid *.midi)") + if filepath: + filename = os.path.basename(filepath) + self.player.load_midi(filepath) + self.current_midi_label.setText(f"Current MIDI: {filename}") + self.player.play_midi() + self.playlist_widget.addItem(filename) + self.player.add_to_playlist(filepath) + + def load_folder(self): + folder = QFileDialog.getExistingDirectory(self, "Select Folder") + if folder: + for file in os.listdir(folder): + if file.endswith((".midi", ".mid")): + filepath = os.path.join(folder, file) + self.playlist_widget.addItem(file) + self.player.add_to_playlist(filepath) # Only add to playlist, don't load immediately!!!!!!!!!!!!!!! + + #probably should be in the MidPlay class + + + def previous_song(self): + if self.player.playlist: + filepath = self.player.playlist[self.current_index] + filename = os.path.basename(filepath) + self.player.current_index = (self.player.current_index - 1) % len(self.player.playlist) + self.current_midi_label.setText(f"Current MIDI: {filename}") + self.player.play_midi() + + def clear_playlist(self): + self.player.clear_playlist() + self.playlist_widget.clear() + + def closeEvent(self, event): + confirmation = QMessageBox.question(self, "Exit Confirmation", "Are you sure you want to exit?", QMessageBox.Yes | QMessageBox.No) + if confirmation == QMessageBox.Yes: + pygame.mixer.quit() + pygame.quit() + event.accept() + else: + event.ignore() + +class MidPlay: + """The Heart of Midi Playback""" + + def __init__(self): + self.playlist = [] + self.current_midi = None + self.playing = False + self.current_index = 0 + + def load_midi(self, filepath: str) -> None: + def load(): + try: + self.current_midi = mido.MidiFile(filepath) + pygame.mixer.music.load(filepath) + except Exception as e: + print(f"Error loading MIDI: {e}") + threading.Thread(target=load).start() + + def add_to_playlist(self, filepath: str) -> None: + self.playlist.append(filepath) + + def clear_playlist(self) -> None: + self.playlist = [] + + def play_midi(self) -> None: + def play(): + if self.current_midi: + self.current_midi.instruments[0].synthesize() + pygame.mixer.music.play() + self.playing = True + pygame.mixer.music.set_endevent(pygame.USEREVENT) + else: + print("No MIDI file loaded") + threading.Thread(target=play).start() + + def pause(self) -> None: + pygame.mixer.music.pause() + self.playing = False + + def stop(self) -> None: + pygame.mixer.music.stop() + self.playing = False + + + def next_song(self) -> None: + #print("Debug: next_song() called", self.playlist) debug line + if self.playlist: + self.current_index = (self.current_index + 1) % len(self.playlist) + filepath = self.playlist[self.current_index] + + # If a new MIDI was loaded before the last one ended, respect that as the new playlist start + if self.current_midi and self.playing: + # print("Debug: New MIDI loaded before last one ended") # debug line + self.load_midi(filepath) + self.play_midi() + # print("Debug: Filepath:", filepath) # debug line + # print("Debug: Current MIDI:", self.current_midi) # debug line + +""" +if __name__ == '__main__': + app = QApplication([]) + player_gui = MidPlayGUI() + player_gui.show() + running = True + while True: + for event in pygame.event.get(): + if event.type == pygame.USEREVENT: + player_gui.player.next_song() + if event.type == pygame.QUIT: + running = False + break + app.exec_() """ \ No newline at end of file diff --git a/ScanOrg.py b/python-src/ScanOrg.py old mode 100755 new mode 100644 similarity index 97% rename from ScanOrg.py rename to python-src/ScanOrg.py index b19fbb3..e051c47 --- a/ScanOrg.py +++ b/python-src/ScanOrg.py @@ -1,187 +1,187 @@ -#Path: ScanOrg.py -# Description: A class to scan and organize music files - -import concurrent.futures -import threading -import queue -import zipfile -import py7zr -import rarfile -import os -import mutagen -from PyQt6.QtCore import Qt, QSortFilterProxyModel, pyqtSignal - -# Directory Filter Proxy Model -class DirectoryFilterProxyModel(QSortFilterProxyModel): - def __init__(self): - super().__init__() - self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) - self.setFilterKeyColumn(0) - def filterAcceptsRow(self, source_row, source_parent): - index = self.sourceModel().index(source_row, 0, source_parent) - return self.sourceModel().isDir(index) - -# File Filter Proxy Model -class FileFilterProxyModel(QSortFilterProxyModel): - def __init__(self): - super().__init__() - self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) - self.setFilterKeyColumn(0) - self.allowed_extensions = ['.zip', '.mp3', '.wav', '.flac', '.mid', '.midi', '.aiff', '.aif', '.aifc', '.au', '.snd', '.wv', '.wma', '.m4a'] - - def filterAcceptsRow(self, source_row, source_parent): - index = self.sourceModel().index(source_row, 0, source_parent) - if self.sourceModel().isDir(index): - return True - else: - return self.sourceModel().fileName(index).endswith(tuple(self.allowed_extensions)) - -# File Scan and Organize -class file_scanner: - def __init__(self): - self.file_list = [] - self.cache = {} - - def scan(self, path): - def background_scan(self, path): - if path in self.cache: - return self.cache[path] - - file_list = [] - dirs_queue = queue.Queue() - dirs_queue.put(path) - - while not dirs_queue.empty(): - current_path = dirs_queue.get() - try: - for root, dirs, files in os.walk(current_path): - for dir in dirs: - dirs_queue.put(os.path.join(root, dir)) - for file in files: - if file.endswith(('.mp3', '.wav', '.flac', '.mid', '.midi', '.aiff', '.aif', '.aifc', '.au', '.snd', '.wv', '.wma', '.m4a')): - file_list.append(os.path.join(root, file)) - self.cache[current_path] = file_list - except (IOError, PermissionError, FileNotFoundError, OSError) as e: - print(f"Error Scanning Files: {e}") - - return file_list - - file_list = [] - thread = threading.Thread(target=background_scan, args=(path, file_list)) - thread.start() - return file_list - - def get_file_list(self): - return self.file_list - - def clear_file_list(self): - self.file_list = [] - -class extractor: - def zipviewer(self, index, file_filter_model, list_model, extraction_directory): - if index.isValid() and extraction_directory is not None: - index = file_filter_model.mapToSource(index) - file_path = list_model.filePath(index) - try: - if file_path.endswith(('.zip', '.rar', '.7z')): - with zipfile.ZipFile(file_path, 'r') as zip_ref: - for filename in zip_ref.namelist(): - destination = os.path.join(extraction_directory, filename) - zip_ref.extract(filename, extraction_directory) - except (zipfile.BadZipFile, OSError, zipfile.LargeZipFile, zipfile.LargeZipFile) as e: - print(f"Extraction Error: {e}") - return - - -class organizer: - global metadata_queue - metadata_queue = queue.Queue() - def __init__(self): - self.file_list = [] - self.artist_list = [] - self.album_list = [] - self.genre_list = [] - self.year_list = [] - self.file_scanner = file_scanner() - self.file_info_cache = {} - - def scan(self, path): - if path in self.file_scanner.cache: - self.file_list = self.file_scanner.cache[path] - else: - self.file_list = self.file_scanner.scan(path) - - def get_file_list(self): - return self.file_list - def clear_file_list(self): - self.file_list = [] - def get_artist_list(self): - return self.artist_list - def get_album_list(self): - return self.album_list - def get_genre_list(self): - return self.genre_list - def get_year_list(self): - return self.year_list - def clear_artist_list(self): - self.artist_list = [] - def clear_album_list(self): - self.album_list = [] - def clear_genre_list(self): - self.genre_list = [] - def clear_year_list(self): - self.year_list = [] - - def organize(self): - results_queue = queue.Queue() - metadata = pyqtSignal(dict) - with concurrent.futures.ThreadPoolExecutor() as executor: - futures = [] - for file in self.file_list: - futures.append(executor.submit(self.get_file_info, file, results_queue)) - - for future in concurrent.futures.as_completed(futures): - try: - metadata = future.result() - if metadata['artist'] not in self.artist_list: - self.artist_list.append(metadata['artist']) - if metadata['album'] not in self.album_list: - self.album_list.append(metadata['album']) - if metadata['genre'] not in self.genre_list: - self.genre_list.append(metadata['genre']) - if metadata['year'] not in self.year_list: - self.year_list.append(metadata['year']) - except mutagen.mp3.HeaderNotFoundError: - print('Error: ' + file) - continue - while not metadata_queue.put(metadata): - pass - - def get_file_info(self, file, results_queue): - try: - audio = mutagen.File(file) - artist = audio['artist'][0] - album = audio['album'][0] - genre = audio['genre'][0] - year = audio['date'][0] - if artist not in self.artist_list: - self.artist_list.append(artist) - if album not in self.album_list: - self.album_list.append(album) - if genre not in self.genre_list: - self.genre_list.append(genre) - if year not in self.year_list: - self.year_list.append(year) - metadata = { - 'artist': artist, - 'album': album, - 'genre': genre, - 'year': year - } - self.metadata_extracted.emit(metadata) - except Exception as e: - results_queue.put(None) - print('Error: ' + file) - if os.path.splitext(file)[1] == ('.mp3', '.wav', '.flac', '.m4a', '.wma', 'mid', '.midi'): - self.organize_audio() +#Path: ScanOrg.py +# Description: A class to scan and organize music files + +import concurrent.futures +import threading +import queue +import zipfile +import py7zr +import rarfile +import os +import mutagen +from PyQt6.QtCore import Qt, QSortFilterProxyModel, pyqtSignal + +# Directory Filter Proxy Model +class DirectoryFilterProxyModel(QSortFilterProxyModel): + def __init__(self): + super().__init__() + self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) + self.setFilterKeyColumn(0) + def filterAcceptsRow(self, source_row, source_parent): + index = self.sourceModel().index(source_row, 0, source_parent) + return self.sourceModel().isDir(index) + +# File Filter Proxy Model +class FileFilterProxyModel(QSortFilterProxyModel): + def __init__(self): + super().__init__() + self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) + self.setFilterKeyColumn(0) + self.allowed_extensions = ['.zip', '.mp3', '.wav', '.flac', '.mid', '.midi', '.aiff', '.aif', '.aifc', '.au', '.snd', '.wv', '.wma', '.m4a'] + + def filterAcceptsRow(self, source_row, source_parent): + index = self.sourceModel().index(source_row, 0, source_parent) + if self.sourceModel().isDir(index): + return True + else: + return self.sourceModel().fileName(index).endswith(tuple(self.allowed_extensions)) + +# File Scan and Organize +class file_scanner: + def __init__(self): + self.file_list = [] + self.cache = {} + + def scan(self, path): + def background_scan(self, path): + if path in self.cache: + return self.cache[path] + + file_list = [] + dirs_queue = queue.Queue() + dirs_queue.put(path) + + while not dirs_queue.empty(): + current_path = dirs_queue.get() + try: + for root, dirs, files in os.walk(current_path): + for dir in dirs: + dirs_queue.put(os.path.join(root, dir)) + for file in files: + if file.endswith(('.mp3', '.wav', '.flac', '.mid', '.midi', '.aiff', '.aif', '.aifc', '.au', '.snd', '.wv', '.wma', '.m4a')): + file_list.append(os.path.join(root, file)) + self.cache[current_path] = file_list + except (IOError, PermissionError, FileNotFoundError, OSError) as e: + print(f"Error Scanning Files: {e}") + + return file_list + + file_list = [] + thread = threading.Thread(target=background_scan, args=(path, file_list)) + thread.start() + return file_list + + def get_file_list(self): + return self.file_list + + def clear_file_list(self): + self.file_list = [] + +class extractor: + def zipviewer(self, index, file_filter_model, list_model, extraction_directory): + if index.isValid() and extraction_directory is not None: + index = file_filter_model.mapToSource(index) + file_path = list_model.filePath(index) + try: + if file_path.endswith(('.zip', '.rar', '.7z')): + with zipfile.ZipFile(file_path, 'r') as zip_ref: + for filename in zip_ref.namelist(): + destination = os.path.join(extraction_directory, filename) + zip_ref.extract(filename, extraction_directory) + except (zipfile.BadZipFile, OSError, zipfile.LargeZipFile, zipfile.LargeZipFile) as e: + print(f"Extraction Error: {e}") + return + + +class organizer: + global metadata_queue + metadata_queue = queue.Queue() + def __init__(self): + self.file_list = [] + self.artist_list = [] + self.album_list = [] + self.genre_list = [] + self.year_list = [] + self.file_scanner = file_scanner() + self.file_info_cache = {} + + def scan(self, path): + if path in self.file_scanner.cache: + self.file_list = self.file_scanner.cache[path] + else: + self.file_list = self.file_scanner.scan(path) + + def get_file_list(self): + return self.file_list + def clear_file_list(self): + self.file_list = [] + def get_artist_list(self): + return self.artist_list + def get_album_list(self): + return self.album_list + def get_genre_list(self): + return self.genre_list + def get_year_list(self): + return self.year_list + def clear_artist_list(self): + self.artist_list = [] + def clear_album_list(self): + self.album_list = [] + def clear_genre_list(self): + self.genre_list = [] + def clear_year_list(self): + self.year_list = [] + + def organize(self): + results_queue = queue.Queue() + metadata = pyqtSignal(dict) + with concurrent.futures.ThreadPoolExecutor() as executor: + futures = [] + for file in self.file_list: + futures.append(executor.submit(self.get_file_info, file, results_queue)) + + for future in concurrent.futures.as_completed(futures): + try: + metadata = future.result() + if metadata['artist'] not in self.artist_list: + self.artist_list.append(metadata['artist']) + if metadata['album'] not in self.album_list: + self.album_list.append(metadata['album']) + if metadata['genre'] not in self.genre_list: + self.genre_list.append(metadata['genre']) + if metadata['year'] not in self.year_list: + self.year_list.append(metadata['year']) + except mutagen.mp3.HeaderNotFoundError: + print('Error: ' + file) + continue + while not metadata_queue.put(metadata): + pass + + def get_file_info(self, file, results_queue): + try: + audio = mutagen.File(file) + artist = audio['artist'][0] + album = audio['album'][0] + genre = audio['genre'][0] + year = audio['date'][0] + if artist not in self.artist_list: + self.artist_list.append(artist) + if album not in self.album_list: + self.album_list.append(album) + if genre not in self.genre_list: + self.genre_list.append(genre) + if year not in self.year_list: + self.year_list.append(year) + metadata = { + 'artist': artist, + 'album': album, + 'genre': genre, + 'year': year + } + self.metadata_extracted.emit(metadata) + except Exception as e: + results_queue.put(None) + print('Error: ' + file) + if os.path.splitext(file)[1] == ('.mp3', '.wav', '.flac', '.m4a', '.wma', 'mid', '.midi'): + self.organize_audio() audio = mutagen.File(file) \ No newline at end of file diff --git a/ScanOrg100.py b/python-src/ScanOrg100.py similarity index 100% rename from ScanOrg100.py rename to python-src/ScanOrg100.py diff --git a/compression.py b/python-src/archive_compression.py old mode 100755 new mode 100644 similarity index 100% rename from compression.py rename to python-src/archive_compression.py diff --git a/bsshpy.py b/python-src/bsshpy.py similarity index 100% rename from bsshpy.py rename to python-src/bsshpy.py diff --git a/extraction.py b/python-src/extraction.py old mode 100755 new mode 100644 similarity index 100% rename from extraction.py rename to python-src/extraction.py diff --git a/maintest.py b/python-src/maintest.py similarity index 100% rename from maintest.py rename to python-src/maintest.py diff --git a/paqtest.py b/python-src/paqtest.py old mode 100755 new mode 100644 similarity index 100% rename from paqtest.py rename to python-src/paqtest.py diff --git a/readme.txt b/python-src/readme.txt old mode 100755 new mode 100644 similarity index 97% rename from readme.txt rename to python-src/readme.txt index 1319b7e..65e8151 --- a/readme.txt +++ b/python-src/readme.txt @@ -1,9 +1,9 @@ -<<<<<<< HEAD -FBroswer is a sample and loop organizer and browser with extra features -such as the ability to sample that audio you're checking out -full midi play back support with soundfont controls -a timer to help balance work life and personal life -a way to keep track of what you're listening to -======= -fbrowser is a sample and loops audio organizer and browser ->>>>>>> 1ee9caf82243dd45a72a97bf6c5de681139670e2 +<<<<<<< HEAD +FBroswer is a sample and loop organizer and browser with extra features +such as the ability to sample that audio you're checking out +full midi play back support with soundfont controls +a timer to help balance work life and personal life +a way to keep track of what you're listening to +======= +fbrowser is a sample and loops audio organizer and browser +>>>>>>> 1ee9caf82243dd45a72a97bf6c5de681139670e2 diff --git a/requirements.txt b/python-src/requirements.txt similarity index 65% rename from requirements.txt rename to python-src/requirements.txt index 9fc0bbe..ea4cf44 100644 --- a/requirements.txt +++ b/python-src/requirements.txt @@ -1,13 +1,12 @@ PyQT5 -PyQt5-tools PyQt6 -PyQt6-tools matplotlib rarfile py7zr pygame mido +mutagen numpy crypto django -pyFluidSynth \ No newline at end of file +pyFluidSynth diff --git a/stanzip.py b/python-src/stanzip.py old mode 100755 new mode 100644 similarity index 100% rename from stanzip.py rename to python-src/stanzip.py diff --git a/test.py b/python-src/test.py old mode 100755 new mode 100644 similarity index 97% rename from test.py rename to python-src/test.py index 1ccb84d..aa75278 --- a/test.py +++ b/python-src/test.py @@ -1,168 +1,168 @@ -import mido as pretty_midi -import random -import tkinter as tk -from tkinter import ttk, filedialog -import pygame -import pypianoroll # type: ignore -from icecream import ic # type: ignore - -class midgen: - - def __init__(self, status_label: ttk.Label): - self.status_label = status_label - self.scales = self.scales() - - def scales(self): - scales = { - "Major": [0, 2, 4, 5, 7, 9, 11], - "Minor": [0, 2, 3, 5, 7, 8, 10], - "Pentatonic": [0, 2, 4, 7, 9], - "Blues": [0, 3, 5, 6, 7, 10], - "Whole Tone": [0, 2, 4, 6, 8, 10], - "Chromatic": [i for i in range(12)], - "Octatonic": [0, 1, 3, 4, 6, 7, 9, 10], - "Harmonic Minor": [0, 2, 3, 5, 7, 8, 11], - "Melodic Minor": [0, 2, 3, 5, 7, 9, 11], - "Dorian": [0, 2, 3, 5, 7, 9, 10], - "Phrygian": [0, 1, 3, 5, 7, 8, 10], - "Lydian": [0, 2, 4, 6, 7, 9, 11], - "Mixolydian": [0, 2, 4, 5, 7, 9, 10], - "Locrian": [0, 1, 3, 5, 6, 8, 10], - "Diminished": [0, 2, 3, 5, 6, 8, 9, 11], - "Whole Half Diminished": [0, 2, 3, 5, 6, 8, 9, 11], - "Arabian": [0, 2, 4, 5, 6, 8, 10], - "Hungarian Minor": [0, 2, 3, 6, 7, 8, 11], - "Enigmatic": [0, 1, 4, 6, 8, 10, 11], - "Neapolitan Major": [0, 1, 3, 5, 7, 9, 11], - "Neapolitan Minor": [0, 1, 3, 5, 7, 8, 11], - "Bluesy": [0, 3, 5, 6, 7, 10], - "Hawaiian": [0, 2, 3, 7, 9], - "Japanese": [0, 1, 5, 7, 8], - "Chinese": [0, 4, 6, 7, 11], - "Gypsy": [0, 2, 3, 6, 7, 8, 10], - "Hirojoshi": [0, 2, 3, 7, 8], - "In Sen": [0, 1, 5, 7, 10], - "Iwato": [0, 1, 5, 6, 10], - "Kumoi": [0, 2, 3, 7, 9], - "Pelog": [0, 1, 3, 7, 8], - "Ryukyu": [0, 4, 5, 7, 11], - "Spanish": [0, 1, 3, 4, 5, 6, 8, 10], - "Todi": [0, 1, 3, 6, 7, 8, 11], - "Yo": [0, 2, 5, 7, 9] - } - return scales - - - def generate_midi(self): - self.status_label.config(text='Generating MIDI...') - - try: - midi = pretty_midi.PrettyMIDI() - instrument = pretty_midi.Instrument(0) - - scale = random.choice(list(self.scales.keys())) - scale_notes = self.scales[scale] - ic(f"Using scale: {scale}") - ic(f"Using notes: {scale_notes}") - - for start, end in zip(range(0, 100, 10), range(10, 110, 10)): - note = pretty_midi.Note( - velocity=100, pitch=random.choice(scale_notes), - start=start, end=end - ) - instrument.notes.append(note) - - midi.instruments.append(instrument) - - filepath = filedialog.asksaveasfilename(defaultextension='.mid') - if filepath: - midi.write(filepath) - track = pypianoroll.Multitrack(filepath) - track.plot() - self.status_label.config(text='MIDI generated successfully!') - - except Exception as e: - self.status_label.config(text=f"Error generating MIDI: {e}") - -class MidPlay: - """A class to handle MIDI file playback.""" - - def __init__(self): - self.playlist = [] - self.current_midi = None - self.playing = False - pygame.mixer.init() - - def load_midi(self, filepath: str) -> None: - try: - self.current_midi = pretty_midi.PrettyMIDI(filepath) - pygame.mixer.music.load(filepath) - except Exception as e: - print(f"Error loading MIDI: {e}") - - def add_to_playlist(self, filepath: str) -> None: - """Adds a MIDI file to the playlist. - - Args: - filepath: The path to the MIDI file. - """ - self.playlist.append(filepath) - - def clear_playlist(self) -> None: - """Clears the playlist.""" - self.playlist = [] - - def play_midi(self) -> None: - """Starts or resumes playback of the current MIDI file.""" - if self.current_midi: - self.current_midi.instruments[0].synthesize() - pygame.mixer.music.play() - self.playing = True - else: - print("No MIDI file loaded") - - def pause(self) -> None: - """Pauses playback.""" - pygame.mixer.music.pause() - self.playing = False - - def stop(self) -> None: - """Stops playback.""" - pygame.mixer.music.stop() - self.playing = False - -class UserInterface: - def __init__(self): - self.root = tk.Tk() - self.root.title("MIDI Generator") - self.root.geometry("400x200") - self.root.resizable(True, True) - self.status_label = ttk.Label(self.root, text="") - self.status_label.pack() - - self.midi_generator = midgen(self.status_label) - self.midi_player = MidPlay() - - - self.filepath = None - self.midi = None - - - self.generate_button = ttk.Button(self.root, text="Generate MIDI", command=self.midi_generator.generate_midi) - self.generate_button.pack() - - self.load_button = ttk.Button(self.root, text="Load MIDI", command=lambda: self.midi_player.load_midi(self.filepath)) - self.load_button.pack() - - self.play_button = ttk.Button(self.root, text="Play MIDI", command=lambda: self.midi_player.play_midi()) - self.play_button.pack() - - self.exit_button = ttk.Button(self.root, text="Exit", command=self.root.quit) - self.exit_button.pack() - - window = tk.Tk() - window.title("MIDI Generator") - self.root.mainloop() - -if __name__ == "__main__": - ui = UserInterface() +import mido as pretty_midi +import random +import tkinter as tk +from tkinter import ttk, filedialog +import pygame +import pypianoroll # type: ignore +from icecream import ic # type: ignore + +class midgen: + + def __init__(self, status_label: ttk.Label): + self.status_label = status_label + self.scales = self.scales() + + def scales(self): + scales = { + "Major": [0, 2, 4, 5, 7, 9, 11], + "Minor": [0, 2, 3, 5, 7, 8, 10], + "Pentatonic": [0, 2, 4, 7, 9], + "Blues": [0, 3, 5, 6, 7, 10], + "Whole Tone": [0, 2, 4, 6, 8, 10], + "Chromatic": [i for i in range(12)], + "Octatonic": [0, 1, 3, 4, 6, 7, 9, 10], + "Harmonic Minor": [0, 2, 3, 5, 7, 8, 11], + "Melodic Minor": [0, 2, 3, 5, 7, 9, 11], + "Dorian": [0, 2, 3, 5, 7, 9, 10], + "Phrygian": [0, 1, 3, 5, 7, 8, 10], + "Lydian": [0, 2, 4, 6, 7, 9, 11], + "Mixolydian": [0, 2, 4, 5, 7, 9, 10], + "Locrian": [0, 1, 3, 5, 6, 8, 10], + "Diminished": [0, 2, 3, 5, 6, 8, 9, 11], + "Whole Half Diminished": [0, 2, 3, 5, 6, 8, 9, 11], + "Arabian": [0, 2, 4, 5, 6, 8, 10], + "Hungarian Minor": [0, 2, 3, 6, 7, 8, 11], + "Enigmatic": [0, 1, 4, 6, 8, 10, 11], + "Neapolitan Major": [0, 1, 3, 5, 7, 9, 11], + "Neapolitan Minor": [0, 1, 3, 5, 7, 8, 11], + "Bluesy": [0, 3, 5, 6, 7, 10], + "Hawaiian": [0, 2, 3, 7, 9], + "Japanese": [0, 1, 5, 7, 8], + "Chinese": [0, 4, 6, 7, 11], + "Gypsy": [0, 2, 3, 6, 7, 8, 10], + "Hirojoshi": [0, 2, 3, 7, 8], + "In Sen": [0, 1, 5, 7, 10], + "Iwato": [0, 1, 5, 6, 10], + "Kumoi": [0, 2, 3, 7, 9], + "Pelog": [0, 1, 3, 7, 8], + "Ryukyu": [0, 4, 5, 7, 11], + "Spanish": [0, 1, 3, 4, 5, 6, 8, 10], + "Todi": [0, 1, 3, 6, 7, 8, 11], + "Yo": [0, 2, 5, 7, 9] + } + return scales + + + def generate_midi(self): + self.status_label.config(text='Generating MIDI...') + + try: + midi = pretty_midi.PrettyMIDI() + instrument = pretty_midi.Instrument(0) + + scale = random.choice(list(self.scales.keys())) + scale_notes = self.scales[scale] + ic(f"Using scale: {scale}") + ic(f"Using notes: {scale_notes}") + + for start, end in zip(range(0, 100, 10), range(10, 110, 10)): + note = pretty_midi.Note( + velocity=100, pitch=random.choice(scale_notes), + start=start, end=end + ) + instrument.notes.append(note) + + midi.instruments.append(instrument) + + filepath = filedialog.asksaveasfilename(defaultextension='.mid') + if filepath: + midi.write(filepath) + track = pypianoroll.Multitrack(filepath) + track.plot() + self.status_label.config(text='MIDI generated successfully!') + + except Exception as e: + self.status_label.config(text=f"Error generating MIDI: {e}") + +class MidPlay: + """A class to handle MIDI file playback.""" + + def __init__(self): + self.playlist = [] + self.current_midi = None + self.playing = False + pygame.mixer.init() + + def load_midi(self, filepath: str) -> None: + try: + self.current_midi = pretty_midi.PrettyMIDI(filepath) + pygame.mixer.music.load(filepath) + except Exception as e: + print(f"Error loading MIDI: {e}") + + def add_to_playlist(self, filepath: str) -> None: + """Adds a MIDI file to the playlist. + + Args: + filepath: The path to the MIDI file. + """ + self.playlist.append(filepath) + + def clear_playlist(self) -> None: + """Clears the playlist.""" + self.playlist = [] + + def play_midi(self) -> None: + """Starts or resumes playback of the current MIDI file.""" + if self.current_midi: + self.current_midi.instruments[0].synthesize() + pygame.mixer.music.play() + self.playing = True + else: + print("No MIDI file loaded") + + def pause(self) -> None: + """Pauses playback.""" + pygame.mixer.music.pause() + self.playing = False + + def stop(self) -> None: + """Stops playback.""" + pygame.mixer.music.stop() + self.playing = False + +class UserInterface: + def __init__(self): + self.root = tk.Tk() + self.root.title("MIDI Generator") + self.root.geometry("400x200") + self.root.resizable(True, True) + self.status_label = ttk.Label(self.root, text="") + self.status_label.pack() + + self.midi_generator = midgen(self.status_label) + self.midi_player = MidPlay() + + + self.filepath = None + self.midi = None + + + self.generate_button = ttk.Button(self.root, text="Generate MIDI", command=self.midi_generator.generate_midi) + self.generate_button.pack() + + self.load_button = ttk.Button(self.root, text="Load MIDI", command=lambda: self.midi_player.load_midi(self.filepath)) + self.load_button.pack() + + self.play_button = ttk.Button(self.root, text="Play MIDI", command=lambda: self.midi_player.play_midi()) + self.play_button.pack() + + self.exit_button = ttk.Button(self.root, text="Exit", command=self.root.quit) + self.exit_button.pack() + + window = tk.Tk() + window.title("MIDI Generator") + self.root.mainloop() + +if __name__ == "__main__": + ui = UserInterface() diff --git a/test_Fbrowser.py b/python-src/test_Fbrowser.py old mode 100755 new mode 100644 similarity index 97% rename from test_Fbrowser.py rename to python-src/test_Fbrowser.py index 83a5739..d1ad59c --- a/test_Fbrowser.py +++ b/python-src/test_Fbrowser.py @@ -1,37 +1,37 @@ -import unittest -from unittest.mock import MagicMock -from PyQt5.QtWidgets import QApplication -from Fbrowser import SampleMusicBrowser - -class TestSampleMusicBrowser(unittest.TestCase): - def setUp(self): - self.app = QApplication([]) - self.browser = SampleMusicBrowser() - - def tearDown(self): - self.app.quit() - - def test_player_error(self): - # Mock QMediaPlayer and set error code - self.browser.player.error = MagicMock(return_value=1) - self.browser.player.errorString = MagicMock(return_value="Test Error") - self.browser.player_error(1) - # Assert that the error message is printed - self.assertIn("An error occurred: Code:1 Test Error", self.browser.console_output) - - def test_player_media_status_changed(self): - # Mock QMediaPlayer and set media status - self.browser.player_media_status_changed(2) - # Assert that the media status is printed - self.assertIn("Media Status: 2", self.browser.console_output) - - def test_play_file(self): - # Mock QFileSystemModel and set file path - self.browser.list_model.filePath = MagicMock(return_value="/path/to/file.mp3") - # Call play_file method - self.browser.play_file(None) - # Assert that the player is playing the correct media - self.assertEqual(self.browser.playlist.media(0).canonicalUrl().toString(), "file:///path/to/file.mp3") - -if __name__ == '__main__': +import unittest +from unittest.mock import MagicMock +from PyQt5.QtWidgets import QApplication +from Fbrowser import SampleMusicBrowser + +class TestSampleMusicBrowser(unittest.TestCase): + def setUp(self): + self.app = QApplication([]) + self.browser = SampleMusicBrowser() + + def tearDown(self): + self.app.quit() + + def test_player_error(self): + # Mock QMediaPlayer and set error code + self.browser.player.error = MagicMock(return_value=1) + self.browser.player.errorString = MagicMock(return_value="Test Error") + self.browser.player_error(1) + # Assert that the error message is printed + self.assertIn("An error occurred: Code:1 Test Error", self.browser.console_output) + + def test_player_media_status_changed(self): + # Mock QMediaPlayer and set media status + self.browser.player_media_status_changed(2) + # Assert that the media status is printed + self.assertIn("Media Status: 2", self.browser.console_output) + + def test_play_file(self): + # Mock QFileSystemModel and set file path + self.browser.list_model.filePath = MagicMock(return_value="/path/to/file.mp3") + # Call play_file method + self.browser.play_file(None) + # Assert that the player is playing the correct media + self.assertEqual(self.browser.playlist.media(0).canonicalUrl().toString(), "file:///path/to/file.mp3") + +if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/testmid.py b/python-src/testmid.py similarity index 100% rename from testmid.py rename to python-src/testmid.py diff --git a/testmidi.py b/python-src/testmidi.py similarity index 100% rename from testmidi.py rename to python-src/testmidi.py diff --git a/timer_m.py b/python-src/timer_m.py similarity index 100% rename from timer_m.py rename to python-src/timer_m.py diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..68c3027 --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "fbrowser-desktop" +version.workspace = true +edition.workspace = true +license.workspace = true + +[build-dependencies] +tauri-build = { version = "2.2.0", features = [] } + +[dependencies] +anyhow.workspace = true +chrono.workspace = true +fbrowser-archive = { path = "../crates/fbrowser-archive" } +fbrowser-audio = { path = "../crates/fbrowser-audio" } +fbrowser-core = { path = "../crates/fbrowser-core" } +fbrowser-midi = { path = "../crates/fbrowser-midi" } +serde.workspace = true +serde_json.workspace = true +sqlx.workspace = true +tauri.workspace = true +tauri-plugin-dialog = "2.3.1" +tokio.workspace = true diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json new file mode 100644 index 0000000..98a9822 --- /dev/null +++ b/src-tauri/capabilities/default.json @@ -0,0 +1,11 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Default desktop capability for Fbrowser.", + "windows": ["main"], + "permissions": [ + "core:default", + "dialog:allow-open", + "dialog:allow-save" + ] +} diff --git a/src-tauri/gen/schemas/acl-manifests.json b/src-tauri/gen/schemas/acl-manifests.json new file mode 100644 index 0000000..db9d3be --- /dev/null +++ b/src-tauri/gen/schemas/acl-manifests.json @@ -0,0 +1 @@ +{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-ask","allow-confirm","allow-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]}},"allow-message":{"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"deny-ask":{"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]}},"deny-message":{"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json new file mode 100644 index 0000000..ff68818 --- /dev/null +++ b/src-tauri/gen/schemas/capabilities.json @@ -0,0 +1 @@ +{"default":{"identifier":"default","description":"Default desktop capability for Fbrowser.","local":true,"windows":["main"],"permissions":["core:default","dialog:allow-open","dialog:allow-save"]}} \ No newline at end of file diff --git a/src-tauri/gen/schemas/desktop-schema.json b/src-tauri/gen/schemas/desktop-schema.json new file mode 100644 index 0000000..5aa9e87 --- /dev/null +++ b/src-tauri/gen/schemas/desktop-schema.json @@ -0,0 +1,2310 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/src-tauri/gen/schemas/windows-schema.json b/src-tauri/gen/schemas/windows-schema.json new file mode 100644 index 0000000..5aa9e87 --- /dev/null +++ b/src-tauri/gen/schemas/windows-schema.json @@ -0,0 +1,2310 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000..f79e820 Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..7255e20 --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,496 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +use std::fs; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use chrono::Utc; +use fbrowser_archive::{compress, extract, ArchiveJobResult, ArchiveJobSpec}; +use fbrowser_audio::{generate_waveform, AudioEngine, LoopRegion, PlaybackState}; +use fbrowser_core::models::{ + AnnotationUpdate, CollectionItemsMutation, CollectionMutation, CollectionRecord, MediaItemDetail, + MetadataPatch, ScanStatus, SearchRequest, SearchResponse, +}; +use fbrowser_core::scanner::{scan_root, ScanProgress}; +use fbrowser_core::AppDatabase; +use fbrowser_midi::{available_backends, MidiBackendConfig, MidiBackendInfo, MidiEngine}; +use serde::{Deserialize, Serialize}; +use tauri::{AppHandle, Emitter, Manager, State}; +use tokio::task::JoinHandle; +use tokio::time::sleep; + +#[derive(Clone)] +struct DesktopState { + db: AppDatabase, + audio: AudioEngine, + midi: MidiEngine, + scan_status: Arc>, + timer: TimerManager, + midi_config: Arc>, + active_media_kind: Arc>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +struct TimerState { + running: bool, + remaining_ms: u64, + started_at: Option, +} + +#[derive(Clone, Default)] +struct TimerManager { + state: Arc>, + task: Arc>>>, +} + +impl TimerManager { + fn state(&self) -> TimerState { + self.state.lock().expect("timer state poisoned").clone() + } + + fn start(&self, app: AppHandle, duration_ms: u64) -> TimerState { + self.stop(); + { + let mut state = self.state.lock().expect("timer state poisoned"); + state.running = true; + state.remaining_ms = duration_ms; + state.started_at = Some(Utc::now().to_rfc3339()); + } + let state = self.state.clone(); + let task = tokio::spawn(async move { + loop { + sleep(Duration::from_secs(1)).await; + let snapshot = { + let mut state = state.lock().expect("timer state poisoned"); + if !state.running { + break; + } + if state.remaining_ms <= 1000 { + state.remaining_ms = 0; + state.running = false; + } else { + state.remaining_ms -= 1000; + } + state.clone() + }; + let _ = app.emit("timer:tick", &snapshot); + if !snapshot.running { + break; + } + } + }); + *self.task.lock().expect("timer task poisoned") = Some(task); + self.state() + } + + fn stop(&self) -> TimerState { + if let Some(task) = self.task.lock().expect("timer task poisoned").take() { + task.abort(); + } + let mut state = self.state.lock().expect("timer state poisoned"); + state.running = false; + state.clone() + } +} + +fn database_url(app: &AppHandle) -> anyhow::Result { + let dir = app.path().app_data_dir()?; + fs::create_dir_all(&dir)?; + let db_path = dir.join("fbrowser.sqlite"); + Ok(format!("sqlite://{}", db_path.display())) +} + +fn desktop_state<'a>(app_state: &'a State<'_, DesktopState>) -> &'a DesktopState { + app_state.inner() +} + +#[tauri::command] +async fn scan_add_root(app: AppHandle, state: State<'_, DesktopState>, path: String) -> Result { + let root = desktop_state(&state).db.add_root(&path).await.map_err(|err| err.to_string())?; + start_scan_job(app, desktop_state(&state).clone(), vec![root.clone()]); + Ok(root) +} + +#[tauri::command] +async fn scan_remove_root(state: State<'_, DesktopState>, root_id: i64) -> Result<(), String> { + desktop_state(&state).db.remove_root(root_id).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn scan_rescan(app: AppHandle, state: State<'_, DesktopState>, root_id: Option, all: Option) -> Result<(), String> { + let roots = desktop_state(&state).db.list_roots().await.map_err(|err| err.to_string())?; + let targets = if all.unwrap_or(false) || root_id.is_none() { + roots + } else { + roots.into_iter().filter(|root| Some(root.id) == root_id).collect() + }; + start_scan_job(app, desktop_state(&state).clone(), targets); + Ok(()) +} + +#[tauri::command] +async fn scan_get_status(state: State<'_, DesktopState>) -> Result { + let mut status = desktop_state(&state).scan_status.lock().expect("scan status poisoned").clone(); + status.roots = desktop_state(&state).db.list_roots().await.map_err(|err| err.to_string())?; + Ok(status) +} + +#[tauri::command] +async fn library_search(state: State<'_, DesktopState>, request: SearchRequest) -> Result { + desktop_state(&state).db.search_library(request).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn library_get_item(state: State<'_, DesktopState>, item_id: i64) -> Result { + desktop_state(&state).db.get_item(item_id).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn library_update_annotations(state: State<'_, DesktopState>, payload: AnnotationUpdate) -> Result { + desktop_state(&state).db.update_annotations(payload).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn library_write_metadata(state: State<'_, DesktopState>, item_id: i64, patch: MetadataPatch) -> Result { + desktop_state(&state).db.write_metadata(item_id, patch).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn collection_list(state: State<'_, DesktopState>) -> Result, String> { + desktop_state(&state).db.list_collections().await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn collection_create(state: State<'_, DesktopState>, payload: CollectionMutation) -> Result { + desktop_state(&state).db.create_collection(payload).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn collection_update(state: State<'_, DesktopState>, id: i64, payload: CollectionMutation) -> Result { + desktop_state(&state).db.update_collection(id, payload).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn collection_delete(state: State<'_, DesktopState>, id: i64) -> Result<(), String> { + desktop_state(&state).db.delete_collection(id).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn collection_add_items(state: State<'_, DesktopState>, payload: CollectionItemsMutation) -> Result<(), String> { + desktop_state(&state).db.add_items_to_collection(payload).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn collection_remove_items(state: State<'_, DesktopState>, payload: CollectionItemsMutation) -> Result<(), String> { + desktop_state(&state).db.remove_items_from_collection(payload).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn collection_reorder(state: State<'_, DesktopState>, payload: fbrowser_core::models::ReorderCollectionMutation) -> Result<(), String> { + desktop_state(&state).db.reorder_collection(payload).await.map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn playback_load(state: State<'_, DesktopState>, path: String, media_kind: String) -> Result { + let app_state = desktop_state(&state); + let playback = if media_kind == "midi" { + let config = app_state.midi_config.lock().expect("midi config poisoned").clone(); + app_state.audio.stop(); + app_state.midi.load(&path, &config).map_err(|err| err.to_string())? + } else { + app_state.midi.stop(); + app_state.audio.load(&path, &media_kind).map_err(|err| err.to_string())? + }; + *app_state.active_media_kind.lock().expect("active media kind poisoned") = media_kind; + Ok(playback) +} + +#[tauri::command] +async fn playback_play(state: State<'_, DesktopState>) -> Result { + let app_state = desktop_state(&state); + let current = if current_media_kind(app_state) == "midi" { + let config = app_state.midi_config.lock().expect("midi config poisoned").clone(); + app_state.midi.play(&config).map_err(|err| err.to_string())? + } else { + app_state.audio.play() + }; + if let Some(path) = ¤t.loaded_path { + if let Some(item_id) = app_state.db.find_item_id_by_path(path).await.map_err(|err| err.to_string())? { + let _ = app_state.db.record_play_history(item_id, "transport").await; + } + } + Ok(current) +} + +#[tauri::command] +async fn playback_pause(state: State<'_, DesktopState>) -> Result { + let app_state = desktop_state(&state); + Ok(if current_media_kind(app_state) == "midi" { + app_state.midi.pause() + } else { + app_state.audio.pause() + }) +} + +#[tauri::command] +async fn playback_stop(state: State<'_, DesktopState>) -> Result { + let app_state = desktop_state(&state); + Ok(if current_media_kind(app_state) == "midi" { + app_state.midi.stop() + } else { + app_state.audio.stop() + }) +} + +#[tauri::command] +async fn playback_seek(state: State<'_, DesktopState>, position_ms: u64) -> Result { + let app_state = desktop_state(&state); + if current_media_kind(app_state) == "midi" { + let config = app_state.midi_config.lock().expect("midi config poisoned").clone(); + app_state.midi.seek(position_ms, &config).map_err(|err| err.to_string()) + } else { + app_state.audio.seek(position_ms).map_err(|err| err.to_string()) + } +} + +#[tauri::command] +async fn playback_set_volume(state: State<'_, DesktopState>, volume: f32) -> Result { + let app_state = desktop_state(&state); + Ok(if current_media_kind(app_state) == "midi" { + app_state.midi.set_volume(volume) + } else { + app_state.audio.set_volume(volume) + }) +} + +#[tauri::command] +async fn playback_set_loop_region(state: State<'_, DesktopState>, loop_start_ms: Option, loop_end_ms: Option) -> Result { + let app_state = desktop_state(&state); + let region = match (loop_start_ms, loop_end_ms) { + (Some(start_ms), Some(end_ms)) => Some(LoopRegion { start_ms, end_ms }), + _ => None, + }; + Ok(if current_media_kind(app_state) == "midi" { + app_state.midi.set_loop_region(region) + } else { + app_state.audio.set_loop_region(region) + }) +} + +#[tauri::command] +async fn playback_get_state(state: State<'_, DesktopState>) -> Result { + let app_state = desktop_state(&state); + Ok(if current_media_kind(app_state) == "midi" { + app_state.midi.state() + } else { + app_state.audio.state() + }) +} + +#[tauri::command] +async fn waveform_get(state: State<'_, DesktopState>, item_id: i64) -> Result, String> { + let path = desktop_state(&state).db.get_path_for_item(item_id).await.map_err(|err| err.to_string())?; + generate_waveform(&path, 128).map_err(|err| err.to_string()) +} + +#[tauri::command] +async fn midi_get_backends() -> Result, String> { + Ok(available_backends()) +} + +#[tauri::command] +async fn midi_get_config(state: State<'_, DesktopState>) -> Result { + Ok(desktop_state(&state).midi_config.lock().expect("midi config poisoned").clone()) +} + +#[tauri::command] +async fn midi_set_backend(state: State<'_, DesktopState>, config: MidiBackendConfig) -> Result { + desktop_state(&state) + .db + .set_setting_json("midi_config", &config) + .await + .map_err(|err| err.to_string())?; + *desktop_state(&state).midi_config.lock().expect("midi config poisoned") = config.clone(); + Ok(config) +} + +#[tauri::command] +async fn archive_extract(app: AppHandle, spec: ArchiveJobSpec) -> Result { + let _ = app.emit("archive:progress", serde_json::json!({ "stage": "extracting", "source": spec.source })); + let result = extract(&spec).map_err(|err| err.to_string())?; + let _ = app.emit("archive:progress", serde_json::json!({ "stage": "done", "output": result.output_path })); + Ok(result) +} + +#[tauri::command] +async fn archive_compress(app: AppHandle, spec: ArchiveJobSpec) -> Result { + let _ = app.emit("archive:progress", serde_json::json!({ "stage": "compressing", "source": spec.source })); + let result = compress(&spec).map_err(|err| err.to_string())?; + let _ = app.emit("archive:progress", serde_json::json!({ "stage": "done", "output": result.output_path })); + Ok(result) +} + +#[tauri::command] +async fn timer_start(app: AppHandle, state: State<'_, DesktopState>, duration_ms: u64) -> Result { + Ok(desktop_state(&state).timer.start(app, duration_ms)) +} + +#[tauri::command] +async fn timer_stop(state: State<'_, DesktopState>) -> Result { + Ok(desktop_state(&state).timer.stop()) +} + +#[tauri::command] +async fn timer_get_state(state: State<'_, DesktopState>) -> Result { + Ok(desktop_state(&state).timer.state()) +} + +fn start_scan_job(app: AppHandle, state: DesktopState, roots: Vec) { + tauri::async_runtime::spawn(async move { + { + let mut status = state.scan_status.lock().expect("scan status poisoned"); + status.active = true; + status.indexed = 0; + status.discovered = 0; + status.last_error = None; + } + for root in roots { + let root_path = root.path.clone(); + { + let mut status = state.scan_status.lock().expect("scan status poisoned"); + status.current_root = Some(root_path.clone()); + } + let app_for_progress = app.clone(); + let state_for_progress = state.clone(); + let progress_root_path = root_path.clone(); + let result = scan_root(&state.db, &root, move |progress: ScanProgress| { + let mut status = state_for_progress.scan_status.lock().expect("scan status poisoned"); + status.discovered = progress.discovered; + status.indexed = progress.indexed; + let _ = app_for_progress.emit("scan:progress", serde_json::json!({ + "root": progress_root_path, + "discovered": progress.discovered, + "indexed": progress.indexed, + "currentPath": progress.current_path + })); + }) + .await; + + match result { + Ok(count) => { + let _ = app.emit("scan:item-indexed", serde_json::json!({ "root": root_path, "count": count })); + } + Err(err) => { + let mut status = state.scan_status.lock().expect("scan status poisoned"); + status.last_error = Some(err.to_string()); + } + } + } + + let roots = state.db.list_roots().await.unwrap_or_default(); + let final_status = { + let mut status = state.scan_status.lock().expect("scan status poisoned"); + status.active = false; + status.current_root = None; + status.roots = roots; + status.clone() + }; + let _ = app.emit("scan:completed", &final_status); + }); +} + +fn spawn_playback_emitter(app: AppHandle, desktop_state: DesktopState) { + tauri::async_runtime::spawn(async move { + loop { + sleep(Duration::from_millis(250)).await; + let snapshot = if current_media_kind(&desktop_state) == "midi" { + desktop_state.midi.state() + } else { + desktop_state.audio.state() + }; + let _ = app.emit("playback:state", &snapshot); + let _ = app.emit( + "playback:position", + serde_json::json!({ "positionMs": snapshot.position_ms, "durationMs": snapshot.duration_ms }), + ); + } + }); +} + +fn build_state(app: &AppHandle) -> anyhow::Result { + let db_url = database_url(app)?; + let db = tauri::async_runtime::block_on(async { + let db = AppDatabase::connect(&db_url).await?; + sqlx::migrate!("../migrations").run(db.pool()).await?; + Ok::<_, anyhow::Error>(db) + })?; + let midi_config = tauri::async_runtime::block_on(async { + db.get_setting_json::("midi_config").await + })? + .unwrap_or_default(); + + Ok(DesktopState { + db, + audio: AudioEngine::new()?, + midi: MidiEngine::new(), + scan_status: Arc::new(Mutex::new(ScanStatus::default())), + timer: TimerManager::default(), + midi_config: Arc::new(Mutex::new(midi_config)), + active_media_kind: Arc::new(Mutex::new("audio".into())), + }) +} + +fn current_media_kind(state: &DesktopState) -> String { + state + .active_media_kind + .lock() + .expect("active media kind poisoned") + .clone() +} + +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) + .setup(|app| { + let desktop_state = build_state(&app.handle())?; + spawn_playback_emitter(app.handle().clone(), desktop_state.clone()); + app.manage(desktop_state); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + scan_add_root, + scan_remove_root, + scan_rescan, + scan_get_status, + library_search, + library_get_item, + library_update_annotations, + library_write_metadata, + collection_list, + collection_create, + collection_update, + collection_delete, + collection_add_items, + collection_remove_items, + collection_reorder, + playback_load, + playback_play, + playback_pause, + playback_stop, + playback_seek, + playback_set_volume, + playback_set_loop_region, + playback_get_state, + waveform_get, + midi_get_backends, + midi_get_config, + midi_set_backend, + archive_extract, + archive_compress, + timer_start, + timer_stop, + timer_get_state + ]) + .run(tauri::generate_context!()) + .expect("failed to run Fbrowser desktop"); +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..66133cc --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,32 @@ +{ + "$schema": "../node_modules/@tauri-apps/cli/schema.json", + "productName": "Fbrowser", + "version": "0.1.0", + "identifier": "com.fbrowser.desktop", + "build": { + "beforeDevCommand": "npm run dev", + "beforeBuildCommand": "npm run build", + "devUrl": "http://localhost:1420", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "Fbrowser", + "width": 1600, + "height": 1040, + "minWidth": 1280, + "minHeight": 840, + "resizable": true + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [] + } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..479edfa --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,298 @@ +import { useEffect, useMemo, useState } from "react"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { Sidebar } from "./components/Sidebar"; +import { LibraryPanel } from "./components/LibraryPanel"; +import { InspectorPanel } from "./components/InspectorPanel"; +import { TransportBar } from "./components/TransportBar"; +import { TimerPanel } from "./components/TimerPanel"; +import { ArchivePanel } from "./components/ArchivePanel"; +import { SettingsPanel } from "./components/SettingsPanel"; +import { api } from "./lib/api"; +import { useUIStore } from "./store/ui"; +import type { + MediaItemSummary, + MidiBackendConfig, + PlaybackState, + SearchRequest, + TimerState, +} from "./lib/types"; + +const initialPlaybackState: PlaybackState = { + loaded_path: null, + is_playing: false, + volume: 0.8, + position_ms: 0, + duration_ms: 0, + loop_region: null, + output_device: "Default", + media_kind: "audio", +}; + +const initialTimerState: TimerState = { + running: false, + remaining_ms: 0, + started_at: null, +}; + +export default function App() { + const queryClient = useQueryClient(); + const { section, setSection, query, setQuery, selectedItem, setSelectedItem } = useUIStore(); + const [selectedCollectionId, setSelectedCollectionId] = useState(null); + const [playback, setPlayback] = useState(initialPlaybackState); + const [timer, setTimer] = useState(initialTimerState); + + useEffect(() => { + let mounted = true; + const unsubscribers: Array<() => void> = []; + api.playbackGetState().then((value) => mounted && setPlayback(value)); + api.timerGetState().then((value) => mounted && setTimer(value)); + + Promise.all([ + api.on("playback:state", (payload) => mounted && setPlayback(payload)), + api.on("timer:tick", (payload) => mounted && setTimer(payload)), + api.on("scan:completed", () => { + queryClient.invalidateQueries({ queryKey: ["scan-status"] }); + queryClient.invalidateQueries({ queryKey: ["library-search"] }); + }), + api.on("scan:progress", () => { + queryClient.invalidateQueries({ queryKey: ["scan-status"] }); + }), + ]).then((handlers) => { + handlers.forEach((unlisten) => unsubscribers.push(unlisten)); + }); + + return () => { + mounted = false; + unsubscribers.forEach((unsubscribe) => unsubscribe()); + }; + }, [queryClient]); + + const scanStatusQuery = useQuery({ + queryKey: ["scan-status"], + queryFn: api.scanGetStatus, + refetchInterval: 5000, + }); + + const collectionsQuery = useQuery({ + queryKey: ["collections"], + queryFn: api.collectionList, + }); + + const midiBackendsQuery = useQuery({ + queryKey: ["midi-backends"], + queryFn: api.midiGetBackends, + }); + + const midiConfigQuery = useQuery({ + queryKey: ["midi-config"], + queryFn: api.midiGetConfig, + }); + + const searchRequest = useMemo( + () => ({ + query, + section: section === "library" || section === "collections" ? undefined : section, + page: 0, + page_size: 250, + collection_id: section === "collections" ? selectedCollectionId ?? undefined : undefined, + }), + [query, section, selectedCollectionId], + ); + + const libraryQuery = useQuery({ + queryKey: ["library-search", searchRequest], + queryFn: () => api.librarySearch(searchRequest), + }); + + const selectedItemDetailQuery = useQuery({ + queryKey: ["library-item", selectedItem?.id], + enabled: Boolean(selectedItem?.id), + queryFn: () => api.libraryGetItem(selectedItem!.id), + }); + + const waveformQuery = useQuery({ + queryKey: ["waveform", selectedItem?.id], + enabled: Boolean(selectedItem?.id && selectedItem.media_kind === "audio"), + queryFn: () => api.waveformGet(selectedItem!.id), + }); + + const addRootMutation = useMutation({ + mutationFn: api.scanAddRoot, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["scan-status"] }); + queryClient.invalidateQueries({ queryKey: ["library-search"] }); + }, + }); + + const removeRootMutation = useMutation({ + mutationFn: api.scanRemoveRoot, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["scan-status"] }); + queryClient.invalidateQueries({ queryKey: ["library-search"] }); + }, + }); + + const createCollectionMutation = useMutation({ + mutationFn: api.collectionCreate, + onSuccess: () => queryClient.invalidateQueries({ queryKey: ["collections"] }), + }); + + const saveAnnotationsMutation = useMutation({ + mutationFn: api.libraryUpdateAnnotations, + onSuccess: (detail) => { + queryClient.setQueryData(["library-item", detail.summary.id], detail); + queryClient.invalidateQueries({ queryKey: ["library-search"] }); + }, + }); + + const writeMetadataMutation = useMutation({ + mutationFn: ({ itemId, patch }: { itemId: number; patch: Record }) => + api.libraryWriteMetadata(itemId, patch), + onSuccess: (detail) => { + queryClient.setQueryData(["library-item", detail.summary.id], detail); + queryClient.invalidateQueries({ queryKey: ["library-search"] }); + }, + }); + + const addToCollectionMutation = useMutation({ + mutationFn: api.collectionAddItems, + }); + + const title = + section === "favorites" + ? "Favorites" + : section === "recent" + ? "Recently previewed" + : section === "collections" + ? "Collections" + : "Indexed Library"; + + const selectedSummary = selectedItemDetailQuery.data?.summary ?? selectedItem; + + async function loadItem(item: MediaItemSummary) { + setSelectedItem(item); + const loaded = await api.playbackLoad(item.absolute_path, item.media_kind); + setPlayback(loaded); + return loaded; + } + + async function loadAndPlay(item: MediaItemSummary) { + await loadItem(item); + const playing = await api.playbackPlay(); + setPlayback(playing); + } + + function createCollection() { + createCollectionMutation.mutate({ + name: `Collection ${new Date().toLocaleTimeString()}`, + kind: "playlist", + rules_json: null, + }); + } + + return ( +
+
+ + +
+ {section === "archives" ? ( + api.archiveExtract({ source, destination })} + onCompress={(source, destination) => api.archiveCompress({ source, destination })} + /> + ) : section === "timer" ? ( + api.timerStart(durationMs).then(setTimer)} + onStop={() => api.timerStop().then(setTimer)} + /> + ) : section === "settings" ? ( + addRootMutation.mutate(path)} + onRemoveRoot={(rootId) => removeRootMutation.mutate(rootId)} + onRescanAll={() => api.scanRescan(undefined, true)} + onMidiConfigChange={(config: MidiBackendConfig) => + api.midiSetBackend(config).then(() => { + queryClient.invalidateQueries({ queryKey: ["midi-config"] }); + }) + } + /> + ) : ( + void loadItem(item)} + onActivateItem={loadAndPlay} + /> + )} + + + selectedSummary + ? playback.loaded_path?.startsWith(selectedSummary.absolute_path) + ? api.playbackPlay().then(setPlayback) + : loadAndPlay(selectedSummary) + : api.playbackPlay().then(setPlayback) + } + onPause={() => api.playbackPause().then(setPlayback)} + onStop={() => api.playbackStop().then(setPlayback)} + onSeek={(value) => api.playbackSeek(value).then(setPlayback)} + onVolume={(value) => api.playbackSetVolume(value).then(setPlayback)} + onToggleLoop={() => { + const nextRegion = playback.loop_region + ? [undefined, undefined] + : [0, Math.max(1000, playback.duration_ms)]; + return api.playbackSetLoopRegion(nextRegion[0], nextRegion[1]).then(setPlayback); + }} + /> +
+ + { + if (!selectedSummary) return; + saveAnnotationsMutation.mutate({ + item_id: selectedSummary.id, + favorite: payload.favorite, + rating: payload.rating, + note: payload.note, + custom_tags: payload.customTags, + color: payload.color, + }); + }} + onWriteMetadata={(patch) => { + if (!selectedSummary) return; + writeMetadataMutation.mutate({ itemId: selectedSummary.id, patch }); + }} + onAddToCollection={(collectionId) => { + if (!selectedSummary) return; + addToCollectionMutation.mutate({ + collection_id: collectionId, + item_ids: [selectedSummary.id], + }); + }} + /> +
+
+ ); +} diff --git a/src/components/ArchivePanel.tsx b/src/components/ArchivePanel.tsx new file mode 100644 index 0000000..63b2ee9 --- /dev/null +++ b/src/components/ArchivePanel.tsx @@ -0,0 +1,156 @@ +import { useState } from "react"; +import { open, save } from "@tauri-apps/plugin-dialog"; +import type { ArchiveJobResult } from "../lib/types"; + +interface ArchivePanelProps { + onExtract: (source: string, destination: string) => Promise; + onCompress: (source: string, destination: string) => Promise; +} + +type ArchiveAction = "extract" | "compress" | null; + +export function ArchivePanel({ onExtract, onCompress }: ArchivePanelProps) { + const [source, setSource] = useState(""); + const [destination, setDestination] = useState(""); + const [lastResult, setLastResult] = useState(null); + const [error, setError] = useState(null); + const [pendingAction, setPendingAction] = useState(null); + + async function runAction(action: Exclude) { + if (!source || !destination) { + setError("Choose both a source and destination before running an archive job."); + return; + } + + setPendingAction(action); + setError(null); + try { + const result = action === "extract" ? await onExtract(source, destination) : await onCompress(source, destination); + setLastResult(result); + } catch (cause) { + setError(cause instanceof Error ? cause.message : "Archive job failed."); + } finally { + setPendingAction(null); + } + } + + return ( +
+
Archive utilities
+

Extract and package sample crates

+

+ Supports ZIP, TAR, and{" "} + TAR.GZ / TGZ. Extraction expects an archive file and a target + folder. Compression accepts either a file or directory and writes a new archive. +

+ +
+
+
Source
+
+ setSource(event.target.value)} + className="flex-1 rounded-2xl border border-line/40 bg-panel/70 px-4 py-3 text-text outline-none" + placeholder="Archive file to extract, or file/folder to compress" + /> + + +
+
+ +
+
Destination
+
+ setDestination(event.target.value)} + className="flex-1 rounded-2xl border border-line/40 bg-panel/70 px-4 py-3 text-text outline-none" + placeholder="Target folder for extract, or archive path like crate.tar.gz" + /> + + +
+
+
+ +
+ + +
+ + {error ? ( +
+ {error} +
+ ) : null} + + {lastResult ? ( +
+ Output: {lastResult.output_path} +
{lastResult.processed_entries} items processed
+
+ ) : null} +
+ ); +} diff --git a/src/components/InspectorPanel.tsx b/src/components/InspectorPanel.tsx new file mode 100644 index 0000000..2b7a5a1 --- /dev/null +++ b/src/components/InspectorPanel.tsx @@ -0,0 +1,229 @@ +import { useEffect, useMemo, useState } from "react"; +import { Disc3, FileAudio, Music3, Star } from "lucide-react"; +import type { CollectionRecord, MediaItemDetail } from "../lib/types"; +import { formatDuration, formatFileSize } from "../lib/format"; + +interface InspectorPanelProps { + detail: MediaItemDetail | null | undefined; + waveform: number[]; + collections: CollectionRecord[]; + onSaveAnnotations: (payload: { + favorite: boolean; + rating: number | null; + note: string; + customTags: string[]; + color: string; + }) => void; + onWriteMetadata: (patch: Record) => void; + onAddToCollection: (collectionId: number) => void; +} + +export function InspectorPanel({ + detail, + waveform, + collections, + onSaveAnnotations, + onWriteMetadata, + onAddToCollection, +}: InspectorPanelProps) { + const summary = detail?.summary; + const [favorite, setFavorite] = useState(false); + const [rating, setRating] = useState(null); + const [note, setNote] = useState(""); + const [tagsText, setTagsText] = useState(""); + const [color, setColor] = useState("#24c4ff"); + const [title, setTitle] = useState(""); + const [artist, setArtist] = useState(""); + const [album, setAlbum] = useState(""); + const [genre, setGenre] = useState(""); + const [year, setYear] = useState(""); + const [comment, setComment] = useState(""); + + useEffect(() => { + setFavorite(summary?.favorite ?? false); + setRating(summary?.rating ?? null); + setNote(summary?.note ?? ""); + setTagsText(detail?.custom_tags.join(", ") ?? ""); + setColor(summary?.color ?? "#24c4ff"); + setTitle(summary?.title ?? ""); + setArtist(summary?.artist ?? ""); + setAlbum(summary?.album ?? ""); + setGenre(summary?.genre ?? ""); + setYear(summary?.year ?? ""); + setComment(summary?.comment ?? ""); + }, [detail, summary]); + + const statRows = useMemo( + () => [ + { label: "Type", value: summary?.media_kind ?? "--" }, + { label: "Length", value: formatDuration(summary?.duration_ms) }, + { label: "Size", value: formatFileSize(summary?.size_bytes) }, + { label: "Rate", value: summary?.sample_rate ? `${summary.sample_rate} Hz` : "--" }, + ], + [summary], + ); + + return ( +