1use alloc::format;
40use alloc::string::String;
41use core::sync::atomic::{AtomicBool, Ordering};
42
43use plat::policy::{Answer, Key};
44use rt::sync::SpinLock;
45
46use super::consts::*;
47use super::i18n::{fill, t_text, tr};
48use super::keys::IC_UNDO;
49use super::win::Shared;
50use super::Ui;
51
52static KEY: SpinLock<Option<Key>> = SpinLock::new(None);
53static ASKING: AtomicBool = AtomicBool::new(false);
55
56const DLG: u32 = 0xFFFF_7700;
57const B_ONCE: u32 = 0xFFFF_7701;
58const B_ALWAYS: u32 = 0xFFFF_7702;
59const B_DENY: u32 = 0xFFFF_7703;
60const B_NEVER: u32 = 0xFFFF_7704;
61
62#[derive(Default)]
64pub(crate) struct S {
65 open: Shared<i32>,
66 shown: u32,
67}
68
69#[doc(hidden)]
71pub fn boot(app: Option<&str>, manifest: Option<&str>, assets: &'static [u8]) -> Result<(), i32> {
72 plat::boot::name_and_assets(app, assets)?;
73 let headless = ["--shot", "--script"].iter().any(|o| plat::arg_value(o).is_some())
75 || plat::args().iter().any(|a| a == "--agent-json" || a == "--agent-mcp");
76 crate::i18n::boot(headless);
77 let key = plat::boot::permissions(manifest)?;
80 if plat::arg_value("--script").is_some() && plat::env("jfgui_PERM_STORE").is_none() {
82 if let Some(k) = &key {
83 k.set_store(None);
84 }
85 }
86 *KEY.lock() = key;
87 Ok(())
88}
89
90fn key() -> Option<Key> {
91 KEY.lock().clone()
92}
93
94pub(crate) fn grant_user(path: &str) {
96 if let Some(k) = key() {
97 plat::boot::grant_user(&k, path);
98 }
99}
100
101pub(crate) fn trusted<R>(f: impl FnOnce() -> R) -> R {
103 match key() {
104 Some(k) => k.trusted(f),
105 None => f(),
106 }
107}
108
109pub(crate) fn grant_all(list: &[String]) {
111 for p in list {
112 grant_user(p);
113 }
114}
115
116pub(crate) fn ask_here(on: bool) {
118 if let Some(k) = key() {
119 k.ask_here(on);
120 ASKING.store(on, Ordering::Release);
121 }
122}
123
124pub(crate) fn asking() -> bool {
126 ASKING.load(Ordering::Acquire)
127}
128
129pub(crate) fn answer(id: u32, a: Answer) -> bool {
131 key().is_some_and(|k| k.answer(id, a))
132}
133
134pub(crate) fn set_answer(a: Option<Answer>) {
136 if let Some(k) = key() {
137 k.set_answer(a);
138 }
139}
140
141pub(crate) fn set_wait(ms: u32) {
143 if let Some(k) = key() {
144 k.set_wait(ms);
145 }
146}
147
148pub(crate) fn set_store(path: Option<&str>) {
150 if let Some(k) = key() {
151 k.set_store(path);
152 }
153}
154
155fn what(kind: &str) -> &'static str {
157 match kind {
158 "fs.read" => tr("Dateien lesen"),
159 "fs.write" => tr("Dateien schreiben"),
160 "process" => tr("Ein Programm starten"),
161 "url" => tr("Eine Adresse öffnen"),
162 "plugins" => tr("Ein Plugin laden"),
163 "print" => tr("Drucken"),
164 "secrets" => tr("Gespeicherte Kennwörter verwenden"),
165 "autostart" => tr("Beim Anmelden starten"),
166 "audio.output" => tr("Klänge abspielen"),
167 "net.connect" => tr("Eine Netzwerkverbindung aufbauen"),
168 "net.listen" => tr("Netzwerkverbindungen annehmen"),
169 "agent" => tr("Eine Aktion für den Agenten ausführen"),
170 "location" => tr("Den Standort abfragen"),
171 "sensors" => tr("Die Bewegungssensoren lesen"),
172 _ => tr("Zugriff erlauben?"),
173 }
174}
175
176fn state(a: Option<Answer>) -> &'static str {
178 match a {
179 None => tr("Wird beim nächsten Zugriff gefragt"),
180 Some(Answer::Once) => tr("Bis zum Programmende erlaubt"),
181 Some(Answer::Always) => tr("Immer erlaubt"),
182 Some(Answer::Deny) => tr("Bis zum Programmende abgelehnt"),
183 Some(Answer::Never) => tr("Nie erlaubt"),
184 }
185}
186
187impl Ui {
188 pub(crate) fn perm_dialog(&mut self) {
190 if !asking() {
191 return;
192 }
193 let qs = plat::policy::questions();
194 let Some(q) = qs.first() else {
195 self.m.perm.shown = 0;
196 return;
197 };
198 if self.m.perm.shown != q.id {
199 self.m.perm.shown = q.id;
200 self.m.perm.open.set(1);
201 }
202 let open = self.m.perm.open.clone();
203 let mut picked = None;
204 if self.dialog_begin(DLG, tr("Zugriff erlauben?"), &open) {
205 self.text(TS_BODY_STRONG, what(q.kind));
206 if !q.target.is_empty() {
207 self.text(TS_BODY, &q.target);
208 }
209 if !q.plugin.is_empty() {
210 self.text(TS_BODY, &fill(tr("Für das Plugin: {}"), &[&q.plugin]));
211 }
212 if !q.reason.is_empty() {
213 self.text(TS_BODY, &fill(tr("Grund: {}"), &[&t_text(&q.reason)]));
214 }
215 self.text(TS_CAPTION, &fill(tr("Regel: {}"), &[&q.rule]));
216 self.box_begin(0, BOX_H | BOX_WRAP, 8);
217 if self.accent_button(B_ONCE, tr("Einmal erlauben")) & EV_ACT != 0 {
218 picked = Some(Answer::Once);
219 }
220 if self.button(B_ALWAYS, tr("Immer erlauben")) & EV_ACT != 0 {
221 picked = Some(Answer::Always);
222 }
223 if self.button(B_DENY, tr("Ablehnen")) & EV_ACT != 0 {
224 picked = Some(Answer::Deny);
225 }
226 if self.button(B_NEVER, tr("Nie erlauben")) & EV_ACT != 0 {
227 picked = Some(Answer::Never);
228 }
229 self.box_end();
230 }
231 self.dialog_end();
232 if picked.is_none() && open.get() == 0 {
233 picked = Some(Answer::Deny); }
235 if let Some(a) = picked {
236 open.set(0);
237 self.m.perm.shown = 0;
238 if answer(q.id, a) {
239 self.cmd_run(UIC_PERMISSION, a.allows() as u32);
240 }
241 self.repass();
242 }
243 }
244
245 #[cfg_attr(jfgui_dev, track_caller)]
248 pub fn permissions_page(&mut self, id: u32) -> bool {
249 let _src = self.src_scope();
250 let list = plat::policy::decisions();
251 let mut changed = false;
252 self.id_push(id);
253 self.box_begin(0, BOX_V | BOX_CARD | pad(16), 12);
254 if list.is_empty() {
255 self.text(TS_BODY, tr("Keine Rückfragen"));
256 }
257 for d in &list {
258 self.id_push_str(&d.rule);
259 self.box_begin(0, BOX_H | BOX_XCENTER, 12);
260 self.next_size(-1, 0);
261 self.box_begin(0, BOX_V, 2);
262 self.text(TS_BODY_STRONG, what(d.kind));
263 let target = d.rule.split_once(' ').map_or("", |x| x.1);
264 if !target.is_empty() {
265 self.text(TS_BODY, target);
266 }
267 if !d.reason.is_empty() {
268 self.text(TS_CAPTION, &t_text(&d.reason));
269 }
270 self.text(TS_CAPTION, state(d.answer));
271 self.box_end();
272 if d.answer.is_none() {
273 self.next_flags(ST_DISABLED);
274 }
275 let reset = format!("{}: {}", tr("Zurücksetzen"), d.rule);
276 if self.icon_button(self.id("reset"), IC_UNDO, &reset) & EV_ACT != 0 {
277 changed |= plat::policy::forget(&d.rule);
278 }
279 self.box_end();
280 self.id_pop();
281 }
282 if !list.iter().any(|d| d.answer.is_some()) {
283 self.next_flags(ST_DISABLED);
284 }
285 if self.button(self.id("reset.all"), tr("Alle zurücksetzen")) & EV_ACT != 0 {
286 plat::policy::forget_all();
287 changed = true;
288 }
289 self.box_end();
290 self.id_pop();
291 if changed {
292 self.repass();
293 }
294 changed
295 }
296}