From 3febac49effe7a6f70ca6ad80883f192a37d1a0c Mon Sep 17 00:00:00 2001 From: Suyog Tandel Date: Thu, 8 Jan 2026 21:28:04 +0530 Subject: [PATCH] feat: replace html code with shadcn components --- .github/workflows/release.yml | 131 +++++++++++++ deno.lock | 8 + package.json | 1 + src-tauri/Cargo.lock | 73 +++++++ src-tauri/Cargo.toml | 25 ++- src-tauri/capabilities/default.json | 9 +- src-tauri/src/fido.rs | 46 +++++ src-tauri/src/lib.rs | 181 ++++++++++++------ src/app.css | 10 + src/lib/components/ui/progress/index.ts | 7 + .../components/ui/progress/progress.svelte | 27 +++ src/lib/components/ui/slider/index.ts | 7 + src/lib/components/ui/slider/slider.svelte | 52 +++++ src/routes/+page.svelte | 38 ++-- 14 files changed, 529 insertions(+), 86 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 src-tauri/src/fido.rs create mode 100644 src/lib/components/ui/progress/index.ts create mode 100644 src/lib/components/ui/progress/progress.svelte create mode 100644 src/lib/components/ui/slider/index.ts create mode 100644 src/lib/components/ui/slider/slider.svelte diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..26410ad --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,131 @@ +name: "publish-stable" +on: + push: + branches: + - release + +jobs: + publish-tauri: + permissions: + contents: write # Ensure this is present + issues: write # Optional but recommended + pull-requests: write # Optional but recommended + strategy: + fail-fast: false + matrix: + include: + - platform: "macos-latest" # for Arm based macs (M1 and above) + args: "--target aarch64-apple-darwin" + - platform: "macos-latest" # for Intel based macs + args: "--target x86_64-apple-darwin" + - platform: "ubuntu-22.04" + args: "" + - platform: "windows-latest" # Windows x64 + args: "--target x86_64-pc-windows-msvc" + - platform: "windows-latest" # Windows x86 (32-bit) + args: "--target i686-pc-windows-msvc" + - platform: "windows-latest" # Windows ARM64 + args: "--target aarch64-pc-windows-msvc" + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + + - name: setup node + uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + targets: > + ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || + matrix.platform == 'windows-latest' && 'x86_64-pc-windows-msvc,i686-pc-windows-msvc,aarch64-pc-windows-msvc' || '' }} + + - name: install dependencies (ubuntu only) + if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-24.04-arm' + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libpcsclite-dev libudev-dev libusb-1.0-0-dev + + - name: install dependencies (ubuntu-arm64 only) + if: matrix.platform == 'ubuntu-24.04-arm' + run: | + sudo apt-get install xdg-utils + + - name: Setup Deno + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: install frontend dependencies + run: deno install + + - name: Extract version from Cargo.toml + id: get_version + shell: bash + run: | + # Define the path to your Cargo.toml file + cargo_file="src-tauri/Cargo.toml" + # Extract ONLY the version number using a more precise regex + version=$(grep -m 1 '^version' "$cargo_file" | sed -E 's/.*"([0-9]+\.[0-9]+\.[0-9]+)".*/\1/') + # Output for debugging + echo "Version found: $version" + # Set the output correctly for GitHub Actions + echo "version=$version" >> $GITHUB_ENV + + - name: Create Git tag + run: | + git tag v${{ env.version }} + git push origin v${{ env.version }} + + # Step 1: Generate auto-generated changelog (using a community Action) + - name: Generate Changelog + id: changelog + uses: requarks/changelog-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag: v${{ env.version }} + + # Step 2: Generate "What's Changed" block from commits + - name: Generate "What's Changed" block + id: whats_changed + shell: bash + run: | + # Get commit messages between the last tag and HEAD, or use all commits if no tags exist + if git describe --tags --abbrev=0 >/dev/null 2>&1; then + commits=$(git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:"- %s") + else + commits=$(git log --pretty=format:"- %s") + fi + + # Format for GitHub Actions output in a cross-platform way + { + echo "whats_changed<> $GITHUB_OUTPUT + + # Step 3: Publish the release with combined release body + - uses: tauri-apps/tauri-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tagName: v__VERSION__ + releaseName: "PicoForge(v__VERSION__) ALPHA" + releaseBody: | + # PicoForge app(v__VERSION__) Alpha release. + + ## Caution: + - This is an alpha release and thus can have some bugs. + + ## What's Changed: + ${{ steps.whats_changed.outputs.whats_changed }} + + ## Auto-generated Changelog: + ${{ steps.changelog.outputs.changes }} + + releaseDraft: false + prerelease: false + args: ${{ matrix.args }} \ No newline at end of file diff --git a/deno.lock b/deno.lock index 542cb67..11da951 100644 --- a/deno.lock +++ b/deno.lock @@ -10,6 +10,7 @@ "npm:@tauri-apps/api@2": "2.9.1", "npm:@tauri-apps/cli@2": "2.9.6", "npm:@tauri-apps/plugin-opener@2": "2.5.2", + "npm:@tauri-apps/plugin-shell@~2.3.4": "2.3.4", "npm:bits-ui@^2.15.4": "2.15.4_@internationalized+date@3.10.1_svelte@5.46.1__acorn@8.15.0_@sveltejs+kit@2.49.3__@sveltejs+vite-plugin-svelte@5.1.1___svelte@5.46.1____acorn@8.15.0___vite@6.4.1____picomatch@4.0.3__svelte@5.46.1___acorn@8.15.0__typescript@5.6.3__vite@6.4.1___picomatch@4.0.3__acorn@8.15.0_@sveltejs+vite-plugin-svelte@5.1.1__svelte@5.46.1___acorn@8.15.0__vite@6.4.1___picomatch@4.0.3_typescript@5.6.3_vite@6.4.1__picomatch@4.0.3", "npm:clsx@^2.1.1": "2.1.1", "npm:svelte-check@4": "4.3.5_svelte@5.46.1__acorn@8.15.0_typescript@5.6.3", @@ -582,6 +583,12 @@ "@tauri-apps/api" ] }, + "@tauri-apps/plugin-shell@2.3.4": { + "integrity": "sha512-ktsRWf8wHLD17aZEyqE8c5x98eNAuTizR1FSX475zQ4TxaiJnhwksLygQz+AGwckJL5bfEP13nWrlTNQJUpKpA==", + "dependencies": [ + "@tauri-apps/api" + ] + }, "@types/cookie@0.6.0": { "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, @@ -1038,6 +1045,7 @@ "npm:@tauri-apps/api@2", "npm:@tauri-apps/cli@2", "npm:@tauri-apps/plugin-opener@2", + "npm:@tauri-apps/plugin-shell@~2.3.4", "npm:bits-ui@^2.15.4", "npm:clsx@^2.1.1", "npm:svelte-check@4", diff --git a/package.json b/package.json index 42445b8..ee5089e 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@tailwindcss/vite": "^4.1.18", "@tauri-apps/api": "^2", "@tauri-apps/plugin-opener": "^2", + "@tauri-apps/plugin-shell": "~2.3.4", "tailwindcss": "^4.1.18" }, "devDependencies": { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 9ef59dc..02a653a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -951,6 +951,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.1" @@ -2595,6 +2604,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "pad" version = "0.1.6" @@ -2837,6 +2856,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-opener", + "tauri-plugin-shell", "thiserror 2.0.17", ] @@ -3583,12 +3603,44 @@ dependencies = [ "digest", ] +[[package]] +name = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -4024,6 +4076,27 @@ dependencies = [ "zbus", ] +[[package]] +name = "tauri-plugin-shell" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39b76f884a3937e04b631ffdc3be506088fa979369d25147361352f2f352e5ed" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "tokio", +] + [[package]] name = "tauri-runtime" version = "2.9.2" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8ce3723..e1a659a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -3,7 +3,8 @@ name = "pico-forge" version = "0.1.0" description = "An open source commissioning tool for Pico FIDO security keys. Developed with Rust, Tauri, and Svelte." authors = ["Suyog Tandel"] -edition = "2021" +license = "AGPL-3.0" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -23,9 +24,21 @@ tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -pcsc = "2" # Standard Smart Card API (connect to the key) -hex = "0.4" # For parsing "CAFE:4242" VID/PID strings -byteorder = "1.5" # Required for writing Big-Endian numbers (firmware requirement) -thiserror = "2" # Makes custom error handling much easier -anyhow = "1.0.100" # For easy error propagation +pcsc = "2" # Standard Smart Card API (connect to the key) +hex = "0.4" # For parsing "CAFE:4242" VID/PID strings +byteorder = "1.5" # Required for writing Big-Endian numbers (firmware requirement) +thiserror = "2" # Makes custom error handling much easier +anyhow = "1" # For easy error propagation ctap-hid-fido2 = "3.5" # For fido2 interface operations +tauri-plugin-shell = "2" + +[profile.dev] +incremental = true # Compile your binary in smaller steps. +codegen-units = 256 + +[profile.release] +codegen-units = 1 # Allows LLVM to perform better optimization. +lto = true # Enables link-time-optimizations however is not stable. +opt-level = 3 # Prioritizes speed. Use `z` if you prefer small binary size. +# panic = "abort" # Higher performance by disabling panic handlers. +strip = true # Ensures debug symbols are removed. diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 813d769..450ca64 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,7 +2,9 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", - "windows": ["main"], + "windows": [ + "main" + ], "permissions": [ "core:default", "opener:default", @@ -10,6 +12,7 @@ "core:window:allow-maximize", "core:window:allow-close", "core:window:allow-start-dragging", - "core:window:allow-toggle-maximize" + "core:window:allow-toggle-maximize", + "shell:default" ] -} +} \ No newline at end of file diff --git a/src-tauri/src/fido.rs b/src-tauri/src/fido.rs new file mode 100644 index 0000000..b6d5771 --- /dev/null +++ b/src-tauri/src/fido.rs @@ -0,0 +1,46 @@ +use ctap_hid_fido2::{Cfg, FidoKeyHidFactory}; +// use ctap_hid_fido2::fidokey::FidoKeyHid; + +#[tauri::command] +pub(crate) fn change_fido_pin( + current_pin: Option, + new_pin: String, +) -> Result { + let cfg = Cfg::init(); + let device = FidoKeyHidFactory::create(&cfg) + .map_err(|e| format!("Failed to connect to FIDO device: {:?}", e))?; + + match current_pin { + Some(old) => { + device + .change_pin(&old, &new_pin) + .map_err(|e| format!("Failed to change PIN: {:?}", e))?; + Ok("PIN Changed Successfully".into()) + } + None => { + device + .set_new_pin(&new_pin) + .map_err(|e| format!("Failed to set PIN: {:?}", e))?; + Ok("PIN Set Successfully".into()) + } + } +} + +#[tauri::command] +pub(crate) fn set_min_pin_length( + current_pin: String, + min_pin_length: u8, +) -> Result { + let cfg = Cfg::init(); + let device = FidoKeyHidFactory::create(&cfg) + .map_err(|e| format!("Failed to connect to FIDO device: {:?}", e))?; + + device + .set_min_pin_length(min_pin_length, Some(¤t_pin)) + .map_err(|e| format!("Failed to set minimum PIN length: {:?}", e))?; + + Ok(format!( + "Minimum PIN length successfully set to {}", + min_pin_length + )) +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5a08f86..8dc3839 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,9 +1,11 @@ -use byteorder::{BigEndian, WriteBytesExt, ReadBytesExt}; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use pcsc::{Context, Protocols, Scope, ShareMode}; use serde::{Deserialize, Serialize}; use std::io::Cursor; // use tauri::State; +mod fido; + // --- Constants --- // The Rescue Application ID (AID) from src/rescue.c @@ -25,9 +27,9 @@ const TAG_CURVES: u8 = 0x0A; const TAG_LED_DRIVER: u8 = 0x0C; // Bitmasks for TAG_OPTS -const OPT_LED_DIMMABLE: u16 = 0x02; -const OPT_DISABLE_POWER_RESET: u16 = 0x04; -const OPT_LED_STEADY: u16 = 0x08; +const OPT_LED_DIMMABLE: u16 = 0x02; +const OPT_DISABLE_POWER_RESET: u16 = 0x04; +const OPT_LED_STEADY: u16 = 0x08; // Bitmasks for TAG_CURVES const CURVE_SECP256K1: u32 = 0x08; @@ -109,27 +111,31 @@ impl serde::Serialize for AppError { /// Connects to the first available reader and selects the Rescue Applet fn connect_and_select() -> Result<(pcsc::Card, Vec), AppError> { let ctx = Context::establish(Scope::User)?; - + // List readers let mut readers_buf = [0; 2048]; let mut readers = ctx.list_readers(&mut readers_buf)?; - + // Use the first reader found - let reader = readers.next().ok_or_else(|| AppError::Device("No Smart Card Reader found.".into()))?; - + let reader = readers + .next() + .ok_or_else(|| AppError::Device("No Smart Card Reader found.".into()))?; + // Connect let card = ctx.connect(reader, ShareMode::Shared, Protocols::ANY)?; - + // Select Applet APDU: 00 A4 04 04 [Len] [AID] let mut apdu = vec![0x00, 0xA4, 0x04, 0x04, RESCUE_AID.len() as u8]; apdu.extend_from_slice(RESCUE_AID); - + let mut rx_buf = [0; 256]; let rx = card.transmit(&apdu, &mut rx_buf)?; - + // Check Success (0x90 0x00) if !rx.ends_with(&[0x90, 0x00]) { - return Err(AppError::Device("Rescue Applet not found on device. Is it in FIDO mode?".into())); + return Err(AppError::Device( + "Rescue Applet not found on device. Is it in FIDO mode?".into(), + )); } Ok((card, rx.to_vec())) @@ -152,9 +158,11 @@ fn read_device_details() -> Result { // 2. Read Flash Info (APDU: 80 1E 02 00 00) let mut rx_buf = [0; 256]; let rx_flash = card.transmit(&[0x80, INS_READ, 0x02, 0x00, 0x00], &mut rx_buf)?; - if !rx_flash.ends_with(&[0x90, 0x00]) { return Err(AppError::Device("Failed to read flash".into())); } - - let mut rdr = Cursor::new(&rx_flash[..rx_flash.len()-2]); + if !rx_flash.ends_with(&[0x90, 0x00]) { + return Err(AppError::Device("Failed to read flash".into())); + } + + let mut rdr = Cursor::new(&rx_flash[..rx_flash.len() - 2]); let _free = rdr.read_u32::().unwrap_or(0); let used = rdr.read_u32::().unwrap_or(0); let total = rdr.read_u32::().unwrap_or(0); @@ -169,19 +177,25 @@ fn read_device_details() -> Result { // 4. Read PHY Config (APDU: 80 1E 01 01 00) -> TLV Data let rx_phy = card.transmit(&[0x80, INS_READ, 0x01, 0x01, 0x00], &mut rx_buf)?; - if !rx_phy.ends_with(&[0x90, 0x00]) { return Err(AppError::Device("Failed to read config".into())); } + if !rx_phy.ends_with(&[0x90, 0x00]) { + return Err(AppError::Device("Failed to read config".into())); + } // Parse TLV let mut config = AppConfig::default(); - let data = &rx_phy[..rx_phy.len()-2]; + let data = &rx_phy[..rx_phy.len() - 2]; let mut i = 0; while i < data.len() { - if i + 2 > data.len() { break; } + if i + 2 > data.len() { + break; + } let tag = data[i]; - let len = data[i+1] as usize; + let len = data[i + 1] as usize; i += 2; - if i + len > data.len() { break; } - let val = &data[i..i+len]; + if i + len > data.len() { + break; + } + let val = &data[i..i + len]; match tag { TAG_VIDPID => { @@ -191,27 +205,49 @@ fn read_device_details() -> Result { config.vid = format!("{:04X}", vid); config.pid = format!("{:04X}", pid); } - }, - TAG_LED_GPIO => if !val.is_empty() { config.led_gpio = val[0]; }, - TAG_LED_BRIGHTNESS => if !val.is_empty() { config.led_brightness = val[0]; }, - TAG_UP_BTN => if !val.is_empty() { config.touch_timeout = val[0]; }, + } + TAG_LED_GPIO => { + if !val.is_empty() { + config.led_gpio = val[0]; + } + } + TAG_LED_BRIGHTNESS => { + if !val.is_empty() { + config.led_brightness = val[0]; + } + } + TAG_UP_BTN => { + if !val.is_empty() { + config.touch_timeout = val[0]; + } + } TAG_USB_PRODUCT => { // Remove null terminator if present - let s = std::str::from_utf8(val).unwrap_or("").trim_matches(char::from(0)); + let s = std::str::from_utf8(val) + .unwrap_or("") + .trim_matches(char::from(0)); config.product_name = s.to_string(); - }, - TAG_OPTS => if val.len() >= 2 { - let opts = u16::from_be_bytes([val[0], val[1]]); - - config.led_dimmable = (opts & OPT_LED_DIMMABLE) != 0; - config.power_cycle_on_reset = (opts & OPT_DISABLE_POWER_RESET) == 0; - config.led_steady = (opts & OPT_LED_STEADY) != 0; - }, - TAG_CURVES => if val.len() >= 4 { - let curves = u32::from_be_bytes([val[0], val[1], val[2], val[3]]); - config.enable_secp256k1 = (curves & CURVE_SECP256K1) != 0; - }, - TAG_LED_DRIVER => if !val.is_empty() { config.led_driver = Some(val[0]); }, + } + TAG_OPTS => { + if val.len() >= 2 { + let opts = u16::from_be_bytes([val[0], val[1]]); + + config.led_dimmable = (opts & OPT_LED_DIMMABLE) != 0; + config.power_cycle_on_reset = (opts & OPT_DISABLE_POWER_RESET) == 0; + config.led_steady = (opts & OPT_LED_STEADY) != 0; + } + } + TAG_CURVES => { + if val.len() >= 4 { + let curves = u32::from_be_bytes([val[0], val[1], val[2], val[3]]); + config.enable_secp256k1 = (curves & CURVE_SECP256K1) != 0; + } + } + TAG_LED_DRIVER => { + if !val.is_empty() { + config.led_driver = Some(val[0]); + } + } _ => {} } i += len; @@ -233,13 +269,13 @@ fn read_device_details() -> Result { #[tauri::command] fn get_device_info() -> Result { let (card, select_resp) = connect_and_select()?; - + // 1. Parse Version & Serial from Select Response (see src/rescue.c) // Response: [MCU, PROD, VER_MAJ, VER_MIN, SERIAL(8 bytes)..., 90, 00] if select_resp.len() < 14 { return Err(AppError::Device("Invalid response from device".into())); } - + let version_major = select_resp[2]; let version_minor = select_resp[3]; let serial_bytes = &select_resp[4..12]; @@ -250,7 +286,7 @@ fn get_device_info() -> Result { let apdu_read = [0x80, INS_READ, 0x02, 0x00, 0x00]; let mut rx_buf = [0; 256]; let rx = card.transmit(&apdu_read, &mut rx_buf)?; - + if !rx.ends_with(&[0x90, 0x00]) { return Err(AppError::Device("Failed to read flash info".into())); } @@ -258,7 +294,7 @@ fn get_device_info() -> Result { // Response: [Free(4), Used(4), Total(4), Files(4), Size(4), 90, 00] // We need 'Used' (index 4) and 'Total' (index 8) // Data is Big Endian - let mut rdr = Cursor::new(&rx[..rx.len()-2]); + let mut rdr = Cursor::new(&rx[..rx.len() - 2]); let _free = rdr.read_u32::().unwrap_or(0); let used = rdr.read_u32::().unwrap_or(0); let total = rdr.read_u32::().unwrap_or(0); @@ -278,9 +314,11 @@ fn write_config(config: AppConfigInput) -> Result { // VID:PID (Tag 0x00) if let (Some(vid_str), Some(pid_str)) = (&config.vid, &config.pid) { - let vid = u16::from_str_radix(vid_str, 16).map_err(|_| AppError::Io("Invalid VID".into()))?; - let pid = u16::from_str_radix(pid_str, 16).map_err(|_| AppError::Io("Invalid PID".into()))?; - + let vid = + u16::from_str_radix(vid_str, 16).map_err(|_| AppError::Io("Invalid VID".into()))?; + let pid = + u16::from_str_radix(pid_str, 16).map_err(|_| AppError::Io("Invalid PID".into()))?; + tlv.push(TAG_VIDPID); tlv.push(0x04); tlv.write_u16::(vid).unwrap(); @@ -309,15 +347,25 @@ fn write_config(config: AppConfigInput) -> Result { } // Options (Tag 0x06) - if let (Some(dim), Some(cycle), Some(steady)) = (config.led_dimmable, config.power_cycle_on_reset, config.led_steady) { + if let (Some(dim), Some(cycle), Some(steady)) = ( + config.led_dimmable, + config.power_cycle_on_reset, + config.led_steady, + ) { let mut opts: u16 = 0; - - if dim { opts |= OPT_LED_DIMMABLE; } - - if !cycle { opts |= OPT_DISABLE_POWER_RESET; } - - if steady { opts |= OPT_LED_STEADY; } - + + if dim { + opts |= OPT_LED_DIMMABLE; + } + + if !cycle { + opts |= OPT_DISABLE_POWER_RESET; + } + + if steady { + opts |= OPT_LED_STEADY; + } + tlv.push(TAG_OPTS); tlv.push(0x02); tlv.write_u16::(opts).unwrap(); @@ -325,12 +373,14 @@ fn write_config(config: AppConfigInput) -> Result { // Curves (Tag 0x0A) if let Some(enabled) = config.enable_secp256k1 { - let mut curves: u32 = 0; - if enabled { curves |= CURVE_SECP256K1; } + let mut curves: u32 = 0; + if enabled { + curves |= CURVE_SECP256K1; + } tlv.push(TAG_CURVES); tlv.push(0x04); - tlv.write_u32::(curves).unwrap(); + tlv.write_u32::(curves).unwrap(); } // LED Driver (Tag 0x0C) @@ -344,13 +394,15 @@ fn write_config(config: AppConfigInput) -> Result { if let Some(name) = config.product_name { if !name.is_empty() { let name_bytes = name.as_bytes(); - let len = name_bytes.len() + 1; - if len > 32 { return Err(AppError::Io("Product name too long".into())); } - + let len = name_bytes.len() + 1; + if len > 32 { + return Err(AppError::Io("Product name too long".into())); + } + tlv.push(TAG_USB_PRODUCT); tlv.push(len as u8); tlv.extend_from_slice(name_bytes); - tlv.push(0x00); + tlv.push(0x00); } } @@ -397,13 +449,16 @@ fn enable_secure_boot(lock: bool) -> Result { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_opener::init()) .invoke_handler(tauri::generate_handler![ read_device_details, get_device_info, write_config, + fido::change_fido_pin, + fido::set_min_pin_length, enable_secure_boot - ]) + ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); -} \ No newline at end of file +} diff --git a/src/app.css b/src/app.css index ee2d8d5..5df15e6 100644 --- a/src/app.css +++ b/src/app.css @@ -118,4 +118,14 @@ body { @apply bg-background text-foreground; } +} + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input[type="number"] { + appearance: textfield; } \ No newline at end of file diff --git a/src/lib/components/ui/progress/index.ts b/src/lib/components/ui/progress/index.ts new file mode 100644 index 0000000..25eee61 --- /dev/null +++ b/src/lib/components/ui/progress/index.ts @@ -0,0 +1,7 @@ +import Root from "./progress.svelte"; + +export { + Root, + // + Root as Progress, +}; diff --git a/src/lib/components/ui/progress/progress.svelte b/src/lib/components/ui/progress/progress.svelte new file mode 100644 index 0000000..6833013 --- /dev/null +++ b/src/lib/components/ui/progress/progress.svelte @@ -0,0 +1,27 @@ + + + +
+
diff --git a/src/lib/components/ui/slider/index.ts b/src/lib/components/ui/slider/index.ts new file mode 100644 index 0000000..820f209 --- /dev/null +++ b/src/lib/components/ui/slider/index.ts @@ -0,0 +1,7 @@ +import Root from "./slider.svelte"; + +export { + Root, + // + Root as Slider, +}; diff --git a/src/lib/components/ui/slider/slider.svelte b/src/lib/components/ui/slider/slider.svelte new file mode 100644 index 0000000..b2f793a --- /dev/null +++ b/src/lib/components/ui/slider/slider.svelte @@ -0,0 +1,52 @@ + + + + + {#snippet children({ thumbs })} + + + + {#each thumbs as thumb (thumb)} + + {/each} + {/snippet} + diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 6bbeec1..5685873 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,6 +2,7 @@ import { onMount, tick } from "svelte"; import { getCurrentWindow } from "@tauri-apps/api/window"; import { invoke } from "@tauri-apps/api/core"; + import { open } from "@tauri-apps/plugin-shell"; import { Home, Settings, @@ -32,6 +33,8 @@ import { Separator } from "$lib/components/ui/separator"; import { Badge } from "$lib/components/ui/badge"; import { ScrollArea } from "$lib/components/ui/scroll-area"; + import { Slider } from "$lib/components/ui/slider/index.js"; + import { Progress } from "$lib/components/ui/progress/index.js"; import * as Card from "$lib/components/ui/card"; import * as Alert from "$lib/components/ui/alert"; import * as Select from "$lib/components/ui/select"; @@ -289,6 +292,14 @@ return; } + async function openGithub() { + await open("https://github.com/librekeys/picoforge"); + } + + async function openWebsite() { + await open("https://github.com/librekeys/picoforge"); + } + onMount(() => { document.documentElement.classList.add("dark"); addLog("Application started.", "info"); @@ -313,7 +324,7 @@
@@ -461,12 +472,11 @@
{deviceConnected ? `${deviceInfo.flashUsed} / ${deviceInfo.flashTotal} KB` : "---"}
-
-
0 ? (deviceInfo.flashUsed / deviceInfo.flashTotal) * 100 : 0}%`}> -
-
+ 0 ? (deviceInfo.flashUsed / deviceInfo.flashTotal) * 100 : 0} + max={100} + class="mt-3" + /> @@ -633,12 +643,12 @@ -
+
- - Level {config.ledBrightness} + + Level {config.ledBrightness}
- +
@@ -776,16 +786,16 @@
Artwork: Suyog Tandel
Copyright: - 2026 Suyog Tandel + 2026 Suyog Tandel
- -