From 5beaa70c0eea6e88cc6d54bc562c61054d93feca Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 24 Nov 2021 19:27:29 +0100 Subject: [PATCH] feat: Use IpcClient from pop-launcher-service --- Cargo.toml | 12 +-- examples/launcher/main.rs | 151 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 152 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 75182ce4..4e30490d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,11 @@ 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" -[[example]] -name = "launcher" # examples/launcher/main.rs \ No newline at end of file +[dev-dependencies] +# examples/launcher +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" diff --git a/examples/launcher/main.rs b/examples/launcher/main.rs index ddbef6a2..cf0db87c 100644 --- a/examples/launcher/main.rs +++ b/examples/launcher/main.rs @@ -3,11 +3,9 @@ use gtk4 as gtk; use gtk::prelude::*; use gtk4::Application; use libcosmic::x; -use std::{cell::RefCell, rc::Rc}; -use gtk4::glib::Type; - -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 { @@ -23,12 +21,153 @@ fn icon_source(icon: >k::Image, source: &Option) { } } +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| build_ui(app)); + 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) + .default_width(480) + .default_height(440) + .title("Launcher") + .build(); + + let vbox = gtk::Box::new(gtk::Orientation::Vertical, 16); + vbox.set_margin_start(16); + vbox.set_margin_end(16); + vbox.set_margin_top(16); + vbox.set_margin_bottom(16); + window.set_child(Some(&vbox)); + + let search = gtk::Entry::new(); + search.set_placeholder_text(Some(" Type to search apps, or type '?' for more options.")); + vbox.append(&search); + + let listbox = gtk::ListBox::new(); + vbox.append(&listbox); + + { + let search_changed = glib::clone!(@strong tx => move |search: >k::Entry| { + let search = search.text().to_string(); + + 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 + // show up centered on the display with the cursor, so we do not have to set position + window.connect_realize(move |window| { + if let Some((display, surface)) = x::get_window_x11(window) { + unsafe { + x::change_property( + &display, + &surface, + "_NET_WM_WINDOW_TYPE", + x::PropMode::Replace, + &[x::Atom::new(&display, "_NET_WM_WINDOW_TYPE_DIALOG").unwrap()], + ); + } + } else { + println!("failed to get X11 window"); + } + }); + + 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(); }