// SPDX-License-Identifier: MPL-2.0-only use apps_window::CosmicAppListWindow; use calloop::channel::SyncSender; use dock_list::DockListType; use dock_object::DockObject; use gio::{ApplicationFlags, DesktopAppInfo}; use gtk4::gdk::Display; use gtk4::{glib, prelude::*, CssProvider, StyleContext}; use once_cell::sync::OnceCell; use wayland::{ToplevelEvent, Toplevel}; use std::collections::BTreeMap; use std::sync::{Arc, Mutex}; use std::time::Duration; use tokio::sync::mpsc; use utils::{block_on, BoxedWindowList, AppListEvent, DEST, PATH}; mod apps_container; mod apps_window; mod dock_item; mod dock_list; mod dock_object; mod dock_popover; mod localize; mod utils; mod wayland; mod wayland_source; mod config; const ID: &str = "com.system76.CosmicAppList"; static TX: OnceCell> = OnceCell::new(); static WAYLAND_TX: OnceCell> = OnceCell::new(); pub fn localize() { let localizer = crate::localize::localizer(); let requested_languages = i18n_embed::DesktopLanguageRequester::requested_languages(); if let Err(error) = localizer.select(&requested_languages) { eprintln!("Error while loading language for App List {}", error); } } fn load_css() { let provider = CssProvider::new(); provider.load_from_data(include_bytes!("style.css")); StyleContext::add_provider_for_display( &Display::default().unwrap(), &provider, gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, ); } fn main() { // Initialize logger pretty_env_logger::init(); glib::set_application_name("Cosmic Dock App List"); localize(); gio::resources_register_include!("compiled.gresource").unwrap(); let app = gtk4::Application::new(Some(ID), ApplicationFlags::default()); app.connect_activate(|app| { load_css(); let (tx, rx) = glib::MainContext::channel(glib::Priority::default()); let window = CosmicAppListWindow::new(app); let apps_container = apps_container::AppsContainer::new(); let wayland_tx = wayland::spawn_toplevels(); WAYLAND_TX.set(wayland_tx).unwrap(); let mut cached_results = Vec::new(); // let zbus_conn = spawn_zbus(tx.clone(), Arc::clone(&cached_results)); TX.set(tx.clone()).unwrap(); rx.attach(None, glib::clone!(@weak window => @default-return glib::prelude::Continue(true), move |event| { match event { AppListEvent::Activate(_) => { // let _activate_window = zbus_conn // .call_method(Some(DEST), PATH, Some(DEST), "WindowFocus", &((e,))) // .await // .expect("Failed to focus selected window"); } AppListEvent::Close(_) => { // let _activate_window = zbus_conn // .call_method(Some(DEST), PATH, Some(DEST), "WindowQuit", &((e,))) // .await // .expect("Failed to close selected window"); } AppListEvent::Favorite((name, should_favorite)) => { let saved_app_model = apps_container.model(DockListType::Saved); let active_app_model = apps_container.model(DockListType::Active); if should_favorite { let mut cur: u32 = 0; let mut index: Option = None; while let Some(item) = active_app_model.item(cur) { if let Ok(cur_dock_object) = item.downcast::() { if cur_dock_object.get_path() == Some(name.clone()) { cur_dock_object.set_saved(true); index = Some(cur); } } cur += 1; } if let Some(index) = index { let object = active_app_model.item(index).unwrap(); active_app_model.remove(index); saved_app_model.append(&object); } } else { let mut cur: u32 = 0; let mut index: Option = None; while let Some(item) = saved_app_model.item(cur) { if let Ok(cur_dock_object) = item.downcast::() { if cur_dock_object.get_path() == Some(name.clone()) { cur_dock_object.set_saved(false); index = Some(cur); } } cur += 1; } if let Some(index) = index { let object = saved_app_model.item(index).unwrap(); saved_app_model.remove(index); active_app_model.append(&object); } } let _ = tx.send(AppListEvent::Refresh); } AppListEvent::Refresh => { // println!("refreshing model from cache"); let stack_active = cached_results.iter().fold( BTreeMap::new(), |mut acc: BTreeMap, elem: &Toplevel| { if let Some(v) = acc.get_mut(&elem.name) { v.0.push(elem.clone()); } else { acc.insert( elem.name.clone(), BoxedWindowList(vec![elem.clone()]), ); } acc }, ); 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 = apps_container.model(DockListType::Saved); let mut saved_i: u32 = 0; while let Some(item) = saved_app_model.item(saved_i) { if let Ok(dock_obj) = item.downcast::() { if let Some(cur_app_info) = dock_obj.property::>("appinfo") { if let Some((i, _s)) = stack_active .iter() .enumerate() .find(|(_i, s)| s.0[0].name == 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()); saved_app_model.items_changed(saved_i, 0, 0); } else if cached_results .iter() .any(|s| s.name == cur_app_info.name()) { dock_obj.set_property( "active", BoxedWindowList(Vec::new()).to_value(), ); saved_app_model.items_changed(saved_i, 0, 0); } } } saved_i += 1; } let active_app_model = apps_container.model(DockListType::Active); 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[..]); } AppListEvent::WindowList(results) => { // sort to make comparison with cache easier cached_results = results.clone(); // 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.name) { v.0.push(elem.clone()); } else { acc.insert( elem.name.clone(), BoxedWindowList(vec![elem.clone()]), ); } acc }, ); 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 = apps_container.model(DockListType::Saved); let mut saved_i: u32 = 0; while let Some(item) = saved_app_model.item(saved_i) { if let Ok(dock_obj) = item.downcast::() { if let Some(cur_app_info) = dock_obj.property::>("appinfo") { if let Some((i, _s)) = stack_active .iter() .enumerate() .find(|(_i, s)| s.0[0].name == 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()); saved_app_model.items_changed(saved_i, 0, 0); } else if results .iter() .any(|s| s.name == cur_app_info.name()) { dock_obj.set_property( "active", BoxedWindowList(Vec::new()).to_value(), ); saved_app_model.items_changed(saved_i, 0, 0); } } } saved_i += 1; } let active_app_model = apps_container.model(DockListType::Active); 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[..]); } } glib::prelude::Continue(true) })); window.show(); }); app.run(); }