Skip to main content

podbox/
labels.rs

1use std::collections::HashMap;
2
3use crate::config::{Config, GpuMode, XdgDirValue};
4
5/// Raw OCI labels read from `podman inspect`.
6pub type LabelMap = HashMap<String, String>;
7
8/// Fetch the OCI labels for a local image tag.
9pub fn fetch(image_ref: &str) -> anyhow::Result<LabelMap> {
10    let output = std::process::Command::new("podman")
11        .args(["inspect", "--format", "{{json .Labels}}", image_ref])
12        .output()?;
13
14    if !output.status.success() {
15        return Ok(LabelMap::new());
16    }
17
18    let stdout = String::from_utf8_lossy(&output.stdout);
19    let map: LabelMap = serde_json::from_str(stdout.trim()).unwrap_or_default();
20    Ok(map)
21}
22
23/// Apply podbox label defaults to a Config, with the user config winning
24/// on every field that was explicitly set.
25///
26/// Accepts both `podbox.*` (preferred) and `podmgr.*` (compat fallback)
27/// label keys. New keys take precedence when both are present.
28pub fn apply_defaults(config: &mut Config, labels: &LabelMap) {
29    // Schema check: accept both new and old key
30    match labels
31        .get("podbox.schema")
32        .or_else(|| labels.get("podmgr.schema"))
33        .map(|s| s.as_str())
34    {
35        Some("1") => {}
36        Some(v) => {
37            eprintln!(
38                "Warning: image declares podbox.schema={}, host supports 1. \
39                 Ignoring image labels.",
40                v
41            );
42            return;
43        }
44        None => return,
45    }
46
47    let int = &mut config.integration;
48
49    apply_bool_compat(
50        labels,
51        "podbox.integration.wayland",
52        "podmgr.integration.wayland",
53        &mut int.wayland,
54    );
55    apply_bool_compat(
56        labels,
57        "podbox.integration.audio",
58        "podmgr.integration.audio",
59        &mut int.audio,
60    );
61    apply_bool_compat(
62        labels,
63        "podbox.integration.dbus",
64        "podmgr.integration.dbus",
65        &mut int.dbus,
66    );
67    apply_bool_compat(
68        labels,
69        "podbox.integration.notify",
70        "podmgr.integration.notify",
71        &mut int.notify,
72    );
73    apply_bool_compat(
74        labels,
75        "podbox.integration.xdg_open",
76        "podmgr.integration.xdg_open",
77        &mut int.xdg_open,
78    );
79    apply_bool_compat(
80        labels,
81        "podbox.integration.clipboard",
82        "podmgr.integration.clipboard",
83        &mut int.clipboard,
84    );
85    apply_bool_compat(
86        labels,
87        "podbox.integration.sync_fonts",
88        "podmgr.integration.sync_fonts",
89        &mut int.sync_fonts,
90    );
91    apply_bool_compat(
92        labels,
93        "podbox.integration.sync_icons",
94        "podmgr.integration.sync_icons",
95        &mut int.sync_icons,
96    );
97    apply_bool_compat(
98        labels,
99        "podbox.integration.sync_themes",
100        "podmgr.integration.sync_themes",
101        &mut int.sync_themes,
102    );
103
104    apply_xdg_compat(
105        labels,
106        "podbox.xdg_dirs.documents",
107        "podmgr.xdg_dirs.documents",
108        &mut int.xdg_dirs.documents,
109    );
110    apply_xdg_compat(
111        labels,
112        "podbox.xdg_dirs.downloads",
113        "podmgr.xdg_dirs.downloads",
114        &mut int.xdg_dirs.downloads,
115    );
116    apply_xdg_compat(
117        labels,
118        "podbox.xdg_dirs.pictures",
119        "podmgr.xdg_dirs.pictures",
120        &mut int.xdg_dirs.pictures,
121    );
122    apply_xdg_compat(
123        labels,
124        "podbox.xdg_dirs.music",
125        "podmgr.xdg_dirs.music",
126        &mut int.xdg_dirs.music,
127    );
128    apply_xdg_compat(
129        labels,
130        "podbox.xdg_dirs.videos",
131        "podmgr.xdg_dirs.videos",
132        &mut int.xdg_dirs.videos,
133    );
134    apply_xdg_compat(
135        labels,
136        "podbox.xdg_dirs.desktop",
137        "podmgr.xdg_dirs.desktop",
138        &mut int.xdg_dirs.desktop,
139    );
140    apply_xdg_compat(
141        labels,
142        "podbox.xdg_dirs.projects",
143        "podmgr.xdg_dirs.projects",
144        &mut int.xdg_dirs.projects,
145    );
146
147    // GPU: accept both keys
148    let gpu_key = if labels.contains_key("podbox.integration.gpu") {
149        "podbox.integration.gpu"
150    } else {
151        "podmgr.integration.gpu"
152    };
153    if let Some(gpu_str) = labels.get(gpu_key) {
154        if config.integration.gpu == GpuMode::Auto {
155            config.integration.gpu = match gpu_str.as_str() {
156                "true" => GpuMode::Enabled,
157                "false" => GpuMode::Disabled,
158                "nvidia" => GpuMode::Nvidia,
159                _ => GpuMode::Auto,
160            };
161        }
162    }
163
164    // Shell: accept both keys
165    let shell_key = if labels.contains_key("podbox.default_shell") {
166        "podbox.default_shell"
167    } else {
168        "podmgr.default_shell"
169    };
170    if let Some(shell) = labels.get(shell_key) {
171        if config.container.shell == "fish" {
172            config.container.shell = shell.clone();
173        }
174    }
175}
176
177fn apply_bool(labels: &LabelMap, key: &str, field: &mut bool) {
178    if let Some(v) = labels.get(key) {
179        *field = v == "true";
180    }
181}
182
183fn apply_bool_compat(labels: &LabelMap, new_key: &str, old_key: &str, field: &mut bool) {
184    // New key takes precedence; fall back to old key
185    if labels.contains_key(new_key) {
186        apply_bool(labels, new_key, field);
187    } else {
188        apply_bool(labels, old_key, field);
189    }
190}
191
192fn apply_xdg_compat(labels: &LabelMap, new_key: &str, old_key: &str, field: &mut XdgDirValue) {
193    let key = if labels.contains_key(new_key) {
194        new_key
195    } else {
196        old_key
197    };
198    if let Some(v) = labels.get(key) {
199        if v == "true" {
200            *field = XdgDirValue::Simple(true);
201        }
202    }
203}