mirror of
https://github.com/librekeys/picoforge.git
synced 2026-07-28 08:01:19 -07:00
feat: replace html code with shadcn components
This commit is contained in:
@@ -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 }}
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Generated
+73
@@ -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
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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(¤t_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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user