proton_launch/command/
desktop_entry.rs

1use std::{
2    fs::File,
3    io::Write,
4    path::{Path, PathBuf},
5    process::Command,
6};
7
8use exe::{Buffer, ResourceDirectory, VecPE};
9use serde::{Deserialize, Serialize};
10
11use crate::{paths::Paths, steam::SteamData};
12
13use super::{Runnable, RunnableResult};
14
15#[derive(Debug, Clone)]
16#[cfg_attr(feature = "commandline", derive(clap::Args))]
17pub struct MakeDE {
18    exe: PathBuf,
19    name: String,
20    save_name: Option<String>,
21}
22
23fn make_icon(exe: &Path, paths: &Paths, name: &str) -> PathBuf {
24    let image = VecPE::from_disk_file(exe).unwrap();
25    let res = ResourceDirectory::parse(&image).unwrap();
26    let groups = res.icon_groups(&image).unwrap();
27    let v = groups.values().next().unwrap();
28    let buf = v.to_icon_buffer(&image).unwrap();
29    let img = image::load_from_memory(buf.as_slice()).unwrap();
30    let img = img.resize(256, 256, image::imageops::FilterType::Lanczos3);
31    let path = paths.icon_path(name);
32    img.save(&path).unwrap();
33    path
34}
35
36impl Runnable for MakeDE {
37    fn run(&self, paths: &Paths, _steam_data: &SteamData) -> RunnableResult<()> {
38        let save_name = self
39            .save_name
40            .as_deref()
41            .unwrap_or_else(|| self.exe.file_stem().unwrap().to_str().unwrap());
42        let mut de = DesktopEntry::new(self.name.clone());
43        de.comment = format!("Run {} with Proton", self.name);
44        de.exec = format!("proton-launch run -s {save_name} {} ", self.exe.display());
45        de.path = paths.run_dir(save_name).display().to_string();
46        let icon_path = make_icon(&self.exe, paths, &self.name);
47        de.icon = icon_path.display().to_string();
48
49        {
50            let mut f = File::create(paths.application_entry(&self.name)).unwrap();
51            writeln!(f, "[Desktop Entry]").unwrap();
52            let mut s = serde_ini::Serializer::new(serde_ini::Writer::new(
53                &mut f,
54                serde_ini::LineEnding::Linefeed,
55            ));
56            de.serialize(&mut s).unwrap();
57        }
58        let command = Command::new("update-desktop-database")
59            .arg(paths.application_entry(&self.name).parent().unwrap())
60            .spawn()
61            .unwrap()
62            .wait()
63            .unwrap();
64        println!("update-desktop-database exited with {}", command);
65        Ok(())
66    }
67}
68
69#[derive(Default, Serialize, Deserialize)]
70#[serde(rename_all = "PascalCase")]
71struct DesktopEntry {
72    #[serde(rename = "Type")]
73    xdg_type: String,
74    version: String,
75    name: String,
76    comment: String,
77    exec: String,
78    path: String,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    terminal: Option<String>,
81    icon: String,
82    generic_name: String,
83    categories: String,
84}
85
86impl DesktopEntry {
87    fn new(name: impl ToString) -> Self {
88        Self {
89            xdg_type: "Application".to_string(),
90            version: "1.0".to_string(),
91            name: name.to_string(),
92            comment: "".to_string(),
93            icon: name.to_string(),
94            exec: "".to_string(),
95            path: "".to_string(),
96            terminal: None,
97            generic_name: "Game".to_string(),
98            categories: "Game".to_string(),
99        }
100    }
101}