feat: wlroots-agnostic support for focused module

This commit is contained in:
Jake Stanger 2022-10-04 23:26:07 +01:00
parent b188bc7146
commit 324f00cdf9
No known key found for this signature in database
GPG Key ID: C51FC8F9CB0BEA61
9 changed files with 597 additions and 94 deletions

110
Cargo.lock generated
View File

@ -280,6 +280,19 @@ dependencies = [
"system-deps",
]
[[package]]
name = "calloop"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a22a6a8f622f797120d452c630b0ab12e1331a1a753e2039ce7868d4ac77b4ee"
dependencies = [
"log",
"nix 0.24.2",
"slotmap",
"thiserror",
"vec_map",
]
[[package]]
name = "cc"
version = "1.0.73"
@ -594,6 +607,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "dlib"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794"
dependencies = [
"libloading",
]
[[package]]
name = "downcast-rs"
version = "1.2.0"
@ -1128,6 +1150,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yaml",
"smithay-client-toolkit",
"stray",
"strip-ansi-escapes",
"swayipc-async",
@ -1181,6 +1204,16 @@ version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "libloading"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "lock_api"
version = "0.4.7"
@ -1215,6 +1248,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memmap2"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498"
dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.6.5"
@ -1740,6 +1782,12 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -1863,12 +1911,40 @@ dependencies = [
"autocfg",
]
[[package]]
name = "slotmap"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
dependencies = [
"version_check",
]
[[package]]
name = "smallvec"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
[[package]]
name = "smithay-client-toolkit"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454"
dependencies = [
"bitflags",
"calloop",
"dlib",
"lazy_static",
"log",
"memmap2",
"nix 0.24.2",
"pkg-config",
"wayland-client",
"wayland-cursor",
"wayland-protocols",
]
[[package]]
name = "socket2"
version = "0.4.4"
@ -2061,9 +2137,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.21.0"
version = "1.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42"
checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
dependencies = [
"autocfg",
"bytes",
@ -2071,7 +2147,6 @@ dependencies = [
"memchr",
"mio",
"num_cpus",
"once_cell",
"pin-project-lite",
"socket2",
"tokio-macros",
@ -2238,6 +2313,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version-compare"
version = "0.1.0"
@ -2310,6 +2391,7 @@ dependencies = [
"downcast-rs",
"libc",
"nix 0.24.2",
"scoped-tls",
"wayland-commons",
"wayland-scanner",
"wayland-sys",
@ -2327,6 +2409,17 @@ dependencies = [
"wayland-sys",
]
[[package]]
name = "wayland-cursor"
version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
dependencies = [
"nix 0.24.2",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-protocols"
version = "0.29.5"
@ -2356,6 +2449,8 @@ version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
dependencies = [
"dlib",
"lazy_static",
"pkg-config",
]
@ -2442,6 +2537,15 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "xcursor"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7"
dependencies = [
"nom",
]
[[package]]
name = "xml-rs"
version = "0.8.4"

View File

@ -12,7 +12,7 @@ derive_builder = "0.11.2"
gtk = "0.15.5"
gtk-layer-shell = "0.4.1"
glib = "0.15.12"
tokio = { version = "1.21.0", features = ["macros", "rt-multi-thread", "time"] }
tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time"] }
tracing = "0.1.36"
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
tracing-error = "0.2.0"
@ -37,4 +37,5 @@ mpd_client = "1.0.0"
swayipc-async = { git = "https://github.com/JakeStanger/swayipc-rs.git", branch = "feat/derive-clone" }
sysinfo = "0.26.2"
wayland-client = "0.29.5"
wayland-protocols = { version = "0.29.5", features=["unstable_protocols", "client"] }
wayland-protocols = { version = "0.29.5", features=["unstable_protocols", "client"] }
smithay-client-toolkit = "0.16.0"

View File

@ -28,6 +28,7 @@ use tokio::task::block_in_place;
use crate::logging::install_tracing;
use tracing::{debug, error, info};
use wayland::WaylandClient;
const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -47,6 +48,8 @@ async fn main() -> Result<()> {
info!("Ironbar version {}", VERSION);
info!("Starting application");
let wayland_client = wayland::get_client().await;
let app = Application::builder()
.application_id("dev.jstanger.ironbar")
.build();
@ -70,7 +73,7 @@ async fn main() -> Result<()> {
};
debug!("Loaded config file");
if let Err(err) = await_sync(create_bars(app, &display, &config)) {
if let Err(err) = await_sync(create_bars(app, &display, wayland_client, &config)) {
error!("{:?}", err);
exit(2);
}
@ -100,8 +103,8 @@ async fn main() -> Result<()> {
}
/// Creates each of the bars across each of the (configured) outputs.
async fn create_bars(app: &Application, display: &Display, config: &Config) -> Result<()> {
let outputs = wayland::get_output_names();
async fn create_bars(app: &Application, display: &Display, wl: &WaylandClient, config: &Config) -> Result<()> {
let outputs = wl.outputs.as_slice();
debug!("Received {} outputs from Wayland", outputs.len());
debug!("Output names: {:?}", outputs);
@ -110,7 +113,8 @@ async fn create_bars(app: &Application, display: &Display, config: &Config) -> R
for i in 0..num_monitors {
let monitor = display.monitor(i).ok_or_else(|| Report::msg("GTK and Sway are reporting a different number of outputs - this is a severe bug and should never happen"))?;
let monitor_name = outputs.get(i as usize).ok_or_else(|| Report::msg("GTK and Sway are reporting a different set of outputs - this is a severe bug and should never happen"))?;
let output = outputs.get(i as usize).ok_or_else(|| Report::msg("GTK and Sway are reporting a different set of outputs - this is a severe bug and should never happen"))?;
let monitor_name = &output.name;
info!("Creating bar on '{}'", monitor_name);

View File

@ -1,16 +1,13 @@
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::sway::node::{get_node_id, get_open_windows};
use crate::sway::{get_client, get_sub_client};
use crate::{await_sync, icon};
use crate::wayland::{ToplevelChange};
use crate::{await_sync, icon, wayland};
use color_eyre::Result;
use glib::Continue;
use gtk::prelude::*;
use gtk::{IconTheme, Image, Label, Orientation};
use serde::Deserialize;
use swayipc_async::WindowChange;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tracing::trace;
#[derive(Debug, Deserialize, Clone)]
pub struct FocusedModule {
@ -43,50 +40,42 @@ impl Module<gtk::Box> for FocusedModule {
_rx: Receiver<Self::ReceiveMessage>,
) -> Result<()> {
let focused = await_sync(async {
let sway = get_client().await;
let mut sway = sway.lock().await;
get_open_windows(&mut sway)
.await
.expect("Failed to get open windows")
.into_iter()
.find(|node| node.focused)
let wl = wayland::get_client().await;
let toplevels = wl
.toplevels
.read()
.expect("Failed to get read lock on toplevels")
.clone();
toplevels.into_iter().find(|top| top.active)
});
if let Some(node) = focused {
let id = get_node_id(&node);
let name = node.name.as_deref().unwrap_or(id);
if let Some(top) = focused {
tx.try_send(ModuleUpdateEvent::Update((
name.to_string(),
id.to_string(),
top.title.clone(),
top.app_id
)))?;
}
spawn(async move {
let mut srx = {
let sway = get_sub_client();
sway.subscribe_window()
let mut wlrx = {
let wl = wayland::get_client().await;
wl.subscribe_toplevels()
};
trace!("Set up Sway window subscription");
while let Ok(payload) = srx.recv().await {
let update = match payload.change {
WindowChange::Focus => true,
WindowChange::Title => payload.container.focused,
_ => false,
while let Ok(event) = wlrx.recv().await {
let update = match event.change {
ToplevelChange::Focus(focus) => focus,
ToplevelChange::Title(_) => event.toplevel.active,
_ => false
};
if update {
let node = payload.container;
let id = get_node_id(&node);
let name = node.name.as_deref().unwrap_or(id);
tx.try_send(ModuleUpdateEvent::Update((
name.to_string(),
id.to_string(),
tx.send(ModuleUpdateEvent::Update((
event.toplevel.title,
event.toplevel.app_id,
)))
.await
.expect("Failed to send focus update");
}
}

View File

@ -1,49 +0,0 @@
use std::cell::RefCell;
use std::rc::Rc;
use wayland_client::protocol::wl_output::{self, Event};
use wayland_client::{global_filter, Display as WlDisplay, GlobalManager, Main};
pub fn get_output_names() -> Vec<String> {
// Connect to the server
let display = WlDisplay::connect_to_env().unwrap();
let mut event_queue = display.create_event_queue();
let attached_display = (*display).clone().attach(event_queue.token());
let outputs = Rc::new(RefCell::new(Vec::<String>::new()));
let _globals = {
let outputs = outputs.clone();
GlobalManager::new_with_cb(&attached_display, {
global_filter!([
wl_output::WlOutput,
4,
move |output: Main<wl_output::WlOutput>, _: DispatchData| {
let outputs = outputs.clone();
output.quick_assign(move |_, event, _| match event {
Event::Name { name: title } => {
let outputs = outputs.clone();
outputs.as_ref().borrow_mut().push(title);
}
_ => {}
})
}
])
})
};
// A roundtrip synchronization to make sure the server received our registry
// creation and sent us the global list
event_queue
.sync_roundtrip(&mut (), |_, _, _| unreachable!())
.unwrap();
// for some reason we need to call this twice?
event_queue
.sync_roundtrip(&mut (), |_, _, _| unreachable!())
.unwrap();
outputs.take()
}

108
src/wayland/client.rs Normal file
View File

@ -0,0 +1,108 @@
use std::sync::{Arc, RwLock};
use super::{Env, ToplevelHandler};
use crate::wayland::toplevel_manager::listen_for_toplevels;
use smithay_client_toolkit::environment::Environment;
use smithay_client_toolkit::output::{with_output_info, OutputInfo};
use smithay_client_toolkit::reexports::calloop;
use smithay_client_toolkit::{new_default_environment, WaylandSource};
use tokio::sync::{broadcast, oneshot};
use tokio::task::spawn_blocking;
use tracing::{trace};
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1;
use crate::collection::Collection;
use crate::wayland::toplevel::{ToplevelEvent, ToplevelInfo};
use crate::wayland::ToplevelChange;
pub struct WaylandClient {
pub outputs: Vec<OutputInfo>,
pub toplevels: Arc<RwLock<Collection<String, ToplevelInfo>>>,
toplevel_tx: broadcast::Sender<ToplevelEvent>,
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
}
impl WaylandClient {
pub(super) async fn new() -> Self {
let (output_tx, output_rx) = oneshot::channel();
let (toplevel_tx, toplevel_rx) = broadcast::channel(32);
let toplevel_tx2 = toplevel_tx.clone();
let toplevels = Arc::new(RwLock::new(Collection::new()));
let toplevels2 = toplevels.clone();
// `queue` is not send so we need to handle everything inside the task
spawn_blocking(move || {
let (env, _display, queue) =
new_default_environment!(Env, fields = [toplevel: ToplevelHandler::init()])
.expect("Failed to connect to Wayland compositor");
let outputs = Self::get_outputs(&env);
output_tx
.send(outputs)
.expect("Failed to send outputs out of task");
let _toplevel_manager = env.require_global::<ZwlrForeignToplevelManagerV1>();
let _listener = listen_for_toplevels(env, move |_handle, event, _ddata| {
trace!("Received toplevel event: {:?}", event);
if event.change != ToplevelChange::Close {
toplevels2
.write()
.expect("Failed to get write lock on toplevels")
.insert(event.toplevel.app_id.clone(), event.toplevel.clone());
} else {
toplevels2
.write()
.expect("Failed to get write lock on toplevels")
.remove(&event.toplevel.app_id);
}
toplevel_tx2
.send(event)
.expect("Failed to send toplevel event");
});
let mut event_loop = calloop::EventLoop::<()>::try_new().unwrap();
WaylandSource::new(queue)
.quick_insert(event_loop.handle())
.unwrap();
loop {
event_loop.dispatch(None, &mut ()).unwrap();
}
});
let outputs = output_rx
.await
.expect("Failed to receive outputs from task");
// spawn(async move {
// println!("start");
// while let Ok(ev) = toplevel_rx.recv().await {
// println!("recv {:?}", ev)
// }
// println!("stop");
// });
Self {
outputs,
toplevels,
toplevel_tx,
_toplevel_rx: toplevel_rx,
}
}
pub fn subscribe_toplevels(&self) -> broadcast::Receiver<ToplevelEvent> {
self.toplevel_tx.subscribe()
}
fn get_outputs(env: &Environment<Env>) -> Vec<OutputInfo> {
let outputs = env.get_all_outputs();
outputs
.iter()
.filter_map(|output| with_output_info(output, |info| info.clone()))
.collect()
}
}

54
src/wayland/mod.rs Normal file
View File

@ -0,0 +1,54 @@
mod client;
mod toplevel;
mod toplevel_manager;
extern crate smithay_client_toolkit as sctk;
use self::toplevel_manager::ToplevelHandler;
pub use crate::wayland::toplevel::{ToplevelChange, ToplevelEvent, ToplevelInfo};
use crate::wayland::toplevel_manager::{ToplevelHandling, ToplevelStatusListener};
use async_once::AsyncOnce;
use lazy_static::lazy_static;
use wayland_client::{Attached, DispatchData, Interface};
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1,
};
pub use client::WaylandClient;
/// A utility for lazy-loading globals.
/// Taken from smithay_client_toolkit where it's not exposed
#[derive(Debug)]
enum LazyGlobal<I: Interface> {
Unknown,
Seen { id: u32, version: u32 },
Bound(Attached<I>),
}
sctk::default_environment!(Env,
fields = [
toplevel: ToplevelHandler
],
singles = [
ZwlrForeignToplevelManagerV1 => toplevel
],
);
impl ToplevelHandling for Env {
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
where
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
{
self.toplevel.listen(f)
}
}
lazy_static! {
static ref CLIENT: AsyncOnce<WaylandClient> =
AsyncOnce::new(async { WaylandClient::new().await });
}
pub async fn get_client() -> &'static WaylandClient {
CLIENT.get().await
}

126
src/wayland/toplevel.rs Normal file
View File

@ -0,0 +1,126 @@
use std::collections::HashSet;
use std::sync::{Arc, RwLock};
use wayland_client::{DispatchData, Main};
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{Event, ZwlrForeignToplevelHandleV1};
const STATE_ACTIVE: u32 = 2;
const STATE_FULLSCREEN: u32 = 3;
#[derive(Debug, Clone, Default)]
pub struct ToplevelInfo {
pub app_id: String,
pub title: String,
pub active: bool,
pub fullscreen: bool,
ready: bool,
}
pub struct Toplevel;
#[derive(Debug, Clone)]
pub struct ToplevelEvent {
pub toplevel: ToplevelInfo,
pub change: ToplevelChange,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ToplevelChange {
New,
Close,
Title(String),
Focus(bool),
Fullscreen(bool),
}
fn toplevel_implem<F>(event: Event, info: &mut ToplevelInfo, implem: &mut F, ddata: DispatchData)
where
F: FnMut(ToplevelEvent, DispatchData),
{
let change = match event {
Event::AppId { app_id } => {
info.app_id = app_id;
None
}
Event::Title { title } => {
info.title = title.clone();
if info.ready {
Some(ToplevelChange::Title(title))
} else {
None
}
}
Event::State { state } => {
// state is received as a `Vec<u8>` where every 4 bytes make up a `u32`
// the u32 then represents a value in the `State` enum.
assert_eq!(state.len() % 4, 0);
let state = (0..state.len() / 4)
.map(|i| {
let slice: [u8; 4] = state[i * 4..i * 4 + 4]
.try_into()
.expect("Received invalid state length");
u32::from_le_bytes(slice)
})
.collect::<HashSet<_>>();
let new_active = state.contains(&STATE_ACTIVE);
let new_fullscreen = state.contains(&STATE_FULLSCREEN);
let change = if info.ready && new_active != info.active {
Some(ToplevelChange::Focus(new_active))
} else if info.ready && new_fullscreen != info.fullscreen {
Some(ToplevelChange::Fullscreen(new_fullscreen))
} else {
None
};
info.active = new_active;
info.fullscreen = new_fullscreen;
change
}
Event::Closed => Some(ToplevelChange::Close),
Event::OutputEnter { output: _ } => None,
Event::OutputLeave { output: _ } => None,
Event::Parent { parent: _ } => None,
Event::Done => {
assert_ne!(info.app_id, "");
if !info.ready {
info.ready = true;
Some(ToplevelChange::New)
} else {
None
}
}
_ => unreachable!(),
};
if let Some(change) = change {
let event = ToplevelEvent {
change,
toplevel: info.clone(),
};
implem(event, ddata);
}
}
impl Toplevel {
pub fn init<F>(handle: Main<ZwlrForeignToplevelHandleV1>, mut callback: F) -> Self
where
F: FnMut(ToplevelEvent, DispatchData) + 'static,
{
let inner = Arc::new(RwLock::new(ToplevelInfo::default()));
handle.quick_assign(move |_handle, event, ddata| {
let mut inner = inner
.write()
.expect("Failed to get write lock on toplevel inner state");
toplevel_implem(event, &mut *inner, &mut callback, ddata);
});
Self
}
}

View File

@ -0,0 +1,166 @@
use crate::wayland::toplevel::{Toplevel, ToplevelEvent};
use crate::wayland::LazyGlobal;
use smithay_client_toolkit::environment::{Environment, GlobalHandler};
use std::cell::RefCell;
use std::rc;
use std::rc::Rc;
use tracing::warn;
use wayland_client::protocol::wl_registry::WlRegistry;
use wayland_client::{Attached, DispatchData};
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
};
struct ToplevelHandlerInner {
manager: LazyGlobal<ZwlrForeignToplevelManagerV1>,
registry: Option<Attached<WlRegistry>>,
toplevels: Vec<Toplevel>,
}
impl ToplevelHandlerInner {
fn new() -> Self {
let toplevels = vec![];
Self {
registry: None,
manager: LazyGlobal::Unknown,
toplevels,
}
}
}
pub struct ToplevelHandler {
inner: Rc<RefCell<ToplevelHandlerInner>>,
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<ToplevelStatusCallback>>>>>,
}
impl ToplevelHandler {
pub fn init() -> Self {
let inner = Rc::new(RefCell::new(ToplevelHandlerInner::new()));
Self {
inner,
status_listeners: Rc::new(RefCell::new(Vec::new())),
}
}
}
impl GlobalHandler<ZwlrForeignToplevelManagerV1> for ToplevelHandler {
fn created(
&mut self,
registry: Attached<WlRegistry>,
id: u32,
version: u32,
_ddata: DispatchData,
) {
let mut inner = RefCell::borrow_mut(&self.inner);
if inner.registry.is_none() {
inner.registry = Some(registry);
}
if let LazyGlobal::Unknown = inner.manager {
inner.manager = LazyGlobal::Seen { id, version }
} else {
warn!(
"Compositor advertised zwlr_foreign_toplevel_manager_v1 multiple times, ignoring."
)
}
}
fn get(&self) -> Option<Attached<ZwlrForeignToplevelManagerV1>> {
let mut inner = RefCell::borrow_mut(&self.inner);
match inner.manager {
LazyGlobal::Bound(ref mgr) => Some(mgr.clone()),
LazyGlobal::Unknown => None,
LazyGlobal::Seen { id, version } => {
let registry = inner.registry.as_ref().expect("Failed to get registry");
// current max protocol version = 3
let version = std::cmp::min(version, 3);
let manager = registry.bind::<ZwlrForeignToplevelManagerV1>(version, id);
{
let inner = self.inner.clone();
let status_listeners = self.status_listeners.clone();
manager.quick_assign(move |_, event, _ddata| {
let mut inner = RefCell::borrow_mut(&inner);
let status_listeners = status_listeners.clone();
match event {
zwlr_foreign_toplevel_manager_v1::Event::Toplevel {
toplevel: handle,
} => {
let toplevel = Toplevel::init(handle.clone(), move |event, ddata| {
notify_status_listeners(
&handle,
event,
ddata,
&status_listeners,
);
});
inner.toplevels.push(toplevel);
}
zwlr_foreign_toplevel_manager_v1::Event::Finished => {}
_ => unreachable!(),
}
});
}
inner.manager = LazyGlobal::Bound((*manager).clone());
Some((*manager).clone())
}
}
}
}
type ToplevelStatusCallback =
dyn FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static;
/// Notifies the callbacks of an event on the toplevel
fn notify_status_listeners(
toplevel: &ZwlrForeignToplevelHandleV1,
event: ToplevelEvent,
mut ddata: DispatchData,
listeners: &RefCell<Vec<rc::Weak<RefCell<ToplevelStatusCallback>>>>,
) {
listeners.borrow_mut().retain(|lst| {
if let Some(cb) = rc::Weak::upgrade(lst) {
(cb.borrow_mut())(toplevel.clone(), event.clone(), ddata.reborrow());
true
} else {
false
}
})
}
pub struct ToplevelStatusListener {
_cb: Rc<RefCell<ToplevelStatusCallback>>,
}
pub trait ToplevelHandling {
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
where
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static;
}
impl ToplevelHandling for ToplevelHandler {
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
where
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
{
let rc = Rc::new(RefCell::new(f)) as Rc<_>;
self.status_listeners.borrow_mut().push(Rc::downgrade(&rc));
ToplevelStatusListener { _cb: rc }
}
}
pub fn listen_for_toplevels<E, F>(env: Environment<E>, f: F) -> ToplevelStatusListener
where
E: ToplevelHandling,
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
{
env.with_inner(move |inner| ToplevelHandling::listen(inner, f))
}