Skip to content

Commit ad7205c

Browse files
committed
feat: add destkop entries actions
1 parent 3f8253a commit ad7205c

File tree

6 files changed

+155
-56
lines changed

6 files changed

+155
-56
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dirs = "4.0.0"
3333
futures = "0.3.21"
3434
bytes = "1.1.0"
3535
recently-used-xbel = "1.0.0"
36+
shell-words = "1.1.0"
3637

3738
[dependencies.reqwest]
3839
version = "0.11.10"

plugins/src/calc/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ impl App {
7070
let options = vec![ContextOption {
7171
id: 0,
7272
name: "Qalculate! Manual".into(),
73+
description: "Browse Qalculate! user manual".to_string(),
74+
exec: None,
7375
}];
7476

7577
crate::send(&mut self.out, PluginResponse::Context { id: 0, options }).await;

plugins/src/desktop_entries/mod.rs

Lines changed: 142 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,86 @@ use futures::StreamExt;
99
use pop_launcher::*;
1010
use std::borrow::Cow;
1111
use std::hash::{Hash, Hasher};
12-
use std::path::PathBuf;
12+
use std::process::Command;
1313
use tokio::io::AsyncWrite;
14+
use tracing::error;
15+
use crate::desktop_entries::graphics::is_switchable;
1416

1517
#[derive(Debug, Eq)]
1618
struct Item {
1719
appid: String,
1820
description: String,
21+
context: Vec<ContextAction>,
22+
prefer_non_default_gpu: bool,
1923
exec: String,
2024
icon: Option<String>,
2125
keywords: Option<Vec<String>>,
2226
name: String,
23-
path: PathBuf,
24-
prefers_non_default_gpu: bool,
2527
src: PathSource,
2628
}
2729

30+
#[derive(Debug, PartialEq, Eq)]
31+
pub enum ContextAction {
32+
Action(Action),
33+
GpuPreference(GpuPreference)
34+
}
35+
36+
impl Item {
37+
fn run(&self, action_idx: Option<Indice>) {
38+
match action_idx {
39+
// No action provided just run the desktop entry with the default gpu
40+
None => run_exec_command(&self.exec, self.prefer_non_default_gpu),
41+
// Run the provided action
42+
Some(idx) => {
43+
match self.context.get(idx as usize) {
44+
None => error!("Could not find context action at index {idx}"),
45+
Some(action) => match action {
46+
ContextAction::Action(action) => run_exec_command(&action.exec, self.prefer_non_default_gpu),
47+
ContextAction::GpuPreference(pref) => match pref {
48+
GpuPreference::Default => run_exec_command(&self.exec, false),
49+
GpuPreference::NonDefault => run_exec_command(&self.exec, true),
50+
},
51+
},
52+
}
53+
}
54+
}
55+
}
56+
}
57+
58+
fn run_exec_command(exec: &str, discrete_graphics: bool) {
59+
let cmd = shell_words::split(exec);
60+
let cmd: Vec<String> = cmd.unwrap();
61+
62+
let args = cmd
63+
.iter()
64+
// Filter desktop entries field code. Is this needed ?
65+
// see: https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html
66+
.filter(|arg| !arg.starts_with('%'))
67+
.collect::<Vec<&String>>();
68+
69+
let mut cmd = Command::new(&args[0]);
70+
let cmd = cmd.args(&args[1..]);
71+
72+
// FIXME: Will this work with Nvidia Gpu ?
73+
// We probably want to look there : https://gitlab.freedesktop.org/hadess/switcheroo-control/-/blob/master/src/switcheroo-control.c
74+
let cmd = if discrete_graphics {
75+
cmd.env("DRI_PRIME", "1")
76+
} else {
77+
cmd
78+
};
79+
80+
if let Err(err) = cmd.spawn() {
81+
error!("Failed to run desktop entry: {err}");
82+
}
83+
}
84+
85+
#[derive(Debug, PartialEq, Eq)]
86+
pub struct Action {
87+
pub name: String,
88+
pub description: String,
89+
pub exec: String,
90+
}
91+
2892
impl Hash for Item {
2993
fn hash<H: Hasher>(&self, state: &mut H) {
3094
self.appid.hash(state);
@@ -150,6 +214,44 @@ impl<W: AsyncWrite + Unpin> App<W> {
150214
continue;
151215
}
152216

217+
let mut actions = vec![];
218+
219+
if let Some(entries) = entry.actions() {
220+
for action in entries.split(';') {
221+
let action =
222+
entry.action_name(action, locale).and_then(|name| {
223+
entry.action_exec(action).map(|exec| Action {
224+
name: action.to_string(),
225+
description: name.to_string(),
226+
exec: exec.to_string(),
227+
})
228+
});
229+
230+
if let Some(action) = action {
231+
actions.push(action);
232+
}
233+
}
234+
}
235+
236+
let actions = actions
237+
.into_iter()
238+
.map(|action| ContextAction::Action(action));
239+
240+
let entry_prefers_non_default_gpu = entry.prefers_non_default_gpu();
241+
let prefer_non_default_gpu = entry_prefers_non_default_gpu && is_switchable();
242+
let prefer_default_gpu = !entry_prefers_non_default_gpu && is_switchable();
243+
244+
let context: Vec<ContextAction> = if prefer_non_default_gpu {
245+
vec![ContextAction::GpuPreference(GpuPreference::Default)]
246+
} else if prefer_default_gpu {
247+
vec![ContextAction::GpuPreference(GpuPreference::NonDefault)]
248+
} else {
249+
vec![]
250+
}
251+
.into_iter()
252+
.chain(actions)
253+
.collect();
254+
153255
let item = Item {
154256
appid: entry.appid.to_owned(),
155257
name: name.to_string(),
@@ -163,9 +265,9 @@ impl<W: AsyncWrite + Unpin> App<W> {
163265
}),
164266
icon: entry.icon().map(|x| x.to_owned()),
165267
exec: exec.to_owned(),
166-
path: path.clone(),
167-
prefers_non_default_gpu: entry.prefers_non_default_gpu(),
168268
src,
269+
context,
270+
prefer_non_default_gpu: entry_prefers_non_default_gpu
169271
};
170272

171273
deduplicator.insert(item);
@@ -179,57 +281,58 @@ impl<W: AsyncWrite + Unpin> App<W> {
179281
}
180282

181283
async fn activate(&mut self, id: u32) {
284+
send(&mut self.tx, PluginResponse::Close).await;
285+
182286
if let Some(entry) = self.entries.get(id as usize) {
183-
let response = PluginResponse::DesktopEntry {
184-
path: entry.path.clone(),
185-
gpu_preference: if entry.prefers_non_default_gpu {
186-
GpuPreference::NonDefault
187-
} else {
188-
GpuPreference::Default
189-
},
190-
};
191-
192-
send(&mut self.tx, response).await;
287+
entry.run(None);
288+
} else {
289+
error!("Desktop entry not found at index {id}");
193290
}
291+
292+
std::process::exit(0);
194293
}
195294

196295
async fn activate_context(&mut self, id: u32, context: u32) {
197-
if let Some(entry) = self.entries.get(id as usize) {
198-
let response = match context {
199-
0 => PluginResponse::DesktopEntry {
200-
path: entry.path.clone(),
201-
gpu_preference: if !entry.prefers_non_default_gpu {
202-
GpuPreference::NonDefault
203-
} else {
204-
GpuPreference::Default
205-
},
206-
},
207-
_ => return,
208-
};
296+
send(&mut self.tx, PluginResponse::Close).await;
209297

210-
send(&mut self.tx, response).await;
298+
if let Some(entry) = self.entries.get(id as usize) {
299+
entry.run(Some(context))
211300
}
212301
}
213302

214303
async fn context(&mut self, id: u32) {
215304
if let Some(entry) = self.entries.get(id as usize) {
216305
let mut options = Vec::new();
217306

218-
if graphics::is_switchable() {
219-
options.push(ContextOption {
220-
id: 0,
221-
name: (if entry.prefers_non_default_gpu {
222-
"Launch Using Integrated Graphics Card"
223-
} else {
224-
"Launch Using Discrete Graphics Card"
225-
})
226-
.to_owned(),
227-
});
307+
for (idx, action) in entry.context.iter().enumerate() {
308+
match action {
309+
ContextAction::Action(action) => options.push(ContextOption {
310+
id: idx as u32,
311+
name: action.name.to_owned(),
312+
description: action.description.to_owned(),
313+
exec: Some(action.exec.to_string()),
314+
}),
315+
ContextAction::GpuPreference(pref) => {
316+
match pref {
317+
GpuPreference::Default => options.push(ContextOption {
318+
id: 0,
319+
name: "Integrated Graphics".to_owned(),
320+
description: "Launch Using Integrated Graphics Card".to_owned(),
321+
exec: None,
322+
}),
323+
GpuPreference::NonDefault => options.push(ContextOption {
324+
id: 0,
325+
name: "Discrete Graphics".to_owned(),
326+
description: "Launch Using Discrete Graphics Card".to_owned(),
327+
exec: None,
328+
}),
329+
}
330+
}
331+
}
228332
}
229333

230334
if !options.is_empty() {
231335
let response = PluginResponse::Context { id, options };
232-
233336
send(&mut self.tx, response).await;
234337
}
235338
}

service/src/lib.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -166,17 +166,6 @@ impl<O: futures::Sink<Response> + Unpin> Service<O> {
166166
}
167167
PluginResponse::Fill(text) => self.fill(text).await,
168168
PluginResponse::Finished => self.finished(plugin).await,
169-
PluginResponse::DesktopEntry {
170-
path,
171-
gpu_preference,
172-
} => {
173-
self.respond(Response::DesktopEntry {
174-
path,
175-
gpu_preference,
176-
})
177-
.await;
178-
}
179-
180169
// Report the plugin as finished and remove it from future polling
181170
PluginResponse::Deactivate => {
182171
self.finished(plugin).await;

src/lib.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ pub type Indice = u32;
4848
pub struct ContextOption {
4949
pub id: Indice,
5050
pub name: String,
51+
pub description: String,
52+
pub exec: Option<String>,
5153
}
5254

53-
#[derive(Debug, Deserialize, Serialize, Clone)]
55+
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
5456
pub enum GpuPreference {
5557
Default,
5658
NonDefault,
@@ -80,11 +82,6 @@ pub enum PluginResponse {
8082
},
8183
/// Instruct the launcher service to deactivate this plugin.
8284
Deactivate,
83-
// Notifies that a .desktop entry should be launched by the frontend.
84-
DesktopEntry {
85-
path: PathBuf,
86-
gpu_preference: GpuPreference,
87-
},
8885
/// Update the text in the launcher.
8986
Fill(String),
9087
/// Indicoates that a plugin is finished with its queries.

0 commit comments

Comments
 (0)