// SPDX-License-Identifier: GPL-3.0-only // Copyright © 2021 System76 use freedesktop_desktop_entry::get_languages_from_env; use futures::prelude::*; use pop_launcher::*; use std::path::PathBuf; pub struct App { last_query: Option, out: tokio::io::Stdout, 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 exe = match self.last_query.take() { Some(cmd) => cmd, None => return, }; use fork::{Fork, daemon}; crate::send(&mut self.out, PluginResponse::Close).await; if let Ok(Fork::Child) = daemon(true, false) { use std::os::unix::process::CommandExt; use std::process::Command; let mut cmd; if self.shell_only { cmd = Command::new("sh"); cmd.args(["-c", &exe]); } else { let (terminal, arg) = detect_terminal(); cmd = Command::new(terminal); cmd.args([ arg, "sh", "-c", &format!("{}; echo \"Press Enter to exit\"; read t", exe), ]); } let _ = cmd.exec(); } std::process::exit(0); } 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 freedesktop_desktop_entry::DesktopEntry; use std::fs::read_link; let terminal_apps: Vec<_> = freedesktop_desktop_entry::Iter::new(freedesktop_desktop_entry::default_paths()) .filter_map(|path| { std::fs::read_to_string(&path).ok().and_then(|input| { DesktopEntry::from_str(&path, &input, Some(&get_languages_from_env())) .ok() .and_then(|de| { if de.no_display() || de .categories() .map(|c| c.iter().all(|c| *c != "TerminalEmulator")) .unwrap_or(true) || de.exec().is_none() { return None; } Some((de.id().to_owned(), de.exec().unwrap().to_owned())) }) }) }) .collect(); for id in ["com.system76.CosmicTerm"] { for (terminal_id, exec) in &terminal_apps { if terminal_id.as_str() == id { return (PathBuf::from(exec), "-e"); } } } if let Some((id, exec)) = terminal_apps.first() { let arg = if id == "org.gnome.Terminal" { "--" } else { "-e" }; return (PathBuf::from(exec), arg); } const SYMLINK: &str = "/usr/bin/x-terminal-emulator"; if let Ok(found) = read_link(SYMLINK) { return (read_link(&found).unwrap_or(found), "-e"); } (PathBuf::from("/usr/bin/gnome-terminal"), "--") }