basic plugin!
This commit is contained in:
parent
7a8e3fe492
commit
537539f43d
13 changed files with 332 additions and 224 deletions
|
|
@ -16,6 +16,8 @@ wayland-protocols = { version = "0.28", features = [ "client", "unstable_protoco
|
||||||
x11 = { version = "2", features = ["xlib"] }
|
x11 = { version = "2", features = ["xlib"] }
|
||||||
|
|
||||||
# examples
|
# examples
|
||||||
|
gtk4 = { version ="0.3.1", features = ["v4_4"] }
|
||||||
|
glib-sys = "0.14.0"
|
||||||
pop-launcher-service = { git = "https://github.com/wash2/launcher.git" }
|
pop-launcher-service = { git = "https://github.com/wash2/launcher.git" }
|
||||||
pop-launcher = { git = "https://github.com/wash2/launcher.git" }
|
pop-launcher = { git = "https://github.com/wash2/launcher.git" }
|
||||||
serde = "1.0.130"
|
serde = "1.0.130"
|
||||||
|
|
@ -30,10 +32,8 @@ x11rb = "0.9.0"
|
||||||
zbus = "2.0.0"
|
zbus = "2.0.0"
|
||||||
zvariant = "3.0.0"
|
zvariant = "3.0.0"
|
||||||
zvariant_derive = "3.0.0"
|
zvariant_derive = "3.0.0"
|
||||||
|
libloading = "0.7.2"
|
||||||
[dependencies.gtk4]
|
gtk4-sys = "0.3.1"
|
||||||
version = "0.3"
|
|
||||||
features = ["v4_4"]
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
incremental = true
|
incremental = true
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use crate::dock_popover::DockPopover;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct DockItem {
|
pub struct DockItem {
|
||||||
pub image: Rc<RefCell<gtk4::Image>>,
|
pub image: Rc<RefCell<Option<gtk4::Image>>>,
|
||||||
pub dots: Rc<RefCell<gtk4::Label>>,
|
pub dots: Rc<RefCell<gtk4::Label>>,
|
||||||
pub item_box: Rc<RefCell<gtk4::Box>>,
|
pub item_box: Rc<RefCell<gtk4::Box>>,
|
||||||
pub popover: Rc<RefCell<gtk4::Popover>>,
|
pub popover: Rc<RefCell<gtk4::Popover>>,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use cascade::cascade;
|
use cascade::cascade;
|
||||||
use gio::DesktopAppInfo;
|
use gio::DesktopAppInfo;
|
||||||
use gio::Icon;
|
|
||||||
use gtk4::glib;
|
use gtk4::glib;
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4::subclass::prelude::*;
|
use gtk4::subclass::prelude::*;
|
||||||
|
|
@ -13,6 +12,7 @@ use gtk4::Popover;
|
||||||
|
|
||||||
use crate::dock_object::DockObject;
|
use crate::dock_object::DockObject;
|
||||||
use crate::dock_popover::DockPopover;
|
use crate::dock_popover::DockPopover;
|
||||||
|
use crate::plugin;
|
||||||
use crate::utils::BoxedWindowList;
|
use crate::utils::BoxedWindowList;
|
||||||
|
|
||||||
mod imp;
|
mod imp;
|
||||||
|
|
@ -82,7 +82,7 @@ impl DockItem {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let imp = imp::DockItem::from_instance(&self_);
|
let imp = imp::DockItem::from_instance(&self_);
|
||||||
imp.image.replace(image);
|
imp.image.replace(Some(image));
|
||||||
imp.dots.replace(dots);
|
imp.dots.replace(dots);
|
||||||
imp.item_box.replace(item_box);
|
imp.item_box.replace(item_box);
|
||||||
imp.popover.replace(popover);
|
imp.popover.replace(popover);
|
||||||
|
|
@ -92,24 +92,21 @@ impl DockItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
// refactor to emit event for removing the item?
|
// refactor to emit event for removing the item?
|
||||||
pub fn set_app_info(&self, dock_object: &DockObject) {
|
pub fn set_dock_object(&self, dock_object: &DockObject) {
|
||||||
let self_ = imp::DockItem::from_instance(self);
|
let self_ = imp::DockItem::from_instance(self);
|
||||||
if let Ok(app_info_value) = dock_object.property("appinfo") {
|
let image = cascade! {
|
||||||
if let Ok(Some(app_info)) = app_info_value.get::<Option<DesktopAppInfo>>() {
|
dock_object.get_image();
|
||||||
self_
|
..set_hexpand(true);
|
||||||
.image
|
..set_halign(Align::Center);
|
||||||
.borrow()
|
..set_pixel_size(64);
|
||||||
.set_tooltip_text(Some(&app_info.name()));
|
..set_tooltip_text(dock_object.get_name().as_deref());
|
||||||
|
};
|
||||||
let icon = app_info.icon().unwrap_or(
|
let old_image = self_.image.replace(None);
|
||||||
Icon::for_string("image-missing").expect("Failed to set default icon"),
|
if let Some(old_image) = old_image {
|
||||||
);
|
self_.item_box.borrow().remove(&old_image);
|
||||||
|
|
||||||
self_.image.borrow().set_from_gicon(&icon);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("initializing dock item failed...");
|
|
||||||
}
|
}
|
||||||
|
self_.item_box.borrow().prepend(&image);
|
||||||
|
let old_image = self_.image.replace(Some(image));
|
||||||
if let Ok(active_value) = dock_object.property("active") {
|
if let Ok(active_value) = dock_object.property("active") {
|
||||||
if let Ok(active) = active_value.get::<BoxedWindowList>() {
|
if let Ok(active) = active_value.get::<BoxedWindowList>() {
|
||||||
let dots = self_.dots.borrow();
|
let dots = self_.dots.borrow();
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,12 @@
|
||||||
|
use crate::dock_item::DockItem;
|
||||||
|
use crate::dock_object::DockObject;
|
||||||
|
use crate::plugin;
|
||||||
|
use crate::utils::data_path;
|
||||||
|
use crate::BoxedWindowList;
|
||||||
|
use crate::Event;
|
||||||
|
use crate::Item;
|
||||||
|
use crate::PLUGINS;
|
||||||
|
use crate::TX;
|
||||||
use cascade::cascade;
|
use cascade::cascade;
|
||||||
use gdk4::ContentProvider;
|
use gdk4::ContentProvider;
|
||||||
use gdk4::Display;
|
use gdk4::Display;
|
||||||
|
|
@ -17,17 +26,12 @@ use gtk4::SignalListItemFactory;
|
||||||
use gtk4::Window;
|
use gtk4::Window;
|
||||||
use gtk4::{gio, glib};
|
use gtk4::{gio, glib};
|
||||||
use gtk4::{DragSource, GestureClick};
|
use gtk4::{DragSource, GestureClick};
|
||||||
|
use std::ffi::CStr;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
use std::io::Read;
|
||||||
use std::path::Path;
|
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;
|
mod imp;
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
|
@ -97,41 +101,122 @@ impl DockList {
|
||||||
|
|
||||||
let model = self.model();
|
let model = self.model();
|
||||||
model.splice(model.n_items(), 0, &dock_objects);
|
model.splice(model.n_items(), 0, &dock_objects);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
eprintln!("Error loading saved apps!");
|
eprintln!("Error loading saved apps!");
|
||||||
let model = &self.model();
|
let model = &self.model();
|
||||||
xdg::BaseDirectories::new()
|
xdg::BaseDirectories::new()
|
||||||
.expect("could not access XDG Base directory")
|
.expect("could not access XDG Base directory")
|
||||||
.get_data_dirs()
|
.get_data_dirs()
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|xdg_data_path| {
|
.for_each(|xdg_data_path| {
|
||||||
let defaults = ["Firefox Web Browser", "Files", "Terminal", "Pop!_Shop"];
|
let defaults = ["Firefox Web Browser", "Files", "Terminal", "Pop!_Shop"];
|
||||||
xdg_data_path.push("applications");
|
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) {
|
if let Ok(dir_iter) = std::fs::read_dir(xdg_data_path) {
|
||||||
dir_iter.for_each(|dir_entry| {
|
dir_iter.for_each(|dir_entry| {
|
||||||
if let Ok(dir_entry) = dir_entry {
|
if let Ok(dir_entry) = dir_entry {
|
||||||
if let Some(path) = dir_entry.path().file_name() {
|
if let Some(path) = dir_entry.path().file_name() {
|
||||||
if let Some(path) = path.to_str() {
|
if let Some(path) = path.to_str() {
|
||||||
if let Some(app_info) = gio::DesktopAppInfo::new(path) {
|
if let Some(app_info) = gio::DesktopAppInfo::new(path) {
|
||||||
if app_info.should_show()
|
if app_info.should_show()
|
||||||
&& defaults.contains(&app_info.name().as_str())
|
&& defaults.contains(&app_info.name().as_str())
|
||||||
{
|
{
|
||||||
model.append(&DockObject::new(app_info));
|
model.append(&DockObject::new(app_info));
|
||||||
|
} else {
|
||||||
|
// println!("Ignoring {}", path);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// println!("Ignoring {}", path);
|
// println!("error loading {}", path);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// println!("error loading {}", path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
// TODO load saved plugins here... for now, load the hardcoded example.
|
||||||
|
// TODO unload plugin library before the dynamic library is changed, otherwise, it will crash after segfault
|
||||||
|
// TODO unload plugin on removal from model
|
||||||
|
// TODO dnd for plugin? I think they should either be at the start or end of the dock and not draggable
|
||||||
|
// TODO call plugin click handler on click or if it is not provided by the library, open the popover menu instead
|
||||||
|
let mut path_dir = glib::user_data_dir();
|
||||||
|
path_dir.push(crate::ID);
|
||||||
|
std::fs::create_dir_all(&path_dir).expect("Could not create directory.");
|
||||||
|
path_dir.push("plugins");
|
||||||
|
std::fs::create_dir_all(&path_dir).expect("Could not create directory.");
|
||||||
|
let mut path = path_dir.clone();
|
||||||
|
path.push("dock_plugin_uwu.so");
|
||||||
|
let mut path_css = path_dir.clone();
|
||||||
|
path_css.push("dock_plugin_uwu.css");
|
||||||
|
let path = path
|
||||||
|
.as_os_str()
|
||||||
|
.to_str()
|
||||||
|
.expect("plugin path needs to be a valid string");
|
||||||
|
let provider = gtk4::CssProvider::new();
|
||||||
|
if let Ok(f) = File::open(path_css) {
|
||||||
|
let mut reader = BufReader::new(f);
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
if reader.read_to_end(&mut buffer).is_ok() {
|
||||||
|
provider.load_from_data(&buffer);
|
||||||
|
// Add the provider to the default screen
|
||||||
|
gtk4::StyleContext::add_provider_for_display(
|
||||||
|
&gdk4::Display::default().expect("Error initializing GTK CSS provider."),
|
||||||
|
&provider,
|
||||||
|
gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
eprintln!("loading plugin css failed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("loading plugin css failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
let (popover_menu, image, name, lib) = unsafe {
|
||||||
|
let lib = libloading::Library::new(path).unwrap();
|
||||||
|
// store library until unloading the plugin
|
||||||
|
let image_func: libloading::Symbol<unsafe extern "C" fn() -> *mut gtk4_sys::GtkWidget> =
|
||||||
|
lib.get(b"dock_plugin_image").unwrap();
|
||||||
|
let popover_func: libloading::Symbol<
|
||||||
|
unsafe extern "C" fn() -> *mut gtk4_sys::GtkWidget,
|
||||||
|
> = lib.get(b"dock_plugin_popover_menu").unwrap();
|
||||||
|
let name_func: libloading::Symbol<
|
||||||
|
unsafe extern "C" fn() -> *const std::os::raw::c_char,
|
||||||
|
> = lib.get(b"dock_plugin_name").unwrap();
|
||||||
|
// click handler is optional
|
||||||
|
|
||||||
|
(popover_func(), image_func(), name_func(), lib)
|
||||||
|
};
|
||||||
|
if let Ok(ref mut mutex) = PLUGINS.try_lock() {
|
||||||
|
mutex.insert(String::from(path), lib);
|
||||||
|
}
|
||||||
|
let name = if !name.is_null() {
|
||||||
|
unsafe { String::from(CStr::from_ptr(name).to_str().unwrap_or_default()) }
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
let image = if !image.is_null() {
|
||||||
|
unsafe { gtk4::glib::translate::from_glib_none::<_, gtk4::Widget>(image).unsafe_cast() }
|
||||||
|
} else {
|
||||||
|
gtk4::Image::new()
|
||||||
|
};
|
||||||
|
let popover_menu = if !popover_menu.is_null() {
|
||||||
|
unsafe {
|
||||||
|
gtk4::glib::translate::from_glib_none::<_, gtk4::Widget>(popover_menu).unsafe_cast()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gtk4::Box::new(Orientation::Vertical, 4)
|
||||||
|
};
|
||||||
|
let boxed_plugin = plugin::BoxedDockPlugin {
|
||||||
|
path: String::from(path),
|
||||||
|
name,
|
||||||
|
image,
|
||||||
|
popover_menu,
|
||||||
|
};
|
||||||
|
let model = self.model();
|
||||||
|
model.append(&DockObject::from_plugin(boxed_plugin).upcast::<Object>());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn store_data(model: &gio::ListStore) {
|
fn store_data(model: &gio::ListStore) {
|
||||||
|
|
@ -160,6 +245,7 @@ impl DockList {
|
||||||
let file = File::create(data_path()).expect("Could not create json file.");
|
let file = File::create(data_path()).expect("Could not create json file.");
|
||||||
serde_json::to_writer_pretty(file, &backup_data)
|
serde_json::to_writer_pretty(file, &backup_data)
|
||||||
.expect("Could not write data to json file");
|
.expect("Could not write data to json file");
|
||||||
|
// TODO save plugins here for now examples are hardcoded and don't need to be saved
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&self) {
|
fn layout(&self) {
|
||||||
|
|
@ -420,7 +506,6 @@ impl DockList {
|
||||||
if type_ == DockListType::Saved {
|
if type_ == DockListType::Saved {
|
||||||
if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end(
|
if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end(
|
||||||
glib::clone!(@weak model => move |_self, _drag, _delete_data| {
|
glib::clone!(@weak model => move |_self, _drag, _delete_data| {
|
||||||
dbg!(_delete_data);
|
|
||||||
if _delete_data {
|
if _delete_data {
|
||||||
model.remove(index);
|
model.remove(index);
|
||||||
glib::MainContext::default().spawn_local(async move {
|
glib::MainContext::default().spawn_local(async move {
|
||||||
|
|
@ -526,7 +611,7 @@ impl DockList {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
factory.connect_bind(move |_, list_item| {
|
factory.connect_bind(move |_, list_item| {
|
||||||
let application_object = list_item
|
let dock_object = list_item
|
||||||
.item()
|
.item()
|
||||||
.expect("The item has to exist.")
|
.expect("The item has to exist.")
|
||||||
.downcast::<DockObject>()
|
.downcast::<DockObject>()
|
||||||
|
|
@ -536,7 +621,7 @@ impl DockList {
|
||||||
.expect("The list item child needs to exist.")
|
.expect("The list item child needs to exist.")
|
||||||
.downcast::<DockItem>()
|
.downcast::<DockItem>()
|
||||||
.expect("The list item type needs to be `DockItem`");
|
.expect("The list item type needs to be `DockItem`");
|
||||||
dock_item.set_app_info(&application_object);
|
dock_item.set_dock_object(&dock_object);
|
||||||
});
|
});
|
||||||
// Set the factory of the list view
|
// Set the factory of the list view
|
||||||
imp.list_view.get().unwrap().set_factory(Some(&factory));
|
imp.list_view.get().unwrap().set_factory(Some(&factory));
|
||||||
|
|
|
||||||
1
examples/dock/dock_object/.#mod.rs
Symbolic link
1
examples/dock/dock_object/.#mod.rs
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
ashleywulber@pop-os.190471:1641501268
|
||||||
|
|
@ -8,6 +8,7 @@ use gtk4::prelude::*;
|
||||||
use gtk4::subclass::prelude::*;
|
use gtk4::subclass::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use crate::plugin::BoxedDockPlugin;
|
||||||
use crate::utils::BoxedWindowList;
|
use crate::utils::BoxedWindowList;
|
||||||
|
|
||||||
// Object holding the state
|
// Object holding the state
|
||||||
|
|
@ -15,6 +16,7 @@ use crate::utils::BoxedWindowList;
|
||||||
pub struct DockObject {
|
pub struct DockObject {
|
||||||
pub(super) appinfo: RefCell<Option<DesktopAppInfo>>,
|
pub(super) appinfo: RefCell<Option<DesktopAppInfo>>,
|
||||||
pub(super) active: RefCell<BoxedWindowList>,
|
pub(super) active: RefCell<BoxedWindowList>,
|
||||||
|
pub(super) plugin: RefCell<Option<BoxedDockPlugin>>,
|
||||||
pub(super) saved: Cell<bool>,
|
pub(super) saved: Cell<bool>,
|
||||||
pub(super) popover: Cell<bool>,
|
pub(super) popover: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +95,6 @@ impl ObjectImpl for DockObject {
|
||||||
self.popover
|
self.popover
|
||||||
.replace(value.get().expect("Value needs to be a boolean"));
|
.replace(value.get().expect("Value needs to be a boolean"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use gdk4::glib::Object;
|
use crate::plugin::{self, BoxedDockPlugin};
|
||||||
use gdk4::prelude::FileExt;
|
|
||||||
use gdk4::subclass::prelude::ObjectSubclassExt;
|
|
||||||
use gio::DesktopAppInfo;
|
|
||||||
use gtk4::glib;
|
|
||||||
use gtk4::prelude::AppInfoExt;
|
|
||||||
|
|
||||||
use crate::utils::BoxedWindowList;
|
use crate::utils::BoxedWindowList;
|
||||||
|
use gdk4::glib::Object;
|
||||||
|
use gdk4::subclass::prelude::ObjectSubclassExt;
|
||||||
|
use gio::{DesktopAppInfo, Icon};
|
||||||
|
use gtk4::prelude::*;
|
||||||
|
use gtk4::{glib, Image};
|
||||||
|
use std::cell::Ref;
|
||||||
|
|
||||||
mod imp;
|
mod imp;
|
||||||
|
|
||||||
|
|
@ -37,17 +37,63 @@ impl DockObject {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_name(&self) -> Option<String> {
|
pub fn from_plugin(plugin: plugin::BoxedDockPlugin) -> Self {
|
||||||
|
let self_ = Object::new(&[("saved", &true)]).expect("Failed to create `DockObject`.");
|
||||||
|
let imp = imp::DockObject::from_instance(&self_);
|
||||||
|
imp.plugin.replace(Some(plugin));
|
||||||
|
self_
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_path(&self) -> Option<String> {
|
||||||
let imp = imp::DockObject::from_instance(&self);
|
let imp = imp::DockObject::from_instance(&self);
|
||||||
if let Some(app_info) = imp.appinfo.borrow().as_ref() {
|
if let Some(app_info) = imp.appinfo.borrow().as_ref() {
|
||||||
app_info
|
app_info
|
||||||
.filename()
|
.filename()
|
||||||
.map(|name| name.to_string_lossy().into())
|
.map(|name| name.to_string_lossy().into())
|
||||||
|
} else if let Some(plugin) = imp.plugin.borrow().as_ref() {
|
||||||
|
Some(plugin.path.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_name(&self) -> Option<String> {
|
||||||
|
let imp = imp::DockObject::from_instance(&self);
|
||||||
|
if let Some(app_info) = imp.appinfo.borrow().as_ref() {
|
||||||
|
Some(app_info.name().to_string())
|
||||||
|
} else if let Some(plugin) = imp.plugin.borrow().as_ref() {
|
||||||
|
Some(plugin.name.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_popover_menu(&self) -> Option<gtk4::Box> {
|
||||||
|
let imp = imp::DockObject::from_instance(&self);
|
||||||
|
if let Some(plugin) = imp.plugin.borrow().as_ref() {
|
||||||
|
Some(plugin.popover_menu.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_image(&self) -> gtk4::Image {
|
||||||
|
let imp = imp::DockObject::from_instance(&self);
|
||||||
|
if let Some(app_info) = imp.appinfo.borrow().as_ref() {
|
||||||
|
let image = Image::new();
|
||||||
|
let icon = app_info
|
||||||
|
.icon()
|
||||||
|
.unwrap_or(Icon::for_string("image-missing").expect("Failed to set default icon"));
|
||||||
|
image.set_from_gicon(&icon);
|
||||||
|
image
|
||||||
|
} else if let Some(plugin) = imp.plugin.borrow().as_ref() {
|
||||||
|
plugin.image.clone()
|
||||||
|
} else {
|
||||||
|
println!("failed to load image");
|
||||||
|
Image::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_saved(&self, is_saved: bool) {
|
pub fn set_saved(&self, is_saved: bool) {
|
||||||
let imp = imp::DockObject::from_instance(&self);
|
let imp = imp::DockObject::from_instance(&self);
|
||||||
imp.saved.replace(is_saved);
|
imp.saved.replace(is_saved);
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,11 @@ use gio::DesktopAppInfo;
|
||||||
use gtk4::subclass::prelude::*;
|
use gtk4::subclass::prelude::*;
|
||||||
use gtk4::{gio, glib};
|
use gtk4::{gio, glib};
|
||||||
use gtk4::{prelude::*, Label};
|
use gtk4::{prelude::*, Label};
|
||||||
use gtk4::{Box, Button, Image, ListBox, Orientation, Revealer, Window};
|
use gtk4::{Box, Button, Image, ListBox, Orientation, Window};
|
||||||
|
|
||||||
use crate::dock_object::{self, DockObject};
|
use crate::dock_object::DockObject;
|
||||||
use crate::utils::BoxedWindowList;
|
use crate::utils::BoxedWindowList;
|
||||||
use crate::Event;
|
use crate::Event;
|
||||||
use crate::Item;
|
|
||||||
use crate::TX;
|
use crate::TX;
|
||||||
|
|
||||||
mod imp;
|
mod imp;
|
||||||
|
|
@ -50,137 +49,108 @@ impl DockPopover {
|
||||||
let dock_object = imp.dock_object.borrow();
|
let dock_object = imp.dock_object.borrow();
|
||||||
let menu_handle = imp.menu_handle.borrow();
|
let menu_handle = imp.menu_handle.borrow();
|
||||||
if let Some(dock_object) = dock_object.as_ref() {
|
if let Some(dock_object) = dock_object.as_ref() {
|
||||||
cascade! {
|
if let Some(menu) = dock_object.get_popover_menu() {
|
||||||
&self;
|
// TODO investigate (dock:255244): Gtk-CRITICAL **: 19:12:38.668: gtk_at_context_set_accessible_role: assertion '!self->realized' failed
|
||||||
..set_spacing(4);
|
// appears after setting the menu handle a second time
|
||||||
..set_orientation(Orientation::Vertical);
|
menu_handle.append(&menu);
|
||||||
..set_hexpand(true);
|
} else {
|
||||||
};
|
cascade! {
|
||||||
|
&self;
|
||||||
|
..set_spacing(4);
|
||||||
|
..set_orientation(Orientation::Vertical);
|
||||||
|
..set_hexpand(true);
|
||||||
|
};
|
||||||
|
|
||||||
let all_windows_item_container = cascade! {
|
let all_windows_item_container = cascade! {
|
||||||
Box::new(Orientation::Vertical, 4);
|
Box::new(Orientation::Vertical, 4);
|
||||||
};
|
};
|
||||||
menu_handle.append(&all_windows_item_container);
|
menu_handle.append(&all_windows_item_container);
|
||||||
|
if let Ok(window_list) = dock_object
|
||||||
// let all_windows_item_header = cascade! {
|
.property("active")
|
||||||
// Button::new();
|
.unwrap()
|
||||||
// ..set_hexpand(true);
|
.get::<BoxedWindowList>()
|
||||||
// };
|
{
|
||||||
// all_windows_item_container.append(&all_windows_item_header);
|
|
||||||
|
|
||||||
// let all_windows_item_header_box = cascade! {
|
|
||||||
// Box::new(Orientation::Horizontal, 4);
|
|
||||||
// ..set_hexpand(true);
|
|
||||||
// };
|
|
||||||
// all_windows_item_header.set_child(Some(&all_windows_item_header_box));
|
|
||||||
|
|
||||||
// let all_windows_item_header_title = cascade! {
|
|
||||||
// Label::new(Some("All Windows"));
|
|
||||||
// ..add_css_class("header-5");
|
|
||||||
// ..set_halign(gtk4::Align::Start);
|
|
||||||
// };
|
|
||||||
// all_windows_item_header_box.append(&all_windows_item_header_title);
|
|
||||||
|
|
||||||
// let all_windows_item_header_icon = cascade! {
|
|
||||||
// Image::from_icon_name(Some("go-down"));
|
|
||||||
// ..set_halign(gtk4::Align::End);
|
|
||||||
// ..set_hexpand(true);
|
|
||||||
// ..set_pixel_size(16);
|
|
||||||
// };
|
|
||||||
// all_windows_item_header_box.append(&all_windows_item_header_icon);
|
|
||||||
// imp.all_windows_item_header.replace(all_windows_item_header);
|
|
||||||
|
|
||||||
if let Ok(window_list) = dock_object
|
|
||||||
.property("active")
|
|
||||||
.unwrap()
|
|
||||||
.get::<BoxedWindowList>()
|
|
||||||
{
|
|
||||||
if window_list.0.len() == 0 {
|
|
||||||
all_windows_item_container.hide();
|
|
||||||
} else {
|
|
||||||
// let window_list_revealer = cascade! {
|
|
||||||
// Revealer::new();
|
|
||||||
// ..set_reveal_child(false);
|
|
||||||
// ..set_transition_type(gtk4::RevealerTransitionType::SlideDown);
|
|
||||||
// };
|
|
||||||
// all_windows_item_container.append(&window_list_revealer);
|
|
||||||
let window_listbox = cascade! {
|
|
||||||
ListBox::new();
|
|
||||||
..set_activate_on_single_click(true);
|
|
||||||
};
|
|
||||||
all_windows_item_container.append(&window_listbox);
|
|
||||||
for w in window_list.0 {
|
|
||||||
let window_box = cascade! {
|
|
||||||
Box::new(Orientation::Vertical, 4);
|
|
||||||
};
|
|
||||||
window_listbox.append(&window_box);
|
|
||||||
|
|
||||||
let window_title = cascade! {
|
|
||||||
Label::new(Some(w.name.as_str()));
|
|
||||||
..set_margin_start(4);
|
|
||||||
..set_margin_end(4);
|
|
||||||
..set_margin_top(4);
|
|
||||||
..set_margin_bottom(4);
|
|
||||||
..set_wrap(true);
|
|
||||||
..set_max_width_chars(20);
|
|
||||||
..set_ellipsize(EllipsizeMode::End);
|
|
||||||
..add_css_class("title-4");
|
|
||||||
..add_css_class("window_title");
|
|
||||||
};
|
|
||||||
|
|
||||||
let window_image = cascade! {
|
|
||||||
//TODO fill with image of window
|
|
||||||
Image::from_pixbuf(None);
|
|
||||||
};
|
|
||||||
window_box.append(&window_image);
|
|
||||||
window_box.append(&window_title);
|
|
||||||
}
|
|
||||||
// imp.all_windows_item_revealer.replace(window_list_revealer);
|
|
||||||
imp.window_list.replace(window_listbox);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let launch_item_container = cascade! {
|
|
||||||
Box::new(Orientation::Vertical, 4);
|
|
||||||
..set_hexpand(true);
|
|
||||||
};
|
|
||||||
menu_handle.append(&launch_item_container);
|
|
||||||
|
|
||||||
let launch_new_item = cascade! {
|
|
||||||
Button::with_label("New Window");
|
|
||||||
};
|
|
||||||
launch_item_container.append(&launch_new_item);
|
|
||||||
imp.launch_new_item.replace(launch_new_item);
|
|
||||||
|
|
||||||
let favorite_item = cascade! {
|
|
||||||
Button::with_label(if dock_object.property("saved").unwrap().get::<bool>().unwrap() {"Remove from Favorites"} else {"Add to Favorites"});
|
|
||||||
};
|
|
||||||
menu_handle.append(&favorite_item);
|
|
||||||
imp.favorite_item.replace(favorite_item);
|
|
||||||
|
|
||||||
if let Ok(window_list) = dock_object
|
|
||||||
.property("active")
|
|
||||||
.unwrap()
|
|
||||||
.get::<BoxedWindowList>()
|
|
||||||
{
|
|
||||||
if window_list.0.len() > 1 {
|
|
||||||
let quit_all_item = cascade! {
|
|
||||||
Button::with_label(format!("Quit {} Windows", window_list.0.len()).as_str());
|
|
||||||
};
|
|
||||||
menu_handle.append(&quit_all_item);
|
|
||||||
imp.quit_all_item.replace(quit_all_item);
|
|
||||||
} else {
|
|
||||||
let quit_all_item = cascade! {
|
|
||||||
Button::with_label("Quit");
|
|
||||||
};
|
|
||||||
menu_handle.append(&quit_all_item);
|
|
||||||
if window_list.0.len() == 0 {
|
if window_list.0.len() == 0 {
|
||||||
quit_all_item.hide();
|
all_windows_item_container.hide();
|
||||||
|
} else {
|
||||||
|
let window_listbox = cascade! {
|
||||||
|
ListBox::new();
|
||||||
|
..set_activate_on_single_click(true);
|
||||||
|
};
|
||||||
|
all_windows_item_container.append(&window_listbox);
|
||||||
|
for w in window_list.0 {
|
||||||
|
let window_box = cascade! {
|
||||||
|
Box::new(Orientation::Vertical, 4);
|
||||||
|
};
|
||||||
|
window_listbox.append(&window_box);
|
||||||
|
|
||||||
|
let window_title = cascade! {
|
||||||
|
Label::new(Some(w.name.as_str()));
|
||||||
|
..set_margin_start(4);
|
||||||
|
..set_margin_end(4);
|
||||||
|
..set_margin_top(4);
|
||||||
|
..set_margin_bottom(4);
|
||||||
|
..set_wrap(true);
|
||||||
|
..set_max_width_chars(20);
|
||||||
|
..set_ellipsize(EllipsizeMode::End);
|
||||||
|
..add_css_class("title-4");
|
||||||
|
..add_css_class("window_title");
|
||||||
|
};
|
||||||
|
|
||||||
|
let window_image = cascade! {
|
||||||
|
//TODO fill with image of window
|
||||||
|
Image::from_pixbuf(None);
|
||||||
|
};
|
||||||
|
window_box.append(&window_image);
|
||||||
|
window_box.append(&window_title);
|
||||||
|
}
|
||||||
|
// imp.all_windows_item_revealer.replace(window_list_revealer);
|
||||||
|
imp.window_list.replace(window_listbox);
|
||||||
}
|
}
|
||||||
imp.quit_all_item.replace(quit_all_item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let launch_item_container = cascade! {
|
||||||
|
Box::new(Orientation::Vertical, 4);
|
||||||
|
..set_hexpand(true);
|
||||||
|
};
|
||||||
|
menu_handle.append(&launch_item_container);
|
||||||
|
|
||||||
|
let launch_new_item = cascade! {
|
||||||
|
Button::with_label("New Window");
|
||||||
|
};
|
||||||
|
launch_item_container.append(&launch_new_item);
|
||||||
|
imp.launch_new_item.replace(launch_new_item);
|
||||||
|
|
||||||
|
let favorite_item = cascade! {
|
||||||
|
Button::with_label(if dock_object.property("saved").unwrap().get::<bool>().unwrap() {"Remove from Favorites"} else {"Add to Favorites"});
|
||||||
|
};
|
||||||
|
menu_handle.append(&favorite_item);
|
||||||
|
imp.favorite_item.replace(favorite_item);
|
||||||
|
|
||||||
|
if let Ok(window_list) = dock_object
|
||||||
|
.property("active")
|
||||||
|
.unwrap()
|
||||||
|
.get::<BoxedWindowList>()
|
||||||
|
{
|
||||||
|
if window_list.0.len() > 1 {
|
||||||
|
let quit_all_item = cascade! {
|
||||||
|
Button::with_label(format!("Quit {} Windows", window_list.0.len()).as_str());
|
||||||
|
};
|
||||||
|
menu_handle.append(&quit_all_item);
|
||||||
|
imp.quit_all_item.replace(quit_all_item);
|
||||||
|
} else {
|
||||||
|
let quit_all_item = cascade! {
|
||||||
|
Button::with_label("Quit");
|
||||||
|
};
|
||||||
|
menu_handle.append(&quit_all_item);
|
||||||
|
if window_list.0.len() == 0 {
|
||||||
|
quit_all_item.hide();
|
||||||
|
}
|
||||||
|
imp.quit_all_item.replace(quit_all_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.setup_handlers();
|
||||||
}
|
}
|
||||||
self.setup_handlers();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Mutex;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::dock_list::DockListType;
|
||||||
|
use crate::utils::{block_on, BoxedWindowList};
|
||||||
use gdk4::Display;
|
use gdk4::Display;
|
||||||
use gio::DesktopAppInfo;
|
use gio::DesktopAppInfo;
|
||||||
use gtk4::gio;
|
use gtk4::gio;
|
||||||
|
|
@ -9,16 +12,14 @@ use gtk4::prelude::*;
|
||||||
use gtk4::Application;
|
use gtk4::Application;
|
||||||
use gtk4::CssProvider;
|
use gtk4::CssProvider;
|
||||||
use gtk4::StyleContext;
|
use gtk4::StyleContext;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::{Lazy, OnceCell};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use x11rb::rust_connection::RustConnection;
|
use x11rb::rust_connection::RustConnection;
|
||||||
use zbus::Connection;
|
use zbus::Connection;
|
||||||
use zvariant_derive::Type;
|
use zvariant_derive::Type;
|
||||||
|
|
||||||
use crate::dock_list::DockListType;
|
|
||||||
use crate::utils::{block_on, thread_context, BoxedWindowList};
|
|
||||||
|
|
||||||
use self::dock_object::DockObject;
|
use self::dock_object::DockObject;
|
||||||
use self::window::Window;
|
use self::window::Window;
|
||||||
|
|
||||||
|
|
@ -26,16 +27,18 @@ mod dock_item;
|
||||||
mod dock_list;
|
mod dock_list;
|
||||||
mod dock_object;
|
mod dock_object;
|
||||||
mod dock_popover;
|
mod dock_popover;
|
||||||
|
mod plugin;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
|
const ID: &str = "com.cosmic.dock";
|
||||||
const DEST: &str = "com.System76.PopShell";
|
const DEST: &str = "com.System76.PopShell";
|
||||||
const PATH: &str = "/com/System76/PopShell";
|
const PATH: &str = "/com/System76/PopShell";
|
||||||
const NUM_LAUNCHER_ITEMS: u8 = 10;
|
|
||||||
|
|
||||||
static TX: OnceCell<mpsc::Sender<Event>> = OnceCell::new();
|
static TX: OnceCell<mpsc::Sender<Event>> = OnceCell::new();
|
||||||
static X11_CONN: OnceCell<RustConnection> = OnceCell::new();
|
static X11_CONN: OnceCell<RustConnection> = OnceCell::new();
|
||||||
static PLUGIN_POOL: OnceCell<glib::ThreadPool> = OnceCell::new();
|
static PLUGINS: Lazy<Mutex<HashMap<String, libloading::Library>>> =
|
||||||
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
WindowList(Vec<Item>),
|
WindowList(Vec<Item>),
|
||||||
|
|
@ -97,7 +100,7 @@ fn spawn_zbus(tx: mpsc::Sender<Event>) -> Connection {
|
||||||
connection
|
connection
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _setup_shortcuts(app: &Application) {}
|
fn _setup_shortcuts(_app: &Application) {}
|
||||||
|
|
||||||
fn load_css() {
|
fn load_css() {
|
||||||
// Load the css file and add it to the provider
|
// Load the css file and add it to the provider
|
||||||
|
|
@ -114,22 +117,15 @@ fn load_css() {
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
assert!(utils::BoxedWindowList::static_type().is_valid());
|
assert!(utils::BoxedWindowList::static_type().is_valid());
|
||||||
let app = gtk4::Application::builder()
|
assert!(plugin::BoxedDockPlugin::static_type().is_valid());
|
||||||
.application_id("com.system76.dock")
|
let app = gtk4::Application::builder().application_id(ID).build();
|
||||||
.build();
|
|
||||||
|
|
||||||
app.connect_startup(|app| {
|
app.connect_startup(|_app| {
|
||||||
// setup_shortcuts(app);
|
// setup_shortcuts(app);
|
||||||
load_css()
|
load_css()
|
||||||
});
|
});
|
||||||
|
|
||||||
app.connect_activate(move |app| {
|
app.connect_activate(move |app| {
|
||||||
let pool = glib::ThreadPool::new_shared(None).expect("Failed to spawn thread pool");
|
|
||||||
if PLUGIN_POOL.set(pool).is_err() {
|
|
||||||
eprintln!("failed to set global thread pool. Exiting");
|
|
||||||
std::process::exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
let (tx, mut rx) = mpsc::channel(100);
|
let (tx, mut rx) = mpsc::channel(100);
|
||||||
|
|
||||||
let zbus_conn = spawn_zbus(tx.clone());
|
let zbus_conn = spawn_zbus(tx.clone());
|
||||||
|
|
@ -174,7 +170,7 @@ fn main() {
|
||||||
let mut index: Option<u32> = None;
|
let mut index: Option<u32> = None;
|
||||||
while let Some(item) = active_app_model.item(cur) {
|
while let Some(item) = active_app_model.item(cur) {
|
||||||
if let Ok(cur_dock_object) = item.downcast::<DockObject>() {
|
if let Ok(cur_dock_object) = item.downcast::<DockObject>() {
|
||||||
if cur_dock_object.get_name() == Some(name.clone()) {
|
if cur_dock_object.get_path() == Some(name.clone()) {
|
||||||
cur_dock_object.set_saved(true);
|
cur_dock_object.set_saved(true);
|
||||||
index = Some(cur);
|
index = Some(cur);
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +187,7 @@ fn main() {
|
||||||
let mut index: Option<u32> = None;
|
let mut index: Option<u32> = None;
|
||||||
while let Some(item) = saved_app_model.item(cur) {
|
while let Some(item) = saved_app_model.item(cur) {
|
||||||
if let Ok(cur_dock_object) = item.downcast::<DockObject>() {
|
if let Ok(cur_dock_object) = item.downcast::<DockObject>() {
|
||||||
if cur_dock_object.get_name() == Some(name.clone()) {
|
if cur_dock_object.get_path() == Some(name.clone()) {
|
||||||
cur_dock_object.set_saved(false);
|
cur_dock_object.set_saved(false);
|
||||||
index = Some(cur);
|
index = Some(cur);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
examples/dock/plugin.rs
Normal file
10
examples/dock/plugin.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
use gtk4::glib;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, gtk4::glib::GBoxed)]
|
||||||
|
#[gboxed(type_name = "BoxedDockPlugin")]
|
||||||
|
pub struct BoxedDockPlugin {
|
||||||
|
pub path: String,
|
||||||
|
pub name: String,
|
||||||
|
pub image: gtk4::Image,
|
||||||
|
pub popover_menu: gtk4::Box,
|
||||||
|
}
|
||||||
|
|
@ -60,6 +60,10 @@ box.dock {
|
||||||
background: #333333CC;
|
background: #333333CC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
image.dock {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
window.root_window {
|
window.root_window {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ pub struct BoxedDockObject(pub Option<DockObject>);
|
||||||
|
|
||||||
pub fn data_path() -> PathBuf {
|
pub fn data_path() -> PathBuf {
|
||||||
let mut path = glib::user_data_dir();
|
let mut path = glib::user_data_dir();
|
||||||
path.push("com.cosmic.dock");
|
path.push(crate::ID);
|
||||||
std::fs::create_dir_all(&path).expect("Could not create directory.");
|
std::fs::create_dir_all(&path).expect("Could not create directory.");
|
||||||
path.push("data.json");
|
path.push("data.json");
|
||||||
path
|
path
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use cascade::cascade;
|
use cascade::cascade;
|
||||||
use gdk4::Rectangle;
|
use gdk4::Rectangle;
|
||||||
use gdk4::Snapshot;
|
|
||||||
use gdk4_x11::X11Display;
|
use gdk4_x11::X11Display;
|
||||||
use gdk4_x11::X11Surface;
|
use gdk4_x11::X11Surface;
|
||||||
use glib::Object;
|
use glib::Object;
|
||||||
use glib::Type;
|
use glib::Type;
|
||||||
use gsk::BlurNode;
|
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4::subclass::prelude::*;
|
use gtk4::subclass::prelude::*;
|
||||||
use gtk4::Align;
|
use gtk4::Align;
|
||||||
|
|
@ -17,7 +15,7 @@ use gtk4::Orientation;
|
||||||
use gtk4::Revealer;
|
use gtk4::Revealer;
|
||||||
use gtk4::RevealerTransitionType;
|
use gtk4::RevealerTransitionType;
|
||||||
use gtk4::Separator;
|
use gtk4::Separator;
|
||||||
use gtk4::{gio, glib, gsk};
|
use gtk4::{gio, glib};
|
||||||
use x11rb::connection::Connection;
|
use x11rb::connection::Connection;
|
||||||
use x11rb::protocol::xproto;
|
use x11rb::protocol::xproto;
|
||||||
use x11rb::protocol::xproto::ConnectionExt;
|
use x11rb::protocol::xproto::ConnectionExt;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue