proton_launch/command/
desktop_entry.rs1use 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}