free_launch/
launch_item.rs1use freedesktop_desktop_entry::DesktopEntry;
2use std::fs::OpenOptions;
3use std::io::Write;
4use std::path::Path;
5use std::process::Command;
6
7use crate::free_launch;
8
9#[derive(Debug, Clone)]
10pub struct LaunchItem {
11 pub name: String,
12 pub exec: String,
13 pub icon: Option<String>,
14 pub comment: Option<String>,
15 pub selected: bool,
16 pub desktop_file_path: Option<String>,
17}
18
19impl LaunchItem {
20 pub(crate) fn from_desktop_file(path: &Path) -> Option<Self> {
21 let desktop_entry = DesktopEntry::from_path(path, Some(&free_launch::LOCALES)).ok()?;
22
23 let name = desktop_entry.name(&free_launch::LOCALES)?.to_string();
24 let exec = desktop_entry.exec()?.to_string();
25 let icon = desktop_entry.icon().map(|s| s.to_string());
26 let comment = desktop_entry
27 .comment(&free_launch::LOCALES)
28 .map(|s| s.to_string());
29
30 Some(LaunchItem {
31 name,
32 exec,
33 icon,
34 comment,
35 selected: false,
36 desktop_file_path: Some(path.to_string_lossy().to_string()),
37 })
38 }
39
40 pub(crate) fn launch(&self) {
41 let sanitized = self
47 .exec
48 .replace("%u", "")
49 .replace("%U", "")
50 .replace("%f", "")
51 .replace("%F", "")
52 .trim()
53 .to_string();
54
55 let mut parts = sanitized.split_whitespace();
60 if let Some(program) = parts.next() {
61 let args: Vec<&str> = parts.collect();
62
63 match Command::new(program).args(&args).spawn() {
66 Ok(_) => {
67 }
69 Err(e) => {
70 self.log_launch_error(program, &args, &e);
72 }
73 }
74 };
76 }
77
78 pub(crate) fn toggle_selected(&mut self) {
79 self.selected = !self.selected
80 }
81
82 pub(crate) fn open_directory(&self) {
83 if let Some(desktop_file_path) = &self.desktop_file_path {
84 let path = Path::new(desktop_file_path);
85 if let Some(parent_dir) = path.parent() {
86 let result = Command::new("xdg-open").arg(parent_dir).spawn();
88
89 if let Err(e) = result {
90 self.log_directory_open_error(parent_dir, &e);
91 }
92 }
93 }
94 }
95
96 fn log_directory_open_error(&self, directory: &Path, error: &std::io::Error) {
97 let log_path = "/var/log/free-launch.log";
98 let timestamp = std::time::SystemTime::now()
99 .duration_since(std::time::UNIX_EPOCH)
100 .unwrap_or_default()
101 .as_secs();
102
103 let log_entry = format!(
104 "[{}] ERROR: Failed to open directory '{}' for item '{}': {}\n",
105 timestamp,
106 directory.display(),
107 self.name,
108 error
109 );
110
111 match OpenOptions::new().create(true).append(true).open(log_path) {
113 Ok(mut file) => {
114 if let Err(write_err) = file.write_all(log_entry.as_bytes()) {
115 eprintln!("Failed to write to log file {}: {}", log_path, write_err);
116 eprintln!("{}", log_entry.trim());
117 }
118 }
119 Err(open_err) => {
120 eprintln!("Failed to open log file {}: {}", log_path, open_err);
121 eprintln!("{}", log_entry.trim());
122 }
123 }
124 }
125
126 fn log_launch_error(&self, program: &str, args: &[&str], error: &std::io::Error) {
127 let log_path = "/var/log/free-launch.log";
128 let timestamp = std::time::SystemTime::now()
129 .duration_since(std::time::UNIX_EPOCH)
130 .unwrap_or_default()
131 .as_secs();
132
133 let log_entry = format!(
134 "[{}] ERROR: Failed to launch '{}' with args {:?} from item '{}': {}\n",
135 timestamp, program, args, self.name, error
136 );
137
138 match OpenOptions::new().create(true).append(true).open(log_path) {
140 Ok(mut file) => {
141 if let Err(write_err) = file.write_all(log_entry.as_bytes()) {
142 eprintln!("Failed to write to log file {}: {}", log_path, write_err);
143 eprintln!("{}", log_entry.trim());
144 }
145 }
146 Err(open_err) => {
147 eprintln!("Failed to open log file {}: {}", log_path, open_err);
148 eprintln!("{}", log_entry.trim());
149 }
150 }
151 }
152}