diff --git a/.gitignore b/.gitignore index 96ef6c0b..408b8a57 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target Cargo.lock +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 75182ce4..e3679e2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,13 @@ gdk4-x11 = "0.3.0" gio = "0.14.8" gtk4 = "0.3.1" x11 = { version = "2", features = ["xlib"] } -# examples/launcher -pop-launcher = "1.0.3" -serde_json = "1.0.70" + +[dev-dependencies] +pop-launcher = { git = "https://github.com/pop-os/launcher", branch = "client" } +pop-launcher-service = { git = "https://github.com/pop-os/launcher", branch = "client" } +postage = "0.4.1" +futures = "0.3.18" +glib = "0.14.8" [[example]] -name = "launcher" # examples/launcher/main.rs \ No newline at end of file +name = "launcher" # examples/launcher/main.rs diff --git a/examples/launcher/ipc.rs b/examples/launcher/ipc.rs deleted file mode 100644 index c1ce7576..00000000 --- a/examples/launcher/ipc.rs +++ /dev/null @@ -1,76 +0,0 @@ -use pop_launcher::{Request, Response}; -use std::process; -use std::io::{self, BufRead, Write}; - -pub struct LauncherIpc { - child: process::Child, - stdin: process::ChildStdin, - stdout: io::BufReader, - exited: bool, -} - -impl LauncherIpc { - pub fn new() -> io::Result { - let mut child = process::Command::new("pop-launcher") - .stdin(process::Stdio::piped()) - .stdout(process::Stdio::piped()) - .spawn()?; - - let stdin = child.stdin.take().ok_or( - io::Error::new(io::ErrorKind::Other, "failed to find child stdin") - )?; - - let stdout = io::BufReader::new(child.stdout.take().ok_or( - io::Error::new(io::ErrorKind::Other, "failed to find child stdout") - )?); - - Ok(Self { - child, - stdin, - stdout, - exited: false, - }) - } - - fn send_request(&mut self, request: Request) -> io::Result<()> { - let mut request_json = serde_json::to_string(&request).map_err(|err| { - io::Error::new(io::ErrorKind::InvalidInput, err) - })?; - request_json.push('\n'); - self.stdin.write_all(request_json.as_bytes()) - } - - fn recv_response(&mut self) -> io::Result { - let mut response_json = String::new(); - self.stdout.read_line(&mut response_json)?; - serde_json::from_str(&response_json).map_err(|err| { - io::Error::new(io::ErrorKind::InvalidData, err) - }) - } - - pub fn request(&mut self, request: Request) -> io::Result { - self.send_request(request)?; - self.recv_response() - } - - //TODO: better exit implementation - pub fn exit(&mut self) -> io::Result> { - if ! self.exited { - self.send_request(Request::Exit)?; - let status = self.child.wait()?; - self.exited = true; - Ok(Some(status)) - } else { - Ok(None) - } - } -} - -impl Drop for LauncherIpc { - fn drop(&mut self) { - match self.exit() { - Ok(_) => (), - Err(err) => eprintln!("LauncherIpc::drop failed: {}", err), - } - } -} diff --git a/examples/launcher/main.rs b/examples/launcher/main.rs index 2692fb7f..8b0c9ca1 100644 --- a/examples/launcher/main.rs +++ b/examples/launcher/main.rs @@ -2,40 +2,53 @@ use gtk4 as gtk; use gtk::prelude::*; use libcosmic::x; -use std::{ - cell::RefCell, - rc::Rc, -}; - -use self::ipc::LauncherIpc; -mod ipc; +use pop_launcher_service::IpcClient; +use postage::mpsc::Sender; +use postage::prelude::*; fn icon_source(icon: >k::Image, source: &Option) { match source { Some(pop_launcher::IconSource::Name(name)) => { icon.set_from_icon_name(Some(name)); - }, + } Some(pop_launcher::IconSource::Mime(content_type)) => { - icon.set_from_gicon( - &gio::content_type_get_icon(content_type) - ); - }, + icon.set_from_gicon(&gio::content_type_get_icon(content_type)); + } _ => { icon.set_from_icon_name(None); } } } -fn main() { - let launcher = Rc::new(RefCell::new( - LauncherIpc::new().expect("failed to connect to launcher service") - )); +enum Event { + Response(pop_launcher::Response), + Search(String), +} +fn spawn_launcher(mut tx: Sender) -> IpcClient { + let (launcher, responses) = + pop_launcher_service::IpcClient::new().expect("failed to connect to launcher service"); + + glib::MainContext::default().spawn_local(async move { + use futures::StreamExt; + futures::pin_mut!(responses); + while let Some(event) = responses.next().await { + let _ = tx.send(Event::Response(event)).await; + } + }); + + launcher +} + +fn main() { let app = gtk::Application::builder() .application_id("com.system76.Launcher") .build(); app.connect_activate(move |app| { + let (tx, mut rx) = postage::mpsc::channel(1); + let mut launcher = spawn_launcher(tx.clone()); + let window = gtk::ApplicationWindow::builder() .application(app) .decorated(false) @@ -59,66 +72,17 @@ fn main() { vbox.append(&listbox); { - let launcher = launcher.clone(); - let search_changed = move |search: >k::SearchEntry| { - //TODO: is this the best way to clear a listbox? - while let Some(child) = listbox.last_child() { - listbox.remove(&child); - } + let search_changed = glib::clone!(@strong tx => move |search: >k::SearchEntry| { + let search = search.text().to_string(); - let response_res = launcher.borrow_mut().request( - pop_launcher::Request::Search(search.text().to_string()) - ); - - println!("{:#?}", response_res); - - if let Ok(pop_launcher::Response::Update(results)) = response_res { - for (i, result) in results.iter().enumerate() { - // Limit to 9 results - if i >= 9 { continue; } - - let row = gtk::ListBoxRow::new(); - listbox.append(&row); - - // Select first row - if i == 0 { - listbox.select_row(Some(&row)); - } - - let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 8); - hbox.set_margin_start(8); - hbox.set_margin_end(8); - hbox.set_margin_top(8); - hbox.set_margin_bottom(8); - row.set_child(Some(&hbox)); - - let category_icon = gtk::Image::new(); - category_icon.set_pixel_size(16); - icon_source(&category_icon, &result.category_icon); - hbox.append(&category_icon); - - let icon = gtk::Image::new(); - icon.set_pixel_size(32); - icon_source(&icon, &result.icon); - hbox.append(&icon); - - let labels = gtk::Box::new(gtk::Orientation::Vertical, 4); - hbox.append(&labels); - - let name = gtk::Label::new(Some(&result.name)); - name.set_halign(gtk::Align::Start); - labels.append(&name); - - let description = gtk::Label::new(Some(&result.description)); - description.set_halign(gtk::Align::Start); - labels.append(&description); - } - } - }; - - search.connect_changed(search_changed.clone()); + let mut tx = tx.clone(); + glib::MainContext::default().spawn_local(async move { + let _ = tx.send(Event::Search(search)).await; + }); + }); search_changed(&search); + search.connect_changed(search_changed); } // Setting the window to dialog type must happen between realize and show. Dialog windows @@ -140,6 +104,68 @@ fn main() { }); window.show(); + + glib::MainContext::default().spawn_local(async move { + while let Some(event) = rx.recv().await { + match event { + Event::Search(search) => { + //TODO: is this the best way to clear a listbox? + while let Some(child) = listbox.last_child() { + listbox.remove(&child); + } + + let _ = launcher.send(pop_launcher::Request::Search(search)).await; + } + + Event::Response(event) => { + if let pop_launcher::Response::Update(results) = event { + for (i, result) in results.iter().enumerate() { + // Limit to 9 results + if i >= 9 { + continue; + } + + let row = gtk::ListBoxRow::new(); + listbox.append(&row); + + // Select first row + if i == 0 { + listbox.select_row(Some(&row)); + } + + let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 8); + hbox.set_margin_start(8); + hbox.set_margin_end(8); + hbox.set_margin_top(8); + hbox.set_margin_bottom(8); + row.set_child(Some(&hbox)); + + let category_icon = gtk::Image::new(); + category_icon.set_pixel_size(16); + icon_source(&category_icon, &result.category_icon); + hbox.append(&category_icon); + + let icon = gtk::Image::new(); + icon.set_pixel_size(32); + icon_source(&icon, &result.icon); + hbox.append(&icon); + + let labels = gtk::Box::new(gtk::Orientation::Vertical, 4); + hbox.append(&labels); + + let name = gtk::Label::new(Some(&result.name)); + name.set_halign(gtk::Align::Start); + labels.append(&name); + + let description = gtk::Label::new(Some(&result.description)); + description.set_halign(gtk::Align::Start); + labels.append(&description); + } + } + } + } + } + }); }); app.run();