From 35eb571528f52e9d8dfa158530bf1984a1d34c4e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Sun, 2 Jan 2022 01:46:44 -0500 Subject: [PATCH] big refactor and cleanup of dock + use tokio mpsc --- Cargo.toml | 18 +- examples/app_library/grid_item/imp.rs | 2 +- examples/dock/dock_list/imp.rs | 33 + examples/dock/dock_list/mod.rs | 473 +++++++++++++ examples/dock/main.rs | 28 +- examples/dock/utils.rs | 18 +- examples/dock/window/.#mod.rs | 1 + examples/dock/window/imp.rs | 29 +- examples/dock/window/mod.rs | 628 +----------------- examples/launcher/main.rs | 10 +- examples/launcher/search_result_object/mod.rs | 4 +- examples/launcher/search_result_row/mod.rs | 2 +- examples/launcher/utils.rs | 1 + examples/launcher/window/mod.rs | 5 - 14 files changed, 578 insertions(+), 674 deletions(-) create mode 100644 examples/dock/dock_list/imp.rs create mode 100644 examples/dock/dock_list/mod.rs create mode 120000 examples/dock/window/.#mod.rs diff --git a/Cargo.toml b/Cargo.toml index 82acb0a7..c787c72f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,30 +10,24 @@ gdk4-x11 = "0.3.0" gio = "0.14.8" x11 = { version = "2", features = ["xlib"] } -# examples/launcher -#pop-launcher = "1.0.3" -pop-launcher = { git = "https://github.com/wash2/launcher.git" } -serde_json = "1.0.72" +# examples pop-launcher-service = { git = "https://github.com/wash2/launcher.git" } -postage = "0.4.1" +pop-launcher = { git = "https://github.com/wash2/launcher.git" } +serde = "1.0.130" +serde_json = "1.0.72" +tokio = { version = "1.15.0", features = ["sync"] } futures = "0.3.17" -glib = "0.14.8" -# examples/gtklauncher +futures-util = "0.3.19" once_cell = "1.8.0" xdg = "2.4.0" -serde = "1.0.130" x11rb = "0.9.0" -async-io = "1.6.0" # zbus zbus = "2.0.0-beta.8" zvariant = "3.0.0" zvariant_derive = "3.0.0" -futures-util = "0.3.19" -gdk4-x11-sys = "0.3.1" [dependencies.gtk4] package = "gtk4" -version = "0.3.1" features = ["v4_4"] [profile.release] diff --git a/examples/app_library/grid_item/imp.rs b/examples/app_library/grid_item/imp.rs index 3da89a4e..c3b7ae60 100644 --- a/examples/app_library/grid_item/imp.rs +++ b/examples/app_library/grid_item/imp.rs @@ -2,7 +2,7 @@ use std::cell::Cell; use std::cell::RefCell; use std::rc::Rc; -use glib; +use gtk4::glib; use gtk4::subclass::prelude::*; #[derive(Debug, Default)] diff --git a/examples/dock/dock_list/imp.rs b/examples/dock/dock_list/imp.rs new file mode 100644 index 00000000..8d45227a --- /dev/null +++ b/examples/dock/dock_list/imp.rs @@ -0,0 +1,33 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use glib::SignalHandlerId; +use gtk4::subclass::prelude::*; +use gtk4::{gio, glib}; +use gtk4::{Box, DragSource, DropTarget, GestureClick, ListView}; +use once_cell::sync::OnceCell; + +#[derive(Debug, Default)] +pub struct DockList { + pub list_view: OnceCell, + pub type_: OnceCell, + pub model: OnceCell, + pub click_controller: OnceCell, + pub drop_controller: OnceCell, + pub drag_source: OnceCell, + pub drag_end_signal: Rc>>, + pub drag_cancel_signal: Rc>>, +} + +#[glib::object_subclass] +impl ObjectSubclass for DockList { + const NAME: &'static str = "DockList"; + type Type = super::DockList; + type ParentType = Box; +} + +impl ObjectImpl for DockList {} + +impl WidgetImpl for DockList {} + +impl BoxImpl for DockList {} diff --git a/examples/dock/dock_list/mod.rs b/examples/dock/dock_list/mod.rs new file mode 100644 index 00000000..23ce4118 --- /dev/null +++ b/examples/dock/dock_list/mod.rs @@ -0,0 +1,473 @@ +use cascade::cascade; +use gdk4::ContentProvider; +use gdk4::Display; +use gdk4::ModifierType; +use gio::DesktopAppInfo; +use gio::Icon; +use glib::Object; +use glib::Type; +use gtk4::prelude::ListModelExt; +use gtk4::prelude::*; +use gtk4::subclass::prelude::*; +use gtk4::DropTarget; +use gtk4::IconTheme; +use gtk4::ListView; +use gtk4::Orientation; +use gtk4::SignalListItemFactory; +use gtk4::Window; +use gtk4::{gio, glib}; +use gtk4::{DragSource, GestureClick}; +use std::fs::File; +use std::path::Path; + +use crate::dock_item::DockItem; +use crate::dock_object::DockObject; +use crate::utils::data_path; +use crate::BoxedWindowList; +use crate::Event; +use crate::Item; +use crate::TX; + +mod imp; + +glib::wrapper! { + pub struct DockList(ObjectSubclass) + @extends gtk4::Widget, gtk4::Box, + @implements gtk4::Accessible, gtk4::Buildable, gtk4::ConstraintTarget, gtk4::Orientable; +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum DockListType { + Saved, + Active, +} + +impl Default for DockListType { + fn default() -> Self { + DockListType::Active + } +} + +impl DockList { + pub fn new(type_: DockListType) -> Self { + let self_: DockList = glib::Object::new(&[]).expect("Failed to create DockList"); + let imp = imp::DockList::from_instance(&self_); + imp.type_.set(type_).unwrap(); + self_.layout(); + //dnd behavior is different for each type, as well as the data in the model + self_.setup_model(); + self_.setup_click_controller(); + self_.setup_drag(); + self_.setup_drop_target(); + self_.setup_factory(); + + self_ + } + + pub fn model(&self) -> &gio::ListStore { + // Get state + let imp = imp::DockList::from_instance(self); + imp.model.get().expect("Could not get model") + } + + pub fn drop_controller(&self) -> &DropTarget { + // Get state + let imp = imp::DockList::from_instance(self); + imp.drop_controller.get().expect("Could not get model") + } + + fn restore_data(&self) { + if let Ok(file) = File::open(data_path()) { + if let Ok(data) = serde_json::from_reader::<_, Vec>(file) { + // dbg!(&data); + let dock_objects: Vec = data + .into_iter() + .filter_map(|d| { + DockObject::from_app_info_path(&d) + .map(|dockobject| dockobject.upcast::()) + }) + .collect(); + // dbg!(&dock_objects); + let model = self.model(); + model.splice(model.n_items(), 0, &dock_objects); + return; + } + } + println!("Error loading saved apps!"); + let model = &self.model(); + xdg::BaseDirectories::new() + .expect("could not access XDG Base directory") + .get_data_dirs() + .iter_mut() + .for_each(|xdg_data_path| { + let defaults = ["Firefox Web Browser", "Files", "Terminal", "Pop!_Shop"]; + xdg_data_path.push("applications"); + // dbg!(&xdg_data_path); + if let Ok(dir_iter) = std::fs::read_dir(xdg_data_path) { + dir_iter.for_each(|dir_entry| { + if let Ok(dir_entry) = dir_entry { + if let Some(path) = dir_entry.path().file_name() { + if let Some(path) = path.to_str() { + if let Some(app_info) = gio::DesktopAppInfo::new(path) { + if app_info.should_show() + && defaults.contains(&app_info.name().as_str()) + { + model.append(&DockObject::new(app_info)); + } else { + // println!("Ignoring {}", path); + } + } else { + // println!("error loading {}", path); + } + } + } + } + }) + } + }); + } + + fn store_data(model: &gio::ListStore) { + // Store todo data in vector + let mut backup_data = Vec::new(); + let mut i = 0; + while let Some(item) = model.item(i) { + // Get `AppGroup` from `glib::Object` + let dock_object = item + .downcast_ref::() + .expect("The object needs to be of type `AppGroupData`."); + // Add todo data to vector and increase position + if let Ok(Some(app_info)) = dock_object + .property("appinfo") + .expect("DockObject must have appinfo property") + .get::>() + { + if let Some(f) = app_info.filename() { + backup_data.push(f); + } + } + i += 1; + } + // dbg!(&backup_data); + // Save state in file + let file = File::create(data_path()).expect("Could not create json file."); + serde_json::to_writer_pretty(file, &backup_data) + .expect("Could not write data to json file"); + } + + fn layout(&self) { + let imp = imp::DockList::from_instance(&self); + let list_view = cascade! { + ListView::default(); + ..set_orientation(Orientation::Horizontal); + }; + if imp.type_.get().unwrap() == &DockListType::Saved { + list_view.set_width_request(64); + } + self.append(&list_view); + imp.list_view.set(list_view).unwrap(); + } + + fn setup_model(&self) { + let imp = imp::DockList::from_instance(self); + let model = gio::ListStore::new(DockObject::static_type()); + + let selection_model = gtk4::NoSelection::new(Some(&model)); + + // Wrap model with selection and pass it to the list view + let list_view = imp.list_view.get().unwrap(); + list_view.set_model(Some(&selection_model)); + imp.model.set(model).expect("Could not set model"); + + if imp.type_.get().unwrap() == &DockListType::Saved { + let model = self.model(); + self.restore_data(); + model.connect_items_changed(|model, _, _removed, _added| { + Self::store_data(&model); + }); + } + } + + fn setup_click_controller(&self) { + let imp = imp::DockList::from_instance(self); + let controller = GestureClick::builder() + .button(0) + .propagation_limit(gtk4::PropagationLimit::None) + .propagation_phase(gtk4::PropagationPhase::Capture) + .build(); + self.add_controller(&controller); + + let model = self.model(); + let list_view = imp.list_view.get().unwrap(); + controller.connect_released(glib::clone!(@weak model, @weak list_view => move |self_, _, x, _y| { + let window = list_view.root().unwrap().downcast::().unwrap(); + let max_x = list_view.allocated_width(); + let n_buckets = model.n_items(); + let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; + dbg!(self_.current_button()); + dbg!(self_.last_event(self_.current_sequence().as_ref())); + let click_modifier = if let Some(event) = self_.last_event(self_.current_sequence().as_ref()) { + dbg!(&event); + Some(event.modifier_state()) + } + else { + None + }; + dbg!(click_modifier); + // Launch the application when an item of the list is activated + let focus_window = move |first_focused_item: &Item| { + let entity = first_focused_item.entity.clone(); + glib::MainContext::default().spawn_local(async move { + if let Some(tx) = TX.get() { + let _ = tx.send(Event::Activate(entity)).await; + } + }); + }; + if let Some(item) = model.item(index) { + if let Ok(dock_object) = item.downcast::() { + let active = dock_object.property("active").expect("DockObject must have active property").get::().expect("Failed to convert value to WindowList"); + let app_info = dock_object.property("appinfo").expect("DockObject must have appinfo property").get::>().expect("Failed to convert value to DesktopAppInfo"); + match (self_.current_button(), click_modifier, active.0.iter().next(), app_info) { + (click, Some(click_modifier), Some(first_focused_item), _) if click == 1 && !click_modifier.contains(ModifierType::CONTROL_MASK) => focus_window(first_focused_item), + (click, None, Some(first_focused_item), _) if click == 1 => focus_window(first_focused_item), + (click, _, _, Some(app_info)) | (click, _, None, Some(app_info)) if click != 3 => { + let context = window.display().app_launch_context(); + if let Err(err) = app_info.launch(&[], Some(&context)) { + gtk4::MessageDialog::builder() + .text(&format!("Failed to start {}", app_info.name())) + .secondary_text(&err.to_string()) + .message_type(gtk4::MessageType::Error) + .modal(true) + .transient_for(&window) + .build() + .show(); + } + + } + (click, _, _, _) if click == 3 => { + println!("handling right click"); + } + _ => println!("Failed to process click.") + } + } + } + })); + imp.click_controller.set(controller).unwrap(); + } + + fn setup_drop_target(&self) { + let imp = imp::DockList::from_instance(self); + if imp.type_.get().unwrap() != &DockListType::Saved { + return; + } + + let drop_target_widget = &imp.list_view.get().unwrap(); + let mut drop_actions = gdk4::DragAction::COPY; + drop_actions.insert(gdk4::DragAction::MOVE); + let drop_format = gdk4::ContentFormats::for_type(Type::STRING); + let drop_format = drop_format + .union(&gdk4::ContentFormats::for_type(Type::U32)) + .expect("couldn't make union"); + let drop_controller = DropTarget::builder() + .preload(true) + .actions(drop_actions) + .formats(&drop_format) + .build(); + drop_target_widget.add_controller(&drop_controller); + + let model = self.model(); + let list_view = &imp.list_view.get().unwrap(); + let drag_end = &imp.drag_end_signal; + let drag_source = &imp.drag_source.get().unwrap(); + drop_controller.connect_drop( + glib::clone!(@weak model, @weak list_view, @weak drag_end, @weak drag_source => @default-return true, move |_self, drop_value, x, _y| { + //calculate insertion location + let max_x = list_view.allocated_width(); + let n_buckets = model.n_items() * 2; + + let drop_bucket = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; + let index = if drop_bucket == 0 { + 0 + } else if drop_bucket == n_buckets - 1 { + model.n_items() + } else { + (drop_bucket + 1) / 2 + }; + + if let Ok(Some(path_str)) = drop_value.get::>() { + let desktop_path = &Path::new(&path_str); + if let Some(pathbase) = desktop_path.file_name() { + if let Some(app_info) = gio::DesktopAppInfo::new(&pathbase.to_string_lossy()) { + // remove item if already exists + let mut i: u32 = 0; + let mut index_of_existing_app: Option = None; + while let Some(item) = model.item(i) { + if let Ok(cur_app_info) = item.downcast::() { + if let Ok(Some(cur_app_info)) = cur_app_info.property("appinfo").expect("property appinfo missing from DockObject").get::>() { + if cur_app_info.filename() == Some(Path::new(&path_str).to_path_buf()) { + index_of_existing_app = Some(i); + } + } + } + i += 1; + } + if let Some(index_of_existing_app) = index_of_existing_app { + // remove existing entry + model.remove(index_of_existing_app); + if let Some(old_handle) = drag_end.replace(None) { + glib::signal_handler_disconnect(&drag_source, old_handle); + } + } + model.insert(index, &DockObject::new(app_info)); + } + } + } + else if let Ok(old_index) = drop_value.get::() { + if let Some(item) = model.item(old_index) { + if let Ok(dock_object) = item.downcast::() { + model.remove(old_index); + model.insert(index, &dock_object); + if let Some(old_handle) = drag_end.replace(None) { + glib::signal_handler_disconnect(&drag_source, old_handle); + } + } + } + } + else { + // dbg!("rejecting drop"); + _self.reject(); + } + glib::MainContext::default().spawn_local(async move { + let _ = TX.get().unwrap().send(Event::RefreshFromCache).await; + }); + true + }), + ); + + imp.drop_controller + .set(drop_controller) + .expect("Could not set dock dnd drop controller"); + } + + fn setup_drag(&self) { + let imp = imp::DockList::from_instance(self); + let type_ = imp.type_.get().unwrap(); + + let actions = match type_ { + &DockListType::Saved => gdk4::DragAction::MOVE, + &DockListType::Active => gdk4::DragAction::COPY, + }; + let drag_source = DragSource::builder() + .name("dock drag source") + .actions(actions) + .build(); + + let model = self.model(); + let list_view = imp.list_view.get().unwrap(); + let drag_end = &imp.drag_end_signal; + let drag_cancel = &imp.drag_cancel_signal; + let type_ = type_.clone(); + list_view.add_controller(&drag_source); + drag_source.connect_prepare(glib::clone!(@weak model, @weak list_view, @weak drag_end, @weak drag_cancel => @default-return None, move |self_, x, _y| { + let max_x = list_view.allocated_width(); + // dbg!(max_x); + // dbg!(max_y); + let n_buckets = model.n_items(); + + let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; + if let Some(item) = model.item(index) { + if type_ == DockListType::Saved { + if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end( + glib::clone!(@weak model => move |_self, _drag, _delete_data| { + dbg!(_delete_data); + if _delete_data { + model.remove(index); + glib::MainContext::default().spawn_local(async move { + if let Some(tx) = TX.get() { + let _ = tx.send(Event::RefreshFromCache).await; + } + }); + }; + }), + ))) { + glib::signal_handler_disconnect(self_, old_handle); + } + if let Some(old_handle) = drag_cancel.replace(Some(self_.connect_drag_cancel( + glib::clone!(@weak model => @default-return false, move |_self, _drag, cancel_reason| { + if cancel_reason != gdk4::DragCancelReason::UserCancelled { + model.remove(index); + glib::MainContext::default().spawn_local(async move { + if let Some(tx) = TX.get() { + let _ = tx.send(Event::RefreshFromCache).await; + } + }); + true + } else { + false + } + }), + ))) { + glib::signal_handler_disconnect(self_, old_handle); + } + } + if let Ok(dock_object) = item.downcast::() { + if let Ok(Some(app_info)) = dock_object.property("appinfo").expect("property appinfo missing from DockObject").get::>() { + let icon = app_info + .icon() + .unwrap_or(Icon::for_string("image-missing").expect("Failed to set default icon")); + + if let Some(default_display) = &Display::default() { + if let Some(icon_theme) = IconTheme::for_display(default_display) { + if let Some(paintable_icon) = icon_theme.lookup_by_gicon( + &icon, + 64, + 1, + gtk4::TextDirection::None, + gtk4::IconLookupFlags::empty(), + ) { + self_.set_icon(Some(&paintable_icon), 32, 32); + } + } + } + + // saved app list provides index + return match type_ { + DockListType::Saved => Some(ContentProvider::for_value(&index.to_value())), + DockListType::Active => app_info.filename().map(|file| ContentProvider::for_value(&file.to_string_lossy().to_value())) + } + } + } + } + None + })); + imp.drag_source + .set(drag_source) + .expect("Could not set saved drag source"); + } + + fn setup_factory(&self) { + let factory = SignalListItemFactory::new(); + factory.connect_setup(move |_, list_item| { + let dock_item = DockItem::new(); + list_item.set_child(Some(&dock_item)); + }); + let imp = imp::DockList::from_instance(self); + let model = imp.model.get().expect("Failed to get saved app model."); + factory.connect_bind(glib::clone!(@weak model => move |_, list_item| { + let application_object = list_item + .item() + .expect("The item has to exist.") + .downcast::() + .expect("The item has to be a `DockObject`"); + let dock_item = list_item + .child() + .expect("The list item child needs to exist.") + .downcast::() + .expect("The list item type needs to be `DockItem`"); + + dock_item.set_app_info(&application_object); + })); + // Set the factory of the list view + imp.list_view.get().unwrap().set_factory(Some(&factory)); + } +} diff --git a/examples/dock/main.rs b/examples/dock/main.rs index 67f0d772..0b60c605 100644 --- a/examples/dock/main.rs +++ b/examples/dock/main.rs @@ -1,6 +1,8 @@ use std::collections::BTreeMap; use std::time::Duration; +use tokio::sync::mpsc; +// use async_std::channel::{bounded, Receiver, Sender}; use futures::executor::block_on; use gdk4::Display; use gio::DesktopAppInfo; @@ -11,19 +13,19 @@ use gtk4::Application; use gtk4::CssProvider; use gtk4::StyleContext; use once_cell::sync::OnceCell; -use postage::mpsc::Sender; -use postage::prelude::*; use serde::{Deserialize, Serialize}; use x11rb::rust_connection::RustConnection; use zbus::Connection; use zvariant_derive::Type; +use crate::dock_list::DockListType; use crate::utils::BoxedWindowList; use self::dock_object::DockObject; use self::window::Window; mod dock_item; +mod dock_list; mod dock_object; mod utils; mod window; @@ -32,7 +34,7 @@ const DEST: &str = "com.System76.PopShell"; const PATH: &str = "/com/System76/PopShell"; const NUM_LAUNCHER_ITEMS: u8 = 10; -static TX: OnceCell> = OnceCell::new(); +static TX: OnceCell> = OnceCell::new(); static X11_CONN: OnceCell = OnceCell::new(); pub enum Event { @@ -49,10 +51,10 @@ pub struct Item { desktop_entry: String, } -fn spawn_launcher(tx: Sender) -> Connection { +fn spawn_launcher(tx: mpsc::Sender) -> Connection { let connection = block_on(Connection::session()).unwrap(); - let mut sender = tx.clone(); + let sender = tx.clone(); let conn = connection.clone(); glib::MainContext::default().spawn_local(async move { loop { @@ -104,12 +106,9 @@ fn main() { load_css() }); - // TODO investigate multiple signals to connect_activate - // crashes when called twice bc of singleton app.connect_activate(move |app| { - // Seems that over a long period of time, this might be called multiple times - // The global variables should be initialized outside this closure - let (tx, mut rx) = postage::mpsc::channel(1); + let (tx, mut rx) = mpsc::channel(100); + let zbus_conn = spawn_launcher(tx.clone()); if TX.set(tx).is_err() { println!("failed to set global Sender. Exiting"); @@ -127,6 +126,7 @@ fn main() { let cached_results: Vec = vec![]; glib::MainContext::default().spawn_local(async move { futures::pin_mut!(cached_results); + // let rx = RX.get().unwrap().clone(); while let Some(event) = rx.recv().await { match event { Event::Activate(e) => { @@ -157,7 +157,7 @@ fn main() { // update active app stacks for saved apps into the saved app model // then put the rest in the active app model (which doesn't include saved apps) - let saved_app_model = window.saved_app_model(); + let saved_app_model = window.model(DockListType::Saved); let mut saved_i: u32 = 0; while let Some(item) = saved_app_model.item(saved_i) { @@ -206,7 +206,7 @@ fn main() { saved_i += 1; } - let active_app_model = window.active_app_model(); + let active_app_model = window.model(DockListType::Active); let model_len = active_app_model.n_items(); let new_results: Vec = stack_active .into_iter() @@ -259,7 +259,7 @@ fn main() { // update active app stacks for saved apps into the saved app model // then put the rest in the active app model (which doesn't include saved apps) - let saved_app_model = window.saved_app_model(); + let saved_app_model = window.model(DockListType::Saved); let mut saved_i: u32 = 0; while let Some(item) = saved_app_model.item(saved_i) { @@ -305,7 +305,7 @@ fn main() { saved_i += 1; } - let active_app_model = window.active_app_model(); + let active_app_model = window.model(DockListType::Active); let model_len = active_app_model.n_items(); let new_results: Vec = stack_active .into_iter() diff --git a/examples/dock/utils.rs b/examples/dock/utils.rs index 4c95bd63..42b27c7e 100644 --- a/examples/dock/utils.rs +++ b/examples/dock/utils.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; -use glib; +use gtk4::glib; +use std::future::Future; use crate::DockObject; use crate::Item; @@ -20,3 +21,18 @@ pub fn data_path() -> PathBuf { path.push("data.json"); path } + +pub fn _thread_context() -> glib::MainContext { + glib::MainContext::thread_default().unwrap_or_else(|| { + let ctx = glib::MainContext::new(); + ctx.push_thread_default(); + ctx + }) +} + +pub fn _block_on(future: F) -> F::Output +where + F: Future, +{ + _thread_context().block_on(future) +} diff --git a/examples/dock/window/.#mod.rs b/examples/dock/window/.#mod.rs new file mode 120000 index 00000000..d8189d53 --- /dev/null +++ b/examples/dock/window/.#mod.rs @@ -0,0 +1 @@ +ashleywulber@pop-os.394616:1640815731 \ No newline at end of file diff --git a/examples/dock/window/imp.rs b/examples/dock/window/imp.rs index ae889d6b..5fdf47fb 100644 --- a/examples/dock/window/imp.rs +++ b/examples/dock/window/imp.rs @@ -1,37 +1,22 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use glib::SignalHandlerId; +use gtk4::glib; use gtk4::subclass::prelude::*; -use gtk4::DragSource; +use gtk4::Box; use gtk4::DropTarget; use gtk4::EventControllerMotion; -use gtk4::ListView; use gtk4::Revealer; -use gtk4::{gio, glib}; -use gtk4::{Box, GestureClick}; use once_cell::sync::OnceCell; +use crate::dock_list::DockList; + // Object holding the state #[derive(Default)] pub struct Window { - pub saved_app_list_view: OnceCell, - pub active_app_list_view: OnceCell, pub revealer: OnceCell, pub cursor_handle: OnceCell, - pub saved_app_model: OnceCell, - pub active_app_model: OnceCell, pub cursor_motion_controller: OnceCell, - pub saved_click_controller: Rc>, - pub active_click_controller: Rc>, - pub drop_controller: OnceCell, - pub saved_drag_source: Rc>, - pub active_drag_source: Rc>, - pub saved_drag_end_signal: Rc>>, - pub active_drag_end_signal: Rc>>, - pub saved_drag_cancel_signal: Rc>>, - pub active_drag_cancel_signal: Rc>>, - pub window_drop_controller: Rc>, + pub window_drop_controller: OnceCell, + pub saved_list: OnceCell, + pub active_list: OnceCell, } // The central trait for subclassing a GObject diff --git a/examples/dock/window/mod.rs b/examples/dock/window/mod.rs index 142f96e5..bbefe361 100644 --- a/examples/dock/window/mod.rs +++ b/examples/dock/window/mod.rs @@ -1,47 +1,29 @@ use cascade::cascade; -use gdk4::ContentProvider; -use gdk4::Display; -use gdk4::ModifierType; use gdk4::Rectangle; use gdk4_x11::X11Display; use gdk4_x11::X11Surface; -use gio::DesktopAppInfo; -use gio::Icon; use glib::Object; use glib::Type; -use gtk4::prelude::ListModelExt; use gtk4::prelude::*; use gtk4::subclass::prelude::*; use gtk4::Align; +use gtk4::Application; use gtk4::Box; use gtk4::DropTarget; use gtk4::EventControllerMotion; -use gtk4::IconTheme; -use gtk4::ListView; use gtk4::Orientation; use gtk4::Revealer; use gtk4::RevealerTransitionType; use gtk4::Separator; use gtk4::{gio, glib}; -use gtk4::{Application, SignalListItemFactory}; -use gtk4::{DragSource, GestureClick}; -use postage::prelude::Sink; -use std::fs::File; -use std::path::Path; use x11rb::connection::Connection; use x11rb::protocol::xproto; use x11rb::protocol::xproto::ConnectionExt; use libcosmic::x; -use crate::BoxedWindowList; -// use crate::ApplicationObject; -use crate::dock_item::DockItem; -use crate::dock_object::DockObject; -use crate::utils::data_path; -use crate::Event; -use crate::Item; -use crate::TX; +use crate::dock_list::DockList; +use crate::dock_list::DockListType; use crate::X11_CONN; mod imp; @@ -93,11 +75,7 @@ impl Window { dock.add_css_class("dock"); revealer.set_child(Some(&dock)); - let saved_app_list_view = cascade! { - ListView::builder().build(); - ..set_orientation(Orientation::Horizontal); - ..set_width_request(64); - }; + let saved_app_list_view = DockList::new(DockListType::Saved); dock.append(&saved_app_list_view); let separator = cascade! { @@ -107,88 +85,36 @@ impl Window { }; dock.append(&separator); - let active_app_list_view = cascade! { - ListView::default(); - ..set_orientation(Orientation::Horizontal); - }; + let active_app_list_view = DockList::new(DockListType::Active); dock.append(&active_app_list_view); imp.cursor_handle.set(cursor_handle).unwrap(); imp.revealer.set(revealer).unwrap(); - imp.saved_app_list_view.set(saved_app_list_view).unwrap(); - imp.active_app_list_view.set(active_app_list_view).unwrap(); + imp.saved_list.set(saved_app_list_view).unwrap(); + imp.active_list.set(active_app_list_view).unwrap(); // Setup - self_.setup_model(); self_.setup_motion_controller(); - self_.setup_click_controller(); self_.setup_drop_target(); - self_.setup_drag_source(); - self_.restore_saved_apps(); self_.setup_callbacks(); - self_.setup_click_callbacks(); - self_.setup_factory(); - // obj.setup_window_callbacks(); - // obj.setup_saved_list_callbacks(); - // obj.setup_active_list_callbacks(); - // obj.setup_drag_callbacks(); self_ } - pub fn saved_app_model(&self) -> &gio::ListStore { + pub fn model(&self, type_: DockListType) -> &gio::ListStore { // Get state let imp = imp::Window::from_instance(self); - imp.saved_app_model - .get() - .expect("Could not get saved_app_model") - } - - pub fn active_app_model(&self) -> &gio::ListStore { - // Get state - let imp = imp::Window::from_instance(self); - imp.active_app_model - .get() - .expect("Could not get active_app_model") - } - - fn setup_model(&self) { - // Get state and set model - - let imp = imp::Window::from_instance(self); - let saved_app_model = gio::ListStore::new(DockObject::static_type()); - - let saved_selection_model = gtk4::NoSelection::new(Some(&saved_app_model)); - - imp.saved_app_model - .set(saved_app_model) - .expect("Could not set model"); - // Wrap model with selection and pass it to the list view - let saved_app_list_view = imp.saved_app_list_view.get().unwrap(); - saved_app_list_view.set_model(Some(&saved_selection_model)); - - let active_app_model = gio::ListStore::new(DockObject::static_type()); - let active_selection_model = gtk4::NoSelection::new(Some(&active_app_model)); - - imp.active_app_model - .set(active_app_model) - .expect("Could not set model"); - // Wrap model with selection and pass it to the list view - let active_app_list_view = imp.active_app_list_view.get().unwrap(); - active_app_list_view.set_model(Some(&active_selection_model)); + match type_ { + DockListType::Active => imp.active_list.get().unwrap().model(), + DockListType::Saved => imp.saved_list.get().unwrap().model(), + } } fn setup_callbacks(&self) { // Get state let imp = imp::Window::from_instance(self); let window = self.clone().upcast::(); - let saved_app_list_view = &imp.saved_app_list_view.get().unwrap(); - let saved_app_model = &imp - .saved_app_model - .get() - .expect("Failed to get saved app model"); - let cursor_event_controller = &imp.cursor_motion_controller.get().unwrap(); - let drop_controller = &imp.drop_controller.get().unwrap(); + // let drop_controller = &imp.drop_controller.get().unwrap(); let window_drop_controller = &imp.window_drop_controller.get().unwrap(); let revealer = &imp.revealer.get().unwrap(); window.connect_show( @@ -290,16 +216,18 @@ impl Window { } })); + let drop_controller = imp.saved_list.get().unwrap().drop_controller(); cursor_event_controller.connect_leave( glib::clone!(@weak revealer, @weak drop_controller => move |_evc| { // only hide if DnD is not happening if drop_controller.current_drop().is_none() { - // dbg!("hello, mouse left me :)"); + dbg!("hello, mouse left me :)"); revealer.set_reveal_child(false); } }), ); + // hack to prevent hiding window when dnd from other apps drop_controller.connect_enter(glib::clone!(@weak revealer => @default-return gdk4::DragAction::COPY, move |_self, _x, _y| { revealer.set_reveal_child(true); @@ -309,93 +237,6 @@ impl Window { println!("dropping into window"); false }); - - // drag end handler - // must be modified in case of reorder... - let drag_end = &imp.saved_drag_end_signal; - let saved_drag_source = &imp.saved_drag_source; - drop_controller.connect_drop( - glib::clone!(@weak saved_app_model, @weak saved_app_list_view, @weak drag_end, @weak saved_drag_source => @default-return true, move |_self, drop_value, x, _y| { - //calculate insertion location - // dbg!(x); - // dbg!(y); - let max_x = saved_app_list_view.allocated_width(); - // dbg!(max_x); - // dbg!(max_y); - let n_buckets = saved_app_model.n_items() * 2; - - let drop_bucket = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; - let index = if drop_bucket == 0 { - 0 - } else if drop_bucket == n_buckets - 1 { - saved_app_model.n_items() - } else { - (drop_bucket + 1) / 2 - }; - - if let Ok(Some(path_str)) = drop_value.get::>() { - let desktop_path = &Path::new(&path_str); - if let Some(pathbase) = desktop_path.file_name() { - if let Some(app_info) = gio::DesktopAppInfo::new(&pathbase.to_string_lossy()) { - // remove item if already exists - let mut i: u32 = 0; - let mut index_of_existing_app: Option = None; - while let Some(item) = saved_app_model.item(i) { - if let Ok(cur_app_info) = item.downcast::() { - if let Ok(Some(cur_app_info)) = cur_app_info.property("appinfo").expect("property appinfo missing from DockObject").get::>() { - // dbg!(cur_app_info.filename()); - if cur_app_info.filename() == Some(Path::new(&path_str).to_path_buf()) { - index_of_existing_app = Some(i); - } - } - } - i += 1; - } - // dbg!(app_info.name()); - // dbg!(index_of_existing_app); - if let Some(index_of_existing_app) = index_of_existing_app { - // remove existing entry - saved_app_model.remove(index_of_existing_app); - if let Some(old_handle) = drag_end.replace(None) { - glib::signal_handler_disconnect(saved_drag_source.get().expect("Failed to get drag handler"), old_handle); - } - } - - // dbg!(index); - // dbg!("dropped it!"); - // dbg!(drop_value.type_()); - saved_app_model.insert(index, &DockObject::new(app_info)); - } - } - } - else if let Ok(old_index) = drop_value.get::() { - if let Some(item) = saved_app_model.item(old_index) { - if let Ok(dock_object) = item.downcast::() { - saved_app_model.remove(old_index); - saved_app_model.insert(index, &dock_object); - if let Some(old_handle) = drag_end.replace(None) { - glib::signal_handler_disconnect(saved_drag_source.get().expect("Failed to get drag handler"), old_handle); - } - } - } - } - else { - // dbg!("rejecting drop"); - _self.reject(); - } - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let mut tx = tx.clone(); - let _ = tx.send(Event::RefreshFromCache).await; - } - }); - true - }), - ); - - saved_app_model.connect_items_changed(|saved_app_model, _, _removed, _added| { - Self::store_saved_apps(saved_app_model); - }); } fn setup_motion_controller(&self) { @@ -411,122 +252,16 @@ impl Window { .set(ev) .expect("Could not set event controller"); } - - fn setup_click_controller(&self) { - let imp = imp::Window::from_instance(self); - let saved_app_list_view = &imp.saved_app_list_view.get().unwrap(); - let controller = GestureClick::builder() - .button(0) - .propagation_limit(gtk4::PropagationLimit::None) - .propagation_phase(gtk4::PropagationPhase::Capture) - .build(); - saved_app_list_view.add_controller(&controller); - - imp.saved_click_controller - .set(controller) - .expect("Could not set event controller"); - - let imp = imp::Window::from_instance(self); - let active_app_list_view = &imp.active_app_list_view.get().unwrap(); - let controller = GestureClick::builder() - .button(0) - .propagation_limit(gtk4::PropagationLimit::None) - .propagation_phase(gtk4::PropagationPhase::Capture) - .build(); - active_app_list_view.add_controller(&controller); - - imp.active_click_controller - .set(controller) - .expect("Could not set event controller"); - } - - fn setup_click_callbacks(&self) { - let imp = imp::Window::from_instance(self); - let window = self.clone().upcast::(); - let saved_click_controller = imp - .saved_click_controller - .get() - .expect("Failed to get saved click controller"); - let saved_app_model = imp - .saved_app_model - .get() - .expect("Failed to get saved_app_model"); - let saved_app_list_view = &imp.saved_app_list_view.get().unwrap(); - saved_click_controller.connect_released(glib::clone!(@weak saved_app_model, @weak saved_app_list_view, @weak window => move |self_, _, x, _y| { - let max_x = saved_app_list_view.allocated_width(); - let n_buckets = saved_app_model.n_items(); - let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; - dbg!(self_.current_button()); - dbg!(self_.last_event(self_.current_sequence().as_ref())); - let click_modifier = if let Some(event) = self_.last_event(self_.current_sequence().as_ref()) { - dbg!(&event); - Some(event.modifier_state()) - } - else { - None - }; - dbg!(click_modifier); - // Launch the application when an item of the list is activated - let focus_window = move |first_focused_item: &Item| { - let entity = first_focused_item.entity.clone(); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let mut tx = tx.clone(); - let _ = tx.send(Event::Activate(entity)).await; - } - }); - }; - if let Some(item) = saved_app_model.item(index) { - if let Ok(dock_object) = item.downcast::() { - let active = dock_object.property("active").expect("DockObject must have active property").get::().expect("Failed to convert value to WindowList"); - let app_info = dock_object.property("appinfo").expect("DockObject must have appinfo property").get::>().expect("Failed to convert value to DesktopAppInfo"); - match (self_.current_button(), click_modifier, active.0.iter().next(), app_info) { - (click, Some(click_modifier), Some(first_focused_item), _) if click == 1 && !click_modifier.contains(ModifierType::CONTROL_MASK) => focus_window(first_focused_item), - (click, None, Some(first_focused_item), _) if click == 1 => focus_window(first_focused_item), - (click, _, _, Some(app_info)) | (click, _, None, Some(app_info)) if click != 3 => { - let context = window.display().app_launch_context(); - if let Err(err) = app_info.launch(&[], Some(&context)) { - gtk4::MessageDialog::builder() - .text(&format!("Failed to start {}", app_info.name())) - .secondary_text(&err.to_string()) - .message_type(gtk4::MessageType::Error) - .modal(true) - .transient_for(&window) - .build() - .show(); - } - - } - (click, _, _, _) if click == 3 => { - println!("handling right click"); - } - _ => println!("Failed to process click.") - } - } - } - })); - } - fn setup_drop_target(&self) { + // hack for revealing hidden dock when drag enters dock window let imp = imp::Window::from_instance(self); - let drop_target_widget = &imp.saved_app_list_view.get().unwrap(); let mut drop_actions = gdk4::DragAction::COPY; drop_actions.insert(gdk4::DragAction::MOVE); let drop_format = gdk4::ContentFormats::for_type(Type::STRING); let drop_format = drop_format .union(&gdk4::ContentFormats::for_type(Type::U32)) .expect("couldn't make union"); - let drop_target_controller = DropTarget::builder() - .preload(true) - .actions(drop_actions) - .formats(&drop_format) - .build(); - drop_target_widget.add_controller(&drop_target_controller); - imp.drop_controller - .set(drop_target_controller) - .expect("Could not set dock dnd drop controller"); - // hack for revealing hidden dock when drag enters dock window let window_drop_target_controller = DropTarget::builder() .actions(drop_actions) .formats(&drop_format) @@ -538,333 +273,4 @@ impl Window { .set(window_drop_target_controller) .expect("Could not set dock dnd drop controller"); } - - fn setup_drag_source(&self) { - let imp = imp::Window::from_instance(self); - let saved_app_list_view = &imp.saved_app_list_view.get().unwrap(); - let saved_app_model = imp - .saved_app_model - .get() - .expect("Failed to get saved app model."); - - let actions = gdk4::DragAction::MOVE; - let saved_drag_source = DragSource::builder() - .name("dock drag source") - .actions(actions) - .build(); - - let drag_end = &imp.saved_drag_end_signal; - let drag_cancel = &imp.saved_drag_cancel_signal; - saved_app_list_view.add_controller(&saved_drag_source); - saved_drag_source.connect_prepare(glib::clone!(@weak saved_app_model, @weak saved_app_list_view, @weak drag_end, @weak drag_cancel => @default-return None, move |self_, x, _y| { - let max_x = saved_app_list_view.allocated_width(); - // dbg!(max_x); - // dbg!(max_y); - let n_buckets = saved_app_model.n_items(); - - let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; - if let Some(item) = saved_app_model.item(index) { - if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end( - glib::clone!(@weak saved_app_model => move |_self, _drag, _delete_data| { - dbg!(_delete_data); - if _delete_data { - saved_app_model.remove(index); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let mut tx = tx.clone(); - let _ = tx.send(Event::RefreshFromCache).await; - } - }); - }; - }), - ))) { - glib::signal_handler_disconnect(self_, old_handle); - } - if let Some(old_handle) = drag_cancel.replace(Some(self_.connect_drag_cancel( - glib::clone!(@weak saved_app_model => @default-return false, move |_self, _drag, cancel_reason| { - if cancel_reason != gdk4::DragCancelReason::UserCancelled { - saved_app_model.remove(index); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let mut tx = tx.clone(); - let _ = tx.send(Event::RefreshFromCache).await; - } - }); - true - } else { - false - } - }), - ))) { - glib::signal_handler_disconnect(self_, old_handle); - } - if let Ok(dock_object) = item.downcast::() { - if let Ok(Some(app_info)) = dock_object.property("appinfo").expect("property appinfo missing from DockObject").get::>() { - let icon = app_info - .icon() - .unwrap_or(Icon::for_string("image-missing").expect("Failed to set default icon")); - - if let Some(default_display) = &Display::default() { - if let Some(icon_theme) = IconTheme::for_display(default_display) { - if let Some(paintable_icon) = icon_theme.lookup_by_gicon( - &icon, - 64, - 1, - gtk4::TextDirection::None, - gtk4::IconLookupFlags::empty(), - ) { - self_.set_icon(Some(&paintable_icon), 32, 32); - } - } - } - - // saved app list provides index - let p = ContentProvider::for_value(&index.to_value()); - dbg!(p.formats().types()); - return Some(p); - // if let Some(file) = app_info.filename() { - // return Some(ContentProvider::for_value(&file.to_string_lossy().to_value())); - // } - } - } - } - None - })); - imp.saved_drag_source - .set(saved_drag_source) - .expect("Could not set saved drag source"); - - let active_app_list_view = &imp.active_app_list_view.get().unwrap(); - let active_app_model = imp - .active_app_model - .get() - .expect("Failed to get saved app model."); - - let actions = gdk4::DragAction::MOVE; - let active_drag_source = DragSource::builder() - .name("dock drag source") - .actions(actions) - .build(); - let drag_end = &imp.active_drag_end_signal; - let drag_cancel = &imp.active_drag_cancel_signal; - - active_drag_source.connect_drag_begin(|_self, drag| { - drag.set_selected_action(gdk4::DragAction::MOVE); - }); - - active_app_list_view.add_controller(&active_drag_source); - active_drag_source.connect_prepare(glib::clone!(@weak active_app_model, @weak active_app_list_view, @weak drag_end, @weak drag_cancel => @default-return None, move |self_, x, _y| { - let max_x = active_app_list_view.allocated_width(); - // dbg!(max_x); - // dbg!(max_y); - let n_buckets = active_app_model.n_items(); - let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32; - if let Some(item) = active_app_model.item(index) { - if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end( - glib::clone!(@weak active_app_model => move |_self, _drag, _delete_data| { - dbg!(_delete_data); - if _delete_data { - active_app_model.remove(index); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let mut tx = tx.clone(); - let _ = tx.send(Event::RefreshFromCache).await; - } - }); - }; - }), - ))) { - glib::signal_handler_disconnect(self_, old_handle); - } - if let Some(old_handle) = drag_cancel.replace(Some(self_.connect_drag_cancel( - glib::clone!(@weak active_app_model => @default-return false, move |_self, _drag, cancel_reason| { - if cancel_reason != gdk4::DragCancelReason::UserCancelled { - active_app_model.remove(index); - glib::MainContext::default().spawn_local(async move { - if let Some(tx) = TX.get() { - let mut tx = tx.clone(); - let _ = tx.send(Event::RefreshFromCache).await; - } - }); - true - } else { - false - } - }), - ))) { - glib::signal_handler_disconnect(self_, old_handle); - } - - - if let Ok(dock_object) = item.downcast::() { - if let Ok(Some(app_info)) = dock_object.property("appinfo").expect("property appinfo missing from DockObject").get::>() { - let icon = app_info - .icon() - .unwrap_or(Icon::for_string("image-missing").expect("Failed to set default icon")); - - if let Some(default_display) = &Display::default() { - if let Some(icon_theme) = IconTheme::for_display(default_display) { - if let Some(paintable_icon) = icon_theme.lookup_by_gicon( - &icon, - 64, - 1, - gtk4::TextDirection::None, - gtk4::IconLookupFlags::empty(), - ) { - self_.set_icon(Some(&paintable_icon), 32, 32); - } - } - } - if let Some(file) = app_info.filename() { - return Some(ContentProvider::for_value(&file.to_string_lossy().to_value())); - } - } - } - } - None - })); - imp.active_drag_source - .set(active_drag_source) - .expect("Could not set saved drag source"); - } - - fn setup_factory(&self) { - let saved_app_factory = SignalListItemFactory::new(); - saved_app_factory.connect_setup(move |_, list_item| { - let dock_item = DockItem::new(); - list_item.set_child(Some(&dock_item)); - }); - let imp = imp::Window::from_instance(self); - let saved_app_model = imp - .saved_app_model - .get() - .expect("Failed to get saved app model."); - saved_app_factory.connect_bind(glib::clone!(@weak saved_app_model => move |_, list_item| { - let application_object = list_item - .item() - .expect("The item has to exist.") - .downcast::() - .expect("The item has to be a `DockObject`"); - let dock_item = list_item - .child() - .expect("The list item child needs to exist.") - .downcast::() - .expect("The list item type needs to be `DockItem`"); - - dock_item.set_app_info(&application_object); - })); - // Set the factory of the list view - imp.saved_app_list_view - .get() - .unwrap() - .set_factory(Some(&saved_app_factory)); - - let active_app_model = imp - .active_app_model - .get() - .expect("Failed to get saved app model."); - let active_factory = SignalListItemFactory::new(); - active_factory.connect_setup(move |_, list_item| { - let dock_item = DockItem::new(); - list_item.set_child(Some(&dock_item)); - }); - active_factory.connect_bind(glib::clone!(@weak active_app_model => move |_, list_item| { - let application_object = list_item - .item() - .expect("The item has to exist.") - .downcast::() - .expect("The item has to be a `DockObject`"); - let dock_item = list_item - .child() - .expect("The list item child needs to exist.") - .downcast::() - .expect("The list item type needs to be `DockItem`"); - - dock_item.set_app_info(&application_object); - })); - // Set the factory of the list view - imp.active_app_list_view - .get() - .unwrap() - .set_factory(Some(&active_factory)); - } - - fn restore_saved_apps(&self) { - if let Ok(file) = File::open(data_path()) { - if let Ok(saved_data) = serde_json::from_reader::<_, Vec>(file) { - // dbg!(&saved_data); - let dock_objects: Vec = saved_data - .into_iter() - .filter_map(|d| { - DockObject::from_app_info_path(&d) - .map(|dockobject| dockobject.upcast::()) - }) - .collect(); - // dbg!(&dock_objects); - let saved_app_model = self.saved_app_model(); - saved_app_model.splice(saved_app_model.n_items(), 0, &dock_objects); - return; - } - } - println!("Error loading saved apps!"); - let saved_app_model = &self.saved_app_model(); - xdg::BaseDirectories::new() - .expect("could not access XDG Base directory") - .get_data_dirs() - .iter_mut() - .for_each(|xdg_data_path| { - let defaults = ["Firefox Web Browser", "Files", "Terminal", "Pop!_Shop"]; - xdg_data_path.push("applications"); - // dbg!(&xdg_data_path); - if let Ok(dir_iter) = std::fs::read_dir(xdg_data_path) { - dir_iter.for_each(|dir_entry| { - if let Ok(dir_entry) = dir_entry { - if let Some(path) = dir_entry.path().file_name() { - if let Some(path) = path.to_str() { - if let Some(app_info) = gio::DesktopAppInfo::new(path) { - if app_info.should_show() - && defaults.contains(&app_info.name().as_str()) - { - saved_app_model.append(&DockObject::new(app_info)); - } else { - // println!("Ignoring {}", path); - } - } else { - // println!("error loading {}", path); - } - } - } - } - }) - } - }); - } - - fn store_saved_apps(saved_app_model: &gio::ListStore) { - // Store todo data in vector - let mut backup_data = Vec::new(); - let mut i = 0; - while let Some(item) = saved_app_model.item(i) { - // Get `AppGroup` from `glib::Object` - let dock_object = item - .downcast_ref::() - .expect("The object needs to be of type `AppGroupData`."); - // Add todo data to vector and increase position - if let Ok(Some(app_info)) = dock_object - .property("appinfo") - .expect("DockObject must have appinfo property") - .get::>() - { - if let Some(f) = app_info.filename() { - backup_data.push(f); - } - } - i += 1; - } - // dbg!(&backup_data); - // Save state in file - let file = File::create(data_path()).expect("Could not create json file."); - serde_json::to_writer_pretty(file, &backup_data) - .expect("Could not write data to json file"); - } } diff --git a/examples/launcher/main.rs b/examples/launcher/main.rs index b680f68e..47011b64 100644 --- a/examples/launcher/main.rs +++ b/examples/launcher/main.rs @@ -8,8 +8,7 @@ use gtk4::CssProvider; use gtk4::StyleContext; use once_cell::sync::OnceCell; use pop_launcher_service::IpcClient; -use postage::mpsc::Sender; -use postage::prelude::*; +use tokio::sync::mpsc; use x11rb::rust_connection::RustConnection; use crate::utils::BoxedSearchResult; @@ -23,7 +22,7 @@ mod utils; mod window; const NUM_LAUNCHER_ITEMS: u8 = 10; -static TX: OnceCell> = OnceCell::new(); +static TX: OnceCell> = OnceCell::new(); static X11_CONN: OnceCell = OnceCell::new(); pub enum Event { @@ -32,7 +31,7 @@ pub enum Event { Activate(u32), } -fn spawn_launcher(mut tx: Sender) -> IpcClient { +fn spawn_launcher(tx: mpsc::Sender) -> IpcClient { let (launcher, responses) = pop_launcher_service::IpcClient::new().expect("failed to connect to launcher service"); @@ -79,12 +78,13 @@ fn main() { load_css() }); app.connect_activate(move |app| { - let (tx, mut rx) = postage::mpsc::channel(1); + let (tx, mut rx) = mpsc::channel(100); let mut launcher = spawn_launcher(tx.clone()); if TX.set(tx).is_err() { println!("failed to set global Sender. Exiting"); std::process::exit(1); }; + let (conn, _screen_num) = x11rb::connect(None).expect("Failed to connect to X"); if X11_CONN.set(conn).is_err() { println!("failed to set X11_CONN. Exiting"); diff --git a/examples/launcher/search_result_object/mod.rs b/examples/launcher/search_result_object/mod.rs index 547fdc4f..f1457dbe 100644 --- a/examples/launcher/search_result_object/mod.rs +++ b/examples/launcher/search_result_object/mod.rs @@ -1,4 +1,4 @@ -use gdk4::glib::Object; +use gtk4::glib; use crate::utils::BoxedSearchResult; @@ -10,6 +10,6 @@ glib::wrapper! { impl SearchResultObject { pub fn new(search_result: &BoxedSearchResult) -> Self { - Object::new(&[("data", search_result)]).expect("Failed to create Application Object") + glib::Object::new(&[("data", search_result)]).expect("Failed to create Application Object") } } diff --git a/examples/launcher/search_result_row/mod.rs b/examples/launcher/search_result_row/mod.rs index 1e6cefa7..9a4d2efa 100644 --- a/examples/launcher/search_result_row/mod.rs +++ b/examples/launcher/search_result_row/mod.rs @@ -1,5 +1,5 @@ use cascade::cascade; -use glib; +use gtk4::glib; use gtk4::pango::EllipsizeMode; use gtk4::prelude::*; use gtk4::subclass::prelude::*; diff --git a/examples/launcher/utils.rs b/examples/launcher/utils.rs index 55a43959..159bf4a4 100644 --- a/examples/launcher/utils.rs +++ b/examples/launcher/utils.rs @@ -1,3 +1,4 @@ +use gtk4::glib; use std::cell::RefCell; use std::rc::Rc; diff --git a/examples/launcher/window/mod.rs b/examples/launcher/window/mod.rs index a3a9fd7b..7a04b265 100644 --- a/examples/launcher/window/mod.rs +++ b/examples/launcher/window/mod.rs @@ -11,7 +11,6 @@ use gtk4::ListView; use gtk4::Orientation; use gtk4::{gio, glib}; use gtk4::{Application, SignalListItemFactory}; -use postage::prelude::Sink; use x11rb::connection::Connection; use x11rb::protocol::xproto; use x11rb::protocol::xproto::ConnectionExt; @@ -126,7 +125,6 @@ impl Window { glib::MainContext::default().spawn_local(async move { if let Some(tx) = TX.get() { - let mut tx = tx.clone(); let _ = tx.send(crate::Event::Activate(id)).await; } }); @@ -153,7 +151,6 @@ impl Window { glib::MainContext::default().spawn_local(async move { if let Some(tx) = TX.get() { - let mut tx = tx.clone(); let _ = tx.send(crate::Event::Activate(id)).await; } }); @@ -165,7 +162,6 @@ impl Window { glib::MainContext::default().spawn_local(async move { if let Some(tx) = TX.get() { - let mut tx = tx.clone(); let _ = tx.send(crate::Event::Search(search)).await; } }); @@ -176,7 +172,6 @@ impl Window { glib::MainContext::default().spawn_local(async move { if let Some(tx) = TX.get() { - let mut tx = tx.clone(); let _ = tx.send(crate::Event::Search(search)).await; } });