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={v}, host supports 1. \
39                 Ignoring image labels."
40            );
41            return;
42        }
43        None => return,
44    }
45
46    let int = &mut config.integration;
47
48    apply_bool_compat(
49        labels,
50        "podbox.integration.wayland",
51        "podmgr.integration.wayland",
52        &mut int.wayland,
53    );
54    apply_bool_compat(
55        labels,
56        "podbox.integration.audio",
57        "podmgr.integration.audio",
58        &mut int.audio,
59    );
60    apply_bool_compat(
61        labels,
62        "podbox.integration.dbus",
63        "podmgr.integration.dbus",
64        &mut int.dbus,
65    );
66    apply_bool_compat(
67        labels,
68        "podbox.integration.notify",
69        "podmgr.integration.notify",
70        &mut int.notify,
71    );
72    apply_bool_compat(
73        labels,
74        "podbox.integration.xdg_open",
75        "podmgr.integration.xdg_open",
76        &mut int.xdg_open,
77    );
78    apply_bool_compat(
79        labels,
80        "podbox.integration.clipboard",
81        "podmgr.integration.clipboard",
82        &mut int.clipboard,
83    );
84    apply_bool_compat(
85        labels,
86        "podbox.integration.sync_fonts",
87        "podmgr.integration.sync_fonts",
88        &mut int.sync_fonts,
89    );
90    apply_bool_compat(
91        labels,
92        "podbox.integration.sync_icons",
93        "podmgr.integration.sync_icons",
94        &mut int.sync_icons,
95    );
96    apply_bool_compat(
97        labels,
98        "podbox.integration.sync_themes",
99        "podmgr.integration.sync_themes",
100        &mut int.sync_themes,
101    );
102
103    apply_xdg_compat(
104        labels,
105        "podbox.xdg_dirs.documents",
106        "podmgr.xdg_dirs.documents",
107        &mut int.xdg_dirs.documents,
108    );
109    apply_xdg_compat(
110        labels,
111        "podbox.xdg_dirs.downloads",
112        "podmgr.xdg_dirs.downloads",
113        &mut int.xdg_dirs.downloads,
114    );
115    apply_xdg_compat(
116        labels,
117        "podbox.xdg_dirs.pictures",
118        "podmgr.xdg_dirs.pictures",
119        &mut int.xdg_dirs.pictures,
120    );
121    apply_xdg_compat(
122        labels,
123        "podbox.xdg_dirs.music",
124        "podmgr.xdg_dirs.music",
125        &mut int.xdg_dirs.music,
126    );
127    apply_xdg_compat(
128        labels,
129        "podbox.xdg_dirs.videos",
130        "podmgr.xdg_dirs.videos",
131        &mut int.xdg_dirs.videos,
132    );
133    apply_xdg_compat(
134        labels,
135        "podbox.xdg_dirs.desktop",
136        "podmgr.xdg_dirs.desktop",
137        &mut int.xdg_dirs.desktop,
138    );
139    apply_xdg_compat(
140        labels,
141        "podbox.xdg_dirs.projects",
142        "podmgr.xdg_dirs.projects",
143        &mut int.xdg_dirs.projects,
144    );
145
146    // GPU: accept both keys
147    let gpu_key = if labels.contains_key("podbox.integration.gpu") {
148        "podbox.integration.gpu"
149    } else {
150        "podmgr.integration.gpu"
151    };
152    if let Some(gpu_str) = labels.get(gpu_key) {
153        if config.integration.gpu == GpuMode::Auto {
154            config.integration.gpu = match gpu_str.as_str() {
155                "true" => GpuMode::Enabled,
156                "false" => GpuMode::Disabled,
157                "nvidia" => GpuMode::Nvidia,
158                _ => GpuMode::Auto,
159            };
160        }
161    }
162
163    // Shell: accept both keys
164    let shell_key = if labels.contains_key("podbox.default_shell") {
165        "podbox.default_shell"
166    } else {
167        "podmgr.default_shell"
168    };
169    if let Some(shell) = labels.get(shell_key) {
170        if config.container.shell == "fish" {
171            config.container.shell.clone_from(shell);
172        }
173    }
174}
175
176fn apply_bool(labels: &LabelMap, key: &str, field: &mut bool) {
177    if let Some(v) = labels.get(key) {
178        *field = v == "true";
179    }
180}
181
182fn apply_bool_compat(labels: &LabelMap, new_key: &str, old_key: &str, field: &mut bool) {
183    // New key takes precedence; fall back to old key
184    if labels.contains_key(new_key) {
185        apply_bool(labels, new_key, field);
186    } else {
187        apply_bool(labels, old_key, field);
188    }
189}
190
191fn apply_xdg_compat(labels: &LabelMap, new_key: &str, old_key: &str, field: &mut XdgDirValue) {
192    let key = if labels.contains_key(new_key) {
193        new_key
194    } else {
195        old_key
196    };
197    if let Some(v) = labels.get(key) {
198        if v == "true" {
199            *field = XdgDirValue::Simple(true);
200        }
201    }
202}