use alloc::format;
use alloc::string::String;
use core::sync::atomic::{AtomicBool, Ordering};
use plat::policy::{Answer, Key};
use rt::sync::SpinLock;
use super::consts::*;
use super::i18n::{fill, t_text, tr};
use super::keys::IC_UNDO;
use super::win::Shared;
use super::Ui;
static KEY: SpinLock<Option<Key>> = SpinLock::new(None);
static ASKING: AtomicBool = AtomicBool::new(false);
const DLG: u32 = 0xFFFF_7700;
const B_ONCE: u32 = 0xFFFF_7701;
const B_ALWAYS: u32 = 0xFFFF_7702;
const B_DENY: u32 = 0xFFFF_7703;
const B_NEVER: u32 = 0xFFFF_7704;
#[derive(Default)]
pub(crate) struct S {
open: Shared<i32>,
shown: u32,
}
#[doc(hidden)]
pub fn boot(app: Option<&str>, manifest: Option<&str>, assets: &'static [u8]) -> Result<(), i32> {
plat::boot::name_and_assets(app, assets)?;
let headless = ["--shot", "--script"].iter().any(|o| plat::arg_value(o).is_some())
|| plat::args().iter().any(|a| a == "--agent-json" || a == "--agent-mcp");
crate::i18n::boot(headless);
let key = plat::boot::permissions(manifest)?;
if plat::arg_value("--script").is_some() && plat::env("jfgui_PERM_STORE").is_none() {
if let Some(k) = &key {
k.set_store(None);
}
}
*KEY.lock() = key;
Ok(())
}
fn key() -> Option<Key> {
KEY.lock().clone()
}
pub(crate) fn grant_user(path: &str) {
if let Some(k) = key() {
plat::boot::grant_user(&k, path);
}
}
pub(crate) fn trusted<R>(f: impl FnOnce() -> R) -> R {
match key() {
Some(k) => k.trusted(f),
None => f(),
}
}
pub(crate) fn grant_all(list: &[String]) {
for p in list {
grant_user(p);
}
}
pub(crate) fn ask_here(on: bool) {
if let Some(k) = key() {
k.ask_here(on);
ASKING.store(on, Ordering::Release);
}
}
pub(crate) fn asking() -> bool {
ASKING.load(Ordering::Acquire)
}
pub(crate) fn answer(id: u32, a: Answer) -> bool {
key().is_some_and(|k| k.answer(id, a))
}
pub(crate) fn set_answer(a: Option<Answer>) {
if let Some(k) = key() {
k.set_answer(a);
}
}
pub(crate) fn set_wait(ms: u32) {
if let Some(k) = key() {
k.set_wait(ms);
}
}
pub(crate) fn set_store(path: Option<&str>) {
if let Some(k) = key() {
k.set_store(path);
}
}
fn what(kind: &str) -> &'static str {
match kind {
"fs.read" => tr("Dateien lesen"),
"fs.write" => tr("Dateien schreiben"),
"process" => tr("Ein Programm starten"),
"url" => tr("Eine Adresse öffnen"),
"plugins" => tr("Ein Plugin laden"),
"print" => tr("Drucken"),
"secrets" => tr("Gespeicherte Kennwörter verwenden"),
"autostart" => tr("Beim Anmelden starten"),
"audio.output" => tr("Klänge abspielen"),
"net.connect" => tr("Eine Netzwerkverbindung aufbauen"),
"net.listen" => tr("Netzwerkverbindungen annehmen"),
"agent" => tr("Eine Aktion für den Agenten ausführen"),
"location" => tr("Den Standort abfragen"),
"sensors" => tr("Die Bewegungssensoren lesen"),
_ => tr("Zugriff erlauben?"),
}
}
fn state(a: Option<Answer>) -> &'static str {
match a {
None => tr("Wird beim nächsten Zugriff gefragt"),
Some(Answer::Once) => tr("Bis zum Programmende erlaubt"),
Some(Answer::Always) => tr("Immer erlaubt"),
Some(Answer::Deny) => tr("Bis zum Programmende abgelehnt"),
Some(Answer::Never) => tr("Nie erlaubt"),
}
}
impl Ui {
pub(crate) fn perm_dialog(&mut self) {
if !asking() {
return;
}
let qs = plat::policy::questions();
let Some(q) = qs.first() else {
self.m.perm.shown = 0;
return;
};
if self.m.perm.shown != q.id {
self.m.perm.shown = q.id;
self.m.perm.open.set(1);
}
let open = self.m.perm.open.clone();
let mut picked = None;
if self.dialog_begin(DLG, tr("Zugriff erlauben?"), &open) {
self.text(TS_BODY_STRONG, what(q.kind));
if !q.target.is_empty() {
self.text(TS_BODY, &q.target);
}
if !q.plugin.is_empty() {
self.text(TS_BODY, &fill(tr("Für das Plugin: {}"), &[&q.plugin]));
}
if !q.reason.is_empty() {
self.text(TS_BODY, &fill(tr("Grund: {}"), &[&t_text(&q.reason)]));
}
self.text(TS_CAPTION, &fill(tr("Regel: {}"), &[&q.rule]));
self.box_begin(0, BOX_H | BOX_WRAP, 8);
if self.accent_button(B_ONCE, tr("Einmal erlauben")) & EV_ACT != 0 {
picked = Some(Answer::Once);
}
if self.button(B_ALWAYS, tr("Immer erlauben")) & EV_ACT != 0 {
picked = Some(Answer::Always);
}
if self.button(B_DENY, tr("Ablehnen")) & EV_ACT != 0 {
picked = Some(Answer::Deny);
}
if self.button(B_NEVER, tr("Nie erlauben")) & EV_ACT != 0 {
picked = Some(Answer::Never);
}
self.box_end();
}
self.dialog_end();
if picked.is_none() && open.get() == 0 {
picked = Some(Answer::Deny); }
if let Some(a) = picked {
open.set(0);
self.m.perm.shown = 0;
if answer(q.id, a) {
self.cmd_run(UIC_PERMISSION, a.allows() as u32);
}
self.repass();
}
}
#[cfg_attr(jfgui_dev, track_caller)]
pub fn permissions_page(&mut self, id: u32) -> bool {
let _src = self.src_scope();
let list = plat::policy::decisions();
let mut changed = false;
self.id_push(id);
self.box_begin(0, BOX_V | BOX_CARD | pad(16), 12);
if list.is_empty() {
self.text(TS_BODY, tr("Keine Rückfragen"));
}
for d in &list {
self.id_push_str(&d.rule);
self.box_begin(0, BOX_H | BOX_XCENTER, 12);
self.next_size(-1, 0);
self.box_begin(0, BOX_V, 2);
self.text(TS_BODY_STRONG, what(d.kind));
let target = d.rule.split_once(' ').map_or("", |x| x.1);
if !target.is_empty() {
self.text(TS_BODY, target);
}
if !d.reason.is_empty() {
self.text(TS_CAPTION, &t_text(&d.reason));
}
self.text(TS_CAPTION, state(d.answer));
self.box_end();
if d.answer.is_none() {
self.next_flags(ST_DISABLED);
}
let reset = format!("{}: {}", tr("Zurücksetzen"), d.rule);
if self.icon_button(self.id("reset"), IC_UNDO, &reset) & EV_ACT != 0 {
changed |= plat::policy::forget(&d.rule);
}
self.box_end();
self.id_pop();
}
if !list.iter().any(|d| d.answer.is_some()) {
self.next_flags(ST_DISABLED);
}
if self.button(self.id("reset.all"), tr("Alle zurücksetzen")) & EV_ACT != 0 {
plat::policy::forget_all();
changed = true;
}
self.box_end();
self.id_pop();
if changed {
self.repass();
}
changed
}
}