mirror of
https://github.com/librekeys/picoforge.git
synced 2026-07-28 08:01:19 -07:00
fix: move window state logic to backend
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/target
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/// Custom error types for Pico Forge application.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum PFError {
|
||||
#[error("PCSC Error: {0}")]
|
||||
Pcsc(#[from] pcsc::Error),
|
||||
#[error("IO/Hex Error: {0}")]
|
||||
Io(String),
|
||||
#[error("Device Error: {0}")]
|
||||
Device(String),
|
||||
}
|
||||
|
||||
// Allow error to be serialized to string for Tauri
|
||||
impl serde::Serialize for PFError {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// pub type Result<T> = std::result::Result<T, PFError>;
|
||||
@@ -3,7 +3,10 @@
|
||||
pub mod constants;
|
||||
pub mod hid;
|
||||
|
||||
use crate::types::{AppConfig, AppError, DeviceInfo, FidoDeviceInfo, FullDeviceStatus};
|
||||
use crate::{
|
||||
error::PFError,
|
||||
types::{AppConfig, DeviceInfo, FidoDeviceInfo, FullDeviceStatus},
|
||||
};
|
||||
use constants::*;
|
||||
use ctap_hid_fido2::{Cfg, FidoKeyHidFactory};
|
||||
use hid::*;
|
||||
@@ -80,12 +83,12 @@ pub(crate) fn set_min_pin_length(
|
||||
|
||||
// Custom Fido functions ( works only with pico-fido firmware )
|
||||
|
||||
pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
|
||||
pub fn read_device_details() -> Result<FullDeviceStatus, PFError> {
|
||||
log::info!("Starting FIDO device details read...");
|
||||
|
||||
let transport = HidTransport::open().map_err(|e| {
|
||||
log::error!("Failed to open HID transport: {}", e);
|
||||
AppError::Device(e.to_string())
|
||||
PFError::Device(e.to_string())
|
||||
})?;
|
||||
|
||||
// --- 1. Get Info ---
|
||||
@@ -95,14 +98,14 @@ pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
|
||||
.send_cbor(CTAPHID_CBOR, &info_payload)
|
||||
.map_err(|e| {
|
||||
log::error!("GetInfo CTAP command failed: {}", e);
|
||||
AppError::Device(format!("GetInfo failed: {}", e))
|
||||
PFError::Device(format!("GetInfo failed: {}", e))
|
||||
})?;
|
||||
|
||||
log::debug!("GetInfo response received ({} bytes)", info_res.len());
|
||||
|
||||
let info_val: Value = from_slice(&info_res).map_err(|e| {
|
||||
log::error!("Failed to parse GetInfo CBOR: {}", e);
|
||||
AppError::Io(e.to_string())
|
||||
PFError::Io(e.to_string())
|
||||
})?;
|
||||
|
||||
// NOTE: Key 0x03 is AAGUID, not the unique device Serial.
|
||||
@@ -158,7 +161,7 @@ pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
|
||||
|
||||
let mem_cbor = to_vec(&Value::Map(mem_req)).map_err(|e| {
|
||||
log::error!("Failed to encode Memory Stats CBOR: {}", e);
|
||||
AppError::Io(format!("CBOR encode error: {}", e))
|
||||
PFError::Io(format!("CBOR encode error: {}", e))
|
||||
})?;
|
||||
|
||||
// FIX: Prepend the Vendor Command ID (0x06 for Memory) to the payload
|
||||
@@ -216,7 +219,7 @@ pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
|
||||
|
||||
let phy_cbor = to_vec(&Value::Map(phy_params)).map_err(|e| {
|
||||
log::error!("Failed to encode Physical Config CBOR: {}", e);
|
||||
AppError::Io(format!("CBOR encode error: {}", e))
|
||||
PFError::Io(format!("CBOR encode error: {}", e))
|
||||
})?;
|
||||
|
||||
// FIX: Prepend Vendor Command ID (0x05 for PhysicalOptions)
|
||||
|
||||
+5
-5
@@ -1,19 +1,19 @@
|
||||
//! Tauri Commands to interact with the pico-fido firmware via rescue and fido protocols.
|
||||
use crate::{fido, rescue, types::*};
|
||||
use crate::{error::PFError, fido, rescue, types::*};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
|
||||
pub fn read_device_details() -> Result<FullDeviceStatus, PFError> {
|
||||
rescue::read_device_details()
|
||||
// fido::read_device_details()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn write_config(config: AppConfigInput) -> Result<String, AppError> {
|
||||
pub fn write_config(config: AppConfigInput) -> Result<String, PFError> {
|
||||
rescue::write_config(config)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn enable_secure_boot(lock: bool) -> Result<String, AppError> {
|
||||
pub fn enable_secure_boot(lock: bool) -> Result<String, PFError> {
|
||||
rescue::enable_secure_boot(lock)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,6 @@ pub(crate) fn set_min_pin_length(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn reboot(to_bootsel: bool) -> Result<String, AppError> {
|
||||
pub fn reboot(to_bootsel: bool) -> Result<String, PFError> {
|
||||
rescue::reboot_device(to_bootsel)
|
||||
}
|
||||
|
||||
+31
-1
@@ -1,11 +1,34 @@
|
||||
// use tauri::State;
|
||||
use serde::Serialize;
|
||||
use tauri::{Emitter, Listener, Manager, WebviewWindow};
|
||||
|
||||
mod error;
|
||||
mod fido;
|
||||
mod io;
|
||||
mod logging;
|
||||
mod rescue;
|
||||
mod types;
|
||||
|
||||
// This will be temporary here untill moved to a dedicated module:
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WindowState {
|
||||
pub is_maximized: bool,
|
||||
}
|
||||
|
||||
/// Sets up window state listener that emits events to the frontend
|
||||
pub fn setup_window_state_listener(window: &WebviewWindow) {
|
||||
let window_clone = window.clone();
|
||||
|
||||
window.on_window_event(move |event| {
|
||||
if let tauri::WindowEvent::Resized(_) = event {
|
||||
if let Ok(is_maximized) = window_clone.is_maximized() {
|
||||
let _ = window_clone.emit("window-state-changed", WindowState { is_maximized });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
logging::logger_init();
|
||||
@@ -14,6 +37,13 @@ pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.setup(|app| {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
setup_window_state_listener(&window);
|
||||
log::info!("Window state listener initialized");
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler
|
||||
|
||||
pub mod constants;
|
||||
|
||||
use crate::{rescue::constants::*, types::*};
|
||||
use crate::{error::PFError, rescue::constants::*, types::*};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use log;
|
||||
use pcsc::{Context, Protocols, Scope, ShareMode};
|
||||
use std::io::Cursor;
|
||||
|
||||
/// Connects to the first available reader and selects the Rescue Applet
|
||||
fn connect_and_select() -> Result<(pcsc::Card, Vec<u8>), AppError> {
|
||||
fn connect_and_select() -> Result<(pcsc::Card, Vec<u8>), PFError> {
|
||||
let ctx = Context::establish(Scope::User)?;
|
||||
|
||||
let mut readers_buf = [0; 2048];
|
||||
@@ -16,7 +20,7 @@ fn connect_and_select() -> Result<(pcsc::Card, Vec<u8>), AppError> {
|
||||
// Use the first reader found
|
||||
let reader = readers.next().ok_or_else(|| {
|
||||
log::error!("No Smart Card Reader found");
|
||||
AppError::Device("No Smart Card Reader found.".into())
|
||||
PFError::Device("No Smart Card Reader found.".into())
|
||||
})?;
|
||||
|
||||
let card = ctx.connect(reader, ShareMode::Shared, Protocols::ANY)?;
|
||||
@@ -37,7 +41,7 @@ fn connect_and_select() -> Result<(pcsc::Card, Vec<u8>), AppError> {
|
||||
// Check Success (0x90 0x00)
|
||||
if !rx.ends_with(&[0x90, 0x00]) {
|
||||
log::error!("Rescue Applet not found on the device!");
|
||||
return Err(AppError::Device(
|
||||
return Err(PFError::Device(
|
||||
// There is no such mode as fido, i tink the rescue applet stays active and at the same time fido mode works?
|
||||
// Need to study this more.
|
||||
"Rescue Applet not found on device. Is it in FIDO mode?".into(),
|
||||
@@ -48,12 +52,12 @@ fn connect_and_select() -> Result<(pcsc::Card, Vec<u8>), AppError> {
|
||||
Ok((card, rx.to_vec()))
|
||||
}
|
||||
|
||||
pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
|
||||
pub fn read_device_details() -> Result<FullDeviceStatus, PFError> {
|
||||
log::info!("Reading full device details");
|
||||
let (card, select_resp) = connect_and_select()?;
|
||||
|
||||
if select_resp.len() < 14 {
|
||||
return Err(AppError::Device("Invalid select response".into()));
|
||||
return Err(PFError::Device("Invalid select response".into()));
|
||||
}
|
||||
let version_major = select_resp[2];
|
||||
let version_minor = select_resp[3];
|
||||
@@ -73,7 +77,7 @@ pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
|
||||
)?;
|
||||
|
||||
if !rx_flash.ends_with(&SW_SUCCESS) {
|
||||
return Err(AppError::Device("Failed to read flash".into()));
|
||||
return Err(PFError::Device("Failed to read flash".into()));
|
||||
}
|
||||
|
||||
let mut rdr = Cursor::new(&rx_flash[..rx_flash.len() - 2]);
|
||||
@@ -116,7 +120,7 @@ pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
|
||||
)?;
|
||||
|
||||
if !rx_phy.ends_with(&[0x90, 0x00]) {
|
||||
return Err(AppError::Device("Failed to read config".into()));
|
||||
return Err(PFError::Device("Failed to read config".into()));
|
||||
}
|
||||
|
||||
// Parse TLV
|
||||
@@ -215,7 +219,7 @@ pub fn read_device_details() -> Result<FullDeviceStatus, AppError> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn write_config(config: AppConfigInput) -> Result<String, AppError> {
|
||||
pub fn write_config(config: AppConfigInput) -> Result<String, PFError> {
|
||||
log::info!("Writing configuration to device");
|
||||
log::debug!("Config input: {:?}", config);
|
||||
|
||||
@@ -225,9 +229,9 @@ pub 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()))?;
|
||||
u16::from_str_radix(vid_str, 16).map_err(|_| PFError::Io("Invalid VID".into()))?;
|
||||
let pid =
|
||||
u16::from_str_radix(pid_str, 16).map_err(|_| AppError::Io("Invalid PID".into()))?;
|
||||
u16::from_str_radix(pid_str, 16).map_err(|_| PFError::Io("Invalid PID".into()))?;
|
||||
|
||||
tlv.push(PhyTag::VidPid as u8);
|
||||
tlv.push(0x04);
|
||||
@@ -303,7 +307,7 @@ pub fn write_config(config: AppConfigInput) -> Result<String, AppError> {
|
||||
let name_bytes = name.as_bytes();
|
||||
let len = name_bytes.len() + 1;
|
||||
if len > 32 {
|
||||
return Err(AppError::Io("Product name too long".into()));
|
||||
return Err(PFError::Io("Product name too long".into()));
|
||||
}
|
||||
|
||||
tlv.push(PhyTag::UsbProduct as u8);
|
||||
@@ -341,11 +345,11 @@ pub fn write_config(config: AppConfigInput) -> Result<String, AppError> {
|
||||
Ok("Configuration Applied Successfully".into())
|
||||
} else {
|
||||
log::error!("Configuration write failed: {:02X?}", rx);
|
||||
Err(AppError::Device(format!("Write failed: {:02X?}", rx)))
|
||||
Err(PFError::Device(format!("Write failed: {:02X?}", rx)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reboot_device(to_bootsel: bool) -> Result<String, AppError> {
|
||||
pub fn reboot_device(to_bootsel: bool) -> Result<String, PFError> {
|
||||
let (card, _) = connect_and_select()?;
|
||||
|
||||
let param = if to_bootsel {
|
||||
@@ -368,12 +372,12 @@ pub fn reboot_device(to_bootsel: bool) -> Result<String, AppError> {
|
||||
if rx.ends_with(&SW_SUCCESS) {
|
||||
Ok("Reboot command sent".into())
|
||||
} else {
|
||||
Err(AppError::Device(format!("Reboot failed: {:02X?}", rx)))
|
||||
Err(PFError::Device(format!("Reboot failed: {:02X?}", rx)))
|
||||
}
|
||||
}
|
||||
|
||||
/// UNSTABLE! (WIP)
|
||||
pub fn enable_secure_boot(lock: bool) -> Result<String, AppError> {
|
||||
pub fn enable_secure_boot(lock: bool) -> Result<String, PFError> {
|
||||
let (card, _) = connect_and_select()?;
|
||||
|
||||
// APDU: 80 1D [KeyIndex] [LockBool] 00
|
||||
@@ -394,6 +398,6 @@ pub fn enable_secure_boot(lock: bool) -> Result<String, AppError> {
|
||||
if rx.ends_with(&[0x90, 0x00]) {
|
||||
Ok("Secure Boot Enabled".into())
|
||||
} else {
|
||||
Err(AppError::Device(format!("Secure Boot failed: {:02X?}", rx)))
|
||||
Err(PFError::Device(format!("Secure Boot failed: {:02X?}", rx)))
|
||||
}
|
||||
}
|
||||
|
||||
+5
-24
@@ -1,6 +1,10 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// --- Data Structures ---
|
||||
struct PForgeState {
|
||||
device_info: DeviceInfo,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -22,7 +26,6 @@ pub struct AppConfig {
|
||||
pub touch_timeout: u8,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub led_driver: Option<u8>,
|
||||
// New Options
|
||||
pub led_dimmable: bool,
|
||||
pub power_cycle_on_reset: bool,
|
||||
pub led_steady: bool,
|
||||
@@ -69,25 +72,3 @@ pub struct FidoDeviceInfo {
|
||||
pub min_pin_length: u32,
|
||||
pub firmware_version: String,
|
||||
}
|
||||
|
||||
// Error stuff:
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AppError {
|
||||
#[error("PCSC Error: {0}")]
|
||||
Pcsc(#[from] pcsc::Error),
|
||||
#[error("IO/Hex Error: {0}")]
|
||||
Io(String),
|
||||
#[error("Device Error: {0}")]
|
||||
Device(String),
|
||||
}
|
||||
|
||||
// Allow error to be serialized to string for Tauri
|
||||
impl serde::Serialize for AppError {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
import { Home, Info, Maximize, Minimize, Minus, RefreshCw, ScrollText, Settings, ShieldCheck, X } from "@lucide/svelte";
|
||||
import type { Component } from "svelte";
|
||||
@@ -22,11 +23,10 @@
|
||||
let { currentView, onViewChange, children }: Props = $props();
|
||||
|
||||
let isMaximized = $state(false);
|
||||
let unlistenResize: () => void;
|
||||
let unlistenWindowState: (() => void) | undefined;
|
||||
|
||||
const appWindow = getCurrentWindow();
|
||||
|
||||
// Menu items configuration
|
||||
const menuItems: Array<{ view: View; icon: Component; label: string }> = [
|
||||
{ view: "home", icon: Home, label: "Home" },
|
||||
{ view: "config", icon: Settings, label: "Configuration" },
|
||||
@@ -50,15 +50,18 @@
|
||||
onMount(() => {
|
||||
const setupWindow = async () => {
|
||||
isMaximized = await appWindow.isMaximized();
|
||||
unlistenResize = await appWindow.onResized(async () => {
|
||||
isMaximized = await appWindow.isMaximized();
|
||||
|
||||
unlistenWindowState = await listen<{ isMaximized: boolean }>("window-state-changed", (event) => {
|
||||
isMaximized = event.payload.isMaximized;
|
||||
});
|
||||
};
|
||||
|
||||
setupWindow();
|
||||
|
||||
return () => {
|
||||
if (unlistenResize) unlistenResize();
|
||||
if (unlistenWindowState) {
|
||||
unlistenWindowState();
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user