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/examples/launcher/ipc.rs b/examples/launcher/ipc.rs index c1ce7576..42a1c6c5 100644 --- a/examples/launcher/ipc.rs +++ b/examples/launcher/ipc.rs @@ -1,6 +1,6 @@ use pop_launcher::{Request, Response}; -use std::process; use std::io::{self, BufRead, Write}; +use std::process; pub struct LauncherIpc { child: process::Child, @@ -16,13 +16,15 @@ impl LauncherIpc { .stdout(process::Stdio::piped()) .spawn()?; - let stdin = child.stdin.take().ok_or( - io::Error::new(io::ErrorKind::Other, "failed to find child stdin") - )?; + 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") - )?); + let stdout = io::BufReader::new(child.stdout.take().ok_or(io::Error::new( + io::ErrorKind::Other, + "failed to find child stdout", + ))?); Ok(Self { child, @@ -33,9 +35,8 @@ impl LauncherIpc { } 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) - })?; + 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()) } @@ -43,9 +44,8 @@ impl LauncherIpc { 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) - }) + 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 { @@ -55,7 +55,7 @@ impl LauncherIpc { //TODO: better exit implementation pub fn exit(&mut self) -> io::Result> { - if ! self.exited { + if !self.exited { self.send_request(Request::Exit)?; let status = self.child.wait()?; self.exited = true; diff --git a/examples/launcher/main.rs b/examples/launcher/main.rs index 2692fb7f..ddbef6a2 100644 --- a/examples/launcher/main.rs +++ b/examples/launcher/main.rs @@ -1,11 +1,10 @@ use gtk4 as gtk; use gtk::prelude::*; +use gtk4::Application; use libcosmic::x; -use std::{ - cell::RefCell, - rc::Rc, -}; +use std::{cell::RefCell, rc::Rc}; +use gtk4::glib::Type; use self::ipc::LauncherIpc; mod ipc; @@ -14,12 +13,10 @@ 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); } @@ -27,120 +24,123 @@ fn icon_source(icon: >k::Image, source: &Option) { } fn main() { - let launcher = Rc::new(RefCell::new( - LauncherIpc::new().expect("failed to connect to launcher service") - )); - let app = gtk::Application::builder() .application_id("com.system76.Launcher") .build(); - app.connect_activate(move |app| { - 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::SearchEntry::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 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 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()); - - search_changed(&search); - } - - // 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(); - }); + app.connect_activate(move |app| build_ui(app)); app.run(); } + +fn build_ui(app: &Application) { + let launcher = Rc::new(RefCell::new( + LauncherIpc::new().expect("failed to connect to launcher service"), + )); + + let window = gtk::ApplicationWindow::builder() + .application(app) + .decorated(false) + .default_width(590) + .default_height(150) + .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::SearchEntry::new(); + search.set_placeholder_text(Some(" Type to search apps, or type '?' for more options.")); + vbox.append(&search); + { + let launcher = launcher.clone(); + let search_changed = move |search: >k::SearchEntry| { + let listbox_widget = vbox.last_child().unwrap(); + let gtk_list_box = Type::from_name("GtkListBox"); + if gtk_list_box.is_some() && listbox_widget.type_() == gtk_list_box.unwrap() { + vbox.remove(&listbox_widget); + } + let listbox = gtk::ListBox::new(); + vbox.append(&listbox); + + 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()); + + search_changed(&search); + } + + // 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(); +}