Skip to content

Commit 3f8253a

Browse files
oknozormmstick
authored andcommitted
docs(toolkit): add a plugin example
1 parent 8293392 commit 3f8253a

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed

Cargo.lock

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

toolkit/Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,12 @@ async-trait = "0.1.53"
1414
tracing = "0.1.32"
1515
tracing-subscriber = { version = "0.3.9", default-features = false, features = ["std", "fmt", "env-filter"] }
1616
dirs = "4.0.0"
17-
futures = "0.3.21"
17+
futures = "0.3.21"
18+
19+
[dev-dependencies]
20+
tokio = { version = "1", features = [ "rt" ] }
21+
fork = "0.1.19"
22+
23+
[[example]]
24+
name = "man-pages-plugin"
25+
path = "examples/man-pages-plugin.rs"
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// SPDX-License-Identifier: GPL-3.0-only
2+
// Copyright © 2021 System76
3+
4+
use fork::{daemon, Fork};
5+
use pop_launcher::{Indice, PluginResponse, PluginSearchResult};
6+
use pop_launcher_toolkit::plugin_trait::{async_trait, PluginExt};
7+
use std::io;
8+
use std::os::unix::process::CommandExt;
9+
use std::path::PathBuf;
10+
use std::process::{exit, Command};
11+
12+
// This example demonstrate how to write a pop-launcher plugin using the `PluginExt` helper trait.
13+
// We are going to build a plugin to display man pages descriptions and open them on activation.
14+
// To do that we will use `whatis`, a command that searches the manual page names and displays their descriptions.
15+
16+
// For instance running `whatis git` would output the following :
17+
// ```
18+
// git (1) - the stupid content tracker
19+
// Git (3pm) - Perl interface to the Git version control system
20+
// ```
21+
22+
// Run `whatis` and split the output line to get a man page name and its description
23+
fn run_whatis(arg: &str) -> io::Result<Vec<(String, String)>> {
24+
let output = Command::new("whatis").arg(arg).output()?.stdout;
25+
26+
Ok(String::from_utf8_lossy(&output)
27+
.lines()
28+
.filter_map(|entry| entry.split_once('-'))
29+
.map(|(man_page, description)| {
30+
(man_page.trim().to_string(), description.trim().to_string())
31+
})
32+
.collect())
33+
}
34+
35+
// Open a new terminal and run `man` with the provided man page name
36+
fn open_man_page(arg: &str) -> io::Result<()> {
37+
//
38+
let (terminal, targ) = detect_terminal();
39+
40+
if let Ok(Fork::Child) = daemon(true, false) {
41+
Command::new(terminal).args(&[targ, "man", arg]).exec();
42+
}
43+
44+
exit(0);
45+
}
46+
47+
// A helper function to detect the user default terminal.
48+
// If the terminal is not found, fallback to `gnome-termninal
49+
fn detect_terminal() -> (PathBuf, &'static str) {
50+
use std::fs::read_link;
51+
52+
const SYMLINK: &str = "/usr/bin/x-terminal-emulator";
53+
54+
if let Ok(found) = read_link(SYMLINK) {
55+
return (read_link(&found).unwrap_or(found), "-e");
56+
}
57+
58+
(PathBuf::from("/usr/bin/gnome-terminal"), "--")
59+
}
60+
61+
// Our plugin struct, holding the search results.
62+
#[derive(Default)]
63+
pub struct WhatIsPlugin {
64+
entries: Vec<(String, String)>,
65+
}
66+
67+
// This is the main part of our plugin, defining how it will react to pop-launcher requests.
68+
#[async_trait]
69+
impl PluginExt for WhatIsPlugin {
70+
// Define the name of our plugin, this is mainly used to write log
71+
// emitted by tracing macros to `$HOME/.local/state/pop-launcher/wathis.log.
72+
fn name(&self) -> &str {
73+
"whatis"
74+
}
75+
76+
// Define how the plugin will react to pop-launcher search requests.
77+
// Note that we need to send `PluginResponse::Finished` once we are done,
78+
// otherwise pop-launcher will not display our search results and wait forever.
79+
async fn search(&mut self, query: &str) {
80+
// pop-launcher will only dispatch query matching the regex defined in our `plugin.ron`
81+
// file, can safely strip it out.
82+
let query = query.strip_prefix("whatis ");
83+
84+
if let Some(query) = query {
85+
// Whenever we get a new query, pass the query to the `whatis` helper function
86+
// and update our plugin entries with the result.
87+
match run_whatis(query) {
88+
Ok(entries) => self.entries = entries,
89+
// If we need to produce log, we use the tracing macros.
90+
Err(err) => tracing::error!("Error while running 'whatis' command: {err}"),
91+
}
92+
93+
// Now we send our entries back to the launcher. We also need a way to find our entry on activation
94+
// requests, here we use the entry index as an idendifier.
95+
for (idx, (cmd, description)) in self.entries.iter().enumerate() {
96+
self.respond_with(PluginResponse::Append(PluginSearchResult {
97+
id: idx as u32,
98+
name: format!("{cmd} - {description}"),
99+
keywords: None,
100+
description: description.clone(),
101+
icon: None,
102+
exec: None,
103+
window: None,
104+
}))
105+
.await;
106+
}
107+
}
108+
109+
// Tell pop-launcher we are done with this search request.
110+
self.respond_with(PluginResponse::Finished).await;
111+
}
112+
113+
// pop-launcher is asking for an entry activation.
114+
async fn activate(&mut self, id: Indice) {
115+
// First we try to find the requested entry in the plugin struct
116+
if let Some((command, _description)) = self.entries.get(id as usize) {
117+
// Open a new terminal with the requested man page and exit the plugin.
118+
if let Err(err) = open_man_page(command) {
119+
tracing::error!("Failed to open man page for '{command}': {err}")
120+
}
121+
}
122+
}
123+
}
124+
125+
// Now we just need to call the `run` function to start our plugin.
126+
// You can test it by writing request to its stdin.
127+
// For instance issuing a search request : `{ "Search": "whatis git" }`,
128+
// or activate one of the search results : `{ "Activate": 0 }`
129+
#[tokio::main(flavor = "current_thread")]
130+
async fn main() {
131+
WhatIsPlugin::default().run().await
132+
}

toolkit/examples/plugin.ron

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(
2+
name: "Find man pages",
3+
description: "Syntax: { whatis }\nExample: whatis git",
4+
query: (
5+
regex: "^(whatis ).+",
6+
help: "whatis",
7+
isolate: true,
8+
no_sort: true,
9+
),
10+
bin: (path: "man-pages-plugin"),
11+
icon: Name("org.gnome.Documents-symbolic"),
12+
)

0 commit comments

Comments
 (0)