diff --git a/Cargo.toml b/Cargo.toml
index aebdb0d..a0e96f0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,3 +14,4 @@ x11 = { version = "2", features = ["xlib"] }
pop-launcher = "=1.0.3"
serde_json = "1.0.70"
once_cell = "1.8.0"
+wnck-sys = "0.1.0"
diff --git a/examples/gtklauncher/README.md b/examples/gtklauncher/README.md
new file mode 100644
index 0000000..f2a6453
--- /dev/null
+++ b/examples/gtklauncher/README.md
@@ -0,0 +1,5 @@
+# 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.
+
+
diff --git a/examples/gtklauncher/application_row/application_row.ui b/examples/gtklauncher/application_row/application_row.ui
new file mode 100644
index 0000000..a70d795
--- /dev/null
+++ b/examples/gtklauncher/application_row/application_row.ui
@@ -0,0 +1,53 @@
+
+
+
+ horizontal
+ 12
+ 4
+ 4
+ true
+
+
+
+
+
+
+
+
+ end
+ false
+
+
+
+
+
diff --git a/examples/gtklauncher/application_row/imp.rs b/examples/gtklauncher/application_row/imp.rs
new file mode 100644
index 0000000..f27eb54
--- /dev/null
+++ b/examples/gtklauncher/application_row/imp.rs
@@ -0,0 +1,38 @@
+use gtk::glib;
+use gtk::prelude::*;
+use gtk::subclass::prelude::*;
+use gtk4 as gtk;
+
+use gtk::CompositeTemplate;
+
+#[derive(Debug, Default, CompositeTemplate)]
+#[template(file = "application_row.ui")]
+pub struct ApplicationRow {
+ #[template_child]
+ pub name: TemplateChild,
+ #[template_child]
+ pub description: TemplateChild,
+ #[template_child]
+ pub shortcut: TemplateChild,
+ #[template_child]
+ pub image: TemplateChild,
+}
+
+#[glib::object_subclass]
+impl ObjectSubclass for ApplicationRow {
+ const NAME: &'static str = "ApplicationRow";
+ type Type = super::ApplicationRow;
+ type ParentType = gtk::Box;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+}
+
+impl ObjectImpl for ApplicationRow {}
+impl WidgetImpl for ApplicationRow {}
+impl BoxImpl for ApplicationRow {}
diff --git a/examples/gtklauncher/application_row/mod.rs b/examples/gtklauncher/application_row/mod.rs
new file mode 100644
index 0000000..8054c92
--- /dev/null
+++ b/examples/gtklauncher/application_row/mod.rs
@@ -0,0 +1,39 @@
+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
new file mode 100644
index 0000000..da62161
--- /dev/null
+++ b/examples/gtklauncher/main.rs
@@ -0,0 +1,289 @@
+use gtk4 as gtk;
+mod application_row;
+use application_row::ApplicationRow;
+use gtk::gdk::Display;
+use gtk::prelude::*;
+use gtk::{gio, glib};
+use libcosmic::x;
+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,
+ );
+ build_ui(app);
+ });
+
+ application.run();
+}
+
+fn build_ui(app: >k::Application) {
+ let window = gtk::ApplicationWindow::builder()
+ .decorated(false)
+ .default_width(600)
+ .default_height(600)
+ .application(app)
+ .title("ListView: Applications Launcher")
+ .build();
+
+ let model = gio::ListStore::new(gio::AppInfo::static_type());
+ gio::AppInfo::all().iter().for_each(|app_info| {
+ model.append(app_info);
+ });
+ let window_model = gtk::Window::list_toplevels();
+ dbg!(window_model.clone());
+ for i in window_model {
+ dbg!(i);
+ }
+ // TODO window ui file and custom class
+ // TODO application search entry ui file and custom class
+ // TODO list open windows in application search entries
+ // TODO list aliases in application search entries
+ let factory = gtk::SignalListItemFactory::new();
+ // the "setup" stage is used for creating the widgets
+ factory.connect_setup(move |_factory, item| {
+ let row = ApplicationRow::new();
+ item.set_child(Some(&row));
+ });
+
+ // the bind stage is used for "binding" the data to the created widgets on the "setup" stage
+ factory.connect_bind(move |_factory, list_item| {
+ let app_info = list_item
+ .item()
+ .unwrap()
+ .downcast::()
+ .unwrap();
+ println!("position: {}", &list_item.position());
+ println!("{}", app_info.name());
+ // println!("{}", app_info.description());
+
+ let child = list_item
+ .child()
+ .unwrap()
+ .downcast::()
+ .unwrap();
+ child.set_app_info(&app_info);
+ if list_item.position() < 9 {
+ child.set_shortcut(list_item.position() + 1);
+ }
+ });
+
+ // A sorter used to sort AppInfo in the model by their name
+ let sorter = gtk::CustomSorter::new(move |obj1, obj2| {
+ let app_info1 = obj1.downcast_ref::().unwrap();
+ let app_info2 = obj2.downcast_ref::().unwrap();
+
+ app_info1
+ .name()
+ .to_lowercase()
+ .cmp(&app_info2.name().to_lowercase())
+ .into()
+ });
+ let filter = gtk::CustomFilter::new(|_obj| true);
+ let filter_model = gtk::FilterListModel::new(Some(&model), Some(filter).as_ref());
+ let sorted_model = gtk::SortListModel::new(Some(&filter_model), Some(&sorter));
+ let slice_model = gtk::SliceListModel::new(Some(&sorted_model), 0, 9);
+ let selection_model = gtk::SingleSelection::new(Some(&slice_model));
+
+ let list_view = gtk::ListView::new(Some(&selection_model), Some(&factory));
+ let action_launch = gio::SimpleAction::new("launch", Some(&i32::static_variant_type()));
+ action_launch.connect_activate(glib::clone!(@weak list_view => move |_action, parameter| {
+ // Get parameter
+ let parameter = parameter
+ .expect("Could not get parameter.")
+ .get::()
+ .expect("The variant needs to be of type `i32`.");
+ println!("{}", parameter);
+ let model = list_view.model().unwrap();
+ let app_info = model
+ .item(u32::try_from(parameter).unwrap());
+
+ if app_info.is_none() {return}
+ let app_info = app_info
+ .unwrap()
+ .downcast::()
+ .unwrap();
+
+ let context = list_view.display().app_launch_context();
+ if let Err(err) = app_info.launch(&[], Some(&context)) {
+ let parent_window = list_view.root().unwrap().downcast::().unwrap();
+
+ gtk::MessageDialog::builder()
+ .text(&format!("Failed to start {}", app_info.name()))
+ .secondary_text(&err.to_string())
+ .message_type(gtk::MessageType::Error)
+ .modal(true)
+ .transient_for(&parent_window)
+ .build()
+ .show();
+ }
+
+ }));
+ window.add_action(&action_launch);
+ for i in 1..10 {
+ let action_launchi = gio::SimpleAction::new(&format!("launch{}", i), None);
+ app.set_accels_for_action(&format!("win.launch{}", i), &[&format!("{}", i)]);
+ window.add_action(&action_launchi);
+ action_launchi.connect_activate(
+ glib::clone!(@weak action_launch, @weak list_view => move |_action, _parameter| {
+ let model = list_view.model().unwrap();
+ let app_info = model
+ .item(i-1);
+
+ println!("launching item {}", i);
+ if app_info.is_none() {return}
+ let app_info = app_info
+ .unwrap()
+ .downcast::()
+ .unwrap();
+ println!("starting {}", app_info.name());
+ let context = list_view.display().app_launch_context();
+ if let Err(err) = app_info.launch(&[], Some(&context)) {
+ let parent_window = list_view.root().unwrap().downcast::().unwrap();
+
+ gtk::MessageDialog::builder()
+ .text(&format!("Failed to start {}", app_info.name()))
+ .secondary_text(&err.to_string())
+ .message_type(gtk::MessageType::Error)
+ .modal(true)
+ .transient_for(&parent_window)
+ .build()
+ .show();
+ }
+ }),
+ );
+ }
+
+ // Launch the application when an item of the list is activated
+ list_view.connect_activate(move |list_view, position| {
+ let model = list_view.model().unwrap();
+ let app_info = model
+ .item(position)
+ .unwrap()
+ .downcast::()
+ .unwrap();
+
+ let context = list_view.display().app_launch_context();
+ if let Err(err) = app_info.launch(&[], Some(&context)) {
+ let parent_window = list_view.root().unwrap().downcast::().unwrap();
+
+ gtk::MessageDialog::builder()
+ .text(&format!("Failed to start {}", app_info.name()))
+ .secondary_text(&err.to_string())
+ .message_type(gtk::MessageType::Error)
+ .modal(true)
+ .transient_for(&parent_window)
+ .build()
+ .show();
+ }
+ });
+
+ let scrolled_window = gtk::ScrolledWindow::builder()
+ .hscrollbar_policy(gtk::PolicyType::Never) // Disable horizontal scrolling
+ .min_content_width(360)
+ .vexpand(true)
+ .child(&list_view)
+ .build();
+
+ let launcher_box = gtk::Box::builder()
+ .margin_top(12)
+ .margin_bottom(12)
+ .margin_start(12)
+ .margin_end(12)
+ //.valign(gtk::Align::Center)
+ //.halign(gtk::Align::Center)
+ //.spacing(12)
+ .orientation(gtk::Orientation::Vertical)
+ .build();
+ let search_input = gtk::Entry::builder()
+ //.margin_top(12)
+ .margin_bottom(12)
+ //.margin_start(12)
+ //.margin_end(12)
+ //.valign(gtk::Align::Center)
+ //.halign(gtk::Align::Center)
+ .build();
+ // Filter model whenever the value of the key "filter" changes
+ search_input.connect_changed(
+ glib::clone!(@weak filter_model, @weak sorted_model => move |search: >k::Entry| {
+ let search_text = search.text().to_string().to_lowercase();
+ let new_filter: gtk::CustomFilter = gtk::CustomFilter::new(move |obj| {
+ let search_res = obj.downcast_ref::()
+ .expect("The Object needs to be of type AppInfo");
+ search_res.name().to_string().to_lowercase().contains(&search_text)
+ });
+ let search_text = search.text().to_string().to_lowercase();
+ let new_sorter: gtk::CustomSorter = gtk::CustomSorter::new(move |obj1, obj2| {
+ let app_info1 = obj1.downcast_ref::().unwrap();
+ let app_info2 = obj2.downcast_ref::().unwrap();
+ if search_text == "" {
+ return app_info1
+ .name()
+ .to_lowercase()
+ .cmp(&app_info2.name().to_lowercase())
+ .into();
+ }
+
+ let i_1 = app_info1.name().to_lowercase().find(&search_text);
+ let i_2 = app_info2.name().to_lowercase().find(&search_text);
+ match (i_1, i_2) {
+ (Some(i_1), Some(i_2)) => i_1.cmp(&i_2).into(),
+ (Some(_), None) => std::cmp::Ordering::Less.into(),
+ (None, Some(_)) => std::cmp::Ordering::Greater.into(),
+ _ => app_info1
+ .name()
+ .to_lowercase()
+ .cmp(&app_info2.name().to_lowercase())
+ .into()
+ }
+ });
+
+ filter_model.set_filter(Some(new_filter).as_ref());
+ sorted_model.set_sorter(Some(new_sorter).as_ref());
+ }),
+ );
+ // 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");
+ }
+ });
+
+ launcher_box.append(&search_input);
+ launcher_box.append(&scrolled_window);
+ window.set_child(Some(&launcher_box));
+ // Add action "quit" to `window` taking no parameter
+ app.set_accels_for_action("win.quit", &["W", "Escape"]);
+ let action_quit = gio::SimpleAction::new("quit", None);
+ action_quit.connect_activate(glib::clone!(@weak window => move |_, _| {
+ window.close();
+ }));
+ window.add_action(&action_quit);
+
+ window.connect_is_active_notify(|win| {
+ if !win.is_active() {
+ win.close();
+ }
+ println!("active or not lets find out...");
+ });
+ window.show();
+}
diff --git a/examples/gtklauncher/screenshot.png b/examples/gtklauncher/screenshot.png
new file mode 100644
index 0000000..8ffcefa
Binary files /dev/null and b/examples/gtklauncher/screenshot.png differ
diff --git a/examples/gtklauncher/style.css b/examples/gtklauncher/style.css
new file mode 100644
index 0000000..0c72f8a
--- /dev/null
+++ b/examples/gtklauncher/style.css
@@ -0,0 +1,15 @@
+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/gtklauncher/window/.#window.ui b/examples/gtklauncher/window/.#window.ui
new file mode 120000
index 0000000..d0bd968
--- /dev/null
+++ b/examples/gtklauncher/window/.#window.ui
@@ -0,0 +1 @@
+wash@pop-os.22535:1638129315
\ No newline at end of file
diff --git a/examples/gtklauncher/window/imp.rs b/examples/gtklauncher/window/imp.rs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/examples/gtklauncher/window/imp.rs
@@ -0,0 +1 @@
+
diff --git a/examples/gtklauncher/window/mod.rs b/examples/gtklauncher/window/mod.rs
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/examples/gtklauncher/window/mod.rs
@@ -0,0 +1 @@
+
diff --git a/examples/gtklauncher/window/window.ui b/examples/gtklauncher/window/window.ui
new file mode 100644
index 0000000..d061a7f
--- /dev/null
+++ b/examples/gtklauncher/window/window.ui
@@ -0,0 +1,30 @@
+
+
+
+ 360
+ To-Do
+
+
+ vertical
+ 12
+ 12
+ 12
+ 12
+ 6
+
+
+
+
+
+ never
+ 360
+ true
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/launcher/launcher_row/imp.rs b/examples/launcher/launcher_row/imp.rs
new file mode 100644
index 0000000..e69de29
diff --git a/examples/launcher/launcher_row/launcher_row.ui b/examples/launcher/launcher_row/launcher_row.ui
new file mode 100644
index 0000000..8e3a569
--- /dev/null
+++ b/examples/launcher/launcher_row/launcher_row.ui
@@ -0,0 +1,40 @@
+?xml version="1.0" encoding="UTF-8"?>
+
+
+ horizontal
+ 12
+
+
+ 6
+ 6
+ 6
+ 6
+ 48
+
+
+
+
+ vertical
+ 6
+ 6
+
+
+ start
+
+
+
+
+
+ start
+ True
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/launcher/launcher_row/mod.rs b/examples/launcher/launcher_row/mod.rs
new file mode 100644
index 0000000..e69de29