mirror of
https://github.com/librekeys/picoforge.git
synced 2026-07-28 08:01:19 -07:00
feat: map RS-Key bcdDevice to release tag, convert buttons to PFButton, fix sidebar clipping
Home view — RS-Key bcdDevice → release tag lookup: - Add rs_key_version_from_bcd() lookup table covering 21 known bcdDevice values from v0.4.0 through v0.4.4, sourced from the RS-Key CHANGELOG and git tags - firmware_version_label() shows "RS-Key v0.4.4 (build 0x085B)" on match, "RS-Key build 0x085B" on unknown value, unchanged for non-RS-Key firmware Button styling — convert outline buttons across all views to PFButton: - slots, audit, backup, attestation, openpgp, piv views: all action buttons now use PFButton with colors #222225/#2a2a2d/#333336 - action_row signatures changed from btn: Button to btn: impl IntoElement in backup, attestation, openpgp, piv views Sidebar — fix device status box clipping regression: - Wrap nav_sidebar in div().flex_1().min_h(px(0.)).overflow_hidden() to allow sidebar content to shrink below its intrinsic height now that nav items grew from 5 to 15 (4 groups)
This commit is contained in:
@@ -169,9 +169,7 @@ impl Render for AppSidebar {
|
||||
let nav_sidebar = Sidebar::new(Side::Left)
|
||||
.collapsed(sidebar_width < px(120.))
|
||||
.collapsible(false)
|
||||
.h_auto()
|
||||
.w_full()
|
||||
.flex_grow()
|
||||
.bg(sidebar_bg)
|
||||
.border_color(gpui::transparent_white())
|
||||
// Grouped so the panel reads as sections, not one long list: the
|
||||
@@ -198,13 +196,13 @@ impl Render for AppSidebar {
|
||||
.child(self.menu_item(
|
||||
cx,
|
||||
"Accounts",
|
||||
"icons/key.svg",
|
||||
"icons/users-round.svg",
|
||||
Destination::Accounts,
|
||||
))
|
||||
.child(self.menu_item(
|
||||
cx,
|
||||
"Slots",
|
||||
"icons/asterisk.svg",
|
||||
"icons/touch-app.svg",
|
||||
Destination::Slots,
|
||||
))
|
||||
.child(self.menu_item(cx, "PIV", "icons/shield.svg", Destination::Piv))
|
||||
@@ -360,7 +358,13 @@ impl Render for AppSidebar {
|
||||
.border_color(border_color)
|
||||
.w(sidebar_width)
|
||||
.child(header)
|
||||
.child(nav_sidebar)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_h(px(0.))
|
||||
.overflow_hidden()
|
||||
.child(nav_sidebar),
|
||||
)
|
||||
.child(footer);
|
||||
|
||||
div()
|
||||
|
||||
@@ -268,7 +268,7 @@ impl Render for AccountsViewModel {
|
||||
let accounts_card = Card::new()
|
||||
.title("Accounts")
|
||||
.description(format!("{} stored", accounts.len()))
|
||||
.icon(Icon::default().path("icons/key.svg"))
|
||||
.icon(Icon::default().path("icons/users-round.svg"))
|
||||
.header_right(toolbar)
|
||||
.child(list);
|
||||
let reset_card = Card::new()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Attestation screen rendering.
|
||||
|
||||
use crate::ui::components::button::PFButton;
|
||||
use crate::ui::components::card::Card;
|
||||
use crate::ui::components::page_view::PageView;
|
||||
use crate::ui::screens::attestation::view_model::AttestationViewModel;
|
||||
@@ -32,7 +33,7 @@ impl AttestationViewModel {
|
||||
&self,
|
||||
title: &'static str,
|
||||
subtitle: &'static str,
|
||||
btn: Button,
|
||||
btn: impl IntoElement,
|
||||
theme: &Theme,
|
||||
) -> impl IntoElement {
|
||||
h_flex()
|
||||
@@ -76,9 +77,9 @@ impl Render for AttestationViewModel {
|
||||
.ghost()
|
||||
.disabled(self.loading)
|
||||
.on_click(cx.listener(|this, _, _, cx| this.refresh(cx)));
|
||||
let import_btn = Button::new("att-import")
|
||||
.label(if installed { "Replace" } else { "Import" })
|
||||
.outline()
|
||||
let import_btn = PFButton::new(if installed { "Replace" } else { "Import" })
|
||||
.id("att-import")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.disabled(self.loading)
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_import(window, cx)));
|
||||
let clear_btn = Button::new("att-clear")
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//! Audit screen rendering.
|
||||
|
||||
use crate::ui::components::button::PFButton;
|
||||
use crate::ui::components::card::Card;
|
||||
use crate::ui::components::page_view::PageView;
|
||||
use crate::ui::models::device::audit;
|
||||
use crate::ui::screens::audit::view_model::AuditViewModel;
|
||||
use gpui::*;
|
||||
use gpui_component::button::Button;
|
||||
use gpui_component::{ActiveTheme, Disableable, Icon, StyledExt, Theme, h_flex, v_flex};
|
||||
use gpui_component::{ActiveTheme, Icon, StyledExt, Theme, h_flex, v_flex};
|
||||
|
||||
fn empty_state(heading: &str, body: String, theme: &Theme) -> AnyElement {
|
||||
v_flex()
|
||||
@@ -207,31 +207,30 @@ impl Render for AuditViewModel {
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
let read_btn = Button::new("audit-read")
|
||||
.icon(Icon::default().path("icons/refresh-cw.svg"))
|
||||
.label("Read journal")
|
||||
.outline()
|
||||
let read_btn = PFButton::new("Read journal")
|
||||
.id("audit-read")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.disabled(self.loading)
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_read(window, cx)));
|
||||
let verify_btn = Button::new("audit-verify")
|
||||
.label("Verify")
|
||||
.outline()
|
||||
let verify_btn = PFButton::new("Verify")
|
||||
.id("audit-verify")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.disabled(self.loading)
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_verify(window, cx)));
|
||||
let toggle_btn = match self.enabled {
|
||||
Some(true) => Some(
|
||||
Button::new("audit-disable")
|
||||
.label("Disable")
|
||||
.outline()
|
||||
PFButton::new("Disable")
|
||||
.id("audit-disable")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.disabled(self.loading)
|
||||
.on_click(
|
||||
cx.listener(|this, _, window, cx| this.open_toggle(false, window, cx)),
|
||||
),
|
||||
),
|
||||
Some(false) => Some(
|
||||
Button::new("audit-enable")
|
||||
.label("Enable")
|
||||
.outline()
|
||||
PFButton::new("Enable")
|
||||
.id("audit-enable")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.disabled(self.loading)
|
||||
.on_click(
|
||||
cx.listener(|this, _, window, cx| this.open_toggle(true, window, cx)),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Backup screen rendering.
|
||||
|
||||
use crate::ui::components::button::PFButton;
|
||||
use crate::ui::components::card::Card;
|
||||
use crate::ui::components::page_view::PageView;
|
||||
use crate::ui::screens::backup::view_model::BackupViewModel;
|
||||
@@ -32,7 +33,7 @@ impl BackupViewModel {
|
||||
&self,
|
||||
title: &'static str,
|
||||
subtitle: &'static str,
|
||||
btn: Button,
|
||||
btn: impl IntoElement,
|
||||
theme: &Theme,
|
||||
) -> impl IntoElement {
|
||||
h_flex()
|
||||
@@ -130,9 +131,9 @@ impl Render for BackupViewModel {
|
||||
.danger()
|
||||
.disabled(self.loading)
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_export(window, cx)));
|
||||
let seal_btn = Button::new("bk-seal")
|
||||
.label("Seal window")
|
||||
.outline()
|
||||
let seal_btn = PFButton::new("Seal window")
|
||||
.id("bk-seal")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.disabled(self.loading)
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_finalize(window, cx)));
|
||||
let restore_btn = Button::new("bk-restore")
|
||||
|
||||
@@ -41,7 +41,11 @@ impl HomeViewModel {
|
||||
if status.firmware_type == FirmwareType::RSKey
|
||||
&& let Some(bcd) = status.info.bcd_device
|
||||
{
|
||||
format!("RS-Key 0x{:04X}", bcd)
|
||||
if let Some(ver) = Self::rs_key_version_from_bcd(bcd) {
|
||||
format!("RS-Key {} (build 0x{:04X})", ver, bcd)
|
||||
} else {
|
||||
format!("RS-Key build 0x{:04X}", bcd)
|
||||
}
|
||||
} else {
|
||||
format!("v{}", status.info.firmware_version)
|
||||
}
|
||||
|
||||
@@ -16,4 +16,33 @@ impl HomeViewModel {
|
||||
.detach();
|
||||
Self { device }
|
||||
}
|
||||
|
||||
/// Map an RS-Key USB `bcdDevice` build counter to a release tag.
|
||||
///
|
||||
/// RS-Key's `bcdDevice` is a **monotonic build counter** (bumped on every
|
||||
/// behaviour change), not a BCD-encoded version number — there is no
|
||||
/// mathematical conversion to semver. This table provides the known
|
||||
/// mapping for released versions. The data comes from the RS-Key
|
||||
/// CHANGELOG (https://github.com/TheMaxMur/RS-Key/blob/main/CHANGELOG.md)
|
||||
/// and the project's git tags.
|
||||
///
|
||||
/// When RS-Key ships a new release, add its `bcdDevice` value(s) here.
|
||||
/// Unknown values fall back to a bare hex display in the caller.
|
||||
pub fn rs_key_version_from_bcd(bcd: u16) -> Option<&'static str> {
|
||||
// Keep sorted for readability; matches are exact.
|
||||
let (tag, _bcd) = match bcd {
|
||||
// v0.4.4 — challenge-response fixes, OTP frame protocol, touch gate
|
||||
0x0859..=0x085B => ("v0.4.4", bcd),
|
||||
// v0.4.3 — CTAP 2.1 text pass, 28th security audit
|
||||
0x0857 | 0x0858 => ("v0.4.3", bcd),
|
||||
// v0.4.2 — fingerprint-free credential IDs, makeCredUvNotRqd
|
||||
0x0851..=0x0855 => ("v0.4.2", bcd),
|
||||
// v0.4.1 — ykman interop fixes, OATH CALCULATE ALL, CCID ATR
|
||||
0x084A..=0x0850 => ("v0.4.1", bcd),
|
||||
// v0.4.0 — USB identity, audit journal, security fixes
|
||||
0x083D | 0x0847 | 0x0848 | 0x0849 => ("v0.4.0", bcd),
|
||||
_ => return None,
|
||||
};
|
||||
Some(tag)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! OpenPGP screen rendering.
|
||||
|
||||
use crate::ui::components::button::PFButton;
|
||||
use crate::ui::components::card::Card;
|
||||
use crate::ui::components::page_view::PageView;
|
||||
use crate::ui::models::device::openpgp;
|
||||
@@ -97,9 +98,9 @@ impl OpenPgpViewModel {
|
||||
.gap_2()
|
||||
.flex_wrap()
|
||||
.child(
|
||||
Button::new(SharedString::from(format!("gen-{}", slot.label())))
|
||||
.label(if k.present { "Regenerate" } else { "Generate" })
|
||||
.outline()
|
||||
PFButton::new(if k.present { "Regenerate" } else { "Generate" })
|
||||
.id(format!("gen-{}", slot.label()))
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.disabled(d)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.open_generate(slot, window, cx);
|
||||
@@ -122,7 +123,7 @@ impl OpenPgpViewModel {
|
||||
&self,
|
||||
title: &'static str,
|
||||
subtitle: &'static str,
|
||||
btn: Button,
|
||||
btn: impl IntoElement,
|
||||
theme: &Theme,
|
||||
) -> impl IntoElement {
|
||||
h_flex()
|
||||
@@ -171,29 +172,29 @@ impl Render for OpenPgpViewModel {
|
||||
.ghost()
|
||||
.disabled(self.loading)
|
||||
.on_click(cx.listener(|this, _, _, cx| this.refresh(cx)));
|
||||
let change_user_btn = Button::new("pgp-change-user")
|
||||
.label("Change User PIN")
|
||||
.outline()
|
||||
let change_user_btn = PFButton::new("Change User PIN")
|
||||
.id("pgp-change-user")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_change_user_pin(window, cx)));
|
||||
let change_admin_btn = Button::new("pgp-change-admin")
|
||||
.label("Change Admin PIN")
|
||||
.outline()
|
||||
let change_admin_btn = PFButton::new("Change Admin PIN")
|
||||
.id("pgp-change-admin")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_change_admin_pin(window, cx)));
|
||||
let unblock_code_btn = Button::new("pgp-unblock-code")
|
||||
.label("Reset code")
|
||||
.outline()
|
||||
let unblock_code_btn = PFButton::new("Reset code")
|
||||
.id("pgp-unblock-code")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_unblock_with_code(window, cx)));
|
||||
let unblock_admin_btn = Button::new("pgp-unblock-admin")
|
||||
.label("Admin PIN")
|
||||
.outline()
|
||||
let unblock_admin_btn = PFButton::new("Admin PIN")
|
||||
.id("pgp-unblock-admin")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_unblock_with_admin(window, cx)));
|
||||
let reset_code_btn = Button::new("pgp-set-rc")
|
||||
.label("Set reset code")
|
||||
.outline()
|
||||
let reset_code_btn = PFButton::new("Set reset code")
|
||||
.id("pgp-set-rc")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_set_reset_code(window, cx)));
|
||||
let cardholder_btn = Button::new("pgp-cardholder")
|
||||
.label("Edit")
|
||||
.outline()
|
||||
let cardholder_btn = PFButton::new("Edit")
|
||||
.id("pgp-cardholder")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_cardholder(window, cx)));
|
||||
let reset_btn = Button::new("pgp-reset")
|
||||
.label("Reset OpenPGP applet")
|
||||
|
||||
+20
-19
@@ -1,5 +1,6 @@
|
||||
//! PIV screen rendering.
|
||||
|
||||
use crate::ui::components::button::PFButton;
|
||||
use crate::ui::components::card::Card;
|
||||
use crate::ui::components::page_view::PageView;
|
||||
use crate::ui::models::device::piv;
|
||||
@@ -78,9 +79,9 @@ impl PivViewModel {
|
||||
}
|
||||
|
||||
let mut btns: Vec<AnyElement> = vec![
|
||||
Button::new(SharedString::from(format!("gen-{slot:02x}")))
|
||||
.label(if has_key { "Regenerate" } else { "Generate" })
|
||||
.outline()
|
||||
PFButton::new(if has_key { "Regenerate" } else { "Generate" })
|
||||
.id(format!("gen-{slot:02x}"))
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.disabled(d)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.open_generate_dialog(slot, window, cx);
|
||||
@@ -144,7 +145,7 @@ impl PivViewModel {
|
||||
&self,
|
||||
title: &'static str,
|
||||
subtitle: &'static str,
|
||||
btn: Button,
|
||||
btn: impl IntoElement,
|
||||
theme: &Theme,
|
||||
) -> impl IntoElement {
|
||||
h_flex()
|
||||
@@ -195,25 +196,25 @@ impl Render for PivViewModel {
|
||||
.ghost()
|
||||
.disabled(self.loading)
|
||||
.on_click(cx.listener(|this, _, _, cx| this.refresh(cx)));
|
||||
let change_pin_btn = Button::new("piv-change-pin")
|
||||
.label("Change PIN")
|
||||
.outline()
|
||||
let change_pin_btn = PFButton::new("Change PIN")
|
||||
.id("piv-change-pin")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_change_pin(false, window, cx)));
|
||||
let change_puk_btn = Button::new("piv-change-puk")
|
||||
.label("Change PUK")
|
||||
.outline()
|
||||
let change_puk_btn = PFButton::new("Change PUK")
|
||||
.id("piv-change-puk")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_change_pin(true, window, cx)));
|
||||
let unblock_btn = Button::new("piv-unblock")
|
||||
.label("Unblock PIN")
|
||||
.outline()
|
||||
let unblock_btn = PFButton::new("Unblock PIN")
|
||||
.id("piv-unblock")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_unblock_pin(window, cx)));
|
||||
let retries_btn = Button::new("piv-retries")
|
||||
.label("Set retries")
|
||||
.outline()
|
||||
let retries_btn = PFButton::new("Set retries")
|
||||
.id("piv-retries")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_set_retries(window, cx)));
|
||||
let mgm_btn = Button::new("piv-mgm")
|
||||
.label("Change key")
|
||||
.outline()
|
||||
let mgm_btn = PFButton::new("Change key")
|
||||
.id("piv-mgm")
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.on_click(cx.listener(|this, _, window, cx| this.open_change_mgm(window, cx)));
|
||||
let reset_btn = Button::new("piv-reset")
|
||||
.label("Reset PIV applet")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Slots (OTP) screen rendering.
|
||||
|
||||
use crate::ui::components::button::PFButton;
|
||||
use crate::ui::components::card::Card;
|
||||
use crate::ui::components::page_view::PageView;
|
||||
use crate::ui::models::device::otp;
|
||||
@@ -44,9 +45,9 @@ impl SlotsViewModel {
|
||||
"Empty".to_string()
|
||||
};
|
||||
|
||||
let program_btn = Button::new(SharedString::from(format!("prog-{slot}")))
|
||||
.label(if configured { "Reprogram" } else { "Program" })
|
||||
.outline()
|
||||
let program_btn = PFButton::new(if configured { "Reprogram" } else { "Program" })
|
||||
.id(format!("prog-{slot}"))
|
||||
.with_colors(rgb(0x222225), rgb(0x2a2a2d), rgb(0x333336))
|
||||
.disabled(self.loading)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.open_program_dialog(slot, window, cx);
|
||||
@@ -137,7 +138,7 @@ impl Render for SlotsViewModel {
|
||||
let slots_card = Card::new()
|
||||
.title("Slots")
|
||||
.description(format!("{count} configurable slots"))
|
||||
.icon(Icon::default().path("icons/asterisk.svg"))
|
||||
.icon(Icon::default().path("icons/touch-app.svg"))
|
||||
.header_right(toolbar)
|
||||
.child(v_flex().gap_2().children(cards));
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M419-80q-28 0-52.5-12T325-126L107-403l19-20q20-21 48-25t52 11l74 45v-328q0-17 11.5-28.5T340-760q17 0 29 11.5t12 28.5v472l-97-60 104 133q6 7 14 11t17 4h221q33 0 56.5-23.5T720-240v-160q0-17-11.5-28.5T680-440H461v-80h219q50 0 85 35t35 85v160q0 66-47 113T640-80H419ZM167-620q-13-22-20-47.5t-7-52.5q0-83 58.5-141.5T340-920q83 0 141.5 58.5T540-720q0 27-7 52.5T513-620l-69-40q8-14 12-28.5t4-31.5q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 17 4 31.5t12 28.5l-69 40Zm335 280Z"/></svg>
|
||||
|
After Width: | Height: | Size: 587 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users-round-icon lucide-users-round"><path d="M18 21a8 8 0 0 0-16 0"/><circle cx="10" cy="8" r="5"/><path d="M22 20c0-3.37-2-6.5-4-8a5 5 0 0 0-.45-8.3"/></svg>
|
||||
|
After Width: | Height: | Size: 361 B |
Reference in New Issue
Block a user