wip: use toplevel protocols

This commit is contained in:
Ashley Wulber 2022-07-19 11:33:19 -04:00
parent 647a402206
commit 560ebc0bf5
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
20 changed files with 1073 additions and 623 deletions

View file

@ -1,9 +1,10 @@
use std::env;
use crate::TX;
// SPDX-License-Identifier: MPL-2.0-only
use crate::dock_list::DockList;
use crate::dock_list::DockListType;
use crate::utils::Event;
use crate::utils::AppListEvent;
use cascade::cascade;
use cosmic_panel_config::{PanelAnchor, CosmicPanelConfig};
use gtk4::prelude::*;
@ -21,7 +22,7 @@ glib::wrapper! {
}
impl AppsContainer {
pub fn new(tx: Sender<Event>) -> Self {
pub fn new() -> Self {
let self_: Self = glib::Object::new(&[]).expect("Failed to create AppsContainer");
let imp = imp::AppsContainer::from_instance(&self_);
@ -34,7 +35,7 @@ impl AppsContainer {
let config = CosmicPanelConfig::load_from_env().unwrap_or_default();
let saved_app_list_view = DockList::new(DockListType::Saved, tx.clone(), config.clone());
let saved_app_list_view = DockList::new(DockListType::Saved, config.clone());
self_.append(&saved_app_list_view);
// let separator_container = cascade! {
@ -52,7 +53,7 @@ impl AppsContainer {
// ..add_css_class("dock_separator");
// };
// separator_container.append(&separator);
let active_app_list_view = DockList::new(DockListType::Active, tx, config.clone());
let active_app_list_view = DockList::new(DockListType::Active, config.clone());
self_.append(&active_app_list_view);
// self_.connect_orientation_notify(glib::clone!(@weak separator => move |c| {
// dbg!(c.orientation());

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MPL-2.0-only
use crate::{apps_container::AppsContainer, fl, Event};
use crate::{apps_container::AppsContainer, fl, AppListEvent};
use cascade::cascade;
use gtk4::{
gio,
@ -20,7 +20,7 @@ glib::wrapper! {
}
impl CosmicAppListWindow {
pub fn new(app: &gtk4::Application, tx: mpsc::Sender<Event>) -> Self {
pub fn new(app: &gtk4::Application) -> Self {
let self_: Self =
Object::new(&[("application", app)]).expect("Failed to create `CosmicAppListWindow`.");
let imp = imp::CosmicAppListWindow::from_instance(&self_);
@ -34,7 +34,7 @@ impl CosmicAppListWindow {
..set_title(Some(&fl!("cosmic-app-list")));
..add_css_class("transparent");
};
let app_list = AppsContainer::new(tx);
let app_list = AppsContainer::new();
self_.set_child(Some(&app_list));
imp.inner.set(app_list).unwrap();

View file

@ -0,0 +1,31 @@
use std::fmt::Debug;
use std::fs::File;
use anyhow::anyhow;
use serde::Deserialize;
use xdg::BaseDirectories;
use crate::ID;
#[derive(Debug, Clone, Deserialize)]
pub enum TopLevelFilter {
ActiveWorkspace,
ConfiguredOutput,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct AppListConfig {
pub filter_top_levels: Option<TopLevelFilter>,
}
impl AppListConfig {
/// load config with the provided name
pub fn load() -> anyhow::Result<AppListConfig> {
let file= match BaseDirectories::new().ok().and_then(|dirs| dirs.find_config_file(format!("{ID}/config.ron"))).and_then(|p| File::open(p).ok()) {
Some(path) => path,
_ => {
anyhow::bail!("Failed to load config");
}
};
ron::de::from_reader::<_, AppListConfig>(file).map_err(|err| anyhow!("Failed to parse config file: {}", err))
}
}

View file

@ -11,7 +11,7 @@ use std::rc::Rc;
use tokio::sync::mpsc::Sender;
use crate::dock_popover::DockPopover;
use crate::utils::Event;
use crate::utils::AppListEvent;
#[derive(Debug, Default)]
pub struct DockItem {
@ -20,7 +20,7 @@ pub struct DockItem {
pub item_box: Rc<RefCell<gtk4::Box>>,
pub popover: Rc<RefCell<gtk4::Popover>>,
pub popover_menu: Rc<RefCell<Option<DockPopover>>>,
pub tx: OnceCell<Sender<Event>>,
pub tx: OnceCell<Sender<AppListEvent>>,
pub icon_size: Rc<Cell<u32>>,
}

View file

@ -3,7 +3,7 @@
use crate::dock_object::DockObject;
use crate::dock_popover::DockPopover;
use crate::utils::BoxedWindowList;
use crate::utils::Event;
use crate::utils::AppListEvent;
use cascade::cascade;
use cosmic_panel_config::PanelAnchor;
use gtk4::glib;
@ -25,7 +25,7 @@ glib::wrapper! {
}
impl DockItem {
pub fn new(tx: Sender<Event>, icon_size: u32) -> Self {
pub fn new(icon_size: u32) -> Self {
let self_: DockItem = glib::Object::new(&[]).expect("Failed to create DockItem");
let item_box = Box::new(Orientation::Vertical, 0);
@ -66,7 +66,7 @@ impl DockItem {
});
let popover_menu = cascade! {
DockPopover::new(tx.clone());
DockPopover::new();
..add_css_class("popover_menu");
};
popover.set_child(Some(&popover_menu));
@ -87,7 +87,6 @@ impl DockItem {
imp.item_box.replace(item_box);
imp.popover.replace(popover);
imp.popover_menu.replace(Some(popover_menu));
imp.tx.set(tx).unwrap();
self_
}

View file

@ -10,7 +10,7 @@ use std::cell::{Cell, RefCell};
use std::rc::Rc;
use tokio::sync::mpsc;
use crate::utils::Event;
use crate::utils::AppListEvent;
#[derive(Debug, Default)]
pub struct DockList {
@ -24,7 +24,7 @@ pub struct DockList {
pub drag_cancel_signal: Rc<RefCell<Option<SignalHandlerId>>>,
pub popover_menu_index: Rc<Cell<Option<u32>>>,
pub position: Rc<Cell<PanelAnchor>>,
pub tx: OnceCell<mpsc::Sender<Event>>,
pub tx: OnceCell<mpsc::Sender<AppListEvent>>,
pub config: OnceCell<CosmicPanelConfig>
}

View file

@ -1,9 +1,11 @@
// SPDX-License-Identifier: MPL-2.0-only
use crate::{TX, WAYLAND_TX};
use crate::dock_item::DockItem;
use crate::dock_object::DockObject;
use crate::utils::data_path;
use crate::utils::{BoxedWindowList, Event, Item};
use crate::utils::{BoxedWindowList, AppListEvent};
use crate::wayland::{Toplevel, ToplevelEvent};
use cascade::cascade;
use gio::DesktopAppInfo;
use gio::Icon;
@ -49,11 +51,10 @@ impl Default for DockListType {
}
impl DockList {
pub fn new(type_: DockListType, tx: Sender<Event>, config: CosmicPanelConfig) -> Self {
pub fn new(type_: DockListType, config: CosmicPanelConfig) -> Self {
let self_: DockList = glib::Object::new(&[]).expect("Failed to create DockList");
let imp = imp::DockList::from_instance(&self_);
imp.type_.set(type_).unwrap();
imp.tx.set(tx).unwrap();
imp.config.set(config).unwrap();
self_.layout();
//dnd behavior is different for each type, as well as the data in the model
@ -219,7 +220,6 @@ impl DockList {
let model = self.model();
let list_view = &imp.list_view.get().unwrap();
let popover_menu_index = &imp.popover_menu_index;
let tx = imp.tx.get().unwrap().clone();
controller.connect_released(glib::clone!(@weak model, @weak list_view, @weak popover_menu_index => move |self_, _, x, y| {
let max_x = list_view.allocated_width();
let max_y = list_view.allocated_height();
@ -238,13 +238,10 @@ impl DockList {
// dbg!(click_modifier);
// Launch the application when an item of the list is activated
let tx = tx.clone();
let focus_window = move |first_focused_item: &Item| {
let entity = first_focused_item.entity;
let tx = tx.clone();
glib::MainContext::default().spawn_local(async move {
let _ = tx.clone().send(Event::Activate(entity)).await;
});
let focus_window = move |first_focused_item: &Toplevel| {
let toplevel_handle = first_focused_item.toplevel_handle.clone();
let tx = WAYLAND_TX.get().unwrap().clone();
let _ = tx.clone().send(ToplevelEvent::Activate(toplevel_handle));
};
let old_index = popover_menu_index.get();
if let Some(old_index) = old_index {
@ -322,7 +319,6 @@ impl DockList {
let list_view = &imp.list_view.get().unwrap();
let drag_end = &imp.drag_end_signal;
let drag_source = &imp.drag_source.get().unwrap();
let tx = imp.tx.get().unwrap().clone();
drop_controller.connect_drop(
glib::clone!(@weak model, @weak list_view, @weak drag_end, @weak drag_source => @default-return true, move |_self, drop_value, x, y| {
//calculate insertion location
@ -388,10 +384,8 @@ impl DockList {
// dbg!("rejecting drop");
_self.reject();
}
let tx = tx.clone();
glib::MainContext::default().spawn_local(async move {
let _ = tx.send(Event::RefreshFromCache).await;
});
let tx = TX.get().unwrap().clone();
let _ = tx.send(AppListEvent::Refresh);
true
}),
);
@ -419,7 +413,6 @@ impl DockList {
let drag_end = &imp.drag_end_signal;
let drag_cancel = &imp.drag_cancel_signal;
let type_ = *type_;
let tx = imp.tx.get().unwrap().clone();
list_view.add_controller(&drag_source);
drag_source.connect_prepare(glib::clone!(@weak model, @weak list_view, @weak drag_end, @weak drag_cancel => @default-return None, move |self_, x, _y| {
let max_x = list_view.allocated_width();
@ -430,30 +423,25 @@ impl DockList {
let index = (x * n_buckets as f64 / (max_x as f64 + 0.1)) as u32;
if let Some(item) = model.item(index) {
if type_ == DockListType::Saved {
let tx1 = tx.clone();
if let Some(old_handle) = drag_end.replace(Some(self_.connect_drag_end(
glib::clone!(@weak model => move |_self, _drag, _delete_data| {
if _delete_data {
model.remove(index);
let tx = tx1.clone();
glib::MainContext::default().spawn_local(async move {
let _ = tx.send(Event::RefreshFromCache).await;
});
let tx = TX.get().unwrap().clone();
let _ = tx.send(AppListEvent::Refresh);
};
}),
))) {
glib::signal_handler_disconnect(self_, old_handle);
}
let tx = tx.clone();
if let Some(old_handle) = drag_cancel.replace(Some(self_.connect_drag_cancel(
glib::clone!(@weak model => @default-return false, move |_self, _drag, cancel_reason| {
if cancel_reason != gdk::DragCancelReason::UserCancelled {
model.remove(index);
let tx = tx.clone();
glib::MainContext::default().spawn_local(async move {
let _ = tx.send(Event::RefreshFromCache).await;
});
let tx = TX.get().unwrap().clone();
let _ = tx.send(AppListEvent::Refresh);
true
} else {
false
@ -515,11 +503,10 @@ impl DockList {
let popover_menu_index = &imp.popover_menu_index;
let factory = SignalListItemFactory::new();
let model = imp.model.get().expect("Failed to get saved app model.");
let tx = imp.tx.get().unwrap().clone();
let icon_size = imp.config.get().unwrap().get_applet_icon_size();
factory.connect_setup(
glib::clone!(@weak popover_menu_index, @weak model => move |_, list_item| {
let dock_item = DockItem::new(tx.clone(), icon_size);
let dock_item = DockItem::new(icon_size);
dock_item
.connect_local("popover-closed", false, move |_| {
if let Some(old_index) = popover_menu_index.replace(None) {

View file

@ -95,7 +95,7 @@ impl DockObject {
if let Some(path) = path.to_str() {
if let Some(app_info) = gio::DesktopAppInfo::new(path) {
if app_info.should_show()
&& first.description.as_str() == app_info.name().as_str()
&& first.name.as_str() == app_info.name().as_str()
{
return Some(app_info);
}

View file

@ -13,7 +13,7 @@ use once_cell::sync::OnceCell;
use tokio::sync::mpsc::Sender;
use crate::dock_object::DockObject;
use crate::utils::Event;
use crate::utils::AppListEvent;
#[derive(Debug, Default)]
pub struct DockPopover {
@ -26,7 +26,7 @@ pub struct DockPopover {
pub quit_all_item: Rc<RefCell<Button>>,
//TODO figure out how to use lifetimes with glib::wrapper! macro
pub dock_object: Rc<RefCell<Option<DockObject>>>,
pub tx: OnceCell<Sender<Event>>,
pub tx: OnceCell<Sender<AppListEvent>>,
}
#[glib::object_subclass]

View file

@ -9,9 +9,11 @@ use gtk4::{prelude::*, Label};
use gtk4::{Box, Button, Image, ListBox, Orientation};
use tokio::sync::mpsc::Sender;
use crate::wayland::ToplevelEvent;
use crate::{TX, WAYLAND_TX};
use crate::dock_object::DockObject;
use crate::utils::BoxedWindowList;
use crate::utils::Event;
use crate::utils::AppListEvent;
mod imp;
@ -22,10 +24,9 @@ glib::wrapper! {
}
impl DockPopover {
pub fn new(tx: Sender<Event>) -> Self {
pub fn new() -> Self {
let self_: DockPopover = glib::Object::new(&[]).expect("Failed to create DockList");
let imp = imp::DockPopover::from_instance(&self_);
imp.tx.set(tx).unwrap();
self_.layout();
//dnd behavior is different for each type, as well as the data in the model
self_
@ -192,30 +193,24 @@ impl DockPopover {
self_.emit_hide();
}));
let tx = imp.tx.get().unwrap().clone();
let self_ = self.clone();
quit_all_item.connect_clicked(glib::clone!(@weak dock_object => move |_| {
let active = dock_object.property::<BoxedWindowList>("active").0;
for w in active {
let entity = w.entity;
let tx = tx.clone();
glib::MainContext::default().spawn_local(async move {
let _ = tx.clone().send(Event::Close(entity)).await;
});
let t = w.toplevel_handle.clone();
let tx = WAYLAND_TX.get().unwrap().clone();
let _ = tx.clone().send(ToplevelEvent::Close(t));
}
self_.emit_hide();
}));
let tx = imp.tx.get().unwrap().clone();
let self_ = self.clone();
favorite_item.connect_clicked(glib::clone!(@weak dock_object => move |_| {
let saved = dock_object.property::<bool>("saved");
let tx = tx.clone();
glib::MainContext::default().spawn_local(async move {
if let Some(name) = dock_object.get_name() {
let _ = tx.clone().send(Event::Favorite((name, !saved))).await;
}
});
if let Some(name) = dock_object.get_name() {
let tx = TX.get().unwrap().clone();
let _ = tx.clone().send(AppListEvent::Favorite((name, !saved)));
}
self_.emit_hide();
}));
@ -227,16 +222,13 @@ impl DockPopover {
// }),
// );
let tx = imp.tx.get().unwrap().clone();
let self_ = self.clone();
window_listbox.connect_row_activated(
glib::clone!(@weak dock_object => move |_, item| {
let active = dock_object.property::<BoxedWindowList>("active").0;
let entity = active[usize::try_from(item.index()).unwrap()].entity;
let tx = tx.clone();
glib::MainContext::default().spawn_local(async move {
let _ = tx.send(Event::Activate(entity)).await;
});
let t = active[usize::try_from(item.index()).unwrap()].toplevel_handle.clone();
let tx = WAYLAND_TX.get().unwrap().clone();
let _ = tx.send(ToplevelEvent::Activate(t));
self_.emit_hide();
}),
);

View file

@ -1,17 +1,19 @@
// SPDX-License-Identifier: MPL-2.0-only
use apps_window::CosmicAppListWindow;
use calloop::channel::SyncSender;
use dock_list::DockListType;
use dock_object::DockObject;
use gio::{ApplicationFlags, DesktopAppInfo};
use gtk4::gdk::Display;
use gtk4::{glib, prelude::*, CssProvider, StyleContext};
use once_cell::sync::OnceCell;
use wayland::{ToplevelEvent, Toplevel};
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::sync::mpsc;
use utils::{block_on, BoxedWindowList, Event, Item, DEST, PATH};
use utils::{block_on, BoxedWindowList, AppListEvent, DEST, PATH};
mod apps_container;
mod apps_window;
@ -21,9 +23,13 @@ mod dock_object;
mod dock_popover;
mod localize;
mod utils;
mod wayland;
mod wayland_source;
mod config;
const ID: &str = "com.system76.CosmicAppList";
static TX: OnceCell<mpsc::Sender<Event>> = OnceCell::new();
static TX: OnceCell<glib::Sender<AppListEvent>> = OnceCell::new();
static WAYLAND_TX: OnceCell<SyncSender<ToplevelEvent>> = OnceCell::new();
pub fn localize() {
let localizer = crate::localize::localizer();
@ -57,202 +63,206 @@ fn main() {
app.connect_activate(|app| {
load_css();
let (tx, mut rx) = mpsc::channel(100);
let (tx, rx) = glib::MainContext::channel(glib::Priority::default());
let window = CosmicAppListWindow::new(app, tx.clone());
let window = CosmicAppListWindow::new(app);
let apps_container = apps_container::AppsContainer::new();
let wayland_tx = wayland::spawn_toplevels();
let apps_container = apps_container::AppsContainer::new(tx.clone());
let cached_results = Arc::new(Mutex::new(Vec::new()));
WAYLAND_TX.set(wayland_tx).unwrap();
let mut cached_results = Vec::new();
// let zbus_conn = spawn_zbus(tx.clone(), Arc::clone(&cached_results));
TX.set(tx.clone()).unwrap();
let _ = glib::MainContext::default().spawn_local(async move {
while let Some(event) = rx.recv().await {
match event {
Event::Activate(_) => {
// let _activate_window = zbus_conn
// .call_method(Some(DEST), PATH, Some(DEST), "WindowFocus", &((e,)))
// .await
// .expect("Failed to focus selected window");
}
Event::Close(_) => {
// let _activate_window = zbus_conn
// .call_method(Some(DEST), PATH, Some(DEST), "WindowQuit", &((e,)))
// .await
// .expect("Failed to close selected window");
}
Event::Favorite((name, should_favorite)) => {
let saved_app_model = apps_container.model(DockListType::Saved);
let active_app_model = apps_container.model(DockListType::Active);
if should_favorite {
let mut cur: u32 = 0;
let mut index: Option<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_path() == Some(name.clone()) {
cur_dock_object.set_saved(true);
index = Some(cur);
}
rx.attach(None, glib::clone!(@weak window => @default-return glib::prelude::Continue(true), move |event| {
match event {
AppListEvent::Activate(_) => {
// let _activate_window = zbus_conn
// .call_method(Some(DEST), PATH, Some(DEST), "WindowFocus", &((e,)))
// .await
// .expect("Failed to focus selected window");
}
AppListEvent::Close(_) => {
// let _activate_window = zbus_conn
// .call_method(Some(DEST), PATH, Some(DEST), "WindowQuit", &((e,)))
// .await
// .expect("Failed to close selected window");
}
AppListEvent::Favorite((name, should_favorite)) => {
let saved_app_model = apps_container.model(DockListType::Saved);
let active_app_model = apps_container.model(DockListType::Active);
if should_favorite {
let mut cur: u32 = 0;
let mut index: Option<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_path() == Some(name.clone()) {
cur_dock_object.set_saved(true);
index = Some(cur);
}
cur += 1;
}
if let Some(index) = index {
let object = active_app_model.item(index).unwrap();
active_app_model.remove(index);
saved_app_model.append(&object);
}
} else {
let mut cur: u32 = 0;
let mut index: Option<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_path() == Some(name.clone()) {
cur_dock_object.set_saved(false);
index = Some(cur);
}
}
cur += 1;
}
if let Some(index) = index {
let object = saved_app_model.item(index).unwrap();
saved_app_model.remove(index);
active_app_model.append(&object);
}
cur += 1;
}
let _ = tx.send(Event::RefreshFromCache).await;
}
Event::RefreshFromCache => {
// println!("refreshing model from cache");
let cached_results = cached_results.as_ref().lock().unwrap();
let stack_active = cached_results.iter().fold(
BTreeMap::new(),
|mut acc: BTreeMap<String, BoxedWindowList>, elem: &Item| {
if let Some(v) = acc.get_mut(&elem.description) {
v.0.push(elem.clone());
} else {
acc.insert(
elem.description.clone(),
BoxedWindowList(vec![elem.clone()]),
);
if let Some(index) = index {
let object = active_app_model.item(index).unwrap();
active_app_model.remove(index);
saved_app_model.append(&object);
}
} else {
let mut cur: u32 = 0;
let mut index: Option<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_path() == Some(name.clone()) {
cur_dock_object.set_saved(false);
index = Some(cur);
}
acc
},
);
let mut stack_active: Vec<BoxedWindowList> =
stack_active.into_values().collect();
}
cur += 1;
}
if let Some(index) = index {
let object = saved_app_model.item(index).unwrap();
saved_app_model.remove(index);
active_app_model.append(&object);
}
}
let _ = tx.send(AppListEvent::Refresh);
}
AppListEvent::Refresh => {
// println!("refreshing model from cache");
let stack_active = cached_results.iter().fold(
BTreeMap::new(),
|mut acc: BTreeMap<String, BoxedWindowList>, elem: &Toplevel| {
if let Some(v) = acc.get_mut(&elem.name) {
v.0.push(elem.clone());
} else {
acc.insert(
elem.name.clone(),
BoxedWindowList(vec![elem.clone()]),
);
}
acc
},
);
let mut stack_active: Vec<BoxedWindowList> =
stack_active.into_values().collect();
// update active app stacks for saved apps into the saved app model
// then put the rest in the active app model (which doesn't include saved apps)
let saved_app_model = apps_container.model(DockListType::Saved);
// update active app stacks for saved apps into the saved app model
// then put the rest in the active app model (which doesn't include saved apps)
let saved_app_model = apps_container.model(DockListType::Saved);
let mut saved_i: u32 = 0;
while let Some(item) = saved_app_model.item(saved_i) {
if let Ok(dock_obj) = item.downcast::<DockObject>() {
if let Some(cur_app_info) =
dock_obj.property::<Option<DesktopAppInfo>>("appinfo")
let mut saved_i: u32 = 0;
while let Some(item) = saved_app_model.item(saved_i) {
if let Ok(dock_obj) = item.downcast::<DockObject>() {
if let Some(cur_app_info) =
dock_obj.property::<Option<DesktopAppInfo>>("appinfo")
{
if let Some((i, _s)) = stack_active
.iter()
.enumerate()
.find(|(_i, s)| s.0[0].name == cur_app_info.name())
{
if let Some((i, _s)) = stack_active
.iter()
.enumerate()
.find(|(_i, s)| s.0[0].description == cur_app_info.name())
{
// println!(
// "found active saved app {} at {}",
// _s.0[0].name, i
// );
let active = stack_active.remove(i);
dock_obj.set_property("active", active.to_value());
saved_app_model.items_changed(saved_i, 0, 0);
} else if cached_results
.iter()
.any(|s| s.description == cur_app_info.name())
{
dock_obj.set_property(
"active",
BoxedWindowList(Vec::new()).to_value(),
);
saved_app_model.items_changed(saved_i, 0, 0);
}
}
}
saved_i += 1;
}
let active_app_model = apps_container.model(DockListType::Active);
let model_len = active_app_model.n_items();
let new_results: Vec<glib::Object> = stack_active
.into_iter()
.map(|v| DockObject::from_search_results(v).upcast())
.collect();
active_app_model.splice(0, model_len, &new_results[..]);
}
Event::WindowList => {
// sort to make comparison with cache easier
let results = cached_results.as_ref().lock().unwrap();
// build active app stacks for each app
let stack_active = results.iter().fold(
BTreeMap::new(),
|mut acc: BTreeMap<String, BoxedWindowList>, elem| {
if let Some(v) = acc.get_mut(&elem.description) {
v.0.push(elem.clone());
} else {
acc.insert(
elem.description.clone(),
BoxedWindowList(vec![elem.clone()]),
);
}
acc
},
);
let mut stack_active: Vec<BoxedWindowList> =
stack_active.into_values().collect();
// update active app stacks for saved apps into the saved app model
// then put the rest in the active app model (which doesn't include saved apps)
let saved_app_model = apps_container.model(DockListType::Saved);
let mut saved_i: u32 = 0;
while let Some(item) = saved_app_model.item(saved_i) {
if let Ok(dock_obj) = item.downcast::<DockObject>() {
if let Some(cur_app_info) =
dock_obj.property::<Option<DesktopAppInfo>>("appinfo")
// println!(
// "found active saved app {} at {}",
// _s.0[0].name, i
// );
let active = stack_active.remove(i);
dock_obj.set_property("active", active.to_value());
saved_app_model.items_changed(saved_i, 0, 0);
} else if cached_results
.iter()
.any(|s| s.name == cur_app_info.name())
{
if let Some((i, _s)) = stack_active
.iter()
.enumerate()
.find(|(_i, s)| s.0[0].description == cur_app_info.name())
{
// println!("found active saved app {} at {}", s.0[0].name, i);
let active = stack_active.remove(i);
dock_obj.set_property("active", active.to_value());
saved_app_model.items_changed(saved_i, 0, 0);
} else if results
.iter()
.any(|s| s.description == cur_app_info.name())
{
dock_obj.set_property(
"active",
BoxedWindowList(Vec::new()).to_value(),
);
saved_app_model.items_changed(saved_i, 0, 0);
}
dock_obj.set_property(
"active",
BoxedWindowList(Vec::new()).to_value(),
);
saved_app_model.items_changed(saved_i, 0, 0);
}
}
saved_i += 1;
}
let active_app_model = apps_container.model(DockListType::Active);
let model_len = active_app_model.n_items();
let new_results: Vec<glib::Object> = stack_active
.into_iter()
.map(|v| DockObject::from_search_results(v).upcast())
.collect();
active_app_model.splice(0, model_len, &new_results[..]);
saved_i += 1;
}
let active_app_model = apps_container.model(DockListType::Active);
let model_len = active_app_model.n_items();
let new_results: Vec<glib::Object> = stack_active
.into_iter()
.map(|v| DockObject::from_search_results(v).upcast())
.collect();
active_app_model.splice(0, model_len, &new_results[..]);
}
AppListEvent::WindowList(results) => {
// sort to make comparison with cache easier
cached_results = results.clone();
// build active app stacks for each app
let stack_active = results.iter().fold(
BTreeMap::new(),
|mut acc: BTreeMap<String, BoxedWindowList>, elem| {
if let Some(v) = acc.get_mut(&elem.name) {
v.0.push(elem.clone());
} else {
acc.insert(
elem.name.clone(),
BoxedWindowList(vec![elem.clone()]),
);
}
acc
},
);
let mut stack_active: Vec<BoxedWindowList> =
stack_active.into_values().collect();
// update active app stacks for saved apps into the saved app model
// then put the rest in the active app model (which doesn't include saved apps)
let saved_app_model = apps_container.model(DockListType::Saved);
let mut saved_i: u32 = 0;
while let Some(item) = saved_app_model.item(saved_i) {
if let Ok(dock_obj) = item.downcast::<DockObject>() {
if let Some(cur_app_info) =
dock_obj.property::<Option<DesktopAppInfo>>("appinfo")
{
if let Some((i, _s)) = stack_active
.iter()
.enumerate()
.find(|(_i, s)| s.0[0].name == cur_app_info.name())
{
// println!("found active saved app {} at {}", s.0[0].name, i);
let active = stack_active.remove(i);
dock_obj.set_property("active", active.to_value());
saved_app_model.items_changed(saved_i, 0, 0);
} else if results
.iter()
.any(|s| s.name == cur_app_info.name())
{
dock_obj.set_property(
"active",
BoxedWindowList(Vec::new()).to_value(),
);
saved_app_model.items_changed(saved_i, 0, 0);
}
}
}
saved_i += 1;
}
let active_app_model = apps_container.model(DockListType::Active);
let model_len = active_app_model.n_items();
let new_results: Vec<glib::Object> = stack_active
.into_iter()
.map(|v| DockObject::from_search_results(v).upcast())
.collect();
active_app_model.splice(0, model_len, &new_results[..]);
}
}
});
glib::prelude::Continue(true)
}));
window.show();
});
app.run();

View file

@ -3,32 +3,25 @@
use std::path::PathBuf;
use gtk4::glib;
use serde::{Deserialize, Serialize};
use std::future::Future;
use crate::wayland::Toplevel;
pub const DEST: &str = "com.System76.PopShell";
pub const PATH: &str = "/com/System76/PopShell";
#[derive(Debug)]
pub enum Event {
WindowList,
pub enum AppListEvent {
WindowList(Vec<Toplevel>),
Activate((u32, u32)),
Close((u32, u32)),
Favorite((String, bool)),
RefreshFromCache,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Item {
pub(crate) entity: (u32, u32),
pub(crate) name: String,
pub(crate) description: String,
pub(crate) desktop_entry: String,
Refresh,
}
#[derive(Clone, Debug, Default, glib::Boxed)]
#[boxed_type(name = "BoxedWindowList")]
pub struct BoxedWindowList(pub Vec<Item>);
pub struct BoxedWindowList(pub Vec<Toplevel>);
pub fn data_path() -> PathBuf {
let mut path = glib::user_data_dir();

View file

@ -0,0 +1,512 @@
use crate::{
wayland_source::WaylandSource, config::TopLevelFilter, TX, utils::AppListEvent,
};
use cosmic_panel_config::CosmicPanelConfig;
use gtk4::glib;
use std::{
env, os::unix::net::UnixStream, path::PathBuf,time::Duration,
};
use wayland_client::{
event_created_child,
protocol::{
wl_output::{self, WlOutput},
wl_registry,
},
ConnectError, Proxy,
};
use cosmic_protocols::{workspace::v1::client::{
zcosmic_workspace_manager_v1::{self, ZcosmicWorkspaceManagerV1},
zcosmic_workspace_group_handle_v1::{self, ZcosmicWorkspaceGroupHandleV1},
zcosmic_workspace_handle_v1::{self, ZcosmicWorkspaceHandleV1},
}, toplevel_info::v1::client::{zcosmic_toplevel_info_v1::{ZcosmicToplevelInfoV1, self}, zcosmic_toplevel_handle_v1::{ZcosmicToplevelHandleV1, self}}, toplevel_management::v1::client::zcosmic_toplevel_manager_v1::{self, ZcosmicToplevelManagerV1}};
use wayland_client::{Connection, Dispatch, QueueHandle};
use calloop::channel::*;
use crate::config::AppListConfig;
#[derive(Debug, Clone)]
pub enum ToplevelEvent {
Activate(ZcosmicToplevelHandleV1),
Close(ZcosmicToplevelHandleV1),
}
pub fn spawn_toplevels() -> SyncSender<ToplevelEvent> {
let config = AppListConfig::load().unwrap_or_default();
let (workspaces_tx, workspaces_rx) = calloop::channel::sync_channel(100);
if let Ok(Ok(conn)) = std::env::var("WAYLAND_DISPLAY")
.map_err(anyhow::Error::msg)
.map(|display_str| {
let mut socket_path = env::var_os("XDG_RUNTIME_DIR")
.map(Into::<PathBuf>::into)
.ok_or(ConnectError::NoCompositor)?;
socket_path.push(display_str);
Ok(UnixStream::connect(socket_path).map_err(|_| ConnectError::NoCompositor)?)
})
.and_then(|s| s.map(|s| Connection::from_socket(s).map_err(anyhow::Error::msg)))
{
std::thread::spawn(move || {
let output = match config.filter_top_levels {
Some(TopLevelFilter::ConfiguredOutput) => CosmicPanelConfig::load_from_env()
.ok().map(|c| c.output),
_ => None,
};
let mut event_loop = calloop::EventLoop::<State>::try_new().unwrap();
let loop_handle = event_loop.handle();
let event_queue = conn.new_event_queue::<State>();
let qhandle = event_queue.handle();
WaylandSource::new(event_queue)
.expect("Failed to create wayland source")
.insert(loop_handle)
.unwrap();
let display = conn.display();
display.get_registry(&qhandle, ()).unwrap();
let mut state = State {
workspace_manager: None,
workspace_groups: Vec::new(),
toplevel_info: None,
toplevel_manager: None,
config,
configured_output: output,
expected_output: None,
running: true,
toplevels: vec![]
};
let loop_handle = event_loop.handle();
loop_handle
.insert_source(workspaces_rx, |e, _, state| match e {
Event::Msg(ToplevelEvent::Activate(_t)) => {
todo!()
}
Event::Msg(ToplevelEvent::Close(_t)) => {
todo!()
}
Event::Closed => {
if let Some(workspace_manager) = &mut state.workspace_manager {
for g in &mut state.workspace_groups {
g.workspace_group_handle.destroy();
}
workspace_manager.stop();
}
if let Some(toplevel_manager) = &mut state.toplevel_manager {
toplevel_manager.destroy();
}
if let Some(toplevel_info) = &mut state.toplevel_info {
for toplevel in &state.toplevels {
toplevel.toplevel_handle.destroy();
}
toplevel_info.stop();
}
}
})
.unwrap();
while state.running {
event_loop
.dispatch(Duration::from_millis(16), &mut state)
.unwrap();
}
});
} else {
eprintln!("ENV variable WAYLAND_DISPLAY is missing. Exiting...");
std::process::exit(1);
}
workspaces_tx
}
#[derive(Debug, Clone)]
pub struct State {
running: bool,
config: AppListConfig,
configured_output: Option<String>,
expected_output: Option<WlOutput>,
workspace_manager: Option<ZcosmicWorkspaceManagerV1>,
workspace_groups: Vec<WorkspaceGroup>,
toplevel_info: Option<ZcosmicToplevelInfoV1>,
toplevel_manager: Option<ZcosmicToplevelManagerV1>,
toplevels: Vec<Toplevel>,
}
impl State {
pub fn workspace_list(&self) -> impl Iterator<Item = (String, u32)> + '_ {
self.workspace_groups
.iter()
.filter_map(|g| {
if g.output == self.expected_output {
Some(g.workspaces.iter().map(|w| (w.name.clone(), match &w.states {
x if x.contains(&zcosmic_workspace_handle_v1::State::Active) => 0,
x if x.contains(&zcosmic_workspace_handle_v1::State::Urgent) => 1,
x if x.contains(&zcosmic_workspace_handle_v1::State::Hidden) => 2,
_ => 3,
})))
} else {
None
}
})
.flatten()
}
}
#[derive(Debug, Clone)]
pub struct Toplevel {
pub name: String,
pub app_id: String,
pub toplevel_handle: ZcosmicToplevelHandleV1,
pub states: Vec<zcosmic_toplevel_handle_v1::State>,
pub output: Option<WlOutput>,
pub workspace: Option<ZcosmicWorkspaceHandleV1>,
}
#[derive(Debug, Clone)]
struct WorkspaceGroup {
workspace_group_handle: ZcosmicWorkspaceGroupHandleV1,
output: Option<WlOutput>,
workspaces: Vec<Workspace>,
}
#[derive(Debug, Clone)]
struct Workspace {
workspace_handle: ZcosmicWorkspaceHandleV1,
name: String,
coordinates: Vec<u32>,
states: Vec<zcosmic_workspace_handle_v1::State>,
}
impl Dispatch<wl_registry::WlRegistry, ()> for State {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: wl_registry::Event,
_: &(),
_: &Connection,
qh: &QueueHandle<Self>,
) {
if let wl_registry::Event::Global {
name,
interface,
version,
} = event
{
match &interface[..] {
"zcosmic_workspace_info_v1" => {
let ti = registry
.bind::<ZcosmicToplevelInfoV1, _, _>(
name,
1,
qh,
(),
)
.unwrap();
state.toplevel_info = Some(ti);
}
"zcosmic_toplevel_manager_v1" => {
let tm = registry
.bind::<ZcosmicToplevelManagerV1, _, _>(
name,
1,
qh,
(),
)
.unwrap();
state.toplevel_manager = Some(tm);
}
"zcosmic_toplevel_manager_v1" => {
let workspace_manager = registry
.bind::<ZcosmicWorkspaceManagerV1, _, _>(
name,
1,
qh,
(),
)
.unwrap();
state.workspace_manager = Some(workspace_manager);
}
"wl_output" => {
registry.bind::<WlOutput, _, _>(name, 1, qh, ()).unwrap();
}
_ => {}
}
}
}
}
impl Dispatch<ZcosmicToplevelInfoV1, ()> for State {
fn event(
state: &mut Self,
_: &ZcosmicToplevelInfoV1,
event: <ZcosmicToplevelInfoV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
dbg!(&event);
match event {
zcosmic_toplevel_info_v1::Event::Toplevel { toplevel } => {
state.toplevels.push(Toplevel { name: "".into(), app_id: "".into(), toplevel_handle: toplevel, states: vec![], output: None, workspace: None });
},
zcosmic_toplevel_info_v1::Event::Finished => {
dbg!(&state.toplevels);
let tx = TX.get().unwrap().clone();
let _ = tx.send(AppListEvent::WindowList(state.toplevels.iter().filter(|t| {
match state.config.filter_top_levels {
Some(TopLevelFilter::ActiveWorkspace) => true,
_ => false,
}
}).cloned().collect()));
},
_ => {},
}
}
}
impl Dispatch<ZcosmicToplevelManagerV1, ()> for State {
fn event(
_: &mut Self,
_: &ZcosmicToplevelManagerV1,
event: <ZcosmicToplevelManagerV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
match event {
zcosmic_toplevel_manager_v1::Event::Capabilities { .. } => {
// TODO capabilities affect what is shown to user in applet
},
_ => {},
}
}
}
impl Dispatch<ZcosmicToplevelHandleV1, ()> for State {
fn event(
state: &mut Self,
p: &ZcosmicToplevelHandleV1,
event: <ZcosmicToplevelHandleV1 as Proxy>::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
dbg!(&event);
match event {
zcosmic_toplevel_handle_v1::Event::Closed => {
if let Some(i) = state.toplevels.iter().position(|t| &t.toplevel_handle == p) {
state.toplevels.remove(i);
}
},
zcosmic_toplevel_handle_v1::Event::Done => {},
zcosmic_toplevel_handle_v1::Event::Title { title } => {
if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) {
i.name = title;
}
},
zcosmic_toplevel_handle_v1::Event::AppId { app_id } => {
if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) {
i.app_id = app_id;
}
},
zcosmic_toplevel_handle_v1::Event::OutputEnter { output } => {
if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) {
i.output.replace(output);
}
},
zcosmic_toplevel_handle_v1::Event::OutputLeave { output } => {
if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p && t.output.as_ref() == Some(&output)) {
i.output.take();
}
},
zcosmic_toplevel_handle_v1::Event::WorkspaceEnter { workspace } => {
if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) {
i.workspace.replace(workspace);
}
},
zcosmic_toplevel_handle_v1::Event::WorkspaceLeave { workspace } => {
if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p && t.workspace.as_ref() == Some(&workspace)) {
i.workspace.take();
}
},
zcosmic_toplevel_handle_v1::Event::State { state: t_state } => {
if let Some(i) = state.toplevels.iter_mut().find(|t| &t.toplevel_handle == p) {
i.states = t_state.chunks(4).map(|chunk| zcosmic_toplevel_handle_v1::State::try_from(u32::from_ne_bytes(chunk.try_into().unwrap())).unwrap()).collect();
}
},
_ => todo!(),
}
}
}
impl Dispatch<ZcosmicWorkspaceManagerV1, ()> for State {
fn event(
state: &mut Self,
_: &ZcosmicWorkspaceManagerV1,
event: zcosmic_workspace_manager_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
match event {
zcosmic_workspace_manager_v1::Event::WorkspaceGroup { workspace_group } => {
state.workspace_groups.push(WorkspaceGroup {
workspace_group_handle: workspace_group,
output: None,
workspaces: Vec::new(),
});
}
zcosmic_workspace_manager_v1::Event::Done => {
for group in &mut state.workspace_groups {
group.workspaces.sort_by(|w1, w2| {
w1.coordinates.iter().zip(w2.coordinates.iter())
.skip_while(|(coord1, coord2)| coord1 == coord2)
.next()
.map(|(coord1, coord2)| coord1.cmp(coord2))
.unwrap_or(std::cmp::Ordering::Equal)
});
}
}
zcosmic_workspace_manager_v1::Event::Finished => {
state.workspace_manager.take();
}
_ => {}
}
}
event_created_child!(State, ZcosmicWorkspaceManagerV1, [
0 => (ZcosmicWorkspaceGroupHandleV1, ())
]);
}
impl Dispatch<ZcosmicWorkspaceGroupHandleV1, ()> for State {
fn event(
state: &mut Self,
group: &ZcosmicWorkspaceGroupHandleV1,
event: zcosmic_workspace_group_handle_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
match event {
zcosmic_workspace_group_handle_v1::Event::OutputEnter { output } => {
if let Some(group) = state
.workspace_groups
.iter_mut()
.find(|g| &g.workspace_group_handle == group)
{
group.output = Some(output);
}
}
zcosmic_workspace_group_handle_v1::Event::OutputLeave { output } => {
if let Some(group) = state.workspace_groups.iter_mut().find(|g| {
&g.workspace_group_handle == group && g.output.as_ref() == Some(&output)
}) {
group.output = None;
}
}
zcosmic_workspace_group_handle_v1::Event::Workspace { workspace } => {
if let Some(group) = state
.workspace_groups
.iter_mut()
.find(|g| &g.workspace_group_handle == group)
{
group.workspaces.push(Workspace {
workspace_handle: workspace,
name: String::new(),
coordinates: Vec::new(),
states: Vec::new(),
})
}
}
zcosmic_workspace_group_handle_v1::Event::Remove => {
if let Some(group) = state
.workspace_groups
.iter()
.position(|g| &g.workspace_group_handle == group)
{
state.workspace_groups.remove(group);
}
}
_ => {}
}
}
event_created_child!(State, ZcosmicWorkspaceGroupHandleV1, [
3 => (ZcosmicWorkspaceHandleV1, ())
]);
}
impl Dispatch<ZcosmicWorkspaceHandleV1, ()> for State {
fn event(
state: &mut Self,
workspace: &ZcosmicWorkspaceHandleV1,
event: zcosmic_workspace_handle_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
match event {
zcosmic_workspace_handle_v1::Event::Name { name } => {
if let Some(w) = state.workspace_groups.iter_mut().find_map(|g| {
g.workspaces
.iter_mut()
.find(|w| &w.workspace_handle == workspace)
}) {
w.name = name;
}
}
zcosmic_workspace_handle_v1::Event::Coordinates { coordinates } => {
if let Some(w) = state.workspace_groups.iter_mut().find_map(|g| {
g.workspaces
.iter_mut()
.find(|w| &w.workspace_handle == workspace)
}) {
// wayland is host byte order
w.coordinates = coordinates.chunks(4).map(|chunk| u32::from_ne_bytes(chunk.try_into().unwrap())).collect();
}
}
zcosmic_workspace_handle_v1::Event::State { state: workspace_state } => {
if let Some(w) = state.workspace_groups.iter_mut().find_map(|g| {
g.workspaces
.iter_mut()
.find(|w| &w.workspace_handle == workspace)
}) {
// wayland is host byte order
w.states = workspace_state.chunks(4).map(|chunk| zcosmic_workspace_handle_v1::State::try_from(u32::from_ne_bytes(chunk.try_into().unwrap())).unwrap()).collect();
}
}
zcosmic_workspace_handle_v1::Event::Remove => {
if let Some((g, w_i)) = state.workspace_groups.iter_mut().find_map(|g| {
g.workspaces
.iter_mut()
.position(|w| &w.workspace_handle == workspace)
.map(|p| (g, p))
}) {
g.workspaces.remove(w_i);
}
}
_ => {}
}
}
}
impl Dispatch<WlOutput, ()> for State {
fn event(
state: &mut Self,
o: &WlOutput,
e: wl_output::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
match e {
wl_output::Event::Name { name } if Some(&name) == state.configured_output.as_ref() => {
state.expected_output.replace(o.clone());
}
_ => {} // ignored
}
}
}

View file

@ -0,0 +1,219 @@
//! Utilities for using an [`EventQueue`] from wayland-client with an event loop that performs polling with
//! [`calloop`](https://crates.io/crates/calloop).
use std::{io, os::unix::prelude::RawFd};
use calloop::{
generic::Generic, EventSource, InsertError, Interest, LoopHandle, Mode, Poll, PostAction,
Readiness, RegistrationToken, Token, TokenFactory,
};
use nix::errno::Errno;
use wayland_backend::client::{ReadEventsGuard, WaylandError};
use wayland_client::{DispatchError, EventQueue};
/// An adapter to insert an [`EventQueue`] into a calloop [`EventLoop`](calloop::EventLoop).
///
/// This type implements [`EventSource`] which generates an event whenever events on the display need to be
/// dispatched. The event queue available in the callback calloop registers may be used to dispatch pending
/// events using [`EventQueue::dispatch_pending`].
///
/// [`WaylandSource::insert`] can be used to insert this source into an event loop and automatically dispatch
/// pending events on the display.
#[derive(Debug)]
pub struct WaylandSource<D> {
queue: EventQueue<D>,
fd: Generic<RawFd>,
read_guard: Option<ReadEventsGuard>,
}
impl<D> WaylandSource<D> {
/// Wrap an [`EventQueue`] as a [`WaylandSource`].
pub fn new(queue: EventQueue<D>) -> Result<WaylandSource<D>, WaylandError> {
let guard = queue.prepare_read()?;
let fd = Generic::new(guard.connection_fd(), Interest::READ, Mode::Level);
drop(guard);
Ok(WaylandSource {
queue,
fd,
read_guard: None,
})
}
/// Access the underlying event queue
///
/// Note that you should be careful when interacting with it if you invoke methods that
/// interact with the wayland socket (such as `dispatch()` or `prepare_read()`). These may
/// interfere with the proper waking up of this event source in the event loop.
pub fn queue(&mut self) -> &mut EventQueue<D> {
&mut self.queue
}
/// Insert this source into the given event loop.
///
/// This adapter will pass the event loop's shared data as the `D` type for the event loop.
pub fn insert(self, handle: LoopHandle<D>) -> Result<RegistrationToken, InsertError<Self>>
where
D: 'static,
{
handle.insert_source(self, |_, queue, data| queue.dispatch_pending(data))
}
}
impl<D> EventSource for WaylandSource<D> {
type Event = ();
/// The underlying event queue.
///
/// You should call [`EventQueue::dispatch_pending`] inside your callback using this queue.
type Metadata = EventQueue<D>;
type Ret = Result<usize, DispatchError>;
type Error = calloop::Error;
fn process_events<F>(
&mut self,
readiness: Readiness,
token: Token,
mut callback: F,
) -> Result<PostAction, Self::Error>
where
F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
{
let queue = &mut self.queue;
let read_guard = &mut self.read_guard;
let action = self.fd.process_events(readiness, token, |_, _| {
// 1. read events from the socket if any are available
if let Some(guard) = read_guard.take() {
// might be None if some other thread read events before us, concurrently
if let Err(WaylandError::Io(err)) = guard.read() {
if err.kind() != io::ErrorKind::WouldBlock {
return Err(err);
}
}
}
// 2. dispatch any pending events in the queue
// This is done to ensure we are not waiting for messages that are already in the buffer.
Self::loop_callback_pending(queue, &mut callback)?;
*read_guard = Some(Self::prepare_read(queue)?);
// 3. Once dispatching is finished, flush the responses to the compositor
if let Err(WaylandError::Io(e)) = queue.flush() {
if e.kind() != io::ErrorKind::WouldBlock {
// in case of error, forward it and fast-exit
return Err(e);
}
// WouldBlock error means the compositor could not process all our messages
// quickly. Either it is slowed down or we are a spammer.
// Should not really happen, if it does we do nothing and will flush again later
}
Ok(PostAction::Continue)
})?;
Ok(action)
}
fn register(
&mut self,
poll: &mut Poll,
token_factory: &mut TokenFactory,
) -> calloop::Result<()> {
self.fd.register(poll, token_factory)
}
fn reregister(
&mut self,
poll: &mut Poll,
token_factory: &mut TokenFactory,
) -> calloop::Result<()> {
self.fd.reregister(poll, token_factory)
}
fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
self.fd.unregister(poll)
}
fn pre_run<F>(&mut self, mut callback: F) -> calloop::Result<()>
where
F: FnMut((), &mut Self::Metadata) -> Self::Ret,
{
debug_assert!(self.read_guard.is_none());
// flush the display before starting to poll
if let Err(WaylandError::Io(err)) = self.queue.flush() {
if err.kind() != io::ErrorKind::WouldBlock {
// in case of error, don't prepare a read, if the error is persistent, it'll trigger in other
// wayland methods anyway
log::error!("Error trying to flush the wayland display: {}", err);
return Err(err.into());
}
}
// ensure we are not waiting for messages that are already in the buffer.
Self::loop_callback_pending(&mut self.queue, &mut callback)?;
self.read_guard = Some(Self::prepare_read(&mut self.queue)?);
Ok(())
}
fn post_run<F>(&mut self, _: F) -> calloop::Result<()>
where
F: FnMut((), &mut Self::Metadata) -> Self::Ret,
{
// Drop implementation of ReadEventsGuard will do cleanup
self.read_guard.take();
Ok(())
}
}
impl<D> WaylandSource<D> {
/// Loop over the callback until all pending messages have been dispatched.
fn loop_callback_pending<F>(queue: &mut EventQueue<D>, callback: &mut F) -> io::Result<()>
where
F: FnMut((), &mut EventQueue<D>) -> Result<usize, DispatchError>,
{
// Loop on the callback until no pending events are left.
loop {
match callback((), queue) {
// No more pending events.
Ok(0) => break Ok(()),
Ok(_) => continue,
Err(DispatchError::Backend(WaylandError::Io(err))) => {
return Err(err);
}
Err(DispatchError::Backend(WaylandError::Protocol(err))) => {
log::error!("Protocol error received on display: {}", err);
break Err(Errno::EPROTO.into());
}
Err(DispatchError::BadMessage { msg, interface }) => {
log::error!(
"Bad message on interface \"{}\": (opcode: {}, args: {:?})",
interface,
msg.opcode,
msg.args,
);
break Err(Errno::EPROTO.into());
}
}
}
}
fn prepare_read(queue: &mut EventQueue<D>) -> io::Result<ReadEventsGuard> {
queue.prepare_read().map_err(|err| match err {
WaylandError::Io(err) => err,
WaylandError::Protocol(err) => {
log::error!("Protocol error received on display: {}", err);
Errno::EPROTO.into()
}
})
}
}