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"] }
|
||||
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -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>>,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
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 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!(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
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;
|
||||
}
|
||||
|
||||
image.dock {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
window.root_window {
|
||||
background: transparent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue