diff --git a/Cargo.toml b/Cargo.toml index aebdb0d4..585cd97d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,14 @@ 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" +#pop-launcher = "1.0.3" +pop-launcher = { git = "https://github.com/pop-os/launcher", branch = "client" } serde_json = "1.0.70" -once_cell = "1.8.0" +pop-launcher-service = { git = "https://github.com/pop-os/launcher", branch = "client" } +postage = "0.4.1" +futures = "0.3.18" +glib = "0.14.8" +# examples/gtklauncher +once_cell = "1.8.0" \ No newline at end of file diff --git a/examples/gtklauncher/README.md b/examples/gtklauncher/README.md deleted file mode 100644 index f2a64530..00000000 --- a/examples/gtklauncher/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# ListView: Applications Launcher - -This example shows how to create a `gtk::ListView` and fill it with applications data from `gio::AppInfo` with the possibility to open an application when an item of the list is activated. - -![Screenshot](screenshot.png) diff --git a/examples/gtklauncher/application_row/mod.rs b/examples/gtklauncher/application_row/mod.rs deleted file mode 100644 index 8054c928..00000000 --- a/examples/gtklauncher/application_row/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -use gtk4 as gtk; -mod imp; - -use gtk::prelude::*; -use gtk::subclass::prelude::*; -use gtk::{gio, glib}; - -glib::wrapper! { - pub struct ApplicationRow(ObjectSubclass) - @extends gtk::Widget, gtk::Box; -} - -impl Default for ApplicationRow { - fn default() -> Self { - Self::new() - } -} - -impl ApplicationRow { - pub fn new() -> Self { - glib::Object::new(&[]).expect("Failed to create ApplicationRow") - } - - pub fn set_app_info(&self, app_info: &gio::AppInfo) { - let self_ = imp::ApplicationRow::from_instance(self); - self_.name.set_text(&app_info.name()); - if let Some(desc) = app_info.description() { - self_.description.set_text(&desc); - } - if let Some(icon) = app_info.icon() { - self_.image.set_from_gicon(&icon); - } - } - - pub fn set_shortcut(&self, indx: u32) { - let self_ = imp::ApplicationRow::from_instance(self); - self_.shortcut.set_text(&format!("Ctrl + {}", indx)); - } -} diff --git a/examples/gtklauncher/main.rs b/examples/gtklauncher/main.rs deleted file mode 100644 index 584e1897..00000000 --- a/examples/gtklauncher/main.rs +++ /dev/null @@ -1,34 +0,0 @@ -mod application_row; -mod window; - -use gtk::gdk::Display; -use gtk::prelude::*; -use gtk4 as gtk; - -use window::Window; - -fn main() { - let application = gtk::Application::new( - Some("com.github.gtk-rs.examples.apps_launcher"), - Default::default(), - ); - - application.connect_activate(|app| { - let provider = gtk::CssProvider::new(); - provider.load_from_data(include_bytes!("style.css")); - gtk::StyleContext::add_provider_for_display( - &Display::default().expect("Error initializing gtk css provider."), - &provider, - gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, - ); - new_build_ui(app); - }); - - application.run(); -} - -fn new_build_ui(app: >k::Application) { - // Create a new custom window and show it - let window = Window::new(app); - window.show(); -} diff --git a/examples/gtklauncher/screenshot.png b/examples/gtklauncher/screenshot.png deleted file mode 100644 index 8ffcefa1..00000000 Binary files a/examples/gtklauncher/screenshot.png and /dev/null differ diff --git a/examples/gtklauncher/style.css b/examples/gtklauncher/style.css deleted file mode 100644 index 0c72f8a3..00000000 --- a/examples/gtklauncher/style.css +++ /dev/null @@ -1,15 +0,0 @@ -description { - line-height: 1.5em; - background-image: none; - background-color: red; -} - -row.row1 { - background-image: none; - background-color: black; -} - -shortcut { - background-image: none; - background-color: green; -} diff --git a/examples/launcher/application_object/imp.rs b/examples/launcher/application_object/imp.rs index 9a267181..5048d2fa 100644 --- a/examples/launcher/application_object/imp.rs +++ b/examples/launcher/application_object/imp.rs @@ -1,7 +1,4 @@ -use glib::{ - types::Type, FromVariant, ParamFlags, ParamSpec, ParamSpecObject, ToVariant, Value, Variant, - VariantTy, -}; +use glib::{FromVariant, ParamFlags, ParamSpec, ToVariant, Value, Variant, VariantTy}; use gtk4::glib; use gtk4::prelude::*; use gtk4::subclass::prelude::*; @@ -21,7 +18,7 @@ pub struct ApplicationObject { // The central trait for subclassing a GObject #[glib::object_subclass] impl ObjectSubclass for ApplicationObject { - const NAME: &'static str = "LibCosmicLauncherApplicationObject"; + const NAME: &'static str = "ApplicationObject"; type Type = super::ApplicationObject; type ParentType = glib::Object; } @@ -31,6 +28,20 @@ impl ObjectImpl for ApplicationObject { fn properties() -> &'static [ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ + ParamSpec::new_uint( + // Name + "id", + // Nickname + "id", + // Short description + "ID of application in launcher search result", + 0, + u32::MAX, + // Default value + 0, + // The property can be read and written to + ParamFlags::READWRITE, + ), ParamSpec::new_string( // Name "name", @@ -102,6 +113,10 @@ impl ObjectImpl for ApplicationObject { fn set_property(&self, _obj: &Self::Type, _id: usize, value: &Value, pspec: &ParamSpec) { match pspec.name() { + "id" => { + let id = value.get().expect("The value needs to be of type `u32`."); + self.data.borrow_mut().0.id = id; + } "name" => { let name = value .get() @@ -115,46 +130,44 @@ impl ObjectImpl for ApplicationObject { self.data.borrow_mut().0.description = description; } "icon" => { - let icon = >::from_variant( + let icon = <(i32, String)>::from_variant( &value .get::() .expect("The icon needs to be a Variant"), ) - .expect("The icon variant needs to be an Option<(i32, String)>"); - if let Some(icon) = icon { - self.data.borrow_mut().0.icon = match icon { - (i_type, name) if i_type == pop_launcher::IconSource::Name as i32 => { - Some(pop_launcher::IconSource::Name(name.into())) - } - (i_type, name) if i_type == pop_launcher::IconSource::Mime as i32 => { - Some(pop_launcher::IconSource::Mime(name.into())) - } - _ => None, - }; - } else { - self.data.borrow_mut().0.icon = None; - } + .expect("The icon variant needs to be an (i32, String)"); + self.data.borrow_mut().0.icon = match icon { + (i_type, name) if i_type == pop_launcher::IconSource::Name as i32 => { + Some(pop_launcher::IconSource::Name(name.into())) + } + (i_type, name) if i_type == pop_launcher::IconSource::Mime as i32 => { + Some(pop_launcher::IconSource::Mime(name.into())) + } + (i_type, name) => { + println!("Failed to set icon. {} {}", i_type, name); + None + } + }; } "categoryicon" => { - let icon = >::from_variant( + let icon = <(i32, String)>::from_variant( &value .get::() .expect("The icon needs to be a Variant"), ) .expect("The icon variant needs to be an Option<(i32, String)>"); - if let Some(icon) = icon { - self.data.borrow_mut().0.icon = match icon { - (i_type, name) if i_type == pop_launcher::IconSource::Name as i32 => { - Some(pop_launcher::IconSource::Name(name.into())) - } - (i_type, name) if i_type == pop_launcher::IconSource::Mime as i32 => { - Some(pop_launcher::IconSource::Mime(name.into())) - } - _ => None, - }; - } else { - self.data.borrow_mut().0.icon = None; - } + self.data.borrow_mut().0.category_icon = match icon { + (i_type, name) if i_type == pop_launcher::IconSource::Name as i32 => { + Some(pop_launcher::IconSource::Name(name.into())) + } + (i_type, name) if i_type == pop_launcher::IconSource::Mime as i32 => { + Some(pop_launcher::IconSource::Mime(name.into())) + } + (i_type, name) => { + println!("Failed to set icon. {} {}", i_type, name); + None + } + }; } "window" => { unimplemented!() @@ -165,8 +178,9 @@ impl ObjectImpl for ApplicationObject { fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value { match pspec.name() { + "id" => self.data.borrow().0.id.to_value(), "name" => self.data.borrow().0.name.to_value(), - "description" => self.data.borrow().0.name.to_value(), + "description" => self.data.borrow().0.description.to_value(), "icon" => match &self.data.borrow().0.icon { Some(pop_launcher::IconSource::Name(icon_name)) => { (pop_launcher::IconSource::Name as i32, icon_name.to_string()) diff --git a/examples/launcher/application_object/mod.rs b/examples/launcher/application_object/mod.rs index 98d9d03c..1ce03377 100644 --- a/examples/launcher/application_object/mod.rs +++ b/examples/launcher/application_object/mod.rs @@ -1,9 +1,9 @@ mod imp; use gdk4::glib::Object; +use glib::ObjectExt; +use glib::ToVariant; use gtk4::glib; -use std::cell::RefCell; -use std::rc::Rc; glib::wrapper! { pub struct ApplicationObject(ObjectSubclass); @@ -11,8 +11,46 @@ glib::wrapper! { impl ApplicationObject { pub fn new(application_search_result: &pop_launcher::SearchResult) -> Self { - Object::new(&[("name", &application_search_result.name)]) - .expect("Failed to create `ApplicationObject`.") + let self_: Self = Object::new(&[ + ("id", &application_search_result.id), + ("name", &application_search_result.name), + ("description", &application_search_result.description), + ]) + .expect("Failed to create `ApplicationObject`."); + if let Some(icon) = &application_search_result.icon { + if let Err(e) = self_.set_property( + "icon", + match icon { + pop_launcher::IconSource::Name(name) => { + (pop_launcher::IconSource::Name as i32, name.to_string()).to_variant() + } + pop_launcher::IconSource::Mime(name) => { + (pop_launcher::IconSource::Mime as i32, name.to_string()).to_variant() + } + }, + ) { + println!("failed to set icon property"); + dbg!(e); + }; + } + if let Some(icon) = &application_search_result.category_icon { + if let Err(e) = self_.set_property( + "categoryicon", + match icon { + pop_launcher::IconSource::Name(name) => { + (pop_launcher::IconSource::Name as i32, name.to_string()).to_variant() + } + pop_launcher::IconSource::Mime(name) => { + (pop_launcher::IconSource::Mime as i32, name.to_string()).to_variant() + } + }, + ) { + println!("failed to set category icon property"); + dbg!(e); + }; + } + + self_ } } diff --git a/examples/gtklauncher/application_row/application_row.ui b/examples/launcher/application_row/application_row.ui similarity index 91% rename from examples/gtklauncher/application_row/application_row.ui rename to examples/launcher/application_row/application_row.ui index a70d7958..f99b6008 100644 --- a/examples/gtklauncher/application_row/application_row.ui +++ b/examples/launcher/application_row/application_row.ui @@ -6,7 +6,12 @@ 4 4 true - + + + 20 + + + 4 4 diff --git a/examples/gtklauncher/application_row/imp.rs b/examples/launcher/application_row/imp.rs similarity index 93% rename from examples/gtklauncher/application_row/imp.rs rename to examples/launcher/application_row/imp.rs index f27eb54e..adc89e35 100644 --- a/examples/gtklauncher/application_row/imp.rs +++ b/examples/launcher/application_row/imp.rs @@ -16,6 +16,8 @@ pub struct ApplicationRow { pub shortcut: TemplateChild, #[template_child] pub image: TemplateChild, + #[template_child] + pub categoryimage: TemplateChild, } #[glib::object_subclass] diff --git a/examples/launcher/application_row/mod.rs b/examples/launcher/application_row/mod.rs new file mode 100644 index 00000000..08d09eb1 --- /dev/null +++ b/examples/launcher/application_row/mod.rs @@ -0,0 +1,79 @@ +use crate::icon_source; +use glib::FromVariant; +use glib::Variant; +use gtk4 as gtk; +mod imp; + +use crate::ApplicationObject; +use gtk::glib; +use gtk::prelude::*; +use gtk::subclass::prelude::*; + +glib::wrapper! { + pub struct ApplicationRow(ObjectSubclass) + @extends gtk::Widget, gtk::Box; +} + +impl Default for ApplicationRow { + fn default() -> Self { + Self::new() + } +} + +impl ApplicationRow { + pub fn new() -> Self { + glib::Object::new(&[]).expect("Failed to create ApplicationRow") + } + + pub fn set_app_info(&self, app_obj: ApplicationObject) { + let self_ = imp::ApplicationRow::from_instance(self); + + if let Ok(name) = app_obj.property("name") { + self_.name.set_text( + &name + .get::() + .expect("Property name needs to be a String."), + ); + } + if let Ok(desc) = app_obj.property("description") { + self_.description.set_text( + &desc + .get::() + .expect("Property description needs to be a String."), + ); + } + if let Ok(icon) = app_obj.property("icon") { + if let Ok(icon) = icon.get::() { + let icon = match <(i32, String)>::from_variant(&icon) { + Some((i_type, name)) if i_type == pop_launcher::IconSource::Name as i32 => { + Some(pop_launcher::IconSource::Name(name.into())) + } + Some((i_type, name)) if i_type == pop_launcher::IconSource::Mime as i32 => { + Some(pop_launcher::IconSource::Mime(name.into())) + } + _ => None, + }; + icon_source(&self_.image, &icon); + } + } + if let Ok(icon) = app_obj.property("categoryicon") { + if let Ok(icon) = icon.get::() { + let icon = match <(i32, String)>::from_variant(&icon) { + Some((i_type, name)) if i_type == pop_launcher::IconSource::Name as i32 => { + Some(pop_launcher::IconSource::Name(name.into())) + } + Some((i_type, name)) if i_type == pop_launcher::IconSource::Mime as i32 => { + Some(pop_launcher::IconSource::Mime(name.into())) + } + _ => None, + }; + icon_source(&self_.categoryimage, &icon); + } + } + } + + pub fn set_shortcut(&self, indx: u32) { + let self_ = imp::ApplicationRow::from_instance(self); + self_.shortcut.set_text(&format!("Ctrl + {}", indx)); + } +} diff --git a/examples/launcher/launcher_row/imp.rs b/examples/launcher/launcher_row/imp.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/launcher/launcher_row/launcher_row.ui b/examples/launcher/launcher_row/launcher_row.ui deleted file mode 100644 index 8e3a5695..00000000 --- a/examples/launcher/launcher_row/launcher_row.ui +++ /dev/null @@ -1,40 +0,0 @@ -?xml version="1.0" encoding="UTF-8"?> - - - \ No newline at end of file diff --git a/examples/launcher/launcher_row/mod.rs b/examples/launcher/launcher_row/mod.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/launcher/main.rs b/examples/launcher/main.rs index e06c8755..e5994b1b 100644 --- a/examples/launcher/main.rs +++ b/examples/launcher/main.rs @@ -1,18 +1,22 @@ mod application_object; +mod application_row; +use gio::DesktopAppInfo; use gtk4 as gtk; +use gtk4::SliceListModel; +use cascade::cascade; use gtk::gio; use gtk::prelude::*; -use gtk::{ - glib, Application, ApplicationWindow, Label, ListView, PolicyType, ScrolledWindow, - SignalListItemFactory, SingleSelection, -}; +use gtk::{glib, ListView, SignalListItemFactory, SingleSelection}; use libcosmic::x; -use std::{cell::RefCell, rc::Rc}; +use pop_launcher_service::IpcClient; +use postage::mpsc::Sender; +use postage::prelude::*; use self::application_object::ApplicationObject; -use self::ipc::LauncherIpc; -mod ipc; +use self::application_row::ApplicationRow; + +const NUM_LAUNCHER_ITEMS: u8 = 10; fn icon_source(icon: >k::Image, source: &Option) { match source { @@ -28,22 +32,50 @@ 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"), - )); +enum Event { + Response(pop_launcher::Response), + Search(String), + Activate(u32), +} +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()); + + //quit shortcut + app.set_accels_for_action("win.quit", &["W", "Escape"]); + //launch shortcuts + for i in 1..10 { + app.set_accels_for_action(&format!("win.launch{}", i), &[&format!("{}", i)]); + } + let window = gtk::ApplicationWindow::builder() .application(app) .decorated(false) - .default_width(480) + .default_width(600) .default_height(440) .title("Launcher") + .resizable(false) .build(); let vbox = gtk::Box::new(gtk::Orientation::Vertical, 16); @@ -57,14 +89,11 @@ fn main() { 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 model = gio::ListStore::new(ApplicationObject::static_type()); let factory = SignalListItemFactory::new(); factory.connect_setup(move |_, list_item| { - let label = Label::new(None); - list_item.set_child(Some(&label)) + let row = ApplicationRow::new(); + list_item.set_child(Some(&row)) }); factory.connect_bind(move |_, list_item| { let application_object = list_item @@ -72,98 +101,85 @@ fn main() { .expect("The item has to exist.") .downcast::() .expect("The item has to be an `ApplicationObject`"); - let name = application_object - .property("name") - .expect("Property name of the wrong type or does not exist!") - .get::() - .expect("Property name needs to be a String."); - let label = list_item + let row = list_item .child() - .expect("The child has to exist.") - .downcast::