diff --git a/examples/dock/dock_item/mod.rs b/examples/dock/dock_item/mod.rs index cbaf965..c224331 100644 --- a/examples/dock/dock_item/mod.rs +++ b/examples/dock/dock_item/mod.rs @@ -55,7 +55,6 @@ impl DockItem { let self_ = imp::DockItem::from_instance(self); if let Ok(app_info_value) = app_info.property("appinfo") { if let Ok(Some(app_info)) = app_info_value.get::>() { - println!("setting app info {}", &app_info.name()); self_.image.set_tooltip_text(Some(&app_info.name())); let icon = app_info.icon().unwrap_or( diff --git a/examples/dock/dock_object/mod.rs b/examples/dock/dock_object/mod.rs index 749bf46..f6924bd 100644 --- a/examples/dock/dock_object/mod.rs +++ b/examples/dock/dock_object/mod.rs @@ -47,7 +47,7 @@ impl DockObject { } else { None }; - dbg!(&appinfo); + // dbg!(&appinfo); Object::new(&[("appinfo", &appinfo), ("active", &results)]) .expect("Failed to create `DockObject`.") } diff --git a/examples/dock/main.rs b/examples/dock/main.rs index b7eed01..856bb73 100644 --- a/examples/dock/main.rs +++ b/examples/dock/main.rs @@ -6,7 +6,7 @@ mod window; use crate::utils::BoxedWindowList; use async_io::Timer; -use futures::StreamExt; +use futures::executor::block_on; use gdk4::Display; use gio::DesktopAppInfo; use gtk::gio; @@ -17,7 +17,6 @@ use gtk4 as gtk; use gtk4::CssProvider; use gtk4::StyleContext; use once_cell::sync::OnceCell; -use pop_launcher_service::IpcClient; use postage::mpsc::Sender; use postage::prelude::*; use serde::{Deserialize, Serialize}; @@ -30,16 +29,17 @@ use zvariant_derive::Type; use self::dock_object::DockObject; use self::window::Window; +const DEST: &str = "com.System76.PopShell"; +const PATH: &str = "/com/System76/PopShell"; const NUM_LAUNCHER_ITEMS: u8 = 10; + static TX: OnceCell> = OnceCell::new(); static X11_CONN: OnceCell = OnceCell::new(); pub enum Event { - Response(pop_launcher::Response), WindowList(Vec), + Activate((u32, u32)), RefreshFromCache, - Search(String), - Activate(u32), } #[derive(Debug, Deserialize, Serialize, Type, Clone, PartialEq, Eq)] @@ -50,30 +50,15 @@ pub struct Item { desktop_entry: String, } -fn spawn_launcher(tx: Sender) -> IpcClient { - let (launcher, responses) = - pop_launcher_service::IpcClient::new().expect("failed to connect to launcher service"); - - let mut sender = tx.clone(); - glib::MainContext::default().spawn_local(async move { - futures::pin_mut!(responses); - while let Some(event) = responses.next().await { - let _ = sender.send(Event::Response(event)).await; - } - }); +fn spawn_launcher(tx: Sender) -> Connection { + let connection = block_on(Connection::session()).unwrap(); let mut sender = tx.clone(); + let conn = connection.clone(); glib::MainContext::default().spawn_local(async move { loop { - let connection = Connection::session().await.unwrap(); - let m = connection - .call_method( - Some("com.System76.PopShell"), - "/com/System76/PopShell", - Some("com.System76.PopShell"), - "WindowList", - &(), - ) + let m = conn + .call_method(Some(DEST), PATH, Some(DEST), "WindowList", &()) .await; if let Ok(m) = m { if let Ok(reply) = m.body::>() { @@ -84,7 +69,7 @@ fn spawn_launcher(tx: Sender) -> IpcClient { } }); - launcher + connection } fn setup_shortcuts(app: &Application) { @@ -126,7 +111,7 @@ fn main() { // 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 mut launcher = spawn_launcher(tx.clone()); + let zbus_conn = spawn_launcher(tx.clone()); if TX.set(tx).is_err() { println!("failed to set global Sender. Exiting"); std::process::exit(1); @@ -145,86 +130,96 @@ fn main() { futures::pin_mut!(cached_results); while let Some(event) = rx.recv().await { match event { - Event::Search(search) => { - let _ = launcher.send(pop_launcher::Request::Search(search)).await; - } - Event::Activate(index) => { - let _ = launcher.send(pop_launcher::Request::Activate(index)).await; + Event::Activate(e) => { + let _activate_window = zbus_conn + .call_method(Some(DEST), PATH, Some(DEST), "WindowFocus", &((e,))) + .await + .expect("Failed to focus selected window"); + dbg!(_activate_window); } Event::RefreshFromCache => { //TODO refresh the model from cached_results (required after DnD for example) } Event::WindowList(mut results) => { -// sort to make comparison with cache easier - let mut cached_results = cached_results.as_mut(); - results.sort_by(|a, b| a.name.cmp(&b.name)); + // sort to make comparison with cache easier + let mut cached_results = cached_results.as_mut(); + results.sort_by(|a, b| a.name.cmp(&b.name)); + // dbg!(&results); + // dbg!(&cached_results); + // // check if cache equals the new polled results + // skip if equal + if cached_results.len() == results.len() + && results.iter().zip(cached_results.iter()).fold( + 0, + |acc, z: (&Item, &Item)| { + let (a, b) = z; + if a.name == b.name { + acc + 1 + } else { + acc + } + }, + ) == cached_results.len() + { + continue; // skip this update + } - // dbg!(&results); - // dbg!(&cached_results); - // // check if cache equals the new polled results - // skip if equal - if cached_results.len() == results.len() && results.iter().zip(cached_results.iter()).fold(0, |acc, z: (&Item, &Item)| { - let (a, b) = z; - if a.name == b.name {acc + 1} else {acc} - }) == cached_results.len() { - continue // skip this update - } - - println!("updating active apps"); - // build active app stacks for each app - let stack_active = results.iter().fold(BTreeMap::new(), |mut acc: BTreeMap, elem| { + // build active app stacks for each app + let stack_active = results.iter().fold( + BTreeMap::new(), + |mut acc: BTreeMap, elem| { if let Some(v) = acc.get_mut(&elem.description) { v.0.push(elem.clone()); } else { - acc.insert(elem.description.clone(), BoxedWindowList(vec![elem.clone()])); + acc.insert( + elem.description.clone(), + BoxedWindowList(vec![elem.clone()]), + ); } acc - }); - let mut stack_active: Vec = stack_active.into_values().collect(); + }, + ); + let mut stack_active: Vec = + stack_active.into_values().collect(); - // 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(); + // 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 mut i: u32 = 0; - while let Some(item) = saved_app_model.item(i) { - if let Ok(dock_obj) = item.downcast::() { - if let Ok(Some(cur_app_info)) = dock_obj.property("appinfo").expect("property appinfo missing from DockObject").get::>() { - if let Some((i, s)) = stack_active.iter().enumerate().find(|(_i, s)| s.0[0].description == cur_app_info.name()) { - println!("found active saved app {} at {}", s.0[0].name, i); - let active = stack_active.remove(i); - dock_obj.set_property("active", active.to_value()).expect("failed to update dock active apps"); - saved_app_model.items_changed(i.try_into().unwrap(), 0, 0); - } + let mut i: u32 = 0; + while let Some(item) = saved_app_model.item(i) { + if let Ok(dock_obj) = item.downcast::() { + if let Ok(Some(cur_app_info)) = dock_obj + .property("appinfo") + .expect("property appinfo missing from DockObject") + .get::>() + { + if let Some((i, _s)) = stack_active + .iter() + .enumerate() + .find(|(_i, s)| s.0[0].description == cur_app_info.name()) + { + // println!("found active saved app {} at {}", s.0[0].name, i); + let active = stack_active.remove(i); + dock_obj + .set_property("active", active.to_value()) + .expect("failed to update dock active apps"); + saved_app_model.items_changed(i.try_into().unwrap(), 0, 0); } - } - i += 1; } - - let active_app_model = window.active_app_model(); - let model_len = active_app_model.n_items(); - let new_results: Vec = stack_active - .into_iter() - .map(|v| DockObject::from_search_results(v).upcast()) - .collect(); - active_app_model.splice(0, model_len, &new_results[..]); - cached_results.splice(.., results); - } - Event::Response(event) => { - // TODO investigate why polled results are out of date after launching a new window - if let pop_launcher::Response::DesktopEntry { - path, - gpu_preference: _gpu_preference, // TODO use GPU preference when launching app - } = event - { - let app_info = - DesktopAppInfo::new(&path.file_name().expect("desktop entry path needs to be a valid filename").to_string_lossy()) - .expect("failed to create a Desktop App info for launching the application."); - app_info - .launch(&[], Some(&window.display().app_launch_context())).expect("failed to launch the application."); + i += 1; } + + let active_app_model = window.active_app_model(); + let model_len = active_app_model.n_items(); + let new_results: Vec = stack_active + .into_iter() + .map(|v| DockObject::from_search_results(v).upcast()) + .collect(); + active_app_model.splice(0, model_len, &new_results[..]); + cached_results.splice(.., results); } } } diff --git a/examples/dock/window/mod.rs b/examples/dock/window/mod.rs index 71b820e..9d59d83 100644 --- a/examples/dock/window/mod.rs +++ b/examples/dock/window/mod.rs @@ -2,6 +2,9 @@ mod imp; // use crate::ApplicationObject; use crate::dock_item::DockItem; use crate::dock_object::DockObject; +use crate::BoxedWindowList; +use crate::Event; +use crate::TX; use crate::X11_CONN; use gdk4::Rectangle; use gdk4::Surface; @@ -12,6 +15,7 @@ use gtk4 as gtk; use gtk4::prelude::ListModelExt; use gtk4::DropTarget; use gtk4::EventControllerMotion; +use postage::prelude::Sink; use std::path::Path; use x11rb::connection::Connection; use x11rb::protocol::xproto::ConnectionExt; @@ -73,7 +77,7 @@ impl Window { .for_each(|xdg_data_path| { let defaults = ["Firefox Web Browser", "Files", "Terminal", "Pop!_Shop"]; xdg_data_path.push("applications"); - dbg!(&xdg_data_path); + // 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 { @@ -135,46 +139,32 @@ impl Window { .expect("List view missing selection model") .downcast::() .expect("could not downcast listview model to single selection model"); - - saved_app_selection_model.connect_selected_notify( - glib::clone!(@weak window => move |model| { - let position = model.selected(); - println!("selected app {}", position); - // Launch the application when an item of the list is activated - if let Some(item) = model.item(position) { - let app_info = item.downcast::().expect("App model must only contain DockObject"); - if let Ok(Some(app_info)) = app_info.property("appinfo").expect("DockObject must have appinfo property").get::>() { - let context = window.display().app_launch_context(); - if let Err(err) = app_info.launch(&[], Some(&context)) { - 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(&window) - .build() - .show(); - } - } - } - model.set_selected(gtk4::INVALID_LIST_POSITION); - }), - ); - - let active_app_selection_model = saved_app_list_view + let active_app_selection_model = imp + .active_app_list_view .model() .expect("List view missing selection model") .downcast::() .expect("could not downcast listview model to single selection model"); - active_app_selection_model.connect_selected_notify( - glib::clone!(@weak window => move |model| { - let position = model.selected(); - println!("selected app {}", position); - // Launch the application when an item of the list is activated - if let Some(item) = model.item(position) { - let app_info = item.downcast::().expect("App model must only contain DockObject"); - if let Ok(Some(app_info)) = app_info.property("appinfo").expect("DockObject must have appinfo property").get::>() { + let selected_handler = glib::clone!(@weak window => move |model: >k::SingleSelection| { + let position = model.selected(); + println!("selected app {}", position); + // Launch the application when an item of the list is activated + if let Some(item) = model.item(position) { + let dockobject = item.downcast::().expect("App model must only contain DockObject"); + if let Ok(active) = dockobject.property("active").expect("DockObject must have active property").get::() { + dbg!(&active); + if let Some(focused_item) = active.0.iter().next() { + let entity = focused_item.entity.clone(); + dbg!(&entity); + 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; + } + }); + } + else if let Ok(Some(app_info)) = dockobject.property("appinfo").expect("DockObject must have appinfo property").get::>() { let context = window.display().app_launch_context(); if let Err(err) = app_info.launch(&[], Some(&context)) { gtk::MessageDialog::builder() @@ -188,9 +178,12 @@ impl Window { } } } - model.set_selected(gtk4::INVALID_LIST_POSITION); - }), - ); + } + model.set_selected(gtk4::INVALID_LIST_POSITION); + }); + saved_app_selection_model.connect_selected_notify(selected_handler.clone()); + + active_app_selection_model.connect_selected_notify(selected_handler); let cursor_event_controller = &imp.cursor_event_controller.get().unwrap(); let drop_controller = &imp.drop_controller.get().unwrap(); @@ -198,7 +191,7 @@ impl Window { let revealer = &imp.revealer.get(); window.connect_show( glib::clone!(@weak revealer, @weak cursor_event_controller => move |_| { - dbg!(!cursor_event_controller.contains_pointer()); + // dbg!(!cursor_event_controller.contains_pointer()); if !cursor_event_controller.contains_pointer() { revealer.set_reveal_child(false); } @@ -218,7 +211,7 @@ impl Window { //TODO clean up duplicated code cursor_event_controller.connect_enter(glib::clone!(@weak revealer, @weak window => move |_evc, _x, _y| { - dbg!("hello, mouse entered me :)"); + // dbg!("hello, mouse entered me :)"); revealer.set_reveal_child(true); let s = window.surface().expect("Failed to get Surface for Window"); let height = s.height() * s.scale_factor(); @@ -262,7 +255,7 @@ impl Window { revealer.connect_child_revealed_notify(glib::clone!(@weak window => move |r| { if !r.is_child_revealed() { let s = window.surface().expect("Failed to get Surface for Window"); - dbg!(r.is_child_revealed()); + // dbg!(r.is_child_revealed()); let height = 4; if let Some((display, _surface)) = x::get_window_x11(&window) { let monitor = display @@ -354,7 +347,7 @@ impl Window { 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); } }), @@ -376,7 +369,7 @@ impl Window { drop_controller.connect_drop( glib::clone!(@weak saved_app_model, @weak saved_app_list_view => @default-return true, move |_self, drop_value, x, y| { if let Ok(Some(path_str)) = drop_value.get::>() { - dbg!(&path_str); + // dbg!(&path_str); 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()) { @@ -385,7 +378,7 @@ impl Window { 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()); + // dbg!(cur_app_info.filename()); if cur_app_info.filename() == Some(Path::new(&path_str).to_path_buf()) { index_of_existing_app = Some(i); } @@ -393,20 +386,20 @@ impl Window { } i += 1; } - dbg!(app_info.name()); - dbg!(index_of_existing_app); + // 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); } //calculate insertion location - dbg!(x); - dbg!(y); + // dbg!(x); + // dbg!(y); let max_y = saved_app_list_view.allocated_height(); let max_x = saved_app_list_view.allocated_width(); - dbg!(max_x); - dbg!(max_y); + // 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; @@ -417,15 +410,15 @@ impl Window { } else { (drop_bucket + 1) / 2 }; - dbg!(index); - dbg!("dropped it!"); - dbg!(drop_value.type_()); + // dbg!(index); + // dbg!("dropped it!"); + // dbg!(drop_value.type_()); saved_app_model.insert(index, &DockObject::new(app_info)); } } } else { - dbg!("rejecting drop"); + // dbg!("rejecting drop"); _self.reject(); } true diff --git a/examples/dock/window/window.ui b/examples/dock/window/window.ui index 14532c3..3dcc713 100644 --- a/examples/dock/window/window.ui +++ b/examples/dock/window/window.ui @@ -20,7 +20,7 @@ true 300 - cross-fade + slide-up 0