show active apps

This commit is contained in:
Ashley Wulber 2021-12-21 13:01:32 -05:00
parent ad6f147546
commit 03300c788a
9 changed files with 113 additions and 339 deletions

View file

@ -1,213 +0,0 @@
use glib::{FromVariant, ParamFlags, ParamSpec, ToVariant, Value, Variant, VariantTy};
use gtk4::glib;
use gtk4::prelude::*;
use gtk4::subclass::prelude::*;
use once_cell::sync::Lazy;
use std::cell::RefCell;
use std::rc::Rc;
use super::ApplicationData;
// Object holding the state
#[derive(Default)]
pub struct ApplicationObject {
data: Rc<RefCell<ApplicationData>>,
}
// The central trait for subclassing a GObject
#[glib::object_subclass]
impl ObjectSubclass for ApplicationObject {
const NAME: &'static str = "ApplicationObject";
type Type = super::ApplicationObject;
type ParentType = glib::Object;
}
// Trait shared by all GObjects
impl ObjectImpl for ApplicationObject {
fn properties() -> &'static [ParamSpec] {
static PROPERTIES: Lazy<Vec<ParamSpec>> = 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",
// Nickname
"name",
// Short description
"Name of application in launcher search result",
// Default value
Some(""),
// The property can be read and written to
ParamFlags::READWRITE,
),
ParamSpec::new_string(
// Name
"description",
// Nickname
"description",
// Short description
"Description of application in launcher search result",
// Default value
Some(""),
// The property can be read and written to
ParamFlags::READWRITE,
),
ParamSpec::new_variant(
// Name
"icon",
// Nickname
"icon",
// Short description
"Icon of application in launcher search result",
VariantTy::new("(is)").expect("Oops invalid string for VariantTy tuple."),
// Default value
None,
// The property can be read and written to
ParamFlags::READWRITE,
),
ParamSpec::new_variant(
// Name
"categoryicon",
// Nickname
"categoryicon",
// Short description
"Category icon of application in launcher search result",
VariantTy::new("(is)").expect("Oops invalid string for VariantTy tuple."),
// Default value
None,
// The property can be read and written to
ParamFlags::READWRITE,
),
ParamSpec::new_variant(
// Name
"window",
// Nickname
"window",
// Short description
"Window of application in launcher search result",
// type (tuple of two uint32)
VariantTy::new("(uu)").expect("Oops invalid string for VariantTy tuple."),
// Default value
None,
// The property can be read and written to
ParamFlags::READWRITE,
),
]
});
PROPERTIES.as_ref()
}
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()
.expect("The value needs to be of type `String`.");
self.data.borrow_mut().0.name = name;
}
"description" => {
let description = value
.get()
.expect("The description needs to be of type `String`");
self.data.borrow_mut().0.description = description;
}
"icon" => {
let icon = <(i32, String)>::from_variant(
&value
.get::<Variant>()
.expect("The icon needs to be a Variant"),
)
.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 = <(i32, String)>::from_variant(
&value
.get::<Variant>()
.expect("The icon needs to be a Variant"),
)
.expect("The icon variant needs to be an Option<(i32, String)>");
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!()
}
_ => unimplemented!(),
}
}
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.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())
.to_variant()
.to_value()
}
Some(pop_launcher::IconSource::Mime(icon_name)) => {
(pop_launcher::IconSource::Mime as i32, icon_name.to_string())
.to_variant()
.to_value()
}
_ => None::<Variant>.to_value(),
},
"categoryicon" => match &self.data.borrow().0.category_icon {
Some(pop_launcher::IconSource::Name(icon_name)) => {
(pop_launcher::IconSource::Name as i32, icon_name.to_string())
.to_variant()
.to_value()
}
Some(pop_launcher::IconSource::Mime(icon_name)) => {
(pop_launcher::IconSource::Mime as i32, icon_name.to_string())
.to_variant()
.to_value()
}
_ => None::<Variant>.to_value(),
},
_ => unimplemented!(),
}
}
}

View file

@ -1,72 +0,0 @@
mod imp;
use gdk4::glib::Object;
use glib::ObjectExt;
use glib::ToVariant;
use gtk4::glib;
glib::wrapper! {
pub struct ApplicationObject(ObjectSubclass<imp::ApplicationObject>);
}
impl ApplicationObject {
pub fn new(application_search_result: &pop_launcher::SearchResult) -> Self {
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_
}
}
// Object holding the state
pub struct ApplicationData(pop_launcher::SearchResult);
impl Default for ApplicationData {
fn default() -> Self {
let default_application = pop_launcher::SearchResult {
id: 0,
name: String::default(),
description: String::default(),
icon: None,
category_icon: None,
window: None,
};
Self(default_application)
}
}

View file

@ -53,10 +53,15 @@ impl DockItem {
pub fn set_app_info(&self, app_info: &DockObject, i: u32, saved_app_model: &ListStore) {
if let Ok(app_info_value) = app_info.property("appinfo") {
if let Ok(Some(app_info)) = app_info_value.get::<Option<DesktopAppInfo>>() {
dbg!("setting app info");
println!("setting app info {}", &app_info.name());
let self_ = imp::DockItem::from_instance(self);
self_.image.set_tooltip_text(Some(&app_info.name()));
let icon = app_info.icon().unwrap_or(
Icon::for_string("image-missing").expect("Failed to set default icon"),
);
self_.image.set_from_gicon(&icon);
if let Some(drag_controller) = self_.drag_controller.get() {
if let Some(file) = app_info.filename() {
let provider =
@ -108,39 +113,9 @@ impl DockItem {
}),
);
}
let icon = app_info.icon().unwrap_or(
Icon::for_string("image-missing").expect("Failed to set default icon"),
);
self_.image.set_from_gicon(&icon);
}
} else {
println!("initializing dock item failed...");
}
}
// pub fn set_app_info(&self, app_obj: ApplicationObject) {
// let self_ = imp::DockItem::from_instance(self);
// if let Ok(name) = app_obj.property("name") {
// self_.image.set_tooltip_text(Some(
// &name
// .get::<String>()
// .expect("Property name needs to be a String."),
// ));
// }
// if let Ok(icon) = app_obj.property("icon") {
// if let Ok(icon) = icon.get::<Variant>() {
// 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);
// }
// }
// }
}

View file

@ -5,6 +5,7 @@ use gtk4::glib;
use gtk4::prelude::*;
use gtk4::subclass::prelude::*;
use once_cell::sync::Lazy;
use std::cell::Cell;
use std::cell::RefCell;
// Object holding the state
@ -12,6 +13,7 @@ use std::cell::RefCell;
pub struct DockObject {
appinfo: RefCell<Option<DesktopAppInfo>>,
active: RefCell<BoxedSearchResults>,
saved: Cell<bool>,
}
// The central trait for subclassing a GObject
@ -49,6 +51,13 @@ impl ObjectImpl for DockObject {
// The property can be read and written to
ParamFlags::READWRITE,
),
ParamSpec::new_boolean(
"saved",
"saved",
"Indicates whether app is saved to the dock",
false,
ParamFlags::READWRITE,
),
]
});
PROPERTIES.as_ref()
@ -66,6 +75,10 @@ impl ObjectImpl for DockObject {
let active = value.get().expect("Value needs to be BoxedSearchResults");
self.active.replace(active);
}
"saved" => {
self.saved
.replace(value.get().expect("Value needs to be a boolean"));
}
_ => unimplemented!(),
}
}
@ -73,6 +86,8 @@ impl ObjectImpl for DockObject {
fn property(&self, _obj: &Self::Type, _id: usize, pspec: &ParamSpec) -> Value {
match pspec.name() {
"appinfo" => self.appinfo.borrow().to_value(),
"active" => self.active.borrow().to_value(),
"saved" => self.saved.get().to_value(),
_ => unimplemented!(),
}
}

View file

@ -12,7 +12,8 @@ glib::wrapper! {
impl DockObject {
pub fn new(appinfo: DesktopAppInfo) -> Self {
Object::new(&[("appinfo", &Some(appinfo))]).expect("Failed to create `DockObject`.")
Object::new(&[("appinfo", &Some(appinfo)), ("saved", &true)])
.expect("Failed to create `DockObject`.")
}
pub fn from_search_results(results: BoxedSearchResults) -> Self {
@ -23,7 +24,6 @@ impl DockObject {
.iter_mut()
.filter_map(|xdg_data_path| {
xdg_data_path.push("applications");
dbg!(&xdg_data_path);
std::fs::read_dir(xdg_data_path).ok()
})
.flatten()
@ -35,7 +35,7 @@ impl DockObject {
if app_info.should_show()
&& first.description.as_str() == app_info.name().as_str()
{
return Some(DockObject::new(app_info));
return Some(app_info);
}
}
}
@ -47,6 +47,7 @@ impl DockObject {
} else {
None
};
dbg!(&appinfo);
Object::new(&[("appinfo", &appinfo), ("active", &results)])
.expect("Failed to create `DockObject`.")
}

View file

@ -1,9 +1,9 @@
mod application_object;
mod dock_item;
mod dock_object;
mod utils;
mod window;
use crate::utils::BoxedSearchResults;
use async_io::Timer;
use gdk4::Display;
use gio::DesktopAppInfo;
@ -18,9 +18,11 @@ use once_cell::sync::OnceCell;
use pop_launcher_service::IpcClient;
use postage::mpsc::Sender;
use postage::prelude::*;
use std::collections::HashMap;
use std::time::Duration;
use x11rb::rust_connection::RustConnection;
use self::dock_object::DockObject;
use self::window::Window;
const NUM_LAUNCHER_ITEMS: u8 = 10;
@ -48,10 +50,10 @@ fn spawn_launcher(tx: Sender<Event>) -> IpcClient {
let mut sender = tx.clone();
glib::MainContext::default().spawn_local(async move {
loop {
Timer::after(Duration::from_secs(1)).await;
let _ = sender.send(Event::Search(String::new())).await;
}
// loop {
let _ = sender.send(Event::Search(String::new())).await;
Timer::after(Duration::from_secs(1)).await;
// }
});
launcher
@ -120,8 +122,23 @@ fn main() {
let _ = launcher.send(pop_launcher::Request::Activate(index)).await;
}
Event::Response(event) => {
if let pop_launcher::Response::Update(_results) = event {
println!("updating active apps")
if let pop_launcher::Response::Update(results) = event {
println!("updating active apps");
let model = window.active_app_model();
let model_len = model.n_items();
let stack_active = results.iter().fold(HashMap::new(), |mut acc: HashMap<String, BoxedSearchResults>, elem| {
if let Some(v) = acc.get_mut(&elem.description) {
v.0.push(elem.clone());
} else {
acc.insert(elem.description.clone(), BoxedSearchResults(vec![elem.clone()]));
}
acc
});
let new_results: Vec<glib::Object> = stack_active
.into_values()
.map(|v| DockObject::from_search_results(v).upcast())
.collect();
model.splice(0, model_len, &new_results[..]);
}
else if let pop_launcher::Response::DesktopEntry {
path,

View file

@ -18,7 +18,7 @@ pub struct Window {
#[template_child]
pub saved_app_list_view: TemplateChild<ListView>,
#[template_child]
pub unsaved_open_app_list_view: TemplateChild<ListView>,
pub active_app_list_view: TemplateChild<ListView>,
#[template_child]
pub revealer: TemplateChild<Revealer>,
#[template_child]
@ -26,7 +26,7 @@ pub struct Window {
#[template_child]
pub cursor_leave_handle: TemplateChild<Box>,
pub saved_app_model: OnceCell<gio::ListStore>,
pub unsaved_open_app_model: OnceCell<gio::ListStore>,
pub active_app_model: OnceCell<gio::ListStore>,
pub enter_event_controller: OnceCell<EventControllerMotion>,
pub leave_event_controller: OnceCell<EventControllerMotion>,
pub drop_controller: OnceCell<DropTarget>,

View file

@ -46,13 +46,21 @@ impl Window {
.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 selection_model = gtk::SingleSelection::builder()
let saved_selection_model = gtk::SingleSelection::builder()
.autoselect(false)
.can_unselect(true)
.selected(gtk4::INVALID_LIST_POSITION)
@ -93,7 +101,23 @@ impl Window {
.set(saved_app_model)
.expect("Could not set model");
// Wrap model with selection and pass it to the list view
imp.saved_app_list_view.set_model(Some(&selection_model));
imp.saved_app_list_view
.set_model(Some(&saved_selection_model));
let active_app_model = gio::ListStore::new(DockObject::static_type());
let active_selection_model = gtk::SingleSelection::builder()
.autoselect(false)
.can_unselect(true)
.selected(gtk4::INVALID_LIST_POSITION)
.model(&active_app_model)
.build();
imp.active_app_model
.set(active_app_model)
.expect("Could not set model");
// Wrap model with selection and pass it to the list view
imp.active_app_list_view
.set_model(Some(&active_selection_model));
}
fn setup_callbacks(&self) {
@ -346,8 +370,8 @@ impl Window {
}
fn setup_factory(&self) {
let factory = SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
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));
});
@ -356,7 +380,7 @@ impl Window {
.saved_app_model
.get()
.expect("Failed to get saved app model.");
factory.connect_bind(glib::clone!(@weak saved_app_model => move |_, list_item| {
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.")
@ -372,6 +396,35 @@ impl Window {
dock_item.set_app_info(&application_object, i, &saved_app_model);
}));
// Set the factory of the list view
imp.saved_app_list_view.set_factory(Some(&factory));
imp.saved_app_list_view
.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::<DockObject>()
.expect("The item has to be a `DockObject`");
let dock_item = list_item
.child()
.expect("The list item child needs to exist.")
.downcast::<DockItem>()
.expect("The list item type needs to be `DockItem`");
let i = list_item.position();
println!("setting position {} of active app list.", i);
dock_item.set_app_info(&application_object, i, &active_app_model);
}));
// Set the factory of the list view
imp.active_app_list_view.set_factory(Some(&active_factory));
}
}

View file

@ -31,7 +31,6 @@
<child>
<object class="GtkListView" id="saved_app_list_view">
<property name="orientation">horizontal</property>
<property name="hexpand">true</property>
</object>
</child>
<child>
@ -44,9 +43,8 @@
</object>
</child>
<child>
<object class="GtkListView" id="unsaved_open_app_list_view">
<object class="GtkListView" id="active_app_list_view">
<property name="orientation">horizontal</property>
<property name="hexpand">true</property>
</object>
</child>
</object>