basic plugin!

This commit is contained in:
Ashley Wulber 2022-01-11 00:05:57 -05:00
parent 7a8e3fe492
commit 537539f43d
13 changed files with 332 additions and 224 deletions

View file

@ -16,6 +16,8 @@ wayland-protocols = { version = "0.28", features = [ "client", "unstable_protoco
x11 = { version = "2", features = ["xlib"] }
# 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 = { git = "https://github.com/wash2/launcher.git" }
serde = "1.0.130"
@ -30,10 +32,8 @@ x11rb = "0.9.0"
zbus = "2.0.0"
zvariant = "3.0.0"
zvariant_derive = "3.0.0"
[dependencies.gtk4]
version = "0.3"
features = ["v4_4"]
libloading = "0.7.2"
gtk4-sys = "0.3.1"
[profile.release]
incremental = true

View file

@ -10,7 +10,7 @@ use crate::dock_popover::DockPopover;
#[derive(Debug, Default)]
pub struct DockItem {
pub image: Rc<RefCell<gtk4::Image>>,
pub image: Rc<RefCell<Option<gtk4::Image>>>,
pub dots: Rc<RefCell<gtk4::Label>>,
pub item_box: Rc<RefCell<gtk4::Box>>,
pub popover: Rc<RefCell<gtk4::Popover>>,

View file

@ -1,6 +1,5 @@
use cascade::cascade;
use gio::DesktopAppInfo;
use gio::Icon;
use gtk4::glib;
use gtk4::prelude::*;
use gtk4::subclass::prelude::*;
@ -13,6 +12,7 @@ use gtk4::Popover;
use crate::dock_object::DockObject;
use crate::dock_popover::DockPopover;
use crate::plugin;
use crate::utils::BoxedWindowList;
mod imp;
@ -82,7 +82,7 @@ impl DockItem {
.unwrap();
let imp = imp::DockItem::from_instance(&self_);
imp.image.replace(image);
imp.image.replace(Some(image));
imp.dots.replace(dots);
imp.item_box.replace(item_box);
imp.popover.replace(popover);
@ -92,24 +92,21 @@ impl DockItem {
}
// 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);
if let Ok(app_info_value) = dock_object.property("appinfo") {
if let Ok(Some(app_info)) = app_info_value.get::<Option<DesktopAppInfo>>() {
self_
.image
.borrow()
.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.borrow().set_from_gicon(&icon);
}
} else {
println!("initializing dock item failed...");
let image = cascade! {
dock_object.get_image();
..set_hexpand(true);
..set_halign(Align::Center);
..set_pixel_size(64);
..set_tooltip_text(dock_object.get_name().as_deref());
};
let old_image = self_.image.replace(None);
if let Some(old_image) = old_image {
self_.item_box.borrow().remove(&old_image);
}
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) = active_value.get::<BoxedWindowList>() {
let dots = self_.dots.borrow();

View file

@ -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 gdk4::ContentProvider;
use gdk4::Display;
@ -17,17 +26,12 @@ use gtk4::SignalListItemFactory;
use gtk4::Window;
use gtk4::{gio, glib};
use gtk4::{DragSource, GestureClick};
use std::ffi::CStr;
use std::fs::File;
use std::io::BufReader;
use std::io::Read;
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! {
@ -97,41 +101,122 @@ impl DockList {
let model = self.model();
model.splice(model.n_items(), 0, &dock_objects);
return;
}
}
eprintln!("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 {
eprintln!("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!("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) {
@ -160,6 +245,7 @@ impl DockList {
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");
// TODO save plugins here for now examples are hardcoded and don't need to be saved
}
fn layout(&self) {
@ -420,7 +506,6 @@ impl DockList {
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 {
@ -526,7 +611,7 @@ impl DockList {
}),
);
factory.connect_bind(move |_, list_item| {
let application_object = list_item
let dock_object = list_item
.item()
.expect("The item has to exist.")
.downcast::<DockObject>()
@ -536,7 +621,7 @@ impl DockList {
.expect("The list item child needs to exist.")
.downcast::<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
imp.list_view.get().unwrap().set_factory(Some(&factory));

View file

@ -0,0 +1 @@
ashleywulber@pop-os.190471:1641501268

View file

@ -8,6 +8,7 @@ use gtk4::prelude::*;
use gtk4::subclass::prelude::*;
use once_cell::sync::Lazy;
use crate::plugin::BoxedDockPlugin;
use crate::utils::BoxedWindowList;
// Object holding the state
@ -15,6 +16,7 @@ use crate::utils::BoxedWindowList;
pub struct DockObject {
pub(super) appinfo: RefCell<Option<DesktopAppInfo>>,
pub(super) active: RefCell<BoxedWindowList>,
pub(super) plugin: RefCell<Option<BoxedDockPlugin>>,
pub(super) saved: Cell<bool>,
pub(super) popover: Cell<bool>,
}
@ -93,7 +95,6 @@ impl ObjectImpl for DockObject {
self.popover
.replace(value.get().expect("Value needs to be a boolean"));
}
_ => unimplemented!(),
}
}

View file

@ -1,13 +1,13 @@
use std::path::Path;
use gdk4::glib::Object;
use gdk4::prelude::FileExt;
use gdk4::subclass::prelude::ObjectSubclassExt;
use gio::DesktopAppInfo;
use gtk4::glib;
use gtk4::prelude::AppInfoExt;
use crate::plugin::{self, BoxedDockPlugin};
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;
@ -37,17 +37,63 @@ impl DockObject {
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);
if let Some(app_info) = imp.appinfo.borrow().as_ref() {
app_info
.filename()
.map(|name| name.to_string_lossy().into())
} else if let Some(plugin) = imp.plugin.borrow().as_ref() {
Some(plugin.path.clone())
} else {
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) {
let imp = imp::DockObject::from_instance(&self);
imp.saved.replace(is_saved);

View file

@ -4,12 +4,11 @@ use gio::DesktopAppInfo;
use gtk4::subclass::prelude::*;
use gtk4::{gio, glib};
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::Event;
use crate::Item;
use crate::TX;
mod imp;
@ -50,137 +49,108 @@ impl DockPopover {
let dock_object = imp.dock_object.borrow();
let menu_handle = imp.menu_handle.borrow();
if let Some(dock_object) = dock_object.as_ref() {
cascade! {
&self;
..set_spacing(4);
..set_orientation(Orientation::Vertical);
..set_hexpand(true);
};
if let Some(menu) = dock_object.get_popover_menu() {
// TODO investigate (dock:255244): Gtk-CRITICAL **: 19:12:38.668: gtk_at_context_set_accessible_role: assertion '!self->realized' failed
// appears after setting the menu handle a second time
menu_handle.append(&menu);
} else {
cascade! {
&self;
..set_spacing(4);
..set_orientation(Orientation::Vertical);
..set_hexpand(true);
};
let all_windows_item_container = cascade! {
Box::new(Orientation::Vertical, 4);
};
menu_handle.append(&all_windows_item_container);
// let all_windows_item_header = cascade! {
// Button::new();
// ..set_hexpand(true);
// };
// 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);
let all_windows_item_container = cascade! {
Box::new(Orientation::Vertical, 4);
};
menu_handle.append(&all_windows_item_container);
if let Ok(window_list) = dock_object
.property("active")
.unwrap()
.get::<BoxedWindowList>()
{
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();
}
}

View file

@ -1,6 +1,9 @@
use std::collections::BTreeMap;
use std::sync::Mutex;
use std::time::Duration;
use crate::dock_list::DockListType;
use crate::utils::{block_on, BoxedWindowList};
use gdk4::Display;
use gio::DesktopAppInfo;
use gtk4::gio;
@ -9,16 +12,14 @@ use gtk4::prelude::*;
use gtk4::Application;
use gtk4::CssProvider;
use gtk4::StyleContext;
use once_cell::sync::OnceCell;
use once_cell::sync::{Lazy, OnceCell};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tokio::sync::mpsc;
use x11rb::rust_connection::RustConnection;
use zbus::Connection;
use zvariant_derive::Type;
use crate::dock_list::DockListType;
use crate::utils::{block_on, thread_context, BoxedWindowList};
use self::dock_object::DockObject;
use self::window::Window;
@ -26,16 +27,18 @@ mod dock_item;
mod dock_list;
mod dock_object;
mod dock_popover;
mod plugin;
mod utils;
mod window;
const ID: &str = "com.cosmic.dock";
const DEST: &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 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 {
WindowList(Vec<Item>),
@ -97,7 +100,7 @@ fn spawn_zbus(tx: mpsc::Sender<Event>) -> Connection {
connection
}
fn _setup_shortcuts(app: &Application) {}
fn _setup_shortcuts(_app: &Application) {}
fn load_css() {
// Load the css file and add it to the provider
@ -114,22 +117,15 @@ fn load_css() {
fn main() {
assert!(utils::BoxedWindowList::static_type().is_valid());
let app = gtk4::Application::builder()
.application_id("com.system76.dock")
.build();
assert!(plugin::BoxedDockPlugin::static_type().is_valid());
let app = gtk4::Application::builder().application_id(ID).build();
app.connect_startup(|app| {
app.connect_startup(|_app| {
// setup_shortcuts(app);
load_css()
});
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 zbus_conn = spawn_zbus(tx.clone());
@ -174,7 +170,7 @@ fn main() {
let mut index: Option<u32> = None;
while let Some(item) = active_app_model.item(cur) {
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);
index = Some(cur);
}
@ -191,7 +187,7 @@ fn main() {
let mut index: Option<u32> = None;
while let Some(item) = saved_app_model.item(cur) {
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);
index = Some(cur);
}

10
examples/dock/plugin.rs Normal file
View 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,
}

View file

@ -60,6 +60,10 @@ box.dock {
background: #333333CC;
}
image.dock {
border-radius: 12px;
}
window.root_window {
background: transparent;
}

View file

@ -16,7 +16,7 @@ pub struct BoxedDockObject(pub Option<DockObject>);
pub fn data_path() -> PathBuf {
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.");
path.push("data.json");
path

View file

@ -1,11 +1,9 @@
use cascade::cascade;
use gdk4::Rectangle;
use gdk4::Snapshot;
use gdk4_x11::X11Display;
use gdk4_x11::X11Surface;
use glib::Object;
use glib::Type;
use gsk::BlurNode;
use gtk4::prelude::*;
use gtk4::subclass::prelude::*;
use gtk4::Align;
@ -17,7 +15,7 @@ use gtk4::Orientation;
use gtk4::Revealer;
use gtk4::RevealerTransitionType;
use gtk4::Separator;
use gtk4::{gio, glib, gsk};
use gtk4::{gio, glib};
use x11rb::connection::Connection;
use x11rb::protocol::xproto;
use x11rb::protocol::xproto::ConnectionExt;