From 29faa29f7eec6b96d43560256591fd3cff0e2d17 Mon Sep 17 00:00:00 2001 From: Pavel Kuzmin Date: Tue, 7 Jul 2026 15:07:39 +0500 Subject: [PATCH 01/35] feat(android): Rust-first USB layer via JNI Replace @Command SerialPortManager with UsbBridge/UsbNative JNI path, vendored usb-serial 3.10.0, SIOM lifecycle fixes, and unified watch/exchange API. Co-authored-by: Cursor --- .editorconfig | 2 +- CHANGELOG.md | 106 +- Cargo.lock | 4 +- Cargo.toml | 8 +- README.md | 409 ++-- android/.gitignore | 3 +- android/BUILD_INSTRUCTIONS.md | 19 +- android/README.md | 47 +- android/build.gradle | 34 +- android/consumer-rules.pro | 1 + android/settings.gradle | 2 +- .../app/tauri/serialplugin/MobileBridge.kt | 19 + .../app/tauri/serialplugin/SerialPlugin.kt | 668 +------ .../app/tauri/serialplugin/UsbNative.kt | 119 ++ .../serialplugin/manager/BufferedEmitter.kt | 78 - .../serialplugin/manager/CoalescingRxSink.kt | 72 + .../serialplugin/manager/CustomProber.kt | 13 + .../tauri/serialplugin/manager/ModemLines.kt | 14 + .../manager/SerialByteAccumulator.kt | 34 - .../manager/SerialDataEmitFields.kt | 39 - .../serialplugin/manager/SerialPortManager.kt | 736 -------- .../serialplugin/manager/SerialRxSink.kt | 16 + .../tauri/serialplugin/manager/UsbBridge.kt | 348 ++++ .../app/tauri/serialplugin/manager/UsbPath.kt | 19 + .../serialplugin/manager/UsbPortSession.kt | 254 +++ .../serialplugin/models/SerialPortConfig.kt | 15 +- .../serialplugin/MobileBridgeResponseTest.kt | 16 + .../SerialPluginConversionTest.kt | 68 - .../manager/BufferedEmitterTest.kt | 19 - .../serialplugin/manager/FakeUsbSerialPort.kt | 249 +++ .../serialplugin/manager/RecordingRxSink.kt | 59 + .../manager/SerialByteAccumulatorTest.kt | 56 - .../manager/SerialDataEmitFieldsTest.kt | 63 - .../serialplugin/manager/UsbBridgeTest.kt | 150 ++ .../tauri/serialplugin/manager/UsbPathTest.kt | 31 + .../manager/UsbPortSessionTest.kt | 69 + .../serialplugin/models/SerialModelsTest.kt | 7 + android/usbserial/LICENSE | 22 + android/usbserial/UPDATE.md | 53 + android/usbserial/VENDOR.md | 16 + android/usbserial/build.gradle | 26 + android/usbserial/consumer-rules.pro | 1 + .../usbserial/src/main/AndroidManifest.xml | 3 + .../usbserial/driver/CdcAcmSerialDriver.java | 359 ++++ .../usbserial/driver/Ch34xSerialDriver.java | 387 ++++ .../driver/ChromeCcdSerialDriver.java | 91 + .../usbserial/driver/CommonUsbSerialPort.java | 478 +++++ .../usbserial/driver/Cp21xxSerialDriver.java | 412 +++++ .../usbserial/driver/FtdiSerialDriver.java | 482 +++++ .../driver/GsmModemSerialDriver.java | 102 + .../android/usbserial/driver/ProbeTable.java | 102 + .../driver/ProlificSerialDriver.java | 622 +++++++ .../driver/SerialTimeoutException.java | 16 + .../hoho/android/usbserial/driver/UsbId.java | 56 + .../usbserial/driver/UsbSerialDriver.java | 38 + .../usbserial/driver/UsbSerialPort.java | 341 ++++ .../usbserial/driver/UsbSerialProber.java | 90 + .../hoho/android/usbserial/util/HexDump.java | 158 ++ .../usbserial/util/MonotonicClock.java | 14 + .../util/SerialInputOutputManager.java | 353 ++++ .../hoho/android/usbserial/util/UsbUtils.java | 37 + .../android/usbserial/util/XonXoffFilter.java | 47 + .../hoho/android/usbserial/BuildConfig.java | 6 + android/usbserial/version.properties | 3 + build.rs | 24 +- examples/serialport-test/.gitignore | 1 + .../serialport-test/.vscode/extensions.json | 2 +- examples/serialport-test/README.md | 38 +- examples/serialport-test/index.html | 4 +- examples/serialport-test/package-lock.json | 1415 +++++++------- examples/serialport-test/package.json | 22 +- examples/serialport-test/src-tauri/Cargo.lock | 4 +- .../src-tauri/build.gradle.kts | 3 - .../src-tauri/gen/android/build.gradle.kts | 3 - .../src-tauri/gen/schemas/acl-manifests.json | 2 +- .../src-tauri/gen/schemas/android-schema.json | 274 +-- .../src-tauri/gen/schemas/desktop-schema.json | 274 +-- .../src-tauri/gen/schemas/macOS-schema.json | 274 +-- .../src-tauri/gen/schemas/mobile-schema.json | 274 +-- examples/serialport-test/src-tauri/src/lib.rs | 41 +- .../serialport-test/src-tauri/tauri.conf.json | 7 +- examples/serialport-test/src/App.svelte | 558 ------ examples/serialport-test/src/App.vue | 168 ++ .../src/components/PortAtConsole.vue | 196 ++ .../src/components/PortPicker.vue | 274 +++ .../src/components/PortSettings.vue | 142 ++ .../src/components/PortSignals.vue | 93 + .../src/components/PortWorkspace.vue | 347 ++++ .../src/components/SerialPortComponent.svelte | 499 ----- .../src/components/StatusBar.vue | 153 ++ .../src/components/TerminalPanel.vue | 398 ++++ .../src/composables/usePortSession.ts | 509 +++++ examples/serialport-test/src/env.d.ts | 7 + examples/serialport-test/src/main.ts | 12 +- examples/serialport-test/src/styles.css | 216 ++- examples/serialport-test/src/types.ts | 27 + examples/serialport-test/src/vite-env.d.ts | 2 - examples/serialport-test/tsconfig.json | 22 +- examples/serialport-test/vite.config.ts | 44 +- guest-js/auto-reconnect-manager.ts | 191 -- guest-js/index.ts | 1180 +----------- guest-js/listener-manager.ts | 72 - guest-js/logger.ts | 28 +- guest-js/serial-port.ts | 836 +++++++++ guest-js/types.ts | 266 +++ package.json | 19 +- permissions/autogenerated/commands/at.toml | 13 + .../autogenerated/commands/at_phases.toml | 13 + .../commands/available_ports_direct.toml | 13 - .../commands/cancel_exchange.toml | 13 + .../autogenerated/commands/capabilities.toml | 13 + .../commands/configure_at_session.toml | 13 + .../autogenerated/commands/disable_mux.toml | 13 + .../autogenerated/commands/enable_mux.toml | 13 + .../autogenerated/commands/exchange.toml | 13 + .../commands/exchange_binary.toml | 13 + .../commands/open_mux_channel.toml | 13 + .../autogenerated/commands/read_cd.toml | 13 - .../autogenerated/commands/read_cts.toml | 13 - .../autogenerated/commands/read_dsr.toml | 13 - .../autogenerated/commands/read_dtr.toml | 13 - .../autogenerated/commands/read_ri.toml | 13 - .../autogenerated/commands/send_sms_pdu.toml | 13 + .../commands/start_listening.toml | 13 - .../commands/stop_listening.toml | 13 - .../autogenerated/commands/unwatch.toml | 13 + .../autogenerated/commands/unwatch_ports.toml | 13 + permissions/autogenerated/commands/watch.toml | 13 + .../autogenerated/commands/watch_ports.toml | 13 + .../autogenerated/commands/write_dtr.toml | 13 - .../autogenerated/commands/write_rts.toml | 13 - permissions/autogenerated/reference.md | 385 ++-- permissions/default.toml | 66 +- permissions/schemas/schema.json | 280 +-- pnpm-lock.yaml | 658 +++---- rollup.config.mjs | 9 +- scripts/vendor-usbserial.sh | 254 +++ src/at_parse.rs | 570 ++++++ src/at_session.rs | 152 ++ src/cmux/frame.rs | 170 ++ src/cmux/io.rs | 53 + src/cmux/mod.rs | 15 + src/cmux/path.rs | 23 + src/cmux/session.rs | 237 +++ src/commands.rs | 1643 +++-------------- src/desktop_api.rs | 1288 ++++++++----- src/events.rs | 236 +++ src/exchange_read.rs | 297 +++ src/lib.rs | 129 +- src/logger.rs | 3 +- src/mobile_api.rs | 1087 +++++++---- src/mobile_jni.rs | 63 + src/mobile_registry.rs | 211 +++ src/mobile_response.rs | 96 + src/mobile_rx_hub.rs | 136 ++ src/mobile_usb_io.rs | 408 ++++ src/mobile_usb_jni.rs | 328 ++++ src/port_list.rs | 223 +++ src/port_list_monitor.rs | 272 +++ src/port_rx_hub.rs | 874 +++++++++ src/port_tx_queue.rs | 201 ++ src/state.rs | 406 ++-- src/tests/available_ports_options_test.rs | 38 + src/tests/commands_test.rs | 37 +- src/tests/desktop_api_test.rs | 1052 ++++++++++- src/tests/error_test.rs | 16 +- src/tests/invoke_contract_test.rs | 143 ++ src/tests/mobile_api_test.rs | 244 +-- src/tests/mock.rs | 12 +- src/tests/serial_test.rs | 852 ++++++--- src/tests/state_test.rs | 20 +- src/tests/watch_registry_test.rs | 37 + src/watch_registry.rs | 53 + tests/README.md | 188 +- ...dling.test.ts => api-error-matrix.test.ts} | 40 +- tests/auto-reconnect.test.ts | 93 +- tests/concurrent-operations.test.ts | 4 +- tests/edge-cases.test.ts | 299 --- tests/encoding-operations.test.ts | 103 +- tests/exchange-mock.ts | 47 + tests/listeners.test.ts | 363 ---- tests/platform-mocks.test.ts | 46 - tests/port-operations.test.ts | 46 +- tests/port-settings.test.ts | 4 +- tests/send-at.test.ts | 106 ++ tests/setup.ts | 47 +- tests/static-methods.test.ts | 106 +- tests/test-utils.ts | 29 +- tests/undefined-unregister.test.ts | 105 -- tests/watch-lifecycle.test.ts | 189 ++ 190 files changed, 21720 insertions(+), 10799 deletions(-) create mode 100644 android/consumer-rules.pro create mode 100644 android/src/main/kotlin/app/tauri/serialplugin/MobileBridge.kt create mode 100644 android/src/main/kotlin/app/tauri/serialplugin/UsbNative.kt delete mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/BufferedEmitter.kt create mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/CoalescingRxSink.kt create mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/CustomProber.kt create mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/ModemLines.kt delete mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/SerialByteAccumulator.kt delete mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/SerialDataEmitFields.kt delete mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/SerialPortManager.kt create mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/SerialRxSink.kt create mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/UsbBridge.kt create mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/UsbPath.kt create mode 100644 android/src/main/kotlin/app/tauri/serialplugin/manager/UsbPortSession.kt create mode 100644 android/src/test/kotlin/app/tauri/serialplugin/MobileBridgeResponseTest.kt delete mode 100644 android/src/test/kotlin/app/tauri/serialplugin/SerialPluginConversionTest.kt delete mode 100644 android/src/test/kotlin/app/tauri/serialplugin/manager/BufferedEmitterTest.kt create mode 100644 android/src/test/kotlin/app/tauri/serialplugin/manager/FakeUsbSerialPort.kt create mode 100644 android/src/test/kotlin/app/tauri/serialplugin/manager/RecordingRxSink.kt delete mode 100644 android/src/test/kotlin/app/tauri/serialplugin/manager/SerialByteAccumulatorTest.kt delete mode 100644 android/src/test/kotlin/app/tauri/serialplugin/manager/SerialDataEmitFieldsTest.kt create mode 100644 android/src/test/kotlin/app/tauri/serialplugin/manager/UsbBridgeTest.kt create mode 100644 android/src/test/kotlin/app/tauri/serialplugin/manager/UsbPathTest.kt create mode 100644 android/src/test/kotlin/app/tauri/serialplugin/manager/UsbPortSessionTest.kt create mode 100644 android/usbserial/LICENSE create mode 100644 android/usbserial/UPDATE.md create mode 100644 android/usbserial/VENDOR.md create mode 100644 android/usbserial/build.gradle create mode 100644 android/usbserial/consumer-rules.pro create mode 100644 android/usbserial/src/main/AndroidManifest.xml create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ChromeCcdSerialDriver.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/GsmModemSerialDriver.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/SerialTimeoutException.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbId.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/util/HexDump.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/util/MonotonicClock.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/util/UsbUtils.java create mode 100644 android/usbserial/src/main/java/com/hoho/android/usbserial/util/XonXoffFilter.java create mode 100644 android/usbserial/stubs/com/hoho/android/usbserial/BuildConfig.java create mode 100644 android/usbserial/version.properties delete mode 100755 examples/serialport-test/src/App.svelte create mode 100644 examples/serialport-test/src/App.vue create mode 100644 examples/serialport-test/src/components/PortAtConsole.vue create mode 100644 examples/serialport-test/src/components/PortPicker.vue create mode 100644 examples/serialport-test/src/components/PortSettings.vue create mode 100644 examples/serialport-test/src/components/PortSignals.vue create mode 100644 examples/serialport-test/src/components/PortWorkspace.vue delete mode 100644 examples/serialport-test/src/components/SerialPortComponent.svelte create mode 100644 examples/serialport-test/src/components/StatusBar.vue create mode 100644 examples/serialport-test/src/components/TerminalPanel.vue create mode 100644 examples/serialport-test/src/composables/usePortSession.ts create mode 100644 examples/serialport-test/src/env.d.ts create mode 100644 examples/serialport-test/src/types.ts delete mode 100755 examples/serialport-test/src/vite-env.d.ts delete mode 100644 guest-js/auto-reconnect-manager.ts delete mode 100644 guest-js/listener-manager.ts create mode 100644 guest-js/serial-port.ts create mode 100644 guest-js/types.ts create mode 100644 permissions/autogenerated/commands/at.toml create mode 100644 permissions/autogenerated/commands/at_phases.toml delete mode 100644 permissions/autogenerated/commands/available_ports_direct.toml create mode 100644 permissions/autogenerated/commands/cancel_exchange.toml create mode 100644 permissions/autogenerated/commands/capabilities.toml create mode 100644 permissions/autogenerated/commands/configure_at_session.toml create mode 100644 permissions/autogenerated/commands/disable_mux.toml create mode 100644 permissions/autogenerated/commands/enable_mux.toml create mode 100644 permissions/autogenerated/commands/exchange.toml create mode 100644 permissions/autogenerated/commands/exchange_binary.toml create mode 100644 permissions/autogenerated/commands/open_mux_channel.toml delete mode 100644 permissions/autogenerated/commands/read_cd.toml delete mode 100644 permissions/autogenerated/commands/read_cts.toml delete mode 100644 permissions/autogenerated/commands/read_dsr.toml delete mode 100644 permissions/autogenerated/commands/read_dtr.toml delete mode 100644 permissions/autogenerated/commands/read_ri.toml create mode 100644 permissions/autogenerated/commands/send_sms_pdu.toml delete mode 100644 permissions/autogenerated/commands/start_listening.toml delete mode 100644 permissions/autogenerated/commands/stop_listening.toml create mode 100644 permissions/autogenerated/commands/unwatch.toml create mode 100644 permissions/autogenerated/commands/unwatch_ports.toml create mode 100644 permissions/autogenerated/commands/watch.toml create mode 100644 permissions/autogenerated/commands/watch_ports.toml delete mode 100644 permissions/autogenerated/commands/write_dtr.toml delete mode 100644 permissions/autogenerated/commands/write_rts.toml create mode 100755 scripts/vendor-usbserial.sh create mode 100644 src/at_parse.rs create mode 100644 src/at_session.rs create mode 100644 src/cmux/frame.rs create mode 100644 src/cmux/io.rs create mode 100644 src/cmux/mod.rs create mode 100644 src/cmux/path.rs create mode 100644 src/cmux/session.rs create mode 100644 src/events.rs create mode 100644 src/exchange_read.rs create mode 100644 src/mobile_jni.rs create mode 100644 src/mobile_registry.rs create mode 100644 src/mobile_response.rs create mode 100644 src/mobile_rx_hub.rs create mode 100644 src/mobile_usb_io.rs create mode 100644 src/mobile_usb_jni.rs create mode 100644 src/port_list.rs create mode 100644 src/port_list_monitor.rs create mode 100644 src/port_rx_hub.rs create mode 100644 src/port_tx_queue.rs create mode 100644 src/tests/available_ports_options_test.rs create mode 100644 src/tests/invoke_contract_test.rs create mode 100644 src/tests/watch_registry_test.rs create mode 100644 src/watch_registry.rs rename tests/{error-handling.test.ts => api-error-matrix.test.ts} (88%) delete mode 100644 tests/edge-cases.test.ts create mode 100644 tests/exchange-mock.ts delete mode 100644 tests/listeners.test.ts delete mode 100644 tests/platform-mocks.test.ts create mode 100644 tests/send-at.test.ts delete mode 100644 tests/undefined-unregister.test.ts create mode 100644 tests/watch-lifecycle.test.ts diff --git a/.editorconfig b/.editorconfig index badeda06..61305601 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,7 +4,7 @@ root = true end_of_line = lf insert_final_newline = true -[*.{js,ts,svelte,html,json,mjs,cjs}] +[*.{js,ts,html,json,mjs,cjs,vue}] charset = utf-8 indent_style = space indent_size = 2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e60266..65aed830 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,110 @@ All notable changes to this project will be documented in this file. See [standa For **Android/USB-focused** details (behavior, limits, testing), see also [`android/README.md`](android/README.md). +## [Unreleased] + +### Breaking / migration (native transaction queue) + +* **Removed `AtCommandQueue` / `port.at`:** Use **`sendAt()`**, **`sendAtPhases()`**, **`sendSmsPdu()`**, **`cancelAt()`**, **`configureAtSession()`**. +* **Native FIFO queue (Rust + Android):** All `exchange` / AT jobs on one port serialize in FIFO order; parallel invokes **wait** instead of `"Exchange already in progress"`. +* **`onUrc` removed from `AtSessionOptions`:** Use **`watch({ onUrc })`** for live URC lines. +* **New commands:** `at`, `at_phases`, `send_sms_pdu`, `configure_at_session`. + +### Features + +* **Android USB watch + TX:** bulkTransfer SIOM read (`readTimeout=200`, `queue=0`); direct `port.write` with short `synchronized(port)` on TX only (no lock across blocking read — fixes deadlock with `LockedUsbSerialPort`). +* **Desktop `PortTxQueue`:** Ticket turnstile per port; `stop_on_error` drains waiters; `cancel_exchange` flushes queue. +* **Android `PortTxQueue`:** Mirror FIFO + AT session merge in Kotlin. +* **CMUX `VirtualPortRef`:** Virtual channels no longer duplicate RX hub entries; hub uses `try_lock` read to avoid writer starvation. +* **PTY E2E:** `cmux_virtual_port_exchange_on_pty` test restored. +* **`watchAvailablePorts()` / `watch_ports`:** Subscribe to serial port hotplug via Channel — initial `snapshot`, then `added` / `removed`. Desktop polls (default 2s); Android uses USB attach/detach + poll fallback. `available_ports()` unchanged. + +### Features (v3.3) + +* **Desktop PortRxHub:** Single RX thread on main fd; `watch()` subscribes (no `try_clone`); all `exchange`/`drain` through hub. +* **Extended AT grammar:** Vendor prefixes (`+^#$%*`), V.250 finals (`NO CARRIER`, `BUSY`, `SEND OK`, …), `derive_solicited_prefixes(command)`. +* **ExchangeDemux:** Live URC before echo during exchange (Rust + Android). +* **drainRx URC forward:** Drain emits URC to watch/`onUrc` instead of silent discard (desktop hub + Android router). +* **`AtCommandQueue`:** `enqueue()` without `enable()`; `enable()`/`disable()` deprecated. + +### Features (v3.1) + +* **Line-framed AT completion:** Native `exchange` completes when the **final line** is `OK`, `ERROR`, `+CME ERROR`, or `+CMS ERROR` (not substring match). `completionMode: 'substring'` for legacy/binary. +* **Structured `ExchangeResponse`:** `status`, `lines`, `solicitedBody`, `urcLines`, `matched`, `raw` — desktop, Android, guest-js. +* **`rxPrepare`:** Default **`drain`** (soft idle drain); **`purge`** opt-in (hardware clear); **`none`** unchanged. +* **`at_parse`:** Rust + Kotlin mirror — solicited `+CSQ` vs URC `+CREG` classification. +* **`AtCommandQueue`:** `expectOk`, `onUrc` (deferred), `solicitedPrefixes`, `defaultRxPrepare`. + +### Features (v3.2) + +* **`PortRxHub` / `PortRxRouter`:** Single RX consumer routes bytes to watch vs exchange; exchange allowed while watch is active. +* **`SerialEvent::Urc`** + **`watch({ onUrc })`** for live unsolicited AT lines. +* **`pauseWatch` default `false`** — watch stays on during AT; pass `pauseWatch: true` for legacy behavior. +* **Desktop:** Poll `read()` rejected while watch active; URC routing in watch thread. + +### Breaking / migration (v3.0 AT → v3.1+) + +* **`exchange()`** returns **`ExchangeResponse`**, not `Uint8Array`. Use `.raw` for bytes. +* **`clearRx: true`** maps to **`purge`**; default is now **`drain`** when unset. + +### Features (v3.0 AT baseline) + +* **Native `exchange` / `cancel_exchange`:** Write + read-until terminators, idle silence, wall timeout, and max response size (desktop + Android). +* **guest-js `AtCommandQueue`:** `port.at.enable()` / `enqueue()` / `disable()` — FIFO AT commands. +* **Demo:** AT tab in `examples/serialport-test` with queue status, parse status, URC column, and script runner. + +## [3.0.0](https://github.com/s00d/tauri-plugin-serialplugin/compare/v2.23.0...v3.0.0) (2026-07-03) + +### Breaking Changes + +* **Streaming API:** `startListening()` / `listen()` / `disconnected()` / `cancelListen()` removed. Use **`watch({ onData, onDisconnect?, onError? })`** → `SerialEvent` (`data` | `error` | `disconnect`) over Tauri [`Channel`](https://v2.tauri.app/develop/calling-frontend/) (desktop + Android). +* **Capabilities:** New `SerialPort.getCapabilities()` (`transport`, `platform`, `version`) — optional runtime info from Rust; the plugin no longer probes the platform via internal `window` hacks. +* **Commands:** `capabilities`, `watch`, `unwatch` replace `start_listening` / `stop_listening`. +* **Public events removed:** `plugin-serialplugin-read-*`, Android `serialData` / `serialError` plugin triggers. +* **Removed:** `available_ports_direct` — use `available_ports()`. + +### Migration (v2 → v3) + +| v2 | v3 | +|----|-----| +| `startListening()` + `listen(fn)` | `watch({ onData: fn })` | +| `disconnected(fn)` | `watch({ onDisconnect: fn })` | +| `cancelListen()` / `stopListening()` | `handle.unwatch()` | + +**New:** `SerialPort.getCapabilities()` when the app needs `transport` / `platform` / `version` (not a v2 API replacement). + +### Features + +* **guest-js:** Modular v3 SDK (`serial-port.ts`, `types.ts`); auto-reconnect restores **`open()` + `watch()`** after disconnect (handlers/options saved on first `watch()`). +* **Android:** `watch` options parsed from nested `options` object (fixes `serialDataFlushIntervalMs` always defaulting to 100 ms). +* **Desktop:** `serialDataFlushIntervalMs` controls batch coalescing; read poll timeout is internal (1–100 ms). +* **Events:** `SerialEvent::Error` for non-fatal watch notifications; `disconnect` remains terminal. +* **macOS:** `available_ports({ singlePortPerDevice: true })` — one path per device (prefers `/dev/cu.*`). +* **guest-js:** Export `DEFAULT_SERIAL_TIMEOUT_MS`; default open timeout **1000 ms**. + +### Bug Fixes + +* **Android:** USB permission `PendingIntent` uses `FLAG_MUTABLE` on API 31+ ([#494](https://github.com/mik3y/usb-serial-for-android/issues/494)). +* **Android:** RTS/DTR honor `level: bool` from Rust; `PortConfigArgs.size` passed to `read` / `readBinary`. +* **Android:** Poll `read` / `readBinary` rejected while `watch` is active; partial writes retried on `SerialTimeoutException`. +* **Android:** `cancelRead`, `clearBuffer` (`ClearBufferArgs`), ISO-8859-1 text `read()`, vendored usb-serial **3.10.0** (no JitPack). +* **Android / Rust bridge:** Fix `managed_ports`, signal-line reads, `bytes_to_read` / `bytes_to_write` parsing. +* **Android:** Parse enum fields from numeric strings and JS-style names ([#19](https://github.com/s00d/tauri-plugin-serialplugin/issues/19)). +* **Android:** USB detach + IO errors → `SerialEvent::Disconnect` on Channel ([#27](https://github.com/s00d/tauri-plugin-serialplugin/issues/27)). +* **desktop:** `write` / `write_binary` flush via `write_all` ([#29](https://github.com/s00d/tauri-plugin-serialplugin/issues/29)). +* **desktop:** Watch read-poll timeout clamped **1–100 ms**; disconnect on unplug via read I/O error ([#27](https://github.com/s00d/tauri-plugin-serialplugin/issues/27)). +* **desktop (Windows):** Enrich truncated USB `serial_number` from WMI ([#23](https://github.com/s00d/tauri-plugin-serialplugin/issues/23)). +* **build:** Remove dead permission aliases; deduplicate `permissions/default.toml`. +* **desktop:** `close_all` no longer deadlocks; watch registry unregisters on thread exit; `force_close` / `open` clear watches; `cancel_read` joins listener thread; `available_ports` surfaces enumeration errors. +* **Android / Rust bridge:** Mutating plugin calls accept Kotlin `invoke.resolve()` (`Null`) via `ensure_mobile_success`; `clearBuffer` maps numeric `0`/`1`/`2` to input/output/all. +* **guest-js:** `readBinary` requires open port; `LogLevel` const object; removed duplicate `setRequestToSend` / `setDataTerminalReady` (use `writeRequestToSend` / `writeDataTerminalReady`). +* **docs:** README v3 cleanup; `cancel_read` stops poll read and active watch (shared stop channel). + +### Build / Android + +* **Android:** `setReadQueue(4)`, `setReadBufferSize(max(endpoint, 4096))`, `BufferedEmitter` coalescing, idempotent `closePort`. +* **Tests:** Jest watch lifecycle, Rust `watch_registry` / `invoke_contract`, JVM `SerialDataEmitFieldsTest`. + ## [2.23.0](https://github.com/s00d/tauri-plugin-serialplugin/compare/v2.22.0...v2.23.0) (2026-07-02) ### Bug Fixes @@ -27,7 +131,7 @@ For **Android/USB-focused** details (behavior, limits, testing), see also [`andr * **Lifecycle:** `SerialPlugin` registers `Application.ActivityLifecycleCallbacks` and runs `SerialPortManager.cleanup()` when the host `Activity` is destroyed (close USB ports, unregister permission receiver, shut down IO executor). * **Listening:** Incoming data is coalesced in `BufferedEmitter` / `SerialByteAccumulator` before `serialData` events; flush interval via `serialDataFlushIntervalMs` (native clamp typically 10–2000 ms). * **USB serial (usb-serial-for-android):** Read/write use configured timeouts; `clearBuffer` maps to `purgeHwBuffers` when supported; `setFlowControl` (RTS/CTS, XON/XOFF); `SerialInputOutputManager` errors trigger `serialError` and port cleanup. -* **`bytesToRead` / `bytesToWrite` (Android):** `bytesToRead` returns bytes buffered **in the plugin** only while `startListening` is active (not the kernel queue — not exposed by `UsbSerialPort`). `bytesToWrite` is `0` (writes complete synchronously from the app’s perspective). Documented in JSDoc on the JS API. +* **`bytesToRead` / `bytesToWrite` (Android):** With active **`watch`**, `bytesToRead` is the plugin-side buffer before the next Channel flush; `bytesToWrite` is typically `0`. * **Tooling:** Gradle wrapper and `android/` settings for local `./gradlew test`; JVM unit tests use a real `org.json` artifact (Android JSON stubs break `JSONObject.put` in tests). Kotlin tests cover models, JSON helpers, emit pipeline, `BufferedEmitter`. ### Features diff --git a/Cargo.lock b/Cargo.lock index 55eb4375..3e01ee7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3348,14 +3348,14 @@ dependencies = [ [[package]] name = "tauri-plugin-serialplugin" -version = "2.23.0" +version = "3.0.0" dependencies = [ + "jni", "serde", "serde_json", "serialport", "tauri", "tauri-plugin", - "thiserror 2.0.12", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 7b05b633..db98a33a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tauri-plugin-serialplugin" -version = "2.23.0" -description = "Access the current process of your Tauri application." +version = "3.0.0" +description = "Tauri plugin for serial port access (desktop and Android)." edition = "2021" authors = ["Tauri Programme within The Commons Conservancy"] license = "Apache-2.0 OR MIT" @@ -26,9 +26,11 @@ tauri-plugin = { version = "2.3.0", features = [ "build" ] } [dependencies] tauri = { version = "2.6.2", features = ["test"] } serde = { version = "1.0.219", features = ["derive"] } -thiserror = "2.0.12" serde_json = "1.0.140" +[target.'cfg(target_os = "android")'.dependencies] +jni = "0.21" + [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] serialport = "4.9.0" diff --git a/README.md b/README.md index 8ce986ac..d6ba0d76 100755 --- a/README.md +++ b/README.md @@ -9,7 +9,38 @@ A comprehensive plugin for Tauri applications to communicate with serial ports. This plugin provides a complete API for reading from and writing to serial devices, with support for various configuration options and control signals. -> **⚠️ Important Notice:** The JavaScript dependency has been renamed from `tauri-plugin-serialplugin` to `tauri-plugin-serialplugin-api`. Please update your `package.json` before updating to the latest version, following the same pattern used by other Tauri plugins. +> **v3.0 breaking change:** `listen` / `startListening` / `disconnected` were removed. Use **`watch()`** and **`SerialPort.getCapabilities()`**. See [Migrating to v3](#migrating-to-v3) below. + +--- + +## Migrating to v3 + +| v2 | v3 | +|----|-----| +| `startListening()` + `listen(fn)` | `const handle = await port.watch({ onData: fn })` | +| `disconnected(fn)` | `watch({ onDisconnect: fn })` | +| `cancelListen()` / `stopListening()` | `await handle.unwatch()` | + +Legacy Tauri events (`plugin-serialplugin-read-*`) and Android plugin triggers (`serialData`, `serialError`) are **no longer part of the public API**. + +**New in v3:** `SerialPort.getCapabilities()` — `{ transport, platform, version }` from Rust (`cfg!`), if the app needs to branch by platform (replaces ad-hoc `window` / `@tauri-apps/plugin-os` probing on the app side). + +### Watch events (`SerialEvent`) + +| `kind` | Meaning | Port stays open? | +|--------|---------|------------------| +| `data` | Incoming bytes (decoded to `string` in JS when `decode !== false`) | Yes | +| `error` | Non-fatal notification (e.g. emitter glitch); watch continues | Yes | +| `disconnect` | Fatal end of stream (unplug, IO manager stopped); triggers auto-reconnect if enabled | No (`isOpen = false`) | + +### Watch options + +| Option | Desktop | Android | +|--------|---------|---------| +| `timeout` | Batch coalescing window (ms) | Reserved (not used by native watch yet) | +| `serialDataFlushIntervalMs` | Preferred batch interval; falls back to `timeout` | `BufferedEmitter` flush (10–2000 ms) | +| `size` | Read chunk size per syscall | Reserved | +| `decode` | JS-only: `TextDecoder` on `onData` | JS-only | --- @@ -47,11 +78,6 @@ A comprehensive plugin for Tauri applications to communicate with serial ports. - **Tauri** 2.0 or higher - **Node.js** and an npm-compatible package manager (npm, yarn, pnpm) - - - -## Installation - ### Automatic Installation (Recommended) Use the Tauri CLI to automatically install both the Rust and JavaScript parts of the plugin: @@ -97,45 +123,9 @@ npm install tauri-plugin-serialplugin-api pnpm add tauri-plugin-serialplugin-api ``` -### Android Setup (Required for Android builds) - -> **⚠️ Important:** If you're building for Android, you **must** configure the JitPack repository before building. The plugin will not compile without this step. +### Android -The plugin depends on `com.github.mik3y:usb-serial-for-android:3.8.1`, which is hosted on JitPack. Add the following to your `/src-tauri/gen/android/build.gradle.kts` file: - -```kotlin -buildscript { - repositories { - google() - mavenCentral() - maven { url = uri("https://jitpack.io") } - } - dependencies { - classpath("com.android.tools.build:gradle:8.11.0") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") - // ... your other dependencies - } -} - -allprojects { - repositories { - google() - mavenCentral() - maven { url = uri("https://jitpack.io") } - } -} - -tasks.register("clean").configure { - delete("build") -} -``` - -**Without this configuration, you will get an error:** -``` -could not find com.github.mik3y:usb-serial-for-android:3.8.1 -``` - -For more details and troubleshooting, see the [Android Setup](#android-setup) section. +USB serial support uses [usb-serial-for-android](https://github.com/mik3y/usb-serial-for-android) **v3.10.0**, vendored inside the plugin (`android/usbserial/`). **No JitPack or extra Maven repositories** are required — standard `google()` + `mavenCentral()` is enough. --- @@ -185,16 +175,14 @@ For more details and troubleshooting, see the [Android Setup](#android-setup) se // Write data await port.write("Hello, Serial Port!"); - // Start port listening - await port.startListening(); - - // Start port listening - const unsubscribe = await port.listen((data) => { - console.log("Received:", data); + // Stream incoming data (desktop + Android) + const handle = await port.watch({ + onData: (data) => console.log("Received:", data), + onDisconnect: (reason) => console.log("Disconnected:", reason), }); - // Stop listening when done - await port.cancelListen(); + // Stop streaming when done + await handle.unwatch(); // Close port await port.close(); @@ -242,13 +230,12 @@ For more details and troubleshooting, see the [Android Setup](#android-setup) se } try { - // Start listening - await port.startListening(); - await port.listen((data) => { - console.log("Received:", data); + const handle = await port.watch({ + onData: (data) => console.log("Received:", data), }); + // ... use handle.unwatch() in cleanup } catch (error) { - throw new Error(`Failed to start listening: ${error}`); + throw new Error(`Failed to start watch: ${error}`); } try { @@ -270,7 +257,6 @@ For more details and troubleshooting, see the [Android Setup](#android-setup) se // Clean up if (port) { try { - await port.cancelListen(); await port.close(); } catch (error) { console.error("Error during cleanup:", error); @@ -346,8 +332,8 @@ await port.setTimeout(500); ```typescript // Set control signals -await port.setRequestToSend(true); -await port.setDataTerminalReady(true); +await port.writeRequestToSend(true); +await port.writeDataTerminalReady(true); // Alternative methods (writeRequestToSend and writeDataTerminalReady) await port.writeRequestToSend(true); @@ -463,7 +449,7 @@ await SerialPort.setLogLevel(LogLevel.None); setInterval(async () => { const ports = await SerialPort.available_ports(); - // No "listen event: plugin-serialplugin-disconnected-COM6" logs + // No extra console noise from the plugin while polling }, 1000); ``` @@ -621,7 +607,7 @@ async fn rust_serial_example( ```rust use tauri_plugin_serialplugin::commands::{ - available_ports, open, write, read, close, force_close, managed_ports, start_listening + available_ports, open, write, read, close, force_close, managed_ports, watch, unwatch }; use tauri_plugin_serialplugin::state::{DataBits, FlowControl, Parity, StopBits}; use tauri::{AppHandle, State}; @@ -670,22 +656,11 @@ async fn advanced_serial_example( } } - // Start listening for data - match start_listening( - app.clone(), - serial.clone(), - port_path.clone(), - Some(1000u64), // timeout - Some(1024usize) // max bytes - ) { - Ok(_) => println!("Started listening"), - Err(e) => { - eprintln!("Failed to start listening: {}", e); - // Continue anyway, we can still read manually - } + // Poll read (for streaming use `watch` + Channel from the frontend) + match read(app.clone(), serial.clone(), port_path.clone(), Some(1000u64), Some(1024usize)) { + Ok(data) => println!("Read: {}", data), + Err(e) => eprintln!("Read failed: {}", e), } - - // Send a command and read response let command = "AT\r\n".to_string(); match write(app.clone(), serial.clone(), port_path.clone(), command) { Ok(bytes) => println!("Sent {} bytes", bytes), @@ -826,7 +801,7 @@ async fn my_serial_function( serial: State<'_, SerialPort> ) -> Result<(), String> { // Use serial methods directly - let ports = serial.available_ports()?; + let ports = serial.available_ports(false)?; // ... rest of your code } ``` @@ -853,7 +828,6 @@ Here are all the available command functions you can import and use. For detaile use tauri_plugin_serialplugin::commands::{ // Port discovery available_ports, // Get list of available ports - available_ports_direct, // Get ports using platform-specific commands managed_ports, // Get list of currently managed ports // Connection management @@ -868,10 +842,11 @@ use tauri_plugin_serialplugin::commands::{ read, // Read string data read_binary, // Read binary data - // Listening - start_listening, // Start listening for data - stop_listening, // Stop listening - cancel_read, // Cancel read operations + // Listening (streaming) + capabilities, // Runtime info + watch, // Stream events via Channel + unwatch, // Stop watch session + cancel_read, // Cancel poll read or active watch (shared stop channel) // Port configuration set_baud_rate, // Set baud rate @@ -950,7 +925,7 @@ pub fn write( - "Failed to open serial port: {error}" - Error opening port - "Failed to clone serial port: {error}" - Error cloning port - "Failed to set short timeout: {error}" - Error setting timeout -- "Failed to stop existing listener: {error}" - Error stopping existing listener +- "Failed to cancel serial port watch: {error}" - Error stopping watch thread - "Failed to join thread: {error}" - Error waiting for thread completion - "Failed to cancel serial port data reading: {error}" - Error canceling data reading @@ -1011,8 +986,6 @@ Below is a list of all permissions the plugin supports. Granting or denying them | `serialplugin:deny-write` | Denies writing data to serial ports | | `serialplugin:allow-write-binary` | Allows writing binary data to serial ports | | `serialplugin:deny-write-binary` | Denies writing binary data to serial ports | -| `serialplugin:allow-available-ports-direct` | Enables the `available_ports_direct` command without any pre-configured scope | -| `serialplugin:deny-available-ports-direct` | Denies the `available_ports_direct` command without any pre-configured scope | | `serialplugin:allow-set-baud-rate` | Allows changing the baud rate of serial ports | | `serialplugin:deny-set-baud-rate` | Denies changing the baud rate of serial ports | | `serialplugin:allow-set-data-bits` | Allows changing the data bits configuration | @@ -1047,10 +1020,12 @@ Below is a list of all permissions the plugin supports. Granting or denying them | `serialplugin:deny-set-break` | Denies starting break signal transmission | | `serialplugin:allow-clear-break` | Allows stopping break signal transmission | | `serialplugin:deny-clear-break` | Denies stopping break signal transmission | -| `serialplugin:allow-start-listening` | Allows starting automatic port monitoring and data listening | -| `serialplugin:deny-start-listening` | Denies starting automatic port monitoring and data listening | -| `serialplugin:allow-stop-listening` | Allows stopping automatic port monitoring and data listening | -| `serialplugin:deny-stop-listening` | Denies stopping automatic port monitoring and data listening | +| `serialplugin:allow-capabilities` | Allows reading runtime plugin capabilities | +| `serialplugin:deny-capabilities` | Denies reading runtime plugin capabilities | +| `serialplugin:allow-watch` | Allows streaming port data through a Tauri Channel | +| `serialplugin:deny-watch` | Denies streaming port data through a Tauri Channel | +| `serialplugin:allow-unwatch` | Allows stopping an active watch session | +| `serialplugin:deny-unwatch` | Denies stopping an active watch session | | `serialplugin:allow-set-log-level` | Allows setting the global log level | | `serialplugin:deny-set-log-level` | Denies setting the global log level | | `serialplugin:allow-get-log-level` | Allows getting the current log level | @@ -1071,7 +1046,6 @@ Below is a list of all permissions the plugin supports. Granting or denying them "serialplugin:allow-read", "serialplugin:allow-write", "serialplugin:allow-write-binary", - "serialplugin:allow-available-ports-direct", "serialplugin:allow-set-baud-rate", "serialplugin:allow-set-data-bits", "serialplugin:allow-set-flow-control", @@ -1089,8 +1063,9 @@ Below is a list of all permissions the plugin supports. Granting or denying them "serialplugin:allow-clear-buffer", "serialplugin:allow-set-break", "serialplugin:allow-clear-break", - "serialplugin:allow-start-listening", - "serialplugin:allow-stop-listening", + "serialplugin:allow-capabilities", + "serialplugin:allow-watch", + "serialplugin:allow-unwatch", "serialplugin:allow-set-log-level", "serialplugin:allow-get-log-level" ] @@ -1102,24 +1077,41 @@ Below is a list of all permissions the plugin supports. Granting or denying them ### Port Discovery +> **Removed in 3.0.0:** `available_ports_direct` — use `available_ports()`. + +> **macOS duplicates:** `serialport-rs` lists both `/dev/cu.*` (callout) and `/dev/tty.*` (dial-in) per device. Pass `{ singlePortPerDevice: true }` to keep one path per device (prefers `/dev/cu.*`, like Node.js `SerialPort.list()`). Default returns all paths. + ```typescript class SerialPort { /** * Lists all available serial ports on the system + * @param options.macOS `singlePortPerDevice` — see note above * @returns {Promise<{[key: string]: PortInfo}>} Map of port names to port information * @example * const ports = await SerialPort.available_ports(); + * const onePerDevice = await SerialPort.available_ports({ singlePortPerDevice: true }); * console.log(ports); */ - static async available_ports(): Promise<{ [key: string]: PortInfo }>; + static async available_ports(options?: AvailablePortsOptions): Promise<{ [key: string]: PortInfo }>; /** - * Lists ports using platform-specific commands for enhanced detection - * @returns {Promise<{[key: string]: PortInfo}>} Map of port names to port information + * Subscribe to available-port hotplug (attach/detach). Sends an initial snapshot, + * then `added` / `removed` events through a Tauri Channel. + * @param handlers Callbacks for snapshot / added / removed + * @param options `singlePortPerDevice`, `pollIntervalMs` (desktop default 2000) + * @returns Handle with `unwatch()` to stop * @example - * const ports = await SerialPort.available_ports_direct(); + * const handle = await SerialPort.watchAvailablePorts({ + * onSnapshot: (ports) => console.log('ports', ports), + * onAdded: (path, info) => console.log('plugged', path, info.type), + * onRemoved: (path) => console.log('unplugged', path), + * }, { singlePortPerDevice: true }); + * // later: await handle.unwatch(); */ - static async available_ports_direct(): Promise<{ [key: string]: PortInfo }>; + static async watchAvailablePorts( + handlers: WatchPortsHandlers, + options?: WatchPortsOptions, + ): Promise; /** * @description Lists all managed serial ports (ports that are currently open and managed by the application). @@ -1153,27 +1145,24 @@ class SerialPort { async close(): Promise; /** - * Starts listening for data on the serial port - * @returns {Promise} A promise that resolves when listening starts - * @throws {Error} If starting listener fails or port is not open + * Streams serial port events through a Tauri Channel. + * @returns {Promise} Handle with `channelId` and `unwatch()` * @example - * await port.startListening(); - * - * // Listen for data events - * port.listen((data) => { - * console.log("Data received:", data); + * const handle = await port.watch({ + * onData: (data) => console.log("Data:", data), + * onError: (message) => console.warn("Non-fatal:", message), + * onDisconnect: (reason) => console.log("Disconnected:", reason), * }); + * await handle.unwatch(); */ - async startListening(): Promise; + async watch(handlers: WatchHandlers, options?: WatchOptions): Promise; /** - * Stops listening for data on the serial port - * @returns {Promise} A promise that resolves when listening stops - * @throws {Error} If stopping listener fails or port is not open + * Runtime plugin info (transport, platform, version). * @example - * await port.stopListening(); + * const caps = await SerialPort.getCapabilities(); */ - async stopListening(): Promise; + static getCapabilities(): Promise; /** * Forces a serial port to close regardless of its state @@ -1232,29 +1221,6 @@ class SerialPort { * const bytesWritten = await port.writeBinary(data); */ async writeBinary(data: Uint8Array | number[]): Promise; - - /** - * Sets up a listener for incoming data - * @param {(data: string | Uint8Array) => void} callback Function to handle received data - * @param {boolean} [decode=true] Whether to decode data as string (true) or return raw bytes (false) - * @returns {Promise} A promise that resolves to an unlisten function - * @example - * const unsubscribe = await port.listen((data) => { - * console.log("Received:", data); - * }); - * - * // Later, to stop listening: - * unsubscribe(); - */ - async listen(callback: (data: string | Uint8Array) => void, decode?: boolean): Promise; - - /** - * Cancels listening for serial port data (does not affect disconnect listeners) - * @returns {Promise} A promise that resolves when listening is cancelled - * @example - * await port.cancelListen(); - */ - async cancelListen(): Promise; } ``` @@ -1340,24 +1306,6 @@ class SerialPort { */ async writeDataTerminalReady(level: boolean): Promise; - /** - * Alternative method to set RTS signal - * @param {boolean} value Signal level (true = high, false = low) - * @returns {Promise} - * @example - * await port.setRequestToSend(true); - */ - async setRequestToSend(value: boolean): Promise; - - /** - * Alternative method to set DTR signal - * @param {boolean} value Signal level (true = high, false = low) - * @returns {Promise} - * @example - * await port.setDataTerminalReady(true); - */ - async setDataTerminalReady(value: boolean): Promise; - /** * Reads the CTS (Clear to Send) signal state * @returns {Promise} Signal state @@ -1489,9 +1437,9 @@ class SerialPort { * @param {number} [options.interval=5000] Reconnection interval in milliseconds * @param {number | null} [options.maxAttempts=10] Maximum number of reconnection attempts (null for infinite) * @param {Function} [options.onReconnect] Callback function called on each reconnection attempt - * @returns {Promise} + * @returns {void} * @example - * await port.enableAutoReconnect({ + * port.enableAutoReconnect({ * interval: 3000, * maxAttempts: 5, * onReconnect: (success, attempt) => { @@ -1499,19 +1447,19 @@ class SerialPort { * } * }); */ - async enableAutoReconnect(options?: { + enableAutoReconnect(options?: { interval?: number; maxAttempts?: number | null; onReconnect?: (success: boolean, attempt: number) => void; - }): Promise; + }): void; /** * Disables auto-reconnect functionality - * @returns {Promise} + * @returns {void} * @example * await port.disableAutoReconnect(); */ - async disableAutoReconnect(): Promise; + disableAutoReconnect(): void; /** * Gets auto-reconnect status and configuration @@ -1553,11 +1501,13 @@ const port = new SerialPort({ }); await port.open(); -await port.startListening(); -await port.listen((data) => { - const sensorValue = parseFloat(data); - console.log("Sensor reading:", sensorValue); +const handle = await port.watch({ + onData: (data) => { + const sensorValue = parseFloat(String(data)); + console.log("Sensor reading:", sensorValue); + }, }); +// await handle.unwatch() when done ``` ### Binary Protocol Communication @@ -1574,14 +1524,12 @@ await port.open(); const command = new Uint8Array([0x02, 0x01, 0x03]); await port.writeBinary(command); -// Start listening for response -await port.startListening(); - -// Read response (raw bytes) -await port.listen((data) => { - const response = data instanceof Uint8Array ? data : new Uint8Array(); - console.log("Response:", response); -}, false); +const handle = await port.watch({ + onData: (data) => { + const response = data instanceof Uint8Array ? data : new Uint8Array(); + console.log("Response:", response); + }, +}, { decode: false }); ``` ### Modbus Communication @@ -1621,27 +1569,20 @@ const port = new SerialPort({ await port.open(); -// Enable auto-reconnect with custom settings +// Enable auto-reconnect: restores both open() and watch() after disconnect await port.enableAutoReconnect({ - interval: 3000, // Try to reconnect every 3 seconds - maxAttempts: 5, // Maximum 5 attempts + interval: 3000, + maxAttempts: 5, onReconnect: (success, attempt) => { - if (success) { - console.log(`Reconnected successfully on attempt ${attempt}`); - } else { - console.log(`Reconnection attempt ${attempt} failed`); - } - } + console.log(success ? `Reconnected on attempt ${attempt}` : `Attempt ${attempt} failed`); + }, }); -// Set up data listener -await port.startListening(); -const unsubscribe = await port.listen((data) => { - console.log("Received data:", data); +const handle = await port.watch({ + onData: (data) => console.log("Received data:", data), + onDisconnect: () => console.log("Port disconnected — auto-reconnect will reopen and re-watch"), }); - -// The port will automatically reconnect if disconnected -// You can also manually trigger reconnection +// Manual reconnect also re-establishes watch when a session was active before disconnect const success = await port.manualReconnect(); if (success) { console.log("Manual reconnection successful"); @@ -1656,58 +1597,68 @@ console.log("Current attempts:", info.currentAttempts); await port.disableAutoReconnect(); ``` ---- +### AT commands (native FIFO queue) + +For modems and AT devices, use **`sendAt()`** / **`sendAtPhases()`** / **`sendSmsPdu()`** — native FIFO queue over **`exchange`** with **line-framed AT completion** (`OK` / `ERROR` / `+CME ERROR` as the final line). + +| Mode | RX | Use when | +|------|-----|----------| +| `watch()` | Streaming Channel events + optional **`onUrc`** | General I/O, live URC | +| `sendAt()` | One structured response per command | AT modems, request/response scripts | + +**Capabilities (v3.0):** + +| Feature | Description | +|---------|-------------| +| `AtCommandResult` | Structured result: `command`, `response`, `status`, `lines`, `solicitedBody`, `urcLines`, `raw` | +| Native queue | Parallel `exchange()` / `sendAt()` **wait in FIFO** (no `"Exchange already in progress"`) | +| `configureAtSession()` | Session defaults: `expectOk`, `stopOnError`, `appendCr`, timeouts, `resultFormat` | +| Default `rxPrepare: 'drain'` | Soft idle drain before each command; use **`purge`** only for recovery | +| `expectOk`, `solicitedPrefixes` | Per-command control via session + `AtCommandOptions` | +| Watch during AT | Watch stays active; live **`SerialEvent::Urc`** via `watch({ onUrc })` | +| Vendor grammar | Auto **`solicitedPrefixes`** from command (`^`, `#`, `$`, `%`, `*`) | +| `resultFormat: 'numeric'` | `ATV0` line codes (`0`/`3`/`4`…) | +| `completionMode: 'atIntermediate'` | CMGS `>` prompt and other intermediate lines | +| `sendAtPhases()` / `sendSmsPdu()` | Multi-phase SMS (prompt → PDU → `SEND OK`) | +| `exchangeBinary()` | Binary write + read-until (PDU + Ctrl+Z) | +| CMUX | `enableMux()`, `openMuxChannel(dlci)`, virtual paths `physical#dlci=N` | + +**Migration (v3.0 major):** + +| Was | Now | +|-----|-----| +| `port.at.enqueue('AT')` | `port.sendAt('AT')` | +| `port.at.enqueuePhases(...)` | `port.sendAtPhases(...)` | +| `port.at.sendSmsPdu(...)` | `port.sendSmsPdu(...)` | +| `port.at.cancel()` | `port.cancelAt()` | +| `new SerialPort({ atSession })` | `atSession` still applies on `open()` via `configureAtSession` | +| Parallel `exchange()` throws | `exchange()` awaits turn in native queue | -## Android Setup - -To use this plugin on Android, you **must** add the JitPack repository to your project's `build.gradle.kts` file located at `/src-tauri/gen/android/build.gradle.kts`. This is required because the plugin depends on `com.github.mik3y:usb-serial-for-android:3.8.1`, which is hosted on JitPack. - -### Required Configuration - -Add the JitPack repository to both `buildscript` and `allprojects` sections: - -```kotlin -buildscript { - repositories { - google() - mavenCentral() - maven { url = uri("https://jitpack.io") } - } - dependencies { - classpath("com.android.tools.build:gradle:8.11.0") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") - // ... your other dependencies - } -} +```typescript +// Session defaults (optional — also via constructor `atSession`) +await port.configureAtSession({ expectOk: true, defaultTimeoutMs: 5000 }); -allprojects { - repositories { - google() - mavenCentral() - maven { url = uri("https://jitpack.io") } - } -} +await port.sendAt('AT^SYSCFG?'); +await port.sendAt('ATV0', { resultFormat: 'numeric' }); +await port.sendSmsPdu(pduLength, pduBytes); -tasks.register("clean").configure { - delete("build") -} +// CMUX: second logical channel on same USB port +await port.enableMux({ command: 'AT+CMUX=0,0,5,31,10,2' }); +const dataPort = await port.openMuxChannel(2); +await dataPort.sendAt('AT'); ``` -### Why is this needed? +Low-level **`exchange`** / **`exchangeBinary`** return **`ExchangeResponse`** (also queued); **`cancel_exchange`** / **`cancelAt()`** cancels in-flight work and rejects queued waiters. -The plugin uses the [usb-serial-for-android](https://github.com/mik3y/usb-serial-for-android) library, which is published on JitPack rather than Maven Central. Without adding JitPack to your repositories, Gradle will fail with an error like: +--- -``` -could not find com.github.mik3y:usb-serial-for-android:3.8.1 -``` +## Android Setup -### Troubleshooting +The plugin vendors [usb-serial-for-android](https://github.com/mik3y/usb-serial-for-android) **3.10.0** in `android/usbserial/`. You do **not** need JitPack in your app's `build.gradle.kts` — only the usual Android repositories (`google()`, `mavenCentral()`). -If you still encounter issues after adding JitPack: +If you previously added `maven { url = uri("https://jitpack.io") }` only for this plugin, you can remove it. -1. Make sure you've added it to **both** `buildscript.repositories` and `allprojects.repositories` -2. Sync your Gradle files in your IDE -3. Clean and rebuild your project: `./gradlew clean build` +See [`android/README.md`](android/README.md) for module details and maintainer notes (`scripts/vendor-usbserial.sh` to refresh upstream sources). --- diff --git a/android/.gitignore b/android/.gitignore index 20a18706..749b99c5 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -1,3 +1,4 @@ /build /.tauri -/.gradle \ No newline at end of file +/.gradle +usbserial/build/ \ No newline at end of file diff --git a/android/BUILD_INSTRUCTIONS.md b/android/BUILD_INSTRUCTIONS.md index 6addac95..953be588 100644 --- a/android/BUILD_INSTRUCTIONS.md +++ b/android/BUILD_INSTRUCTIONS.md @@ -53,11 +53,22 @@ npm run tauri android dev ### 3. Checking Logs ```bash -# View Android logs -adb logcat | grep -E "(SerialPlugin|SerialPortManager)" +# Plugin + Rust only (без шума WebView SSL/DNS): +adb logcat -v time -s UsbPortDriver SerialPlugin RustStdoutStderr -# Or through Android Studio -# View -> Tool Windows -> Logcat +# Только отвалы / снимки состояния порта: +adb logcat -v time -s UsbPortDriver:W UsbPortDriver:E + +# SIOM подробно (debug APK): +adb logcat -v time SerialInputOutputManager UsbPortDriver + +# Перед отвалом ищите SNAPSHOT[heartbeat] и SNAPSHOT[detach-broadcast]: +# lastRx=… — когда последний раз приходили данные +# siom=RUNNING — был ли listen активен +# lastErr — ошибка SIOM до detach (если была) + +# Сохранить сессию в файл: +adb logcat -v time -s UsbPortDriver SerialPlugin RustStdoutStderr > /tmp/serial-usb.log ``` ## Troubleshooting diff --git a/android/README.md b/android/README.md index 2cc3bda1..8262b3cb 100644 --- a/android/README.md +++ b/android/README.md @@ -1,43 +1,16 @@ -# Android module (Tauri serial plugin) - -## Dependency - -- [usb-serial-for-android](https://github.com/mik3y/usb-serial-for-android) (`com.github.mik3y:usb-serial-for-android`) — the `UsbSerialPort` interface: `read` / `write` with timeout, `purgeHwBuffers`, `setFlowControl`, RTS/DTR/CTS lines, etc. - -## Lifecycle / cleanup - -`SerialPlugin` registers `Application.ActivityLifecycleCallbacks` in `load()` and calls `SerialPortManager.cleanup()` when the host `Activity` is destroyed (USB ports closed, broadcast receiver unregistered, IO executor shut down). This complements JS `close` / `closeAll` and reduces leaks or stale native work after the WebView/activity is gone. - -## Unit tests (Kotlin) - -Tests live under `src/test/kotlin/`. Pure JVM tests (no Robolectric required): - -- `SerialModelsTest` — model enums / defaults (`Parity` / `StopBits` round-trip, invalid `fromValue` fallbacks) -- `SerialByteAccumulatorTest` — thread-safe byte coalescing for `BufferedEmitter` -- `SerialDataEmitFieldsTest` — `serialDataPayloadFromChunk` / `flushAccumulatorToEmit` / `applyToJSObject` (binary + UTF-8) -- `SerialPluginConversionTest` — `Map.toJSObject` / `List.toJSArray` helpers from `SerialPlugin.kt` -- `BufferedEmitterTest` — `pendingByteCount()` before flush - -JVM unit tests use **`testImplementation("org.json:json:…")`** so `JSONObject.put` is not the Android stub that throws (“Method … not mocked”). - -`BufferedEmitter` itself still schedules timers and builds `JSObject` on device; the buffer + flush pipeline is covered by the tests above. - -Run from the `android/` directory (Android SDK, **JDK 17+** for Gradle; use **17** if Gradle errors on JDK 25): +# Android serial plugin tests ```bash -cd android -export JAVA_HOME=$(/usr/libexec/java_home -v 17) # macOS; use JDK 17 on Linux/Windows -./gradlew test +cd android && ./gradlew test ``` -This repo includes a Gradle wrapper (`gradlew`, `gradle/wrapper/`). The module depends on `:tauri-android` from `.tauri/tauri-api` (bundled under `android/.tauri/tauri-api` or generated by Tauri). +## Layout -## Behavior tied to the library +| File | Role | +|------|------| +| `UsbPortSession` | one port: SIOM → JNI `feedRx`, `write` → USB | +| `UsbBridge` | `path → session`, enumerate, permission, attach/detach | +| `SerialPlugin` | 6 `@Command` → bridge | +| `MobileBridge` | 4 JNI callbacks | -| Plugin API | Implementation | -|------------|----------------| -| `setTimeout` | Stored in `SerialPortConfig.timeout` and used for `write()` and as a fallback for `read()` when the call passes `timeout == 0`. | -| `clearBuffer` | `UsbSerialPort.purgeHwBuffers()` (when supported by the driver). | -| `setFlowControl` | `UsbSerialPort.setFlowControl(RTS_CTS / XON_XOFF / NONE)`. | -| `bytesToRead` | With **listening** (`startListening`): bytes in the plugin’s [BufferedEmitter] before the next `serialData` flush (not the kernel queue). Without listening: `0`. | -| `bytesToWrite` | Always `0`: writes are synchronous; usb-serial exposes no TX backlog. | +Logcat: `adb logcat -s UsbBridge UsbPort SerialPlugin` diff --git a/android/build.gradle b/android/build.gradle index 16b1be90..fb9acbd9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -3,6 +3,8 @@ plugins { id("org.jetbrains.kotlin.android") } +def isTauriSubproject = rootProject.findProject(":usbserial") == null + android { namespace = "app.tauri.serialplugin" compileSdk = 34 @@ -25,11 +27,23 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" + } + + if (isTauriSubproject) { + buildFeatures { + buildConfig = true + } + sourceSets { + main { + java.srcDirs += "usbserial/src/main/java" + java.srcDirs += "usbserial/stubs" + } + } } } @@ -37,13 +51,19 @@ dependencies { implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.appcompat:appcompat:1.6.0") implementation("com.google.android.material:material:1.7.0") - implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3") testImplementation("junit:junit:4.13.2") // Real org.json for JVM unit tests (Android stubs throw on JSONObject.put — see not-mocked) - testImplementation("org.json:json:20240303") + testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0") + testImplementation("org.robolectric:robolectric:4.14.1") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") implementation(project(":tauri-android")) implementation("com.google.code.gson:gson:2.10.1") - implementation("com.github.mik3y:usb-serial-for-android:3.8.1") -} \ No newline at end of file + + if (isTauriSubproject) { + // Tauri includes only this module; vendored usbserial sources are merged via sourceSets. + implementation("androidx.annotation:annotation:1.9.1") + } else { + implementation(project(":usbserial")) + } +} diff --git a/android/consumer-rules.pro b/android/consumer-rules.pro new file mode 100644 index 00000000..5c8f2ff7 --- /dev/null +++ b/android/consumer-rules.pro @@ -0,0 +1 @@ +-keep class com.hoho.android.usbserial.driver.* { *; } diff --git a/android/settings.gradle b/android/settings.gradle index aa26926f..3bba7402 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -15,11 +15,11 @@ dependencyResolutionManagement { repositories { mavenCentral() google() - maven { url 'https://jitpack.io' } } } rootProject.name = 'tauri-plugin-serialplugin-android' +include ':usbserial' include ':tauri-android' project(':tauri-android').projectDir = new File('./.tauri/tauri-api') \ No newline at end of file diff --git a/android/src/main/kotlin/app/tauri/serialplugin/MobileBridge.kt b/android/src/main/kotlin/app/tauri/serialplugin/MobileBridge.kt new file mode 100644 index 00000000..213b928a --- /dev/null +++ b/android/src/main/kotlin/app/tauri/serialplugin/MobileBridge.kt @@ -0,0 +1,19 @@ +package app.tauri.serialplugin + +/** + * JNI entry points into the Tauri Rust library (loaded by the host app). + * Rust implements these in [mobile_jni.rs]. + */ +object MobileBridge { + @JvmStatic + external fun feedRx(path: String, data: ByteArray) + + @JvmStatic + external fun onUsbError(path: String, reason: String) + + @JvmStatic + external fun onPortListChange() + + @JvmStatic + external fun onAppDestroy() +} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/SerialPlugin.kt b/android/src/main/kotlin/app/tauri/serialplugin/SerialPlugin.kt index ad5df6e1..c3c69b32 100644 --- a/android/src/main/kotlin/app/tauri/serialplugin/SerialPlugin.kt +++ b/android/src/main/kotlin/app/tauri/serialplugin/SerialPlugin.kt @@ -5,660 +5,36 @@ package app.tauri.serialplugin import android.app.Activity import android.app.Application import android.os.Bundle -import app.tauri.annotation.Command -import app.tauri.annotation.InvokeArg +import android.webkit.WebView import app.tauri.annotation.TauriPlugin -import app.tauri.plugin.Invoke -import app.tauri.plugin.JSObject import app.tauri.plugin.Plugin -import app.tauri.serialplugin.manager.SerialPortManager -import app.tauri.serialplugin.models.* -import android.webkit.WebView -import android.util.Log -import app.tauri.plugin.JSArray -// --- Reused from previous answer (Converts a Map to a JSObject) --- -fun Map.toJSObject(): JSObject { - val jsObject = JSObject() - for ((key, value) in this) { - val convertedValue: Any? = when (value) { - is Map<*, *> -> @Suppress("UNCHECKED_CAST") (value as Map).toJSObject() - is List<*> -> @Suppress("UNCHECKED_CAST") value.toJSArray() // Call the new list utility - else -> value - } - jsObject.put(key, convertedValue) - } - return jsObject -} - -// --- NEW Utility: Converts a List to a JSArray --- -fun List.toJSArray(): JSArray { - val jsArray = JSArray() - - for (item in this) { - val convertedItem: Any? = when (item) { - is Map<*, *> -> { - // If the item is a Map, convert it to a JSObject - @Suppress("UNCHECKED_CAST") - (item as Map).toJSObject() - } - is List<*> -> { - // If the item is a nested List, convert it to a JSArray (for list of lists) - @Suppress("UNCHECKED_CAST") - item.toJSArray() - } - else -> item // Primitives (String, Int, Boolean, etc.) can be put directly - } - // Add the converted item to the JSArray - jsArray.put(convertedItem) - } - - return jsArray -} -@InvokeArg -class PortConfigArgs { - lateinit var path: String - var baudRate: Int = 9600 - var dataBits: Any? = null - val size: Int? = null - var flowControl: Any? = null - var parity: Any? = null - var stopBits: Any? = null - var timeout: Int = 1000 -} - -@InvokeArg -class WriteArgs { - lateinit var path: String - lateinit var value: String -} - -@InvokeArg -class WriteBinaryArgs { - lateinit var path: String - lateinit var value: List -} - -@InvokeArg -class CloseArgs { - lateinit var path: String -} - -@InvokeArg -class StartListenArgs { - lateinit var path: String - /** Flush interval for batched serialData events (ms). Default 100; clamped 10–2000 on native side. */ - var serialDataFlushIntervalMs: Long = 100L -} +import app.tauri.serialplugin.manager.UsbBridge @TauriPlugin class SerialPlugin(private val activity: Activity) : Plugin(activity) { - private var webView: WebView? = null - private lateinit var serialPortManager: SerialPortManager - /** Unregistered after [activity] is destroyed so we do not leak the callback. */ - private var activityDestroyCallback: Application.ActivityLifecycleCallbacks? = null + private lateinit var usb: UsbBridge + private var destroyCb: Application.ActivityLifecycleCallbacks? = null override fun load(webView: WebView) { super.load(webView) - serialPortManager = SerialPortManager(activity) { path, message -> - try { - val errorData = JSObject() - errorData.put("path", path) - errorData.put("error", message) - trigger("serialError", errorData) - } catch (e: Exception) { - Log.e("SerialPlugin", "serialError trigger failed: ${e.message}", e) - } - } - this.webView = webView - registerActivityDestroyCleanup() - - Log.d("SerialPlugin", "SerialPlugin loaded successfully") - } - - /** - * [Plugin] has no `onDestroy()`. When the host [Activity] is destroyed (back, process kill path), - * release USB threads/receiver/ports so we do not leak or fire stale events after WebView is gone. - */ - private fun registerActivityDestroyCleanup() { - unregisterActivityDestroyCleanup() + usb = UsbBridge(activity.applicationContext) + UsbNative.bind(usb) val app = activity.application - val cb = object : Application.ActivityLifecycleCallbacks { - override fun onActivityDestroyed(destroyed: Activity) { - if (destroyed !== activity) return - try { - Log.d("SerialPlugin", "Activity destroyed — releasing USB serial resources") - serialPortManager.cleanup() - } catch (e: Exception) { - Log.e("SerialPlugin", "cleanup on activity destroy failed: ${e.message}", e) - } finally { - unregisterActivityDestroyCleanup() - } - } - - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {} - override fun onActivityStarted(activity: Activity) {} - override fun onActivityResumed(activity: Activity) {} - override fun onActivityPaused(activity: Activity) {} - override fun onActivityStopped(activity: Activity) {} - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} - } - activityDestroyCallback = cb - app.registerActivityLifecycleCallbacks(cb) - } - - private fun unregisterActivityDestroyCleanup() { - activityDestroyCallback?.let { cb -> - try { - activity.application.unregisterActivityLifecycleCallbacks(cb) - } catch (_: Exception) { - } - activityDestroyCallback = null - } - } - - @Command - fun availablePorts(invoke: Invoke) { - try { - Log.d("SerialPlugin", "Fetching available ports") - val ports = serialPortManager.getAvailablePorts() - Log.d("SerialPlugin", "Available ports fetched successfully: ${ports.size} ports") - val result = JSObject() - - result.put("ports", ports.toJSObject()) - invoke.resolve(result) - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to get available ports: ${e.message}", e) - invoke.reject("Failed to get available ports: ${e.message}") - } - } - - @Command - fun managedPorts(invoke: Invoke) { - try { - val managedPorts = serialPortManager.getManagedPorts() - Log.d("SerialPlugin", "Managed ports: ${managedPorts.size} ports") - val result = JSObject() - result.put("ports", managedPorts) - invoke.resolve(result) - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to get managed ports: ${e.message}", e) - invoke.reject("Failed to get managed ports: ${e.message}") - } - } - - @Command - fun open(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - Log.d("SerialPlugin", "Opening port: ${args.path}") - - val dataBits = when (args.dataBits) { - is String -> DataBits.valueOf(args.dataBits as String) - is Number -> DataBits.fromValue((args.dataBits as Number).toInt()) - null -> DataBits.EIGHT - else -> throw IllegalArgumentException("Invalid data bits type") - } - - val flowControl = when (args.flowControl) { - is String -> FlowControl.valueOf(args.flowControl as String) - is Number -> FlowControl.fromValue((args.flowControl as Number).toInt()) - null -> FlowControl.NONE - else -> throw IllegalArgumentException("Invalid flow control type") - } - - val parity = when (args.parity) { - is String -> Parity.valueOf(args.parity as String) - is Number -> Parity.fromValue((args.parity as Number).toInt()) - null -> Parity.NONE - else -> throw IllegalArgumentException("Invalid parity type") - } - - val stopBits = when (args.stopBits) { - is String -> StopBits.valueOf(args.stopBits as String) - is Number -> StopBits.fromValue((args.stopBits as Number).toInt()) - null -> StopBits.ONE - else -> throw IllegalArgumentException("Invalid stop bits type") - } - - val serialConfig = SerialPortConfig( - path = args.path, - baudRate = args.baudRate, - dataBits = dataBits, - flowControl = flowControl, - parity = parity, - stopBits = stopBits, - timeout = args.timeout - ) - - val success = serialPortManager.openPort(serialConfig) - if (success) { - Log.d("SerialPlugin", "Port opened successfully: ${args.path}") - invoke.resolve() - } else { - Log.e("SerialPlugin", "Failed to open port: ${args.path}") - invoke.reject("Failed to open port") - } - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to open port: ${e.message}", e) - invoke.reject("Failed to open port: ${e.message}") - } - } - - @Command - fun write(invoke: Invoke) { - try { - val args = invoke.parseArgs(WriteArgs::class.java) - Log.d("SerialPlugin", "Writing to port: ${args.path}, data: ${args.value}") - val bytesWritten = serialPortManager.writeToPort(args.path, args.value.toByteArray()) - val result = JSObject() - result.put("bytesWritten", bytesWritten) - Log.d("SerialPlugin", "Write successful: $bytesWritten bytes written") - invoke.resolve(result) - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to write data: ${e.message}", e) - invoke.reject("Failed to write data: ${e.message}") - } - } - - @Command - fun close(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - Log.d("SerialPlugin", "Closing port: ${args.path}") - serialPortManager.closePort(args.path) - Log.d("SerialPlugin", "Port closed successfully: ${args.path}") - invoke.resolve() - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to close port: ${e.message}", e) - invoke.reject("Failed to close port: ${e.message}") - } - } - - @Command - fun closeAll(invoke: Invoke) { - try { - Log.d("SerialPlugin", "Closing all ports") - serialPortManager.closeAllPorts() - Log.d("SerialPlugin", "All ports closed successfully") - invoke.resolve() - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to close all ports: ${e.message}", e) - invoke.reject("Failed to close all ports: ${e.message}") - } - } - - @Command - fun forceClose(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - Log.d("SerialPlugin", "Force closing port: ${args.path}") - serialPortManager.closePort(args.path) - Log.d("SerialPlugin", "Port force closed successfully: ${args.path}") - invoke.resolve() - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to force close port: ${e.message}", e) - invoke.reject("Failed to force close port: ${e.message}") - } - } - - @Command - fun writeBinary(invoke: Invoke) { - try { - val args = invoke.parseArgs(WriteBinaryArgs::class.java) - Log.d("SerialPlugin", "Writing binary to port: ${args.path}") - val bytesToSend = args.value.map { it.toByte() }.toByteArray() - val bytesWritten = serialPortManager.writeToPort(args.path, bytesToSend) - val result = JSObject() - result.put("bytesWritten", bytesWritten) - Log.d("SerialPlugin", "Binary write successful: $bytesWritten bytes written") - invoke.resolve(result) - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to write binary data: ${e.message}", e) - invoke.reject("Failed to write binary data: ${e.message}") - } - } - - @Command - fun read(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - Log.d("SerialPlugin", "Reading from port: ${args.path}") - val data = serialPortManager.readFromPort(args.path, args.timeout, args.size) - val result = JSObject() - result.put("data", String(data)) - Log.d("SerialPlugin", "Read successful: ${data.size} bytes read") - invoke.resolve(result) - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to read data: ${e.message}", e) - invoke.reject("Failed to read data: ${e.message}") - } - } - - @Command - fun readBinary(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - Log.d("SerialPlugin", "Reading binary from port: ${args.path}") - val data = serialPortManager.readFromPort(args.path, args.timeout, args.size) - val result = JSObject().apply { - put("data", data.toList().map { it.toInt() and 0xFF }.toJSArray()) - } - Log.d("SerialPlugin", "Binary read successful: ${data.size} bytes read") - invoke.resolve(result) - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to read binary data: ${e.message}", e) - invoke.reject("Failed to read binary data: ${e.message}") - } - } - - @Command - fun startListening(invoke: Invoke) { - try { - val args = invoke.parseArgs(StartListenArgs::class.java) - val path = args.path - val flushMs = args.serialDataFlushIntervalMs.coerceIn(10L, 2000L) - Log.d("SerialPlugin", "Starting listening on port: $path (flush ${flushMs}ms)") - - serialPortManager.startListening(path, flushMs) { eventData: JSObject -> - try { - Log.d("SerialPlugin", "Emitting serialData batch for $path") - trigger("serialData", eventData) - } catch (e: Exception) { - Log.e("SerialPlugin", "Error in serialData emit: ${e.message}", e) - } - } - Log.d("SerialPlugin", "Listening started successfully on port: $path") - invoke.resolve() - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to start listening: ${e.message}", e) - invoke.reject("Failed to start listening: ${e.message}") - } - } - - @Command - fun stopListening(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - Log.d("SerialPlugin", "Stopping listening on port: ${args.path}") - serialPortManager.stopListening(args.path) - Log.d("SerialPlugin", "Listening stopped successfully on port: ${args.path}") - invoke.resolve() - } catch (e: Exception) { - Log.e("SerialPlugin", "Failed to stop listening: ${e.message}", e) - invoke.reject("Failed to stop listening: ${e.message}") - } - } - - @Command - fun setBaudRate(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - val success = serialPortManager.setBaudRate(args.path, args.baudRate) - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to set baud rate") - } - } catch (e: Exception) { - invoke.reject("Failed to set baud rate: ${e.message}") - } - } - - @Command - fun setDataBits(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - val dataBits = when (args.dataBits) { - is String -> DataBits.valueOf(args.dataBits as String) - is Number -> DataBits.fromValue((args.dataBits as Number).toInt()) - null -> DataBits.EIGHT - else -> throw IllegalArgumentException("Invalid data bits type") - } - val success = serialPortManager.setDataBits(args.path, dataBits) - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to set data bits") - } - } catch (e: Exception) { - invoke.reject("Failed to set data bits: ${e.message}") - } - } - - @Command - fun setFlowControl(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - val flowControl = when (args.flowControl) { - is String -> FlowControl.valueOf(args.flowControl as String) - is Number -> FlowControl.fromValue((args.flowControl as Number).toInt()) - null -> FlowControl.NONE - else -> throw IllegalArgumentException("Invalid flow control type") - } - val success = serialPortManager.setFlowControl(args.path, flowControl) - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to set flow control") - } - } catch (e: Exception) { - invoke.reject("Failed to set flow control: ${e.message}") - } - } - - @Command - fun setParity(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - val parity = when (args.parity) { - is String -> Parity.valueOf(args.parity as String) - is Number -> Parity.fromValue((args.parity as Number).toInt()) - null -> Parity.NONE - else -> throw IllegalArgumentException("Invalid parity type") - } - val success = serialPortManager.setParity(args.path, parity) - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to set parity") - } - } catch (e: Exception) { - invoke.reject("Failed to set parity: ${e.message}") - } - } - - @Command - fun setStopBits(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - val stopBits = when (args.stopBits) { - is String -> StopBits.valueOf(args.stopBits as String) - is Number -> StopBits.fromValue((args.stopBits as Number).toInt()) - null -> StopBits.ONE - else -> throw IllegalArgumentException("Invalid stop bits type") - } - val success = serialPortManager.setStopBits(args.path, stopBits) - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to set stop bits") - } - } catch (e: Exception) { - invoke.reject("Failed to set stop bits: ${e.message}") - } - } - - @Command - fun setTimeout(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - val success = serialPortManager.setTimeout(args.path, args.timeout) - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to set timeout") - } - } catch (e: Exception) { - invoke.reject("Failed to set timeout: ${e.message}") - } - } - - @Command - fun writeRequestToSend(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - val success = serialPortManager.writeRequestToSend(args.path, args.flowControl == "HARDWARE") - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to set RTS") - } - } catch (e: Exception) { - invoke.reject("Failed to set RTS: ${e.message}") - } - } - - @Command - fun writeDataTerminalReady(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - val success = serialPortManager.writeDataTerminalReady(args.path, args.flowControl == "HARDWARE") - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to set DTR") - } - } catch (e: Exception) { - invoke.reject("Failed to set DTR: ${e.message}") - } - } - - @Command - fun readClearToSend(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - val state = serialPortManager.readClearToSend(args.path) - val result = JSObject() - result.put("state", state) - invoke.resolve(result) - } catch (e: Exception) { - invoke.reject("Failed to read CTS: ${e.message}") - } - } - - @Command - fun readDataSetReady(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - val state = serialPortManager.readDataSetReady(args.path) - val result = JSObject() - result.put("state", state) - invoke.resolve(result) - } catch (e: Exception) { - invoke.reject("Failed to read DSR: ${e.message}") - } - } - - @Command - fun readRingIndicator(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - val state = serialPortManager.readRingIndicator(args.path) - val result = JSObject() - result.put("state", state) - invoke.resolve(result) - } catch (e: Exception) { - invoke.reject("Failed to read RI: ${e.message}") - } - } - - @Command - fun readCarrierDetect(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - val state = serialPortManager.readCarrierDetect(args.path) - val result = JSObject() - result.put("state", state) - invoke.resolve(result) - } catch (e: Exception) { - invoke.reject("Failed to read CD: ${e.message}") - } - } - - @Command - fun bytesToRead(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - val bytes = serialPortManager.bytesToRead(args.path) - val result = JSObject() - result.put("bytes", bytes) - invoke.resolve(result) - } catch (e: Exception) { - invoke.reject("Failed to get bytes to read: ${e.message}") - } - } - - @Command - fun bytesToWrite(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - val bytes = serialPortManager.bytesToWrite(args.path) - val result = JSObject() - result.put("bytes", bytes) - invoke.resolve(result) - } catch (e: Exception) { - invoke.reject("Failed to get bytes to write: ${e.message}") - } - } - - @Command - fun clearBuffer(invoke: Invoke) { - try { - val args = invoke.parseArgs(PortConfigArgs::class.java) - val bufferType = when (args.dataBits) { - is String -> ClearBuffer.fromValue(args.dataBits as String) - is Number -> ClearBuffer.INPUT // By default we use INPUT for numeric values - null -> ClearBuffer.INPUT - else -> throw IllegalArgumentException("Invalid buffer type") - } - val success = serialPortManager.clearBuffer(args.path, bufferType.name.lowercase()) - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to clear buffer") - } - } catch (e: Exception) { - invoke.reject("Failed to clear buffer: ${e.message}") - } - } - - @Command - fun setBreak(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - val success = serialPortManager.setBreak(args.path) - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to set break") - } - } catch (e: Exception) { - invoke.reject("Failed to set break: ${e.message}") - } - } - - @Command - fun clearBreak(invoke: Invoke) { - try { - val args = invoke.parseArgs(CloseArgs::class.java) - val success = serialPortManager.clearBreak(args.path) - if (success) { - invoke.resolve() - } else { - invoke.reject("Failed to clear break") - } - } catch (e: Exception) { - invoke.reject("Failed to clear break: ${e.message}") - } + destroyCb = object : Application.ActivityLifecycleCallbacks { + override fun onActivityDestroyed(a: Activity) { + if (a !== activity) return + usb.shutdown() + MobileBridge.onAppDestroy() + app.unregisterActivityLifecycleCallbacks(this) + destroyCb = null + } + override fun onActivityCreated(a: Activity, s: Bundle?) {} + override fun onActivityStarted(a: Activity) {} + override fun onActivityResumed(a: Activity) {} + override fun onActivityPaused(a: Activity) {} + override fun onActivityStopped(a: Activity) {} + override fun onActivitySaveInstanceState(a: Activity, s: Bundle) {} + } + app.registerActivityLifecycleCallbacks(destroyCb!!) } } diff --git a/android/src/main/kotlin/app/tauri/serialplugin/UsbNative.kt b/android/src/main/kotlin/app/tauri/serialplugin/UsbNative.kt new file mode 100644 index 00000000..6e78809b --- /dev/null +++ b/android/src/main/kotlin/app/tauri/serialplugin/UsbNative.kt @@ -0,0 +1,119 @@ +package app.tauri.serialplugin + +import app.tauri.serialplugin.manager.UsbBridge +import app.tauri.serialplugin.models.ClearBuffer +import app.tauri.serialplugin.models.DataBits +import app.tauri.serialplugin.models.FlowControl +import app.tauri.serialplugin.models.Parity +import app.tauri.serialplugin.models.SerialPortConfig +import app.tauri.serialplugin.models.StopBits +import org.json.JSONObject + +/** Static JNI facade: Rust calls these instead of Tauri @Command handlers. */ +object UsbNative { + @Volatile + private var bridge: UsbBridge? = null + + @JvmStatic + fun bind(usb: UsbBridge) { + bridge = usb + nativeInit() + } + + @JvmStatic + private external fun nativeInit() + + private fun usb(): UsbBridge = + bridge ?: throw IllegalStateException("UsbNative not bound") + + @JvmStatic + fun enumerateJson(): String = usb().runOnIoSync { + val ports = JSONObject() + usb().enumerate().forEach { (path, info) -> + ports.put(path, JSONObject(info)) + } + JSONObject().put("ports", ports).toString() + } + + @JvmStatic + fun open( + path: String, + baudRate: Int, + dataBits: Int, + flowControl: Int, + parity: Int, + stopBits: Int, + timeout: Int, + ) { + usb().runOnIoSync { + usb().open( + SerialPortConfig( + path = path, + baudRate = baudRate, + dataBits = DataBits.fromValue(dataBits), + flowControl = FlowControl.fromValue(flowControl), + parity = Parity.fromValue(parity), + stopBits = StopBits.fromValue(stopBits), + timeout = timeout, + ), + ) + } + } + + @JvmStatic + fun close(path: String?) { + usb().runOnIoSync { usb().close(path) } + } + + @JvmStatic + fun write(path: String, data: ByteArray): Int = + usb().runOnIoSync { usb().write(path, data) } + + @JvmStatic + fun read(path: String, timeout: Int, size: Int): ByteArray = + usb().runOnIoSync { usb().read(path, timeout, size) } + + @JvmStatic + fun ctl( + path: String, + op: String, + baudRate: Int, + timeout: Int, + dataBits: Int, + flowControl: Int, + parity: Int, + stopBits: Int, + bufferType: Int, + ): Boolean = usb().runOnIoSync { + val buf = ClearBuffer.fromValue(bufferType).name.lowercase() + when (val r = usb().ctl( + path, + op, + mapOf( + "baudRate" to baudRate, + "timeout" to timeout, + "dataBits" to dataBits, + "flowControl" to flowControl, + "parity" to parity, + "stopBits" to stopBits, + "bufferType" to buf, + ), + )) { + is Boolean -> r + is Number -> true + else -> false + } + } + + @JvmStatic + fun ctlBytesToWrite(path: String): Int = usb().runOnIoSync { + when (val r = usb().ctl(path, "bytesToWrite", emptyMap())) { + is Number -> r.toInt() + else -> 0 + } + } + + @JvmStatic + fun signal(path: String, op: String, level: Boolean): Boolean = + usb().runOnIoSync { usb().signal(path, op, level) } +} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/BufferedEmitter.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/BufferedEmitter.kt deleted file mode 100644 index 1581019f..00000000 --- a/android/src/main/kotlin/app/tauri/serialplugin/manager/BufferedEmitter.kt +++ /dev/null @@ -1,78 +0,0 @@ -package app.tauri.serialplugin.manager - -import android.util.Log -import app.tauri.plugin.JSObject -import java.util.concurrent.Executors -import java.util.concurrent.ScheduledFuture -import java.util.concurrent.TimeUnit - -/** - * Coalesces high-frequency [onNewData] chunks and emits at most once per [flushIntervalMs] - * to reduce WebView/JS pressure (backpressure). - */ -internal class BufferedEmitter( - private val path: String, - flushIntervalMs: Long, - private val emit: (JSObject) -> Unit, -) { - private val accumulator = SerialByteAccumulator() - private val scheduler = Executors.newSingleThreadScheduledExecutor { r -> - Thread(r, "serial-emit-$path").apply { isDaemon = true } - } - private val scheduled: ScheduledFuture<*> - - init { - val interval = flushIntervalMs.coerceIn(10L, 2000L) - scheduled = scheduler.scheduleAtFixedRate( - { - try { - flushOnce() - } catch (e: Exception) { - Log.e("BufferedEmitter", "flush: ${e.message}", e) - } - }, - interval, - interval, - TimeUnit.MILLISECONDS, - ) - } - - private fun flushOnce() { - flushAccumulatorToEmit(path, accumulator) { fields -> - val eventData = JSObject() - fields.applyToJSObject(eventData) - emit(eventData) - } - } - - fun addData(data: ByteArray) { - accumulator.append(data) - } - - /** - * Bytes received via [addData] but not yet emitted to JS (waiting for the next flush). - * Does not include data still inside the USB/driver stack — only this plugin buffer. - */ - fun pendingByteCount(): Int = accumulator.pendingByteCount() - - fun stop() { - scheduled.cancel(false) - scheduler.shutdown() - try { - if (!scheduler.awaitTermination(300, TimeUnit.MILLISECONDS)) { - scheduler.shutdownNow() - } - } catch (_: InterruptedException) { - scheduler.shutdownNow() - } - try { - flushAccumulatorToEmit(path, accumulator) { fields -> - val eventData = JSObject() - fields.applyToJSObject(eventData) - emit(eventData) - } - } catch (e: Exception) { - Log.w("BufferedEmitter", "final flush: ${e.message}") - } - } -} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/CoalescingRxSink.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/CoalescingRxSink.kt new file mode 100644 index 00000000..948fadca --- /dev/null +++ b/android/src/main/kotlin/app/tauri/serialplugin/manager/CoalescingRxSink.kt @@ -0,0 +1,72 @@ +package app.tauri.serialplugin.manager + +import java.io.ByteArrayOutputStream +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executors +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit + +/** + * Merges rapid SIOM RX chunks before JNI (SimpleUsbTerminal SerialService pattern). + * Disabled in test mode ([immediate] = true). + */ +internal class CoalescingRxSink( + private val delegate: SerialRxSink, + private val immediate: Boolean, +) : SerialRxSink { + private val buffers = ConcurrentHashMap() + private val flushTasks = ConcurrentHashMap>() + private val scheduler = Executors.newSingleThreadScheduledExecutor { r -> + Thread(r, "usb-rx-coalesce").apply { isDaemon = true } + } + + override fun feedRx(path: String, data: ByteArray) { + if (immediate || data.isEmpty()) { + if (data.isNotEmpty()) delegate.feedRx(path, data) + return + } + val buf = buffers.computeIfAbsent(path) { ByteArrayOutputStream() } + val flushNow: Boolean + synchronized(buf) { + buf.write(data) + flushNow = buf.size() >= MAX_BATCH + } + if (flushNow) { + cancelFlush(path) + flush(path) + } else { + scheduleFlush(path) + } + } + + override fun onUsbError(path: String, reason: String) { + cancelFlush(path) + buffers.remove(path) + delegate.onUsbError(path, reason) + } + + override fun onPortListChange() = delegate.onPortListChange() + + private fun scheduleFlush(path: String) { + flushTasks.compute(path) { _, existing -> + existing?.cancel(false) + scheduler.schedule({ flush(path) }, FLUSH_MS, TimeUnit.MILLISECONDS) + } + } + + private fun cancelFlush(path: String) { + flushTasks.remove(path)?.cancel(false) + } + + private fun flush(path: String) { + flushTasks.remove(path) + val buf = buffers.remove(path) ?: return + val bytes = synchronized(buf) { buf.toByteArray() } + if (bytes.isNotEmpty()) delegate.feedRx(path, bytes) + } + + companion object { + private const val FLUSH_MS = 8L + private const val MAX_BATCH = 512 + } +} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/CustomProber.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/CustomProber.kt new file mode 100644 index 00000000..a37f0434 --- /dev/null +++ b/android/src/main/kotlin/app/tauri/serialplugin/manager/CustomProber.kt @@ -0,0 +1,13 @@ +package app.tauri.serialplugin.manager + +import com.hoho.android.usbserial.driver.ProbeTable +import com.hoho.android.usbserial.driver.UsbSerialProber + +/** Fallback prober for VID/PID not in the default table (host can extend later). */ +internal object CustomProber { + fun getCustomProber(): UsbSerialProber { + val table = ProbeTable() + // Example: table.addProduct(0x1234, 0xabcd, FtdiSerialDriver::class.java) + return UsbSerialProber(table) + } +} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/ModemLines.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/ModemLines.kt new file mode 100644 index 00000000..b28e6a08 --- /dev/null +++ b/android/src/main/kotlin/app/tauri/serialplugin/manager/ModemLines.kt @@ -0,0 +1,14 @@ +package app.tauri.serialplugin.manager + +import com.hoho.android.usbserial.driver.UsbSerialPort + +internal fun setModemLines(port: UsbSerialPort, dtr: Boolean, rts: Boolean) { + try { + port.dtr = dtr + } catch (_: Exception) { + } + try { + port.rts = rts + } catch (_: Exception) { + } +} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialByteAccumulator.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialByteAccumulator.kt deleted file mode 100644 index b52e8221..00000000 --- a/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialByteAccumulator.kt +++ /dev/null @@ -1,34 +0,0 @@ -package app.tauri.serialplugin.manager - -import java.io.ByteArrayOutputStream - -/** - * Thread-safe byte buffer for coalescing serial chunks before emitting to JS. - * Pure JVM logic — unit-testable without Robolectric. - */ -internal class SerialByteAccumulator { - private val lock = Any() - private val stream = ByteArrayOutputStream() - - fun append(data: ByteArray) { - if (data.isEmpty()) return - synchronized(lock) { - stream.write(data) - } - } - - /** - * Returns all accumulated bytes and clears the buffer. - * Returns an empty array if nothing was pending. - */ - fun drain(): ByteArray { - synchronized(lock) { - if (stream.size() == 0) return ByteArray(0) - val b = stream.toByteArray() - stream.reset() - return b - } - } - - fun pendingByteCount(): Int = synchronized(lock) { stream.size() } -} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialDataEmitFields.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialDataEmitFields.kt deleted file mode 100644 index 5371c1b8..00000000 --- a/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialDataEmitFields.kt +++ /dev/null @@ -1,39 +0,0 @@ -package app.tauri.serialplugin.manager - -import app.tauri.plugin.JSObject - -/** - * Payload fields for the `serialData` plugin event (mirrors what we put on [JSObject]). - * Kept as a plain Kotlin type so tests do not need [JSObject]. - */ -internal data class SerialDataEmitFields( - val path: String, - val dataAsString: String, - val size: Int, -) - -internal fun serialDataPayloadFromChunk(path: String, chunk: ByteArray): SerialDataEmitFields = - SerialDataEmitFields( - path = path, - dataAsString = String(chunk), - size = chunk.size, - ) - -internal fun SerialDataEmitFields.applyToJSObject(target: JSObject) { - target.put("path", path) - target.put("data", dataAsString) - target.put("size", size) -} - -/** - * Drains [accumulator] and invokes [emitFields] once if there was data. - */ -internal fun flushAccumulatorToEmit( - path: String, - accumulator: SerialByteAccumulator, - emitFields: (SerialDataEmitFields) -> Unit, -) { - val chunk = accumulator.drain() - if (chunk.isEmpty()) return - emitFields(serialDataPayloadFromChunk(path, chunk)) -} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialPortManager.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialPortManager.kt deleted file mode 100644 index 4b82b7a7..00000000 --- a/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialPortManager.kt +++ /dev/null @@ -1,736 +0,0 @@ -package app.tauri.serialplugin.manager - -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.hardware.usb.UsbDevice -import android.hardware.usb.UsbManager -import android.os.Build -import com.hoho.android.usbserial.driver.UsbSerialPort -import com.hoho.android.usbserial.driver.UsbSerialProber -import com.hoho.android.usbserial.util.SerialInputOutputManager -import com.hoho.android.usbserial.driver.ProbeTable -import app.tauri.plugin.JSObject -import app.tauri.serialplugin.models.* -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.Executors -import java.io.IOException -import android.util.Log -import androidx.core.content.ContextCompat -import java.util.concurrent.CompletableFuture -import java.util.concurrent.TimeUnit - -data class ManagedPort ( - val port: UsbSerialPort, - val config: SerialPortConfig -) - -/** - * @param onIoRunError Optional: invoked on [SerialInputOutputManager.Listener.onRunError] before [closePort] - * (e.g. emit plugin event so JS can set isOpen = false). - */ -class SerialPortManager( - private val context: Context, - private val onIoRunError: ((path: String, message: String) -> Unit)? = null, -) { - private val usbManager: UsbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager - private val portMap = mutableMapOf() - /** Active [SerialInputOutputManager] per path; must be stopped on close/stop/replace to avoid thread leaks. */ - private val ioManagers = ConcurrentHashMap() - /** Batched emission to WebView (one per path while listening). */ - private val emitters = ConcurrentHashMap() - private val executor = Executors.newCachedThreadPool() - private val permissionFutures = mutableMapOf>() - - private val ACTION_USB_PERMISSION = "app.tauri.serialplugin.USB_PERMISSION" - - // Custom prober for unknown devices (custom VID/PID only) - private val customProber: UsbSerialProber by lazy { - val customTable = ProbeTable() - - // Add only devices with custom VID/PID not covered by the default table - // Example: device with VID=0x1234 and PID=0x0001 compatible with FTDI - // customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver::class.java) - - UsbSerialProber(customTable) - } - - private val usbReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (ACTION_USB_PERMISSION == intent.action) { - synchronized(this) { - val device: UsbDevice? = if (Build.VERSION.SDK_INT >= 33) { - intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java) - } else { - @Suppress("DEPRECATION") - intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) as UsbDevice? - } - - val permissionGranted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) - val deviceName = device?.deviceName - - Log.d("SerialPortManager", "USB permission result for $deviceName: $permissionGranted") - - deviceName?.let { name -> - permissionFutures[name]?.complete(permissionGranted) - permissionFutures.remove(name) - } - } - } - } - } - - fun registerReceiver() { - val filter = IntentFilter(ACTION_USB_PERMISSION) - - if (Build.VERSION.SDK_INT >= 33) { - context.registerReceiver(usbReceiver, filter, Context.RECEIVER_EXPORTED) - } else { - ContextCompat.registerReceiver( - context, - usbReceiver, - filter, - ContextCompat.RECEIVER_NOT_EXPORTED - ) - } - } - - fun unregisterReceiver() { - try { - context.unregisterReceiver(usbReceiver) - } catch (_: IllegalArgumentException) { - Log.w("SerialPortManager", "Receiver not registered") - } - } - - init { - registerReceiver() - } - - fun getAvailablePorts(): Map> { - val result = mutableMapOf>() - - try { - // Use default prober first - val availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager) - Log.d("SerialPortManager", "Available drivers (default prober): ${availableDrivers.size}") - - availableDrivers.forEach { driver -> - val device = driver.device - Log.d("SerialPortManager", "Found device: ${device.deviceName}, Vendor ID: ${device.vendorId}, Product ID: ${device.productId}") - - result[device.deviceName] = mapOf( - "type" to "USB", - "vid" to device.vendorId.toString(), - "pid" to device.productId.toString(), - "manufacturer" to (device.manufacturerName ?: "Unknown"), - "product" to (device.productName ?: "Unknown"), - "serial_number" to (device.serialNumber ?: "Unknown") - ) - - Log.d("SerialPortManager", "Device Info: ${result[device.deviceName]}") - } - - // Also check for custom prober devices - val customDrivers = customProber.findAllDrivers(usbManager) - Log.d("SerialPortManager", "Available drivers (custom prober): ${customDrivers.size}") - - customDrivers.forEach { driver -> - val device = driver.device - if (!result.containsKey(device.deviceName)) { - Log.d("SerialPortManager", "Found custom device: ${device.deviceName}, Vendor ID: ${device.vendorId}, Product ID: ${device.productId}") - - result[device.deviceName] = mapOf( - "type" to "USB (Custom)", - "vid" to device.vendorId.toString(), - "pid" to device.productId.toString(), - "manufacturer" to (device.manufacturerName ?: "Unknown"), - "product" to (device.productName ?: "Unknown"), - "serial_number" to (device.serialNumber ?: "Unknown") - ) - } - } - - } catch (e: Exception) { - Log.e("SerialPortManager", "Error getting available ports: ${e.message}", e) - } - - Log.d("SerialPortManager", "Total available ports: ${result.size}") - return result - } - - fun getManagedPorts(): List { - return portMap.keys.toList() - } - - fun openPort(config: SerialPortConfig): Boolean { - try { - Log.d("SerialPortManager", "Opening port: ${config.path}") - - // Find the device by name - val device = findDeviceByName(config.path) - ?: throw IOException("Device not found: ${config.path}") - - // Probe for driver using default prober first - var driver = UsbSerialProber.getDefaultProber().probeDevice(device) - - // If no driver found, try custom prober - if (driver == null) { - driver = customProber.probeDevice(device) - Log.d("SerialPortManager", "Device found via custom prober: ${device.deviceName}") - } - - if (driver == null) { - throw IOException("No driver found for device: ${config.path}") - } - - // Check permissions - if (!usbManager.hasPermission(device)) { - Log.d("SerialPortManager", "Requesting USB permission for device: ${device.deviceName}") - - val permissionFuture = CompletableFuture() - permissionFutures[device.deviceName] = permissionFuture - - val flags = - PendingIntent.FLAG_IMMUTABLE - - val permissionIntent = PendingIntent.getBroadcast( - context, - 0, - Intent(ACTION_USB_PERMISSION), - flags - ) - usbManager.requestPermission(device, permissionIntent) - - // Wait for permission result with timeout - val permissionGranted = permissionFuture.get(10, TimeUnit.SECONDS) - if (!permissionGranted) { - throw IOException("USB permission denied for device: ${config.path}") - } - } - - // Open connection - val connection = usbManager.openDevice(device) - ?: throw IOException("Failed to open device: ${config.path}") - - // Get port (most devices have just one port) - val port = driver.ports[0] - - // Open port - port.open(connection) - Log.d("SerialPortManager", "Setting port parameters: baudRate=${config.baudRate}, dataBits=${config.dataBits.value}, stopBits=${config.stopBits.value}, parity=${config.parity.value}") - - try { - port.setParameters( - config.baudRate, - config.dataBits.value, - config.stopBits.value, - config.parity.value - ) - Log.d("SerialPortManager", "Port parameters set successfully") - } catch (_: UnsupportedOperationException) { - Log.w("SerialPortManager", "setParameters not supported for this device, using default settings") - // Some devices don't support parameter changes, continue with defaults - } catch (e: Exception) { - Log.w("SerialPortManager", "Failed to set parameters: ${e.message}, using default settings") - // Continue with default parameters - } - - // Flow control — [UsbSerialPort.setFlowControl](https://github.com/mik3y/usb-serial-for-android) - when (config.flowControl) { - FlowControl.NONE -> { - try { - port.setFlowControl(UsbSerialPort.FlowControl.NONE) - Log.d("SerialPortManager", "Flow control: NONE") - } catch (e: Exception) { - Log.w("SerialPortManager", "setFlowControl(NONE): ${e.message}") - } - } - FlowControl.HARDWARE -> { - Log.d("SerialPortManager", "Enabling RTS/CTS flow control") - try { - port.setFlowControl(UsbSerialPort.FlowControl.RTS_CTS) - Log.d("SerialPortManager", "Hardware (RTS/CTS) flow control set") - } catch (_: UnsupportedOperationException) { - Log.w("SerialPortManager", "RTS/CTS not supported, falling back to DTR/RTS pins") - try { - port.dtr = true - port.rts = true - } catch (e: Exception) { - Log.w("SerialPortManager", "Fallback DTR/RTS failed: ${e.message}") - } - } catch (e: Exception) { - Log.w("SerialPortManager", "Failed to set RTS/CTS: ${e.message}") - } - } - FlowControl.SOFTWARE -> { - Log.d("SerialPortManager", "Enabling XON/XOFF flow control") - try { - port.setFlowControl(UsbSerialPort.FlowControl.XON_XOFF) - Log.d("SerialPortManager", "Software flow control set") - } catch (e: Exception) { - Log.w("SerialPortManager", "XON/XOFF not supported: ${e.message}") - } - } - } - - portMap[config.path] = ManagedPort(port, config) - Log.d("SerialPortManager", "Port opened successfully: ${config.path}") - return true - - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to open port: ${e.message}", e) - throw IOException("Failed to open port: ${e.message}") - } - } - - private fun findDeviceByName(deviceName: String): UsbDevice? { - return usbManager.deviceList.values.find { it.deviceName == deviceName } - } - - private fun startIoManager( - path: String, - port: UsbSerialPort, - flushIntervalMs: Long, - emit: (JSObject) -> Unit, - ) { - // Replace existing reader: stop previous manager so threads are not leaked - emitters.remove(path)?.stop() - ioManagers.remove(path)?.let { old -> - try { - old.stop() - } catch (e: Exception) { - Log.w("SerialPortManager", "Failed to stop previous IO manager for $path: ${e.message}") - } - } - - val emitter = BufferedEmitter(path, flushIntervalMs, emit) - emitters[path] = emitter - - val ioManager = SerialInputOutputManager(port, object : SerialInputOutputManager.Listener { - override fun onNewData(data: ByteArray) { - try { - Log.d("SerialPortManager", "Data received on $path: ${data.size} bytes") - emitter.addData(data) - } catch (e: Exception) { - Log.e("SerialPortManager", "Error in data callback for $path: ${e.message}", e) - } - } - - override fun onRunError(e: Exception) { - Log.e("SerialPortManager", "IO Manager error for $path: ${e.message}", e) - val msg = e.message ?: e.toString() - try { - onIoRunError?.invoke(path, msg) - } catch (cb: Exception) { - Log.e("SerialPortManager", "onIoRunError callback failed: ${cb.message}", cb) - } - closePort(path) - } - }) - - ioManagers[path] = ioManager - - try { - executor.submit { - try { - ioManager.start() - Log.d("SerialPortManager", "IO Manager started successfully for $path") - } catch (e: Exception) { - Log.e( - "SerialPortManager", - "Failed to start IO Manager for $path: ${e.message}", - e - ) - ioManagers.remove(path) - emitters.remove(path)?.stop() - closePort(path) - } - } - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to submit IO Manager task for $path: ${e.message}", e) - ioManagers.remove(path) - emitters.remove(path)?.stop() - closePort(path) - } - } - - fun writeToPort(path: String, data: ByteArray): Int { - try { - val port = portMap[path] ?: throw IOException("Port not found") - - Log.d("SerialPortManager", "Writing to port $path: ${data.size} bytes") - - val writeTimeout = port.config.timeout.coerceIn(1, 600_000) - port.port.write(data, writeTimeout) - val bytesWritten = data.size - - return bytesWritten - } catch (e: IOException) { - Log.e("SerialPortManager", "Write failed: ${e.message}") - throw e - } catch (e: Exception) { - Log.e("SerialPortManager", "Unexpected error during write: ${e.message}", e) - throw IOException("Failed to write data: ${e.message}") - } - } - - fun closePort(path: String) { - try { - emitters.remove(path)?.stop() - ioManagers[path]?.let { mgr -> - try { - mgr.stop() - } catch (e: Exception) { - Log.w("SerialPortManager", "Failed to stop IO manager for $path: ${e.message}") - } - } - ioManagers.remove(path) - portMap[path]?.port?.close() - portMap.remove(path) - Log.d("SerialPortManager", "Port closed: $path") - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to close port $path: ${e.message}", e) - throw IOException("Failed to close port: ${e.message}") - } - } - - fun closeAllPorts() { - val exceptions = mutableListOf() - - portMap.keys.toList().forEach { path -> - try { - closePort(path) - } catch (e: Exception) { - exceptions.add(e) - } - } - - if (exceptions.isNotEmpty()) { - throw IOException("Failed to close all ports: ${exceptions.joinToString(", ") { it.message ?: "" }}") - } - } - - fun setPortParameters(path: String, config: SerialPortConfig): Boolean { - return try { - portMap[path]?.let { port -> - port.port.setParameters( - config.baudRate, - config.dataBits.value, - config.stopBits.value, - config.parity.value - ) - true - } ?: false - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to set port parameters: ${e.message}", e) - false - } - } - - fun readFromPort(path: String, timeout: Int, size: Int?): ByteArray { - return try { - val port = portMap[path] ?: throw IOException("Port not found") - - val targetSize = size ?: 1024 - val maxPacketSize = port.port.readEndpoint.maxPacketSize - val bufferSize = minOf(targetSize, maxPacketSize) - - val buffer = ByteArray(bufferSize) - // Prefer invoke timeout; fall back to open/config timeout (usb-serial: read timeout in ms) - val stored = port.config.timeout - val adjustedTimeout = (if (timeout > 0) timeout else stored).coerceAtLeast(200) - - Log.d("SerialPortManager", "Reading from port $path: bufferSize=$bufferSize, timeout=$adjustedTimeout") - - val bytesRead = port.port.read(buffer, adjustedTimeout) - - if (bytesRead > 0) { - Log.d("SerialPortManager", "Read successful: $bytesRead bytes") - buffer.copyOf(bytesRead) - } else { - Log.w("SerialPortManager", "Read timeout: no data received within $adjustedTimeout ms") - throw IOException("Read timeout: no data received within $adjustedTimeout ms") - } - } catch (e: IOException) { - Log.e("SerialPortManager", "Read failed: ${e.message}") - throw e - } catch (e: Exception) { - Log.e("SerialPortManager", "Unexpected error during read: ${e.message}", e) - throw IOException("Failed to read data: ${e.message}") - } - } - - fun readFullyFromPort(path: String, timeout: Int, size: Int?): ByteArray { - val port = portMap[path] ?: throw IOException("Port not found") - val buffer = mutableListOf() - val startTime = System.currentTimeMillis() - - val targetSize = size ?: 1024 - val maxPacketSize = port.port.readEndpoint.maxPacketSize - - while (buffer.size < targetSize && (System.currentTimeMillis() - startTime) < timeout) { - val remainingTime = timeout - (System.currentTimeMillis() - startTime).toInt() - if (remainingTime <= 0) break - - val chunkSize = minOf(targetSize - buffer.size, maxPacketSize) - val tempBuffer = ByteArray(chunkSize) - val bytesRead = port.port.read(tempBuffer, remainingTime.coerceAtLeast(200)) - - if (bytesRead > 0) { - buffer.addAll(tempBuffer.copyOf(bytesRead).toList()) - } else { - throw IOException("Read timeout: no data received within $timeout ms") - } - } - - return if (buffer.isEmpty()) { - throw IOException("Read timeout: no data received within $timeout ms") - } else { - buffer.toByteArray() - } - } - - fun setBaudRate(path: String, baudRate: Int): Boolean { - return try { - val port = portMap[path] ?: return false - port.config.baudRate = baudRate - port.port.setParameters(port.config.baudRate, port.config.dataBits.value, port.config.stopBits.value, port.config.parity.value) - true - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to set baud rate: ${e.message}", e) - false - } - } - - fun setDataBits(path: String, dataBits: DataBits): Boolean { - return try { - val port = portMap[path] ?: return false - port.config.dataBits = dataBits - port.port.setParameters(port.config.baudRate, port.config.dataBits.value, port.config.stopBits.value, port.config.parity.value) - true - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to set data bits: ${e.message}", e) - false - } - } - - fun setFlowControl(path: String, flowControl: FlowControl): Boolean { - return try { - val p = portMap[path]?.port ?: return false - when (flowControl) { - FlowControl.NONE -> p.setFlowControl(UsbSerialPort.FlowControl.NONE) - FlowControl.HARDWARE -> p.setFlowControl(UsbSerialPort.FlowControl.RTS_CTS) - FlowControl.SOFTWARE -> p.setFlowControl(UsbSerialPort.FlowControl.XON_XOFF) - } - portMap[path]?.let { it.config.flowControl = flowControl } - true - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to set flow control: ${e.message}", e) - false - } - } - - fun setParity(path: String, parity: Parity): Boolean { - return try { - val port = portMap[path] ?: return false - port.config.parity = parity - port.port.setParameters(port.config.baudRate, port.config.dataBits.value, port.config.stopBits.value, port.config.parity.value) - true - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to set parity: ${e.message}", e) - false - } - } - - fun setStopBits(path: String, stopBits: StopBits): Boolean { - return try { - val port = portMap[path] ?: return false - port.config.stopBits = stopBits - port.port.setParameters(port.config.baudRate, port.config.dataBits.value, port.config.stopBits.value, port.config.parity.value) - true - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to set stop bits: ${e.message}", e) - false - } - } - - fun setTimeout(path: String, timeout: Int): Boolean { - return try { - val managed = portMap[path] ?: return false - // No USB-level "read timeout" register — store for read()/write() (see usb-serial read/write timeout args) - managed.config.timeout = timeout.coerceIn(1, 600_000) - Log.d("SerialPortManager", "Read/write timeout preference set to ${managed.config.timeout} ms for $path") - true - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to set timeout: ${e.message}", e) - false - } - } - - fun writeRequestToSend(path: String, level: Boolean): Boolean { - return try { - portMap[path]?.port?.rts = level - true - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to set RTS: ${e.message}", e) - false - } - } - - fun writeDataTerminalReady(path: String, level: Boolean): Boolean { - return try { - portMap[path]?.port?.dtr = level - true - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to set DTR: ${e.message}", e) - false - } - } - - fun readClearToSend(path: String): Boolean { - return try { - portMap[path]?.port?.cts ?: false - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to read CTS: ${e.message}", e) - false - } - } - - fun readDataSetReady(path: String): Boolean { - return try { - portMap[path]?.port?.dsr ?: false - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to read DSR: ${e.message}", e) - false - } - } - - fun readRingIndicator(path: String): Boolean { - return try { - portMap[path]?.port?.ri ?: false - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to read RI: ${e.message}", e) - false - } - } - - fun readCarrierDetect(path: String): Boolean { - return try { - portMap[path]?.port?.cd ?: false - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to read CD: ${e.message}", e) - false - } - } - - /** - * When [startListening] is active, returns bytes accumulated in [BufferedEmitter] before the next - * `serialData` flush — **not** the OS/driver queue (UsbSerialPort exposes no such API). - * Without an active listener, returns `0`. - */ - fun bytesToRead(path: String): Int { - return try { - if (!portMap.containsKey(path)) throw IOException("Port not found") - emitters[path]?.pendingByteCount() ?: 0 - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to get bytes to read: ${e.message}", e) - 0 - } - } - - /** - * [UsbSerialPort] does not expose a pending TX queue; [writeToPort] is synchronous, so there is - * nothing application-level queued after a successful write — always `0` here. - */ - fun bytesToWrite(path: String): Int { - return try { - if (!portMap.containsKey(path)) throw IOException("Port not found") - 0 - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to get bytes to write: ${e.message}", e) - 0 - } - } - - /** [UsbSerialPort.purgeHwBuffers](https://github.com/mik3y/usb-serial-for-android) */ - fun clearBuffer(path: String, bufferType: String): Boolean { - return try { - val p = portMap[path]?.port ?: return false - when (bufferType.lowercase()) { - "input" -> p.purgeHwBuffers(false, true) - "output" -> p.purgeHwBuffers(true, false) - "all" -> p.purgeHwBuffers(true, true) - else -> return false - } - true - } catch (e: UnsupportedOperationException) { - Log.w("SerialPortManager", "purgeHwBuffers not supported: ${e.message}") - false - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to clear buffer: ${e.message}", e) - false - } - } - - fun setBreak(path: String): Boolean { - return try { - portMap[path]?.port?.setBreak(true) - true - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to set break: ${e.message}", e) - false - } - } - - fun clearBreak(path: String): Boolean { - return try { - portMap[path]?.port?.setBreak(false) - true - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to clear break: ${e.message}", e) - false - } - } - - /** - * @param flushIntervalMs how often to flush buffered bytes to [emit] (10–2000 ms). - */ - fun startListening( - path: String, - flushIntervalMs: Long, - emit: (JSObject) -> Unit, - ) { - val port = portMap[path] ?: throw IOException("Port not found") - startIoManager(path, port.port, flushIntervalMs, emit) - } - - fun stopListening(path: String) { - try { - emitters.remove(path)?.stop() - ioManagers[path]?.let { mgr -> - try { - mgr.stop() - } catch (e: Exception) { - Log.w("SerialPortManager", "Failed to stop IO manager for $path: ${e.message}") - } - } - ioManagers.remove(path) - Log.d("SerialPortManager", "Stopped listening on port: $path") - } catch (e: Exception) { - Log.e("SerialPortManager", "Failed to stop listening: ${e.message}", e) - } - } - - fun cleanup() { - try { - closeAllPorts() - unregisterReceiver() - executor.shutdown() - } catch (e: Exception) { - Log.e("SerialPortManager", "Error during cleanup: ${e.message}", e) - } - } -} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialRxSink.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialRxSink.kt new file mode 100644 index 00000000..93adcab6 --- /dev/null +++ b/android/src/main/kotlin/app/tauri/serialplugin/manager/SerialRxSink.kt @@ -0,0 +1,16 @@ +package app.tauri.serialplugin.manager + +import app.tauri.serialplugin.MobileBridge + +/** Injectable sink for RX bytes and USB lifecycle (production = JNI). */ +internal interface SerialRxSink { + fun feedRx(path: String, data: ByteArray) + fun onUsbError(path: String, reason: String) + fun onPortListChange() +} + +internal object JniSerialRxSink : SerialRxSink { + override fun feedRx(path: String, data: ByteArray) = MobileBridge.feedRx(path, data) + override fun onUsbError(path: String, reason: String) = MobileBridge.onUsbError(path, reason) + override fun onPortListChange() = MobileBridge.onPortListChange() +} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/UsbBridge.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/UsbBridge.kt new file mode 100644 index 00000000..a615b119 --- /dev/null +++ b/android/src/main/kotlin/app/tauri/serialplugin/manager/UsbBridge.kt @@ -0,0 +1,348 @@ +package app.tauri.serialplugin.manager + +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.hardware.usb.UsbDevice +import android.hardware.usb.UsbManager +import android.os.Build +import android.util.Log +import androidx.core.content.ContextCompat +import app.tauri.serialplugin.models.FlowControl +import app.tauri.serialplugin.models.SerialPortConfig +import com.hoho.android.usbserial.driver.UsbSerialDriver +import com.hoho.android.usbserial.driver.UsbSerialPort +import com.hoho.android.usbserial.driver.UsbSerialProber +import java.io.IOException +import java.util.concurrent.CompletableFuture +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.RejectedExecutionException +import java.util.concurrent.TimeUnit + +/** path → open session; USB permission + attach/detach only. */ +class UsbBridge private constructor( + private val context: Context?, + private val rxSink: SerialRxSink, + private val registerReceiver: Boolean, + private val testMode: Boolean, +) { + constructor(context: Context) : this( + context, + CoalescingRxSink(JniSerialRxSink, immediate = false), + true, + false, + ) + + companion object { + private const val TAG = "UsbBridge" + private const val ACTION_USB_PERMISSION = "app.tauri.serialplugin.USB_PERMISSION" + + internal fun forTesting(rxSink: SerialRxSink): UsbBridge = + UsbBridge(null, rxSink, false, true) + } + + private val ioExecutor: Executor = if (testMode) { + Executor { it.run() } + } else { + Executors.newSingleThreadExecutor { r -> Thread(r, "usb-io").apply { isDaemon = true } } + } + private val usbManager = context?.getSystemService(Context.USB_SERVICE) as? UsbManager + private val sessions = ConcurrentHashMap() + private val permissionFutures = ConcurrentHashMap>() + private val closing = ConcurrentHashMap.newKeySet() + private val defaultProber by lazy { UsbSerialProber.getDefaultProber() } + private val customProber by lazy { CustomProber.getCustomProber() } + + private val usbReceiver = object : BroadcastReceiver() { + override fun onReceive(ctx: Context, intent: Intent) { + when (intent.action) { + ACTION_USB_PERMISSION -> { + val device = deviceFromIntent(intent) + val granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) + device?.deviceName?.let { permissionFutures[it]?.complete(granted) } + } + UsbManager.ACTION_USB_DEVICE_DETACHED -> { + val device = deviceFromIntent(intent) + val deviceName = device?.deviceName + Log.w(TAG, "DETACH device=$deviceName vid=${device?.vendorId} pid=${device?.productId}") + if (deviceName != null) { + sessions.keys + .filter { UsbPath.deviceName(it) == deviceName } + .forEach { path -> runOnIo { fail(path, "USB device detached") } } + } + rxSink.onPortListChange() + } + UsbManager.ACTION_USB_DEVICE_ATTACHED -> { + val d = deviceFromIntent(intent) + Log.d(TAG, "ATTACH path=${d?.deviceName} vid=${d?.vendorId} pid=${d?.productId}") + rxSink.onPortListChange() + } + } + } + } + + init { + if (registerReceiver && context != null) { + val filter = IntentFilter(ACTION_USB_PERMISSION).apply { + addAction(UsbManager.ACTION_USB_DEVICE_DETACHED) + addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED) + } + if (Build.VERSION.SDK_INT >= 33) { + context.registerReceiver(usbReceiver, filter, Context.RECEIVER_EXPORTED) + } else { + ContextCompat.registerReceiver( + context, usbReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED, + ) + } + } + } + + /** Enqueue port teardown on [usb-io], then stop the executor (PR #34 lifecycle pattern). */ + fun shutdown() { + if (testMode) { + sessions.keys.toList().forEach { close(it) } + return + } + val pool = ioExecutor as ExecutorService + val ctx = context + val shouldUnregister = registerReceiver + try { + pool.execute { + try { + sessions.keys.toList().forEach { close(it) } + } finally { + if (shouldUnregister && ctx != null) { + try { + ctx.unregisterReceiver(usbReceiver) + } catch (_: IllegalArgumentException) { + } + } + } + } + } catch (_: RejectedExecutionException) { + sessions.keys.toList().forEach { close(it) } + if (shouldUnregister && ctx != null) { + try { + ctx.unregisterReceiver(usbReceiver) + } catch (_: IllegalArgumentException) { + } + } + } + pool.shutdown() + } + + /** All blocking USB work runs here — never on the UI thread. */ + fun runOnIo(block: () -> Unit) { + ioExecutor.execute(block) + } + + /** Blocking USB work on [usb-io]; safe from any JNI/Rust thread. */ + fun runOnIoSync(block: () -> T): T = + if (testMode) { + block() + } else { + CompletableFuture.supplyAsync(block, ioExecutor).get() + } + + internal fun adoptFakePort(port: UsbSerialPort, config: SerialPortConfig) { + check(testMode) + if (config.assertDtrRts) setModemLines(port, dtr = true, rts = true) + putSession(config.path, newSession(config.path, port, config)) + sessions[config.path]!!.startReadLoop() + } + + fun enumerate(): Map> { + val mgr = usbManager ?: return emptyMap() + return buildMap { + allDrivers(mgr).forEach { driver -> + val d = driver.device + val ports = driver.ports + ports.indices.forEach { idx -> + val key = UsbPath.sessionKey(d.deviceName, idx, ports.size) + put(key, deviceInfo(d, idx, ports.size)) + } + } + } + } + + fun open(config: SerialPortConfig) { + val mgr = usbManager ?: throw IOException("no UsbManager") + val (deviceName, pathPort) = UsbPath.parse(config.path) + val portIndex = if (pathPort != 0) pathPort else config.portIndex + close(config.path) + val device = mgr.deviceList.values.find { it.deviceName == deviceName } + ?: throw IOException("device not found: $deviceName") + val driver = probeDevice(device) ?: throw IOException("no driver: $deviceName") + if (portIndex < 0 || portIndex >= driver.ports.size) { + throw IOException("invalid port index $portIndex for $deviceName") + } + val sessionPath = UsbPath.sessionKey(deviceName, portIndex, driver.ports.size) + if (!mgr.hasPermission(device)) requestPermission(device) + val conn = mgr.openDevice(device) ?: throw IOException("open failed: $deviceName") + val port = driver.ports[portIndex] + port.open(conn) + try { + port.setParameters( + config.baudRate, config.dataBits.value, + config.stopBits.value, config.parity.value, + ) + } catch (_: Exception) { + } + applyFlowControl(port, config.flowControl) + if (config.assertDtrRts) setModemLines(port, dtr = true, rts = true) + val sessionConfig = config.copy(path = sessionPath, portIndex = portIndex) + putSession(sessionPath, newSession(sessionPath, port, sessionConfig)) + sessions[sessionPath]!!.startReadLoop() + Log.i(TAG, "open $sessionPath port=$portIndex") + } + + fun close(path: String?) { + if (path == null) { + sessions.keys.toList().forEach { close(it) } + return + } + if (!closing.add(path)) return + try { + sessions.remove(path)?.let { session -> + session.shutdown() + session.close() + } + } finally { + closing.remove(path) + } + } + + fun write(path: String, data: ByteArray): Int = + session(path).write(data) + + fun read(path: String, timeout: Int, size: Int?): ByteArray = + session(path).pollRead(timeout, size ?: 1024) + + fun ctl(path: String, op: String, params: Map): Any? = + session(path).ctl(op, params) + + fun signal(path: String, op: String, level: Boolean?): Boolean = + session(path).signal(op, level) + + internal fun isSiomRunning(path: String) = sessions[path]?.isReading == true + + internal fun stopSiom(path: String) { + sessions[path]?.stopReadLoop() + } + + private fun putSession(path: String, s: UsbPortSession) { + sessions[path]?.close() + sessions[path] = s + } + + private fun session(path: String): UsbPortSession = + sessions[path] ?: throw IOException("port not open: $path") + + private fun newSession( + path: String, + port: UsbSerialPort, + config: SerialPortConfig, + ) = UsbPortSession( + path, + port, + config, + onRx = { rxSink.feedRx(path, it) }, + onError = { msg -> fail(path, msg) }, + onRecover = { runOnIo { sessions[path]?.restartReadLoop() } }, + ) + + private fun fail(path: String, reason: String) { + Log.e(TAG, "fail $path: $reason") + val session = sessions.remove(path) ?: return + session.shutdown() + rxSink.onUsbError(path, reason) + try { + session.close() + } catch (_: Exception) { + } + } + + private fun probeDevice(device: UsbDevice): UsbSerialDriver? = + defaultProber.probeDevice(device) ?: customProber.probeDevice(device) + + private fun allDrivers(mgr: UsbManager): List { + val seen = LinkedHashSet() + val out = ArrayList() + defaultProber.findAllDrivers(mgr).forEach { d -> + if (seen.add(d.device)) out.add(d) + } + mgr.deviceList.values.forEach { device -> + if (seen.add(device)) { + customProber.probeDevice(device)?.let { out.add(it) } + } + } + return out + } + + private fun requestPermission(device: UsbDevice) { + val ctx = context ?: throw IOException("no context") + val future = CompletableFuture() + permissionFutures[device.deviceName] = future + try { + val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE + } else { + PendingIntent.FLAG_IMMUTABLE + } + usbManager!!.requestPermission( + device, + PendingIntent.getBroadcast(ctx, 0, Intent(ACTION_USB_PERMISSION), flags), + ) + if (!future.get(10, TimeUnit.SECONDS)) { + throw IOException("permission denied") + } + } finally { + permissionFutures.remove(device.deviceName) + } + } + + private fun applyFlowControl(port: UsbSerialPort, flow: FlowControl) { + try { + when (flow) { + FlowControl.NONE -> port.setFlowControl(UsbSerialPort.FlowControl.NONE) + FlowControl.HARDWARE -> port.setFlowControl(UsbSerialPort.FlowControl.RTS_CTS) + FlowControl.SOFTWARE -> port.setFlowControl(UsbSerialPort.FlowControl.XON_XOFF) + } + } catch (_: Exception) { + } + } + + private fun deviceInfo(device: UsbDevice, portIndex: Int, portCount: Int): Map { + val ok = usbManager!!.hasPermission(device) + val base = mapOf( + "type" to "USB", + "vid" to device.vendorId.toString(), + "pid" to device.productId.toString(), + "manufacturer" to if (ok) device.manufacturerName ?: "?" else "?", + "product" to if (ok) device.productName ?: "?" else "?", + "serial_number" to if (ok) try { + device.serialNumber ?: "?" + } catch (_: SecurityException) { + "permission_required" + } else "permission_required", + ) + return if (portCount > 1) { + base + mapOf("port_index" to portIndex.toString(), "port_count" to portCount.toString()) + } else { + base + } + } + + private fun deviceFromIntent(intent: Intent): UsbDevice? = + if (Build.VERSION.SDK_INT >= 33) { + intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(UsbManager.EXTRA_DEVICE) as UsbDevice? + } +} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/UsbPath.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/UsbPath.kt new file mode 100644 index 00000000..1e6024b7 --- /dev/null +++ b/android/src/main/kotlin/app/tauri/serialplugin/manager/UsbPath.kt @@ -0,0 +1,19 @@ +package app.tauri.serialplugin.manager + +/** Session path ↔ USB device name + port index (`deviceName#1` for multi-port). */ +internal object UsbPath { + private const val SEP = '#' + + fun sessionKey(deviceName: String, portIndex: Int, portCount: Int): String = + if (portCount <= 1) deviceName else "$deviceName$SEP$portIndex" + + fun parse(path: String): Pair { + val idx = path.lastIndexOf(SEP) + if (idx < 0) return path to 0 + val device = path.substring(0, idx) + val port = path.substring(idx + 1).toIntOrNull() ?: 0 + return device to port + } + + fun deviceName(sessionPath: String): String = parse(sessionPath).first +} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/manager/UsbPortSession.kt b/android/src/main/kotlin/app/tauri/serialplugin/manager/UsbPortSession.kt new file mode 100644 index 00000000..96d58e39 --- /dev/null +++ b/android/src/main/kotlin/app/tauri/serialplugin/manager/UsbPortSession.kt @@ -0,0 +1,254 @@ +package app.tauri.serialplugin.manager + +import android.util.Log +import app.tauri.serialplugin.models.ClearBuffer +import app.tauri.serialplugin.models.DataBits +import app.tauri.serialplugin.models.FlowControl +import app.tauri.serialplugin.models.Parity +import app.tauri.serialplugin.models.SerialPortConfig +import app.tauri.serialplugin.models.StopBits +import com.hoho.android.usbserial.driver.SerialTimeoutException +import com.hoho.android.usbserial.driver.UsbSerialPort +import com.hoho.android.usbserial.util.SerialInputOutputManager +import java.io.IOException + +/** + * One open USB serial port: SIOM pushes RX to [onRx], writes go straight to hardware. + */ +internal class UsbPortSession( + val path: String, + private val port: UsbSerialPort, + var config: SerialPortConfig, + private val onRx: (ByteArray) -> Unit, + private val onError: (String) -> Unit, + private val onRecover: () -> Unit, +) { + private val siomLock = Any() + private var siom: SerialInputOutputManager? = null + @Volatile private var alive = true + + val isReading: Boolean + get() = synchronized(siomLock) { siom != null } + + fun startReadLoop() { + synchronized(siomLock) { + if (!alive || siom != null || !port.isOpen) return + startReadLoopLocked() + } + } + + /** Stop SIOM; further recover/restart is ignored until a new session is opened. */ + fun shutdown() { + synchronized(siomLock) { + alive = false + stopReadLoopLocked() + } + } + + fun restartReadLoop() { + synchronized(siomLock) { + if (!alive || !port.isOpen) return + stopReadLoopLocked() + if (!alive || !port.isOpen) return + startReadLoopLocked() + } + } + + fun stopReadLoop() { + synchronized(siomLock) { + stopReadLoopLocked() + } + } + + private fun startReadLoopLocked() { + val mgr = SerialInputOutputManager(port, object : SerialInputOutputManager.Listener { + override fun onNewData(data: ByteArray) { + Log.d(TAG, "RX $path +${data.size}B") + onRx(data) + } + + override fun onRunError(e: Exception) { + val msg = e.message ?: e.toString() + if (isRecoverableSiomError(msg)) { + Log.w(TAG, "SIOM $path: spurious $msg — scheduling read loop restart") + onRecover() + return + } + Log.e(TAG, "SIOM $path: $msg") + onError(msg) + } + }) + val packet = maxOf(port.readEndpoint.maxPacketSize, MIN_READ_BUF) + mgr.setReadBufferSize(packet) + mgr.setReadQueue(0) + mgr.setReadTimeout(0) // requestWait like SimpleUsbTerminal; avoids bulkTransfer+GET_STATUS polling + siom = mgr + mgr.start() + Log.i(TAG, "read loop $path buf=$packet") + } + + private fun stopReadLoopLocked() { + val mgr = siom ?: return + siom = null + try { + mgr.stop() + } catch (_: Exception) { + } + } + + private fun isRecoverableSiomError(msg: String): Boolean { + val lower = msg.lowercase() + if (lower.contains("null object reference") || lower.contains("connection closed")) { + return false + } + return lower.contains("get_status") || + lower.contains("queueing usb request failed") || + lower.contains("waiting for usb request failed") + } + + fun write(data: ByteArray): Int { + val timeout = maxOf(config.timeout.coerceIn(1, 600_000), WRITE_TIMEOUT_MS) + var offset = 0 + while (offset < data.size) { + try { + port.write(data.copyOfRange(offset, data.size), timeout) + Log.d(TAG, "TX $path +${data.size}B") + return data.size + } catch (e: SerialTimeoutException) { + val n = maxOf(e.bytesTransferred, 0) + offset += n + if (n <= 0) throw e + } catch (e: IOException) { + Log.e(TAG, "TX $path failed: ${e.message}") + throw e + } + } + return data.size + } + + fun pollRead(timeout: Int, size: Int): ByteArray { + if (isReading) throw IOException(LISTEN_READ_MUTEX_MESSAGE) + val buf = ByteArray(maxOf(size, MIN_READ_BUF)) + val ms = maxOf(if (timeout > 0) timeout else config.timeout, 200) + val n = port.read(buf, ms) + if (n > 0) return buf.copyOf(n) + throw IOException("Read timeout ($ms ms)") + } + + fun close() { + synchronized(siomLock) { + alive = false + stopReadLoopLocked() + } + if (config.assertDtrRts) setModemLines(port, dtr = false, rts = false) + try { + port.close() + } catch (_: Exception) { + } + Log.i(TAG, "close $path") + } + + fun ctl(op: String, params: Map): Any? = when (op) { + "setBaudRate" -> { + config.baudRate = (params["baudRate"] as Number).toInt() + applyParams() + true + } + "setTimeout" -> { + config.timeout = (params["timeout"] as Number).toInt().coerceIn(1, 600_000) + true + } + "setDataBits" -> { + config.dataBits = DataBits.fromValue((params["dataBits"] as Number).toInt()) + applyParams() + true + } + "setParity" -> { + config.parity = Parity.fromValue((params["parity"] as Number).toInt()) + applyParams() + true + } + "setStopBits" -> { + config.stopBits = StopBits.fromValue((params["stopBits"] as Number).toInt()) + applyParams() + true + } + "setFlowControl" -> setFlowControl((params["flowControl"] as Number).toInt()) + "clearBuffer" -> clearBuffer(ClearBuffer.fromValue(params["bufferType"]?.toString() ?: "input")) + "setBreak" -> setBreak(true) + "clearBreak" -> setBreak(false) + "cancelRead" -> true + "bytesToWrite" -> 0 + else -> false + } + + fun signal(op: String, level: Boolean?): Boolean = try { + when (op) { + "writeRts" -> { + port.rts = level == true + true + } + "writeDtr" -> { + port.dtr = level == true + true + } + "readCts" -> port.cts + "readDsr" -> port.dsr + "readRi" -> port.ri + "readCd" -> port.cd + else -> false + } + } catch (_: Exception) { + false + } + + private fun applyParams() { + port.setParameters( + config.baudRate, + config.dataBits.value, + config.stopBits.value, + config.parity.value, + ) + } + + private fun setFlowControl(value: Int): Boolean = try { + val mode = when (FlowControl.fromValue(value)) { + FlowControl.NONE -> UsbSerialPort.FlowControl.NONE + FlowControl.HARDWARE -> UsbSerialPort.FlowControl.RTS_CTS + FlowControl.SOFTWARE -> UsbSerialPort.FlowControl.XON_XOFF + } + port.setFlowControl(mode) + config.flowControl = FlowControl.fromValue(value) + true + } catch (_: Exception) { + false + } + + private fun clearBuffer(kind: ClearBuffer): Boolean = try { + when (kind) { + ClearBuffer.INPUT -> port.purgeHwBuffers(false, true) + ClearBuffer.OUTPUT -> port.purgeHwBuffers(true, false) + ClearBuffer.ALL -> port.purgeHwBuffers(true, true) + } + true + } catch (_: UnsupportedOperationException) { + true + } catch (_: Exception) { + false + } + + private fun setBreak(value: Boolean): Boolean = try { + port.setBreak(value) + true + } catch (_: Exception) { + false + } + + companion object { + private const val TAG = "UsbPort" + private const val WRITE_TIMEOUT_MS = 2000 + private const val MIN_READ_BUF = 64 + const val LISTEN_READ_MUTEX_MESSAGE = + "Cannot read while watch is active; call unwatch first" + } +} diff --git a/android/src/main/kotlin/app/tauri/serialplugin/models/SerialPortConfig.kt b/android/src/main/kotlin/app/tauri/serialplugin/models/SerialPortConfig.kt index b1b8927c..dcbaccfa 100644 --- a/android/src/main/kotlin/app/tauri/serialplugin/models/SerialPortConfig.kt +++ b/android/src/main/kotlin/app/tauri/serialplugin/models/SerialPortConfig.kt @@ -68,6 +68,15 @@ enum class ClearBuffer { else -> INPUT } } + + fun fromValue(value: Int): ClearBuffer { + return when (value) { + 0 -> INPUT + 1 -> OUTPUT + 2 -> ALL + else -> INPUT + } + } } } @@ -78,5 +87,9 @@ data class SerialPortConfig( var flowControl: FlowControl = FlowControl.NONE, var parity: Parity = Parity.NONE, var stopBits: StopBits = StopBits.ONE, - var timeout: Int = 1000 + var timeout: Int = 1000, + /** USB port index on multi-port adapters (FT2232, …); also encoded in path as `device#N`. */ + var portIndex: Int = 0, + /** Raise DTR/RTS on open, lower on close (Arduino / modem default). */ + var assertDtrRts: Boolean = true, ) diff --git a/android/src/test/kotlin/app/tauri/serialplugin/MobileBridgeResponseTest.kt b/android/src/test/kotlin/app/tauri/serialplugin/MobileBridgeResponseTest.kt new file mode 100644 index 00000000..2465ec87 --- /dev/null +++ b/android/src/test/kotlin/app/tauri/serialplugin/MobileBridgeResponseTest.kt @@ -0,0 +1,16 @@ +package app.tauri.serialplugin + +import org.junit.Assert.assertEquals +import org.junit.Test +import java.nio.charset.Charset + +class MobileBridgeResponseTest { + @Test + fun readIso88591PreservesHighBytes() { + val data = byteArrayOf(0x48, 0x65, 0x6C, 0x6C, 0x6F, 0xFF.toByte(), 0xFE.toByte()) + val text = String(data, Charset.forName("ISO-8859-1")) + assertEquals(7, text.length) + assertEquals(0xFF, text[5].code) + assertEquals(0xFE, text[6].code) + } +} diff --git a/android/src/test/kotlin/app/tauri/serialplugin/SerialPluginConversionTest.kt b/android/src/test/kotlin/app/tauri/serialplugin/SerialPluginConversionTest.kt deleted file mode 100644 index 1e44f089..00000000 --- a/android/src/test/kotlin/app/tauri/serialplugin/SerialPluginConversionTest.kt +++ /dev/null @@ -1,68 +0,0 @@ -package app.tauri.serialplugin - -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Test - -/** - * Tests for package-level [Map.toJSObject] and [List.toJSArray] helpers used by [SerialPlugin]. - */ -class SerialPluginConversionTest { - @Test - fun toJSObject_flatPrimitives() { - val m = mapOf( - "n" to 42, - "s" to "hello", - "b" to true, - ) - val o = m.toJSObject() - assertEquals(42, o.getInteger("n")) - assertEquals("hello", o.getString("s")) - assertEquals(true, o.getBoolean("b")) - } - - @Test - fun toJSObject_nestedMap() { - val inner = mapOf("k" to "v", "x" to 1) - val m = mapOf("outer" to inner) - val o = m.toJSObject() - val innerObj = o.getJSObject("outer") - assertNotNull(innerObj) - assertEquals("v", innerObj!!.getString("k")) - assertEquals(1, innerObj.getInteger("x")) - } - - @Test - fun toJSArray_listOfMaps() { - val list: List = listOf( - mapOf("a" to 1), - mapOf("b" to 2), - ) - val arr = list.toJSArray() - assertEquals(2, arr.length()) - val first = arr.getJSONObject(0) - assertEquals(1, first.getInt("a")) - } - - @Test - fun toJSArray_mixedPrimitives() { - val list: List = listOf("x", 3, false) - val arr = list.toJSArray() - assertEquals(3, arr.length()) - assertEquals("x", arr.getString(0)) - assertEquals(3, arr.getInt(1)) - assertEquals(false, arr.getBoolean(2)) - } - - @Test - fun toJSArray_nestedListOfInts() { - val inner: List = listOf(1, 2) - val outer: List = listOf(inner) - val arr = outer.toJSArray() - assertEquals(1, arr.length()) - val nested = arr.getJSONArray(0) - assertEquals(2, nested.length()) - assertEquals(1, nested.getInt(0)) - assertEquals(2, nested.getInt(1)) - } -} diff --git a/android/src/test/kotlin/app/tauri/serialplugin/manager/BufferedEmitterTest.kt b/android/src/test/kotlin/app/tauri/serialplugin/manager/BufferedEmitterTest.kt deleted file mode 100644 index e919fc3c..00000000 --- a/android/src/test/kotlin/app/tauri/serialplugin/manager/BufferedEmitterTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package app.tauri.serialplugin.manager - -import org.junit.Assert.assertEquals -import org.junit.Test - -class BufferedEmitterTest { - @Test - fun pendingByteCount_tracks_data_before_flush() { - // Max flush interval (2000 ms) so the first scheduled flush is late; we only assert immediately. - val emitter = BufferedEmitter("/dev/usbX", 2000L) { _ -> } - try { - assertEquals(0, emitter.pendingByteCount()) - emitter.addData(byteArrayOf(10, 20, 30)) - assertEquals(3, emitter.pendingByteCount()) - } finally { - emitter.stop() - } - } -} diff --git a/android/src/test/kotlin/app/tauri/serialplugin/manager/FakeUsbSerialPort.kt b/android/src/test/kotlin/app/tauri/serialplugin/manager/FakeUsbSerialPort.kt new file mode 100644 index 00000000..d985ed66 --- /dev/null +++ b/android/src/test/kotlin/app/tauri/serialplugin/manager/FakeUsbSerialPort.kt @@ -0,0 +1,249 @@ +package app.tauri.serialplugin.manager + +import android.hardware.usb.UsbDevice +import android.hardware.usb.UsbDeviceConnection +import android.hardware.usb.UsbEndpoint +import com.hoho.android.usbserial.driver.SerialTimeoutException +import com.hoho.android.usbserial.driver.UsbSerialDriver +import com.hoho.android.usbserial.driver.UsbSerialPort +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import java.io.IOException +import java.util.EnumSet +import java.util.concurrent.LinkedBlockingQueue +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Software UART emulator for JVM tests — same [UsbSerialPort] contract as hardware, + * backed by in-memory RX/TX queues (similar spirit to upstream RFC-2217 device tests). + */ +class FakeUsbSerialPort( + private val path: String = "/dev/bus/usb/001/099", + packetSize: Int = 64, +) : UsbSerialPort { + + private val rxQueue = LinkedBlockingQueue() + private val written = mutableListOf() + private val open = AtomicBoolean(false) + private val readEndpoint: UsbEndpoint = mockEndpoint(packetSize, IN) + private val writeEndpoint: UsbEndpoint = mockEndpoint(packetSize, OUT) + private val device: UsbDevice = mock().also { + whenever(it.deviceName).thenReturn(path) + whenever(it.vendorId).thenReturn(0x1234) + whenever(it.productId).thenReturn(0x5678) + } + private val driver = FakeUsbSerialDriver(device) + + @Volatile var autoAtEcho: Boolean = true + + /** Next [read] throws this message once (SIOM error tests). */ + @Volatile var failNextRead: String? = null + + @Volatile var detached: Boolean = false + + var cts: Boolean = true + var dsr: Boolean = true + var ri: Boolean = false + var cd: Boolean = true + var dtr: Boolean = false + var rts: Boolean = false + + private var baudRate: Int = 115200 + private var dataBits: Int = UsbSerialPort.DATABITS_8 + private var stopBits: Int = UsbSerialPort.STOPBITS_1 + private var parity: Int = UsbSerialPort.PARITY_NONE + private var flowControl: UsbSerialPort.FlowControl = UsbSerialPort.FlowControl.NONE + + fun openForTest() { + detached = false + open.set(true) + } + + fun enqueueRx(data: ByteArray) { + data.forEach { rxQueue.offer(it) } + } + + fun writtenBytes(): ByteArray = written.toByteArray() + + fun clearWritten() { + written.clear() + } + + override fun getDriver(): UsbSerialDriver = driver + + override fun getDevice(): UsbDevice = device + + override fun getPortNumber(): Int = 0 + + override fun getWriteEndpoint(): UsbEndpoint = writeEndpoint + + override fun getReadEndpoint(): UsbEndpoint = readEndpoint + + override fun getSerial(): String? = "FAKE-SERIAL" + + override fun setReadQueue(bufferCount: Int, bufferSize: Int) { + require(open.get()) { "not open" } + } + + override fun getReadQueueBufferCount(): Int = 0 + + override fun getReadQueueBufferSize(): Int = 0 + + override fun open(connection: UsbDeviceConnection) { + openForTest() + } + + override fun close() { + open.set(false) + } + + override fun read(dest: ByteArray, timeout: Int): Int = + read(dest, dest.size, timeout) + + override fun read(dest: ByteArray, length: Int, timeout: Int): Int { + assertOpen() + failNextRead?.let { msg -> + failNextRead = null + throw IOException(msg) + } + if (timeout == 0) { + if (detached) throw IOException("USB device detached") + if (!open.get()) throw IOException("Port not open") + if (Thread.currentThread().isInterrupted) throw IOException("Interrupted") + val first = rxQueue.poll(50, TimeUnit.MILLISECONDS) ?: return 0 + dest[0] = first + var n = 1 + while (n < length) { + val b = rxQueue.poll() ?: break + dest[n++] = b + } + return n + } + val deadline = System.nanoTime() + timeout * 1_000_000L + var total = 0 + while (total < length && System.nanoTime() < deadline) { + val remainingMs = ((deadline - System.nanoTime()) / 1_000_000L).coerceAtLeast(1) + val b = rxQueue.poll(remainingMs, TimeUnit.MILLISECONDS) ?: break + dest[total++] = b + while (total < length) { + val next = rxQueue.poll() ?: break + dest[total++] = next + } + } + return total + } + + override fun write(src: ByteArray, timeout: Int) { + write(src, src.size, timeout) + } + + override fun write(src: ByteArray, length: Int, timeout: Int) { + assertOpen() + val chunk = src.copyOfRange(0, length) + written.addAll(chunk.toList()) + if (autoAtEcho) { + maybeEchoAt(chunk) + } + } + + private fun maybeEchoAt(chunk: ByteArray) { + val text = String(chunk, Charsets.US_ASCII).trim() + when { + text.equals("AT", ignoreCase = true) -> enqueueRx("OK\r\n".toByteArray()) + text.startsWith("AT+", ignoreCase = true) -> { + val prefix = text.substringBefore('?').substringBefore('=') + enqueueRx("$prefix: 0,0\r\nOK\r\n".toByteArray()) + } + } + } + + override fun setParameters(baudRate: Int, dataBits: Int, stopBits: Int, parity: Int) { + assertOpen() + this.baudRate = baudRate + this.dataBits = dataBits + this.stopBits = stopBits + this.parity = parity + } + + override fun getCD(): Boolean = cd + override fun getCTS(): Boolean = cts + override fun getDSR(): Boolean = dsr + override fun getDTR(): Boolean = dtr + override fun getRI(): Boolean = ri + override fun getRTS(): Boolean = rts + + override fun setDTR(value: Boolean) { + dtr = value + } + + override fun setRTS(value: Boolean) { + rts = value + } + + override fun getControlLines(): EnumSet { + val set = EnumSet.noneOf(UsbSerialPort.ControlLine::class.java) + if (cts) set.add(UsbSerialPort.ControlLine.CTS) + if (dsr) set.add(UsbSerialPort.ControlLine.DSR) + if (cd) set.add(UsbSerialPort.ControlLine.CD) + if (ri) set.add(UsbSerialPort.ControlLine.RI) + if (rts) set.add(UsbSerialPort.ControlLine.RTS) + if (dtr) set.add(UsbSerialPort.ControlLine.DTR) + return set + } + + override fun getSupportedControlLines(): EnumSet = + EnumSet.allOf(UsbSerialPort.ControlLine::class.java) + + override fun setFlowControl(flowControl: UsbSerialPort.FlowControl) { + this.flowControl = flowControl + } + + override fun getFlowControl(): UsbSerialPort.FlowControl = flowControl + + override fun getSupportedFlowControl(): EnumSet = + EnumSet.of(UsbSerialPort.FlowControl.NONE, UsbSerialPort.FlowControl.RTS_CTS) + + override fun getXON(): Boolean = true + + override fun purgeHwBuffers(purgeWriteBuffers: Boolean, purgeReadBuffers: Boolean) { + if (purgeReadBuffers) { + rxQueue.clear() + } + if (purgeWriteBuffers) { + written.clear() + } + } + + override fun setBreak(value: Boolean) { + // no-op + } + + override fun isOpen(): Boolean = open.get() && !detached + + private fun assertOpen() { + if (detached) throw IOException("USB device detached") + if (!open.get()) throw IOException("Port not open") + } + + private companion object { + private const val IN = 0x80 + private const val OUT = 0x00 + + private fun mockEndpoint(packetSize: Int, direction: Int): UsbEndpoint { + val endpoint = mock() + whenever(endpoint.maxPacketSize).thenReturn(packetSize) + whenever(endpoint.direction).thenReturn(direction) + return endpoint + } + } +} + +/** Minimal driver shell so [FakeUsbSerialPort.getDriver] / [getDevice] work. */ +class FakeUsbSerialDriver( + private val usbDevice: UsbDevice, +) : UsbSerialDriver { + override fun getDevice(): UsbDevice = usbDevice + + override fun getPorts(): List = emptyList() +} diff --git a/android/src/test/kotlin/app/tauri/serialplugin/manager/RecordingRxSink.kt b/android/src/test/kotlin/app/tauri/serialplugin/manager/RecordingRxSink.kt new file mode 100644 index 00000000..8112df97 --- /dev/null +++ b/android/src/test/kotlin/app/tauri/serialplugin/manager/RecordingRxSink.kt @@ -0,0 +1,59 @@ +package app.tauri.serialplugin.manager + +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.TimeUnit + +/** In-memory [SerialRxSink] for JVM unit tests. */ +class RecordingRxSink : SerialRxSink { + data class RxEvent(val path: String, val data: ByteArray) + data class ErrorEvent(val path: String, val reason: String) + + val rx = ConcurrentLinkedQueue() + val errors = ConcurrentLinkedQueue() + @Volatile var portListChanges: Int = 0 + + override fun feedRx(path: String, data: ByteArray) { + rx.add(RxEvent(path, data.copyOf())) + } + + override fun onUsbError(path: String, reason: String) { + errors.add(ErrorEvent(path, reason)) + } + + override fun onPortListChange() { + portListChanges++ + } + + fun awaitRx(path: String, timeoutMs: Long = 3000): ByteArray? { + val deadline = System.currentTimeMillis() + timeoutMs + while (System.currentTimeMillis() < deadline) { + rx.forEach { event -> + if (event.path == path) { + rx.remove(event) + return event.data + } + } + Thread.sleep(10) + } + return null + } + + fun drainRx(path: String): List { + val out = mutableListOf() + val it = rx.iterator() + while (it.hasNext()) { + val event = it.next() + if (event.path == path) { + out.add(event.data) + it.remove() + } + } + return out + } + + fun clear() { + rx.clear() + errors.clear() + portListChanges = 0 + } +} diff --git a/android/src/test/kotlin/app/tauri/serialplugin/manager/SerialByteAccumulatorTest.kt b/android/src/test/kotlin/app/tauri/serialplugin/manager/SerialByteAccumulatorTest.kt deleted file mode 100644 index 566adef8..00000000 --- a/android/src/test/kotlin/app/tauri/serialplugin/manager/SerialByteAccumulatorTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package app.tauri.serialplugin.manager - -import org.junit.Assert.assertArrayEquals -import org.junit.Assert.assertEquals -import org.junit.Test - -class SerialByteAccumulatorTest { - @Test - fun append_empty_is_noop() { - val acc = SerialByteAccumulator() - acc.append(ByteArray(0)) - assertEquals(0, acc.pendingByteCount()) - assertArrayEquals(ByteArray(0), acc.drain()) - } - - @Test - fun append_then_drain_preserves_order() { - val acc = SerialByteAccumulator() - acc.append(byteArrayOf(1, 2)) - acc.append(byteArrayOf(3)) - assertEquals(3, acc.pendingByteCount()) - assertArrayEquals(byteArrayOf(1, 2, 3), acc.drain()) - assertEquals(0, acc.pendingByteCount()) - assertArrayEquals(ByteArray(0), acc.drain()) - } - - @Test - fun drain_when_empty_returns_empty() { - val acc = SerialByteAccumulator() - assertArrayEquals(ByteArray(0), acc.drain()) - } - - @Test - fun concurrent_appends_merge_consistently() { - val acc = SerialByteAccumulator() - val threads = List(8) { i -> - Thread { - repeat(100) { - acc.append(byteArrayOf(i.toByte())) - } - } - } - threads.forEach { it.start() } - threads.forEach { it.join() } - val out = acc.drain() - assertEquals(800, out.size) - // All bytes are in 0..7 — count occurrences - val counts = IntArray(8) - for (b in out) { - counts[b.toInt() and 0xff]++ - } - for (i in 0 until 8) { - assertEquals(100, counts[i]) - } - } -} diff --git a/android/src/test/kotlin/app/tauri/serialplugin/manager/SerialDataEmitFieldsTest.kt b/android/src/test/kotlin/app/tauri/serialplugin/manager/SerialDataEmitFieldsTest.kt deleted file mode 100644 index b4b46659..00000000 --- a/android/src/test/kotlin/app/tauri/serialplugin/manager/SerialDataEmitFieldsTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -package app.tauri.serialplugin.manager - -import app.tauri.plugin.JSObject -import org.junit.Assert.assertEquals -import org.junit.Test - -class SerialDataEmitFieldsTest { - @Test - fun payload_from_chunk_preserves_utf8_string() { - val chunk = "café 🚀".toByteArray(Charsets.UTF_8) - val p = serialDataPayloadFromChunk("/dev/usb", chunk) - assertEquals("/dev/usb", p.path) - assertEquals("café 🚀", p.dataAsString) - assertEquals(chunk.size, p.size) - } - - @Test - fun payload_from_chunk_preserves_nul_and_binary_bytes() { - val chunk = byteArrayOf(0x48, 0x00, 0x01.toByte()) - val p = serialDataPayloadFromChunk("/dev/usb", chunk) - assertEquals(3, p.size) - assertEquals(3, p.dataAsString.length) - assertEquals('\u0000', p.dataAsString[1]) - assertEquals(1.toChar(), p.dataAsString[2]) - } - - @Test - fun applyToJSObject_sets_path_data_size() { - val p = SerialDataEmitFields( - path = "/dev/tty", - dataAsString = "ab", - size = 2, - ) - val o = JSObject() - p.applyToJSObject(o) - assertEquals("/dev/tty", o.getString("path")) - assertEquals("ab", o.getString("data")) - assertEquals(2, o.getInteger("size")) - } - - @Test - fun flush_accumulator_invokes_emit_once_with_merged_bytes() { - val acc = SerialByteAccumulator() - acc.append(byteArrayOf(65, 66)) - acc.append(byteArrayOf(67)) - val captured = mutableListOf() - flushAccumulatorToEmit("COM1", acc) { captured.add(it) } - assertEquals(1, captured.size) - assertEquals("COM1", captured[0].path) - assertEquals("ABC", captured[0].dataAsString) - assertEquals(3, captured[0].size) - } - - @Test - fun flush_accumulator_skips_emit_when_empty() { - val acc = SerialByteAccumulator() - assertEquals(0, acc.pendingByteCount()) - assertEquals(0, acc.drain().size) - val captured = mutableListOf() - flushAccumulatorToEmit("COM1", acc) { captured.add(it) } - assertEquals(0, captured.size) - } -} diff --git a/android/src/test/kotlin/app/tauri/serialplugin/manager/UsbBridgeTest.kt b/android/src/test/kotlin/app/tauri/serialplugin/manager/UsbBridgeTest.kt new file mode 100644 index 00000000..7ae30381 --- /dev/null +++ b/android/src/test/kotlin/app/tauri/serialplugin/manager/UsbBridgeTest.kt @@ -0,0 +1,150 @@ +package app.tauri.serialplugin.manager + +import app.tauri.serialplugin.manager.UsbPortSession +import app.tauri.serialplugin.models.DataBits +import app.tauri.serialplugin.models.FlowControl +import app.tauri.serialplugin.models.Parity +import app.tauri.serialplugin.models.SerialPortConfig +import app.tauri.serialplugin.models.StopBits +import org.junit.After +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import java.io.IOException + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [28]) +class UsbBridgeTest { + + private val path = "/dev/bus/usb/001/099" + private lateinit var sink: RecordingRxSink + private lateinit var fake: FakeUsbSerialPort + private lateinit var bridge: UsbBridge + + @Before + fun setUp() { + sink = RecordingRxSink() + fake = FakeUsbSerialPort(path) + fake.openForTest() + bridge = UsbBridge.forTesting(sink) + bridge.adoptFakePort( + fake, + SerialPortConfig( + path = path, + baudRate = 115200, + dataBits = DataBits.EIGHT, + flowControl = FlowControl.NONE, + parity = Parity.NONE, + stopBits = StopBits.ONE, + timeout = 1000, + ), + ) + } + + @After + fun tearDown() { + if (::bridge.isInitialized) { + bridge.close(path) + } + } + + @Test + fun open_assertsDtrAndRtsWhenEnabled() { + assertTrue(fake.dtr) + assertTrue(fake.rts) + } + + @Test + fun close_deassertsDtrAndRts() { + bridge.close(path) + assertFalse(fake.dtr) + assertFalse(fake.rts) + } + + @Test + fun open_startsSiomAutomatically() { + assertTrue(bridge.isSiomRunning(path)) + } + + @Test + fun write_recordsBytesOnFake() { + val payload = "AT\r".toByteArray() + val n = bridge.write(path, payload) + assertEquals(payload.size, n) + assertArrayEquals(payload, fake.writtenBytes()) + } + + @Test + fun pollRead_rejectedWhileSiomActive() { + val ex = assertThrows(IOException::class.java) { + bridge.read(path, timeout = 500, size = 64) + } + assertEquals(UsbPortSession.LISTEN_READ_MUTEX_MESSAGE, ex.message) + } + + @Test + fun pollRead_afterStopSiom_returnsEnqueuedBytes() { + bridge.stopSiom(path) + Thread.sleep(200) + fake.enqueueRx("OK\r\n".toByteArray()) + val data = bridge.read(path, timeout = 500, size = 64) + assertArrayEquals("OK\r\n".toByteArray(), data) + } + + @Test + fun siom_deliversRxViaSink() { + fake.enqueueRx("RING\r\n".toByteArray()) + val rx = sink.awaitRx(path, timeoutMs = 3000) + assertNotNull(rx) + assertArrayEquals("RING\r\n".toByteArray(), rx) + } + + @Test + fun siom_atEchoRoundTrip() { + bridge.write(path, "AT\r".toByteArray()) + val rx = sink.awaitRx(path, timeoutMs = 3000) + assertNotNull(rx) + assertTrue(String(rx!!, Charsets.US_ASCII).contains("OK")) + } + + @Test + fun siom_consecutiveWritesDoNotDeadlock() { + repeat(5) { + bridge.write(path, "AT\r".toByteArray()) + val rx = sink.awaitRx(path, timeoutMs = 3000) + assertNotNull("missing RX for write #$it", rx) + } + assertEquals(5, fake.writtenBytes().size / 3) + } + + @Test + fun modemLines_readableDuringSiom() { + fake.cts = true + assertTrue(bridge.signal(path, "readCts", null)) + fake.cts = false + assertFalse(bridge.signal(path, "readCts", null)) + } + + @Test + fun hardReadError_reportsUsbError() { + fake.failNextRead = "device exploded" + Thread.sleep(500) + assertFalse(sink.errors.isEmpty()) + assertTrue(sink.errors.any { it.path == path }) + } + + @Test + fun close_stopsSiom() { + assertTrue(bridge.isSiomRunning(path)) + bridge.close(path) + assertFalse(bridge.isSiomRunning(path)) + } +} diff --git a/android/src/test/kotlin/app/tauri/serialplugin/manager/UsbPathTest.kt b/android/src/test/kotlin/app/tauri/serialplugin/manager/UsbPathTest.kt new file mode 100644 index 00000000..091cb365 --- /dev/null +++ b/android/src/test/kotlin/app/tauri/serialplugin/manager/UsbPathTest.kt @@ -0,0 +1,31 @@ +package app.tauri.serialplugin.manager + +import org.junit.Assert.assertEquals +import org.junit.Test + +class UsbPathTest { + @Test + fun sessionKey_singlePort_usesDeviceName() { + assertEquals("/dev/bus/usb/001/002", UsbPath.sessionKey("/dev/bus/usb/001/002", 0, 1)) + } + + @Test + fun sessionKey_multiPort_appendsIndex() { + assertEquals("/dev/bus/usb/001/002#1", UsbPath.sessionKey("/dev/bus/usb/001/002", 1, 2)) + } + + @Test + fun parse_plainPath_portZero() { + assertEquals("/dev/bus/usb/001/002" to 0, UsbPath.parse("/dev/bus/usb/001/002")) + } + + @Test + fun parse_withPortSuffix() { + assertEquals("/dev/bus/usb/001/002" to 1, UsbPath.parse("/dev/bus/usb/001/002#1")) + } + + @Test + fun deviceName_stripsPortSuffix() { + assertEquals("/dev/bus/usb/001/002", UsbPath.deviceName("/dev/bus/usb/001/002#0")) + } +} diff --git a/android/src/test/kotlin/app/tauri/serialplugin/manager/UsbPortSessionTest.kt b/android/src/test/kotlin/app/tauri/serialplugin/manager/UsbPortSessionTest.kt new file mode 100644 index 00000000..1a64293a --- /dev/null +++ b/android/src/test/kotlin/app/tauri/serialplugin/manager/UsbPortSessionTest.kt @@ -0,0 +1,69 @@ +package app.tauri.serialplugin.manager + +import app.tauri.serialplugin.models.DataBits +import app.tauri.serialplugin.models.FlowControl +import app.tauri.serialplugin.models.Parity +import app.tauri.serialplugin.models.SerialPortConfig +import app.tauri.serialplugin.models.StopBits +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [28]) +class UsbPortSessionTest { + + private val path = "/dev/bus/usb/001/099" + private lateinit var fake: FakeUsbSerialPort + private lateinit var session: UsbPortSession + + @Before + fun setUp() { + fake = FakeUsbSerialPort(path) + fake.openForTest() + session = UsbPortSession( + path = path, + port = fake, + config = SerialPortConfig( + path = path, + baudRate = 115200, + dataBits = DataBits.EIGHT, + flowControl = FlowControl.NONE, + parity = Parity.NONE, + stopBits = StopBits.ONE, + timeout = 1000, + ), + onRx = {}, + onError = {}, + onRecover = { session.restartReadLoop() }, + ) + } + + @After + fun tearDown() { + session.shutdown() + session.close() + } + + @Test + fun shutdown_preventsRestart() { + session.startReadLoop() + assertTrue(session.isReading) + session.shutdown() + session.restartReadLoop() + assertFalse(session.isReading) + } + + @Test + fun close_preventsRestart() { + session.startReadLoop() + session.close() + session.restartReadLoop() + assertFalse(session.isReading) + } +} diff --git a/android/src/test/kotlin/app/tauri/serialplugin/models/SerialModelsTest.kt b/android/src/test/kotlin/app/tauri/serialplugin/models/SerialModelsTest.kt index 5be20a79..e50210e3 100644 --- a/android/src/test/kotlin/app/tauri/serialplugin/models/SerialModelsTest.kt +++ b/android/src/test/kotlin/app/tauri/serialplugin/models/SerialModelsTest.kt @@ -51,6 +51,13 @@ class SerialModelsTest { assertEquals(StopBits.ONE, StopBits.fromValue(-99)) } + @Test + fun clearBuffer_fromNumericValue() { + assertEquals(ClearBuffer.INPUT, ClearBuffer.fromValue(0)) + assertEquals(ClearBuffer.OUTPUT, ClearBuffer.fromValue(1)) + assertEquals(ClearBuffer.ALL, ClearBuffer.fromValue(2)) + } + @Test fun clearBuffer_unknownDefaultsToInput() { assertEquals(ClearBuffer.INPUT, ClearBuffer.fromValue("not-a-buffer")) diff --git a/android/usbserial/LICENSE b/android/usbserial/LICENSE new file mode 100644 index 00000000..98e65da7 --- /dev/null +++ b/android/usbserial/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2011-2013 Google Inc. +Copyright (c) 2013 Mike Wakerly + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/android/usbserial/UPDATE.md b/android/usbserial/UPDATE.md new file mode 100644 index 00000000..223ad349 --- /dev/null +++ b/android/usbserial/UPDATE.md @@ -0,0 +1,53 @@ +# Updating vendored `usbserial` + +Single source of version truth: [`version.properties`](version.properties). + +## Quick commands + +```bash +# Is there a new upstream release on GitHub? +./scripts/vendor-usbserial.sh check + +# Pull the latest upstream tag and run ./gradlew test +./scripts/vendor-usbserial.sh latest + +# Pin a specific version +./scripts/vendor-usbserial.sh v3.11.0 + +# Re-import the current version from version.properties (no tag change) +./scripts/vendor-usbserial.sh vendor + +# Via pnpm +pnpm vendor:usbserial:check +pnpm vendor:usbserial:latest +``` + +## Maintainer checklist (after `latest` or `vendor `) + +1. **Gradle** — the script syncs `androidx.annotation` from upstream `build.gradle`. Verify `compileSdk` / `minSdk` if upstream raised requirements. +2. **`BuildConfig`** — if `grep -r BuildConfig android/usbserial/src` finds imports, the stub in [`stubs/com/hoho/android/usbserial/BuildConfig.java`](stubs/com/hoho/android/usbserial/BuildConfig.java) must remain (Tauri `sourceSets`). +3. **`consumer-rules.pro`** — when upstream changes, the script warns; update [`../consumer-rules.pro`](../consumer-rules.pro) if needed. +4. **Kotlin wrapper** — review [release notes](https://github.com/mik3y/usb-serial-for-android/releases): `SerialInputOutputManager`, `setReadQueue`, `UsbSerialPort` API → [`UsbPortDriver.kt`](../src/main/kotlin/app/tauri/serialplugin/manager/UsbPortDriver.kt). +5. **Builds** + ```bash + cd android && ./gradlew test + cd examples/serialport-test && pnpm tauri android build --debug + ``` +6. **Docs** — `VENDOR.md` and `version.properties` are updated by the script; manually update `CHANGELOG.md`, `android/README.md`, `README.md` (version mentions). +7. **Commit** — separate commit such as `chore(android): vendor usb-serial-for-android vX.Y.Z`. + +## What the script does not touch + +| Path | Why | +|------|-----| +| `usbserial/build.gradle` | Our `compileSdk`/`minSdk`, no maven-publish | +| `usbserial/stubs/` | Patch for Tauri sourceSets | +| `android/build.gradle` | Conditional `:usbserial` vs sourceSets wiring | + +## Rollback + +```bash +git checkout HEAD -- android/usbserial/src android/usbserial/LICENSE android/usbserial/consumer-rules.pro android/usbserial/VENDOR.md android/usbserial/version.properties +``` + +Or `git revert` the vendor commit. diff --git a/android/usbserial/VENDOR.md b/android/usbserial/VENDOR.md new file mode 100644 index 00000000..ece340b9 --- /dev/null +++ b/android/usbserial/VENDOR.md @@ -0,0 +1,16 @@ +# Vendored: usb-serial-for-android + +| Field | Value | +|-------|-------| +| Upstream | https://github.com/mik3y/usb-serial-for-android | +| Version | 3.10.0 | +| Tag | `v3.10.0` | +| Commit | `a8b9ecc7d32ce6df749c44a2b9e8cb208ac30609` | +| Imported | 2026-07-03 | +| Module path (upstream) | `usbSerialForAndroid/` | +| License | Apache-2.0 (see [LICENSE](LICENSE)) | + +Only `src/main/java`, `src/main/AndroidManifest.xml`, and `consumer-rules.pro` were copied. +Examples, tests, and JitPack publish config were omitted. + +Update workflow: [UPDATE.md](UPDATE.md) · script: `scripts/vendor-usbserial.sh` diff --git a/android/usbserial/build.gradle b/android/usbserial/build.gradle new file mode 100644 index 00000000..b4d912b1 --- /dev/null +++ b/android/usbserial/build.gradle @@ -0,0 +1,26 @@ +plugins { + id("com.android.library") +} + +android { + namespace = "com.hoho.android.usbserial" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + consumerProguardFiles("consumer-rules.pro") + } + + buildFeatures { + buildConfig = true + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation("androidx.annotation:annotation:1.9.1") +} diff --git a/android/usbserial/consumer-rules.pro b/android/usbserial/consumer-rules.pro new file mode 100644 index 00000000..5c8f2ff7 --- /dev/null +++ b/android/usbserial/consumer-rules.pro @@ -0,0 +1 @@ +-keep class com.hoho.android.usbserial.driver.* { *; } diff --git a/android/usbserial/src/main/AndroidManifest.xml b/android/usbserial/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9a40236b --- /dev/null +++ b/android/usbserial/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java new file mode 100644 index 00000000..9f9403cd --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/CdcAcmSerialDriver.java @@ -0,0 +1,359 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import com.hoho.android.usbserial.util.HexDump; +import com.hoho.android.usbserial.util.UsbUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * USB CDC/ACM serial driver implementation. + * + * @author mike wakerly (opensource@hoho.com) + * @see Universal + * Serial Bus Class Definitions for Communication Devices, v1.1 + */ +public class CdcAcmSerialDriver implements UsbSerialDriver { + + public static final int USB_SUBCLASS_ACM = 2; + + private final String TAG = CdcAcmSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + public CdcAcmSerialDriver(UsbDevice device) { + mDevice = device; + mPorts = new ArrayList<>(); + int ports = countPorts(device); + for (int port = 0; port < ports; port++) { + mPorts.add(new CdcAcmSerialPort(mDevice, port)); + } + if (mPorts.size() == 0) { + mPorts.add(new CdcAcmSerialPort(mDevice, -1)); + } + } + + @SuppressWarnings({"unused"}) + public static boolean probe(UsbDevice device) { + return countPorts(device) > 0; + } + + private static int countPorts(UsbDevice device) { + int controlInterfaceCount = 0; + int dataInterfaceCount = 0; + for (int i = 0; i < device.getInterfaceCount(); i++) { + if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_COMM && + device.getInterface(i).getInterfaceSubclass() == USB_SUBCLASS_ACM) + controlInterfaceCount++; + if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) + dataInterfaceCount++; + } + return Math.min(controlInterfaceCount, dataInterfaceCount); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public class CdcAcmSerialPort extends CommonUsbSerialPort { + + private UsbInterface mControlInterface; + private UsbInterface mDataInterface; + + private UsbEndpoint mControlEndpoint; + + private int mControlIndex; + + private boolean mRts = false; + private boolean mDtr = false; + + private static final int USB_RECIP_INTERFACE = 0x01; + private static final int USB_RT_ACM = UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; + + private static final int SET_LINE_CODING = 0x20; // USB CDC 1.1 section 6.2 + private static final int GET_LINE_CODING = 0x21; + private static final int SET_CONTROL_LINE_STATE = 0x22; + private static final int SEND_BREAK = 0x23; + + public CdcAcmSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return CdcAcmSerialDriver.this; + } + + @Override + protected void openInt() throws IOException { + Log.d(TAG, "interfaces:"); + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + Log.d(TAG, mDevice.getInterface(i).toString()); + } + if (mPortNumber == -1) { + Log.d(TAG,"device might be castrated ACM device, trying single interface logic"); + openSingleInterface(); + } else { + Log.d(TAG,"trying default interface logic"); + openInterface(); + } + } + + private void openSingleInterface() throws IOException { + // the following code is inspired by the cdc-acm driver in the linux kernel + + mControlIndex = 0; + mControlInterface = mDevice.getInterface(0); + mDataInterface = mDevice.getInterface(0); + if (!mConnection.claimInterface(mControlInterface, true)) { + throw new IOException("Could not claim shared control/data interface"); + } + + for (int i = 0; i < mControlInterface.getEndpointCount(); ++i) { + UsbEndpoint ep = mControlInterface.getEndpoint(i); + if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) { + mControlEndpoint = ep; + } else if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + mReadEndpoint = ep; + } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + mWriteEndpoint = ep; + } + } + if (mControlEndpoint == null) { + throw new IOException("No control endpoint"); + } + } + + private void openInterface() throws IOException { + + mControlInterface = null; + mDataInterface = null; + int j = getInterfaceIdFromDescriptors(); + Log.d(TAG, "interface count=" + mDevice.getInterfaceCount() + ", IAD=" + j); + if (j >= 0) { + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + UsbInterface usbInterface = mDevice.getInterface(i); + if (usbInterface.getId() == j || usbInterface.getId() == j+1) { + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM && + usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) { + mControlIndex = usbInterface.getId(); + mControlInterface = usbInterface; + } + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { + mDataInterface = usbInterface; + } + } + } + } + if (mControlInterface == null || mDataInterface == null) { + Log.d(TAG, "no IAD fallback"); + int controlInterfaceCount = 0; + int dataInterfaceCount = 0; + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + UsbInterface usbInterface = mDevice.getInterface(i); + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_COMM && + usbInterface.getInterfaceSubclass() == USB_SUBCLASS_ACM) { + if (controlInterfaceCount == mPortNumber) { + mControlIndex = usbInterface.getId(); + mControlInterface = usbInterface; + } + controlInterfaceCount++; + } + if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_CDC_DATA) { + if (dataInterfaceCount == mPortNumber) { + mDataInterface = usbInterface; + } + dataInterfaceCount++; + } + } + } + + if(mControlInterface == null) { + throw new IOException("No control interface"); + } + Log.d(TAG, "Control interface id " + mControlInterface.getId()); + + if (!mConnection.claimInterface(mControlInterface, true)) { + throw new IOException("Could not claim control interface"); + } + mControlEndpoint = mControlInterface.getEndpoint(0); + if (mControlEndpoint.getDirection() != UsbConstants.USB_DIR_IN || mControlEndpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_INT) { + throw new IOException("Invalid control endpoint"); + } + + if(mDataInterface == null) { + throw new IOException("No data interface"); + } + Log.d(TAG, "data interface id " + mDataInterface.getId()); + if (!mConnection.claimInterface(mDataInterface, true)) { + throw new IOException("Could not claim data interface"); + } + for (int i = 0; i < mDataInterface.getEndpointCount(); i++) { + UsbEndpoint ep = mDataInterface.getEndpoint(i); + if (ep.getDirection() == UsbConstants.USB_DIR_IN && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) + mReadEndpoint = ep; + if (ep.getDirection() == UsbConstants.USB_DIR_OUT && ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) + mWriteEndpoint = ep; + } + } + + private int getInterfaceIdFromDescriptors() { + ArrayList descriptors = UsbUtils.getDescriptors(mConnection); + Log.d(TAG, "USB descriptor:"); + for(byte[] descriptor : descriptors) + Log.d(TAG, HexDump.toHexString(descriptor)); + + if (descriptors.size() > 0 && + descriptors.get(0).length == 18 && + descriptors.get(0)[1] == 1 && // bDescriptorType + descriptors.get(0)[4] == (byte)(UsbConstants.USB_CLASS_MISC) && //bDeviceClass + descriptors.get(0)[5] == 2 && // bDeviceSubClass + descriptors.get(0)[6] == 1) { // bDeviceProtocol + // is IAD device, see https://www.usb.org/sites/default/files/iadclasscode_r10.pdf + int port = -1; + for (int d = 1; d < descriptors.size(); d++) { + if (descriptors.get(d).length == 8 && + descriptors.get(d)[1] == 0x0b && // bDescriptorType == IAD + descriptors.get(d)[4] == UsbConstants.USB_CLASS_COMM && // bFunctionClass == CDC + descriptors.get(d)[5] == USB_SUBCLASS_ACM) { // bFunctionSubClass == ACM + port++; + if (port == mPortNumber && + descriptors.get(d)[3] == 2) { // bInterfaceCount + return descriptors.get(d)[2]; // bFirstInterface + } + } + } + } + return -1; + } + + private int sendAcmControlMessage(int request, int value, byte[] buf) throws IOException { + int len = mConnection.controlTransfer( + USB_RT_ACM, request, value, mControlIndex, buf, buf != null ? buf.length : 0, 5000); + if(len < 0) { + throw new IOException("controlTransfer failed"); + } + return len; + } + + @Override + protected void closeInt() { + try { + mConnection.releaseInterface(mControlInterface); + mConnection.releaseInterface(mDataInterface); + } catch(Exception ignored) {} + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + if(dataBits < DATABITS_5 || dataBits > DATABITS_8) { + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + byte stopBitsByte; + switch (stopBits) { + case STOPBITS_1: stopBitsByte = 0; break; + case STOPBITS_1_5: stopBitsByte = 1; break; + case STOPBITS_2: stopBitsByte = 2; break; + default: throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + byte parityBitesByte; + switch (parity) { + case PARITY_NONE: parityBitesByte = 0; break; + case PARITY_ODD: parityBitesByte = 1; break; + case PARITY_EVEN: parityBitesByte = 2; break; + case PARITY_MARK: parityBitesByte = 3; break; + case PARITY_SPACE: parityBitesByte = 4; break; + default: throw new IllegalArgumentException("Invalid parity: " + parity); + } + byte[] msg = { + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff), + stopBitsByte, + parityBitesByte, + (byte) dataBits}; + sendAcmControlMessage(SET_LINE_CODING, 0, msg); + } + + @Override + public boolean getDTR() throws IOException { + return mDtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + mDtr = value; + setDtrRts(); + } + + @Override + public boolean getRTS() throws IOException { + return mRts; + } + + @Override + public void setRTS(boolean value) throws IOException { + mRts = value; + setDtrRts(); + } + + private void setDtrRts() throws IOException { + int value = (mRts ? 0x2 : 0) | (mDtr ? 0x1 : 0); + sendAcmControlMessage(SET_CONTROL_LINE_STATE, value, null); + } + + @Override + public EnumSet getControlLines() throws IOException { + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(mRts) set.add(ControlLine.RTS); + if(mDtr) set.add(ControlLine.DTR); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.of(ControlLine.RTS, ControlLine.DTR); + } + + @Override + public void setBreak(boolean value) throws IOException { + sendAcmControlMessage(SEND_BREAK, value ? 0xffff : 0, null); + } + + } + + @SuppressWarnings({"unused"}) + public static Map getSupportedDevices() { + return new LinkedHashMap<>(); + } + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java new file mode 100644 index 00000000..8d020012 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/Ch34xSerialDriver.java @@ -0,0 +1,387 @@ +/* Copyright 2014 Andreas Butti + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import com.hoho.android.usbserial.BuildConfig; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Ch34xSerialDriver implements UsbSerialDriver { + + private static final String TAG = Ch34xSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final UsbSerialPort mPort; + + private static final int LCR_ENABLE_RX = 0x80; + private static final int LCR_ENABLE_TX = 0x40; + private static final int LCR_MARK_SPACE = 0x20; + private static final int LCR_PAR_EVEN = 0x10; + private static final int LCR_ENABLE_PAR = 0x08; + private static final int LCR_STOP_BITS_2 = 0x04; + private static final int LCR_CS8 = 0x03; + private static final int LCR_CS7 = 0x02; + private static final int LCR_CS6 = 0x01; + private static final int LCR_CS5 = 0x00; + + private static final int GCL_CTS = 0x01; + private static final int GCL_DSR = 0x02; + private static final int GCL_RI = 0x04; + private static final int GCL_CD = 0x08; + private static final int SCL_DTR = 0x20; + private static final int SCL_RTS = 0x40; + + public Ch34xSerialDriver(UsbDevice device) { + mDevice = device; + mPort = new Ch340SerialPort(mDevice, 0); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return Collections.singletonList(mPort); + } + + public class Ch340SerialPort extends CommonUsbSerialPort { + + private static final int USB_TIMEOUT_MILLIS = 5000; + + private final int DEFAULT_BAUD_RATE = 9600; + + private boolean dtr = false; + private boolean rts = false; + + public Ch340SerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return Ch34xSerialDriver.this; + } + + @Override + protected void openInt() throws IOException { + for (int i = 0; i < mDevice.getInterfaceCount(); i++) { + UsbInterface usbIface = mDevice.getInterface(i); + if (!mConnection.claimInterface(usbIface, true)) { + throw new IOException("Could not claim data interface"); + } + } + + UsbInterface dataIface = mDevice.getInterface(mDevice.getInterfaceCount() - 1); + for (int i = 0; i < dataIface.getEndpointCount(); i++) { + UsbEndpoint ep = dataIface.getEndpoint(i); + if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { + if (ep.getDirection() == UsbConstants.USB_DIR_IN) { + mReadEndpoint = ep; + } else { + mWriteEndpoint = ep; + } + } + } + + initialize(); + setBaudRate(DEFAULT_BAUD_RATE); + } + + @Override + protected void closeInt() { + try { + for (int i = 0; i < mDevice.getInterfaceCount(); i++) + mConnection.releaseInterface(mDevice.getInterface(i)); + } catch(Exception ignored) {} + } + + private int controlOut(int request, int value, int index) { + final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; + return mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, + value, index, null, 0, USB_TIMEOUT_MILLIS); + } + + + private int controlIn(int request, int value, int index, byte[] buffer) { + final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; + return mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, request, + value, index, buffer, buffer.length, USB_TIMEOUT_MILLIS); + } + + + private void checkState(String msg, int request, int value, int[] expected) throws IOException { + byte[] buffer = new byte[expected.length]; + int ret = controlIn(request, value, 0, buffer); + + if (ret < 0) { + throw new IOException("Failed send cmd [" + msg + "]"); + } + + if (ret != expected.length) { + throw new IOException("Expected " + expected.length + " bytes, but get " + ret + " [" + msg + "]"); + } + + for (int i = 0; i < expected.length; i++) { + if (expected[i] == -1) { + continue; + } + + int current = buffer[i] & 0xff; + if (expected[i] != current) { + throw new IOException("Expected 0x" + Integer.toHexString(expected[i]) + " byte, but get 0x" + Integer.toHexString(current) + " [" + msg + "]"); + } + } + } + + private void setControlLines() throws IOException { + if (controlOut(0xa4, ~((dtr ? SCL_DTR : 0) | (rts ? SCL_RTS : 0)), 0) < 0) { + throw new IOException("Failed to set control lines"); + } + } + + private byte getStatus() throws IOException { + byte[] buffer = new byte[2]; + int ret = controlIn(0x95, 0x0706, 0, buffer); + if (ret < 0) + throw new IOException("Error getting control lines"); + return buffer[0]; + } + + private void initialize() throws IOException { + checkState("init #1", 0x5f, 0, new int[]{-1 /* 0x27, 0x30 */, 0x00}); + + if (controlOut(0xa1, 0, 0) < 0) { + throw new IOException("Init failed: #2"); + } + + setBaudRate(DEFAULT_BAUD_RATE); + + checkState("init #4", 0x95, 0x2518, new int[]{-1 /* 0x56, c3*/, 0x00}); + + if (controlOut(0x9a, 0x2518, LCR_ENABLE_RX | LCR_ENABLE_TX | LCR_CS8) < 0) { + throw new IOException("Init failed: #5"); + } + + checkState("init #6", 0x95, 0x0706, new int[]{-1/*0xf?*/, -1/*0xec,0xee*/}); + + if (controlOut(0xa1, 0x501f, 0xd90a) < 0) { + throw new IOException("Init failed: #7"); + } + + setBaudRate(DEFAULT_BAUD_RATE); + + setControlLines(); + + checkState("init #10", 0x95, 0x0706, new int[]{-1/* 0x9f, 0xff*/, -1/*0xec,0xee*/}); + } + + + private void setBaudRate(int baudRate) throws IOException { + long factor; + long divisor; + + if (baudRate == 921600) { + divisor = 7; + factor = 0xf300; + } else { + final long BAUDBASE_FACTOR = 1532620800; + final int BAUDBASE_DIVMAX = 3; + + if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) + baudRate &= ~(1<<29); // for testing purpose bypass dedicated baud rate handling + factor = BAUDBASE_FACTOR / baudRate; + divisor = BAUDBASE_DIVMAX; + while ((factor > 0xfff0) && divisor > 0) { + factor >>= 3; + divisor--; + } + if (factor > 0xfff0) { + throw new UnsupportedOperationException("Unsupported baud rate: " + baudRate); + } + factor = 0x10000 - factor; + } + + divisor |= 0x0080; // else ch341a waits until buffer full + int val1 = (int) ((factor & 0xff00) | divisor); + int val2 = (int) (factor & 0xff); + Log.d(TAG, String.format("baud rate=%d, 0x1312=0x%04x, 0x0f2c=0x%04x", baudRate, val1, val2)); + int ret = controlOut(0x9a, 0x1312, val1); + if (ret < 0) { + throw new IOException("Error setting baud rate: #1)"); + } + ret = controlOut(0x9a, 0x0f2c, val2); + if (ret < 0) { + throw new IOException("Error setting baud rate: #2"); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudRate(baudRate); + + int lcr = LCR_ENABLE_RX | LCR_ENABLE_TX; + + switch (dataBits) { + case DATABITS_5: + lcr |= LCR_CS5; + break; + case DATABITS_6: + lcr |= LCR_CS6; + break; + case DATABITS_7: + lcr |= LCR_CS7; + break; + case DATABITS_8: + lcr |= LCR_CS8; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case PARITY_NONE: + break; + case PARITY_ODD: + lcr |= LCR_ENABLE_PAR; + break; + case PARITY_EVEN: + lcr |= LCR_ENABLE_PAR | LCR_PAR_EVEN; + break; + case PARITY_MARK: + lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE; + break; + case PARITY_SPACE: + lcr |= LCR_ENABLE_PAR | LCR_MARK_SPACE | LCR_PAR_EVEN; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + lcr |= LCR_STOP_BITS_2; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + int ret = controlOut(0x9a, 0x2518, lcr); + if (ret < 0) { + throw new IOException("Error setting control byte"); + } + } + + @Override + public boolean getCD() throws IOException { + return (getStatus() & GCL_CD) == 0; + } + + @Override + public boolean getCTS() throws IOException { + return (getStatus() & GCL_CTS) == 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & GCL_DSR) == 0; + } + + @Override + public boolean getDTR() throws IOException { + return dtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + dtr = value; + setControlLines(); + } + + @Override + public boolean getRI() throws IOException { + return (getStatus() & GCL_RI) == 0; + } + + @Override + public boolean getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + rts = value; + setControlLines(); + } + + @Override + public EnumSet getControlLines() throws IOException { + int status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(rts) set.add(ControlLine.RTS); + if((status & GCL_CTS) == 0) set.add(ControlLine.CTS); + if(dtr) set.add(ControlLine.DTR); + if((status & GCL_DSR) == 0) set.add(ControlLine.DSR); + if((status & GCL_CD) == 0) set.add(ControlLine.CD); + if((status & GCL_RI) == 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public void setBreak(boolean value) throws IOException { + byte[] req = new byte[2]; + if(controlIn(0x95, 0x1805, 0, req) < 0) { + throw new IOException("Error getting BREAK condition"); + } + if(value) { + req[0] &= ~1; + req[1] &= ~0x40; + } else { + req[0] |= 1; + req[1] |= 0x40; + } + int val = (req[1] & 0xff) << 8 | (req[0] & 0xff); + if(controlOut(0x9a, 0x1805, val) < 0) { + throw new IOException("Error setting BREAK condition"); + } + } + } + + @SuppressWarnings({"unused"}) + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_QINHENG, new int[]{ + UsbId.QINHENG_CH340, + UsbId.QINHENG_CH341A, + }); + return supportedDevices; + } + +} \ No newline at end of file diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ChromeCcdSerialDriver.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ChromeCcdSerialDriver.java new file mode 100644 index 00000000..db19aca8 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ChromeCcdSerialDriver.java @@ -0,0 +1,91 @@ +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; + +public class ChromeCcdSerialDriver implements UsbSerialDriver{ + + private final String TAG = ChromeCcdSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public ChromeCcdSerialDriver(UsbDevice mDevice) { + this.mDevice = mDevice; + mPorts = new ArrayList(); + for (int i = 0; i < 3; i++) + mPorts.add(new ChromeCcdSerialPort(mDevice, i)); + } + + public class ChromeCcdSerialPort extends CommonUsbSerialPort { + private UsbInterface mDataInterface; + + public ChromeCcdSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + protected void openInt() throws IOException { + Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); + mDataInterface = mDevice.getInterface(mPortNumber); + if (!mConnection.claimInterface(mDataInterface, true)) { + throw new IOException("Could not claim shared control/data interface"); + } + Log.d(TAG, "endpoint count=" + mDataInterface.getEndpointCount()); + for (int i = 0; i < mDataInterface.getEndpointCount(); ++i) { + UsbEndpoint ep = mDataInterface.getEndpoint(i); + if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + mReadEndpoint = ep; + } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + mWriteEndpoint = ep; + } + } + } + + @Override + protected void closeInt() { + try { + mConnection.releaseInterface(mDataInterface); + } catch(Exception ignored) {} + } + + @Override + public UsbSerialDriver getDriver() { + return ChromeCcdSerialDriver.this; + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { + throw new UnsupportedOperationException(); + } + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_GOOGLE, new int[]{ + UsbId.GOOGLE_CR50, + }); + return supportedDevices; + } +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java new file mode 100644 index 00000000..f517793c --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/CommonUsbSerialPort.java @@ -0,0 +1,478 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbRequest; +import android.os.Build; +import android.util.Log; + +import com.hoho.android.usbserial.util.MonotonicClock; +import com.hoho.android.usbserial.util.UsbUtils; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.LinkedList; +import java.util.Objects; + +/** + * A base class shared by several driver implementations. + * + * @author mike wakerly (opensource@hoho.com) + */ +public abstract class CommonUsbSerialPort implements UsbSerialPort { + + public static boolean DEBUG = false; + + private static final String TAG = CommonUsbSerialPort.class.getSimpleName(); + private static final int MAX_READ_SIZE = 16 * 1024; // = old bulkTransfer limit prior to Android 9 + + protected final UsbDevice mDevice; + protected final int mPortNumber; + + // non-null when open() + protected UsbDeviceConnection mConnection; + protected UsbEndpoint mReadEndpoint; + protected UsbEndpoint mWriteEndpoint; + protected UsbRequest mReadRequest; + protected LinkedList mReadQueueRequests; + private int mReadQueueBufferCount; + private int mReadQueueBufferSize; + protected FlowControl mFlowControl = FlowControl.NONE; + protected UsbUtils.Supplier mUsbRequestSupplier = UsbRequest::new; // override for testing + + /** + * Internal write buffer. + * Guarded by {@link #mWriteBufferLock}. + * Default length = mReadEndpoint.getMaxPacketSize() + **/ + protected byte[] mWriteBuffer; + protected final Object mWriteBufferLock = new Object(); + + + public CommonUsbSerialPort(UsbDevice device, int portNumber) { + mDevice = device; + mPortNumber = portNumber; + } + + @Override + public String toString() { + return String.format("<%s device_name=%s device_id=%s port_number=%s>", + getClass().getSimpleName(), mDevice.getDeviceName(), + mDevice.getDeviceId(), mPortNumber); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public int getPortNumber() { + return mPortNumber; + } + + @Override + public UsbEndpoint getWriteEndpoint() { return mWriteEndpoint; } + + @Override + public UsbEndpoint getReadEndpoint() { return mReadEndpoint; } + + /** + * Returns the device serial number + * @return serial number + */ + @Override + public String getSerial() { + return mConnection.getSerial(); + } + + /** + * Sets the size of the internal buffer used to exchange data with the USB + * stack for write operations. Most users should not need to change this. + * + * @param bufferSize the size in bytes, <= 0 resets original size + */ + public final void setWriteBufferSize(int bufferSize) { + synchronized (mWriteBufferLock) { + if (bufferSize <= 0) { + if (mWriteEndpoint != null) { + bufferSize = mWriteEndpoint.getMaxPacketSize(); + } else { + mWriteBuffer = null; + return; + } + } + if (mWriteBuffer != null && bufferSize == mWriteBuffer.length) { + return; + } + mWriteBuffer = new byte[bufferSize]; + } + } + + @Override + public void setReadQueue(int bufferCount, int bufferSize) { + if (bufferCount < 0) { + throw new IllegalArgumentException("Invalid bufferCount"); + } + if (bufferSize < 0) { + throw new IllegalArgumentException("Invalid bufferSize"); + } + if(isOpen()) { + if (bufferCount < mReadQueueBufferCount) { + throw new IllegalStateException("Cannot reduce bufferCount when port is open"); + } + if (bufferSize == 0) { + bufferSize = mReadEndpoint.getMaxPacketSize(); + } + if (mReadQueueBufferSize == 0) { + mReadQueueBufferSize = mReadEndpoint.getMaxPacketSize(); + } + if (mReadQueueBufferCount != 0 && bufferSize != mReadQueueBufferSize) { + throw new IllegalStateException("Cannot change bufferSize when port is open"); + } + if (bufferCount > 0) { + if (mReadQueueRequests == null) { + mReadQueueRequests = new LinkedList<>(); + } + for (int i = mReadQueueRequests.size(); i < bufferCount; i++) { + ByteBuffer buffer = ByteBuffer.allocate(bufferSize); + UsbRequest request = mUsbRequestSupplier.get(); + request.initialize(mConnection, mReadEndpoint); + request.setClientData(buffer); + request.queue(buffer, bufferSize); + mReadQueueRequests.add(request); + } + } + } + mReadQueueBufferCount = bufferCount; + mReadQueueBufferSize = bufferSize; + } + + @Override + public int getReadQueueBufferCount() { return mReadQueueBufferCount; } + @Override + public int getReadQueueBufferSize() { return mReadQueueBufferSize; } + + private boolean useReadQueue() { return mReadQueueBufferCount != 0; } + + /** Retry queue — concurrent SIOM write can briefly fail queue on some hosts. */ + private boolean queueReadRequest(ByteBuffer buf, int length) { + for (int attempt = 0; attempt < 4; attempt++) { + if (mReadRequest.queue(buf, length)) { + return true; + } + try { + Thread.sleep(2); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } + } + return false; + } + + @Override + public void open(UsbDeviceConnection connection) throws IOException { + if (mConnection != null) { + throw new IOException("Already open"); + } + if(connection == null) { + throw new IllegalArgumentException("Connection is null"); + } + mConnection = connection; + boolean ok = false; + try { + openInt(); + if (mReadEndpoint == null || mWriteEndpoint == null) { + throw new IOException("Could not get read & write endpoints"); + } + mReadRequest = mUsbRequestSupplier.get(); + mReadRequest.initialize(mConnection, mReadEndpoint); + setReadQueue(mReadQueueBufferCount, mReadQueueBufferSize); // fill mReadQueueRequests + ok = true; + } finally { + if (!ok) { + try { + close(); + } catch (Exception ignored) {} + } + } + } + + protected abstract void openInt() throws IOException; + + @Override + public void close() throws IOException { + if (mConnection == null) { + throw new IOException("Already closed"); + } + UsbRequest readRequest = mReadRequest; + mReadRequest = null; + try { + readRequest.cancel(); + } catch(Exception ignored) {} + if(mReadQueueRequests != null) { + for(UsbRequest readQueueRequest : mReadQueueRequests) { + try { + readQueueRequest.cancel(); + } catch(Exception ignored) {} + } + mReadQueueRequests = null; + } + try { + closeInt(); + } catch(Exception ignored) {} + try { + mConnection.close(); + } catch(Exception ignored) {} + mConnection = null; + } + + protected abstract void closeInt(); + + /** + * use simple USB request supported by all devices to test if connection is still valid + */ + protected void testConnection(boolean full) throws IOException { + testConnection(full, "USB get_status request failed"); + } + + protected void testConnection(boolean full, String msg) throws IOException { + if(mReadRequest == null) { + throw new IOException("Connection closed"); + } + if(!full) { + return; + } + byte[] buf = new byte[2]; + int len = mConnection.controlTransfer(0x80 /*DEVICE*/, 0 /*GET_STATUS*/, 0, 0, buf, buf.length, 200); + if(len < 0) { + // Unreliable on CH340/CDC under concurrent SIOM+write; see usb-serial-for-android #503. + // Must not tear down an open port based on this heuristic. + if (DEBUG) Log.w(TAG, msg); + } + } + + @Override + public int read(final byte[] dest, final int timeout) throws IOException { + if(dest.length == 0) { + throw new IllegalArgumentException("Read buffer too small"); + } + return read(dest, dest.length, timeout); + } + + @Override + public int read(final byte[] dest, final int length, final int timeout) throws IOException {return read(dest, length, timeout, false);} + + protected int read(final byte[] dest, int length, final int timeout, boolean testConnection) throws IOException { + testConnection(false); + if(length <= 0) { + throw new IllegalArgumentException("Read length too small"); + } + length = Math.min(length, dest.length); + final int nread; + if (timeout != 0) { + if(useReadQueue()) { + throw new IllegalStateException("Cannot use timeout!=0 if readQueue is enabled"); + } + // bulkTransfer will cause data loss with short timeout + high baud rates + continuous transfer + // https://stackoverflow.com/questions/9108548/android-usb-host-bulktransfer-is-losing-data + // but mConnection.requestWait(timeout) available since Android 8.0 es even worse, + // as it crashes with short timeout, e.g. + // A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x276a in tid 29846 (pool-2-thread-1), pid 29618 (.usbserial.test) + // /system/lib64/libusbhost.so (usb_request_wait+192) + // /system/lib64/libandroid_runtime.so (android_hardware_UsbDeviceConnection_request_wait(_JNIEnv*, _jobject*, long)+84) + // data loss / crashes were observed with timeout up to 200 msec + long endTime = testConnection ? MonotonicClock.millis() + timeout : 0; + int readMax = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) ? length : Math.min(length, MAX_READ_SIZE); + nread = mConnection.bulkTransfer(mReadEndpoint, dest, readMax, timeout); + // Android error propagation is improvable: + // nread == -1 can be: timeout, connection lost, buffer to small, ??? + if (nread == -1) { + if (testConnection && MonotonicClock.millis() < endTime) { + try { + testConnection(true); + } catch (IOException e) { + // bulkTransfer idle timeout + failed GET_STATUS is common on CDC modems + // after TX bursts; not a reliable disconnect signal. + } + } + return 0; + } + + } else { + ByteBuffer buf = null; + if(useReadQueue()) { + if (length != mReadQueueBufferSize) { + throw new IllegalStateException("Cannot use different length if readQueue is enabled"); + } + } else { + buf = ByteBuffer.wrap(dest, 0, length); + if (!queueReadRequest(buf, length)) { + throw new IOException("Queueing USB request failed"); + } + } + final UsbRequest response = mConnection.requestWait(); + if (response == null) { + throw new IOException("Waiting for USB request failed"); + } + if(useReadQueue()) { + buf = (ByteBuffer) response.getClientData(); + System.arraycopy(buf.array(), 0, dest, 0, buf.position()); + if(mReadRequest != null) { // re-queue if connection not closed + if (!response.queue(buf, buf.capacity())) { + throw new IOException("Queueing USB request failed"); + } + } + } + nread = Objects.requireNonNull(buf).position(); + // Android error propagation is improvable: + // response != null & nread == 0 can be: connection lost, buffer to small, ??? + if (nread == 0) { + try { + testConnection(true); + } catch (IOException e) { + // Idle read with failed GET_STATUS is common on modems during concurrent + // SIOM write; not a reliable disconnect signal — continue listening. + return 0; + } + } + } + return Math.max(nread, 0); + } + + @Override + public void write(byte[] src, int timeout) throws IOException {write(src, src.length, timeout);} + + @Override + public void write(final byte[] src, int length, final int timeout) throws IOException { + int offset = 0; + long startTime = MonotonicClock.millis(); + length = Math.min(length, src.length); + + testConnection(false); + while (offset < length) { + int requestTimeout; + final int requestLength; + final int actualLength; + + synchronized (mWriteBufferLock) { + final byte[] writeBuffer; + + if (mWriteBuffer == null) { + mWriteBuffer = new byte[mWriteEndpoint.getMaxPacketSize()]; + } + requestLength = Math.min(length - offset, mWriteBuffer.length); + if (offset == 0) { + writeBuffer = src; + } else { + // bulkTransfer does not support offsets, make a copy. + System.arraycopy(src, offset, mWriteBuffer, 0, requestLength); + writeBuffer = mWriteBuffer; + } + if (timeout == 0 || offset == 0) { + requestTimeout = timeout; + } else { + requestTimeout = (int)(startTime + timeout - MonotonicClock.millis()); + if(requestTimeout == 0) + requestTimeout = -1; + } + if (requestTimeout < 0) { + actualLength = -2; + } else { + actualLength = mConnection.bulkTransfer(mWriteEndpoint, writeBuffer, requestLength, requestTimeout); + } + } + long elapsed = MonotonicClock.millis() - startTime; + if (DEBUG) { + Log.d(TAG, "Wrote " + actualLength + "/" + requestLength + " offset " + offset + "/" + length + " time " + elapsed + "/" + requestTimeout); + } + if (actualLength <= 0) { + String msg = "Error writing " + requestLength + " bytes at offset " + offset + " of total " + src.length + " after " + elapsed + "msec, rc=" + actualLength; + if (timeout != 0) { + // could be buffer full: writing too fast, flow control, or CH340 busy with SIOM read + if (elapsed < timeout) { + try { + testConnection(true); + } catch (IOException e) { + // bulkTransfer -1 + failed GET_STATUS is common on CDC/CH340 during + // concurrent SIOM; not a reliable disconnect signal — report timeout. + } + } + throw new SerialTimeoutException(msg, offset); + } else { + throw new IOException(msg); + } + } + offset += actualLength; + } + } + + @Override + public boolean isOpen() { + return mReadRequest != null; + } + + @Override + public abstract void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException; + + @Override + public boolean getCD() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getCTS() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getDSR() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getDTR() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public void setDTR(boolean value) throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getRI() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public boolean getRTS() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public void setRTS(boolean value) throws IOException { throw new UnsupportedOperationException(); } + + @Override + public EnumSet getControlLines() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public EnumSet getSupportedControlLines() throws IOException { return EnumSet.noneOf(ControlLine.class); } + + @Override + public void setFlowControl(FlowControl flowcontrol) throws IOException { + if (flowcontrol != FlowControl.NONE) + throw new UnsupportedOperationException(); + } + + @Override + public FlowControl getFlowControl() { return mFlowControl; } + + @Override + public EnumSet getSupportedFlowControl() { return EnumSet.of(FlowControl.NONE); } + + @Override + public boolean getXON() throws IOException { throw new UnsupportedOperationException(); } + + @Override + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { throw new UnsupportedOperationException(); } + + @Override + public void setBreak(boolean value) throws IOException { throw new UnsupportedOperationException(); } + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java new file mode 100644 index 00000000..492bd6c1 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/Cp21xxSerialDriver.java @@ -0,0 +1,412 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Cp21xxSerialDriver implements UsbSerialDriver { + + private static final String TAG = Cp21xxSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + public Cp21xxSerialDriver(UsbDevice device) { + mDevice = device; + mPorts = new ArrayList<>(); + for( int port = 0; port < device.getInterfaceCount(); port++) { + mPorts.add(new Cp21xxSerialPort(mDevice, port)); + } + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public class Cp21xxSerialPort extends CommonUsbSerialPort { + + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + + /* + * Configuration Request Types + */ + private static final int REQTYPE_HOST_TO_DEVICE = 0x41; + private static final int REQTYPE_DEVICE_TO_HOST = 0xc1; + + /* + * Configuration Request Codes + */ + private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00; + private static final int SILABSER_SET_LINE_CTL_REQUEST_CODE = 0x03; + private static final int SILABSER_SET_BREAK_REQUEST_CODE = 0x05; + private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07; + private static final int SILABSER_GET_MDMSTS_REQUEST_CODE = 0x08; + private static final int SILABSER_SET_XON_REQUEST_CODE = 0x09; + private static final int SILABSER_SET_XOFF_REQUEST_CODE = 0x0A; + private static final int SILABSER_GET_COMM_STATUS_REQUEST_CODE = 0x10; + private static final int SILABSER_FLUSH_REQUEST_CODE = 0x12; + private static final int SILABSER_SET_FLOW_REQUEST_CODE = 0x13; + private static final int SILABSER_SET_CHARS_REQUEST_CODE = 0x19; + private static final int SILABSER_SET_BAUDRATE_REQUEST_CODE = 0x1E; + + private static final int FLUSH_READ_CODE = 0x0a; + private static final int FLUSH_WRITE_CODE = 0x05; + + /* + * SILABSER_IFC_ENABLE_REQUEST_CODE + */ + private static final int UART_ENABLE = 0x0001; + private static final int UART_DISABLE = 0x0000; + + /* + * SILABSER_SET_MHS_REQUEST_CODE + */ + private static final int DTR_ENABLE = 0x101; + private static final int DTR_DISABLE = 0x100; + private static final int RTS_ENABLE = 0x202; + private static final int RTS_DISABLE = 0x200; + + /* + * SILABSER_GET_MDMSTS_REQUEST_CODE + */ + private static final int STATUS_DTR = 0x01; + private static final int STATUS_RTS = 0x02; + private static final int STATUS_CTS = 0x10; + private static final int STATUS_DSR = 0x20; + private static final int STATUS_RI = 0x40; + private static final int STATUS_CD = 0x80; + + + private boolean dtr = false; + private boolean rts = false; + + // second port of Cp2105 has limited baudRate, dataBits, stopBits, parity + // unsupported baudrate returns error at controlTransfer(), other parameters are silently ignored + private boolean mIsRestrictedPort; + + public Cp21xxSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return Cp21xxSerialDriver.this; + } + + private void setConfigSingle(int request, int value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, + mPortNumber, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Control transfer failed: " + request + " / " + value + " -> " + result); + } + } + + private byte getStatus() throws IOException { + byte[] buffer = new byte[1]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_MDMSTS_REQUEST_CODE, 0, + mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != buffer.length) { + throw new IOException("Control transfer failed: " + SILABSER_GET_MDMSTS_REQUEST_CODE + " / " + 0 + " -> " + result); + } + return buffer[0]; + } + + @Override + protected void openInt() throws IOException { + mIsRestrictedPort = mDevice.getInterfaceCount() == 2 && mPortNumber == 1; + if(mPortNumber >= mDevice.getInterfaceCount()) { + throw new IOException("Unknown port number"); + } + UsbInterface dataIface = mDevice.getInterface(mPortNumber); + if (!mConnection.claimInterface(dataIface, true)) { + throw new IOException("Could not claim interface " + mPortNumber); + } + for (int i = 0; i < dataIface.getEndpointCount(); i++) { + UsbEndpoint ep = dataIface.getEndpoint(i); + if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { + if (ep.getDirection() == UsbConstants.USB_DIR_IN) { + mReadEndpoint = ep; + } else { + mWriteEndpoint = ep; + } + } + } + + setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_ENABLE); + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, (dtr ? DTR_ENABLE : DTR_DISABLE) | (rts ? RTS_ENABLE : RTS_DISABLE)); + setFlowControl(mFlowControl); + } + + @Override + protected void closeInt() { + try { + setConfigSingle(SILABSER_IFC_ENABLE_REQUEST_CODE, UART_DISABLE); + } catch (Exception ignored) {} + try { + mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); + } catch(Exception ignored) {} + } + + private void setBaudRate(int baudRate) throws IOException { + byte[] data = new byte[] { + (byte) ( baudRate & 0xff), + (byte) ((baudRate >> 8 ) & 0xff), + (byte) ((baudRate >> 16) & 0xff), + (byte) ((baudRate >> 24) & 0xff) + }; + int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_BAUDRATE_REQUEST_CODE, + 0, mPortNumber, data, 4, USB_WRITE_TIMEOUT_MILLIS); + if (ret < 0) { + throw new IOException("Error setting baud rate"); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudRate(baudRate); + + int configDataBits = 0; + switch (dataBits) { + case DATABITS_5: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0500; + break; + case DATABITS_6: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0600; + break; + case DATABITS_7: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + configDataBits |= 0x0700; + break; + case DATABITS_8: + configDataBits |= 0x0800; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case PARITY_NONE: + break; + case PARITY_ODD: + configDataBits |= 0x0010; + break; + case PARITY_EVEN: + configDataBits |= 0x0020; + break; + case PARITY_MARK: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported parity: mark"); + configDataBits |= 0x0030; + break; + case PARITY_SPACE: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported parity: space"); + configDataBits |= 0x0040; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + if(mIsRestrictedPort) + throw new UnsupportedOperationException("Unsupported stop bits: 2"); + configDataBits |= 2; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + setConfigSingle(SILABSER_SET_LINE_CTL_REQUEST_CODE, configDataBits); + } + + @Override + public boolean getCD() throws IOException { + return (getStatus() & STATUS_CD) != 0; + } + + @Override + public boolean getCTS() throws IOException { + return (getStatus() & STATUS_CTS) != 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & STATUS_DSR) != 0; + } + + @Override + public boolean getDTR() throws IOException { + return dtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + dtr = value; + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, dtr ? DTR_ENABLE : DTR_DISABLE); + } + + @Override + public boolean getRI() throws IOException { + return (getStatus() & STATUS_RI) != 0; + } + + @Override + public boolean getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + rts = value; + setConfigSingle(SILABSER_SET_MHS_REQUEST_CODE, rts ? RTS_ENABLE : RTS_DISABLE); + } + + @Override + public EnumSet getControlLines() throws IOException { + byte status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + //if(rts) set.add(ControlLine.RTS); // configured value + if((status & STATUS_RTS) != 0) set.add(ControlLine.RTS); // actual value + if((status & STATUS_CTS) != 0) set.add(ControlLine.CTS); + //if(dtr) set.add(ControlLine.DTR); // configured value + if((status & STATUS_DTR) != 0) set.add(ControlLine.DTR); // actual value + if((status & STATUS_DSR) != 0) set.add(ControlLine.DSR); + if((status & STATUS_CD) != 0) set.add(ControlLine.CD); + if((status & STATUS_RI) != 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public boolean getXON() throws IOException { + byte[] buffer = new byte[0x13]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, SILABSER_GET_COMM_STATUS_REQUEST_CODE, 0, + mPortNumber, buffer, buffer.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != buffer.length) { + throw new IOException("Control transfer failed: " + SILABSER_GET_COMM_STATUS_REQUEST_CODE + " -> " + result); + } + return (buffer[4] & 8) == 0; + } + + /** + * emulate external XON/OFF + * @throws IOException + */ + public void setXON(boolean value) throws IOException { + setConfigSingle(value ? SILABSER_SET_XON_REQUEST_CODE : SILABSER_SET_XOFF_REQUEST_CODE, 0); + } + + @Override + public void setFlowControl(FlowControl flowControl) throws IOException { + byte[] data = new byte[16]; + if(flowControl == FlowControl.RTS_CTS) { + data[4] |= 0b1000_0000; // RTS + data[0] |= 0b0000_1000; // CTS + } else { + if(rts) + data[4] |= 0b0100_0000; + } + if(flowControl == FlowControl.DTR_DSR) { + data[0] |= 0b0000_0010; // DTR + data[0] |= 0b0001_0000; // DSR + } else { + if(dtr) + data[0] |= 0b0000_0001; + } + if(flowControl == FlowControl.XON_XOFF) { + byte[] chars = new byte[]{0, 0, 0, 0, CHAR_XON, CHAR_XOFF}; + int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_CHARS_REQUEST_CODE, + 0, mPortNumber, chars, chars.length, USB_WRITE_TIMEOUT_MILLIS); + if (ret != chars.length) { + throw new IOException("Error setting XON/XOFF chars"); + } + data[4] |= 0b0000_0011; + data[7] |= 0b1000_0000; + data[8] = (byte)128; + data[12] = (byte)128; + } + if(flowControl == FlowControl.XON_XOFF_INLINE) { + throw new UnsupportedOperationException(); + } + int ret = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SILABSER_SET_FLOW_REQUEST_CODE, + 0, mPortNumber, data, data.length, USB_WRITE_TIMEOUT_MILLIS); + if (ret != data.length) { + throw new IOException("Error setting flow control"); + } + if(flowControl == FlowControl.XON_XOFF) { + setXON(true); + } + mFlowControl = flowControl; + } + + @Override + public EnumSet getSupportedFlowControl() { + return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF); + } + + @Override + // note: only working on some devices, on other devices ignored w/o error + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + int value = (purgeReadBuffers ? FLUSH_READ_CODE : 0) + | (purgeWriteBuffers ? FLUSH_WRITE_CODE : 0); + + if (value != 0) { + setConfigSingle(SILABSER_FLUSH_REQUEST_CODE, value); + } + } + + @Override + public void setBreak(boolean value) throws IOException { + setConfigSingle(SILABSER_SET_BREAK_REQUEST_CODE, value ? 1 : 0); + } + } + + @SuppressWarnings({"unused"}) + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_SILABS, + new int[] { + UsbId.SILABS_CP2102, // same ID for CP2101, CP2103, CP2104, CP2109 + UsbId.SILABS_CP2105, + UsbId.SILABS_CP2108, + }); + return supportedDevices; + } + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java new file mode 100644 index 00000000..1b4d961d --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/FtdiSerialDriver.java @@ -0,0 +1,482 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * Copyright 2020 kai morich + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.util.Log; + +import com.hoho.android.usbserial.util.MonotonicClock; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/* + * driver is implemented from various information scattered over FTDI documentation + * + * baud rate calculation https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-05_BaudRates.pdf + * control bits https://www.ftdichip.com/Firmware/Precompiled/UM_VinculumFirmware_V205.pdf + * device type https://www.ftdichip.com/Support/Documents/AppNotes/AN_233_Java_D2XX_for_Android_API_User_Manual.pdf -> bvdDevice + * + */ + +public class FtdiSerialDriver implements UsbSerialDriver { + + private static final String TAG = FtdiSerialPort.class.getSimpleName(); + + private final UsbDevice mDevice; + private final List mPorts; + + public FtdiSerialDriver(UsbDevice device) { + mDevice = device; + mPorts = new ArrayList<>(); + for( int port = 0; port < device.getInterfaceCount(); port++) { + mPorts.add(new FtdiSerialPort(mDevice, port)); + } + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return mPorts; + } + + public class FtdiSerialPort extends CommonUsbSerialPort { + + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + private static final int READ_HEADER_LENGTH = 2; // contains MODEM_STATUS + + private static final int REQTYPE_HOST_TO_DEVICE = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_OUT; + private static final int REQTYPE_DEVICE_TO_HOST = UsbConstants.USB_TYPE_VENDOR | UsbConstants.USB_DIR_IN; + + private static final int RESET_REQUEST = 0; + private static final int MODEM_CONTROL_REQUEST = 1; + private static final int SET_FLOW_CONTROL_REQUEST = 2; + private static final int SET_BAUD_RATE_REQUEST = 3; + private static final int SET_DATA_REQUEST = 4; + private static final int GET_MODEM_STATUS_REQUEST = 5; + private static final int SET_LATENCY_TIMER_REQUEST = 9; + private static final int GET_LATENCY_TIMER_REQUEST = 10; + + private static final int MODEM_CONTROL_DTR_ENABLE = 0x0101; + private static final int MODEM_CONTROL_DTR_DISABLE = 0x0100; + private static final int MODEM_CONTROL_RTS_ENABLE = 0x0202; + private static final int MODEM_CONTROL_RTS_DISABLE = 0x0200; + private static final int MODEM_STATUS_CTS = 0x10; + private static final int MODEM_STATUS_DSR = 0x20; + private static final int MODEM_STATUS_RI = 0x40; + private static final int MODEM_STATUS_CD = 0x80; + private static final int RESET_ALL = 0; + private static final int RESET_PURGE_RX = 1; + private static final int RESET_PURGE_TX = 2; + + private boolean baudRateWithPort = false; + private boolean dtr = false; + private boolean rts = false; + private int breakConfig = 0; + + public FtdiSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return FtdiSerialDriver.this; + } + + + @Override + protected void openInt() throws IOException { + if (!mConnection.claimInterface(mDevice.getInterface(mPortNumber), true)) { + throw new IOException("Could not claim interface " + mPortNumber); + } + if (mDevice.getInterface(mPortNumber).getEndpointCount() < 2) { + throw new IOException("Not enough endpoints"); + } + mReadEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(0); + mWriteEndpoint = mDevice.getInterface(mPortNumber).getEndpoint(1); + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + RESET_ALL, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Reset failed: result=" + result); + } + result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + (dtr ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE) | + (rts ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE), + mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Init RTS,DTR failed: result=" + result); + } + setFlowControl(mFlowControl); + + // mDevice.getVersion() would require API 23 + byte[] rawDescriptors = mConnection.getRawDescriptors(); + if(rawDescriptors == null || rawDescriptors.length < 14) { + throw new IOException("Could not get device descriptors"); + } + int deviceType = rawDescriptors[13]; + baudRateWithPort = deviceType == 7 || deviceType == 8 || deviceType == 9 // ...H devices + || mDevice.getInterfaceCount() > 1; // FT2232C + } + + @Override + protected void closeInt() { + try { + mConnection.releaseInterface(mDevice.getInterface(mPortNumber)); + } catch(Exception ignored) {} + } + + @Override + public int read(final byte[] dest, final int timeout) throws IOException + { + if(dest.length <= READ_HEADER_LENGTH) { + throw new IllegalArgumentException("Read buffer too small"); + // could allocate larger buffer, including space for 2 header bytes, but this would + // result in buffers not being 64 byte aligned any more, causing data loss at continuous + // data transfer at high baud rates when buffers are fully filled. + } + return read(dest, dest.length, timeout); + } + + @Override + public int read(final byte[] dest, int length, final int timeout) throws IOException { + if(length <= READ_HEADER_LENGTH) { + throw new IllegalArgumentException("Read length too small"); + // could allocate larger buffer, including space for 2 header bytes, but this would + // result in buffers not being 64 byte aligned any more, causing data loss at continuous + // data transfer at high baud rates when buffers are fully filled. + } + length = Math.min(length, dest.length); + int nread; + if (timeout != 0) { + long endTime = MonotonicClock.millis() + timeout; + do { + nread = super.read(dest, length, Math.max(1, (int)(endTime - MonotonicClock.millis())), false); + } while (nread == READ_HEADER_LENGTH && MonotonicClock.millis() < endTime); + if(nread <= 0) { + try { + testConnection(MonotonicClock.millis() < endTime); + } catch (IOException ignored) { + } + } + } else { + do { + nread = super.read(dest, length, timeout); + } while (nread == READ_HEADER_LENGTH); + } + return readFilter(dest, nread); + } + + protected int readFilter(byte[] buffer, int totalBytesRead) throws IOException { + final int maxPacketSize = mReadEndpoint.getMaxPacketSize(); + int destPos = 0; + for(int srcPos = 0; srcPos < totalBytesRead; srcPos += maxPacketSize) { + int length = Math.min(srcPos + maxPacketSize, totalBytesRead) - (srcPos + READ_HEADER_LENGTH); + if (length < 0) + throw new IOException("Expected at least " + READ_HEADER_LENGTH + " bytes"); + System.arraycopy(buffer, srcPos + READ_HEADER_LENGTH, buffer, destPos, length); + destPos += length; + } + //Log.d(TAG, "read filter " + totalBytesRead + " -> " + destPos); + return destPos; + } + + private void setBaudrate(int baudRate) throws IOException { + int divisor, subdivisor, effectiveBaudRate; + if (baudRate > 3500000) { + throw new UnsupportedOperationException("Baud rate to high"); + } else if(baudRate >= 2500000) { + divisor = 0; + subdivisor = 0; + effectiveBaudRate = 3000000; + } else if(baudRate >= 1750000) { + divisor = 1; + subdivisor = 0; + effectiveBaudRate = 2000000; + } else { + divisor = (24000000 << 1) / baudRate; + divisor = (divisor + 1) >> 1; // round + subdivisor = divisor & 0x07; + divisor >>= 3; + if (divisor > 0x3fff) // exceeds bit 13 at 183 baud + throw new UnsupportedOperationException("Baud rate to low"); + effectiveBaudRate = (24000000 << 1) / ((divisor << 3) + subdivisor); + effectiveBaudRate = (effectiveBaudRate +1) >> 1; + } + double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); + if(baudRateError >= 0.031) // can happen only > 1.5Mbaud + throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); + int value = divisor; + int index = 0; + switch(subdivisor) { + case 0: break; // 16,15,14 = 000 - sub-integer divisor = 0 + case 4: value |= 0x4000; break; // 16,15,14 = 001 - sub-integer divisor = 0.5 + case 2: value |= 0x8000; break; // 16,15,14 = 010 - sub-integer divisor = 0.25 + case 1: value |= 0xc000; break; // 16,15,14 = 011 - sub-integer divisor = 0.125 + case 3: value |= 0x0000; index |= 1; break; // 16,15,14 = 100 - sub-integer divisor = 0.375 + case 5: value |= 0x4000; index |= 1; break; // 16,15,14 = 101 - sub-integer divisor = 0.625 + case 6: value |= 0x8000; index |= 1; break; // 16,15,14 = 110 - sub-integer divisor = 0.75 + case 7: value |= 0xc000; index |= 1; break; // 16,15,14 = 111 - sub-integer divisor = 0.875 + } + if(baudRateWithPort) { + index <<= 8; + index |= mPortNumber+1; + } + Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%04x, index=0x%04x, divisor=%d, subdivisor=%d", + baudRate, effectiveBaudRate, baudRateError*100, value, index, divisor, subdivisor)); + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_BAUD_RATE_REQUEST, + value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting baudrate failed: result=" + result); + } + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + if(baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + setBaudrate(baudRate); + + int config = 0; + switch (dataBits) { + case DATABITS_5: + case DATABITS_6: + throw new UnsupportedOperationException("Unsupported data bits: " + dataBits); + case DATABITS_7: + case DATABITS_8: + config |= dataBits; + break; + default: + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + + switch (parity) { + case PARITY_NONE: + break; + case PARITY_ODD: + config |= 0x100; + break; + case PARITY_EVEN: + config |= 0x200; + break; + case PARITY_MARK: + config |= 0x300; + break; + case PARITY_SPACE: + config |= 0x400; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + switch (stopBits) { + case STOPBITS_1: + break; + case STOPBITS_1_5: + throw new UnsupportedOperationException("Unsupported stop bits: 1.5"); + case STOPBITS_2: + config |= 0x1000; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, + config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting parameters failed: result=" + result); + } + breakConfig = config; + } + + private int getStatus() throws IOException { + byte[] data = new byte[2]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_MODEM_STATUS_REQUEST, + 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != data.length) { + throw new IOException("Get modem status failed: result=" + result); + } + return data[0]; + } + + @Override + public boolean getCD() throws IOException { + return (getStatus() & MODEM_STATUS_CD) != 0; + } + + @Override + public boolean getCTS() throws IOException { + return (getStatus() & MODEM_STATUS_CTS) != 0; + } + + @Override + public boolean getDSR() throws IOException { + return (getStatus() & MODEM_STATUS_DSR) != 0; + } + + @Override + public boolean getDTR() throws IOException { + return dtr; + } + + @Override + public void setDTR(boolean value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + value ? MODEM_CONTROL_DTR_ENABLE : MODEM_CONTROL_DTR_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Set DTR failed: result=" + result); + } + dtr = value; + } + + @Override + public boolean getRI() throws IOException { + return (getStatus() & MODEM_STATUS_RI) != 0; + } + + @Override + public boolean getRTS() throws IOException { + return rts; + } + + @Override + public void setRTS(boolean value) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, MODEM_CONTROL_REQUEST, + value ? MODEM_CONTROL_RTS_ENABLE : MODEM_CONTROL_RTS_DISABLE, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Set DTR failed: result=" + result); + } + rts = value; + } + + @Override + public EnumSet getControlLines() throws IOException { + int status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if(rts) set.add(ControlLine.RTS); + if((status & MODEM_STATUS_CTS) != 0) set.add(ControlLine.CTS); + if(dtr) set.add(ControlLine.DTR); + if((status & MODEM_STATUS_DSR) != 0) set.add(ControlLine.DSR); + if((status & MODEM_STATUS_CD) != 0) set.add(ControlLine.CD); + if((status & MODEM_STATUS_RI) != 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public void setFlowControl(FlowControl flowControl) throws IOException { + int value = 0; + int index = mPortNumber+1; + switch (flowControl) { + case NONE: + break; + case RTS_CTS: + index |= 0x100; + break; + case DTR_DSR: + index |= 0x200; + break; + case XON_XOFF_INLINE: + value = CHAR_XON + (CHAR_XOFF << 8); + index |= 0x400; + break; + default: + throw new UnsupportedOperationException(); + } + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_FLOW_CONTROL_REQUEST, + value, index, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) + throw new IOException("Set flow control failed: result=" + result); + mFlowControl = flowControl; + } + + @Override + public EnumSet getSupportedFlowControl() { + return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.DTR_DSR, FlowControl.XON_XOFF_INLINE); + } + + @Override + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + if (purgeWriteBuffers) { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + RESET_PURGE_RX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Purge write buffer failed: result=" + result); + } + } + + if (purgeReadBuffers) { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, RESET_REQUEST, + RESET_PURGE_TX, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Purge read buffer failed: result=" + result); + } + } + } + + @Override + public void setBreak(boolean value) throws IOException { + int config = breakConfig; + if(value) config |= 0x4000; + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_DATA_REQUEST, + config, mPortNumber+1,null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Setting BREAK failed: result=" + result); + } + } + + public void setLatencyTimer(int latencyTime) throws IOException { + int result = mConnection.controlTransfer(REQTYPE_HOST_TO_DEVICE, SET_LATENCY_TIMER_REQUEST, + latencyTime, mPortNumber+1, null, 0, USB_WRITE_TIMEOUT_MILLIS); + if (result != 0) { + throw new IOException("Set latency timer failed: result=" + result); + } + } + + public int getLatencyTimer() throws IOException { + byte[] data = new byte[1]; + int result = mConnection.controlTransfer(REQTYPE_DEVICE_TO_HOST, GET_LATENCY_TIMER_REQUEST, + 0, mPortNumber+1, data, data.length, USB_WRITE_TIMEOUT_MILLIS); + if (result != data.length) { + throw new IOException("Get latency timer failed: result=" + result); + } + return data[0]; + } + + } + + @SuppressWarnings({"unused"}) + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_FTDI, + new int[] { + UsbId.FTDI_FT232R, + UsbId.FTDI_FT232H, + UsbId.FTDI_FT2232H, + UsbId.FTDI_FT4232H, + UsbId.FTDI_FT231X, // same ID for FT230X, FT231X, FT234XD + }); + return supportedDevices; + } + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/GsmModemSerialDriver.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/GsmModemSerialDriver.java new file mode 100644 index 00000000..2c3212d9 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/GsmModemSerialDriver.java @@ -0,0 +1,102 @@ +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class GsmModemSerialDriver implements UsbSerialDriver{ + + private final String TAG = GsmModemSerialDriver.class.getSimpleName(); + + private final UsbDevice mDevice; + private final UsbSerialPort mPort; + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + @Override + public List getPorts() { + return Collections.singletonList(mPort); + } + + public GsmModemSerialDriver(UsbDevice mDevice) { + this.mDevice = mDevice; + mPort = new GsmModemSerialPort(mDevice, 0); + } + + public class GsmModemSerialPort extends CommonUsbSerialPort { + + private UsbInterface mDataInterface; + + public GsmModemSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + protected void openInt() throws IOException { + Log.d(TAG, "claiming interfaces, count=" + mDevice.getInterfaceCount()); + mDataInterface = mDevice.getInterface(0); + if (!mConnection.claimInterface(mDataInterface, true)) { + throw new IOException("Could not claim shared control/data interface"); + } + Log.d(TAG, "endpoint count=" + mDataInterface.getEndpointCount()); + for (int i = 0; i < mDataInterface.getEndpointCount(); ++i) { + UsbEndpoint ep = mDataInterface.getEndpoint(i); + if ((ep.getDirection() == UsbConstants.USB_DIR_IN) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + mReadEndpoint = ep; + } else if ((ep.getDirection() == UsbConstants.USB_DIR_OUT) && (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK)) { + mWriteEndpoint = ep; + } + } + initGsmModem(); + } + + @Override + protected void closeInt() { + try { + mConnection.releaseInterface(mDataInterface); + } catch(Exception ignored) {} + + } + + private int initGsmModem() throws IOException { + int len = mConnection.controlTransfer( + 0x21, 0x22, 0x01, 0, null, 0, 5000); + if(len < 0) { + throw new IOException("init failed"); + } + return len; + } + + @Override + public UsbSerialDriver getDriver() { + return GsmModemSerialDriver.this; + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, int parity) throws IOException { + throw new UnsupportedOperationException(); + } + + } + + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_UNISOC, new int[]{ + UsbId.FIBOCOM_L610, + UsbId.FIBOCOM_L612, + }); + return supportedDevices; + } +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java new file mode 100644 index 00000000..67ea2261 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ProbeTable.java @@ -0,0 +1,102 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.util.Pair; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Maps (vendor id, product id) pairs to the corresponding serial driver, + * or invoke 'probe' method to check actual USB devices for matching interfaces. + */ +public class ProbeTable { + + private final Map, Class> mVidPidProbeTable = + new LinkedHashMap<>(); + private final Map> mMethodProbeTable = new LinkedHashMap<>(); + + /** + * Adds or updates a (vendor, product) pair in the table. + * + * @param vendorId the USB vendor id + * @param productId the USB product id + * @param driverClass the driver class responsible for this pair + * @return {@code this}, for chaining + */ + public ProbeTable addProduct(int vendorId, int productId, + Class driverClass) { + mVidPidProbeTable.put(Pair.create(vendorId, productId), driverClass); + return this; + } + + /** + * Internal method to add all supported products from + * {@code getSupportedProducts} static method. + * + * @param driverClass to be added + */ + @SuppressWarnings("unchecked") + void addDriver(Class driverClass) { + Method method; + + try { + method = driverClass.getMethod("getSupportedDevices"); + } catch (SecurityException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + + final Map devices; + try { + devices = (Map) method.invoke(null); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + + for (Map.Entry entry : devices.entrySet()) { + final int vendorId = entry.getKey(); + for (int productId : entry.getValue()) { + addProduct(vendorId, productId, driverClass); + } + } + + try { + method = driverClass.getMethod("probe", UsbDevice.class); + mMethodProbeTable.put(method, driverClass); + } catch (SecurityException | NoSuchMethodException ignored) { + } + } + + /** + * Returns the driver for the given USB device, or {@code null} if no match. + * + * @param usbDevice the USB device to be probed + * @return the driver class matching this pair, or {@code null} + */ + public Class findDriver(final UsbDevice usbDevice) { + final Pair pair = Pair.create(usbDevice.getVendorId(), usbDevice.getProductId()); + Class driverClass = mVidPidProbeTable.get(pair); + if (driverClass != null) + return driverClass; + for (Map.Entry> entry : mMethodProbeTable.entrySet()) { + try { + Method method = entry.getKey(); + Object o = method.invoke(null, usbDevice); + if((boolean)o) + return entry.getValue(); + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + return null; + } + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java new file mode 100644 index 00000000..c837857a --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/ProlificSerialDriver.java @@ -0,0 +1,622 @@ +/* + * Ported to usb-serial-for-android by Felix Hädicke + * + * Based on the pyprolific driver written by Emmanuel Blot + * See https://github.com/eblot/pyftdi + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.util.Log; + +import com.hoho.android.usbserial.BuildConfig; +import com.hoho.android.usbserial.util.MonotonicClock; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ProlificSerialDriver implements UsbSerialDriver { + + private final String TAG = ProlificSerialDriver.class.getSimpleName(); + + private final static int[] standardBaudRates = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200, + 28800, 38400, 57600, 115200, 128000, 134400, 161280, 201600, 230400, 268800, + 403200, 460800, 614400, 806400, 921600, 1228800, 2457600, 3000000, 6000000 + }; + protected enum DeviceType { DEVICE_TYPE_01, DEVICE_TYPE_T, DEVICE_TYPE_HX, DEVICE_TYPE_HXN } + + private final UsbDevice mDevice; + private final UsbSerialPort mPort; + + public ProlificSerialDriver(UsbDevice device) { + mDevice = device; + mPort = new ProlificSerialPort(mDevice, 0); + } + + @Override + public List getPorts() { + return Collections.singletonList(mPort); + } + + @Override + public UsbDevice getDevice() { + return mDevice; + } + + class ProlificSerialPort extends CommonUsbSerialPort { + + private static final int USB_READ_TIMEOUT_MILLIS = 1000; + private static final int USB_WRITE_TIMEOUT_MILLIS = 5000; + + private static final int USB_RECIP_INTERFACE = 0x01; + + private static final int VENDOR_READ_REQUEST = 0x01; + private static final int VENDOR_WRITE_REQUEST = 0x01; + private static final int VENDOR_READ_HXN_REQUEST = 0x81; + private static final int VENDOR_WRITE_HXN_REQUEST = 0x80; + + private static final int VENDOR_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_VENDOR; + private static final int VENDOR_IN_REQTYPE = UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_VENDOR; + private static final int CTRL_OUT_REQTYPE = UsbConstants.USB_DIR_OUT | UsbConstants.USB_TYPE_CLASS | USB_RECIP_INTERFACE; + + private static final int WRITE_ENDPOINT = 0x02; + private static final int READ_ENDPOINT = 0x83; + private static final int INTERRUPT_ENDPOINT = 0x81; + + private static final int RESET_HXN_REQUEST = 0x07; + private static final int FLUSH_RX_REQUEST = 0x08; + private static final int FLUSH_TX_REQUEST = 0x09; + private static final int SET_LINE_REQUEST = 0x20; // same as CDC SET_LINE_CODING + private static final int SET_CONTROL_REQUEST = 0x22; // same as CDC SET_CONTROL_LINE_STATE + private static final int SEND_BREAK_REQUEST = 0x23; // same as CDC SEND_BREAK + private static final int GET_CONTROL_HXN_REQUEST = 0x80; + private static final int GET_CONTROL_REQUEST = 0x87; + private static final int STATUS_NOTIFICATION = 0xa1; // similar to CDC SERIAL_STATE but different length + + /* RESET_HXN_REQUEST */ + private static final int RESET_HXN_RX_PIPE = 1; + private static final int RESET_HXN_TX_PIPE = 2; + + /* SET_CONTROL_REQUEST */ + private static final int CONTROL_DTR = 0x01; + private static final int CONTROL_RTS = 0x02; + + /* GET_CONTROL_REQUEST */ + private static final int GET_CONTROL_FLAG_CD = 0x02; + private static final int GET_CONTROL_FLAG_DSR = 0x04; + private static final int GET_CONTROL_FLAG_RI = 0x01; + private static final int GET_CONTROL_FLAG_CTS = 0x08; + + /* GET_CONTROL_HXN_REQUEST */ + private static final int GET_CONTROL_HXN_FLAG_CD = 0x40; + private static final int GET_CONTROL_HXN_FLAG_DSR = 0x20; + private static final int GET_CONTROL_HXN_FLAG_RI = 0x80; + private static final int GET_CONTROL_HXN_FLAG_CTS = 0x08; + + /* interrupt endpoint read */ + private static final int STATUS_FLAG_CD = 0x01; + private static final int STATUS_FLAG_DSR = 0x02; + private static final int STATUS_FLAG_RI = 0x08; + private static final int STATUS_FLAG_CTS = 0x80; + + private static final int STATUS_BUFFER_SIZE = 10; + private static final int STATUS_BYTE_IDX = 8; + + protected DeviceType mDeviceType = DeviceType.DEVICE_TYPE_HX; + private UsbEndpoint mInterruptEndpoint; + private int mControlLinesValue = 0; + private int mBaudRate = -1, mDataBits = -1, mStopBits = -1, mParity = -1; + + private int mStatus = 0; + private volatile Thread mReadStatusThread = null; + private final Object mReadStatusThreadLock = new Object(); + private boolean mStopReadStatusThread = false; + private Exception mReadStatusException = null; + + + public ProlificSerialPort(UsbDevice device, int portNumber) { + super(device, portNumber); + } + + @Override + public UsbSerialDriver getDriver() { + return ProlificSerialDriver.this; + } + + private byte[] inControlTransfer(int requestType, int request, int value, int index, int length) throws IOException { + byte[] buffer = new byte[length]; + int result = mConnection.controlTransfer(requestType, request, value, index, buffer, length, USB_READ_TIMEOUT_MILLIS); + if (result != length) { + throw new IOException(String.format("ControlTransfer %s 0x%x failed: %d",mDeviceType.name(), value, result)); + } + return buffer; + } + + private void outControlTransfer(int requestType, int request, int value, int index, byte[] data) throws IOException { + int length = (data == null) ? 0 : data.length; + int result = mConnection.controlTransfer(requestType, request, value, index, data, length, USB_WRITE_TIMEOUT_MILLIS); + if (result != length) { + throw new IOException( String.format("ControlTransfer %s 0x%x failed: %d", mDeviceType.name(), value, result)); + } + } + + private byte[] vendorIn(int value, int index, int length) throws IOException { + int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_READ_HXN_REQUEST : VENDOR_READ_REQUEST; + return inControlTransfer(VENDOR_IN_REQTYPE, request, value, index, length); + } + + private void vendorOut(int value, int index, byte[] data) throws IOException { + int request = (mDeviceType == DeviceType.DEVICE_TYPE_HXN) ? VENDOR_WRITE_HXN_REQUEST : VENDOR_WRITE_REQUEST; + outControlTransfer(VENDOR_OUT_REQTYPE, request, value, index, data); + } + + private void resetDevice() throws IOException { + purgeHwBuffers(true, true); + } + + private void ctrlOut(int request, int value, int index, byte[] data) throws IOException { + outControlTransfer(CTRL_OUT_REQTYPE, request, value, index, data); + } + + private boolean testHxStatus() { + try { + inControlTransfer(VENDOR_IN_REQTYPE, VENDOR_READ_REQUEST, 0x8080, 0, 1); + return true; + } catch(IOException ignored) { + return false; + } + } + + private void doBlackMagic() throws IOException { + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) + return; + vendorIn(0x8484, 0, 1); + vendorOut(0x0404, 0, null); + vendorIn(0x8484, 0, 1); + vendorIn(0x8383, 0, 1); + vendorIn(0x8484, 0, 1); + vendorOut(0x0404, 1, null); + vendorIn(0x8484, 0, 1); + vendorIn(0x8383, 0, 1); + vendorOut(0, 1, null); + vendorOut(1, 0, null); + vendorOut(2, (mDeviceType == DeviceType.DEVICE_TYPE_01) ? 0x24 : 0x44, null); + } + + private void setControlLines(int newControlLinesValue) throws IOException { + ctrlOut(SET_CONTROL_REQUEST, newControlLinesValue, 0, null); + mControlLinesValue = newControlLinesValue; + } + + private void readStatusThreadFunction() { + try { + byte[] buffer = new byte[STATUS_BUFFER_SIZE]; + while (!mStopReadStatusThread) { + long endTime = MonotonicClock.millis() + 500; + int readBytesCount = mConnection.bulkTransfer(mInterruptEndpoint, buffer, STATUS_BUFFER_SIZE, 500); + if(readBytesCount == -1) { + try { + testConnection(MonotonicClock.millis() < endTime); + } catch (IOException ignored) { + } + } + if (readBytesCount > 0) { + if (readBytesCount != STATUS_BUFFER_SIZE) { + throw new IOException("Invalid status notification, expected " + STATUS_BUFFER_SIZE + " bytes, got " + readBytesCount); + } else if(buffer[0] != (byte)STATUS_NOTIFICATION ) { + throw new IOException("Invalid status notification, expected " + STATUS_NOTIFICATION + " request, got " + buffer[0]); + } else { + mStatus = buffer[STATUS_BYTE_IDX] & 0xff; + } + } + } + } catch (Exception e) { + if (isOpen()) + mReadStatusException = e; + } + //Log.d(TAG, "end control line status thread " + mStopReadStatusThread + " " + (mReadStatusException == null ? "-" : mReadStatusException.getMessage())); + } + + private int getStatus() throws IOException { + if ((mReadStatusThread == null) && (mReadStatusException == null)) { + synchronized (mReadStatusThreadLock) { + if (mReadStatusThread == null) { + mStatus = 0; + if(mDeviceType == DeviceType.DEVICE_TYPE_HXN) { + byte[] data = vendorIn(GET_CONTROL_HXN_REQUEST, 0, 1); + if ((data[0] & GET_CONTROL_HXN_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS; + if ((data[0] & GET_CONTROL_HXN_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR; + if ((data[0] & GET_CONTROL_HXN_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD; + if ((data[0] & GET_CONTROL_HXN_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI; + } else { + byte[] data = vendorIn(GET_CONTROL_REQUEST, 0, 1); + if ((data[0] & GET_CONTROL_FLAG_CTS) == 0) mStatus |= STATUS_FLAG_CTS; + if ((data[0] & GET_CONTROL_FLAG_DSR) == 0) mStatus |= STATUS_FLAG_DSR; + if ((data[0] & GET_CONTROL_FLAG_CD) == 0) mStatus |= STATUS_FLAG_CD; + if ((data[0] & GET_CONTROL_FLAG_RI) == 0) mStatus |= STATUS_FLAG_RI; + } + //Log.d(TAG, "start control line status thread " + mStatus); + mReadStatusThread = new Thread(this::readStatusThreadFunction); + mReadStatusThread.setDaemon(true); + mReadStatusThread.start(); + } + } + } + + /* throw and clear an exception which occurred in the status read thread */ + Exception readStatusException = mReadStatusException; + if (mReadStatusException != null) { + mReadStatusException = null; + throw new IOException(readStatusException); + } + + return mStatus; + } + + private boolean testStatusFlag(int flag) throws IOException { + return ((getStatus() & flag) == flag); + } + + @Override + public void openInt() throws IOException { + UsbInterface usbInterface = mDevice.getInterface(0); + + if (!mConnection.claimInterface(usbInterface, true)) { + throw new IOException("Error claiming Prolific interface 0"); + } + + for (int i = 0; i < usbInterface.getEndpointCount(); ++i) { + UsbEndpoint currentEndpoint = usbInterface.getEndpoint(i); + + switch (currentEndpoint.getAddress()) { + case READ_ENDPOINT: + mReadEndpoint = currentEndpoint; + break; + + case WRITE_ENDPOINT: + mWriteEndpoint = currentEndpoint; + break; + + case INTERRUPT_ENDPOINT: + mInterruptEndpoint = currentEndpoint; + break; + } + } + + byte[] rawDescriptors = mConnection.getRawDescriptors(); + if(rawDescriptors == null || rawDescriptors.length < 14) { + throw new IOException("Could not get device descriptors"); + } + int usbVersion = (rawDescriptors[3] << 8) + rawDescriptors[2]; + int deviceVersion = (rawDescriptors[13] << 8) + rawDescriptors[12]; + byte maxPacketSize0 = rawDescriptors[7]; + if (mDevice.getDeviceClass() == 0x02 || maxPacketSize0 != 64) { + mDeviceType = DeviceType.DEVICE_TYPE_01; + } else if(usbVersion == 0x200) { + if(deviceVersion == 0x300 && testHxStatus()) { + mDeviceType = DeviceType.DEVICE_TYPE_T; // TA + } else if(deviceVersion == 0x500 && testHxStatus()) { + mDeviceType = DeviceType.DEVICE_TYPE_T; // TB + } else { + mDeviceType = DeviceType.DEVICE_TYPE_HXN; + } + } else { + mDeviceType = DeviceType.DEVICE_TYPE_HX; + } + Log.d(TAG, String.format("usbVersion=%x, deviceVersion=%x, deviceClass=%d, packetSize=%d => deviceType=%s", + usbVersion, deviceVersion, mDevice.getDeviceClass(), maxPacketSize0, mDeviceType.name())); + resetDevice(); + doBlackMagic(); + setControlLines(mControlLinesValue); + setFlowControl(mFlowControl); + } + + @Override + public void closeInt() { + try { + synchronized (mReadStatusThreadLock) { + if (mReadStatusThread != null) { + try { + mStopReadStatusThread = true; + mReadStatusThread.join(); + } catch (Exception e) { + Log.w(TAG, "An error occured while waiting for status read thread", e); + } + mStopReadStatusThread = false; + mReadStatusThread = null; + mReadStatusException = null; + } + } + resetDevice(); + } catch(Exception ignored) {} + try { + mConnection.releaseInterface(mDevice.getInterface(0)); + } catch(Exception ignored) {} + } + + private int filterBaudRate(int baudRate) { + if(BuildConfig.DEBUG && (baudRate & (3<<29)) == (1<<29)) { + return baudRate & ~(1<<29); // for testing purposes accept without further checks + } + if (baudRate <= 0) { + throw new IllegalArgumentException("Invalid baud rate: " + baudRate); + } + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { + return baudRate; + } + for(int br : standardBaudRates) { + if (br == baudRate) { + return baudRate; + } + } + /* + * Formula taken from Linux + FreeBSD. + * + * For TA+TB devices + * baudrate = baseline / (mantissa * 2^exponent) + * where + * mantissa = buf[10:0] + * exponent = buf[15:13 16] + * + * For other devices + * baudrate = baseline / (mantissa * 4^exponent) + * where + * mantissa = buf[8:0] + * exponent = buf[11:9] + * + */ + int baseline, mantissa, exponent, buf, effectiveBaudRate; + baseline = 12000000 * 32; + mantissa = baseline / baudRate; + if (mantissa == 0) { // > unrealistic 384 MBaud + throw new UnsupportedOperationException("Baud rate to high"); + } + exponent = 0; + if (mDeviceType == DeviceType.DEVICE_TYPE_T) { + while (mantissa >= 2048) { + if (exponent < 15) { + mantissa >>= 1; /* divide by 2 */ + exponent++; + } else { // < 7 baud + throw new UnsupportedOperationException("Baud rate to low"); + } + } + buf = mantissa + ((exponent & ~1) << 12) + ((exponent & 1) << 16) + (1 << 31); + effectiveBaudRate = (baseline / mantissa) >> exponent; + } else { + while (mantissa >= 512) { + if (exponent < 7) { + mantissa >>= 2; /* divide by 4 */ + exponent++; + } else { // < 45.8 baud + throw new UnsupportedOperationException("Baud rate to low"); + } + } + buf = mantissa + (exponent << 9) + (1 << 31); + effectiveBaudRate = (baseline / mantissa) >> (exponent << 1); + } + double baudRateError = Math.abs(1.0 - (effectiveBaudRate / (double)baudRate)); + if(baudRateError >= 0.031) // > unrealistic 11.6 Mbaud + throw new UnsupportedOperationException(String.format("Baud rate deviation %.1f%% is higher than allowed 3%%", baudRateError*100)); + + Log.d(TAG, String.format("baud rate=%d, effective=%d, error=%.1f%%, value=0x%08x, mantissa=%d, exponent=%d", + baudRate, effectiveBaudRate, baudRateError*100, buf, mantissa, exponent)); + return buf; + } + + @Override + public void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException { + baudRate = filterBaudRate(baudRate); + if ((mBaudRate == baudRate) && (mDataBits == dataBits) + && (mStopBits == stopBits) && (mParity == parity)) { + // Make sure no action is performed if there is nothing to change + return; + } + + byte[] lineRequestData = new byte[7]; + lineRequestData[0] = (byte) (baudRate & 0xff); + lineRequestData[1] = (byte) ((baudRate >> 8) & 0xff); + lineRequestData[2] = (byte) ((baudRate >> 16) & 0xff); + lineRequestData[3] = (byte) ((baudRate >> 24) & 0xff); + + switch (stopBits) { + case STOPBITS_1: + lineRequestData[4] = 0; + break; + case STOPBITS_1_5: + lineRequestData[4] = 1; + break; + case STOPBITS_2: + lineRequestData[4] = 2; + break; + default: + throw new IllegalArgumentException("Invalid stop bits: " + stopBits); + } + + switch (parity) { + case PARITY_NONE: + lineRequestData[5] = 0; + break; + case PARITY_ODD: + lineRequestData[5] = 1; + break; + case PARITY_EVEN: + lineRequestData[5] = 2; + break; + case PARITY_MARK: + lineRequestData[5] = 3; + break; + case PARITY_SPACE: + lineRequestData[5] = 4; + break; + default: + throw new IllegalArgumentException("Invalid parity: " + parity); + } + + if(dataBits < DATABITS_5 || dataBits > DATABITS_8) { + throw new IllegalArgumentException("Invalid data bits: " + dataBits); + } + lineRequestData[6] = (byte) dataBits; + + ctrlOut(SET_LINE_REQUEST, 0, 0, lineRequestData); + + resetDevice(); + + mBaudRate = baudRate; + mDataBits = dataBits; + mStopBits = stopBits; + mParity = parity; + } + + @Override + public boolean getCD() throws IOException { + return testStatusFlag(STATUS_FLAG_CD); + } + + @Override + public boolean getCTS() throws IOException { + return testStatusFlag(STATUS_FLAG_CTS); + } + + @Override + public boolean getDSR() throws IOException { + return testStatusFlag(STATUS_FLAG_DSR); + } + + @Override + public boolean getDTR() throws IOException { + return (mControlLinesValue & CONTROL_DTR) != 0; + } + + @Override + public void setDTR(boolean value) throws IOException { + int newControlLinesValue; + if (value) { + newControlLinesValue = mControlLinesValue | CONTROL_DTR; + } else { + newControlLinesValue = mControlLinesValue & ~CONTROL_DTR; + } + setControlLines(newControlLinesValue); + } + + @Override + public boolean getRI() throws IOException { + return testStatusFlag(STATUS_FLAG_RI); + } + + @Override + public boolean getRTS() throws IOException { + return (mControlLinesValue & CONTROL_RTS) != 0; + } + + @Override + public void setRTS(boolean value) throws IOException { + int newControlLinesValue; + if (value) { + newControlLinesValue = mControlLinesValue | CONTROL_RTS; + } else { + newControlLinesValue = mControlLinesValue & ~CONTROL_RTS; + } + setControlLines(newControlLinesValue); + } + + @Override + public EnumSet getControlLines() throws IOException { + int status = getStatus(); + EnumSet set = EnumSet.noneOf(ControlLine.class); + if((mControlLinesValue & CONTROL_RTS) != 0) set.add(ControlLine.RTS); + if((status & STATUS_FLAG_CTS) != 0) set.add(ControlLine.CTS); + if((mControlLinesValue & CONTROL_DTR) != 0) set.add(ControlLine.DTR); + if((status & STATUS_FLAG_DSR) != 0) set.add(ControlLine.DSR); + if((status & STATUS_FLAG_CD) != 0) set.add(ControlLine.CD); + if((status & STATUS_FLAG_RI) != 0) set.add(ControlLine.RI); + return set; + } + + @Override + public EnumSet getSupportedControlLines() throws IOException { + return EnumSet.allOf(ControlLine.class); + } + + @Override + public void setFlowControl(FlowControl flowControl) throws IOException { + // vendorOut values from https://www.mail-archive.com/linux-usb@vger.kernel.org/msg110968.html + switch (flowControl) { + case NONE: + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) + vendorOut(0x0a, 0xff, null); + else + vendorOut(0, 0, null); + break; + case RTS_CTS: + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) + vendorOut(0x0a, 0xfa, null); + else + vendorOut(0, 0x61, null); + break; + case XON_XOFF_INLINE: + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) + vendorOut(0x0a, 0xee, null); + else + vendorOut(0, 0xc1, null); + break; + default: + throw new UnsupportedOperationException(); + } + mFlowControl = flowControl; + } + + @Override + public EnumSet getSupportedFlowControl() { + return EnumSet.of(FlowControl.NONE, FlowControl.RTS_CTS, FlowControl.XON_XOFF_INLINE); + } + + @Override + public void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException { + if (mDeviceType == DeviceType.DEVICE_TYPE_HXN) { + int index = 0; + if(purgeWriteBuffers) index |= RESET_HXN_RX_PIPE; + if(purgeReadBuffers) index |= RESET_HXN_TX_PIPE; + if(index != 0) + vendorOut(RESET_HXN_REQUEST, index, null); + } else { + if (purgeWriteBuffers) + vendorOut(FLUSH_RX_REQUEST, 0, null); + if (purgeReadBuffers) + vendorOut(FLUSH_TX_REQUEST, 0, null); + } + } + + @Override + public void setBreak(boolean value) throws IOException { + ctrlOut(SEND_BREAK_REQUEST, value ? 0xffff : 0, 0, null); + } + } + + @SuppressWarnings({"unused"}) + public static Map getSupportedDevices() { + final Map supportedDevices = new LinkedHashMap<>(); + supportedDevices.put(UsbId.VENDOR_PROLIFIC, + new int[] { + UsbId.PROLIFIC_PL2303, + UsbId.PROLIFIC_PL2303GC, + UsbId.PROLIFIC_PL2303GB, + UsbId.PROLIFIC_PL2303GT, + UsbId.PROLIFIC_PL2303GL, + UsbId.PROLIFIC_PL2303GE, + UsbId.PROLIFIC_PL2303GS, + }); + return supportedDevices; + } +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/SerialTimeoutException.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/SerialTimeoutException.java new file mode 100644 index 00000000..53304bcb --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/SerialTimeoutException.java @@ -0,0 +1,16 @@ +package com.hoho.android.usbserial.driver; + +import java.io.InterruptedIOException; + +/** + * Signals that a timeout has occurred on serial write. + * Similar to SocketTimeoutException. + * + * {@see InterruptedIOException#bytesTransferred} may contain bytes transferred + */ +public class SerialTimeoutException extends InterruptedIOException { + public SerialTimeoutException(String s, int bytesTransferred) { + super(s); + this.bytesTransferred = bytesTransferred; + } +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbId.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbId.java new file mode 100644 index 00000000..b2656285 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbId.java @@ -0,0 +1,56 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +/** + * Registry of USB vendor/product ID constants. + * + * Culled from various sources; see + * usb.ids for one listing. + * + * @author mike wakerly (opensource@hoho.com) + */ +public final class UsbId { + + public static final int VENDOR_FTDI = 0x0403; + public static final int FTDI_FT232R = 0x6001; + public static final int FTDI_FT2232H = 0x6010; + public static final int FTDI_FT4232H = 0x6011; + public static final int FTDI_FT232H = 0x6014; + public static final int FTDI_FT231X = 0x6015; // same ID for FT230X, FT231X, FT234XD + + public static final int VENDOR_SILABS = 0x10c4; + public static final int SILABS_CP2102 = 0xea60; // same ID for CP2101, CP2103, CP2104, CP2109 + public static final int SILABS_CP2105 = 0xea70; + public static final int SILABS_CP2108 = 0xea71; + + public static final int VENDOR_PROLIFIC = 0x067b; + public static final int PROLIFIC_PL2303 = 0x2303; // device type 01, T, HX + public static final int PROLIFIC_PL2303GC = 0x23a3; // device type HXN + public static final int PROLIFIC_PL2303GB = 0x23b3; // " + public static final int PROLIFIC_PL2303GT = 0x23c3; // " + public static final int PROLIFIC_PL2303GL = 0x23d3; // " + public static final int PROLIFIC_PL2303GE = 0x23e3; // " + public static final int PROLIFIC_PL2303GS = 0x23f3; // " + + public static final int VENDOR_GOOGLE = 0x18d1; + public static final int GOOGLE_CR50 = 0x5014; + + public static final int VENDOR_QINHENG = 0x1a86; + public static final int QINHENG_CH340 = 0x7523; + public static final int QINHENG_CH341A = 0x5523; + + public static final int VENDOR_UNISOC = 0x1782; + public static final int FIBOCOM_L610 = 0x4D10; + public static final int FIBOCOM_L612 = 0x4D12; + + + private UsbId() { + throw new IllegalAccessError("Non-instantiable class"); + } + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java new file mode 100644 index 00000000..78c16655 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialDriver.java @@ -0,0 +1,38 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; + +import java.util.List; + +public interface UsbSerialDriver { + + /* + * Additional interface properties. Invoked thru reflection. + * + UsbSerialDriver(UsbDevice device); // constructor with device + static Map getSupportedDevices(); + static boolean probe(UsbDevice device); // optional + */ + + + /** + * Returns the raw {@link UsbDevice} backing this port. + * + * @return the device + */ + UsbDevice getDevice(); + + /** + * Returns all available ports for this device. This list must have at least + * one entry. + * + * @return the ports + */ + List getPorts(); +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java new file mode 100644 index 00000000..567e45a5 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialPort.java @@ -0,0 +1,341 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbManager; + +import androidx.annotation.IntDef; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.EnumSet; + +/** + * Interface for a single serial port. + * + * @author mike wakerly (opensource@hoho.com) + */ +public interface UsbSerialPort extends Closeable { + + /** 5 data bits. */ + int DATABITS_5 = 5; + /** 6 data bits. */ + int DATABITS_6 = 6; + /** 7 data bits. */ + int DATABITS_7 = 7; + /** 8 data bits. */ + int DATABITS_8 = 8; + + /** Values for setParameters(..., parity) */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({PARITY_NONE, PARITY_ODD, PARITY_EVEN, PARITY_MARK, PARITY_SPACE}) + @interface Parity {} + /** No parity. */ + int PARITY_NONE = 0; + /** Odd parity. */ + int PARITY_ODD = 1; + /** Even parity. */ + int PARITY_EVEN = 2; + /** Mark parity. */ + int PARITY_MARK = 3; + /** Space parity. */ + int PARITY_SPACE = 4; + + /** 1 stop bit. */ + int STOPBITS_1 = 1; + /** 1.5 stop bits. */ + int STOPBITS_1_5 = 3; + /** 2 stop bits. */ + int STOPBITS_2 = 2; + + /** Values for get[Supported]ControlLines() */ + enum ControlLine { RTS, CTS, DTR, DSR, CD, RI } + + /** Values for (set|get|getSupported)FlowControl() */ + enum FlowControl { NONE, RTS_CTS, DTR_DSR, XON_XOFF, XON_XOFF_INLINE } + + /** XON character used with flow control XON/XOFF */ + char CHAR_XON = 17; + /** XOFF character used with flow control XON/XOFF */ + char CHAR_XOFF = 19; + + + /** + * Returns the driver used by this port. + */ + UsbSerialDriver getDriver(); + + /** + * Returns the currently-bound USB device. + */ + UsbDevice getDevice(); + + /** + * Port number within driver. + */ + int getPortNumber(); + + /** + * Returns the write endpoint. + * @return write endpoint + */ + UsbEndpoint getWriteEndpoint(); + + /** + * Returns the read endpoint. + * @return read endpoint + */ + UsbEndpoint getReadEndpoint(); + + /** + * The serial number of the underlying UsbDeviceConnection, or {@code null}. + * + * @return value from {@link UsbDeviceConnection#getSerial()} + * @throws SecurityException starting with target SDK 29 (Android 10) if permission for USB device is not granted + */ + String getSerial(); + + /** + * Applications doing permanent {@link #read} with timeout=0 can reduce data loss likelihood + * at high baud rate and continuous data transfer by using multiple buffers to copy next data + * from Linux kernel, while the current data is processed. + * When enabled, {@link #read} can not be called with timeout!=0 or different buffer size. + * + * @param bufferCount number of buffers to use for readQueue. + * Use 0 to disable. + * @param bufferSize size of each buffer. + * Use 0 for optimal size (= getReadEndpoint().getMaxPacketSize()). + * @throws IllegalStateException if port is open and buffer count should be lowered or + * buffer size should be changed. + */ + void setReadQueue(int bufferCount, int bufferSize); + int getReadQueueBufferCount(); + int getReadQueueBufferSize(); + + /** + * Opens and initializes the port. Upon success, caller must ensure that + * {@link #close()} is eventually called. + * + * @param connection an open device connection, acquired with + * {@link UsbManager#openDevice(android.hardware.usb.UsbDevice)} + * @throws IOException on error opening or initializing the port. + */ + void open(UsbDeviceConnection connection) throws IOException; + + /** + * Closes the port and {@link UsbDeviceConnection} + * + * @throws IOException on error closing the port. + */ + void close() throws IOException; + + /** + * Reads as many bytes as possible into the destination buffer. + * + * @param dest the destination byte buffer + * @param timeout the timeout for reading in milliseconds, 0 is infinite + * @return the actual number of bytes read + * @throws IOException if an error occurred during reading + */ + int read(final byte[] dest, final int timeout) throws IOException; + + /** + * Reads bytes with specified length into the destination buffer. + * + * @param dest the destination byte buffer + * @param length the maximum length of the data to read + * @param timeout the timeout for reading in milliseconds, 0 is infinite + * @return the actual number of bytes read + * @throws IOException if an error occurred during reading + */ + int read(final byte[] dest, int length, final int timeout) throws IOException; + + /** + * Writes as many bytes as possible from the source buffer. + * + * @param src the source byte buffer + * @param timeout the timeout for writing in milliseconds, 0 is infinite + * @throws SerialTimeoutException if timeout reached before sending all data. + * ex.bytesTransferred may contain bytes transferred + * @throws IOException if an error occurred during writing + */ + void write(final byte[] src, final int timeout) throws IOException; + + /** + * Writes bytes with specified length from the source buffer. + * + * @param src the source byte buffer + * @param length the length of the data to write + * @param timeout the timeout for writing in milliseconds, 0 is infinite + * @throws SerialTimeoutException if timeout reached before sending all data. + * ex.bytesTransferred may contain bytes transferred + * @throws IOException if an error occurred during writing + */ + void write(final byte[] src, int length, final int timeout) throws IOException; + + /** + * Sets various serial port parameters. + * + * @param baudRate baud rate as an integer, for example {@code 115200}. + * @param dataBits one of {@link #DATABITS_5}, {@link #DATABITS_6}, + * {@link #DATABITS_7}, or {@link #DATABITS_8}. + * @param stopBits one of {@link #STOPBITS_1}, {@link #STOPBITS_1_5}, or {@link #STOPBITS_2}. + * @param parity one of {@link #PARITY_NONE}, {@link #PARITY_ODD}, + * {@link #PARITY_EVEN}, {@link #PARITY_MARK}, or {@link #PARITY_SPACE}. + * @throws IOException on error setting the port parameters + * @throws UnsupportedOperationException if not supported or values are not supported by a specific device + */ + void setParameters(int baudRate, int dataBits, int stopBits, @Parity int parity) throws IOException; + + /** + * Gets the CD (Carrier Detect) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getCD() throws IOException; + + /** + * Gets the CTS (Clear To Send) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getCTS() throws IOException; + + /** + * Gets the DSR (Data Set Ready) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getDSR() throws IOException; + + /** + * Gets the DTR (Data Terminal Ready) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getDTR() throws IOException; + + /** + * Sets the DTR (Data Terminal Ready) bit on the underlying UART, if supported. + * + * @param value the value to set + * @throws IOException if an error occurred during writing + * @throws UnsupportedOperationException if not supported + */ + void setDTR(boolean value) throws IOException; + + /** + * Gets the RI (Ring Indicator) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getRI() throws IOException; + + /** + * Gets the RTS (Request To Send) bit from the underlying UART. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getRTS() throws IOException; + + /** + * Sets the RTS (Request To Send) bit on the underlying UART, if supported. + * + * @param value the value to set + * @throws IOException if an error occurred during writing + * @throws UnsupportedOperationException if not supported + */ + void setRTS(boolean value) throws IOException; + + /** + * Gets all control line values from the underlying UART, if supported. + * Requires less USB calls than calling getRTS() + ... + getRI() individually. + * + * @return EnumSet.contains(...) is {@code true} if set, else {@code false} + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + EnumSet getControlLines() throws IOException; + + /** + * Gets all control line supported flags. + * + * @return EnumSet.contains(...) is {@code true} if supported, else {@code false} + * @throws IOException if an error occurred during reading + */ + EnumSet getSupportedControlLines() throws IOException; + + /** + * Set flow control mode, if supported + * @param flowControl @FlowControl + * @throws IOException if an error occurred during writing + * @throws UnsupportedOperationException if not supported + */ + void setFlowControl(FlowControl flowControl) throws IOException; + + /** + * Get flow control mode. + * @return FlowControl + */ + FlowControl getFlowControl(); + + /** + * Get supported flow control modes + * @return EnumSet.contains(...) is {@code true} if supported, else {@code false} + */ + EnumSet getSupportedFlowControl(); + + /** + * If flow control = XON_XOFF, indicates that send is enabled by XON. + * Devices supporting flow control = XON_XOFF_INLINE return CHAR_XON/CHAR_XOFF in read() data. + * + * @return the current state + * @throws IOException if an error occurred during reading + * @throws UnsupportedOperationException if not supported + */ + boolean getXON() throws IOException; + + /** + * Purge non-transmitted output data and / or non-read input data. + * + * @param purgeWriteBuffers {@code true} to discard non-transmitted output data + * @param purgeReadBuffers {@code true} to discard non-read input data + * @throws IOException if an error occurred during flush + * @throws UnsupportedOperationException if not supported + */ + void purgeHwBuffers(boolean purgeWriteBuffers, boolean purgeReadBuffers) throws IOException; + + /** + * send BREAK condition. + * + * @param value set/reset + */ + void setBreak(boolean value) throws IOException; + + /** + * Returns the current state of the connection. + */ + boolean isOpen(); + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java new file mode 100644 index 00000000..cba2bdc7 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/driver/UsbSerialProber.java @@ -0,0 +1,90 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.driver; + +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author mike wakerly (opensource@hoho.com) + */ +public class UsbSerialProber { + + private final ProbeTable mProbeTable; + + public UsbSerialProber(ProbeTable probeTable) { + mProbeTable = probeTable; + } + + public static UsbSerialProber getDefaultProber() { + return new UsbSerialProber(getDefaultProbeTable()); + } + + public static ProbeTable getDefaultProbeTable() { + final ProbeTable probeTable = new ProbeTable(); + probeTable.addDriver(CdcAcmSerialDriver.class); + probeTable.addDriver(Cp21xxSerialDriver.class); + probeTable.addDriver(FtdiSerialDriver.class); + probeTable.addDriver(ProlificSerialDriver.class); + probeTable.addDriver(Ch34xSerialDriver.class); + probeTable.addDriver(GsmModemSerialDriver.class); + probeTable.addDriver(ChromeCcdSerialDriver.class); + return probeTable; + } + + /** + * Finds and builds all possible {@link UsbSerialDriver UsbSerialDrivers} + * from the currently-attached {@link UsbDevice} hierarchy. This method does + * not require permission from the Android USB system, since it does not + * open any of the devices. + * + * @param usbManager usb manager + * @return a list, possibly empty, of all compatible drivers + */ + public List findAllDrivers(final UsbManager usbManager) { + final List result = new ArrayList<>(); + + for (final UsbDevice usbDevice : usbManager.getDeviceList().values()) { + final UsbSerialDriver driver = probeDevice(usbDevice); + if (driver != null) { + result.add(driver); + } + } + return result; + } + + /** + * Probes a single device for a compatible driver. + * + * @param usbDevice the usb device to probe + * @return a new {@link UsbSerialDriver} compatible with this device, or + * {@code null} if none available. + */ + public UsbSerialDriver probeDevice(final UsbDevice usbDevice) { + final Class driverClass = mProbeTable.findDriver(usbDevice); + if (driverClass != null) { + final UsbSerialDriver driver; + try { + final Constructor ctor = + driverClass.getConstructor(UsbDevice.class); + driver = ctor.newInstance(usbDevice); + } catch (NoSuchMethodException | IllegalArgumentException | InstantiationException | + IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + return driver; + } + return null; + } + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/util/HexDump.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/util/HexDump.java new file mode 100644 index 00000000..ddf8f0f5 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/util/HexDump.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hoho.android.usbserial.util; + +import java.security.InvalidParameterException; + +/** + * Clone of Android's /core/java/com/android/internal/util/HexDump class, for use in debugging. + * Changes: space separated hex strings + */ +public class HexDump { + private final static char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + private HexDump() { + } + + public static String dumpHexString(byte[] array) { + return dumpHexString(array, 0, array.length); + } + + public static String dumpHexString(byte[] array, int offset, int length) { + StringBuilder result = new StringBuilder(); + + byte[] line = new byte[8]; + int lineIndex = 0; + + for (int i = offset; i < offset + length; i++) { + if (lineIndex == line.length) { + for (int j = 0; j < line.length; j++) { + if (line[j] > ' ' && line[j] < '~') { + result.append(new String(line, j, 1)); + } else { + result.append("."); + } + } + + result.append("\n"); + lineIndex = 0; + } + + byte b = array[i]; + result.append(HEX_DIGITS[(b >>> 4) & 0x0F]); + result.append(HEX_DIGITS[b & 0x0F]); + result.append(" "); + + line[lineIndex++] = b; + } + + for (int i = 0; i < (line.length - lineIndex); i++) { + result.append(" "); + } + for (int i = 0; i < lineIndex; i++) { + if (line[i] > ' ' && line[i] < '~') { + result.append(new String(line, i, 1)); + } else { + result.append("."); + } + } + + return result.toString(); + } + + public static String toHexString(byte b) { + return toHexString(toByteArray(b)); + } + + public static String toHexString(byte[] array) { + return toHexString(array, 0, array.length); + } + + public static String toHexString(byte[] array, int offset, int length) { + char[] buf = new char[length > 0 ? length * 3 - 1 : 0]; + + int bufIndex = 0; + for (int i = offset; i < offset + length; i++) { + if (i > offset) + buf[bufIndex++] = ' '; + byte b = array[i]; + buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; + buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; + } + + return new String(buf); + } + + public static String toHexString(int i) { + return toHexString(toByteArray(i)); + } + + public static String toHexString(short i) { + return toHexString(toByteArray(i)); + } + + public static byte[] toByteArray(byte b) { + byte[] array = new byte[1]; + array[0] = b; + return array; + } + + public static byte[] toByteArray(int i) { + byte[] array = new byte[4]; + + array[3] = (byte) (i & 0xFF); + array[2] = (byte) ((i >> 8) & 0xFF); + array[1] = (byte) ((i >> 16) & 0xFF); + array[0] = (byte) ((i >> 24) & 0xFF); + + return array; + } + + public static byte[] toByteArray(short i) { + byte[] array = new byte[2]; + + array[1] = (byte) (i & 0xFF); + array[0] = (byte) ((i >> 8) & 0xFF); + + return array; + } + + private static int toByte(char c) { + if (c >= '0' && c <= '9') + return (c - '0'); + if (c >= 'A' && c <= 'F') + return (c - 'A' + 10); + if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + + throw new InvalidParameterException("Invalid hex char '" + c + "'"); + } + + /** accepts any separator, e.g. space or newline */ + public static byte[] hexStringToByteArray(String hexString) { + int length = hexString.length(); + byte[] buffer = new byte[(length + 1) / 3]; + + for (int i = 0; i < length; i += 3) { + buffer[i / 3] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i + 1))); + } + + return buffer; + } +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/util/MonotonicClock.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/util/MonotonicClock.java new file mode 100644 index 00000000..befc3dcb --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/util/MonotonicClock.java @@ -0,0 +1,14 @@ +package com.hoho.android.usbserial.util; + +public final class MonotonicClock { + + private static final long NS_PER_MS = 1_000_000; + + private MonotonicClock() { + } + + public static long millis() { + return System.nanoTime() / NS_PER_MS; + } + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java new file mode 100644 index 00000000..dc135eb5 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/util/SerialInputOutputManager.java @@ -0,0 +1,353 @@ +/* Copyright 2011-2013 Google Inc. + * Copyright 2013 mike wakerly + * + * Project home page: https://github.com/mik3y/usb-serial-for-android + */ + +package com.hoho.android.usbserial.util; + +import android.os.Process; +import android.util.Log; + +import com.hoho.android.usbserial.driver.UsbSerialPort; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Utility class which services a {@link UsbSerialPort} in its {@link #runWrite()} ()} and {@link #runRead()} ()} ()} methods. + * + * @author mike wakerly (opensource@hoho.com) + */ +public class SerialInputOutputManager { + + public enum State { + STOPPED, + STARTING, + RUNNING, + STOPPING + } + + public static boolean DEBUG = false; + + private static final String TAG = SerialInputOutputManager.class.getSimpleName(); + private static final int WRITE_BUFFER_SIZE = 4096; + + private int mReadTimeout = 0; + private int mWriteTimeout = 0; + private int mReadQueueBufferCount = 0; + // no mReadQueueBufferSize, using mReadBuffer.size instead + + private final Object mReadBufferLock = new Object(); + private final Object mWriteBufferLock = new Object(); + + private ByteBuffer mReadBuffer; // default size = getReadEndpoint().getMaxPacketSize() + private ByteBuffer mWriteBuffer = ByteBuffer.allocate(WRITE_BUFFER_SIZE); + + private int mThreadPriority = Process.THREAD_PRIORITY_URGENT_AUDIO; + private final AtomicReference mState = new AtomicReference<>(State.STOPPED); + private CountDownLatch mStartuplatch = new CountDownLatch(2); + private Listener mListener; // Synchronized by 'this' + private final UsbSerialPort mSerialPort; + + public interface Listener { + /** + * Called when new incoming data is available. + */ + void onNewData(byte[] data); + + /** + * Called when {@link SerialInputOutputManager#runRead()} ()} or {@link SerialInputOutputManager#runWrite()} ()} ()} aborts due to an error. + */ + void onRunError(Exception e); + } + + public SerialInputOutputManager(UsbSerialPort serialPort) { + mSerialPort = serialPort; + mReadBuffer = ByteBuffer.allocate(serialPort.getReadEndpoint().getMaxPacketSize()); + } + + public SerialInputOutputManager(UsbSerialPort serialPort, Listener listener) { + this(serialPort); + mListener = listener; + } + + public synchronized void setListener(Listener listener) { + mListener = listener; + } + + public synchronized Listener getListener() { + return mListener; + } + + /** + * setThreadPriority. By default a higher priority than UI thread is used to prevent data loss + * + * @param threadPriority see {@link Process#setThreadPriority(int)} + * */ + public void setThreadPriority(int threadPriority) { + if (!mState.compareAndSet(State.STOPPED, State.STOPPED)) { + throw new IllegalStateException("threadPriority only configurable before SerialInputOutputManager is started"); + } + mThreadPriority = threadPriority; + } + + /** + * read/write timeout + */ + public void setReadTimeout(int timeout) { + // when set if already running, read already blocks and the new value will not become effective now + if(mReadTimeout == 0 && timeout != 0 && mState.get() != State.STOPPED) + throw new IllegalStateException("readTimeout only configurable before SerialInputOutputManager is started"); + mReadTimeout = timeout; + } + + public int getReadTimeout() { + return mReadTimeout; + } + + public void setWriteTimeout(int timeout) { + mWriteTimeout = timeout; + } + + public int getWriteTimeout() { + return mWriteTimeout; + } + + /** + * read/write buffer size + */ + public void setReadBufferSize(int bufferSize) { + if (getReadBufferSize() == bufferSize) + return; + synchronized (mReadBufferLock) { + mReadBuffer = ByteBuffer.allocate(bufferSize); + } + } + + public int getReadBufferSize() { + return mReadBuffer.capacity(); + } + + public void setWriteBufferSize(int bufferSize) { + if(getWriteBufferSize() == bufferSize) + return; + synchronized (mWriteBufferLock) { + ByteBuffer newWriteBuffer = ByteBuffer.allocate(bufferSize); + if(mWriteBuffer.position() > 0) + newWriteBuffer.put(mWriteBuffer.array(), 0, mWriteBuffer.position()); + mWriteBuffer = newWriteBuffer; + } + } + + public int getWriteBufferSize() { + return mWriteBuffer.capacity(); + } + + /** + * Set read queue, similar to {@link UsbSerialPort#setReadQueue} + * except buffer size to be set before with {@link #setReadBufferSize}. + * + * @param bufferCount number of buffers to use for readQueue, + * disable with value 0 + */ + public void setReadQueue(int bufferCount) { + mSerialPort.setReadQueue(bufferCount, getReadBufferSize()); + mReadQueueBufferCount = bufferCount; // only store if set ok + } + + public int getReadQueueBufferCount() { return mReadQueueBufferCount; } + + /** + * write data asynchronously + */ + public void writeAsync(byte[] data) { + synchronized (mWriteBufferLock) { + mWriteBuffer.put(data); + mWriteBufferLock.notifyAll(); // Notify waiting threads + } + } + + /** + * start SerialInputOutputManager in separate threads + */ + public void start() { + mSerialPort.setReadQueue(mReadQueueBufferCount, getReadBufferSize()); + if(mState.compareAndSet(State.STOPPED, State.STARTING)) { + mStartuplatch = new CountDownLatch(2); + new Thread(this::runRead, this.getClass().getSimpleName() + "_read").start(); + new Thread(this::runWrite, this.getClass().getSimpleName() + "_write").start(); + try { + mStartuplatch.await(); + mState.set(State.RUNNING); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } else { + throw new IllegalStateException("already started"); + } + } + + /** + * stop SerialInputOutputManager threads + * + * when using readTimeout == 0 (default), additionally use usbSerialPort.close() to + * interrupt blocking read + */ + public void stop() { + if(mState.compareAndSet(State.RUNNING, State.STOPPING)) { + synchronized (mWriteBufferLock) { + mWriteBufferLock.notifyAll(); // wake up write thread to check the stop condition + } + Log.i(TAG, "Stop requested"); + } + } + + public State getState() { + return mState.get(); + } + + /** + * @return true if the thread is still running + */ + private boolean isStillRunning() { + State state = mState.get(); + return ((state == State.RUNNING) || (state == State.STARTING)) + && !Thread.currentThread().isInterrupted(); + } + + /** + * Notify listener of an error + * + * @param e the exception + */ + private void notifyErrorListener(Throwable e) { + Listener listener = getListener(); + if (listener != null) { + try { + listener.onRunError(e instanceof Exception ? (Exception) e : new Exception(e)); + } catch (Throwable t) { + Log.w(TAG, "Exception in onRunError: " + t.getMessage(), t); + } + } + } + + /** + * Set the thread priority + */ + private void setThreadPriority() { + if (mThreadPriority != Process.THREAD_PRIORITY_DEFAULT) { + Process.setThreadPriority(mThreadPriority); + } + } + + /** + * Continuously services the read buffers until {@link #stop()} is called, or until a driver exception is + * raised. + */ + void runRead() { + Log.i(TAG, "runRead running ..."); + try { + setThreadPriority(); + mStartuplatch.countDown(); + do { + stepRead(); + } while (isStillRunning()); + Log.i(TAG, "runRead: Stopping mState=" + getState()); + } catch (Throwable e) { + if (Thread.currentThread().isInterrupted()) { + Log.w(TAG, "runRead: interrupted"); + } else if(mSerialPort.isOpen()) { + Log.w(TAG, "runRead ending due to exception: " + e.getMessage(), e); + } else { + Log.i(TAG, "runRead: Socket closed"); + } + notifyErrorListener(e); + } finally { + if (mState.compareAndSet(State.RUNNING, State.STOPPING)) { + synchronized (mWriteBufferLock) { + mWriteBufferLock.notifyAll(); // wake up write thread to check the stop condition + } + } else if (mState.compareAndSet(State.STOPPING, State.STOPPED)) { + Log.i(TAG, "runRead: Stopped mState=" + getState()); + } + } + } + + /** + * Continuously services the write buffers until {@link #stop()} is called, or until a driver exception is + * raised. + */ + void runWrite() { + Log.i(TAG, "runWrite running ..."); + try { + setThreadPriority(); + mStartuplatch.countDown(); + do { + stepWrite(); + } while (isStillRunning()); + Log.i(TAG, "runWrite: Stopping mState=" + getState()); + } catch (Throwable e) { + if (Thread.currentThread().isInterrupted()) { + Log.w(TAG, "runWrite: interrupted"); + } else if(mSerialPort.isOpen()) { + Log.w(TAG, "runWrite ending due to exception: " + e.getMessage(), e); + } else { + Log.i(TAG, "runWrite: Socket closed"); + } + notifyErrorListener(e); + } finally { + if (!mState.compareAndSet(State.RUNNING, State.STOPPING)) { + if (mState.compareAndSet(State.STOPPING, State.STOPPED)) { + Log.i(TAG, "runWrite: Stopped mState=" + getState()); + } + } + } + } + + private void stepRead() throws IOException { + // Handle incoming data. + byte[] buffer; + synchronized (mReadBufferLock) { + buffer = mReadBuffer.array(); + } + int len = mSerialPort.read(buffer, mReadTimeout); + if (len > 0) { + if (DEBUG) { + Log.d(TAG, "Read data len=" + len); + } + final Listener listener = getListener(); + if (listener != null) { + final byte[] data = new byte[len]; + System.arraycopy(buffer, 0, data, 0, len); + listener.onNewData(data); + } + } + } + + private void stepWrite() throws IOException, InterruptedException { + // Handle outgoing data. + byte[] buffer = null; + synchronized (mWriteBufferLock) { + int len = mWriteBuffer.position(); + if (len > 0) { + buffer = new byte[len]; + mWriteBuffer.rewind(); + mWriteBuffer.get(buffer, 0, len); + mWriteBuffer.clear(); + mWriteBufferLock.notifyAll(); // Notify writeAsync that there is space in the buffer + } else { + mWriteBufferLock.wait(); + } + } + if (buffer != null) { + if (DEBUG) { + Log.d(TAG, "Writing data len=" + buffer.length); + } + mSerialPort.write(buffer, mWriteTimeout); + } + } + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/util/UsbUtils.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/util/UsbUtils.java new file mode 100644 index 00000000..e019ef15 --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/util/UsbUtils.java @@ -0,0 +1,37 @@ +package com.hoho.android.usbserial.util; + +import android.hardware.usb.UsbDeviceConnection; + +import java.util.ArrayList; + +public class UsbUtils { + + // copied from java.util.function.Supplier which is not available < API 24 + public interface Supplier { + T get(); + } + + private UsbUtils() { + } + + public static ArrayList getDescriptors(UsbDeviceConnection connection) { + ArrayList descriptors = new ArrayList<>(); + byte[] rawDescriptors = connection.getRawDescriptors(); + if (rawDescriptors != null) { + int pos = 0; + while (pos < rawDescriptors.length) { + int len = rawDescriptors[pos] & 0xFF; + if (len == 0) + break; + if (pos + len > rawDescriptors.length) + len = rawDescriptors.length - pos; + byte[] descriptor = new byte[len]; + System.arraycopy(rawDescriptors, pos, descriptor, 0, len); + descriptors.add(descriptor); + pos += len; + } + } + return descriptors; + } + +} diff --git a/android/usbserial/src/main/java/com/hoho/android/usbserial/util/XonXoffFilter.java b/android/usbserial/src/main/java/com/hoho/android/usbserial/util/XonXoffFilter.java new file mode 100644 index 00000000..261682fc --- /dev/null +++ b/android/usbserial/src/main/java/com/hoho/android/usbserial/util/XonXoffFilter.java @@ -0,0 +1,47 @@ +package com.hoho.android.usbserial.util; + +import com.hoho.android.usbserial.driver.UsbSerialPort; + +import java.io.IOException; + +/** + * Some devices return XON and XOFF characters inline in read() data. + * Other devices return XON / XOFF condition thru getXOFF() method. + */ + +public class XonXoffFilter { + private boolean xon = true; + + public XonXoffFilter() { + } + + public boolean getXON() { + return xon; + } + + /** + * Filter XON/XOFF from read() data and remember + * + * @param data unfiltered data + * @return filtered data + */ + public byte[] filter(byte[] data) { + int found = 0; + for (int i=0; i - - Tauri + Svelte + TS + + Serial Port Test — Tauri + Vue diff --git a/examples/serialport-test/package-lock.json b/examples/serialport-test/package-lock.json index a68429d7..e7e66bb4 100644 --- a/examples/serialport-test/package-lock.json +++ b/examples/serialport-test/package-lock.json @@ -9,55 +9,92 @@ "version": "0.0.0", "dependencies": { "@tauri-apps/api": "^2.5.0", - "tauri-plugin-serialplugin": "file:../../" + "tauri-plugin-serialplugin-api": "file:../../", + "vue": "^3.5.13" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.3", "@tauri-apps/cli": "^2.5.0", - "@tsconfig/svelte": "^5.0.4", "@types/node": "^20.17.6", - "svelte": "^5.25.6", - "svelte-check": "^4.1.5", - "svelte-preprocess": "^6.0.3", - "tslib": "^2.8.1", + "@vitejs/plugin-vue": "^5.2.1", "typescript": "^5.6.3", - "vite": "^6.2.5" + "vite": "^6.2.5", + "vue-tsc": "^2.2.8" } }, "../..": { - "version": "2.11.0", + "name": "tauri-plugin-serialplugin-api", + "version": "2.24.0", "license": "MIT or APACHE-2.0", "dependencies": { - "@tauri-apps/api": "^2.5.0" + "@tauri-apps/api": ">=2.0.0-beta.6" }, "devDependencies": { "@rollup/plugin-node-resolve": "15.3.1", "@rollup/plugin-terser": "0.4.4", - "@rollup/plugin-typescript": "11.1.6", + "@rollup/plugin-typescript": "^12.1.4", + "@types/jest": "^29.5.12", + "@types/node": "^20.17.22", + "jest": "^29.7.0", + "jest-environment-jsdom": "^30.0.2", + "jest-junit": "^16.0.0", + "jsdom": "^26.1.0", "rollup": "4.34.9", "standard-version": "^9.5.0", + "ts-jest": "^29.1.2", "tslib": "^2.8.1", "typescript": "5.6.3" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { "node": ">=6.0.0" } }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", - "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "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" ], @@ -72,9 +109,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", - "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "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" ], @@ -89,9 +126,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", - "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "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" ], @@ -106,9 +143,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", - "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "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" ], @@ -123,9 +160,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", - "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "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" ], @@ -140,9 +177,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", - "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -157,9 +194,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", - "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "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" ], @@ -174,9 +211,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", - "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -191,9 +228,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", - "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -208,9 +245,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", - "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -225,9 +262,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", - "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "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" ], @@ -242,9 +279,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", - "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "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" ], @@ -259,9 +296,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", - "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "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" ], @@ -276,9 +313,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", - "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "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" ], @@ -293,9 +330,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", - "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "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" ], @@ -310,9 +347,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", - "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "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" ], @@ -327,9 +364,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", - "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "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" ], @@ -344,9 +381,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", - "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "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" ], @@ -361,9 +398,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", - "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "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" ], @@ -378,9 +415,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", - "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "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" ], @@ -395,9 +432,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", - "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "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" ], @@ -411,10 +448,27 @@ "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.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", - "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -429,9 +483,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", - "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "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" ], @@ -446,9 +500,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", - "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -463,9 +517,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", - "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -479,63 +533,16 @@ "node": ">=18" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "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/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz", - "integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", "cpu": [ "arm" ], @@ -547,9 +554,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz", - "integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", "cpu": [ "arm64" ], @@ -561,9 +568,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz", - "integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", "cpu": [ "arm64" ], @@ -575,9 +582,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz", - "integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", "cpu": [ "x64" ], @@ -589,9 +596,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz", - "integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", "cpu": [ "arm64" ], @@ -603,9 +610,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz", - "integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", "cpu": [ "x64" ], @@ -617,13 +624,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz", - "integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -631,13 +641,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz", - "integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -645,13 +658,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz", - "integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -659,41 +675,84 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz", - "integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz", - "integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz", - "integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==", + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -701,13 +760,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz", - "integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -715,13 +777,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz", - "integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -729,13 +794,16 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz", - "integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -743,13 +811,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz", - "integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -757,23 +828,54 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz", - "integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz", - "integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", "cpu": [ "arm64" ], @@ -785,9 +887,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz", - "integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", "cpu": [ "ia32" ], @@ -798,10 +900,10 @@ "win32" ] }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz", - "integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", "cpu": [ "x64" ], @@ -812,60 +914,24 @@ "win32" ] }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", - "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz", - "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", - "debug": "^4.4.0", - "deepmerge": "^4.3.1", - "kleur": "^4.1.5", - "magic-string": "^0.30.15", - "vitefu": "^1.0.4" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" - }, - "peerDependencies": { - "svelte": "^5.0.0", - "vite": "^6.0.0" - } - }, - "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", - "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.7" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" - }, - "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.0", - "svelte": "^5.0.0", - "vite": "^6.0.0" - } + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@tauri-apps/api": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.5.0.tgz", - "integrity": "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", + "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", "license": "Apache-2.0 OR MIT", "funding": { "type": "opencollective", @@ -873,9 +939,9 @@ } }, "node_modules/@tauri-apps/cli": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.5.0.tgz", - "integrity": "sha512-rAtHqG0Gh/IWLjN2zTf3nZqYqbo81oMbqop56rGTjrlWk9pTTAjkqOjSL9XQLIMZ3RbeVjveCqqCA0s8RnLdMg==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", + "integrity": "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==", "dev": true, "license": "Apache-2.0 OR MIT", "bin": { @@ -889,23 +955,23 @@ "url": "https://opencollective.com/tauri" }, "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.5.0", - "@tauri-apps/cli-darwin-x64": "2.5.0", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.5.0", - "@tauri-apps/cli-linux-arm64-gnu": "2.5.0", - "@tauri-apps/cli-linux-arm64-musl": "2.5.0", - "@tauri-apps/cli-linux-riscv64-gnu": "2.5.0", - "@tauri-apps/cli-linux-x64-gnu": "2.5.0", - "@tauri-apps/cli-linux-x64-musl": "2.5.0", - "@tauri-apps/cli-win32-arm64-msvc": "2.5.0", - "@tauri-apps/cli-win32-ia32-msvc": "2.5.0", - "@tauri-apps/cli-win32-x64-msvc": "2.5.0" + "@tauri-apps/cli-darwin-arm64": "2.11.4", + "@tauri-apps/cli-darwin-x64": "2.11.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", + "@tauri-apps/cli-linux-arm64-musl": "2.11.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-musl": "2.11.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", + "@tauri-apps/cli-win32-x64-msvc": "2.11.4" } }, "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.5.0.tgz", - "integrity": "sha512-VuVAeTFq86dfpoBDNYAdtQVLbP0+2EKCHIIhkaxjeoPARR0sLpFHz2zs0PcFU76e+KAaxtEtAJAXGNUc8E1PzQ==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", + "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", "cpu": [ "arm64" ], @@ -920,9 +986,9 @@ } }, "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.5.0.tgz", - "integrity": "sha512-hUF01sC06cZVa8+I0/VtsHOk9BbO75rd+YdtHJ48xTdcYaQ5QIwL4yZz9OR1AKBTaUYhBam8UX9Pvd5V2/4Dpw==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", + "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", "cpu": [ "x64" ], @@ -937,9 +1003,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.5.0.tgz", - "integrity": "sha512-LQKqttsK252LlqYyX8R02MinUsfFcy3+NZiJwHFgi5Y3+ZUIAED9cSxJkyNtuY5KMnR4RlpgWyLv4P6akN1xhg==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", + "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", "cpu": [ "arm" ], @@ -954,13 +1020,16 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.5.0.tgz", - "integrity": "sha512-mTQufsPcpdHg5RW0zypazMo4L55EfeE5snTzrPqbLX4yCK2qalN7+rnP8O8GT06xhp6ElSP/Ku1M2MR297SByQ==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", + "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -971,13 +1040,16 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.0.tgz", - "integrity": "sha512-rQO1HhRUQqyEaal5dUVOQruTRda/TD36s9kv1hTxZiFuSq3558lsTjAcUEnMAtBcBkps20sbyTJNMT0AwYIk8Q==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", + "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -988,13 +1060,16 @@ } }, "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.5.0.tgz", - "integrity": "sha512-7oS18FN46yDxyw1zX/AxhLAd7T3GrLj3Ai6s8hZKd9qFVzrAn36ESL7d3G05s8wEtsJf26qjXnVF4qleS3dYsA==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", + "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -1005,13 +1080,16 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.5.0.tgz", - "integrity": "sha512-SG5sFNL7VMmDBdIg3nO3EzNRT306HsiEQ0N90ILe3ZABYAVoPDO/ttpCO37ApLInTzrq/DLN+gOlC/mgZvLw1w==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.4.tgz", + "integrity": "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -1022,13 +1100,16 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.0.tgz", - "integrity": "sha512-QXDM8zp/6v05PNWju5ELsVwF0VH1n6b5pk2E6W/jFbbiwz80Vs1lACl9pv5kEHkrxBj+aWU/03JzGuIj2g3SkQ==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", + "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "Apache-2.0 OR MIT", "optional": true, "os": [ @@ -1039,9 +1120,9 @@ } }, "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.5.0.tgz", - "integrity": "sha512-pFSHFK6b+o9y4Un8w0gGLwVyFTZaC3P0kQ7umRt/BLDkzD5RnQ4vBM7CF8BCU5nkwmEBUCZd7Wt3TWZxe41o6Q==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", + "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", "cpu": [ "arm64" ], @@ -1056,9 +1137,9 @@ } }, "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.5.0.tgz", - "integrity": "sha512-EArv1IaRlogdLAQyGlKmEqZqm5RfHCUMhJoedWu7GtdbOMUfSAz6FMX2boE1PtEmNO4An+g188flLeVErrxEKg==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", + "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", "cpu": [ "ia32" ], @@ -1073,9 +1154,9 @@ } }, "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.5.0.tgz", - "integrity": "sha512-lj43EFYbnAta8pd9JnUq87o+xRUR0odz+4rixBtTUwUgdRdwQ2V9CzFtsMu6FQKpFQ6mujRK6P1IEwhL6ADRsQ==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", + "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", "cpu": [ "x64" ], @@ -1089,121 +1170,255 @@ "node": ">= 10" } }, - "node_modules/@tsconfig/svelte": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.4.tgz", - "integrity": "sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.16.tgz", - "integrity": "sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==", + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.21.0" } }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, "engines": { - "node": ">=0.4.0" + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" } }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } + "license": "MIT" }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" } }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "node_modules/@vue/compiler-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.39.tgz", + "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.39", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", + "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", + "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.39", + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", + "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" } }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" }, - "engines": { - "node": ">=6.0" + "peerDependencies": { + "typescript": "*" }, "peerDependenciesMeta": { - "supports-color": { + "typescript": { "optional": true } } }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/@vue/reactivity": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.39.tgz", + "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.39.tgz", + "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/shared": "3.5.39" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", + "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.39", + "@vue/runtime-core": "3.5.39", + "@vue/shared": "3.5.39", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.39.tgz", + "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.39", + "@vue/shared": "3.5.39" + }, + "peerDependencies": { + "vue": "3.5.39" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.39.tgz", + "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==", + "license": "MIT" + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/esbuild": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", - "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "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", @@ -1214,56 +1429,49 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.0", - "@esbuild/android-arm": "0.25.0", - "@esbuild/android-arm64": "0.25.0", - "@esbuild/android-x64": "0.25.0", - "@esbuild/darwin-arm64": "0.25.0", - "@esbuild/darwin-x64": "0.25.0", - "@esbuild/freebsd-arm64": "0.25.0", - "@esbuild/freebsd-x64": "0.25.0", - "@esbuild/linux-arm": "0.25.0", - "@esbuild/linux-arm64": "0.25.0", - "@esbuild/linux-ia32": "0.25.0", - "@esbuild/linux-loong64": "0.25.0", - "@esbuild/linux-mips64el": "0.25.0", - "@esbuild/linux-ppc64": "0.25.0", - "@esbuild/linux-riscv64": "0.25.0", - "@esbuild/linux-s390x": "0.25.0", - "@esbuild/linux-x64": "0.25.0", - "@esbuild/netbsd-arm64": "0.25.0", - "@esbuild/netbsd-x64": "0.25.0", - "@esbuild/openbsd-arm64": "0.25.0", - "@esbuild/openbsd-x64": "0.25.0", - "@esbuild/sunos-x64": "0.25.0", - "@esbuild/win32-arm64": "0.25.0", - "@esbuild/win32-ia32": "0.25.0", - "@esbuild/win32-x64": "0.25.0" - } - }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, + "@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/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "license": "MIT" }, - "node_modules/esrap": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.6.tgz", - "integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "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" }, @@ -1288,65 +1496,52 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, - "node_modules/kleur": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", - "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "bin": { + "he": "bin/he" } }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, - "license": "MIT" - }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/mri": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", - "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, "engines": { - "node": ">=4" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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==", + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", "funding": [ { "type": "github", @@ -1361,17 +1556,23 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "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": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -1382,10 +1583,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", "funding": [ { "type": "opencollective", @@ -1402,7 +1602,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -1410,28 +1610,14 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/readdirp": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", - "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/rollup": { - "version": "4.41.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz", - "integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -1441,171 +1627,56 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.41.1", - "@rollup/rollup-android-arm64": "4.41.1", - "@rollup/rollup-darwin-arm64": "4.41.1", - "@rollup/rollup-darwin-x64": "4.41.1", - "@rollup/rollup-freebsd-arm64": "4.41.1", - "@rollup/rollup-freebsd-x64": "4.41.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", - "@rollup/rollup-linux-arm-musleabihf": "4.41.1", - "@rollup/rollup-linux-arm64-gnu": "4.41.1", - "@rollup/rollup-linux-arm64-musl": "4.41.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", - "@rollup/rollup-linux-riscv64-gnu": "4.41.1", - "@rollup/rollup-linux-riscv64-musl": "4.41.1", - "@rollup/rollup-linux-s390x-gnu": "4.41.1", - "@rollup/rollup-linux-x64-gnu": "4.41.1", - "@rollup/rollup-linux-x64-musl": "4.41.1", - "@rollup/rollup-win32-arm64-msvc": "4.41.1", - "@rollup/rollup-win32-ia32-msvc": "4.41.1", - "@rollup/rollup-win32-x64-msvc": "4.41.1", + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" } }, - "node_modules/sade": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", - "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mri": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, "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/svelte": { - "version": "5.33.2", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.2.tgz", - "integrity": "sha512-uiyusx2rUa9NmVMaIcShnZyDhOfFXxgkn5eXOcgjDBL3RYQGR1+7TctPcI6AWNbu4gHWF5xZ/TlFM7nnw5H+JQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "esm-env": "^1.2.1", - "esrap": "^1.4.6", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/svelte-check": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.5.tgz", - "integrity": "sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "chokidar": "^4.0.1", - "fdir": "^6.2.0", - "picocolors": "^1.0.0", - "sade": "^1.7.4" - }, - "bin": { - "svelte-check": "bin/svelte-check" - }, - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "svelte": "^4.0.0 || ^5.0.0-next.0", - "typescript": ">=5.0.0" - } - }, - "node_modules/svelte-preprocess": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.3.tgz", - "integrity": "sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "engines": { - "node": ">= 18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.10.2", - "coffeescript": "^2.5.1", - "less": "^3.11.3 || ^4.0.0", - "postcss": "^7 || ^8", - "postcss-load-config": ">=3", - "pug": "^3.0.0", - "sass": "^1.26.8", - "stylus": ">=0.55", - "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "svelte": "^4.0.0 || ^5.0.0-next.100 || ^5.0.0", - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "coffeescript": { - "optional": true - }, - "less": { - "optional": true - }, - "postcss": { - "optional": true - }, - "postcss-load-config": { - "optional": true - }, - "pug": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/tauri-plugin-serialplugin": { + "node_modules/tauri-plugin-serialplugin-api": { "resolved": "../..", "link": true }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -1614,18 +1685,11 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "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==", - "dev": true, - "license": "0BSD" - }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "dev": true, + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -1636,16 +1700,16 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", "dependencies": { @@ -1717,31 +1781,50 @@ } } }, - "node_modules/vitefu": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.5.tgz", - "integrity": "sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==", + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.39", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.39.tgz", + "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", "license": "MIT", - "workspaces": [ - "tests/deps/*", - "tests/projects/*" - ], + "dependencies": { + "@vue/compiler-dom": "3.5.39", + "@vue/compiler-sfc": "3.5.39", + "@vue/runtime-dom": "3.5.39", + "@vue/server-renderer": "3.5.39", + "@vue/shared": "3.5.39" + }, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + "typescript": "*" }, "peerDependenciesMeta": { - "vite": { + "typescript": { "optional": true } } }, - "node_modules/zimmerframe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", - "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } } } } diff --git a/examples/serialport-test/package.json b/examples/serialport-test/package.json index c40d6150..4724892e 100755 --- a/examples/serialport-test/package.json +++ b/examples/serialport-test/package.json @@ -5,25 +5,23 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", + "build": "pnpm --dir ../.. run build && vue-tsc --noEmit && vite build", "preview": "vite preview", - "check": "svelte-check --tsconfig ./tsconfig.json", + "check": "vue-tsc --noEmit", + "clean:vite": "rm -rf node_modules/.vite", "tauri": "tauri" }, "dependencies": { - "@tauri-apps/api": "^2.5.0", - "tauri-plugin-serialplugin-api": "file:../../" + "tauri-plugin-serialplugin-api": "file:../../", + "vue": "^3.5.13" }, "devDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.3", - "@tauri-apps/cli": "^2.5.0", - "@tsconfig/svelte": "^5.0.4", + "@tauri-apps/api": "^2.6.0", + "@tauri-apps/cli": "^2.11.4", "@types/node": "^20.17.6", - "svelte": "^5.25.6", - "svelte-check": "^4.1.5", - "svelte-preprocess": "^6.0.3", - "tslib": "^2.8.1", + "@vitejs/plugin-vue": "^5.2.1", "typescript": "^5.6.3", - "vite": "^6.2.5" + "vite": "^6.2.5", + "vue-tsc": "^2.2.8" } } diff --git a/examples/serialport-test/src-tauri/Cargo.lock b/examples/serialport-test/src-tauri/Cargo.lock index b5a5dfca..9321ae4c 100644 --- a/examples/serialport-test/src-tauri/Cargo.lock +++ b/examples/serialport-test/src-tauri/Cargo.lock @@ -3713,14 +3713,14 @@ dependencies = [ [[package]] name = "tauri-plugin-serialplugin" -version = "2.23.0" +version = "3.0.0" dependencies = [ + "jni", "serde", "serde_json", "serialport", "tauri", "tauri-plugin", - "thiserror 2.0.12", ] [[package]] diff --git a/examples/serialport-test/src-tauri/build.gradle.kts b/examples/serialport-test/src-tauri/build.gradle.kts index 9fa801cd..b6b59572 100644 --- a/examples/serialport-test/src-tauri/build.gradle.kts +++ b/examples/serialport-test/src-tauri/build.gradle.kts @@ -2,7 +2,6 @@ buildscript { repositories { google() mavenCentral() - maven { url = uri("https://jitpack.io") } } dependencies { classpath("com.android.tools.build:gradle:8.5.1") @@ -14,11 +13,9 @@ allprojects { repositories { google() mavenCentral() - maven { url = uri("https://jitpack.io") } } } tasks.register("clean").configure { delete("build") } - diff --git a/examples/serialport-test/src-tauri/gen/android/build.gradle.kts b/examples/serialport-test/src-tauri/gen/android/build.gradle.kts index 9fa801cd..b6b59572 100644 --- a/examples/serialport-test/src-tauri/gen/android/build.gradle.kts +++ b/examples/serialport-test/src-tauri/gen/android/build.gradle.kts @@ -2,7 +2,6 @@ buildscript { repositories { google() mavenCentral() - maven { url = uri("https://jitpack.io") } } dependencies { classpath("com.android.tools.build:gradle:8.5.1") @@ -14,11 +13,9 @@ allprojects { repositories { google() mavenCentral() - maven { url = uri("https://jitpack.io") } } } tasks.register("clean").configure { delete("build") } - diff --git a/examples/serialport-test/src-tauri/gen/schemas/acl-manifests.json b/examples/serialport-test/src-tauri/gen/schemas/acl-manifests.json index b612917b..7153bd20 100644 --- a/examples/serialport-test/src-tauri/gen/schemas/acl-manifests.json +++ b/examples/serialport-test/src-tauri/gen/schemas/acl-manifests.json @@ -1 +1 @@ -{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier"]},"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-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-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-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-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-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-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-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-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-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-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},"devtools":{"default_permission":null,"permissions":{},"permission_sets":{},"global_scope_schema":null},"serialplugin":{"default_permission":{"identifier":"default","description":"# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n","permissions":["allow-managed-ports","allow-available-ports","allow-available-ports-direct","allow-cancel-read","allow-close","allow-close-all","allow-force-close","allow-open","allow-read","allow-write","allow-write-binary","allow-start-listening","allow-stop-listening","allow-available-ports","allow-available-ports-direct","allow-bytes-to-read","allow-bytes-to-write","allow-cancel-read","allow-clear-break","allow-clear-buffer","allow-close","allow-close-all","allow-force-close","allow-managed-ports","allow-open","allow-read","allow-read-binary","allow-read-carrier-detect","allow-read-cd","allow-read-clear-to-send","allow-read-cts","allow-read-data-set-ready","allow-read-dsr","allow-read-dtr","allow-read-ri","allow-read-ring-indicator","allow-set-baud-rate","allow-set-break","allow-set-data-bits","allow-set-flow-control","allow-set-parity","allow-set-stop-bits","allow-set-timeout","allow-start-listening","allow-stop-listening","allow-write","allow-write-binary","allow-write-data-terminal-ready","allow-write-dtr","allow-write-request-to-send","allow-write-rts","allow-set-log-level","allow-get-log-level"]},"permissions":{"allow-available-ports":{"identifier":"allow-available-ports","description":"Enables the available_ports command without any pre-configured scope.","commands":{"allow":["available_ports"],"deny":[]}},"allow-available-ports-direct":{"identifier":"allow-available-ports-direct","description":"Enables the available_ports_direct command without any pre-configured scope.","commands":{"allow":["available_ports_direct"],"deny":[]}},"allow-bytes-to-read":{"identifier":"allow-bytes-to-read","description":"Enables the bytes_to_read command without any pre-configured scope.","commands":{"allow":["bytes_to_read"],"deny":[]}},"allow-bytes-to-write":{"identifier":"allow-bytes-to-write","description":"Enables the bytes_to_write command without any pre-configured scope.","commands":{"allow":["bytes_to_write"],"deny":[]}},"allow-cancel-read":{"identifier":"allow-cancel-read","description":"Enables the cancel_read command without any pre-configured scope.","commands":{"allow":["cancel_read"],"deny":[]}},"allow-clear-break":{"identifier":"allow-clear-break","description":"Enables the clear_break command without any pre-configured scope.","commands":{"allow":["clear_break"],"deny":[]}},"allow-clear-buffer":{"identifier":"allow-clear-buffer","description":"Enables the clear_buffer command without any pre-configured scope.","commands":{"allow":["clear_buffer"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-close-all":{"identifier":"allow-close-all","description":"Enables the close_all command without any pre-configured scope.","commands":{"allow":["close_all"],"deny":[]}},"allow-force-close":{"identifier":"allow-force-close","description":"Enables the force_close command without any pre-configured scope.","commands":{"allow":["force_close"],"deny":[]}},"allow-get-log-level":{"identifier":"allow-get-log-level","description":"Enables the get_log_level command without any pre-configured scope.","commands":{"allow":["get_log_level"],"deny":[]}},"allow-managed-ports":{"identifier":"allow-managed-ports","description":"Enables the managed_ports command without any pre-configured scope.","commands":{"allow":["managed_ports"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-read":{"identifier":"allow-read","description":"Enables the read command without any pre-configured scope.","commands":{"allow":["read"],"deny":[]}},"allow-read-binary":{"identifier":"allow-read-binary","description":"Enables the read_binary command without any pre-configured scope.","commands":{"allow":["read_binary"],"deny":[]}},"allow-read-carrier-detect":{"identifier":"allow-read-carrier-detect","description":"Enables the read_carrier_detect command without any pre-configured scope.","commands":{"allow":["read_carrier_detect"],"deny":[]}},"allow-read-cd":{"identifier":"allow-read-cd","description":"Enables the read_cd command without any pre-configured scope.","commands":{"allow":["read_cd"],"deny":[]}},"allow-read-clear-to-send":{"identifier":"allow-read-clear-to-send","description":"Enables the read_clear_to_send command without any pre-configured scope.","commands":{"allow":["read_clear_to_send"],"deny":[]}},"allow-read-cts":{"identifier":"allow-read-cts","description":"Enables the read_cts command without any pre-configured scope.","commands":{"allow":["read_cts"],"deny":[]}},"allow-read-data-set-ready":{"identifier":"allow-read-data-set-ready","description":"Enables the read_data_set_ready command without any pre-configured scope.","commands":{"allow":["read_data_set_ready"],"deny":[]}},"allow-read-dsr":{"identifier":"allow-read-dsr","description":"Enables the read_dsr command without any pre-configured scope.","commands":{"allow":["read_dsr"],"deny":[]}},"allow-read-dtr":{"identifier":"allow-read-dtr","description":"Enables the read_dtr command without any pre-configured scope.","commands":{"allow":["read_dtr"],"deny":[]}},"allow-read-ri":{"identifier":"allow-read-ri","description":"Enables the read_ri command without any pre-configured scope.","commands":{"allow":["read_ri"],"deny":[]}},"allow-read-ring-indicator":{"identifier":"allow-read-ring-indicator","description":"Enables the read_ring_indicator command without any pre-configured scope.","commands":{"allow":["read_ring_indicator"],"deny":[]}},"allow-set-baud-rate":{"identifier":"allow-set-baud-rate","description":"Enables the set_baud_rate command without any pre-configured scope.","commands":{"allow":["set_baud_rate"],"deny":[]}},"allow-set-break":{"identifier":"allow-set-break","description":"Enables the set_break command without any pre-configured scope.","commands":{"allow":["set_break"],"deny":[]}},"allow-set-data-bits":{"identifier":"allow-set-data-bits","description":"Enables the set_data_bits command without any pre-configured scope.","commands":{"allow":["set_data_bits"],"deny":[]}},"allow-set-flow-control":{"identifier":"allow-set-flow-control","description":"Enables the set_flow_control command without any pre-configured scope.","commands":{"allow":["set_flow_control"],"deny":[]}},"allow-set-log-level":{"identifier":"allow-set-log-level","description":"Enables the set_log_level command without any pre-configured scope.","commands":{"allow":["set_log_level"],"deny":[]}},"allow-set-parity":{"identifier":"allow-set-parity","description":"Enables the set_parity command without any pre-configured scope.","commands":{"allow":["set_parity"],"deny":[]}},"allow-set-stop-bits":{"identifier":"allow-set-stop-bits","description":"Enables the set_stop_bits command without any pre-configured scope.","commands":{"allow":["set_stop_bits"],"deny":[]}},"allow-set-timeout":{"identifier":"allow-set-timeout","description":"Enables the set_timeout command without any pre-configured scope.","commands":{"allow":["set_timeout"],"deny":[]}},"allow-start-listening":{"identifier":"allow-start-listening","description":"Enables the start_listening command without any pre-configured scope.","commands":{"allow":["start_listening"],"deny":[]}},"allow-stop-listening":{"identifier":"allow-stop-listening","description":"Enables the stop_listening command without any pre-configured scope.","commands":{"allow":["stop_listening"],"deny":[]}},"allow-write":{"identifier":"allow-write","description":"Enables the write command without any pre-configured scope.","commands":{"allow":["write"],"deny":[]}},"allow-write-binary":{"identifier":"allow-write-binary","description":"Enables the write_binary command without any pre-configured scope.","commands":{"allow":["write_binary"],"deny":[]}},"allow-write-data-terminal-ready":{"identifier":"allow-write-data-terminal-ready","description":"Enables the write_data_terminal_ready command without any pre-configured scope.","commands":{"allow":["write_data_terminal_ready"],"deny":[]}},"allow-write-dtr":{"identifier":"allow-write-dtr","description":"Enables the write_dtr command without any pre-configured scope.","commands":{"allow":["write_dtr"],"deny":[]}},"allow-write-request-to-send":{"identifier":"allow-write-request-to-send","description":"Enables the write_request_to_send command without any pre-configured scope.","commands":{"allow":["write_request_to_send"],"deny":[]}},"allow-write-rts":{"identifier":"allow-write-rts","description":"Enables the write_rts command without any pre-configured scope.","commands":{"allow":["write_rts"],"deny":[]}},"deny-available-ports":{"identifier":"deny-available-ports","description":"Denies the available_ports command without any pre-configured scope.","commands":{"allow":[],"deny":["available_ports"]}},"deny-available-ports-direct":{"identifier":"deny-available-ports-direct","description":"Denies the available_ports_direct command without any pre-configured scope.","commands":{"allow":[],"deny":["available_ports_direct"]}},"deny-bytes-to-read":{"identifier":"deny-bytes-to-read","description":"Denies the bytes_to_read command without any pre-configured scope.","commands":{"allow":[],"deny":["bytes_to_read"]}},"deny-bytes-to-write":{"identifier":"deny-bytes-to-write","description":"Denies the bytes_to_write command without any pre-configured scope.","commands":{"allow":[],"deny":["bytes_to_write"]}},"deny-cancel-read":{"identifier":"deny-cancel-read","description":"Denies the cancel_read command without any pre-configured scope.","commands":{"allow":[],"deny":["cancel_read"]}},"deny-clear-break":{"identifier":"deny-clear-break","description":"Denies the clear_break command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_break"]}},"deny-clear-buffer":{"identifier":"deny-clear-buffer","description":"Denies the clear_buffer command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_buffer"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-close-all":{"identifier":"deny-close-all","description":"Denies the close_all command without any pre-configured scope.","commands":{"allow":[],"deny":["close_all"]}},"deny-force-close":{"identifier":"deny-force-close","description":"Denies the force_close command without any pre-configured scope.","commands":{"allow":[],"deny":["force_close"]}},"deny-get-log-level":{"identifier":"deny-get-log-level","description":"Denies the get_log_level command without any pre-configured scope.","commands":{"allow":[],"deny":["get_log_level"]}},"deny-managed-ports":{"identifier":"deny-managed-ports","description":"Denies the managed_ports command without any pre-configured scope.","commands":{"allow":[],"deny":["managed_ports"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-read":{"identifier":"deny-read","description":"Denies the read command without any pre-configured scope.","commands":{"allow":[],"deny":["read"]}},"deny-read-binary":{"identifier":"deny-read-binary","description":"Denies the read_binary command without any pre-configured scope.","commands":{"allow":[],"deny":["read_binary"]}},"deny-read-carrier-detect":{"identifier":"deny-read-carrier-detect","description":"Denies the read_carrier_detect command without any pre-configured scope.","commands":{"allow":[],"deny":["read_carrier_detect"]}},"deny-read-cd":{"identifier":"deny-read-cd","description":"Denies the read_cd command without any pre-configured scope.","commands":{"allow":[],"deny":["read_cd"]}},"deny-read-clear-to-send":{"identifier":"deny-read-clear-to-send","description":"Denies the read_clear_to_send command without any pre-configured scope.","commands":{"allow":[],"deny":["read_clear_to_send"]}},"deny-read-cts":{"identifier":"deny-read-cts","description":"Denies the read_cts command without any pre-configured scope.","commands":{"allow":[],"deny":["read_cts"]}},"deny-read-data-set-ready":{"identifier":"deny-read-data-set-ready","description":"Denies the read_data_set_ready command without any pre-configured scope.","commands":{"allow":[],"deny":["read_data_set_ready"]}},"deny-read-dsr":{"identifier":"deny-read-dsr","description":"Denies the read_dsr command without any pre-configured scope.","commands":{"allow":[],"deny":["read_dsr"]}},"deny-read-dtr":{"identifier":"deny-read-dtr","description":"Denies the read_dtr command without any pre-configured scope.","commands":{"allow":[],"deny":["read_dtr"]}},"deny-read-ri":{"identifier":"deny-read-ri","description":"Denies the read_ri command without any pre-configured scope.","commands":{"allow":[],"deny":["read_ri"]}},"deny-read-ring-indicator":{"identifier":"deny-read-ring-indicator","description":"Denies the read_ring_indicator command without any pre-configured scope.","commands":{"allow":[],"deny":["read_ring_indicator"]}},"deny-set-baud-rate":{"identifier":"deny-set-baud-rate","description":"Denies the set_baud_rate command without any pre-configured scope.","commands":{"allow":[],"deny":["set_baud_rate"]}},"deny-set-break":{"identifier":"deny-set-break","description":"Denies the set_break command without any pre-configured scope.","commands":{"allow":[],"deny":["set_break"]}},"deny-set-data-bits":{"identifier":"deny-set-data-bits","description":"Denies the set_data_bits command without any pre-configured scope.","commands":{"allow":[],"deny":["set_data_bits"]}},"deny-set-flow-control":{"identifier":"deny-set-flow-control","description":"Denies the set_flow_control command without any pre-configured scope.","commands":{"allow":[],"deny":["set_flow_control"]}},"deny-set-log-level":{"identifier":"deny-set-log-level","description":"Denies the set_log_level command without any pre-configured scope.","commands":{"allow":[],"deny":["set_log_level"]}},"deny-set-parity":{"identifier":"deny-set-parity","description":"Denies the set_parity command without any pre-configured scope.","commands":{"allow":[],"deny":["set_parity"]}},"deny-set-stop-bits":{"identifier":"deny-set-stop-bits","description":"Denies the set_stop_bits command without any pre-configured scope.","commands":{"allow":[],"deny":["set_stop_bits"]}},"deny-set-timeout":{"identifier":"deny-set-timeout","description":"Denies the set_timeout command without any pre-configured scope.","commands":{"allow":[],"deny":["set_timeout"]}},"deny-start-listening":{"identifier":"deny-start-listening","description":"Denies the start_listening command without any pre-configured scope.","commands":{"allow":[],"deny":["start_listening"]}},"deny-stop-listening":{"identifier":"deny-stop-listening","description":"Denies the stop_listening command without any pre-configured scope.","commands":{"allow":[],"deny":["stop_listening"]}},"deny-write":{"identifier":"deny-write","description":"Denies the write command without any pre-configured scope.","commands":{"allow":[],"deny":["write"]}},"deny-write-binary":{"identifier":"deny-write-binary","description":"Denies the write_binary command without any pre-configured scope.","commands":{"allow":[],"deny":["write_binary"]}},"deny-write-data-terminal-ready":{"identifier":"deny-write-data-terminal-ready","description":"Denies the write_data_terminal_ready command without any pre-configured scope.","commands":{"allow":[],"deny":["write_data_terminal_ready"]}},"deny-write-dtr":{"identifier":"deny-write-dtr","description":"Denies the write_dtr command without any pre-configured scope.","commands":{"allow":[],"deny":["write_dtr"]}},"deny-write-request-to-send":{"identifier":"deny-write-request-to-send","description":"Denies the write_request_to_send command without any pre-configured scope.","commands":{"allow":[],"deny":["write_request_to_send"]}},"deny-write-rts":{"identifier":"deny-write-rts","description":"Denies the write_rts command without any pre-configured scope.","commands":{"allow":[],"deny":["write_rts"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file +{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier"]},"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-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-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-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-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-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-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-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-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-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-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},"devtools":{"default_permission":null,"permissions":{},"permission_sets":{},"global_scope_schema":null},"serialplugin":{"default_permission":{"identifier":"default","description":"Default permissions for the serialplugin: enumerate, open, read/write, watch/unwatch,\nport hotplug, AT session, exchange, and configure serial ports on desktop and Android.\n","permissions":["allow-managed-ports","allow-available-ports","allow-watch-ports","allow-unwatch-ports","allow-cancel-read","allow-close","allow-close-all","allow-force-close","allow-open","allow-read","allow-read-binary","allow-write","allow-write-binary","allow-capabilities","allow-watch","allow-unwatch","allow-bytes-to-read","allow-bytes-to-write","allow-clear-break","allow-clear-buffer","allow-read-carrier-detect","allow-read-clear-to-send","allow-read-data-set-ready","allow-read-ring-indicator","allow-set-baud-rate","allow-set-break","allow-set-data-bits","allow-set-flow-control","allow-set-parity","allow-set-stop-bits","allow-set-timeout","allow-write-data-terminal-ready","allow-write-request-to-send","allow-set-log-level","allow-get-log-level","allow-exchange","allow-exchange-binary","allow-cancel-exchange","allow-at","allow-at-phases","allow-send-sms-pdu","allow-configure-at-session","allow-enable-mux","allow-open-mux-channel","allow-disable-mux"]},"permissions":{"allow-at":{"identifier":"allow-at","description":"Enables the at command without any pre-configured scope.","commands":{"allow":["at"],"deny":[]}},"allow-at-phases":{"identifier":"allow-at-phases","description":"Enables the at_phases command without any pre-configured scope.","commands":{"allow":["at_phases"],"deny":[]}},"allow-available-ports":{"identifier":"allow-available-ports","description":"Enables the available_ports command without any pre-configured scope.","commands":{"allow":["available_ports"],"deny":[]}},"allow-bytes-to-read":{"identifier":"allow-bytes-to-read","description":"Enables the bytes_to_read command without any pre-configured scope.","commands":{"allow":["bytes_to_read"],"deny":[]}},"allow-bytes-to-write":{"identifier":"allow-bytes-to-write","description":"Enables the bytes_to_write command without any pre-configured scope.","commands":{"allow":["bytes_to_write"],"deny":[]}},"allow-cancel-exchange":{"identifier":"allow-cancel-exchange","description":"Enables the cancel_exchange command without any pre-configured scope.","commands":{"allow":["cancel_exchange"],"deny":[]}},"allow-cancel-read":{"identifier":"allow-cancel-read","description":"Enables the cancel_read command without any pre-configured scope.","commands":{"allow":["cancel_read"],"deny":[]}},"allow-capabilities":{"identifier":"allow-capabilities","description":"Enables the capabilities command without any pre-configured scope.","commands":{"allow":["capabilities"],"deny":[]}},"allow-clear-break":{"identifier":"allow-clear-break","description":"Enables the clear_break command without any pre-configured scope.","commands":{"allow":["clear_break"],"deny":[]}},"allow-clear-buffer":{"identifier":"allow-clear-buffer","description":"Enables the clear_buffer command without any pre-configured scope.","commands":{"allow":["clear_buffer"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-close-all":{"identifier":"allow-close-all","description":"Enables the close_all command without any pre-configured scope.","commands":{"allow":["close_all"],"deny":[]}},"allow-configure-at-session":{"identifier":"allow-configure-at-session","description":"Enables the configure_at_session command without any pre-configured scope.","commands":{"allow":["configure_at_session"],"deny":[]}},"allow-disable-mux":{"identifier":"allow-disable-mux","description":"Enables the disable_mux command without any pre-configured scope.","commands":{"allow":["disable_mux"],"deny":[]}},"allow-enable-mux":{"identifier":"allow-enable-mux","description":"Enables the enable_mux command without any pre-configured scope.","commands":{"allow":["enable_mux"],"deny":[]}},"allow-exchange":{"identifier":"allow-exchange","description":"Enables the exchange command without any pre-configured scope.","commands":{"allow":["exchange"],"deny":[]}},"allow-exchange-binary":{"identifier":"allow-exchange-binary","description":"Enables the exchange_binary command without any pre-configured scope.","commands":{"allow":["exchange_binary"],"deny":[]}},"allow-force-close":{"identifier":"allow-force-close","description":"Enables the force_close command without any pre-configured scope.","commands":{"allow":["force_close"],"deny":[]}},"allow-get-log-level":{"identifier":"allow-get-log-level","description":"Enables the get_log_level command without any pre-configured scope.","commands":{"allow":["get_log_level"],"deny":[]}},"allow-managed-ports":{"identifier":"allow-managed-ports","description":"Enables the managed_ports command without any pre-configured scope.","commands":{"allow":["managed_ports"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-open-mux-channel":{"identifier":"allow-open-mux-channel","description":"Enables the open_mux_channel command without any pre-configured scope.","commands":{"allow":["open_mux_channel"],"deny":[]}},"allow-read":{"identifier":"allow-read","description":"Enables the read command without any pre-configured scope.","commands":{"allow":["read"],"deny":[]}},"allow-read-binary":{"identifier":"allow-read-binary","description":"Enables the read_binary command without any pre-configured scope.","commands":{"allow":["read_binary"],"deny":[]}},"allow-read-carrier-detect":{"identifier":"allow-read-carrier-detect","description":"Enables the read_carrier_detect command without any pre-configured scope.","commands":{"allow":["read_carrier_detect"],"deny":[]}},"allow-read-clear-to-send":{"identifier":"allow-read-clear-to-send","description":"Enables the read_clear_to_send command without any pre-configured scope.","commands":{"allow":["read_clear_to_send"],"deny":[]}},"allow-read-data-set-ready":{"identifier":"allow-read-data-set-ready","description":"Enables the read_data_set_ready command without any pre-configured scope.","commands":{"allow":["read_data_set_ready"],"deny":[]}},"allow-read-ring-indicator":{"identifier":"allow-read-ring-indicator","description":"Enables the read_ring_indicator command without any pre-configured scope.","commands":{"allow":["read_ring_indicator"],"deny":[]}},"allow-send-sms-pdu":{"identifier":"allow-send-sms-pdu","description":"Enables the send_sms_pdu command without any pre-configured scope.","commands":{"allow":["send_sms_pdu"],"deny":[]}},"allow-set-baud-rate":{"identifier":"allow-set-baud-rate","description":"Enables the set_baud_rate command without any pre-configured scope.","commands":{"allow":["set_baud_rate"],"deny":[]}},"allow-set-break":{"identifier":"allow-set-break","description":"Enables the set_break command without any pre-configured scope.","commands":{"allow":["set_break"],"deny":[]}},"allow-set-data-bits":{"identifier":"allow-set-data-bits","description":"Enables the set_data_bits command without any pre-configured scope.","commands":{"allow":["set_data_bits"],"deny":[]}},"allow-set-flow-control":{"identifier":"allow-set-flow-control","description":"Enables the set_flow_control command without any pre-configured scope.","commands":{"allow":["set_flow_control"],"deny":[]}},"allow-set-log-level":{"identifier":"allow-set-log-level","description":"Enables the set_log_level command without any pre-configured scope.","commands":{"allow":["set_log_level"],"deny":[]}},"allow-set-parity":{"identifier":"allow-set-parity","description":"Enables the set_parity command without any pre-configured scope.","commands":{"allow":["set_parity"],"deny":[]}},"allow-set-stop-bits":{"identifier":"allow-set-stop-bits","description":"Enables the set_stop_bits command without any pre-configured scope.","commands":{"allow":["set_stop_bits"],"deny":[]}},"allow-set-timeout":{"identifier":"allow-set-timeout","description":"Enables the set_timeout command without any pre-configured scope.","commands":{"allow":["set_timeout"],"deny":[]}},"allow-unwatch":{"identifier":"allow-unwatch","description":"Enables the unwatch command without any pre-configured scope.","commands":{"allow":["unwatch"],"deny":[]}},"allow-unwatch-ports":{"identifier":"allow-unwatch-ports","description":"Enables the unwatch_ports command without any pre-configured scope.","commands":{"allow":["unwatch_ports"],"deny":[]}},"allow-watch":{"identifier":"allow-watch","description":"Enables the watch command without any pre-configured scope.","commands":{"allow":["watch"],"deny":[]}},"allow-watch-ports":{"identifier":"allow-watch-ports","description":"Enables the watch_ports command without any pre-configured scope.","commands":{"allow":["watch_ports"],"deny":[]}},"allow-write":{"identifier":"allow-write","description":"Enables the write command without any pre-configured scope.","commands":{"allow":["write"],"deny":[]}},"allow-write-binary":{"identifier":"allow-write-binary","description":"Enables the write_binary command without any pre-configured scope.","commands":{"allow":["write_binary"],"deny":[]}},"allow-write-data-terminal-ready":{"identifier":"allow-write-data-terminal-ready","description":"Enables the write_data_terminal_ready command without any pre-configured scope.","commands":{"allow":["write_data_terminal_ready"],"deny":[]}},"allow-write-request-to-send":{"identifier":"allow-write-request-to-send","description":"Enables the write_request_to_send command without any pre-configured scope.","commands":{"allow":["write_request_to_send"],"deny":[]}},"deny-at":{"identifier":"deny-at","description":"Denies the at command without any pre-configured scope.","commands":{"allow":[],"deny":["at"]}},"deny-at-phases":{"identifier":"deny-at-phases","description":"Denies the at_phases command without any pre-configured scope.","commands":{"allow":[],"deny":["at_phases"]}},"deny-available-ports":{"identifier":"deny-available-ports","description":"Denies the available_ports command without any pre-configured scope.","commands":{"allow":[],"deny":["available_ports"]}},"deny-bytes-to-read":{"identifier":"deny-bytes-to-read","description":"Denies the bytes_to_read command without any pre-configured scope.","commands":{"allow":[],"deny":["bytes_to_read"]}},"deny-bytes-to-write":{"identifier":"deny-bytes-to-write","description":"Denies the bytes_to_write command without any pre-configured scope.","commands":{"allow":[],"deny":["bytes_to_write"]}},"deny-cancel-exchange":{"identifier":"deny-cancel-exchange","description":"Denies the cancel_exchange command without any pre-configured scope.","commands":{"allow":[],"deny":["cancel_exchange"]}},"deny-cancel-read":{"identifier":"deny-cancel-read","description":"Denies the cancel_read command without any pre-configured scope.","commands":{"allow":[],"deny":["cancel_read"]}},"deny-capabilities":{"identifier":"deny-capabilities","description":"Denies the capabilities command without any pre-configured scope.","commands":{"allow":[],"deny":["capabilities"]}},"deny-clear-break":{"identifier":"deny-clear-break","description":"Denies the clear_break command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_break"]}},"deny-clear-buffer":{"identifier":"deny-clear-buffer","description":"Denies the clear_buffer command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_buffer"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-close-all":{"identifier":"deny-close-all","description":"Denies the close_all command without any pre-configured scope.","commands":{"allow":[],"deny":["close_all"]}},"deny-configure-at-session":{"identifier":"deny-configure-at-session","description":"Denies the configure_at_session command without any pre-configured scope.","commands":{"allow":[],"deny":["configure_at_session"]}},"deny-disable-mux":{"identifier":"deny-disable-mux","description":"Denies the disable_mux command without any pre-configured scope.","commands":{"allow":[],"deny":["disable_mux"]}},"deny-enable-mux":{"identifier":"deny-enable-mux","description":"Denies the enable_mux command without any pre-configured scope.","commands":{"allow":[],"deny":["enable_mux"]}},"deny-exchange":{"identifier":"deny-exchange","description":"Denies the exchange command without any pre-configured scope.","commands":{"allow":[],"deny":["exchange"]}},"deny-exchange-binary":{"identifier":"deny-exchange-binary","description":"Denies the exchange_binary command without any pre-configured scope.","commands":{"allow":[],"deny":["exchange_binary"]}},"deny-force-close":{"identifier":"deny-force-close","description":"Denies the force_close command without any pre-configured scope.","commands":{"allow":[],"deny":["force_close"]}},"deny-get-log-level":{"identifier":"deny-get-log-level","description":"Denies the get_log_level command without any pre-configured scope.","commands":{"allow":[],"deny":["get_log_level"]}},"deny-managed-ports":{"identifier":"deny-managed-ports","description":"Denies the managed_ports command without any pre-configured scope.","commands":{"allow":[],"deny":["managed_ports"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-open-mux-channel":{"identifier":"deny-open-mux-channel","description":"Denies the open_mux_channel command without any pre-configured scope.","commands":{"allow":[],"deny":["open_mux_channel"]}},"deny-read":{"identifier":"deny-read","description":"Denies the read command without any pre-configured scope.","commands":{"allow":[],"deny":["read"]}},"deny-read-binary":{"identifier":"deny-read-binary","description":"Denies the read_binary command without any pre-configured scope.","commands":{"allow":[],"deny":["read_binary"]}},"deny-read-carrier-detect":{"identifier":"deny-read-carrier-detect","description":"Denies the read_carrier_detect command without any pre-configured scope.","commands":{"allow":[],"deny":["read_carrier_detect"]}},"deny-read-clear-to-send":{"identifier":"deny-read-clear-to-send","description":"Denies the read_clear_to_send command without any pre-configured scope.","commands":{"allow":[],"deny":["read_clear_to_send"]}},"deny-read-data-set-ready":{"identifier":"deny-read-data-set-ready","description":"Denies the read_data_set_ready command without any pre-configured scope.","commands":{"allow":[],"deny":["read_data_set_ready"]}},"deny-read-ring-indicator":{"identifier":"deny-read-ring-indicator","description":"Denies the read_ring_indicator command without any pre-configured scope.","commands":{"allow":[],"deny":["read_ring_indicator"]}},"deny-send-sms-pdu":{"identifier":"deny-send-sms-pdu","description":"Denies the send_sms_pdu command without any pre-configured scope.","commands":{"allow":[],"deny":["send_sms_pdu"]}},"deny-set-baud-rate":{"identifier":"deny-set-baud-rate","description":"Denies the set_baud_rate command without any pre-configured scope.","commands":{"allow":[],"deny":["set_baud_rate"]}},"deny-set-break":{"identifier":"deny-set-break","description":"Denies the set_break command without any pre-configured scope.","commands":{"allow":[],"deny":["set_break"]}},"deny-set-data-bits":{"identifier":"deny-set-data-bits","description":"Denies the set_data_bits command without any pre-configured scope.","commands":{"allow":[],"deny":["set_data_bits"]}},"deny-set-flow-control":{"identifier":"deny-set-flow-control","description":"Denies the set_flow_control command without any pre-configured scope.","commands":{"allow":[],"deny":["set_flow_control"]}},"deny-set-log-level":{"identifier":"deny-set-log-level","description":"Denies the set_log_level command without any pre-configured scope.","commands":{"allow":[],"deny":["set_log_level"]}},"deny-set-parity":{"identifier":"deny-set-parity","description":"Denies the set_parity command without any pre-configured scope.","commands":{"allow":[],"deny":["set_parity"]}},"deny-set-stop-bits":{"identifier":"deny-set-stop-bits","description":"Denies the set_stop_bits command without any pre-configured scope.","commands":{"allow":[],"deny":["set_stop_bits"]}},"deny-set-timeout":{"identifier":"deny-set-timeout","description":"Denies the set_timeout command without any pre-configured scope.","commands":{"allow":[],"deny":["set_timeout"]}},"deny-unwatch":{"identifier":"deny-unwatch","description":"Denies the unwatch command without any pre-configured scope.","commands":{"allow":[],"deny":["unwatch"]}},"deny-unwatch-ports":{"identifier":"deny-unwatch-ports","description":"Denies the unwatch_ports command without any pre-configured scope.","commands":{"allow":[],"deny":["unwatch_ports"]}},"deny-watch":{"identifier":"deny-watch","description":"Denies the watch command without any pre-configured scope.","commands":{"allow":[],"deny":["watch"]}},"deny-watch-ports":{"identifier":"deny-watch-ports","description":"Denies the watch_ports command without any pre-configured scope.","commands":{"allow":[],"deny":["watch_ports"]}},"deny-write":{"identifier":"deny-write","description":"Denies the write command without any pre-configured scope.","commands":{"allow":[],"deny":["write"]}},"deny-write-binary":{"identifier":"deny-write-binary","description":"Denies the write_binary command without any pre-configured scope.","commands":{"allow":[],"deny":["write_binary"]}},"deny-write-data-terminal-ready":{"identifier":"deny-write-data-terminal-ready","description":"Denies the write_data_terminal_ready command without any pre-configured scope.","commands":{"allow":[],"deny":["write_data_terminal_ready"]}},"deny-write-request-to-send":{"identifier":"deny-write-request-to-send","description":"Denies the write_request_to_send command without any pre-configured scope.","commands":{"allow":[],"deny":["write_request_to_send"]}}},"permission_sets":{},"global_scope_schema":null}} \ No newline at end of file diff --git a/examples/serialport-test/src-tauri/gen/schemas/android-schema.json b/examples/serialport-test/src-tauri/gen/schemas/android-schema.json index 521f154f..b55dfdda 100644 --- a/examples/serialport-test/src-tauri/gen/schemas/android-schema.json +++ b/examples/serialport-test/src-tauri/gen/schemas/android-schema.json @@ -2085,22 +2085,28 @@ "markdownDescription": "Denies the unminimize command without any pre-configured scope." }, { - "description": "# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`", + "description": "Default permissions for the serialplugin: enumerate, open, read/write, watch/unwatch,\nport hotplug, AT session, exchange, and configure serial ports on desktop and Android.\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-watch-ports`\n- `allow-unwatch-ports`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-write`\n- `allow-write-binary`\n- `allow-capabilities`\n- `allow-watch`\n- `allow-unwatch`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-read-carrier-detect`\n- `allow-read-clear-to-send`\n- `allow-read-data-set-ready`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-write-data-terminal-ready`\n- `allow-write-request-to-send`\n- `allow-set-log-level`\n- `allow-get-log-level`\n- `allow-exchange`\n- `allow-exchange-binary`\n- `allow-cancel-exchange`\n- `allow-at`\n- `allow-at-phases`\n- `allow-send-sms-pdu`\n- `allow-configure-at-session`\n- `allow-enable-mux`\n- `allow-open-mux-channel`\n- `allow-disable-mux`", "type": "string", "const": "serialplugin:default", - "markdownDescription": "# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`" + "markdownDescription": "Default permissions for the serialplugin: enumerate, open, read/write, watch/unwatch,\nport hotplug, AT session, exchange, and configure serial ports on desktop and Android.\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-watch-ports`\n- `allow-unwatch-ports`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-write`\n- `allow-write-binary`\n- `allow-capabilities`\n- `allow-watch`\n- `allow-unwatch`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-read-carrier-detect`\n- `allow-read-clear-to-send`\n- `allow-read-data-set-ready`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-write-data-terminal-ready`\n- `allow-write-request-to-send`\n- `allow-set-log-level`\n- `allow-get-log-level`\n- `allow-exchange`\n- `allow-exchange-binary`\n- `allow-cancel-exchange`\n- `allow-at`\n- `allow-at-phases`\n- `allow-send-sms-pdu`\n- `allow-configure-at-session`\n- `allow-enable-mux`\n- `allow-open-mux-channel`\n- `allow-disable-mux`" }, { - "description": "Enables the available_ports command without any pre-configured scope.", + "description": "Enables the at command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-available-ports", - "markdownDescription": "Enables the available_ports command without any pre-configured scope." + "const": "serialplugin:allow-at", + "markdownDescription": "Enables the at command without any pre-configured scope." }, { - "description": "Enables the available_ports_direct command without any pre-configured scope.", + "description": "Enables the at_phases command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-available-ports-direct", - "markdownDescription": "Enables the available_ports_direct command without any pre-configured scope." + "const": "serialplugin:allow-at-phases", + "markdownDescription": "Enables the at_phases command without any pre-configured scope." + }, + { + "description": "Enables the available_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-available-ports", + "markdownDescription": "Enables the available_ports command without any pre-configured scope." }, { "description": "Enables the bytes_to_read command without any pre-configured scope.", @@ -2114,12 +2120,24 @@ "const": "serialplugin:allow-bytes-to-write", "markdownDescription": "Enables the bytes_to_write command without any pre-configured scope." }, + { + "description": "Enables the cancel_exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-cancel-exchange", + "markdownDescription": "Enables the cancel_exchange command without any pre-configured scope." + }, { "description": "Enables the cancel_read command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-cancel-read", "markdownDescription": "Enables the cancel_read command without any pre-configured scope." }, + { + "description": "Enables the capabilities command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-capabilities", + "markdownDescription": "Enables the capabilities command without any pre-configured scope." + }, { "description": "Enables the clear_break command without any pre-configured scope.", "type": "string", @@ -2144,6 +2162,36 @@ "const": "serialplugin:allow-close-all", "markdownDescription": "Enables the close_all command without any pre-configured scope." }, + { + "description": "Enables the configure_at_session command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-configure-at-session", + "markdownDescription": "Enables the configure_at_session command without any pre-configured scope." + }, + { + "description": "Enables the disable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-disable-mux", + "markdownDescription": "Enables the disable_mux command without any pre-configured scope." + }, + { + "description": "Enables the enable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-enable-mux", + "markdownDescription": "Enables the enable_mux command without any pre-configured scope." + }, + { + "description": "Enables the exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-exchange", + "markdownDescription": "Enables the exchange command without any pre-configured scope." + }, + { + "description": "Enables the exchange_binary command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-exchange-binary", + "markdownDescription": "Enables the exchange_binary command without any pre-configured scope." + }, { "description": "Enables the force_close command without any pre-configured scope.", "type": "string", @@ -2168,6 +2216,12 @@ "const": "serialplugin:allow-open", "markdownDescription": "Enables the open command without any pre-configured scope." }, + { + "description": "Enables the open_mux_channel command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-open-mux-channel", + "markdownDescription": "Enables the open_mux_channel command without any pre-configured scope." + }, { "description": "Enables the read command without any pre-configured scope.", "type": "string", @@ -2186,54 +2240,30 @@ "const": "serialplugin:allow-read-carrier-detect", "markdownDescription": "Enables the read_carrier_detect command without any pre-configured scope." }, - { - "description": "Enables the read_cd command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-cd", - "markdownDescription": "Enables the read_cd command without any pre-configured scope." - }, { "description": "Enables the read_clear_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-clear-to-send", "markdownDescription": "Enables the read_clear_to_send command without any pre-configured scope." }, - { - "description": "Enables the read_cts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-cts", - "markdownDescription": "Enables the read_cts command without any pre-configured scope." - }, { "description": "Enables the read_data_set_ready command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-data-set-ready", "markdownDescription": "Enables the read_data_set_ready command without any pre-configured scope." }, - { - "description": "Enables the read_dsr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-dsr", - "markdownDescription": "Enables the read_dsr command without any pre-configured scope." - }, - { - "description": "Enables the read_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-dtr", - "markdownDescription": "Enables the read_dtr command without any pre-configured scope." - }, - { - "description": "Enables the read_ri command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-ri", - "markdownDescription": "Enables the read_ri command without any pre-configured scope." - }, { "description": "Enables the read_ring_indicator command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-ring-indicator", "markdownDescription": "Enables the read_ring_indicator command without any pre-configured scope." }, + { + "description": "Enables the send_sms_pdu command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-send-sms-pdu", + "markdownDescription": "Enables the send_sms_pdu command without any pre-configured scope." + }, { "description": "Enables the set_baud_rate command without any pre-configured scope.", "type": "string", @@ -2283,16 +2313,28 @@ "markdownDescription": "Enables the set_timeout command without any pre-configured scope." }, { - "description": "Enables the start_listening command without any pre-configured scope.", + "description": "Enables the unwatch command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-start-listening", - "markdownDescription": "Enables the start_listening command without any pre-configured scope." + "const": "serialplugin:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." }, { - "description": "Enables the stop_listening command without any pre-configured scope.", + "description": "Enables the unwatch_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-stop-listening", - "markdownDescription": "Enables the stop_listening command without any pre-configured scope." + "const": "serialplugin:allow-unwatch-ports", + "markdownDescription": "Enables the unwatch_ports command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the watch_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-watch-ports", + "markdownDescription": "Enables the watch_ports command without any pre-configured scope." }, { "description": "Enables the write command without any pre-configured scope.", @@ -2312,12 +2354,6 @@ "const": "serialplugin:allow-write-data-terminal-ready", "markdownDescription": "Enables the write_data_terminal_ready command without any pre-configured scope." }, - { - "description": "Enables the write_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-write-dtr", - "markdownDescription": "Enables the write_dtr command without any pre-configured scope." - }, { "description": "Enables the write_request_to_send command without any pre-configured scope.", "type": "string", @@ -2325,22 +2361,22 @@ "markdownDescription": "Enables the write_request_to_send command without any pre-configured scope." }, { - "description": "Enables the write_rts command without any pre-configured scope.", + "description": "Denies the at command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-write-rts", - "markdownDescription": "Enables the write_rts command without any pre-configured scope." + "const": "serialplugin:deny-at", + "markdownDescription": "Denies the at command without any pre-configured scope." }, { - "description": "Denies the available_ports command without any pre-configured scope.", + "description": "Denies the at_phases command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-available-ports", - "markdownDescription": "Denies the available_ports command without any pre-configured scope." + "const": "serialplugin:deny-at-phases", + "markdownDescription": "Denies the at_phases command without any pre-configured scope." }, { - "description": "Denies the available_ports_direct command without any pre-configured scope.", + "description": "Denies the available_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-available-ports-direct", - "markdownDescription": "Denies the available_ports_direct command without any pre-configured scope." + "const": "serialplugin:deny-available-ports", + "markdownDescription": "Denies the available_ports command without any pre-configured scope." }, { "description": "Denies the bytes_to_read command without any pre-configured scope.", @@ -2354,12 +2390,24 @@ "const": "serialplugin:deny-bytes-to-write", "markdownDescription": "Denies the bytes_to_write command without any pre-configured scope." }, + { + "description": "Denies the cancel_exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-cancel-exchange", + "markdownDescription": "Denies the cancel_exchange command without any pre-configured scope." + }, { "description": "Denies the cancel_read command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-cancel-read", "markdownDescription": "Denies the cancel_read command without any pre-configured scope." }, + { + "description": "Denies the capabilities command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-capabilities", + "markdownDescription": "Denies the capabilities command without any pre-configured scope." + }, { "description": "Denies the clear_break command without any pre-configured scope.", "type": "string", @@ -2384,6 +2432,36 @@ "const": "serialplugin:deny-close-all", "markdownDescription": "Denies the close_all command without any pre-configured scope." }, + { + "description": "Denies the configure_at_session command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-configure-at-session", + "markdownDescription": "Denies the configure_at_session command without any pre-configured scope." + }, + { + "description": "Denies the disable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-disable-mux", + "markdownDescription": "Denies the disable_mux command without any pre-configured scope." + }, + { + "description": "Denies the enable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-enable-mux", + "markdownDescription": "Denies the enable_mux command without any pre-configured scope." + }, + { + "description": "Denies the exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-exchange", + "markdownDescription": "Denies the exchange command without any pre-configured scope." + }, + { + "description": "Denies the exchange_binary command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-exchange-binary", + "markdownDescription": "Denies the exchange_binary command without any pre-configured scope." + }, { "description": "Denies the force_close command without any pre-configured scope.", "type": "string", @@ -2408,6 +2486,12 @@ "const": "serialplugin:deny-open", "markdownDescription": "Denies the open command without any pre-configured scope." }, + { + "description": "Denies the open_mux_channel command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-open-mux-channel", + "markdownDescription": "Denies the open_mux_channel command without any pre-configured scope." + }, { "description": "Denies the read command without any pre-configured scope.", "type": "string", @@ -2426,54 +2510,30 @@ "const": "serialplugin:deny-read-carrier-detect", "markdownDescription": "Denies the read_carrier_detect command without any pre-configured scope." }, - { - "description": "Denies the read_cd command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-cd", - "markdownDescription": "Denies the read_cd command without any pre-configured scope." - }, { "description": "Denies the read_clear_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-clear-to-send", "markdownDescription": "Denies the read_clear_to_send command without any pre-configured scope." }, - { - "description": "Denies the read_cts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-cts", - "markdownDescription": "Denies the read_cts command without any pre-configured scope." - }, { "description": "Denies the read_data_set_ready command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-data-set-ready", "markdownDescription": "Denies the read_data_set_ready command without any pre-configured scope." }, - { - "description": "Denies the read_dsr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-dsr", - "markdownDescription": "Denies the read_dsr command without any pre-configured scope." - }, - { - "description": "Denies the read_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-dtr", - "markdownDescription": "Denies the read_dtr command without any pre-configured scope." - }, - { - "description": "Denies the read_ri command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-ri", - "markdownDescription": "Denies the read_ri command without any pre-configured scope." - }, { "description": "Denies the read_ring_indicator command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-ring-indicator", "markdownDescription": "Denies the read_ring_indicator command without any pre-configured scope." }, + { + "description": "Denies the send_sms_pdu command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-send-sms-pdu", + "markdownDescription": "Denies the send_sms_pdu command without any pre-configured scope." + }, { "description": "Denies the set_baud_rate command without any pre-configured scope.", "type": "string", @@ -2523,16 +2583,28 @@ "markdownDescription": "Denies the set_timeout command without any pre-configured scope." }, { - "description": "Denies the start_listening command without any pre-configured scope.", + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the unwatch_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-start-listening", - "markdownDescription": "Denies the start_listening command without any pre-configured scope." + "const": "serialplugin:deny-unwatch-ports", + "markdownDescription": "Denies the unwatch_ports command without any pre-configured scope." }, { - "description": "Denies the stop_listening command without any pre-configured scope.", + "description": "Denies the watch command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-stop-listening", - "markdownDescription": "Denies the stop_listening command without any pre-configured scope." + "const": "serialplugin:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "Denies the watch_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-watch-ports", + "markdownDescription": "Denies the watch_ports command without any pre-configured scope." }, { "description": "Denies the write command without any pre-configured scope.", @@ -2552,23 +2624,11 @@ "const": "serialplugin:deny-write-data-terminal-ready", "markdownDescription": "Denies the write_data_terminal_ready command without any pre-configured scope." }, - { - "description": "Denies the write_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-write-dtr", - "markdownDescription": "Denies the write_dtr command without any pre-configured scope." - }, { "description": "Denies the write_request_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-write-request-to-send", "markdownDescription": "Denies the write_request_to_send command without any pre-configured scope." - }, - { - "description": "Denies the write_rts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-write-rts", - "markdownDescription": "Denies the write_rts command without any pre-configured scope." } ] }, diff --git a/examples/serialport-test/src-tauri/gen/schemas/desktop-schema.json b/examples/serialport-test/src-tauri/gen/schemas/desktop-schema.json index 521f154f..b55dfdda 100644 --- a/examples/serialport-test/src-tauri/gen/schemas/desktop-schema.json +++ b/examples/serialport-test/src-tauri/gen/schemas/desktop-schema.json @@ -2085,22 +2085,28 @@ "markdownDescription": "Denies the unminimize command without any pre-configured scope." }, { - "description": "# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`", + "description": "Default permissions for the serialplugin: enumerate, open, read/write, watch/unwatch,\nport hotplug, AT session, exchange, and configure serial ports on desktop and Android.\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-watch-ports`\n- `allow-unwatch-ports`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-write`\n- `allow-write-binary`\n- `allow-capabilities`\n- `allow-watch`\n- `allow-unwatch`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-read-carrier-detect`\n- `allow-read-clear-to-send`\n- `allow-read-data-set-ready`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-write-data-terminal-ready`\n- `allow-write-request-to-send`\n- `allow-set-log-level`\n- `allow-get-log-level`\n- `allow-exchange`\n- `allow-exchange-binary`\n- `allow-cancel-exchange`\n- `allow-at`\n- `allow-at-phases`\n- `allow-send-sms-pdu`\n- `allow-configure-at-session`\n- `allow-enable-mux`\n- `allow-open-mux-channel`\n- `allow-disable-mux`", "type": "string", "const": "serialplugin:default", - "markdownDescription": "# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`" + "markdownDescription": "Default permissions for the serialplugin: enumerate, open, read/write, watch/unwatch,\nport hotplug, AT session, exchange, and configure serial ports on desktop and Android.\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-watch-ports`\n- `allow-unwatch-ports`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-write`\n- `allow-write-binary`\n- `allow-capabilities`\n- `allow-watch`\n- `allow-unwatch`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-read-carrier-detect`\n- `allow-read-clear-to-send`\n- `allow-read-data-set-ready`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-write-data-terminal-ready`\n- `allow-write-request-to-send`\n- `allow-set-log-level`\n- `allow-get-log-level`\n- `allow-exchange`\n- `allow-exchange-binary`\n- `allow-cancel-exchange`\n- `allow-at`\n- `allow-at-phases`\n- `allow-send-sms-pdu`\n- `allow-configure-at-session`\n- `allow-enable-mux`\n- `allow-open-mux-channel`\n- `allow-disable-mux`" }, { - "description": "Enables the available_ports command without any pre-configured scope.", + "description": "Enables the at command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-available-ports", - "markdownDescription": "Enables the available_ports command without any pre-configured scope." + "const": "serialplugin:allow-at", + "markdownDescription": "Enables the at command without any pre-configured scope." }, { - "description": "Enables the available_ports_direct command without any pre-configured scope.", + "description": "Enables the at_phases command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-available-ports-direct", - "markdownDescription": "Enables the available_ports_direct command without any pre-configured scope." + "const": "serialplugin:allow-at-phases", + "markdownDescription": "Enables the at_phases command without any pre-configured scope." + }, + { + "description": "Enables the available_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-available-ports", + "markdownDescription": "Enables the available_ports command without any pre-configured scope." }, { "description": "Enables the bytes_to_read command without any pre-configured scope.", @@ -2114,12 +2120,24 @@ "const": "serialplugin:allow-bytes-to-write", "markdownDescription": "Enables the bytes_to_write command without any pre-configured scope." }, + { + "description": "Enables the cancel_exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-cancel-exchange", + "markdownDescription": "Enables the cancel_exchange command without any pre-configured scope." + }, { "description": "Enables the cancel_read command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-cancel-read", "markdownDescription": "Enables the cancel_read command without any pre-configured scope." }, + { + "description": "Enables the capabilities command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-capabilities", + "markdownDescription": "Enables the capabilities command without any pre-configured scope." + }, { "description": "Enables the clear_break command without any pre-configured scope.", "type": "string", @@ -2144,6 +2162,36 @@ "const": "serialplugin:allow-close-all", "markdownDescription": "Enables the close_all command without any pre-configured scope." }, + { + "description": "Enables the configure_at_session command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-configure-at-session", + "markdownDescription": "Enables the configure_at_session command without any pre-configured scope." + }, + { + "description": "Enables the disable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-disable-mux", + "markdownDescription": "Enables the disable_mux command without any pre-configured scope." + }, + { + "description": "Enables the enable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-enable-mux", + "markdownDescription": "Enables the enable_mux command without any pre-configured scope." + }, + { + "description": "Enables the exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-exchange", + "markdownDescription": "Enables the exchange command without any pre-configured scope." + }, + { + "description": "Enables the exchange_binary command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-exchange-binary", + "markdownDescription": "Enables the exchange_binary command without any pre-configured scope." + }, { "description": "Enables the force_close command without any pre-configured scope.", "type": "string", @@ -2168,6 +2216,12 @@ "const": "serialplugin:allow-open", "markdownDescription": "Enables the open command without any pre-configured scope." }, + { + "description": "Enables the open_mux_channel command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-open-mux-channel", + "markdownDescription": "Enables the open_mux_channel command without any pre-configured scope." + }, { "description": "Enables the read command without any pre-configured scope.", "type": "string", @@ -2186,54 +2240,30 @@ "const": "serialplugin:allow-read-carrier-detect", "markdownDescription": "Enables the read_carrier_detect command without any pre-configured scope." }, - { - "description": "Enables the read_cd command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-cd", - "markdownDescription": "Enables the read_cd command without any pre-configured scope." - }, { "description": "Enables the read_clear_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-clear-to-send", "markdownDescription": "Enables the read_clear_to_send command without any pre-configured scope." }, - { - "description": "Enables the read_cts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-cts", - "markdownDescription": "Enables the read_cts command without any pre-configured scope." - }, { "description": "Enables the read_data_set_ready command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-data-set-ready", "markdownDescription": "Enables the read_data_set_ready command without any pre-configured scope." }, - { - "description": "Enables the read_dsr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-dsr", - "markdownDescription": "Enables the read_dsr command without any pre-configured scope." - }, - { - "description": "Enables the read_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-dtr", - "markdownDescription": "Enables the read_dtr command without any pre-configured scope." - }, - { - "description": "Enables the read_ri command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-ri", - "markdownDescription": "Enables the read_ri command without any pre-configured scope." - }, { "description": "Enables the read_ring_indicator command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-ring-indicator", "markdownDescription": "Enables the read_ring_indicator command without any pre-configured scope." }, + { + "description": "Enables the send_sms_pdu command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-send-sms-pdu", + "markdownDescription": "Enables the send_sms_pdu command without any pre-configured scope." + }, { "description": "Enables the set_baud_rate command without any pre-configured scope.", "type": "string", @@ -2283,16 +2313,28 @@ "markdownDescription": "Enables the set_timeout command without any pre-configured scope." }, { - "description": "Enables the start_listening command without any pre-configured scope.", + "description": "Enables the unwatch command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-start-listening", - "markdownDescription": "Enables the start_listening command without any pre-configured scope." + "const": "serialplugin:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." }, { - "description": "Enables the stop_listening command without any pre-configured scope.", + "description": "Enables the unwatch_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-stop-listening", - "markdownDescription": "Enables the stop_listening command without any pre-configured scope." + "const": "serialplugin:allow-unwatch-ports", + "markdownDescription": "Enables the unwatch_ports command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the watch_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-watch-ports", + "markdownDescription": "Enables the watch_ports command without any pre-configured scope." }, { "description": "Enables the write command without any pre-configured scope.", @@ -2312,12 +2354,6 @@ "const": "serialplugin:allow-write-data-terminal-ready", "markdownDescription": "Enables the write_data_terminal_ready command without any pre-configured scope." }, - { - "description": "Enables the write_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-write-dtr", - "markdownDescription": "Enables the write_dtr command without any pre-configured scope." - }, { "description": "Enables the write_request_to_send command without any pre-configured scope.", "type": "string", @@ -2325,22 +2361,22 @@ "markdownDescription": "Enables the write_request_to_send command without any pre-configured scope." }, { - "description": "Enables the write_rts command without any pre-configured scope.", + "description": "Denies the at command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-write-rts", - "markdownDescription": "Enables the write_rts command without any pre-configured scope." + "const": "serialplugin:deny-at", + "markdownDescription": "Denies the at command without any pre-configured scope." }, { - "description": "Denies the available_ports command without any pre-configured scope.", + "description": "Denies the at_phases command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-available-ports", - "markdownDescription": "Denies the available_ports command without any pre-configured scope." + "const": "serialplugin:deny-at-phases", + "markdownDescription": "Denies the at_phases command without any pre-configured scope." }, { - "description": "Denies the available_ports_direct command without any pre-configured scope.", + "description": "Denies the available_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-available-ports-direct", - "markdownDescription": "Denies the available_ports_direct command without any pre-configured scope." + "const": "serialplugin:deny-available-ports", + "markdownDescription": "Denies the available_ports command without any pre-configured scope." }, { "description": "Denies the bytes_to_read command without any pre-configured scope.", @@ -2354,12 +2390,24 @@ "const": "serialplugin:deny-bytes-to-write", "markdownDescription": "Denies the bytes_to_write command without any pre-configured scope." }, + { + "description": "Denies the cancel_exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-cancel-exchange", + "markdownDescription": "Denies the cancel_exchange command without any pre-configured scope." + }, { "description": "Denies the cancel_read command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-cancel-read", "markdownDescription": "Denies the cancel_read command without any pre-configured scope." }, + { + "description": "Denies the capabilities command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-capabilities", + "markdownDescription": "Denies the capabilities command without any pre-configured scope." + }, { "description": "Denies the clear_break command without any pre-configured scope.", "type": "string", @@ -2384,6 +2432,36 @@ "const": "serialplugin:deny-close-all", "markdownDescription": "Denies the close_all command without any pre-configured scope." }, + { + "description": "Denies the configure_at_session command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-configure-at-session", + "markdownDescription": "Denies the configure_at_session command without any pre-configured scope." + }, + { + "description": "Denies the disable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-disable-mux", + "markdownDescription": "Denies the disable_mux command without any pre-configured scope." + }, + { + "description": "Denies the enable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-enable-mux", + "markdownDescription": "Denies the enable_mux command without any pre-configured scope." + }, + { + "description": "Denies the exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-exchange", + "markdownDescription": "Denies the exchange command without any pre-configured scope." + }, + { + "description": "Denies the exchange_binary command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-exchange-binary", + "markdownDescription": "Denies the exchange_binary command without any pre-configured scope." + }, { "description": "Denies the force_close command without any pre-configured scope.", "type": "string", @@ -2408,6 +2486,12 @@ "const": "serialplugin:deny-open", "markdownDescription": "Denies the open command without any pre-configured scope." }, + { + "description": "Denies the open_mux_channel command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-open-mux-channel", + "markdownDescription": "Denies the open_mux_channel command without any pre-configured scope." + }, { "description": "Denies the read command without any pre-configured scope.", "type": "string", @@ -2426,54 +2510,30 @@ "const": "serialplugin:deny-read-carrier-detect", "markdownDescription": "Denies the read_carrier_detect command without any pre-configured scope." }, - { - "description": "Denies the read_cd command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-cd", - "markdownDescription": "Denies the read_cd command without any pre-configured scope." - }, { "description": "Denies the read_clear_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-clear-to-send", "markdownDescription": "Denies the read_clear_to_send command without any pre-configured scope." }, - { - "description": "Denies the read_cts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-cts", - "markdownDescription": "Denies the read_cts command without any pre-configured scope." - }, { "description": "Denies the read_data_set_ready command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-data-set-ready", "markdownDescription": "Denies the read_data_set_ready command without any pre-configured scope." }, - { - "description": "Denies the read_dsr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-dsr", - "markdownDescription": "Denies the read_dsr command without any pre-configured scope." - }, - { - "description": "Denies the read_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-dtr", - "markdownDescription": "Denies the read_dtr command without any pre-configured scope." - }, - { - "description": "Denies the read_ri command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-ri", - "markdownDescription": "Denies the read_ri command without any pre-configured scope." - }, { "description": "Denies the read_ring_indicator command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-ring-indicator", "markdownDescription": "Denies the read_ring_indicator command without any pre-configured scope." }, + { + "description": "Denies the send_sms_pdu command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-send-sms-pdu", + "markdownDescription": "Denies the send_sms_pdu command without any pre-configured scope." + }, { "description": "Denies the set_baud_rate command without any pre-configured scope.", "type": "string", @@ -2523,16 +2583,28 @@ "markdownDescription": "Denies the set_timeout command without any pre-configured scope." }, { - "description": "Denies the start_listening command without any pre-configured scope.", + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the unwatch_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-start-listening", - "markdownDescription": "Denies the start_listening command without any pre-configured scope." + "const": "serialplugin:deny-unwatch-ports", + "markdownDescription": "Denies the unwatch_ports command without any pre-configured scope." }, { - "description": "Denies the stop_listening command without any pre-configured scope.", + "description": "Denies the watch command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-stop-listening", - "markdownDescription": "Denies the stop_listening command without any pre-configured scope." + "const": "serialplugin:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "Denies the watch_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-watch-ports", + "markdownDescription": "Denies the watch_ports command without any pre-configured scope." }, { "description": "Denies the write command without any pre-configured scope.", @@ -2552,23 +2624,11 @@ "const": "serialplugin:deny-write-data-terminal-ready", "markdownDescription": "Denies the write_data_terminal_ready command without any pre-configured scope." }, - { - "description": "Denies the write_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-write-dtr", - "markdownDescription": "Denies the write_dtr command without any pre-configured scope." - }, { "description": "Denies the write_request_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-write-request-to-send", "markdownDescription": "Denies the write_request_to_send command without any pre-configured scope." - }, - { - "description": "Denies the write_rts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-write-rts", - "markdownDescription": "Denies the write_rts command without any pre-configured scope." } ] }, diff --git a/examples/serialport-test/src-tauri/gen/schemas/macOS-schema.json b/examples/serialport-test/src-tauri/gen/schemas/macOS-schema.json index 521f154f..b55dfdda 100644 --- a/examples/serialport-test/src-tauri/gen/schemas/macOS-schema.json +++ b/examples/serialport-test/src-tauri/gen/schemas/macOS-schema.json @@ -2085,22 +2085,28 @@ "markdownDescription": "Denies the unminimize command without any pre-configured scope." }, { - "description": "# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`", + "description": "Default permissions for the serialplugin: enumerate, open, read/write, watch/unwatch,\nport hotplug, AT session, exchange, and configure serial ports on desktop and Android.\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-watch-ports`\n- `allow-unwatch-ports`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-write`\n- `allow-write-binary`\n- `allow-capabilities`\n- `allow-watch`\n- `allow-unwatch`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-read-carrier-detect`\n- `allow-read-clear-to-send`\n- `allow-read-data-set-ready`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-write-data-terminal-ready`\n- `allow-write-request-to-send`\n- `allow-set-log-level`\n- `allow-get-log-level`\n- `allow-exchange`\n- `allow-exchange-binary`\n- `allow-cancel-exchange`\n- `allow-at`\n- `allow-at-phases`\n- `allow-send-sms-pdu`\n- `allow-configure-at-session`\n- `allow-enable-mux`\n- `allow-open-mux-channel`\n- `allow-disable-mux`", "type": "string", "const": "serialplugin:default", - "markdownDescription": "# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`" + "markdownDescription": "Default permissions for the serialplugin: enumerate, open, read/write, watch/unwatch,\nport hotplug, AT session, exchange, and configure serial ports on desktop and Android.\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-watch-ports`\n- `allow-unwatch-ports`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-write`\n- `allow-write-binary`\n- `allow-capabilities`\n- `allow-watch`\n- `allow-unwatch`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-read-carrier-detect`\n- `allow-read-clear-to-send`\n- `allow-read-data-set-ready`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-write-data-terminal-ready`\n- `allow-write-request-to-send`\n- `allow-set-log-level`\n- `allow-get-log-level`\n- `allow-exchange`\n- `allow-exchange-binary`\n- `allow-cancel-exchange`\n- `allow-at`\n- `allow-at-phases`\n- `allow-send-sms-pdu`\n- `allow-configure-at-session`\n- `allow-enable-mux`\n- `allow-open-mux-channel`\n- `allow-disable-mux`" }, { - "description": "Enables the available_ports command without any pre-configured scope.", + "description": "Enables the at command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-available-ports", - "markdownDescription": "Enables the available_ports command without any pre-configured scope." + "const": "serialplugin:allow-at", + "markdownDescription": "Enables the at command without any pre-configured scope." }, { - "description": "Enables the available_ports_direct command without any pre-configured scope.", + "description": "Enables the at_phases command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-available-ports-direct", - "markdownDescription": "Enables the available_ports_direct command without any pre-configured scope." + "const": "serialplugin:allow-at-phases", + "markdownDescription": "Enables the at_phases command without any pre-configured scope." + }, + { + "description": "Enables the available_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-available-ports", + "markdownDescription": "Enables the available_ports command without any pre-configured scope." }, { "description": "Enables the bytes_to_read command without any pre-configured scope.", @@ -2114,12 +2120,24 @@ "const": "serialplugin:allow-bytes-to-write", "markdownDescription": "Enables the bytes_to_write command without any pre-configured scope." }, + { + "description": "Enables the cancel_exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-cancel-exchange", + "markdownDescription": "Enables the cancel_exchange command without any pre-configured scope." + }, { "description": "Enables the cancel_read command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-cancel-read", "markdownDescription": "Enables the cancel_read command without any pre-configured scope." }, + { + "description": "Enables the capabilities command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-capabilities", + "markdownDescription": "Enables the capabilities command without any pre-configured scope." + }, { "description": "Enables the clear_break command without any pre-configured scope.", "type": "string", @@ -2144,6 +2162,36 @@ "const": "serialplugin:allow-close-all", "markdownDescription": "Enables the close_all command without any pre-configured scope." }, + { + "description": "Enables the configure_at_session command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-configure-at-session", + "markdownDescription": "Enables the configure_at_session command without any pre-configured scope." + }, + { + "description": "Enables the disable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-disable-mux", + "markdownDescription": "Enables the disable_mux command without any pre-configured scope." + }, + { + "description": "Enables the enable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-enable-mux", + "markdownDescription": "Enables the enable_mux command without any pre-configured scope." + }, + { + "description": "Enables the exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-exchange", + "markdownDescription": "Enables the exchange command without any pre-configured scope." + }, + { + "description": "Enables the exchange_binary command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-exchange-binary", + "markdownDescription": "Enables the exchange_binary command without any pre-configured scope." + }, { "description": "Enables the force_close command without any pre-configured scope.", "type": "string", @@ -2168,6 +2216,12 @@ "const": "serialplugin:allow-open", "markdownDescription": "Enables the open command without any pre-configured scope." }, + { + "description": "Enables the open_mux_channel command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-open-mux-channel", + "markdownDescription": "Enables the open_mux_channel command without any pre-configured scope." + }, { "description": "Enables the read command without any pre-configured scope.", "type": "string", @@ -2186,54 +2240,30 @@ "const": "serialplugin:allow-read-carrier-detect", "markdownDescription": "Enables the read_carrier_detect command without any pre-configured scope." }, - { - "description": "Enables the read_cd command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-cd", - "markdownDescription": "Enables the read_cd command without any pre-configured scope." - }, { "description": "Enables the read_clear_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-clear-to-send", "markdownDescription": "Enables the read_clear_to_send command without any pre-configured scope." }, - { - "description": "Enables the read_cts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-cts", - "markdownDescription": "Enables the read_cts command without any pre-configured scope." - }, { "description": "Enables the read_data_set_ready command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-data-set-ready", "markdownDescription": "Enables the read_data_set_ready command without any pre-configured scope." }, - { - "description": "Enables the read_dsr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-dsr", - "markdownDescription": "Enables the read_dsr command without any pre-configured scope." - }, - { - "description": "Enables the read_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-dtr", - "markdownDescription": "Enables the read_dtr command without any pre-configured scope." - }, - { - "description": "Enables the read_ri command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-ri", - "markdownDescription": "Enables the read_ri command without any pre-configured scope." - }, { "description": "Enables the read_ring_indicator command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-ring-indicator", "markdownDescription": "Enables the read_ring_indicator command without any pre-configured scope." }, + { + "description": "Enables the send_sms_pdu command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-send-sms-pdu", + "markdownDescription": "Enables the send_sms_pdu command without any pre-configured scope." + }, { "description": "Enables the set_baud_rate command without any pre-configured scope.", "type": "string", @@ -2283,16 +2313,28 @@ "markdownDescription": "Enables the set_timeout command without any pre-configured scope." }, { - "description": "Enables the start_listening command without any pre-configured scope.", + "description": "Enables the unwatch command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-start-listening", - "markdownDescription": "Enables the start_listening command without any pre-configured scope." + "const": "serialplugin:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." }, { - "description": "Enables the stop_listening command without any pre-configured scope.", + "description": "Enables the unwatch_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-stop-listening", - "markdownDescription": "Enables the stop_listening command without any pre-configured scope." + "const": "serialplugin:allow-unwatch-ports", + "markdownDescription": "Enables the unwatch_ports command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the watch_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-watch-ports", + "markdownDescription": "Enables the watch_ports command without any pre-configured scope." }, { "description": "Enables the write command without any pre-configured scope.", @@ -2312,12 +2354,6 @@ "const": "serialplugin:allow-write-data-terminal-ready", "markdownDescription": "Enables the write_data_terminal_ready command without any pre-configured scope." }, - { - "description": "Enables the write_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-write-dtr", - "markdownDescription": "Enables the write_dtr command without any pre-configured scope." - }, { "description": "Enables the write_request_to_send command without any pre-configured scope.", "type": "string", @@ -2325,22 +2361,22 @@ "markdownDescription": "Enables the write_request_to_send command without any pre-configured scope." }, { - "description": "Enables the write_rts command without any pre-configured scope.", + "description": "Denies the at command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-write-rts", - "markdownDescription": "Enables the write_rts command without any pre-configured scope." + "const": "serialplugin:deny-at", + "markdownDescription": "Denies the at command without any pre-configured scope." }, { - "description": "Denies the available_ports command without any pre-configured scope.", + "description": "Denies the at_phases command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-available-ports", - "markdownDescription": "Denies the available_ports command without any pre-configured scope." + "const": "serialplugin:deny-at-phases", + "markdownDescription": "Denies the at_phases command without any pre-configured scope." }, { - "description": "Denies the available_ports_direct command without any pre-configured scope.", + "description": "Denies the available_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-available-ports-direct", - "markdownDescription": "Denies the available_ports_direct command without any pre-configured scope." + "const": "serialplugin:deny-available-ports", + "markdownDescription": "Denies the available_ports command without any pre-configured scope." }, { "description": "Denies the bytes_to_read command without any pre-configured scope.", @@ -2354,12 +2390,24 @@ "const": "serialplugin:deny-bytes-to-write", "markdownDescription": "Denies the bytes_to_write command without any pre-configured scope." }, + { + "description": "Denies the cancel_exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-cancel-exchange", + "markdownDescription": "Denies the cancel_exchange command without any pre-configured scope." + }, { "description": "Denies the cancel_read command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-cancel-read", "markdownDescription": "Denies the cancel_read command without any pre-configured scope." }, + { + "description": "Denies the capabilities command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-capabilities", + "markdownDescription": "Denies the capabilities command without any pre-configured scope." + }, { "description": "Denies the clear_break command without any pre-configured scope.", "type": "string", @@ -2384,6 +2432,36 @@ "const": "serialplugin:deny-close-all", "markdownDescription": "Denies the close_all command without any pre-configured scope." }, + { + "description": "Denies the configure_at_session command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-configure-at-session", + "markdownDescription": "Denies the configure_at_session command without any pre-configured scope." + }, + { + "description": "Denies the disable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-disable-mux", + "markdownDescription": "Denies the disable_mux command without any pre-configured scope." + }, + { + "description": "Denies the enable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-enable-mux", + "markdownDescription": "Denies the enable_mux command without any pre-configured scope." + }, + { + "description": "Denies the exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-exchange", + "markdownDescription": "Denies the exchange command without any pre-configured scope." + }, + { + "description": "Denies the exchange_binary command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-exchange-binary", + "markdownDescription": "Denies the exchange_binary command without any pre-configured scope." + }, { "description": "Denies the force_close command without any pre-configured scope.", "type": "string", @@ -2408,6 +2486,12 @@ "const": "serialplugin:deny-open", "markdownDescription": "Denies the open command without any pre-configured scope." }, + { + "description": "Denies the open_mux_channel command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-open-mux-channel", + "markdownDescription": "Denies the open_mux_channel command without any pre-configured scope." + }, { "description": "Denies the read command without any pre-configured scope.", "type": "string", @@ -2426,54 +2510,30 @@ "const": "serialplugin:deny-read-carrier-detect", "markdownDescription": "Denies the read_carrier_detect command without any pre-configured scope." }, - { - "description": "Denies the read_cd command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-cd", - "markdownDescription": "Denies the read_cd command without any pre-configured scope." - }, { "description": "Denies the read_clear_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-clear-to-send", "markdownDescription": "Denies the read_clear_to_send command without any pre-configured scope." }, - { - "description": "Denies the read_cts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-cts", - "markdownDescription": "Denies the read_cts command without any pre-configured scope." - }, { "description": "Denies the read_data_set_ready command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-data-set-ready", "markdownDescription": "Denies the read_data_set_ready command without any pre-configured scope." }, - { - "description": "Denies the read_dsr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-dsr", - "markdownDescription": "Denies the read_dsr command without any pre-configured scope." - }, - { - "description": "Denies the read_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-dtr", - "markdownDescription": "Denies the read_dtr command without any pre-configured scope." - }, - { - "description": "Denies the read_ri command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-ri", - "markdownDescription": "Denies the read_ri command without any pre-configured scope." - }, { "description": "Denies the read_ring_indicator command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-ring-indicator", "markdownDescription": "Denies the read_ring_indicator command without any pre-configured scope." }, + { + "description": "Denies the send_sms_pdu command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-send-sms-pdu", + "markdownDescription": "Denies the send_sms_pdu command without any pre-configured scope." + }, { "description": "Denies the set_baud_rate command without any pre-configured scope.", "type": "string", @@ -2523,16 +2583,28 @@ "markdownDescription": "Denies the set_timeout command without any pre-configured scope." }, { - "description": "Denies the start_listening command without any pre-configured scope.", + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the unwatch_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-start-listening", - "markdownDescription": "Denies the start_listening command without any pre-configured scope." + "const": "serialplugin:deny-unwatch-ports", + "markdownDescription": "Denies the unwatch_ports command without any pre-configured scope." }, { - "description": "Denies the stop_listening command without any pre-configured scope.", + "description": "Denies the watch command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-stop-listening", - "markdownDescription": "Denies the stop_listening command without any pre-configured scope." + "const": "serialplugin:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "Denies the watch_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-watch-ports", + "markdownDescription": "Denies the watch_ports command without any pre-configured scope." }, { "description": "Denies the write command without any pre-configured scope.", @@ -2552,23 +2624,11 @@ "const": "serialplugin:deny-write-data-terminal-ready", "markdownDescription": "Denies the write_data_terminal_ready command without any pre-configured scope." }, - { - "description": "Denies the write_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-write-dtr", - "markdownDescription": "Denies the write_dtr command without any pre-configured scope." - }, { "description": "Denies the write_request_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-write-request-to-send", "markdownDescription": "Denies the write_request_to_send command without any pre-configured scope." - }, - { - "description": "Denies the write_rts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-write-rts", - "markdownDescription": "Denies the write_rts command without any pre-configured scope." } ] }, diff --git a/examples/serialport-test/src-tauri/gen/schemas/mobile-schema.json b/examples/serialport-test/src-tauri/gen/schemas/mobile-schema.json index 521f154f..b55dfdda 100644 --- a/examples/serialport-test/src-tauri/gen/schemas/mobile-schema.json +++ b/examples/serialport-test/src-tauri/gen/schemas/mobile-schema.json @@ -2085,22 +2085,28 @@ "markdownDescription": "Denies the unminimize command without any pre-configured scope." }, { - "description": "# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`", + "description": "Default permissions for the serialplugin: enumerate, open, read/write, watch/unwatch,\nport hotplug, AT session, exchange, and configure serial ports on desktop and Android.\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-watch-ports`\n- `allow-unwatch-ports`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-write`\n- `allow-write-binary`\n- `allow-capabilities`\n- `allow-watch`\n- `allow-unwatch`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-read-carrier-detect`\n- `allow-read-clear-to-send`\n- `allow-read-data-set-ready`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-write-data-terminal-ready`\n- `allow-write-request-to-send`\n- `allow-set-log-level`\n- `allow-get-log-level`\n- `allow-exchange`\n- `allow-exchange-binary`\n- `allow-cancel-exchange`\n- `allow-at`\n- `allow-at-phases`\n- `allow-send-sms-pdu`\n- `allow-configure-at-session`\n- `allow-enable-mux`\n- `allow-open-mux-channel`\n- `allow-disable-mux`", "type": "string", "const": "serialplugin:default", - "markdownDescription": "# Tauri `serialport` default permissions\n\nThis configuration file defines the default permissions granted\nto the serialport.\n\n### Granted Permissions\n\nThis default permission set enables all read-related commands and\nallows access to the `$APP` folder and sub directories created in it.\nThe location of the `$APP` folder depends on the operating system,\nwhere the application is run.\n\nIn general the `$APP` folder needs to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\n### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-write`\n- `allow-write-binary`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-available-ports`\n- `allow-available-ports-direct`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-cancel-read`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-managed-ports`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-read-carrier-detect`\n- `allow-read-cd`\n- `allow-read-clear-to-send`\n- `allow-read-cts`\n- `allow-read-data-set-ready`\n- `allow-read-dsr`\n- `allow-read-dtr`\n- `allow-read-ri`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-start-listening`\n- `allow-stop-listening`\n- `allow-write`\n- `allow-write-binary`\n- `allow-write-data-terminal-ready`\n- `allow-write-dtr`\n- `allow-write-request-to-send`\n- `allow-write-rts`\n- `allow-set-log-level`\n- `allow-get-log-level`" + "markdownDescription": "Default permissions for the serialplugin: enumerate, open, read/write, watch/unwatch,\nport hotplug, AT session, exchange, and configure serial ports on desktop and Android.\n\n#### This default permission set includes:\n\n- `allow-managed-ports`\n- `allow-available-ports`\n- `allow-watch-ports`\n- `allow-unwatch-ports`\n- `allow-cancel-read`\n- `allow-close`\n- `allow-close-all`\n- `allow-force-close`\n- `allow-open`\n- `allow-read`\n- `allow-read-binary`\n- `allow-write`\n- `allow-write-binary`\n- `allow-capabilities`\n- `allow-watch`\n- `allow-unwatch`\n- `allow-bytes-to-read`\n- `allow-bytes-to-write`\n- `allow-clear-break`\n- `allow-clear-buffer`\n- `allow-read-carrier-detect`\n- `allow-read-clear-to-send`\n- `allow-read-data-set-ready`\n- `allow-read-ring-indicator`\n- `allow-set-baud-rate`\n- `allow-set-break`\n- `allow-set-data-bits`\n- `allow-set-flow-control`\n- `allow-set-parity`\n- `allow-set-stop-bits`\n- `allow-set-timeout`\n- `allow-write-data-terminal-ready`\n- `allow-write-request-to-send`\n- `allow-set-log-level`\n- `allow-get-log-level`\n- `allow-exchange`\n- `allow-exchange-binary`\n- `allow-cancel-exchange`\n- `allow-at`\n- `allow-at-phases`\n- `allow-send-sms-pdu`\n- `allow-configure-at-session`\n- `allow-enable-mux`\n- `allow-open-mux-channel`\n- `allow-disable-mux`" }, { - "description": "Enables the available_ports command without any pre-configured scope.", + "description": "Enables the at command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-available-ports", - "markdownDescription": "Enables the available_ports command without any pre-configured scope." + "const": "serialplugin:allow-at", + "markdownDescription": "Enables the at command without any pre-configured scope." }, { - "description": "Enables the available_ports_direct command without any pre-configured scope.", + "description": "Enables the at_phases command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-available-ports-direct", - "markdownDescription": "Enables the available_ports_direct command without any pre-configured scope." + "const": "serialplugin:allow-at-phases", + "markdownDescription": "Enables the at_phases command without any pre-configured scope." + }, + { + "description": "Enables the available_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-available-ports", + "markdownDescription": "Enables the available_ports command without any pre-configured scope." }, { "description": "Enables the bytes_to_read command without any pre-configured scope.", @@ -2114,12 +2120,24 @@ "const": "serialplugin:allow-bytes-to-write", "markdownDescription": "Enables the bytes_to_write command without any pre-configured scope." }, + { + "description": "Enables the cancel_exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-cancel-exchange", + "markdownDescription": "Enables the cancel_exchange command without any pre-configured scope." + }, { "description": "Enables the cancel_read command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-cancel-read", "markdownDescription": "Enables the cancel_read command without any pre-configured scope." }, + { + "description": "Enables the capabilities command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-capabilities", + "markdownDescription": "Enables the capabilities command without any pre-configured scope." + }, { "description": "Enables the clear_break command without any pre-configured scope.", "type": "string", @@ -2144,6 +2162,36 @@ "const": "serialplugin:allow-close-all", "markdownDescription": "Enables the close_all command without any pre-configured scope." }, + { + "description": "Enables the configure_at_session command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-configure-at-session", + "markdownDescription": "Enables the configure_at_session command without any pre-configured scope." + }, + { + "description": "Enables the disable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-disable-mux", + "markdownDescription": "Enables the disable_mux command without any pre-configured scope." + }, + { + "description": "Enables the enable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-enable-mux", + "markdownDescription": "Enables the enable_mux command without any pre-configured scope." + }, + { + "description": "Enables the exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-exchange", + "markdownDescription": "Enables the exchange command without any pre-configured scope." + }, + { + "description": "Enables the exchange_binary command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-exchange-binary", + "markdownDescription": "Enables the exchange_binary command without any pre-configured scope." + }, { "description": "Enables the force_close command without any pre-configured scope.", "type": "string", @@ -2168,6 +2216,12 @@ "const": "serialplugin:allow-open", "markdownDescription": "Enables the open command without any pre-configured scope." }, + { + "description": "Enables the open_mux_channel command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-open-mux-channel", + "markdownDescription": "Enables the open_mux_channel command without any pre-configured scope." + }, { "description": "Enables the read command without any pre-configured scope.", "type": "string", @@ -2186,54 +2240,30 @@ "const": "serialplugin:allow-read-carrier-detect", "markdownDescription": "Enables the read_carrier_detect command without any pre-configured scope." }, - { - "description": "Enables the read_cd command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-cd", - "markdownDescription": "Enables the read_cd command without any pre-configured scope." - }, { "description": "Enables the read_clear_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-clear-to-send", "markdownDescription": "Enables the read_clear_to_send command without any pre-configured scope." }, - { - "description": "Enables the read_cts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-cts", - "markdownDescription": "Enables the read_cts command without any pre-configured scope." - }, { "description": "Enables the read_data_set_ready command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-data-set-ready", "markdownDescription": "Enables the read_data_set_ready command without any pre-configured scope." }, - { - "description": "Enables the read_dsr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-dsr", - "markdownDescription": "Enables the read_dsr command without any pre-configured scope." - }, - { - "description": "Enables the read_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-dtr", - "markdownDescription": "Enables the read_dtr command without any pre-configured scope." - }, - { - "description": "Enables the read_ri command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-read-ri", - "markdownDescription": "Enables the read_ri command without any pre-configured scope." - }, { "description": "Enables the read_ring_indicator command without any pre-configured scope.", "type": "string", "const": "serialplugin:allow-read-ring-indicator", "markdownDescription": "Enables the read_ring_indicator command without any pre-configured scope." }, + { + "description": "Enables the send_sms_pdu command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-send-sms-pdu", + "markdownDescription": "Enables the send_sms_pdu command without any pre-configured scope." + }, { "description": "Enables the set_baud_rate command without any pre-configured scope.", "type": "string", @@ -2283,16 +2313,28 @@ "markdownDescription": "Enables the set_timeout command without any pre-configured scope." }, { - "description": "Enables the start_listening command without any pre-configured scope.", + "description": "Enables the unwatch command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-start-listening", - "markdownDescription": "Enables the start_listening command without any pre-configured scope." + "const": "serialplugin:allow-unwatch", + "markdownDescription": "Enables the unwatch command without any pre-configured scope." }, { - "description": "Enables the stop_listening command without any pre-configured scope.", + "description": "Enables the unwatch_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-stop-listening", - "markdownDescription": "Enables the stop_listening command without any pre-configured scope." + "const": "serialplugin:allow-unwatch-ports", + "markdownDescription": "Enables the unwatch_ports command without any pre-configured scope." + }, + { + "description": "Enables the watch command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-watch", + "markdownDescription": "Enables the watch command without any pre-configured scope." + }, + { + "description": "Enables the watch_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:allow-watch-ports", + "markdownDescription": "Enables the watch_ports command without any pre-configured scope." }, { "description": "Enables the write command without any pre-configured scope.", @@ -2312,12 +2354,6 @@ "const": "serialplugin:allow-write-data-terminal-ready", "markdownDescription": "Enables the write_data_terminal_ready command without any pre-configured scope." }, - { - "description": "Enables the write_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:allow-write-dtr", - "markdownDescription": "Enables the write_dtr command without any pre-configured scope." - }, { "description": "Enables the write_request_to_send command without any pre-configured scope.", "type": "string", @@ -2325,22 +2361,22 @@ "markdownDescription": "Enables the write_request_to_send command without any pre-configured scope." }, { - "description": "Enables the write_rts command without any pre-configured scope.", + "description": "Denies the at command without any pre-configured scope.", "type": "string", - "const": "serialplugin:allow-write-rts", - "markdownDescription": "Enables the write_rts command without any pre-configured scope." + "const": "serialplugin:deny-at", + "markdownDescription": "Denies the at command without any pre-configured scope." }, { - "description": "Denies the available_ports command without any pre-configured scope.", + "description": "Denies the at_phases command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-available-ports", - "markdownDescription": "Denies the available_ports command without any pre-configured scope." + "const": "serialplugin:deny-at-phases", + "markdownDescription": "Denies the at_phases command without any pre-configured scope." }, { - "description": "Denies the available_ports_direct command without any pre-configured scope.", + "description": "Denies the available_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-available-ports-direct", - "markdownDescription": "Denies the available_ports_direct command without any pre-configured scope." + "const": "serialplugin:deny-available-ports", + "markdownDescription": "Denies the available_ports command without any pre-configured scope." }, { "description": "Denies the bytes_to_read command without any pre-configured scope.", @@ -2354,12 +2390,24 @@ "const": "serialplugin:deny-bytes-to-write", "markdownDescription": "Denies the bytes_to_write command without any pre-configured scope." }, + { + "description": "Denies the cancel_exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-cancel-exchange", + "markdownDescription": "Denies the cancel_exchange command without any pre-configured scope." + }, { "description": "Denies the cancel_read command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-cancel-read", "markdownDescription": "Denies the cancel_read command without any pre-configured scope." }, + { + "description": "Denies the capabilities command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-capabilities", + "markdownDescription": "Denies the capabilities command without any pre-configured scope." + }, { "description": "Denies the clear_break command without any pre-configured scope.", "type": "string", @@ -2384,6 +2432,36 @@ "const": "serialplugin:deny-close-all", "markdownDescription": "Denies the close_all command without any pre-configured scope." }, + { + "description": "Denies the configure_at_session command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-configure-at-session", + "markdownDescription": "Denies the configure_at_session command without any pre-configured scope." + }, + { + "description": "Denies the disable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-disable-mux", + "markdownDescription": "Denies the disable_mux command without any pre-configured scope." + }, + { + "description": "Denies the enable_mux command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-enable-mux", + "markdownDescription": "Denies the enable_mux command without any pre-configured scope." + }, + { + "description": "Denies the exchange command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-exchange", + "markdownDescription": "Denies the exchange command without any pre-configured scope." + }, + { + "description": "Denies the exchange_binary command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-exchange-binary", + "markdownDescription": "Denies the exchange_binary command without any pre-configured scope." + }, { "description": "Denies the force_close command without any pre-configured scope.", "type": "string", @@ -2408,6 +2486,12 @@ "const": "serialplugin:deny-open", "markdownDescription": "Denies the open command without any pre-configured scope." }, + { + "description": "Denies the open_mux_channel command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-open-mux-channel", + "markdownDescription": "Denies the open_mux_channel command without any pre-configured scope." + }, { "description": "Denies the read command without any pre-configured scope.", "type": "string", @@ -2426,54 +2510,30 @@ "const": "serialplugin:deny-read-carrier-detect", "markdownDescription": "Denies the read_carrier_detect command without any pre-configured scope." }, - { - "description": "Denies the read_cd command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-cd", - "markdownDescription": "Denies the read_cd command without any pre-configured scope." - }, { "description": "Denies the read_clear_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-clear-to-send", "markdownDescription": "Denies the read_clear_to_send command without any pre-configured scope." }, - { - "description": "Denies the read_cts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-cts", - "markdownDescription": "Denies the read_cts command without any pre-configured scope." - }, { "description": "Denies the read_data_set_ready command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-data-set-ready", "markdownDescription": "Denies the read_data_set_ready command without any pre-configured scope." }, - { - "description": "Denies the read_dsr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-dsr", - "markdownDescription": "Denies the read_dsr command without any pre-configured scope." - }, - { - "description": "Denies the read_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-dtr", - "markdownDescription": "Denies the read_dtr command without any pre-configured scope." - }, - { - "description": "Denies the read_ri command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-read-ri", - "markdownDescription": "Denies the read_ri command without any pre-configured scope." - }, { "description": "Denies the read_ring_indicator command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-read-ring-indicator", "markdownDescription": "Denies the read_ring_indicator command without any pre-configured scope." }, + { + "description": "Denies the send_sms_pdu command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-send-sms-pdu", + "markdownDescription": "Denies the send_sms_pdu command without any pre-configured scope." + }, { "description": "Denies the set_baud_rate command without any pre-configured scope.", "type": "string", @@ -2523,16 +2583,28 @@ "markdownDescription": "Denies the set_timeout command without any pre-configured scope." }, { - "description": "Denies the start_listening command without any pre-configured scope.", + "description": "Denies the unwatch command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-unwatch", + "markdownDescription": "Denies the unwatch command without any pre-configured scope." + }, + { + "description": "Denies the unwatch_ports command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-start-listening", - "markdownDescription": "Denies the start_listening command without any pre-configured scope." + "const": "serialplugin:deny-unwatch-ports", + "markdownDescription": "Denies the unwatch_ports command without any pre-configured scope." }, { - "description": "Denies the stop_listening command without any pre-configured scope.", + "description": "Denies the watch command without any pre-configured scope.", "type": "string", - "const": "serialplugin:deny-stop-listening", - "markdownDescription": "Denies the stop_listening command without any pre-configured scope." + "const": "serialplugin:deny-watch", + "markdownDescription": "Denies the watch command without any pre-configured scope." + }, + { + "description": "Denies the watch_ports command without any pre-configured scope.", + "type": "string", + "const": "serialplugin:deny-watch-ports", + "markdownDescription": "Denies the watch_ports command without any pre-configured scope." }, { "description": "Denies the write command without any pre-configured scope.", @@ -2552,23 +2624,11 @@ "const": "serialplugin:deny-write-data-terminal-ready", "markdownDescription": "Denies the write_data_terminal_ready command without any pre-configured scope." }, - { - "description": "Denies the write_dtr command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-write-dtr", - "markdownDescription": "Denies the write_dtr command without any pre-configured scope." - }, { "description": "Denies the write_request_to_send command without any pre-configured scope.", "type": "string", "const": "serialplugin:deny-write-request-to-send", "markdownDescription": "Denies the write_request_to_send command without any pre-configured scope." - }, - { - "description": "Denies the write_rts command without any pre-configured scope.", - "type": "string", - "const": "serialplugin:deny-write-rts", - "markdownDescription": "Denies the write_rts command without any pre-configured scope." } ] }, diff --git a/examples/serialport-test/src-tauri/src/lib.rs b/examples/serialport-test/src-tauri/src/lib.rs index 7b8078ab..884b6293 100644 --- a/examples/serialport-test/src-tauri/src/lib.rs +++ b/examples/serialport-test/src-tauri/src/lib.rs @@ -4,61 +4,56 @@ fn greet(name: &str) -> String { format!("Hello, {}! You've been greeted from Rust!", name) } +#[cfg(desktop)] #[tauri::command] async fn get_ports_programmatically( app: tauri::AppHandle, - serial: tauri::State<'_, tauri_plugin_serialplugin::desktop_api::SerialPort> + serial: tauri::State<'_, tauri_plugin_serialplugin::desktop_api::SerialPort>, ) -> Result { - - // Get list of available ports - let available_ports = tauri_plugin_serialplugin::commands::available_ports(app.clone(), serial.clone()) + let available_ports = tauri_plugin_serialplugin::commands::available_ports(app.clone(), serial.clone(), None) .map_err(|e| format!("Failed to get available ports: {}", e))?; - - // Get list of ports via direct commands - let direct_ports = tauri_plugin_serialplugin::commands::available_ports_direct(app.clone(), serial.clone()) - .map_err(|e| format!("Failed to get direct ports: {}", e))?; - - // Get list of managed ports + let managed_ports = tauri_plugin_serialplugin::commands::managed_ports(app.clone(), serial.clone()) .map_err(|e| format!("Failed to get managed ports: {}", e))?; - - // Format the result - let result = format!( + + Ok(format!( "=== Programmatic Port List Retrieval ===\n\ Available ports count: {}\n\ - Direct ports count: {}\n\ Managed ports count: {}\n\ \n\ Available ports: {:?}\n\ - Direct ports: {:?}\n\ Managed ports: {:?}\n\ \n\ === Programmatic retrieval completed ===", available_ports.len(), - direct_ports.len(), managed_ports.len(), available_ports, - direct_ports, managed_ports - ); - - Ok(result) + )) } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .setup(|_app| { - #[cfg(debug_assertions)] // only include this code on debug builds + #[cfg(all(debug_assertions, desktop))] { - // Note: DevTools are not available on mobile use tauri::Manager; let window = _app.get_webview_window("main").unwrap(); window.open_devtools(); } Ok(()) }) - .invoke_handler(tauri::generate_handler![greet, get_ports_programmatically]) + .invoke_handler({ + #[cfg(desktop)] + { + tauri::generate_handler![greet, get_ports_programmatically] + } + #[cfg(mobile)] + { + tauri::generate_handler![greet] + } + }) .plugin(tauri_plugin_serialplugin::init()) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/examples/serialport-test/src-tauri/tauri.conf.json b/examples/serialport-test/src-tauri/tauri.conf.json index a86f6c4a..fb0f36ff 100755 --- a/examples/serialport-test/src-tauri/tauri.conf.json +++ b/examples/serialport-test/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "build": { - "beforeDevCommand": "npm run dev", - "beforeBuildCommand": "npm run build", + "beforeDevCommand": "pnpm run dev", + "beforeBuildCommand": "pnpm run build", "frontendDist": "../dist", "devUrl": "http://localhost:1420" }, @@ -14,8 +14,7 @@ "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico" - ], - "createUpdaterArtifacts": "v1Compatible" + ] }, "productName": "serialport-test", "version": "0.0.1", diff --git a/examples/serialport-test/src/App.svelte b/examples/serialport-test/src/App.svelte deleted file mode 100755 index e36d9dda..00000000 --- a/examples/serialport-test/src/App.svelte +++ /dev/null @@ -1,558 +0,0 @@ - - -
-
-

Serial Port Manager

-

Multi-port serial communication demo

-
- - -
-
-

🔧 Rust Commands

-

Test backend functionality

-
- -

Result will be displayed in browser console

-
- - -
-
-

🔗 Manual Connection

-

Connect to a specific port

-
-
- - -
-
- - -
-
-

📡 Port Discovery

-

Available serial ports on your system

-
-
- -
-
-

🔍 Available Ports

- -
- {#if Object.keys(availablePorts).length > 0} -
    - {#each Object.entries(availablePorts).sort( (a, b) => a[0].localeCompare(b[0]), ) as [portName, info]} -
  • -
    - {portName} - {info.type} -
    - -
  • - {/each} -
- {:else} -

No ports found

- {/if} -
- - -
-
-

🎯 Direct Ports

- -
- {#if Object.keys(directPorts).length > 0} -
    - {#each Object.entries(directPorts).sort( (a, b) => a[0].localeCompare(b[0]), ) as [portName, info]} -
  • -
    - {portName} - {info.type} -
    - -
  • - {/each} -
- {:else} -

No direct ports found

- {/if} -
- - -
-
-

⚙️ Managed Ports

- -
- {#if managedPorts.length > 0} -
    - {#each managedPorts as portName} -
  • -
    - {portName} - Managed -
    - -
  • - {/each} -
- {:else} -

No managed ports found

- {/if} -
-
-
- - -
-
-

🔌 Active Connections

-

Currently connected serial ports

-
- {#if activePorts.length > 0} -
- {#each activePorts as portName} -
- -
- {/each} -
- {:else} -
-
🔌
-

No active connections

-

Add a port from the lists above to get started

-
- {/if} -
-
- - diff --git a/examples/serialport-test/src/App.vue b/examples/serialport-test/src/App.vue new file mode 100644 index 00000000..a86e464d --- /dev/null +++ b/examples/serialport-test/src/App.vue @@ -0,0 +1,168 @@ + + + + + diff --git a/examples/serialport-test/src/components/PortAtConsole.vue b/examples/serialport-test/src/components/PortAtConsole.vue new file mode 100644 index 00000000..aa7a9384 --- /dev/null +++ b/examples/serialport-test/src/components/PortAtConsole.vue @@ -0,0 +1,196 @@ + + +