Skip to main content

ui/
assoc.rs

1// assoc -- the app's file types and URL schemes at start-up (plat::assoc): the switches every
2// app gets through ui::main!
3//
4//   --register            register this program for PACKAGE filetypes= / schemes= (Windows:
5//                         HKCU\Software\Classes; Linux: a .desktop file, the own MIME types,
6//                         mimeapps.list); defaults of other programs stay and are listed
7//   --register-replace    the same, and those defaults go to this app too (the user's own pick
8//                         in Windows' dialog always stays)
9//   --unregister          remove exactly what the registration added, put back what it replaced
10// They print what they did (a Windows GUI program: on the console it was started from) and end
11// the program: exit code 0 done, 1 failed, 2 not possible here or nothing declared.
12//
13//   desk_boot(package) -> Option<exit code>   (main!) keep the built-in PACKAGE text
14//                         (plat::assoc::package_set), run a switch; headless runs (--shot,
15//                         --script, the agent protocols) leave the system's list of recent files
16//                         alone (plat::recent::recent_os(false); jfgui_RECENT still redirects it).
17//                         Without a switch, Windows: the app id as the explicit AppUserModelID
18//                         once the setup installed the program or --register registered it
19//                         (plat::assoc::app_id_boot), before the first window
20//   assoc_switch(pkg, switch) -> Option<(exit code, text)>   one switch for pkg (None: no switch)
21
22use alloc::format;
23use alloc::string::String;
24
25use plat::assoc::{self, AssocError, Conflict, Package};
26
27fn error_code(e: &AssocError) -> i32 {
28    match e {
29        AssocError::Unavailable | AssocError::Empty => 2,
30        _ => 1,
31    }
32}
33
34/// (main!) PACKAGE, then the switches -> Some(exit code) when one of them ran
35#[doc(hidden)]
36pub fn desk_boot(package: Option<&str>) -> Option<i32> {
37    assoc::package_set(package);
38    let args = plat::args();
39    let has = |o: &str| args.iter().skip(1).any(|a| a == o);
40    // as perm.rs: the runs without a window
41    let headless = ["--shot", "--script"].iter().any(|o| plat::arg_value(o).is_some()) || has("--agent-json") || has("--agent-mcp");
42    if headless {
43        plat::recent::recent_os(false);
44    }
45    let Some(switch) = ["--unregister", "--register-replace", "--register"].into_iter().find(|s| has(s)) else {
46        assoc::app_id_boot();
47        return None;
48    };
49    let (code, text) = assoc_switch(&assoc::package(), switch)?;
50    assoc::console_print(&text);
51    Some(code)
52}
53
54/// run --register / --register-replace / --unregister for pkg -> (exit code, what it says)
55pub fn assoc_switch(pkg: &Package, switch: &str) -> Option<(i32, String)> {
56    if switch == "--unregister" {
57        return Some(match assoc::assoc_unregister() {
58            Ok(true) => (0, String::from("unregistered: the file types and URL schemes are removed\n")),
59            Ok(false) => (0, String::from("nothing to unregister\n")),
60            Err(e) => (error_code(&e), format!("error: {e}\n")),
61        });
62    }
63    let replace = match switch {
64        "--register" => false,
65        "--register-replace" => true,
66        _ => return None,
67    };
68    let mut take = |_: &Conflict| replace;
69    Some(match assoc::assoc_register(pkg, &mut take) {
70        Ok(r) => {
71            let plural = |n: usize, one: &str, many: &str| format!("{n} {}", if n == 1 { one } else { many });
72            let mut out = format!("registered {}: {}, {}\n", r.exe, plural(r.types, "file type", "file types"), plural(r.schemes, "URL scheme", "URL schemes"));
73            for c in &r.kept {
74                let why = if c.fixed { "the user's choice in Windows' settings" } else { "--register-replace takes it" };
75                out.push_str(&format!("kept: {} stays with {} ({why})\n", c.what, c.holder));
76            }
77            for n in &r.notes {
78                out.push_str(&format!("note: {n}\n"));
79            }
80            (0, out)
81        }
82        Err(e) => {
83            let mut out = format!("error: {e}\n");
84            if let AssocError::Declined(_) = e {
85                out.push_str("(--register-replace moves it aside; --unregister puts it back)\n");
86            }
87            (error_code(&e), out)
88        }
89    })
90}