diff --git a/Makefile b/Makefile index d876ed2..db38ac6 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ install: ln -sf $(BIN) $(PLUGIN_DIR)/pulse/pulse # Terminal plugin - install -Dm0755 plugins/src/terminal/terminal.js $(PLUGIN_DIR)/terminal + ln -sf $(BIN) $(PLUGIN_DIR)/terminal/terminal # Scripts mkdir -p $(SCRIPTS_DIR) diff --git a/bin/src/main.rs b/bin/src/main.rs index b07ff6e..4cb7c28 100644 --- a/bin/src/main.rs +++ b/bin/src/main.rs @@ -23,6 +23,7 @@ fn main() { "pop-shell" => block_on(plugins::pop_shell::main()), "pulse" => block_on(plugins::pulse::main()), "scripts" => block_on(plugins::scripts::main()), + "terminal" => block_on(plugins::terminal::main()), "web" => block_on(plugins::web::main()), unknown => { eprintln!("unknown cmd: {}", unknown); diff --git a/debian/pop-launcher.links b/debian/pop-launcher.links index d33dcd9..930316f 100644 --- a/debian/pop-launcher.links +++ b/debian/pop-launcher.links @@ -5,4 +5,5 @@ /usr/bin/pop-launcher /usr/lib/pop-launcher/plugins/pop_shell/pop-shell /usr/bin/pop-launcher /usr/lib/pop-launcher/plugins/pulse/pulse /usr/bin/pop-launcher /usr/lib/pop-launcher/plugins/scripts/scripts +/usr/bin/pop-launcher /usr/lib/pop-launcher/plugins/terminal/terminal /usr/bin/pop-launcher /usr/lib/pop-launcher/plugins/web/web diff --git a/plugins/src/lib.rs b/plugins/src/lib.rs index d26fdf4..e29ff70 100644 --- a/plugins/src/lib.rs +++ b/plugins/src/lib.rs @@ -5,6 +5,7 @@ pub mod find; pub mod pop_shell; pub mod pulse; pub mod scripts; +pub mod terminal; pub mod web; use futures_lite::{AsyncWrite, AsyncWriteExt}; diff --git a/plugins/src/recent/mod.rs b/plugins/src/recent/mod.rs new file mode 100644 index 0000000..6c61133 --- /dev/null +++ b/plugins/src/recent/mod.rs @@ -0,0 +1,44 @@ +use futures_lite::prelude::*; +use pop_launcher::*; +use smol::Unblock; +use std::io; + +pub struct App { + out: Unblock, +} + +impl Default for App { + fn default() -> Self { + Self { + out: async_stdout(), + } + } +} + +pub async fn main() { + let mut requests = json_input_stream(async_stdin()); + + let mut app = App::default(); + + while let Some(result) = requests.next().await { + match result { + Ok(request) => match request { + Request::Activate(id) => app.activate(id).await, + Request::Search(query) => app.search(query).await, + Request::Exit => break, + _ => (), + }, + Err(why) => { + tracing::error!("malformed JSON input: {}", why); + } + } + } +} + +impl App { + async fn activate(&mut self, id: u32) {} + + async fn search(&mut self, query: String) { + + } +} diff --git a/plugins/src/terminal/mod.rs b/plugins/src/terminal/mod.rs new file mode 100644 index 0000000..8f8c9c7 --- /dev/null +++ b/plugins/src/terminal/mod.rs @@ -0,0 +1,122 @@ +use futures_lite::prelude::*; +use pop_launcher::*; +use smol::Unblock; +use std::{io, path::PathBuf}; + +pub struct App { + last_query: Option, + out: Unblock, + shell_only: bool, +} + +impl Default for App { + fn default() -> Self { + Self { + last_query: None, + out: async_stdout(), + shell_only: false, + } + } +} + +pub async fn main() { + let mut requests = json_input_stream(async_stdin()); + + let mut app = App::default(); + + while let Some(result) = requests.next().await { + match result { + Ok(request) => match request { + Request::Activate(id) => app.activate(id).await, + Request::Search(query) => app.search(query).await, + Request::Exit => break, + _ => (), + }, + Err(why) => { + tracing::error!("malformed JSON input: {}", why); + } + } + } +} + +impl App { + async fn activate(&mut self, _id: u32) { + let exec = match self.last_query.take() { + Some(cmd) => format!("{}; echo \"Press Enter to exit\"; read t", cmd), + None => return, + }; + + use fork::{daemon, Fork}; + + crate::send(&mut self.out, PluginResponse::Close).await; + + if let Ok(Fork::Child) = daemon(true, true) { + use std::os::unix::process::CommandExt; + use std::process::Command; + + let mut cmd; + + if self.shell_only { + cmd = Command::new("sh"); + cmd.args(&["-c", &exec]); + } else { + let (terminal, arg) = detect_terminal(); + cmd = Command::new(terminal); + cmd.args(&[arg, "sh", "-c", &exec]); + } + + let _ = cmd.exec(); + std::process::exit(1); + } + } + + async fn search(&mut self, query: String) { + self.splice_input(&query).await; + crate::send(&mut self.out, PluginResponse::Finished).await; + } + + async fn splice_input(&mut self, mut query: &str) { + if let Some(q) = query.strip_prefix(':') { + self.shell_only = true; + query = q.trim(); + self.last_query = Some(query.to_owned()); + } else { + self.shell_only = false; + + let query = if let Some(query) = query.strip_prefix("t:") { + query.trim() + } else if let Some(pos) = query.find(' ') { + query[pos + 1..].trim() + } else { + return; + }; + + self.last_query = Some(query.to_owned()); + } + + crate::send( + &mut self.out, + PluginResponse::Append(PluginSearchResult { + id: 0, + name: query.to_owned(), + description: String::from("run command in terminal"), + ..Default::default() + }), + ) + .await; + } +} + +fn detect_terminal() -> (PathBuf, &'static str) { + use std::fs::read_link; + + const SYMLINK: &str = "/usr/bin/x-terminal-emulator"; + + if let Ok(found) = read_link(SYMLINK) { + if let Ok(found) = read_link(&found) { + return (found, "-e"); + } + } + + (PathBuf::from("/usr/bin/gnome-terminal"), "--") +} diff --git a/plugins/src/terminal/plugin.ron b/plugins/src/terminal/plugin.ron index 31ad4ad..c023ec9 100644 --- a/plugins/src/terminal/plugin.ron +++ b/plugins/src/terminal/plugin.ron @@ -6,6 +6,6 @@ help: "run ", isolate: true, ), - bin: (path: "terminal.js"), + bin: (path: "terminal"), icon: Name("utilities-terminal"), ) \ No newline at end of file diff --git a/plugins/src/terminal/terminal.js b/plugins/src/terminal/terminal.js deleted file mode 100755 index a3538bf..0000000 --- a/plugins/src/terminal/terminal.js +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/gjs - -const { GLib, Gio } = imports.gi; - -const STDIN = new Gio.DataInputStream({ base_stream: new Gio.UnixInputStream({ fd: 0 }) }) -const STDOUT = new Gio.DataOutputStream({ base_stream: new Gio.UnixOutputStream({ fd: 1 }) }) - -class App { - constructor() { - this.last_query = "" - this.shell_only = false - } - - /** @param {string} input */ - query(input) { - if (input.startsWith(':')) { - this.shell_only = true - this.last_query = input.substr(1).trim() - } else { - this.shell_only = false - this.last_query = input.startsWith('t:') - ? input.substr(2).trim() - : input.substr(input.indexOf(" ") + 1).trim() - } - - this.send({ "Append": { - id: 0, - name: this.last_query, - description: "run command in terminal" - }}) - - this.send("Finished") - } - - /** @param {number} _id */ - submit(_id) { - try { - let runner - if (this.shell_only) { - runner = "" - } else { - let path = GLib.find_program_in_path('x-terminal-emulator'); - let [terminal, splitter] = path ? [path, "-e"] : ["gnome-terminal", "--"]; - runner = `${terminal} ${splitter} ` - } - - GLib.spawn_command_line_async(`${runner}sh -c '${this.last_query}; echo "Press to exit"; read t'`); - } catch (e) { - log(`command launch error: ${e}`) - } - - this.send("Close") - } - - /** @param {Object} object */ - send(object) { - try { - STDOUT.write_bytes(new GLib.Bytes(JSON.stringify(object) + "\n"), null) - } catch (e) { - log(`failed to send response to Pop Shell: ${e}`) - } - } -} - -function main() { - /** @type {null | ByteArray} */ - let input_array - - /** @type {string} */ - let input_str - - /** @type {null | LauncherRequest} */ - let event - - let app = new App() - - mainloop: - while (true) { - try { - [input_array,] = STDIN.read_line(null) - } catch (e) { - break - } - - input_str = imports.byteArray.toString(input_array) - if ((event = parse_event(input_str)) !== null) { - if ("Search" in event) { - app.query(event.Search) - } else if ("Activate" in event) { - app.submit(event.Activate) - } else if ("Exit" === event) { - break mainloop - } - } - } -} - -/** - * Parses an IPC event received from STDIN - * @param {string} input - * @returns {null | LauncherRequest} - */ -function parse_event(input) { - try { - return JSON.parse(input) - } catch (e) { - log(`Input not valid JSON`) - return null - } -} - -main() \ No newline at end of file