feat: replace html code with shadcn components

This commit is contained in:
Suyog Tandel
2026-01-08 21:28:04 +05:30
parent ca4800cb65
commit 3febac49ef
14 changed files with 529 additions and 86 deletions
+131
View File
@@ -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<<EOF"
echo "$commits"
echo "EOF"
} >> $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 }}
Generated
+8
View File
@@ -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",
+1
View File
@@ -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": {
+73
View File
@@ -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"
+19 -6
View File
@@ -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.
+6 -3
View File
@@ -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"
]
}
}
+46
View File
@@ -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<String>,
new_pin: String,
) -> Result<String, String> {
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<String, String> {
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(&current_pin))
.map_err(|e| format!("Failed to set minimum PIN length: {:?}", e))?;
Ok(format!(
"Minimum PIN length successfully set to {}",
min_pin_length
))
}
+118 -63
View File
@@ -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<u8>), 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<FullDeviceStatus, AppError> {
// 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::<BigEndian>().unwrap_or(0);
let used = rdr.read_u32::<BigEndian>().unwrap_or(0);
let total = rdr.read_u32::<BigEndian>().unwrap_or(0);
@@ -169,19 +177,25 @@ fn read_device_details() -> Result<FullDeviceStatus, AppError> {
// 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<FullDeviceStatus, AppError> {
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<FullDeviceStatus, AppError> {
#[tauri::command]
fn get_device_info() -> Result<DeviceInfo, AppError> {
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<DeviceInfo, AppError> {
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<DeviceInfo, AppError> {
// 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::<BigEndian>().unwrap_or(0);
let used = rdr.read_u32::<BigEndian>().unwrap_or(0);
let total = rdr.read_u32::<BigEndian>().unwrap_or(0);
@@ -278,9 +314,11 @@ fn write_config(config: AppConfigInput) -> Result<String, AppError> {
// 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::<BigEndian>(vid).unwrap();
@@ -309,15 +347,25 @@ fn write_config(config: AppConfigInput) -> Result<String, AppError> {
}
// 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::<BigEndian>(opts).unwrap();
@@ -325,12 +373,14 @@ fn write_config(config: AppConfigInput) -> Result<String, AppError> {
// 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::<BigEndian>(curves).unwrap();
tlv.write_u32::<BigEndian>(curves).unwrap();
}
// LED Driver (Tag 0x0C)
@@ -344,13 +394,15 @@ fn write_config(config: AppConfigInput) -> Result<String, AppError> {
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<String, AppError> {
#[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");
}
}
+10
View File
@@ -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;
}
+7
View File
@@ -0,0 +1,7 @@
import Root from "./progress.svelte";
export {
Root,
//
Root as Progress,
};
@@ -0,0 +1,27 @@
<script lang="ts">
import { Progress as ProgressPrimitive } from "bits-ui";
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
let {
ref = $bindable(null),
class: className,
max = 100,
value,
...restProps
}: WithoutChildrenOrChild<ProgressPrimitive.RootProps> = $props();
</script>
<ProgressPrimitive.Root
bind:ref
data-slot="progress"
class={cn("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", className)}
{value}
{max}
{...restProps}
>
<div
data-slot="progress-indicator"
class="bg-primary h-full w-full flex-1 transition-all"
style="transform: translateX(-{100 - (100 * (value ?? 0)) / (max ?? 1)}%)"
></div>
</ProgressPrimitive.Root>
+7
View File
@@ -0,0 +1,7 @@
import Root from "./slider.svelte";
export {
Root,
//
Root as Slider,
};
@@ -0,0 +1,52 @@
<script lang="ts">
import { Slider as SliderPrimitive } from "bits-ui";
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
let {
ref = $bindable(null),
value = $bindable(),
orientation = "horizontal",
class: className,
...restProps
}: WithoutChildrenOrChild<SliderPrimitive.RootProps> = $props();
</script>
<!--
Discriminated Unions + Destructing (required for bindable) do not
get along, so we shut typescript up by casting `value` to `never`.
-->
<SliderPrimitive.Root
bind:ref
bind:value={value as never}
data-slot="slider"
{orientation}
class={cn(
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
className
)}
{...restProps}
>
{#snippet children({ thumbs })}
<span
data-orientation={orientation}
data-slot="slider-track"
class={cn(
"bg-muted relative grow overflow-hidden rounded-full data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5"
)}
>
<SliderPrimitive.Range
data-slot="slider-range"
class={cn(
"bg-primary absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full"
)}
/>
</span>
{#each thumbs as thumb (thumb)}
<SliderPrimitive.Thumb
data-slot="slider-thumb"
index={thumb}
class="border-primary ring-ring/50 block size-4 shrink-0 rounded-full border bg-white shadow-sm transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
/>
{/each}
{/snippet}
</SliderPrimitive.Root>
+24 -14
View File
@@ -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 @@
<header
data-tauri-drag-region
class="h-10 bg-muted/50 border-b flex items-center justify-between px-4 select-none"
class="h-10 bg-muted/50 border-b flex items-center justify-between px-2 select-none"
>
<div class="text-xs font-medium text-muted-foreground pointer-events-none flex items-center gap-2">
</div>
@@ -461,12 +472,11 @@
<div class="text-2xl font-bold">
{deviceConnected ? `${deviceInfo.flashUsed} / ${deviceInfo.flashTotal} KB` : "---"}
</div>
<div class="w-full bg-secondary h-1.5 mt-3 rounded-full overflow-hidden">
<div
class="bg-primary h-full transition-all duration-500"
style={`width: ${deviceConnected && deviceInfo.flashTotal > 0 ? (deviceInfo.flashUsed / deviceInfo.flashTotal) * 100 : 0}%`}>
</div>
</div>
<Progress
value={deviceConnected && deviceInfo.flashTotal > 0 ? (deviceInfo.flashUsed / deviceInfo.flashTotal) * 100 : 0}
max={100}
class="mt-3"
/>
</Card.Content>
</Card.Root>
@@ -633,12 +643,12 @@
<Switch id="power-cycle" bind:checked={config.powerCycleOnReset} disabled={!deviceConnected} />
</div>
<div class="space-y-3 pt-4">
<div class="space-y-3 pt-4">
<div class="flex justify-between">
<Label>LED Brightness (0-15)</Label>
<span class="text-xs text-muted-foreground">Level {config.ledBrightness}</span>
<Label>LED Brightness (0-15)</Label>
<span class="text-xs text-muted-foreground">Level {config.ledBrightness}</span>
</div>
<Input type="range" min="0" max="15" bind:value={config.ledBrightness} disabled={!deviceConnected} />
<Slider type="single" bind:value={config.ledBrightness} max={15} step={1} disabled={!deviceConnected} />
</div>
</Card.Content>
<Card.Footer class="border-t bg-muted/20 px-6 py-4 flex justify-end">
@@ -776,16 +786,16 @@
<div class="flex justify-between"><span>Artwork:</span> <span class="font-medium text-foreground">Suyog Tandel</span></div>
<div class="flex justify-between items-center pt-2 mt-2 border-t border-dashed">
<span class="flex items-center gap-1"><Copyright class="h-3 w-3" /> Copyright:</span>
<span class="font-medium text-foreground">2026 Suyog Tandel</span>
<span class="font-medium text-foreground">2026 Suyog Tandel</span>
</div>
</div>
<div class="flex gap-4 pt-6">
<Button variant="outline" size="sm" class="gap-2">
<Button variant="outline" size="sm" class="gap-2" onclick={openGithub}>
<Github class="h-4 w-4" />
GitHub
</Button>
<Button variant="outline" size="sm" class="gap-2">
<Button variant="outline" size="sm" class="gap-2 onclick={openWebsite}">
<Home class="h-4 w-4" />
Website
</Button>