mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-04-19 19:34:24 +02:00
refactor(wayland): update to 0.30.0
This is pretty much a rewrite of the Wayland client code for `wayland-client` and `wayland-protocols` v0.30.0, and `smithay-client-toolkit` v0.17.0
This commit is contained in:
parent
5c18ec8ba0
commit
7f46cb4976
23 changed files with 1779 additions and 1338 deletions
123
Cargo.lock
generated
123
Cargo.lock
generated
|
@ -1555,6 +1555,7 @@ dependencies = [
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
|
"wayland-protocols-wlr",
|
||||||
"zbus 3.11.1",
|
"zbus 3.11.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1824,18 +1825,6 @@ dependencies = [
|
||||||
"memoffset 0.6.5",
|
"memoffset 0.6.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nix"
|
|
||||||
version = "0.24.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"cfg-if",
|
|
||||||
"libc",
|
|
||||||
"memoffset 0.6.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
|
@ -2222,6 +2211,15 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
|
@ -2451,6 +2449,12 @@ dependencies = [
|
||||||
"windows-sys 0.42.0",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped-tls"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -2649,9 +2653,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smithay-client-toolkit"
|
name = "smithay-client-toolkit"
|
||||||
version = "0.16.0"
|
version = "0.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454"
|
checksum = "e1476c3d89bb67079264b88aaf4f14358353318397e083b7c4e8c14517f55de7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"calloop",
|
"calloop",
|
||||||
|
@ -2659,11 +2663,14 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"memmap2",
|
"memmap2",
|
||||||
"nix 0.24.3",
|
"nix 0.26.2",
|
||||||
"pkg-config",
|
"thiserror",
|
||||||
|
"wayland-backend",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-cursor",
|
"wayland-cursor",
|
||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
|
"wayland-protocols-wlr",
|
||||||
|
"wayland-scanner",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3408,72 +3415,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-client"
|
name = "wayland-backend"
|
||||||
version = "0.29.5"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
|
checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"cc",
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"libc",
|
"io-lifetimes",
|
||||||
"nix 0.24.3",
|
"nix 0.26.2",
|
||||||
"wayland-commons",
|
"scoped-tls",
|
||||||
"wayland-scanner",
|
|
||||||
"wayland-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wayland-commons"
|
|
||||||
version = "0.29.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
|
|
||||||
dependencies = [
|
|
||||||
"nix 0.24.3",
|
|
||||||
"once_cell",
|
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"wayland-sys",
|
"wayland-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-cursor"
|
name = "wayland-client"
|
||||||
version = "0.29.5"
|
version = "0.30.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
|
checksum = "85bde68449abab1a808e5227b6e295f4ae3680911eb7711b4a2cb90141edb780"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nix 0.24.3",
|
"bitflags 1.3.2",
|
||||||
|
"calloop",
|
||||||
|
"nix 0.26.2",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-cursor"
|
||||||
|
version = "0.30.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d0c3a0d5b4b688b07b0442362d3ed6bf04724fcc16cd69ab6285b90dbc487aa"
|
||||||
|
dependencies = [
|
||||||
|
"nix 0.26.2",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"xcursor",
|
"xcursor",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-protocols"
|
name = "wayland-protocols"
|
||||||
version = "0.29.5"
|
version = "0.30.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
|
checksum = "7fefbeb8a360abe67ab7c2efe1d297a1a50ee011f5460791bc18870c26bb84e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
|
"wayland-backend",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-commons",
|
"wayland-scanner",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wayland-protocols-wlr"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fce991093320e4a6a525876e6b629ab24da25f9baef0c2e0080ad173ec89588a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"wayland-backend",
|
||||||
|
"wayland-client",
|
||||||
|
"wayland-protocols",
|
||||||
"wayland-scanner",
|
"wayland-scanner",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-scanner"
|
name = "wayland-scanner"
|
||||||
version = "0.29.5"
|
version = "0.30.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
|
checksum = "4834c14b3edf1d9986c83ca79b1e7e3afbe9874c7c144702f6467063259ce45d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
"quick-xml",
|
||||||
"quote 1.0.26",
|
"quote 1.0.26",
|
||||||
"xml-rs",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-sys"
|
name = "wayland-sys"
|
||||||
version = "0.29.5"
|
version = "0.30.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
|
checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"dlib",
|
||||||
|
"log",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3701,12 +3724,6 @@ dependencies = [
|
||||||
"nom",
|
"nom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "xml-rs"
|
|
||||||
version = "0.8.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "zbus"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
|
@ -62,9 +62,10 @@ indexmap = "1.9.1"
|
||||||
dirs = "5.0.0"
|
dirs = "5.0.0"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
notify = { version = "5.0.0", default-features = false }
|
notify = { version = "5.0.0", default-features = false }
|
||||||
wayland-client = "0.29.5"
|
wayland-client = "0.30.0"
|
||||||
wayland-protocols = { version = "0.29.5", features = ["unstable_protocols", "client"] }
|
wayland-protocols = { version = "0.30.0", features = ["unstable", "client"] }
|
||||||
smithay-client-toolkit = { version = "0.16.0", default-features = false, features = ["calloop"] }
|
wayland-protocols-wlr = { version = "0.1.0", features = ["client"] }
|
||||||
|
smithay-client-toolkit = { version = "0.17.0", default-features = false, features = ["calloop"] }
|
||||||
universal-config = { version = "0.4.0", default_features = false }
|
universal-config = { version = "0.4.0", default_features = false }
|
||||||
|
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lazy_static::lazy_static;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tracing::debug;
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ClipboardEvent {
|
pub enum ClipboardEvent {
|
||||||
|
@ -26,6 +26,8 @@ pub struct ClipboardClient {
|
||||||
|
|
||||||
impl ClipboardClient {
|
impl ClipboardClient {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
|
trace!("Initializing clipboard client");
|
||||||
|
|
||||||
let senders = Arc::new(Mutex::new(Vec::<(EventSender, usize)>::new()));
|
let senders = Arc::new(Mutex::new(Vec::<(EventSender, usize)>::new()));
|
||||||
|
|
||||||
let cache = Arc::new(Mutex::new(ClipboardCache::new()));
|
let cache = Arc::new(Mutex::new(ClipboardCache::new()));
|
||||||
|
@ -35,11 +37,21 @@ impl ClipboardClient {
|
||||||
let cache = cache.clone();
|
let cache = cache.clone();
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let mut rx = {
|
let (mut rx, item) = {
|
||||||
let wl = wayland::get_client().await;
|
let wl = wayland::get_client().await;
|
||||||
wl.subscribe_clipboard()
|
wl.subscribe_clipboard()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(item) = item {
|
||||||
|
let senders = lock!(senders);
|
||||||
|
let iter = senders.iter();
|
||||||
|
for (tx, _) in iter {
|
||||||
|
try_send!(tx, ClipboardEvent::Add(item.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
lock!(cache).insert(item, senders.len());
|
||||||
|
}
|
||||||
|
|
||||||
while let Ok(item) = rx.recv().await {
|
while let Ok(item) = rx.recv().await {
|
||||||
debug!("Received clipboard item (ID: {})", item.id);
|
debug!("Received clipboard item (ID: {})", item.id);
|
||||||
|
|
||||||
|
@ -59,7 +71,6 @@ impl ClipboardClient {
|
||||||
let iter = senders.iter();
|
let iter = senders.iter();
|
||||||
for (tx, sender_cache_size) in iter {
|
for (tx, sender_cache_size) in iter {
|
||||||
if cache_size == *sender_cache_size {
|
if cache_size == *sender_cache_size {
|
||||||
// let mut cache = lock!(cache);
|
|
||||||
let removed_id = lock!(cache)
|
let removed_id = lock!(cache)
|
||||||
.remove_ref_first()
|
.remove_ref_first()
|
||||||
.expect("Clipboard cache unexpectedly empty");
|
.expect("Clipboard cache unexpectedly empty");
|
||||||
|
@ -83,18 +94,11 @@ impl ClipboardClient {
|
||||||
Self { senders, cache }
|
Self { senders, cache }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn subscribe(&self, cache_size: usize) -> mpsc::Receiver<ClipboardEvent> {
|
pub fn subscribe(&self, cache_size: usize) -> mpsc::Receiver<ClipboardEvent> {
|
||||||
let (tx, rx) = mpsc::channel(16);
|
let (tx, rx) = mpsc::channel(16);
|
||||||
|
|
||||||
let wl = wayland::get_client().await;
|
|
||||||
wl.roundtrip();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut cache = lock!(self.cache);
|
let cache = lock!(self.cache);
|
||||||
|
|
||||||
if let Some(item) = wl.get_clipboard() {
|
|
||||||
cache.insert_or_inc_ref(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
let iter = cache.iter();
|
let iter = cache.iter();
|
||||||
for (_, (item, _)) in iter {
|
for (_, (item, _)) in iter {
|
||||||
|
@ -102,10 +106,7 @@ impl ClipboardClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
lock!(self.senders).push((tx, cache_size));
|
||||||
let mut senders = lock!(self.senders);
|
|
||||||
senders.push((tx, cache_size));
|
|
||||||
}
|
|
||||||
|
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
@ -171,13 +172,6 @@ impl ClipboardCache {
|
||||||
.map(|(item, _)| item)
|
.map(|(item, _)| item)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts an entry with `ref_count` initial references,
|
|
||||||
/// or increments the `ref_count` by 1 if it already exists.
|
|
||||||
fn insert_or_inc_ref(&mut self, item: Arc<ClipboardItem>) {
|
|
||||||
let mut item = self.cache.entry(item.id).or_insert((item, 0));
|
|
||||||
item.1 += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the entry with key `id`.
|
/// Removes the entry with key `id`.
|
||||||
/// This ignores references.
|
/// This ignores references.
|
||||||
fn remove(&mut self, id: usize) -> Option<Arc<ClipboardItem>> {
|
fn remove(&mut self, id: usize) -> Option<Arc<ClipboardItem>> {
|
||||||
|
|
|
@ -1,79 +1,90 @@
|
||||||
use super::wlr_foreign_toplevel::{
|
use super::wlr_foreign_toplevel::handle::ToplevelHandle;
|
||||||
handle::{ToplevelEvent, ToplevelInfo},
|
use super::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
||||||
manager::listen_for_toplevels,
|
use super::wlr_foreign_toplevel::ToplevelEvent;
|
||||||
};
|
use super::Environment;
|
||||||
use super::{DData, Env, ToplevelHandler};
|
use crate::error::ERR_CHANNEL_RECV;
|
||||||
use crate::{error as err, send};
|
use crate::send;
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use indexmap::IndexMap;
|
use smithay_client_toolkit::output::{OutputInfo, OutputState};
|
||||||
use smithay_client_toolkit::environment::Environment;
|
|
||||||
use smithay_client_toolkit::output::{with_output_info, OutputInfo};
|
|
||||||
use smithay_client_toolkit::reexports::calloop::channel::{channel, Event, Sender};
|
use smithay_client_toolkit::reexports::calloop::channel::{channel, Event, Sender};
|
||||||
use smithay_client_toolkit::reexports::calloop::EventLoop;
|
use smithay_client_toolkit::reexports::calloop::EventLoop;
|
||||||
use smithay_client_toolkit::WaylandSource;
|
use smithay_client_toolkit::registry::RegistryState;
|
||||||
|
use smithay_client_toolkit::seat::SeatState;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::mpsc;
|
||||||
use tokio::sync::{broadcast, oneshot};
|
use tokio::sync::broadcast;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error, trace};
|
||||||
|
use wayland_client::globals::registry_queue_init;
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use wayland_client::{ConnectError, Display, EventQueue};
|
use wayland_client::{Connection, WaylandSource};
|
||||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{
|
|
||||||
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
|
||||||
zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1,
|
|
||||||
};
|
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "clipboard")] {
|
if #[cfg(feature = "clipboard")] {
|
||||||
use super::{ClipboardItem};
|
use super::ClipboardItem;
|
||||||
use super::wlr_data_control::manager::{listen_to_devices, DataControlDeviceHandler};
|
use super::wlr_data_control::manager::DataControlDeviceManagerState;
|
||||||
use crate::{read_lock, write_lock};
|
use crate::lock;
|
||||||
use tokio::spawn;
|
use std::sync::{Arc, Mutex};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Request {
|
pub enum Request {
|
||||||
|
/// Sends a request for all the outputs.
|
||||||
|
/// These are then sent on the `output` channel.
|
||||||
|
Outputs,
|
||||||
|
/// Sends a request for all the seats.
|
||||||
|
/// These are then sent ont the `seat` channel.
|
||||||
|
Seats,
|
||||||
|
/// Sends a request for all the toplevels.
|
||||||
|
/// These are then sent on the `toplevel_init` channel.
|
||||||
|
Toplevels,
|
||||||
|
/// Sends a request for the current clipboard item.
|
||||||
|
/// This is then sent on the `clipboard_init` channel.
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
Clipboard,
|
||||||
/// Copies the value to the clipboard
|
/// Copies the value to the clipboard
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
CopyToClipboard(Arc<ClipboardItem>),
|
CopyToClipboard(Arc<ClipboardItem>),
|
||||||
/// Forces a dispatch, flushing any currently queued events
|
/// Forces a dispatch, flushing any currently queued events
|
||||||
Refresh,
|
Roundtrip,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WaylandClient {
|
pub struct WaylandClient {
|
||||||
pub outputs: Vec<OutputInfo>,
|
// External channels
|
||||||
pub seats: Vec<WlSeat>,
|
|
||||||
|
|
||||||
pub toplevels: Arc<RwLock<IndexMap<usize, (ToplevelInfo, ZwlrForeignToplevelHandleV1)>>>,
|
|
||||||
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
||||||
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
|
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard: Arc<RwLock<Option<Arc<ClipboardItem>>>>,
|
_clipboard_rx: broadcast::Receiver<Arc<ClipboardItem>>,
|
||||||
|
|
||||||
|
// Internal channels
|
||||||
|
toplevel_init_rx: mpsc::Receiver<HashMap<usize, ToplevelHandle>>,
|
||||||
|
output_rx: mpsc::Receiver<Vec<OutputInfo>>,
|
||||||
|
seat_rx: mpsc::Receiver<Vec<WlSeat>>,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
clipboard_init_rx: mpsc::Receiver<Option<Arc<ClipboardItem>>>,
|
||||||
|
|
||||||
request_tx: Sender<Request>,
|
request_tx: Sender<Request>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WaylandClient {
|
impl WaylandClient {
|
||||||
pub(super) async fn new() -> Self {
|
pub(super) async fn new() -> Self {
|
||||||
let (output_tx, output_rx) = oneshot::channel();
|
|
||||||
let (seat_tx, seat_rx) = oneshot::channel();
|
|
||||||
|
|
||||||
let (toplevel_tx, toplevel_rx) = broadcast::channel(32);
|
let (toplevel_tx, toplevel_rx) = broadcast::channel(32);
|
||||||
|
|
||||||
let toplevels = Arc::new(RwLock::new(IndexMap::new()));
|
let (toplevel_init_tx, toplevel_init_rx) = mpsc::channel();
|
||||||
let toplevels2 = toplevels.clone();
|
#[cfg(feature = "clipboard")]
|
||||||
|
let (clipboard_init_tx, clipboard_init_rx) = mpsc::channel();
|
||||||
|
let (output_tx, output_rx) = mpsc::channel();
|
||||||
|
let (seat_tx, seat_rx) = mpsc::channel();
|
||||||
|
|
||||||
let toplevel_tx2 = toplevel_tx.clone();
|
let toplevel_tx2 = toplevel_tx.clone();
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "clipboard")] {
|
if #[cfg(feature = "clipboard")] {
|
||||||
let (clipboard_tx, mut clipboard_rx) = broadcast::channel(32);
|
let (clipboard_tx, clipboard_rx) = broadcast::channel(32);
|
||||||
let clipboard = Arc::new(RwLock::new(None));
|
|
||||||
let clipboard_tx2 = clipboard_tx.clone();
|
let clipboard_tx2 = clipboard_tx.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,85 +93,100 @@ impl WaylandClient {
|
||||||
|
|
||||||
// `queue` is not `Send` so we need to handle everything inside the task
|
// `queue` is not `Send` so we need to handle everything inside the task
|
||||||
spawn_blocking(move || {
|
spawn_blocking(move || {
|
||||||
let toplevels = toplevels2;
|
|
||||||
let toplevel_tx = toplevel_tx2;
|
let toplevel_tx = toplevel_tx2;
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
let clipboard_tx = clipboard_tx2;
|
||||||
|
|
||||||
let (env, _display, queue) =
|
let conn =
|
||||||
Self::new_environment().expect("Failed to connect to Wayland compositor");
|
Connection::connect_to_env().expect("Failed to connect to Wayland compositor");
|
||||||
|
let (globals, queue) =
|
||||||
|
registry_queue_init(&conn).expect("Failed to retrieve Wayland globals");
|
||||||
|
|
||||||
|
let qh = queue.handle();
|
||||||
let mut event_loop =
|
let mut event_loop =
|
||||||
EventLoop::<DData>::try_new().expect("Failed to create new event loop");
|
EventLoop::<Environment>::try_new().expect("Failed to create new event loop");
|
||||||
|
|
||||||
WaylandSource::new(queue)
|
WaylandSource::new(queue)
|
||||||
.quick_insert(event_loop.handle())
|
.expect("Failed to create Wayland source from queue")
|
||||||
|
.insert(event_loop.handle())
|
||||||
.expect("Failed to insert Wayland event queue into event loop");
|
.expect("Failed to insert Wayland event queue into event loop");
|
||||||
|
|
||||||
let outputs = Self::get_outputs(&env);
|
let loop_handle = event_loop.handle();
|
||||||
send!(output_tx, outputs);
|
|
||||||
|
|
||||||
let seats = env.get_all_seats();
|
// Initialize the registry handling
|
||||||
|
// so other parts of Smithay's client toolkit may bind globals.
|
||||||
|
let registry_state = RegistryState::new(&globals);
|
||||||
|
|
||||||
|
let output_delegate = OutputState::new(&globals, &qh);
|
||||||
|
let seat_delegate = SeatState::new(&globals, &qh);
|
||||||
|
|
||||||
// TODO: Actually handle seats properly
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
let default_seat = seats[0].detach();
|
let data_control_device_manager_delegate =
|
||||||
|
DataControlDeviceManagerState::bind(&globals, &qh)
|
||||||
|
.expect("data device manager is not available");
|
||||||
|
|
||||||
send!(
|
let foreign_toplevel_manager_delegate = ToplevelManagerState::bind(&globals, &qh)
|
||||||
seat_tx,
|
.expect("foreign toplevel manager is not available");
|
||||||
seats
|
|
||||||
.into_iter()
|
|
||||||
.map(|seat| seat.detach())
|
|
||||||
.collect::<Vec<WlSeat>>()
|
|
||||||
);
|
|
||||||
|
|
||||||
let handle = event_loop.handle();
|
let mut env = Environment {
|
||||||
handle
|
registry_state,
|
||||||
.insert_source(ev_rx, move |event, _metadata, ddata| {
|
output_state: output_delegate,
|
||||||
// let env = &ddata.env;
|
seat_state: seat_delegate,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
data_control_device_manager_state: data_control_device_manager_delegate,
|
||||||
|
foreign_toplevel_manager_state: foreign_toplevel_manager_delegate,
|
||||||
|
seats: vec![],
|
||||||
|
handles: HashMap::new(),
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
clipboard: Arc::new(Mutex::new(None)),
|
||||||
|
toplevel_tx,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
clipboard_tx,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
data_control_devices: vec![],
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
selection_offers: vec![],
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
copy_paste_sources: vec![],
|
||||||
|
loop_handle: event_loop.handle(),
|
||||||
|
};
|
||||||
|
|
||||||
|
loop_handle
|
||||||
|
.insert_source(ev_rx, move |event, _metadata, env| {
|
||||||
|
trace!("{event:?}");
|
||||||
match event {
|
match event {
|
||||||
Event::Msg(Request::Refresh) => debug!("Received refresh event"),
|
Event::Msg(Request::Roundtrip) => debug!("Received refresh event"),
|
||||||
|
Event::Msg(Request::Outputs) => {
|
||||||
|
trace!("Received get outputs request");
|
||||||
|
|
||||||
|
send!(output_tx, env.output_info());
|
||||||
|
}
|
||||||
|
Event::Msg(Request::Seats) => {
|
||||||
|
trace!("Receive get seats request");
|
||||||
|
send!(seat_tx, env.seats.clone());
|
||||||
|
}
|
||||||
|
Event::Msg(Request::Toplevels) => {
|
||||||
|
trace!("Receive get toplevels request");
|
||||||
|
send!(toplevel_init_tx, env.handles.clone());
|
||||||
|
}
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
Event::Msg(Request::Clipboard) => {
|
||||||
|
trace!("Receive get clipboard requests");
|
||||||
|
let clipboard = lock!(env.clipboard).clone();
|
||||||
|
send!(clipboard_init_tx, clipboard);
|
||||||
|
}
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
Event::Msg(Request::CopyToClipboard(value)) => {
|
Event::Msg(Request::CopyToClipboard(value)) => {
|
||||||
super::wlr_data_control::copy_to_clipboard(
|
env.copy_to_clipboard(value, &qh);
|
||||||
&ddata.env,
|
|
||||||
&default_seat,
|
|
||||||
&value,
|
|
||||||
)
|
|
||||||
.expect("Failed to copy to clipboard");
|
|
||||||
}
|
}
|
||||||
Event::Closed => panic!("Channel unexpectedly closed"),
|
Event::Closed => panic!("Channel unexpectedly closed"),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.expect("Failed to insert channel into event queue");
|
.expect("Failed to insert channel into event queue");
|
||||||
|
|
||||||
let _toplevel_manager = env.require_global::<ZwlrForeignToplevelManagerV1>();
|
|
||||||
|
|
||||||
let _toplevel_listener = listen_for_toplevels(&env, move |handle, event, _ddata| {
|
|
||||||
super::wlr_foreign_toplevel::update_toplevels(
|
|
||||||
&toplevels,
|
|
||||||
handle,
|
|
||||||
event,
|
|
||||||
&toplevel_tx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
cfg_if! {
|
|
||||||
if #[cfg(feature = "clipboard")] {
|
|
||||||
let clipboard_tx = clipboard_tx2;
|
|
||||||
let handle = event_loop.handle();
|
|
||||||
|
|
||||||
let _offer_listener = listen_to_devices(&env, move |_seat, event, ddata| {
|
|
||||||
debug!("Received clipboard event");
|
|
||||||
super::wlr_data_control::receive_offer(event, &handle, clipboard_tx.clone(), ddata);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut data = DData {
|
|
||||||
env,
|
|
||||||
offer_tokens: HashMap::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Err(err) = event_loop.dispatch(None, &mut data) {
|
trace!("Dispatching event loop");
|
||||||
|
if let Err(err) = event_loop.dispatch(None, &mut env) {
|
||||||
error!(
|
error!(
|
||||||
"{:?}",
|
"{:?}",
|
||||||
Report::new(err).wrap_err("Failed to dispatch pending wayland events")
|
Report::new(err).wrap_err("Failed to dispatch pending wayland events")
|
||||||
|
@ -169,119 +195,76 @@ impl WaylandClient {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// keep track of current clipboard item
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
{
|
|
||||||
let clipboard = clipboard.clone();
|
|
||||||
spawn(async move {
|
|
||||||
while let Ok(item) = clipboard_rx.recv().await {
|
|
||||||
let mut clipboard = write_lock!(clipboard);
|
|
||||||
clipboard.replace(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let outputs = output_rx.await.expect(err::ERR_CHANNEL_RECV);
|
|
||||||
|
|
||||||
let seats = seat_rx.await.expect(err::ERR_CHANNEL_RECV);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
outputs,
|
|
||||||
seats,
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
clipboard,
|
|
||||||
toplevels,
|
|
||||||
toplevel_tx,
|
toplevel_tx,
|
||||||
_toplevel_rx: toplevel_rx,
|
_toplevel_rx: toplevel_rx,
|
||||||
|
toplevel_init_rx,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
clipboard_init_rx,
|
||||||
|
output_rx,
|
||||||
|
seat_rx,
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
clipboard_tx,
|
clipboard_tx,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
_clipboard_rx: clipboard_rx,
|
||||||
request_tx: ev_tx,
|
request_tx: ev_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe_toplevels(&self) -> broadcast::Receiver<ToplevelEvent> {
|
pub fn subscribe_toplevels(
|
||||||
self.toplevel_tx.subscribe()
|
&self,
|
||||||
|
) -> (
|
||||||
|
broadcast::Receiver<ToplevelEvent>,
|
||||||
|
HashMap<usize, ToplevelHandle>,
|
||||||
|
) {
|
||||||
|
let rx = self.toplevel_tx.subscribe();
|
||||||
|
|
||||||
|
let receiver = &self.toplevel_init_rx;
|
||||||
|
send!(self.request_tx, Request::Toplevels);
|
||||||
|
let data = receiver.recv().expect(ERR_CHANNEL_RECV);
|
||||||
|
|
||||||
|
(rx, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
pub fn subscribe_clipboard(&self) -> broadcast::Receiver<Arc<ClipboardItem>> {
|
pub fn subscribe_clipboard(
|
||||||
self.clipboard_tx.subscribe()
|
&self,
|
||||||
|
) -> (
|
||||||
|
broadcast::Receiver<Arc<ClipboardItem>>,
|
||||||
|
Option<Arc<ClipboardItem>>,
|
||||||
|
) {
|
||||||
|
let rx = self.clipboard_tx.subscribe();
|
||||||
|
|
||||||
|
let receiver = &self.clipboard_init_rx;
|
||||||
|
send!(self.request_tx, Request::Clipboard);
|
||||||
|
let data = receiver.recv().expect(ERR_CHANNEL_RECV);
|
||||||
|
|
||||||
|
(rx, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Force a roundtrip on the wayland connection,
|
||||||
|
/// flushing any queued events and immediately receiving any new ones.
|
||||||
pub fn roundtrip(&self) {
|
pub fn roundtrip(&self) {
|
||||||
send!(self.request_tx, Request::Refresh);
|
trace!("Sending roundtrip request");
|
||||||
|
send!(self.request_tx, Request::Roundtrip);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
pub fn get_outputs(&self) -> Vec<OutputInfo> {
|
||||||
pub fn get_clipboard(&self) -> Option<Arc<ClipboardItem>> {
|
trace!("Sending get outputs request");
|
||||||
let clipboard = read_lock!(self.clipboard);
|
|
||||||
clipboard.as_ref().cloned()
|
send!(self.request_tx, Request::Outputs);
|
||||||
|
self.output_rx.recv().expect(ERR_CHANNEL_RECV)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_seats(&self) -> Vec<WlSeat> {
|
||||||
|
trace!("Sending get seats request");
|
||||||
|
|
||||||
|
send!(self.request_tx, Request::Seats);
|
||||||
|
self.seat_rx.recv().expect(ERR_CHANNEL_RECV)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
#[cfg(feature = "clipboard")]
|
||||||
pub fn copy_to_clipboard(&self, item: Arc<ClipboardItem>) {
|
pub fn copy_to_clipboard(&self, item: Arc<ClipboardItem>) {
|
||||||
send!(self.request_tx, Request::CopyToClipboard(item));
|
send!(self.request_tx, Request::CopyToClipboard(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_outputs(env: &Environment<Env>) -> Vec<OutputInfo> {
|
|
||||||
let outputs = env.get_all_outputs();
|
|
||||||
|
|
||||||
outputs
|
|
||||||
.iter()
|
|
||||||
.filter_map(|output| with_output_info(output, Clone::clone))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_environment() -> Result<(Environment<Env>, Display, EventQueue), ConnectError> {
|
|
||||||
Display::connect_to_env().and_then(|display| {
|
|
||||||
let mut queue = display.create_event_queue();
|
|
||||||
let ret = {
|
|
||||||
let mut sctk_seats = smithay_client_toolkit::seat::SeatHandler::new();
|
|
||||||
let sctk_data_device_manager =
|
|
||||||
smithay_client_toolkit::data_device::DataDeviceHandler::init(&mut sctk_seats);
|
|
||||||
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
let data_control_device = DataControlDeviceHandler::init(&mut sctk_seats);
|
|
||||||
|
|
||||||
let sctk_primary_selection_manager =
|
|
||||||
smithay_client_toolkit::primary_selection::PrimarySelectionHandler::init(
|
|
||||||
&mut sctk_seats,
|
|
||||||
);
|
|
||||||
|
|
||||||
let display = ::smithay_client_toolkit::reexports::client::Proxy::clone(&display);
|
|
||||||
let env = Environment::new(
|
|
||||||
&display.attach(queue.token()),
|
|
||||||
&mut queue,
|
|
||||||
Env {
|
|
||||||
sctk_compositor: smithay_client_toolkit::environment::SimpleGlobal::new(),
|
|
||||||
sctk_subcompositor: smithay_client_toolkit::environment::SimpleGlobal::new(
|
|
||||||
),
|
|
||||||
sctk_shm: smithay_client_toolkit::shm::ShmHandler::new(),
|
|
||||||
sctk_outputs: smithay_client_toolkit::output::OutputHandler::new(),
|
|
||||||
sctk_seats,
|
|
||||||
sctk_data_device_manager,
|
|
||||||
sctk_primary_selection_manager,
|
|
||||||
toplevel: ToplevelHandler::init(),
|
|
||||||
#[cfg(feature = "clipboard")]
|
|
||||||
data_control_device,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Ok(env) = env.as_ref() {
|
|
||||||
let _psm = env.get_primary_selection_manager();
|
|
||||||
}
|
|
||||||
|
|
||||||
env
|
|
||||||
};
|
|
||||||
match ret {
|
|
||||||
Ok(env) => Ok((env, display, queue)),
|
|
||||||
Err(_e) => display.protocol_error().map_or_else(
|
|
||||||
|| Err(ConnectError::NoCompositorListening),
|
|
||||||
|perr| {
|
|
||||||
panic!("[SCTK] A protocol error occured during initial setup: {perr}");
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
101
src/clients/wayland/macros.rs
Normal file
101
src/clients/wayland/macros.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/// It is necessary to store macros in a separate file due to a compilation error.
|
||||||
|
/// I believe this stems from the feature flags.
|
||||||
|
/// Related issue: https://github.com/rust-lang/rust/issues/81066
|
||||||
|
|
||||||
|
// --- Data Control Device --- \\
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! delegate_data_control_device_manager {
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||||
|
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
||||||
|
[
|
||||||
|
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1: smithay_client_toolkit::globals::GlobalData
|
||||||
|
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! delegate_data_control_device {
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => {
|
||||||
|
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
||||||
|
[
|
||||||
|
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1: $udata,
|
||||||
|
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||||
|
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
||||||
|
[
|
||||||
|
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1: $crate::clients::wayland::wlr_data_control::device::DataControlDeviceData
|
||||||
|
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! delegate_data_control_offer {
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => {
|
||||||
|
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
||||||
|
[
|
||||||
|
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1: $udata,
|
||||||
|
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||||
|
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
||||||
|
[
|
||||||
|
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1: $crate::clients::wayland::wlr_data_control::offer::DataControlOfferData
|
||||||
|
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! delegate_data_control_source {
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => {
|
||||||
|
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
||||||
|
[
|
||||||
|
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1: $udata,
|
||||||
|
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||||
|
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
||||||
|
[
|
||||||
|
wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1: $crate::clients::wayland::wlr_data_control::source::DataControlSourceData
|
||||||
|
] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Foreign Toplevel --- \\
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! delegate_foreign_toplevel_manager {
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||||
|
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
||||||
|
[
|
||||||
|
wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1: smithay_client_toolkit::globals::GlobalData
|
||||||
|
] => $crate::clients::wayland::wlr_foreign_toplevel::manager::ToplevelManagerState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! delegate_foreign_toplevel_handle {
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => {
|
||||||
|
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
||||||
|
[
|
||||||
|
wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1: $udata,
|
||||||
|
] => $crate::clients::wayland::wlr_foreign_toplevel::manager::ToplevelManagerState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||||
|
wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty:
|
||||||
|
[
|
||||||
|
wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1: $crate::clients::wayland::wlr_foreign_toplevel::handle::ToplevelHandleData
|
||||||
|
] => $crate::clients::wayland::wlr_foreign_toplevel::manager::ToplevelManagerState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,70 +1,110 @@
|
||||||
mod client;
|
mod client;
|
||||||
|
|
||||||
|
mod macros;
|
||||||
|
mod wl_output;
|
||||||
|
mod wl_seat;
|
||||||
mod wlr_foreign_toplevel;
|
mod wlr_foreign_toplevel;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use self::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
||||||
|
use crate::{delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager};
|
||||||
use async_once::AsyncOnce;
|
use async_once::AsyncOnce;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use cfg_if::cfg_if;
|
use cfg_if::cfg_if;
|
||||||
use smithay_client_toolkit::default_environment;
|
use lazy_static::lazy_static;
|
||||||
use smithay_client_toolkit::environment::Environment;
|
use smithay_client_toolkit::output::OutputState;
|
||||||
use smithay_client_toolkit::reexports::calloop::RegistrationToken;
|
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
||||||
use wayland_client::{Attached, Interface};
|
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
|
||||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1;
|
use smithay_client_toolkit::seat::SeatState;
|
||||||
pub use wlr_foreign_toplevel::handle::{ToplevelChange, ToplevelEvent, ToplevelInfo};
|
use smithay_client_toolkit::{
|
||||||
use wlr_foreign_toplevel::manager::{ToplevelHandler};
|
delegate_output, delegate_registry, delegate_seat, registry_handlers,
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
|
|
||||||
pub use client::WaylandClient;
|
pub use self::client::WaylandClient;
|
||||||
|
pub use self::wlr_foreign_toplevel::{ToplevelEvent, ToplevelHandle, ToplevelInfo};
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "clipboard")] {
|
if #[cfg(feature = "clipboard")] {
|
||||||
mod wlr_data_control;
|
mod wlr_data_control;
|
||||||
|
|
||||||
use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
use crate::{delegate_data_control_device, delegate_data_control_device_manager, delegate_data_control_offer, delegate_data_control_source};
|
||||||
use wlr_data_control::manager::DataControlDeviceHandler;
|
use self::wlr_data_control::device::DataControlDevice;
|
||||||
|
use self::wlr_data_control::manager::DataControlDeviceManagerState;
|
||||||
|
use self::wlr_data_control::source::CopyPasteSource;
|
||||||
|
use self::wlr_data_control::SelectionOfferItem;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
pub use wlr_data_control::{ClipboardItem, ClipboardValue};
|
pub use wlr_data_control::{ClipboardItem, ClipboardValue};
|
||||||
|
|
||||||
|
pub struct DataControlDeviceEntry {
|
||||||
|
seat: WlSeat,
|
||||||
|
device: DataControlDevice,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A utility for lazy-loading globals.
|
pub struct Environment {
|
||||||
/// Taken from `smithay_client_toolkit` where it's not exposed
|
pub registry_state: RegistryState,
|
||||||
#[derive(Debug)]
|
pub output_state: OutputState,
|
||||||
enum LazyGlobal<I: Interface> {
|
pub seat_state: SeatState,
|
||||||
Unknown,
|
pub foreign_toplevel_manager_state: ToplevelManagerState,
|
||||||
Seen { id: u32, version: u32 },
|
#[cfg(feature = "clipboard")]
|
||||||
Bound(Attached<I>),
|
pub data_control_device_manager_state: DataControlDeviceManagerState,
|
||||||
|
pub loop_handle: LoopHandle<'static, Self>,
|
||||||
|
|
||||||
|
pub seats: Vec<WlSeat>,
|
||||||
|
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
pub data_control_devices: Vec<DataControlDeviceEntry>,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
pub selection_offers: Vec<SelectionOfferItem>,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
pub copy_paste_sources: Vec<CopyPasteSource>,
|
||||||
|
|
||||||
|
pub handles: HashMap<usize, ToplevelHandle>,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
clipboard: Arc<Mutex<Option<Arc<ClipboardItem>>>>,
|
||||||
|
|
||||||
|
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DData {
|
// Now we need to say we are delegating the responsibility of output related events for our application data
|
||||||
env: Environment<Env>,
|
// type to the requisite delegate.
|
||||||
offer_tokens: HashMap<u128, RegistrationToken>,
|
delegate_output!(Environment);
|
||||||
}
|
delegate_seat!(Environment);
|
||||||
|
|
||||||
|
delegate_foreign_toplevel_manager!(Environment);
|
||||||
|
delegate_foreign_toplevel_handle!(Environment);
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(feature = "clipboard")] {
|
if #[cfg(feature = "clipboard")] {
|
||||||
default_environment!(Env,
|
delegate_data_control_device_manager!(Environment);
|
||||||
fields = [
|
delegate_data_control_device!(Environment);
|
||||||
toplevel: ToplevelHandler,
|
delegate_data_control_source!(Environment);
|
||||||
data_control_device: DataControlDeviceHandler
|
delegate_data_control_offer!(Environment);
|
||||||
],
|
|
||||||
singles = [
|
|
||||||
ZwlrForeignToplevelManagerV1 => toplevel,
|
|
||||||
ZwlrDataControlManagerV1 => data_control_device
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
default_environment!(Env,
|
|
||||||
fields = [
|
|
||||||
toplevel: ToplevelHandler,
|
|
||||||
],
|
|
||||||
singles = [
|
|
||||||
ZwlrForeignToplevelManagerV1 => toplevel,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In order for our delegate to know of the existence of globals, we need to implement registry
|
||||||
|
// handling for the program. This trait will forward events to the RegistryHandler trait
|
||||||
|
// implementations.
|
||||||
|
delegate_registry!(Environment);
|
||||||
|
|
||||||
|
// In order for delegate_registry to work, our application data type needs to provide a way for the
|
||||||
|
// implementation to access the registry state.
|
||||||
|
//
|
||||||
|
// We also need to indicate which delegates will get told about globals being created. We specify
|
||||||
|
// the types of the delegates inside the array.
|
||||||
|
impl ProvidesRegistryState for Environment {
|
||||||
|
fn registry(&mut self) -> &mut RegistryState {
|
||||||
|
&mut self.registry_state
|
||||||
|
}
|
||||||
|
registry_handlers![OutputState, SeatState];
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CLIENT: AsyncOnce<WaylandClient> =
|
static ref CLIENT: AsyncOnce<WaylandClient> =
|
||||||
AsyncOnce::new(async { WaylandClient::new().await });
|
AsyncOnce::new(async { WaylandClient::new().await });
|
||||||
|
|
55
src/clients/wayland/wl_output.rs
Normal file
55
src/clients/wayland/wl_output.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use super::Environment;
|
||||||
|
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
|
||||||
|
use tracing::debug;
|
||||||
|
use wayland_client::protocol::wl_output;
|
||||||
|
use wayland_client::{Connection, QueueHandle};
|
||||||
|
|
||||||
|
impl Environment {
|
||||||
|
pub fn output_info(&mut self) -> Vec<OutputInfo> {
|
||||||
|
self.output_state
|
||||||
|
.outputs()
|
||||||
|
.filter_map(|output| self.output_state.info(&output))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In order to use OutputDelegate, we must implement this trait to indicate when something has happened to an
|
||||||
|
// output and to provide an instance of the output state to the delegate when dispatching events.
|
||||||
|
impl OutputHandler for Environment {
|
||||||
|
// First we need to provide a way to access the delegate.
|
||||||
|
//
|
||||||
|
// This is needed because delegate implementations for handling events use the application data type in
|
||||||
|
// their function signatures. This allows the implementation to access an instance of the type.
|
||||||
|
fn output_state(&mut self) -> &mut OutputState {
|
||||||
|
&mut self.output_state
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then there exist these functions that indicate the lifecycle of an output.
|
||||||
|
// These will be called as appropriate by the delegate implementation.
|
||||||
|
|
||||||
|
fn new_output(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_output: wl_output::WlOutput,
|
||||||
|
) {
|
||||||
|
debug!("Handler received new output");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_output(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_output: wl_output::WlOutput,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output_destroyed(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_output: wl_output::WlOutput,
|
||||||
|
) {
|
||||||
|
debug!("Handle received output destruction");
|
||||||
|
}
|
||||||
|
}
|
63
src/clients/wayland/wl_seat.rs
Normal file
63
src/clients/wayland/wl_seat.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
use super::Environment;
|
||||||
|
use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState};
|
||||||
|
use tracing::debug;
|
||||||
|
use wayland_client::protocol::wl_seat;
|
||||||
|
use wayland_client::{Connection, QueueHandle};
|
||||||
|
|
||||||
|
impl SeatHandler for Environment {
|
||||||
|
fn seat_state(&mut self) -> &mut SeatState {
|
||||||
|
&mut self.seat_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
|
||||||
|
debug!("Handler received new seat");
|
||||||
|
self.seats.push(seat);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_capability(
|
||||||
|
&mut self,
|
||||||
|
_: &Connection,
|
||||||
|
qh: &QueueHandle<Self>,
|
||||||
|
seat: wl_seat::WlSeat,
|
||||||
|
_: Capability,
|
||||||
|
) {
|
||||||
|
debug!("Handler received new capability");
|
||||||
|
|
||||||
|
#[cfg(feature = "clipboard")]
|
||||||
|
if !self
|
||||||
|
.data_control_devices
|
||||||
|
.iter_mut()
|
||||||
|
.any(|entry| entry.seat == seat)
|
||||||
|
{
|
||||||
|
debug!("Adding new data control device");
|
||||||
|
// create the data device here for this seat
|
||||||
|
let data_control_device_manager = &self.data_control_device_manager_state;
|
||||||
|
let data_control_device = data_control_device_manager.get_data_device(qh, &seat);
|
||||||
|
self.data_control_devices
|
||||||
|
.push(super::DataControlDeviceEntry {
|
||||||
|
seat: seat.clone(),
|
||||||
|
device: data_control_device,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.seats.iter().any(|s| s == &seat) {
|
||||||
|
self.seats.push(seat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_capability(
|
||||||
|
&mut self,
|
||||||
|
_: &Connection,
|
||||||
|
_: &QueueHandle<Self>,
|
||||||
|
_: wl_seat::WlSeat,
|
||||||
|
_: Capability,
|
||||||
|
) {
|
||||||
|
debug!("Handler received capability removal");
|
||||||
|
// Not applicable
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
|
||||||
|
debug!("Handler received seat removal");
|
||||||
|
self.seats.retain(|s| s != &seat);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,88 +1,166 @@
|
||||||
use super::offer::DataControlOffer;
|
use super::manager::DataControlDeviceManagerState;
|
||||||
use super::source::DataControlSource;
|
use super::offer::{
|
||||||
|
DataControlOfferData, DataControlOfferDataExt, DataControlOfferHandler, SelectionOffer,
|
||||||
|
};
|
||||||
|
use crate::error::ERR_WAYLAND_DATA;
|
||||||
use crate::lock;
|
use crate::lock;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use tracing::warn;
|
||||||
use wayland_client::{Attached, DispatchData, Main};
|
use wayland_client::{event_created_child, Connection, Dispatch, Proxy, QueueHandle};
|
||||||
use wayland_protocols::wlr::unstable::data_control::v1::client::{
|
use wayland_protocols_wlr::data_control::v1::client::{
|
||||||
zwlr_data_control_device_v1::{Event, ZwlrDataControlDeviceV1},
|
zwlr_data_control_device_v1::{Event, ZwlrDataControlDeviceV1},
|
||||||
zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
|
|
||||||
zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
|
zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Inner {
|
|
||||||
offer: Option<Arc<DataControlOffer>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Inner {
|
|
||||||
fn new_offer(&mut self, offer: &Main<ZwlrDataControlOfferV1>) {
|
|
||||||
self.offer.replace(Arc::new(DataControlOffer::new(offer)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct DataControlDeviceEvent(pub Arc<DataControlOffer>);
|
|
||||||
|
|
||||||
fn data_control_device_implem<F>(
|
|
||||||
event: Event,
|
|
||||||
inner: &mut Inner,
|
|
||||||
implem: &mut F,
|
|
||||||
ddata: DispatchData,
|
|
||||||
) where
|
|
||||||
F: FnMut(DataControlDeviceEvent, DispatchData),
|
|
||||||
{
|
|
||||||
match event {
|
|
||||||
Event::DataOffer { id } => {
|
|
||||||
inner.new_offer(&id);
|
|
||||||
}
|
|
||||||
Event::Selection { id: Some(offer) } => {
|
|
||||||
let inner_offer = inner
|
|
||||||
.offer
|
|
||||||
.clone()
|
|
||||||
.expect("Offer should exist at this stage");
|
|
||||||
if offer == inner_offer.offer {
|
|
||||||
implem(DataControlDeviceEvent(inner_offer), ddata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DataControlDevice {
|
pub struct DataControlDevice {
|
||||||
device: ZwlrDataControlDeviceV1,
|
pub device: ZwlrDataControlDeviceV1,
|
||||||
_inner: Arc<Mutex<Inner>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataControlDevice {
|
#[derive(Debug, Default)]
|
||||||
pub fn init_for_seat<F>(
|
pub struct DataControlDeviceInner {
|
||||||
manager: &Attached<ZwlrDataControlManagerV1>,
|
/// the active selection offer and its data
|
||||||
seat: &WlSeat,
|
selection_offer: Arc<Mutex<Option<ZwlrDataControlOfferV1>>>,
|
||||||
mut callback: F,
|
/// the active undetermined offers and their data
|
||||||
) -> Self
|
pub undetermined_offers: Arc<Mutex<Vec<ZwlrDataControlOfferV1>>>,
|
||||||
where
|
}
|
||||||
F: FnMut(DataControlDeviceEvent, DispatchData) + 'static,
|
|
||||||
{
|
|
||||||
let inner = Arc::new(Mutex::new(Inner { offer: None }));
|
|
||||||
|
|
||||||
let device = manager.get_data_device(seat);
|
#[derive(Debug, Default)]
|
||||||
|
pub struct DataControlDeviceData {
|
||||||
|
pub(super) inner: Arc<Mutex<DataControlDeviceInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
{
|
pub trait DataControlDeviceDataExt: Send + Sync {
|
||||||
let inner = inner.clone();
|
type DataControlOfferInner: DataControlOfferDataExt + Send + Sync + 'static;
|
||||||
device.quick_assign(move |_handle, event, ddata| {
|
|
||||||
let mut inner = lock!(inner);
|
|
||||||
data_control_device_implem(event, &mut inner, &mut callback, ddata);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
fn data_control_device_data(&self) -> &DataControlDeviceData;
|
||||||
device: device.detach(),
|
|
||||||
_inner: inner,
|
fn selection_mime_types(&self) -> Vec<String> {
|
||||||
}
|
let inner = self.data_control_device_data();
|
||||||
|
lock!(lock!(inner.inner).selection_offer)
|
||||||
|
.as_ref()
|
||||||
|
.map(|offer| {
|
||||||
|
let data = offer
|
||||||
|
.data::<Self::DataControlOfferInner>()
|
||||||
|
.expect(ERR_WAYLAND_DATA);
|
||||||
|
data.mime_types()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_selection(&self, source: &Option<DataControlSource>) {
|
/// Get the active selection offer if it exists.
|
||||||
self.device
|
fn selection_offer(&self) -> Option<SelectionOffer> {
|
||||||
.set_selection(source.as_ref().map(|s| &s.source));
|
let inner = self.data_control_device_data();
|
||||||
|
lock!(lock!(inner.inner).selection_offer)
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|offer| {
|
||||||
|
let data = offer
|
||||||
|
.data::<Self::DataControlOfferInner>()
|
||||||
|
.expect(ERR_WAYLAND_DATA);
|
||||||
|
data.as_selection_offer()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataControlDeviceDataExt for DataControlDevice {
|
||||||
|
type DataControlOfferInner = DataControlOfferData;
|
||||||
|
fn data_control_device_data(&self) -> &DataControlDeviceData {
|
||||||
|
self.device.data().expect(ERR_WAYLAND_DATA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataControlDeviceDataExt for DataControlDeviceData {
|
||||||
|
type DataControlOfferInner = DataControlOfferData;
|
||||||
|
fn data_control_device_data(&self) -> &DataControlDeviceData {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler trait for `DataDevice` events.
|
||||||
|
///
|
||||||
|
/// The functions defined in this trait are called as `DataDevice` events are received from the compositor.
|
||||||
|
pub trait DataControlDeviceHandler: Sized {
|
||||||
|
/// Advertises a new selection.
|
||||||
|
fn selection(
|
||||||
|
&mut self,
|
||||||
|
conn: &Connection,
|
||||||
|
qh: &QueueHandle<Self>,
|
||||||
|
data_device: DataControlDevice,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, U, V> Dispatch<ZwlrDataControlDeviceV1, U, D> for DataControlDeviceManagerState<V>
|
||||||
|
where
|
||||||
|
D: Dispatch<ZwlrDataControlDeviceV1, U>
|
||||||
|
+ Dispatch<ZwlrDataControlOfferV1, V>
|
||||||
|
+ DataControlDeviceHandler
|
||||||
|
+ DataControlOfferHandler
|
||||||
|
+ 'static,
|
||||||
|
U: DataControlDeviceDataExt,
|
||||||
|
V: DataControlOfferDataExt + Default + 'static + Send + Sync,
|
||||||
|
{
|
||||||
|
event_created_child!(D, ZwlrDataControlDeviceV1, [
|
||||||
|
0 => (ZwlrDataControlOfferV1, V::default())
|
||||||
|
]);
|
||||||
|
|
||||||
|
fn event(
|
||||||
|
state: &mut D,
|
||||||
|
data_device: &ZwlrDataControlDeviceV1,
|
||||||
|
event: Event,
|
||||||
|
data: &U,
|
||||||
|
conn: &Connection,
|
||||||
|
qh: &QueueHandle<D>,
|
||||||
|
) {
|
||||||
|
let data = data.data_control_device_data();
|
||||||
|
let inner = lock!(data.inner);
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::DataOffer { id } => {
|
||||||
|
// XXX Drop done here to prevent Mutex deadlocks.S
|
||||||
|
|
||||||
|
lock!(inner.undetermined_offers).push(id.clone());
|
||||||
|
let data = id
|
||||||
|
.data::<V>()
|
||||||
|
.expect(ERR_WAYLAND_DATA)
|
||||||
|
.data_control_offer_data();
|
||||||
|
data.init_undetermined_offer(&id);
|
||||||
|
|
||||||
|
// Append the data offer to our list of offers.
|
||||||
|
drop(inner);
|
||||||
|
}
|
||||||
|
Event::Selection { id } => {
|
||||||
|
let mut selection_offer = lock!(inner.selection_offer);
|
||||||
|
|
||||||
|
if let Some(offer) = id {
|
||||||
|
let mut undetermined = lock!(inner.undetermined_offers);
|
||||||
|
if let Some(i) = undetermined.iter().position(|o| o == &offer) {
|
||||||
|
undetermined.remove(i);
|
||||||
|
}
|
||||||
|
drop(undetermined);
|
||||||
|
|
||||||
|
let data = offer
|
||||||
|
.data::<V>()
|
||||||
|
.expect(ERR_WAYLAND_DATA)
|
||||||
|
.data_control_offer_data();
|
||||||
|
data.to_selection_offer();
|
||||||
|
// XXX Drop done here to prevent Mutex deadlocks.
|
||||||
|
*selection_offer = Some(offer.clone());
|
||||||
|
drop(selection_offer);
|
||||||
|
drop(inner);
|
||||||
|
state.selection(
|
||||||
|
conn,
|
||||||
|
qh,
|
||||||
|
DataControlDevice {
|
||||||
|
device: data_device.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
*selection_offer = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Finished => {
|
||||||
|
warn!("Data control offer is no longer valid, but has not been dropped by client. This could cause clipboard issues.");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,253 +1,132 @@
|
||||||
use super::device::{DataControlDevice, DataControlDeviceEvent};
|
use super::device::{DataControlDevice, DataControlDeviceData, DataControlDeviceDataExt};
|
||||||
use super::source::DataControlSource;
|
use super::offer::DataControlOfferData;
|
||||||
use smithay_client_toolkit::data_device::WritePipe;
|
use super::source::{CopyPasteSource, DataControlSourceData, DataControlSourceDataExt};
|
||||||
use smithay_client_toolkit::environment::{Environment, GlobalHandler};
|
use smithay_client_toolkit::error::GlobalError;
|
||||||
use smithay_client_toolkit::seat::{SeatHandling, SeatListener};
|
use smithay_client_toolkit::globals::{GlobalData, ProvidesBoundGlobal};
|
||||||
use smithay_client_toolkit::MissingGlobal;
|
use std::marker::PhantomData;
|
||||||
use std::cell::RefCell;
|
use tracing::debug;
|
||||||
use std::rc::{self, Rc};
|
use wayland_client::globals::{BindError, GlobalList};
|
||||||
use tracing::warn;
|
|
||||||
use wayland_client::protocol::wl_registry::WlRegistry;
|
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use wayland_client::{Attached, DispatchData};
|
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||||
use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
use wayland_protocols_wlr::data_control::v1::client::{
|
||||||
|
zwlr_data_control_device_v1::ZwlrDataControlDeviceV1,
|
||||||
|
zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
|
||||||
|
zwlr_data_control_source_v1::ZwlrDataControlSourceV1,
|
||||||
|
};
|
||||||
|
|
||||||
enum DataControlDeviceHandlerInner {
|
pub struct DataControlDeviceManagerState<V = DataControlOfferData> {
|
||||||
Ready {
|
manager: ZwlrDataControlManagerV1,
|
||||||
manager: Attached<ZwlrDataControlManagerV1>,
|
_phantom: PhantomData<V>,
|
||||||
devices: Vec<(WlSeat, DataControlDevice)>,
|
|
||||||
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<DataControlDeviceStatusCallback>>>>>,
|
|
||||||
},
|
|
||||||
Pending {
|
|
||||||
seats: Vec<WlSeat>,
|
|
||||||
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<DataControlDeviceStatusCallback>>>>>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataControlDeviceHandlerInner {
|
impl DataControlDeviceManagerState {
|
||||||
fn init_manager(&mut self, manager: Attached<ZwlrDataControlManagerV1>) {
|
pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError>
|
||||||
let (seats, status_listeners) = if let Self::Pending {
|
where
|
||||||
seats,
|
State: Dispatch<ZwlrDataControlManagerV1, GlobalData, State> + 'static,
|
||||||
status_listeners,
|
{
|
||||||
} = self
|
let manager = globals.bind(qh, 1..=2, GlobalData)?;
|
||||||
{
|
debug!("Bound to ZwlDataControlManagerV1 global");
|
||||||
(std::mem::take(seats), status_listeners.clone())
|
Ok(Self {
|
||||||
} else {
|
|
||||||
warn!("Ignoring second zwlr_data_control_manager_v1");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut devices = Vec::new();
|
|
||||||
|
|
||||||
for seat in seats {
|
|
||||||
let my_seat = seat.clone();
|
|
||||||
let status_listeners = status_listeners.clone();
|
|
||||||
let device =
|
|
||||||
DataControlDevice::init_for_seat(&manager, &seat, move |event, dispatch_data| {
|
|
||||||
notify_status_listeners(&my_seat, &event, dispatch_data, &status_listeners);
|
|
||||||
});
|
|
||||||
devices.push((seat.clone(), device));
|
|
||||||
}
|
|
||||||
|
|
||||||
*self = Self::Ready {
|
|
||||||
manager,
|
manager,
|
||||||
devices,
|
_phantom: PhantomData,
|
||||||
status_listeners,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_manager(&self) -> Option<Attached<ZwlrDataControlManagerV1>> {
|
|
||||||
match self {
|
|
||||||
Self::Ready { manager, .. } => Some(manager.clone()),
|
|
||||||
Self::Pending { .. } => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_seat(&mut self, seat: &WlSeat) {
|
|
||||||
match self {
|
|
||||||
Self::Ready {
|
|
||||||
manager,
|
|
||||||
devices,
|
|
||||||
status_listeners,
|
|
||||||
} => {
|
|
||||||
if devices.iter().any(|(s, _)| s == seat) {
|
|
||||||
// the seat already exists, nothing to do
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let my_seat = seat.clone();
|
|
||||||
let status_listeners = status_listeners.clone();
|
|
||||||
let device =
|
|
||||||
DataControlDevice::init_for_seat(manager, seat, move |event, dispatch_data| {
|
|
||||||
notify_status_listeners(&my_seat, &event, dispatch_data, &status_listeners);
|
|
||||||
});
|
|
||||||
devices.push((seat.clone(), device));
|
|
||||||
}
|
|
||||||
Self::Pending { seats, .. } => {
|
|
||||||
seats.push(seat.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_seat(&mut self, seat: &WlSeat) {
|
|
||||||
match self {
|
|
||||||
Self::Ready { devices, .. } => devices.retain(|(s, _)| s != seat),
|
|
||||||
Self::Pending { seats, .. } => seats.retain(|s| s != seat),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_source<F>(&self, mime_types: Vec<String>, callback: F) -> Option<DataControlSource>
|
|
||||||
where
|
|
||||||
F: FnMut(String, WritePipe, DispatchData) + 'static,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::Ready { manager, .. } => {
|
|
||||||
let source = DataControlSource::new(manager, mime_types, callback);
|
|
||||||
Some(source)
|
|
||||||
}
|
|
||||||
Self::Pending { .. } => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_device<F>(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal>
|
|
||||||
where
|
|
||||||
F: FnOnce(&DataControlDevice),
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Self::Ready { devices, .. } => {
|
|
||||||
let device = devices
|
|
||||||
.iter()
|
|
||||||
.find_map(|(s, device)| if s == seat { Some(device) } else { None });
|
|
||||||
|
|
||||||
device.map_or(Err(MissingGlobal), |device| {
|
|
||||||
f(device);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Self::Pending { .. } => Err(MissingGlobal),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DataControlDeviceHandler {
|
|
||||||
inner: Rc<RefCell<DataControlDeviceHandlerInner>>,
|
|
||||||
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<DataControlDeviceStatusCallback>>>>>,
|
|
||||||
_seat_listener: SeatListener,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataControlDeviceHandler {
|
|
||||||
pub fn init<S>(seat_handler: &mut S) -> Self
|
|
||||||
where
|
|
||||||
S: SeatHandling,
|
|
||||||
{
|
|
||||||
let status_listeners = Rc::new(RefCell::new(Vec::new()));
|
|
||||||
|
|
||||||
let inner = Rc::new(RefCell::new(DataControlDeviceHandlerInner::Pending {
|
|
||||||
seats: Vec::new(),
|
|
||||||
status_listeners: status_listeners.clone(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
let seat_inner = inner.clone();
|
|
||||||
let seat_listener = seat_handler.listen(move |seat, seat_data, _| {
|
|
||||||
if seat_data.defunct {
|
|
||||||
seat_inner.borrow_mut().remove_seat(&seat);
|
|
||||||
} else {
|
|
||||||
seat_inner.borrow_mut().new_seat(&seat);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
inner,
|
|
||||||
_seat_listener: seat_listener,
|
|
||||||
status_listeners,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GlobalHandler<ZwlrDataControlManagerV1> for DataControlDeviceHandler {
|
|
||||||
fn created(
|
|
||||||
&mut self,
|
|
||||||
registry: Attached<WlRegistry>,
|
|
||||||
id: u32,
|
|
||||||
version: u32,
|
|
||||||
_ddata: DispatchData,
|
|
||||||
) {
|
|
||||||
// data control manager is supported until version 2
|
|
||||||
let version = std::cmp::min(version, 2);
|
|
||||||
|
|
||||||
let manager = registry.bind::<ZwlrDataControlManagerV1>(version, id);
|
|
||||||
self.inner.borrow_mut().init_manager((*manager).clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(&self) -> Option<Attached<ZwlrDataControlManagerV1>> {
|
|
||||||
RefCell::borrow(&self.inner).get_manager()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataControlDeviceStatusCallback =
|
|
||||||
dyn FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static;
|
|
||||||
|
|
||||||
/// Notifies the callbacks of an event on the data device
|
|
||||||
fn notify_status_listeners(
|
|
||||||
seat: &WlSeat,
|
|
||||||
event: &DataControlDeviceEvent,
|
|
||||||
mut ddata: DispatchData,
|
|
||||||
listeners: &RefCell<Vec<rc::Weak<RefCell<DataControlDeviceStatusCallback>>>>,
|
|
||||||
) {
|
|
||||||
listeners.borrow_mut().retain(|lst| {
|
|
||||||
rc::Weak::upgrade(lst).map_or(false, |cb| {
|
|
||||||
(cb.borrow_mut())(seat.clone(), event.clone(), ddata.reborrow());
|
|
||||||
true
|
|
||||||
})
|
})
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DataControlDeviceStatusListener {
|
|
||||||
_cb: Rc<RefCell<DataControlDeviceStatusCallback>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DataControlDeviceHandling {
|
|
||||||
fn listen<F>(&mut self, f: F) -> DataControlDeviceStatusListener
|
|
||||||
where
|
|
||||||
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static;
|
|
||||||
|
|
||||||
fn with_data_control_device<F>(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal>
|
|
||||||
where
|
|
||||||
F: FnOnce(&DataControlDevice);
|
|
||||||
|
|
||||||
fn create_source<F>(&self, mime_types: Vec<String>, callback: F) -> Option<DataControlSource>
|
|
||||||
where
|
|
||||||
F: FnMut(String, WritePipe, DispatchData) + 'static;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataControlDeviceHandling for DataControlDeviceHandler {
|
|
||||||
fn listen<F>(&mut self, f: F) -> DataControlDeviceStatusListener
|
|
||||||
where
|
|
||||||
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static,
|
|
||||||
{
|
|
||||||
let rc = Rc::new(RefCell::new(f)) as Rc<_>;
|
|
||||||
self.status_listeners.borrow_mut().push(Rc::downgrade(&rc));
|
|
||||||
DataControlDeviceStatusListener { _cb: rc }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_data_control_device<F>(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal>
|
/// creates a data source for copy paste
|
||||||
|
pub fn create_copy_paste_source<'s, D, I>(
|
||||||
|
&self,
|
||||||
|
qh: &QueueHandle<D>,
|
||||||
|
mime_types: I,
|
||||||
|
) -> CopyPasteSource
|
||||||
where
|
where
|
||||||
F: FnOnce(&DataControlDevice),
|
D: Dispatch<ZwlrDataControlSourceV1, DataControlSourceData> + 'static,
|
||||||
|
I: IntoIterator<Item = &'s str>,
|
||||||
{
|
{
|
||||||
RefCell::borrow(&self.inner).with_device(seat, f)
|
CopyPasteSource {
|
||||||
|
inner: self.create_data_control_source(qh, mime_types),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_source<F>(&self, mime_types: Vec<String>, callback: F) -> Option<DataControlSource>
|
/// creates a data source
|
||||||
|
fn create_data_control_source<'s, D, I>(
|
||||||
|
&self,
|
||||||
|
qh: &QueueHandle<D>,
|
||||||
|
mime_types: I,
|
||||||
|
) -> ZwlrDataControlSourceV1
|
||||||
where
|
where
|
||||||
F: FnMut(String, WritePipe, DispatchData) + 'static,
|
D: Dispatch<ZwlrDataControlSourceV1, DataControlSourceData> + 'static,
|
||||||
|
I: IntoIterator<Item = &'s str>,
|
||||||
{
|
{
|
||||||
RefCell::borrow(&self.inner).create_source(mime_types, callback)
|
let source =
|
||||||
|
self.create_data_control_source_with_data(qh, DataControlSourceData::default());
|
||||||
|
|
||||||
|
for mime in mime_types {
|
||||||
|
source.offer(mime.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
source
|
||||||
|
}
|
||||||
|
|
||||||
|
/// create a new data source for a given seat with some user data
|
||||||
|
pub fn create_data_control_source_with_data<D, U>(
|
||||||
|
&self,
|
||||||
|
qh: &QueueHandle<D>,
|
||||||
|
data: U,
|
||||||
|
) -> ZwlrDataControlSourceV1
|
||||||
|
where
|
||||||
|
D: Dispatch<ZwlrDataControlSourceV1, U> + 'static,
|
||||||
|
U: DataControlSourceDataExt + 'static,
|
||||||
|
{
|
||||||
|
self.manager.create_data_source(qh, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// create a new data device for a given seat
|
||||||
|
pub fn get_data_device<D>(&self, qh: &QueueHandle<D>, seat: &WlSeat) -> DataControlDevice
|
||||||
|
where
|
||||||
|
D: Dispatch<ZwlrDataControlDeviceV1, DataControlDeviceData> + 'static,
|
||||||
|
{
|
||||||
|
DataControlDevice {
|
||||||
|
device: self.get_data_control_device_with_data(
|
||||||
|
qh,
|
||||||
|
seat,
|
||||||
|
DataControlDeviceData::default(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// create a new data device for a given seat with some user data
|
||||||
|
pub fn get_data_control_device_with_data<D, U>(
|
||||||
|
&self,
|
||||||
|
qh: &QueueHandle<D>,
|
||||||
|
seat: &WlSeat,
|
||||||
|
data: U,
|
||||||
|
) -> ZwlrDataControlDeviceV1
|
||||||
|
where
|
||||||
|
D: Dispatch<ZwlrDataControlDeviceV1, U> + 'static,
|
||||||
|
U: DataControlDeviceDataExt + 'static,
|
||||||
|
{
|
||||||
|
self.manager.get_data_device(seat, qh, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listen_to_devices<E, F>(env: &Environment<E>, f: F) -> DataControlDeviceStatusListener
|
impl ProvidesBoundGlobal<ZwlrDataControlManagerV1, 2> for DataControlDeviceManagerState {
|
||||||
|
fn bound_global(&self) -> Result<ZwlrDataControlManagerV1, GlobalError> {
|
||||||
|
Ok(self.manager.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> Dispatch<ZwlrDataControlManagerV1, GlobalData, D> for DataControlDeviceManagerState
|
||||||
where
|
where
|
||||||
E: DataControlDeviceHandling,
|
D: Dispatch<ZwlrDataControlManagerV1, GlobalData>,
|
||||||
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static,
|
|
||||||
{
|
{
|
||||||
env.with_inner(move |inner| DataControlDeviceHandling::listen(inner, f))
|
fn event(
|
||||||
|
_state: &mut D,
|
||||||
|
_proxy: &ZwlrDataControlManagerV1,
|
||||||
|
_event: <ZwlrDataControlManagerV1 as Proxy>::Event,
|
||||||
|
_data: &GlobalData,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qhandle: &QueueHandle<D>,
|
||||||
|
) {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,28 +3,25 @@ pub mod manager;
|
||||||
pub mod offer;
|
pub mod offer;
|
||||||
pub mod source;
|
pub mod source;
|
||||||
|
|
||||||
use super::Env;
|
use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
|
||||||
use crate::clients::wayland::DData;
|
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer};
|
||||||
use crate::send;
|
use self::source::DataControlSourceHandler;
|
||||||
use color_eyre::Report;
|
use crate::clients::wayland::Environment;
|
||||||
use device::{DataControlDevice, DataControlDeviceEvent};
|
use crate::{lock, send};
|
||||||
|
use device::DataControlDevice;
|
||||||
use glib::Bytes;
|
use glib::Bytes;
|
||||||
use manager::{DataControlDeviceHandling, DataControlDeviceStatusListener};
|
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||||
use smithay_client_toolkit::data_device::WritePipe;
|
use smithay_client_toolkit::reexports::calloop::RegistrationToken;
|
||||||
use smithay_client_toolkit::environment::Environment;
|
use std::fmt::{Debug, Formatter};
|
||||||
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
|
||||||
use smithay_client_toolkit::MissingGlobal;
|
|
||||||
use source::DataControlSource;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use std::os::fd::OwnedFd;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::UNIX_EPOCH;
|
use tracing::{debug, error};
|
||||||
use tokio::sync::broadcast;
|
use wayland_client::{Connection, QueueHandle};
|
||||||
use tracing::{debug, error, trace};
|
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1;
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
|
||||||
use wayland_client::DispatchData;
|
|
||||||
|
|
||||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||||
|
|
||||||
|
@ -34,6 +31,11 @@ fn get_id() -> usize {
|
||||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SelectionOfferItem {
|
||||||
|
offer: SelectionOffer,
|
||||||
|
token: Option<RegistrationToken>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq)]
|
#[derive(Debug, Clone, Eq)]
|
||||||
pub struct ClipboardItem {
|
pub struct ClipboardItem {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
|
@ -47,77 +49,27 @@ impl PartialEq<Self> for ClipboardItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub enum ClipboardValue {
|
pub enum ClipboardValue {
|
||||||
Text(String),
|
Text(String),
|
||||||
Image(Bytes),
|
Image(Bytes),
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataControlDeviceHandling for Env {
|
impl Debug for ClipboardValue {
|
||||||
fn listen<F>(&mut self, f: F) -> DataControlDeviceStatusListener
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
where
|
write!(
|
||||||
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static,
|
f,
|
||||||
{
|
"{}",
|
||||||
self.data_control_device.listen(f)
|
match self {
|
||||||
}
|
Self::Text(text) => text.clone(),
|
||||||
|
Self::Image(bytes) => {
|
||||||
fn with_data_control_device<F>(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal>
|
format!("[{} Bytes]", bytes.len())
|
||||||
where
|
}
|
||||||
F: FnOnce(&DataControlDevice),
|
Self::Other => "[Unknown]".to_string(),
|
||||||
{
|
|
||||||
self.data_control_device.with_data_control_device(seat, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_source<F>(&self, mime_types: Vec<String>, callback: F) -> Option<DataControlSource>
|
|
||||||
where
|
|
||||||
F: FnMut(String, WritePipe, DispatchData) + 'static,
|
|
||||||
{
|
|
||||||
self.data_control_device.create_source(mime_types, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn copy_to_clipboard<E>(
|
|
||||||
env: &Environment<E>,
|
|
||||||
seat: &WlSeat,
|
|
||||||
item: &ClipboardItem,
|
|
||||||
) -> Result<(), MissingGlobal>
|
|
||||||
where
|
|
||||||
E: DataControlDeviceHandling,
|
|
||||||
{
|
|
||||||
debug!("Copying item with id {} [{}]", item.id, item.mime_type);
|
|
||||||
trace!("Copying: {item:?}");
|
|
||||||
|
|
||||||
let item = item.clone();
|
|
||||||
|
|
||||||
env.with_inner(|env| {
|
|
||||||
let mime_types = vec![INTERNAL_MIME_TYPE.to_string(), item.mime_type];
|
|
||||||
let source = env.create_source(mime_types, move |mime_type, mut pipe, _ddata| {
|
|
||||||
debug!(
|
|
||||||
"Triggering source callback for item with id {} [{}]",
|
|
||||||
item.id, mime_type
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: Not working for large (buffered) values in xwayland
|
|
||||||
let bytes = match &item.value {
|
|
||||||
ClipboardValue::Text(text) => text.as_bytes(),
|
|
||||||
ClipboardValue::Image(bytes) => bytes.as_ref(),
|
|
||||||
ClipboardValue::Other => panic!(
|
|
||||||
"{:?}",
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::Other,
|
|
||||||
"Attempted to copy unsupported mime type",
|
|
||||||
)
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(err) = pipe.write_all(bytes) {
|
|
||||||
error!("{err:?}");
|
|
||||||
}
|
}
|
||||||
});
|
)
|
||||||
|
}
|
||||||
env.with_data_control_device(seat, |device| device.set_selection(&source))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -133,126 +85,230 @@ enum MimeTypeCategory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MimeType {
|
impl MimeType {
|
||||||
fn parse(mime_types: &[String]) -> Option<Self> {
|
fn parse(mime_type: &str) -> Option<Self> {
|
||||||
mime_types
|
match mime_type.to_lowercase().as_str() {
|
||||||
.iter()
|
"text"
|
||||||
.map(|s| s.to_lowercase())
|
| "string"
|
||||||
.find_map(|mime_type| match mime_type.as_str() {
|
| "utf8_string"
|
||||||
"text"
|
| "text/plain"
|
||||||
| "string"
|
| "text/plain;charset=utf-8"
|
||||||
| "utf8_string"
|
| "text/plain;charset=iso-8859-1"
|
||||||
| "text/plain"
|
| "text/plain;charset=us-ascii"
|
||||||
| "text/plain;charset=utf-8"
|
| "text/plain;charset=unicode" => Some(Self {
|
||||||
| "text/plain;charset=iso-8859-1"
|
value: mime_type.to_string(),
|
||||||
| "text/plain;charset=us-ascii"
|
category: MimeTypeCategory::Text,
|
||||||
| "text/plain;charset=unicode" => Some(Self {
|
}),
|
||||||
value: mime_type,
|
"image/png" | "image/jpg" | "image/jpeg" | "image/tiff" | "image/bmp"
|
||||||
category: MimeTypeCategory::Text,
|
| "image/x-bmp" | "image/icon" => Some(Self {
|
||||||
}),
|
value: mime_type.to_string(),
|
||||||
"image/png" | "image/jpg" | "image/jpeg" | "image/tiff" | "image/bmp"
|
category: MimeTypeCategory::Image,
|
||||||
| "image/x-bmp" | "image/icon" => Some(Self {
|
}),
|
||||||
value: mime_type,
|
_ => None,
|
||||||
category: MimeTypeCategory::Image,
|
}
|
||||||
}),
|
}
|
||||||
_ => None,
|
|
||||||
})
|
fn parse_multiple(mime_types: &[String]) -> Option<Self> {
|
||||||
|
mime_types.iter().find_map(|mime| Self::parse(mime))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn receive_offer(
|
impl Environment {
|
||||||
event: DataControlDeviceEvent,
|
pub fn copy_to_clipboard(&mut self, item: Arc<ClipboardItem>, qh: &QueueHandle<Self>) {
|
||||||
handle: &LoopHandle<DData>,
|
debug!("Copying item to clipboard");
|
||||||
tx: broadcast::Sender<Arc<ClipboardItem>>,
|
|
||||||
mut ddata: DispatchData,
|
|
||||||
) {
|
|
||||||
let timestamp = std::time::SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.expect("Could not get epoch, system time is probably very wrong")
|
|
||||||
.as_nanos();
|
|
||||||
|
|
||||||
let offer = event.0;
|
// TODO: Proper device tracking
|
||||||
|
let device = self.data_control_devices.first();
|
||||||
|
if let Some(device) = device {
|
||||||
|
let source = self
|
||||||
|
.data_control_device_manager_state
|
||||||
|
.create_copy_paste_source(qh, [item.mime_type.as_str()]);
|
||||||
|
|
||||||
let ddata = ddata
|
source.set_selection(&device.device);
|
||||||
.get::<DData>()
|
self.copy_paste_sources.push(source);
|
||||||
.expect("Expected dispatch data to exist");
|
|
||||||
|
|
||||||
let handle2 = handle.clone();
|
lock!(self.clipboard).replace(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let res = offer.with_mime_types(|mime_types| {
|
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> {
|
||||||
debug!("Offer mime types: {mime_types:?}");
|
let value = match mime_type.category {
|
||||||
|
MimeTypeCategory::Text => {
|
||||||
|
let mut txt = String::new();
|
||||||
|
file.read_to_string(&mut txt)?;
|
||||||
|
|
||||||
|
ClipboardValue::Text(txt)
|
||||||
|
}
|
||||||
|
MimeTypeCategory::Image => {
|
||||||
|
let mut bytes = vec![];
|
||||||
|
file.read_to_end(&mut bytes)?;
|
||||||
|
let bytes = Bytes::from(&bytes);
|
||||||
|
|
||||||
|
ClipboardValue::Image(bytes)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ClipboardItem {
|
||||||
|
id: get_id(),
|
||||||
|
value,
|
||||||
|
mime_type: mime_type.value.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataControlDeviceHandler for Environment {
|
||||||
|
fn selection(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
data_device: DataControlDevice,
|
||||||
|
) {
|
||||||
|
debug!("Handler received selection event");
|
||||||
|
|
||||||
|
let mime_types = data_device.selection_mime_types();
|
||||||
|
|
||||||
if mime_types.contains(&INTERNAL_MIME_TYPE.to_string()) {
|
if mime_types.contains(&INTERNAL_MIME_TYPE.to_string()) {
|
||||||
debug!("Skipping value provided by bar");
|
return;
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mime_type = MimeType::parse(mime_types);
|
if let Some(offer) = data_device.selection_offer() {
|
||||||
debug!("Detected mime type: {mime_type:?}");
|
self.selection_offers
|
||||||
|
.push(SelectionOfferItem { offer, token: None });
|
||||||
|
|
||||||
match mime_type {
|
let cur_offer = self
|
||||||
Some(mime_type) => {
|
.selection_offers
|
||||||
debug!("[{timestamp}] Sending clipboard read request ({mime_type:?})");
|
.last_mut()
|
||||||
let read_pipe = offer.receive(mime_type.value.clone())?;
|
.expect("Failed to get current offer");
|
||||||
let source = handle.insert_source(read_pipe, move |(), file, ddata| {
|
|
||||||
debug!(
|
|
||||||
"[{timestamp}] Reading clipboard contents ({:?})",
|
|
||||||
&mime_type.category
|
|
||||||
);
|
|
||||||
match read_file(&mime_type, file) {
|
|
||||||
Ok(item) => {
|
|
||||||
send!(tx, Arc::new(item));
|
|
||||||
}
|
|
||||||
Err(err) => error!("{err:?}"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(src) = ddata.offer_tokens.remove(×tamp) {
|
let Some(mime_type) = MimeType::parse_multiple(&mime_types) else {
|
||||||
handle2.remove(src);
|
lock!(self.clipboard).take();
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
ddata.offer_tokens.insert(timestamp, source);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// send an event so the clipboard module is aware it's changed
|
// send an event so the clipboard module is aware it's changed
|
||||||
send!(
|
send!(
|
||||||
tx,
|
self.clipboard_tx,
|
||||||
Arc::new(ClipboardItem {
|
Arc::new(ClipboardItem {
|
||||||
id: usize::MAX,
|
id: usize::MAX,
|
||||||
mime_type: String::new(),
|
mime_type: String::new(),
|
||||||
value: ClipboardValue::Other
|
value: ClipboardValue::Other
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(read_pipe) = cur_offer.offer.receive(mime_type.value.clone()) {
|
||||||
|
let offer_clone = cur_offer.offer.clone();
|
||||||
|
|
||||||
|
let tx = self.clipboard_tx.clone();
|
||||||
|
let clipboard = self.clipboard.clone();
|
||||||
|
|
||||||
|
let token = self
|
||||||
|
.loop_handle
|
||||||
|
.insert_source(read_pipe, move |_, file, state| {
|
||||||
|
let item = state
|
||||||
|
.selection_offers
|
||||||
|
.iter()
|
||||||
|
.position(|o| o.offer == offer_clone)
|
||||||
|
.map(|p| state.selection_offers.remove(p))
|
||||||
|
.expect("Failed to find selection offer item");
|
||||||
|
|
||||||
|
match Self::read_file(&mime_type, file) {
|
||||||
|
Ok(item) => {
|
||||||
|
let item = Arc::new(item);
|
||||||
|
lock!(clipboard).replace(item.clone());
|
||||||
|
send!(tx, item);
|
||||||
|
}
|
||||||
|
Err(err) => error!("{err:?}"),
|
||||||
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
.loop_handle
|
||||||
|
.remove(item.token.expect("Missing item token"));
|
||||||
|
});
|
||||||
|
|
||||||
|
match token {
|
||||||
|
Ok(token) => {
|
||||||
|
cur_offer.token.replace(token);
|
||||||
|
}
|
||||||
|
Err(err) => error!("{err:?}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<(), Report>(())
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Err(err) = res {
|
|
||||||
error!("{err:?}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> {
|
impl DataControlOfferHandler for Environment {
|
||||||
let value = match mime_type.category {
|
fn offer(
|
||||||
MimeTypeCategory::Text => {
|
&mut self,
|
||||||
let mut txt = String::new();
|
_conn: &Connection,
|
||||||
file.read_to_string(&mut txt)?;
|
_qh: &QueueHandle<Self>,
|
||||||
|
_offer: &mut DataControlDeviceOffer,
|
||||||
ClipboardValue::Text(txt)
|
_mime_type: String,
|
||||||
}
|
) {
|
||||||
MimeTypeCategory::Image => {
|
debug!("Handler received offer");
|
||||||
let mut bytes = vec![];
|
}
|
||||||
file.read_to_end(&mut bytes)?;
|
}
|
||||||
let bytes = Bytes::from(&bytes);
|
|
||||||
|
impl DataControlSourceHandler for Environment {
|
||||||
ClipboardValue::Image(bytes)
|
fn accept_mime(
|
||||||
}
|
&mut self,
|
||||||
};
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
Ok(ClipboardItem {
|
_source: &ZwlrDataControlSourceV1,
|
||||||
id: get_id(),
|
mime: Option<String>,
|
||||||
value,
|
) {
|
||||||
mime_type: mime_type.value.clone(),
|
debug!("Accepted mime type: {mime:?}");
|
||||||
})
|
}
|
||||||
|
|
||||||
|
fn send_request(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
source: &ZwlrDataControlSourceV1,
|
||||||
|
mime: String,
|
||||||
|
write_pipe: WritePipe,
|
||||||
|
) {
|
||||||
|
debug!("Handler received source send request event");
|
||||||
|
|
||||||
|
if let Some(item) = lock!(self.clipboard).clone() {
|
||||||
|
let fd = OwnedFd::from(write_pipe);
|
||||||
|
if let Some(_source) = self
|
||||||
|
.copy_paste_sources
|
||||||
|
.iter_mut()
|
||||||
|
.find(|s| s.inner() == source && MimeType::parse(&mime).is_some())
|
||||||
|
{
|
||||||
|
let mut file = File::from(fd);
|
||||||
|
|
||||||
|
// FIXME: Not working for large (buffered) values in xwayland
|
||||||
|
// Might be something strange going on with byte count?
|
||||||
|
let bytes = match &item.value {
|
||||||
|
ClipboardValue::Text(text) => text.as_bytes(),
|
||||||
|
ClipboardValue::Image(bytes) => bytes.as_ref(),
|
||||||
|
ClipboardValue::Other => panic!(
|
||||||
|
"{:?}",
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::Other,
|
||||||
|
"Attempted to copy unsupported mime type",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = file.write_all(bytes) {
|
||||||
|
error!("{err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancelled(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
source: &ZwlrDataControlSourceV1,
|
||||||
|
) {
|
||||||
|
debug!("Handler received source cancelled event");
|
||||||
|
|
||||||
|
self.copy_paste_sources
|
||||||
|
.iter()
|
||||||
|
.position(|s| s.inner() == source)
|
||||||
|
.map(|pos| self.copy_paste_sources.remove(pos));
|
||||||
|
source.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +1,182 @@
|
||||||
|
use super::manager::DataControlDeviceManagerState;
|
||||||
use crate::lock;
|
use crate::lock;
|
||||||
use nix::fcntl::OFlag;
|
use nix::fcntl::OFlag;
|
||||||
use nix::unistd::{close, pipe2};
|
use nix::unistd::{close, pipe2};
|
||||||
use smithay_client_toolkit::data_device::ReadPipe;
|
use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError;
|
||||||
use std::io;
|
use smithay_client_toolkit::data_device_manager::ReadPipe;
|
||||||
|
use std::ops::DerefMut;
|
||||||
use std::os::fd::FromRawFd;
|
use std::os::fd::FromRawFd;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use wayland_client::Main;
|
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||||
use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_offer_v1::{
|
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::{
|
||||||
Event, ZwlrDataControlOfferV1,
|
Event, ZwlrDataControlOfferV1,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Inner {
|
pub struct UndeterminedOffer {
|
||||||
mime_types: Vec<String>,
|
pub(crate) data_offer: Option<ZwlrDataControlOfferV1>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for UndeterminedOffer {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.data_offer == other.data_offer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DataControlOffer {
|
pub struct SelectionOffer {
|
||||||
inner: Arc<Mutex<Inner>>,
|
pub data_offer: ZwlrDataControlOfferV1,
|
||||||
pub(crate) offer: ZwlrDataControlOfferV1,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataControlOffer {
|
impl PartialEq for SelectionOffer {
|
||||||
pub(crate) fn new(offer: &Main<ZwlrDataControlOfferV1>) -> Self {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
let inner = Arc::new(Mutex::new(Inner {
|
self.data_offer == other.data_offer
|
||||||
mime_types: Vec::new(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
{
|
|
||||||
let inner = inner.clone();
|
|
||||||
|
|
||||||
offer.quick_assign(move |_, event, _| {
|
|
||||||
let mut inner = lock!(inner);
|
|
||||||
if let Event::Offer { mime_type } = event {
|
|
||||||
inner.mime_types.push(mime_type);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
offer: offer.detach(),
|
|
||||||
inner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_mime_types<F, T>(&self, f: F) -> T
|
|
||||||
where
|
|
||||||
F: FnOnce(&[String]) -> T,
|
|
||||||
{
|
|
||||||
let inner = lock!(self.inner);
|
|
||||||
f(&inner.mime_types)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn receive(&self, mime_type: String) -> io::Result<ReadPipe> {
|
|
||||||
// create a pipe
|
|
||||||
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?;
|
|
||||||
|
|
||||||
self.offer.receive(mime_type, writefd);
|
|
||||||
|
|
||||||
if let Err(err) = close(writefd) {
|
|
||||||
warn!("Failed to close write pipe: {}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(unsafe { FromRawFd::from_raw_fd(readfd) })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for DataControlOffer {
|
impl SelectionOffer {
|
||||||
fn drop(&mut self) {
|
pub fn receive(&self, mime_type: String) -> Result<ReadPipe, DataOfferError> {
|
||||||
self.offer.destroy();
|
receive(&self.data_offer, mime_type).map_err(DataOfferError::Io)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum DataControlDeviceOffer {
|
||||||
|
Selection(SelectionOffer),
|
||||||
|
Undetermined(UndeterminedOffer),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DataControlDeviceOffer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Undetermined(UndeterminedOffer { data_offer: None })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct DataControlOfferData {
|
||||||
|
pub(crate) inner: Arc<Mutex<DataControlDeviceOfferInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct DataControlDeviceOfferInner {
|
||||||
|
pub(crate) offer: DataControlDeviceOffer,
|
||||||
|
pub(crate) mime_types: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataControlOfferData {
|
||||||
|
pub(crate) fn push_mime_type(&self, mime_type: String) {
|
||||||
|
lock!(self.inner).mime_types.push(mime_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn to_selection_offer(&self) {
|
||||||
|
let mut inner = lock!(self.inner);
|
||||||
|
match &mut inner.deref_mut().offer {
|
||||||
|
DataControlDeviceOffer::Selection(_) => {}
|
||||||
|
DataControlDeviceOffer::Undetermined(o) => {
|
||||||
|
inner.offer = DataControlDeviceOffer::Selection(SelectionOffer {
|
||||||
|
data_offer: o.data_offer.clone().expect("Missing current data offer"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init_undetermined_offer(&self, offer: &ZwlrDataControlOfferV1) {
|
||||||
|
let mut inner = lock!(self.inner);
|
||||||
|
match &mut inner.deref_mut().offer {
|
||||||
|
DataControlDeviceOffer::Selection(_) => {
|
||||||
|
inner.offer = DataControlDeviceOffer::Undetermined(UndeterminedOffer {
|
||||||
|
data_offer: Some(offer.clone()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
DataControlDeviceOffer::Undetermined(o) => {
|
||||||
|
o.data_offer = Some(offer.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DataControlOfferDataExt {
|
||||||
|
fn data_control_offer_data(&self) -> &DataControlOfferData;
|
||||||
|
fn mime_types(&self) -> Vec<String>;
|
||||||
|
fn as_selection_offer(&self) -> Option<SelectionOffer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataControlOfferDataExt for DataControlOfferData {
|
||||||
|
fn data_control_offer_data(&self) -> &DataControlOfferData {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mime_types(&self) -> Vec<String> {
|
||||||
|
lock!(self.inner).mime_types.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_selection_offer(&self) -> Option<SelectionOffer> {
|
||||||
|
match &lock!(self.inner).offer {
|
||||||
|
DataControlDeviceOffer::Selection(o) => Some(o.clone()),
|
||||||
|
DataControlDeviceOffer::Undetermined(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler trait for `DataOffer` events.
|
||||||
|
///
|
||||||
|
/// The functions defined in this trait are called as `DataOffer` events are received from the compositor.
|
||||||
|
pub trait DataControlOfferHandler: Sized {
|
||||||
|
// Called for each mime type the data offer advertises.
|
||||||
|
fn offer(
|
||||||
|
&mut self,
|
||||||
|
conn: &Connection,
|
||||||
|
qh: &QueueHandle<Self>,
|
||||||
|
offer: &mut DataControlDeviceOffer,
|
||||||
|
mime_type: String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, U> Dispatch<ZwlrDataControlOfferV1, U, D> for DataControlDeviceManagerState
|
||||||
|
where
|
||||||
|
D: Dispatch<ZwlrDataControlOfferV1, U> + DataControlOfferHandler,
|
||||||
|
U: DataControlOfferDataExt,
|
||||||
|
{
|
||||||
|
fn event(
|
||||||
|
state: &mut D,
|
||||||
|
_offer: &ZwlrDataControlOfferV1,
|
||||||
|
event: <ZwlrDataControlOfferV1 as Proxy>::Event,
|
||||||
|
data: &U,
|
||||||
|
conn: &Connection,
|
||||||
|
qh: &QueueHandle<D>,
|
||||||
|
) {
|
||||||
|
let data = data.data_control_offer_data();
|
||||||
|
|
||||||
|
if let Event::Offer { mime_type } = event {
|
||||||
|
data.push_mime_type(mime_type.clone());
|
||||||
|
state.offer(conn, qh, &mut lock!(data.inner).offer, mime_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request to receive the data of a given mime type.
|
||||||
|
///
|
||||||
|
/// You can do this several times, as a reaction to motion of
|
||||||
|
/// the dnd cursor, or to inspect the data in order to choose your
|
||||||
|
/// response.
|
||||||
|
///
|
||||||
|
/// Note that you should *not* read the contents right away in a
|
||||||
|
/// blocking way, as you may deadlock your application doing so.
|
||||||
|
/// At least make sure you flush your events to the server before
|
||||||
|
/// doing so.
|
||||||
|
///
|
||||||
|
/// Fails if too many file descriptors were already open and a pipe
|
||||||
|
/// could not be created.
|
||||||
|
pub fn receive(offer: &ZwlrDataControlOfferV1, mime_type: String) -> std::io::Result<ReadPipe> {
|
||||||
|
// create a pipe
|
||||||
|
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?;
|
||||||
|
|
||||||
|
offer.receive(mime_type, writefd);
|
||||||
|
|
||||||
|
if let Err(err) = close(writefd) {
|
||||||
|
warn!("Failed to close write pipe: {}", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(unsafe { FromRawFd::from_raw_fd(readfd) })
|
||||||
|
}
|
||||||
|
|
|
@ -1,54 +1,101 @@
|
||||||
use smithay_client_toolkit::data_device::WritePipe;
|
use super::device::DataControlDevice;
|
||||||
use std::os::fd::FromRawFd;
|
use super::manager::DataControlDeviceManagerState;
|
||||||
use wayland_client::{Attached, DispatchData};
|
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||||
use wayland_protocols::wlr::unstable::data_control::v1::client::{
|
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||||
zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
|
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::{
|
||||||
zwlr_data_control_source_v1::{Event, ZwlrDataControlSourceV1},
|
Event, ZwlrDataControlSourceV1,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn data_control_source_impl<F>(
|
#[derive(Debug, Default)]
|
||||||
source: &ZwlrDataControlSourceV1,
|
pub struct DataControlSourceData {}
|
||||||
event: Event,
|
|
||||||
implem: &mut F,
|
pub trait DataControlSourceDataExt: Send + Sync {
|
||||||
ddata: DispatchData,
|
fn data_source_data(&self) -> &DataControlSourceData;
|
||||||
) where
|
}
|
||||||
F: FnMut(String, WritePipe, DispatchData),
|
|
||||||
|
impl DataControlSourceDataExt for DataControlSourceData {
|
||||||
|
fn data_source_data(&self) -> &DataControlSourceData {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler trait for `DataSource` events.
|
||||||
|
///
|
||||||
|
/// The functions defined in this trait are called as `DataSource` events are received from the compositor.
|
||||||
|
pub trait DataControlSourceHandler: Sized {
|
||||||
|
/// This may be called multiple times, once for each accepted mime type from the destination, if any.
|
||||||
|
fn accept_mime(
|
||||||
|
&mut self,
|
||||||
|
conn: &Connection,
|
||||||
|
qh: &QueueHandle<Self>,
|
||||||
|
source: &ZwlrDataControlSourceV1,
|
||||||
|
mime: Option<String>,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The client has requested the data for this source to be sent.
|
||||||
|
/// Send the data, then close the fd.
|
||||||
|
fn send_request(
|
||||||
|
&mut self,
|
||||||
|
conn: &Connection,
|
||||||
|
qh: &QueueHandle<Self>,
|
||||||
|
source: &ZwlrDataControlSourceV1,
|
||||||
|
mime: String,
|
||||||
|
fd: WritePipe,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The data source is no longer valid
|
||||||
|
/// Cleanup & destroy this resource
|
||||||
|
fn cancelled(
|
||||||
|
&mut self,
|
||||||
|
conn: &Connection,
|
||||||
|
qh: &QueueHandle<Self>,
|
||||||
|
source: &ZwlrDataControlSourceV1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, U> Dispatch<ZwlrDataControlSourceV1, U, D> for DataControlDeviceManagerState
|
||||||
|
where
|
||||||
|
D: Dispatch<ZwlrDataControlSourceV1, U> + DataControlSourceHandler,
|
||||||
|
U: DataControlSourceDataExt,
|
||||||
{
|
{
|
||||||
match event {
|
fn event(
|
||||||
Event::Send { mime_type, fd } => {
|
state: &mut D,
|
||||||
let pipe = unsafe { FromRawFd::from_raw_fd(fd) };
|
source: &ZwlrDataControlSourceV1,
|
||||||
implem(mime_type, pipe, ddata);
|
event: <ZwlrDataControlSourceV1 as Proxy>::Event,
|
||||||
}
|
_data: &U,
|
||||||
Event::Cancelled => source.destroy(),
|
conn: &Connection,
|
||||||
_ => unreachable!(),
|
qh: &QueueHandle<D>,
|
||||||
}
|
) {
|
||||||
}
|
match event {
|
||||||
|
Event::Send { mime_type, fd } => {
|
||||||
pub struct DataControlSource {
|
state.send_request(conn, qh, source, mime_type, fd.into());
|
||||||
pub(crate) source: ZwlrDataControlSourceV1,
|
}
|
||||||
}
|
Event::Cancelled => {
|
||||||
|
state.cancelled(conn, qh, source);
|
||||||
impl DataControlSource {
|
}
|
||||||
pub fn new<F>(
|
_ => {}
|
||||||
manager: &Attached<ZwlrDataControlManagerV1>,
|
|
||||||
mime_types: Vec<String>,
|
|
||||||
mut callback: F,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
F: FnMut(String, WritePipe, DispatchData) + 'static,
|
|
||||||
{
|
|
||||||
let source = manager.create_data_source();
|
|
||||||
|
|
||||||
source.quick_assign(move |source, evt, ddata| {
|
|
||||||
data_control_source_impl(&source, evt, &mut callback, ddata);
|
|
||||||
});
|
|
||||||
|
|
||||||
for mime_type in mime_types {
|
|
||||||
source.offer(mime_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
source: source.detach(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct CopyPasteSource {
|
||||||
|
pub(crate) inner: ZwlrDataControlSourceV1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CopyPasteSource {
|
||||||
|
/// Set the selection of the provided data device as a response to the event with with provided serial.
|
||||||
|
pub fn set_selection(&self, device: &DataControlDevice) {
|
||||||
|
device.device.set_selection(Some(&self.inner));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn inner(&self) -> &ZwlrDataControlSourceV1 {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CopyPasteSource {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.inner.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
|
use super::manager::ToplevelManagerState;
|
||||||
|
use crate::lock;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
use wayland_client::{DispatchData, Main};
|
use wayland_client::protocol::wl_output::WlOutput;
|
||||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{Event, ZwlrForeignToplevelHandleV1};
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use crate::write_lock;
|
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||||
|
use wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{
|
||||||
const STATE_ACTIVE: u32 = 2;
|
Event, ZwlrForeignToplevelHandleV1,
|
||||||
const STATE_FULLSCREEN: u32 = 3;
|
};
|
||||||
|
|
||||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||||
|
|
||||||
|
@ -15,138 +17,168 @@ fn get_id() -> usize {
|
||||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ToplevelHandle {
|
||||||
|
pub handle: ZwlrForeignToplevelHandleV1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ToplevelHandle {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.handle == other.handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToplevelHandle {
|
||||||
|
pub fn info(&self) -> Option<ToplevelInfo> {
|
||||||
|
trace!("Retrieving handle info");
|
||||||
|
|
||||||
|
let data = self.handle.data::<ToplevelHandleData>()?;
|
||||||
|
data.info()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn focus(&self, seat: &WlSeat) {
|
||||||
|
trace!("Activating handle");
|
||||||
|
self.handle.activate(seat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ToplevelHandleData {
|
||||||
|
pub inner: Arc<Mutex<ToplevelHandleDataInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToplevelHandleData {
|
||||||
|
fn info(&self) -> Option<ToplevelInfo> {
|
||||||
|
lock!(self.inner).current_info.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ToplevelHandleDataInner {
|
||||||
|
initial_done: bool,
|
||||||
|
output: Option<WlOutput>,
|
||||||
|
|
||||||
|
current_info: Option<ToplevelInfo>,
|
||||||
|
pending_info: ToplevelInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct ToplevelInfo {
|
pub struct ToplevelInfo {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub app_id: String,
|
pub app_id: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub active: bool,
|
|
||||||
pub fullscreen: bool,
|
pub fullscreen: bool,
|
||||||
|
pub focused: bool,
|
||||||
ready: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelInfo {
|
impl Default for ToplevelInfo {
|
||||||
fn new() -> Self {
|
fn default() -> Self {
|
||||||
let id = get_id();
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id: get_id(),
|
||||||
..Default::default()
|
app_id: String::new(),
|
||||||
|
title: String::new(),
|
||||||
|
fullscreen: false,
|
||||||
|
focused: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Toplevel;
|
pub trait ToplevelHandleDataExt {
|
||||||
|
fn toplevel_handle_data(&self) -> &ToplevelHandleData;
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ToplevelEvent {
|
|
||||||
pub toplevel: ToplevelInfo,
|
|
||||||
pub change: ToplevelChange,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
impl ToplevelHandleDataExt for ToplevelHandleData {
|
||||||
pub enum ToplevelChange {
|
fn toplevel_handle_data(&self) -> &ToplevelHandleData {
|
||||||
New,
|
self
|
||||||
Close,
|
}
|
||||||
Title(String),
|
|
||||||
Focus(bool),
|
|
||||||
Fullscreen(bool),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toplevel_implem<F>(event: Event, info: &mut ToplevelInfo, implem: &mut F, ddata: DispatchData)
|
pub trait ToplevelHandleHandler: Sized {
|
||||||
|
fn new_handle(&mut self, conn: &Connection, qh: &QueueHandle<Self>, handle: ToplevelHandle);
|
||||||
|
|
||||||
|
fn update_handle(&mut self, conn: &Connection, qh: &QueueHandle<Self>, handle: ToplevelHandle);
|
||||||
|
|
||||||
|
fn remove_handle(&mut self, conn: &Connection, qh: &QueueHandle<Self>, handle: ToplevelHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, U> Dispatch<ZwlrForeignToplevelHandleV1, U, D> for ToplevelManagerState
|
||||||
where
|
where
|
||||||
F: FnMut(ToplevelEvent, DispatchData),
|
D: Dispatch<ZwlrForeignToplevelHandleV1, U> + ToplevelHandleHandler,
|
||||||
|
U: ToplevelHandleDataExt,
|
||||||
{
|
{
|
||||||
trace!("event: {event:?} (info: {info:?})");
|
fn event(
|
||||||
|
state: &mut D,
|
||||||
|
handle: &ZwlrForeignToplevelHandleV1,
|
||||||
|
event: Event,
|
||||||
|
data: &U,
|
||||||
|
conn: &Connection,
|
||||||
|
qh: &QueueHandle<D>,
|
||||||
|
) {
|
||||||
|
const STATE_ACTIVE: u32 = 2;
|
||||||
|
const STATE_FULLSCREEN: u32 = 3;
|
||||||
|
|
||||||
let change = match event {
|
let data = data.toplevel_handle_data();
|
||||||
Event::AppId { app_id } => {
|
|
||||||
info.app_id = app_id;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Event::Title { title } => {
|
|
||||||
info.title = title.clone();
|
|
||||||
|
|
||||||
if info.ready {
|
trace!("Processing handle event: {event:?}");
|
||||||
Some(ToplevelChange::Title(title))
|
|
||||||
} else {
|
match event {
|
||||||
None
|
Event::Title { title } => {
|
||||||
|
lock!(data.inner).pending_info.title = title;
|
||||||
}
|
}
|
||||||
}
|
Event::AppId { app_id } => lock!(data.inner).pending_info.app_id = app_id,
|
||||||
Event::State { state } => {
|
Event::State { state } => {
|
||||||
// state is received as a `Vec<u8>` where every 4 bytes make up a `u32`
|
// 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.
|
// the u32 then represents a value in the `State` enum.
|
||||||
assert_eq!(state.len() % 4, 0);
|
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 state = (0..state.len() / 4)
|
lock!(data.inner).pending_info.focused = state.contains(&STATE_ACTIVE);
|
||||||
.map(|i| {
|
lock!(data.inner).pending_info.fullscreen = state.contains(&STATE_FULLSCREEN);
|
||||||
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 => {
|
|
||||||
if info.ready {
|
|
||||||
Some(ToplevelChange::Close)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
Event::OutputEnter { output } => lock!(data.inner).output = Some(output),
|
||||||
Event::OutputEnter { output: _ }
|
Event::OutputLeave { output: _ } => lock!(data.inner).output = None,
|
||||||
| Event::OutputLeave { output: _ }
|
Event::Closed => state.remove_handle(
|
||||||
| Event::Parent { parent: _ } => None,
|
conn,
|
||||||
Event::Done => {
|
qh,
|
||||||
if info.ready || info.app_id.is_empty() {
|
ToplevelHandle {
|
||||||
None
|
handle: handle.clone(),
|
||||||
} else {
|
},
|
||||||
info.ready = true;
|
),
|
||||||
Some(ToplevelChange::New)
|
Event::Done => {
|
||||||
|
{
|
||||||
|
let pending_info = lock!(data.inner).pending_info.clone();
|
||||||
|
lock!(data.inner).current_info = Some(pending_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !lock!(data.inner).initial_done {
|
||||||
|
lock!(data.inner).initial_done = true;
|
||||||
|
state.new_handle(
|
||||||
|
conn,
|
||||||
|
qh,
|
||||||
|
ToplevelHandle {
|
||||||
|
handle: handle.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
state.update_handle(
|
||||||
|
conn,
|
||||||
|
qh,
|
||||||
|
ToplevelHandle {
|
||||||
|
handle: handle.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unreachable!(),
|
trace!("Event processed");
|
||||||
};
|
|
||||||
|
|
||||||
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::new()));
|
|
||||||
|
|
||||||
handle.quick_assign(move |_handle, event, ddata| {
|
|
||||||
let mut inner = write_lock!(inner);
|
|
||||||
toplevel_implem(event, &mut inner, &mut callback, ddata);
|
|
||||||
});
|
|
||||||
|
|
||||||
Self
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,163 +1,86 @@
|
||||||
use super::handle::{Toplevel, ToplevelEvent};
|
use super::handle::{ToplevelHandleData, ToplevelHandleDataExt, ToplevelHandleHandler};
|
||||||
use crate::wayland::LazyGlobal;
|
use smithay_client_toolkit::error::GlobalError;
|
||||||
use smithay_client_toolkit::environment::{Environment, GlobalHandler};
|
use smithay_client_toolkit::globals::{GlobalData, ProvidesBoundGlobal};
|
||||||
use std::cell::RefCell;
|
use std::marker::PhantomData;
|
||||||
use std::rc::{self, Rc};
|
use tracing::{debug, warn};
|
||||||
use tracing::warn;
|
use wayland_client::globals::{BindError, GlobalList};
|
||||||
use wayland_client::protocol::wl_registry::WlRegistry;
|
use wayland_client::{event_created_child, Connection, Dispatch, QueueHandle};
|
||||||
use wayland_client::{Attached, DispatchData};
|
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
||||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{
|
|
||||||
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
||||||
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
|
zwlr_foreign_toplevel_manager_v1::{Event, ZwlrForeignToplevelManagerV1},
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ToplevelHandlerInner {
|
pub struct ToplevelManagerState<V = ToplevelHandleData> {
|
||||||
manager: LazyGlobal<ZwlrForeignToplevelManagerV1>,
|
manager: ZwlrForeignToplevelManagerV1,
|
||||||
registry: Option<Attached<WlRegistry>>,
|
_phantom: PhantomData<V>,
|
||||||
toplevels: Vec<Toplevel>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToplevelHandlerInner {
|
impl ToplevelManagerState {
|
||||||
const fn new() -> Self {
|
pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError>
|
||||||
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 matches!(inner.manager, LazyGlobal::Unknown) {
|
|
||||||
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| {
|
|
||||||
rc::Weak::upgrade(lst).map_or(false, |cb| {
|
|
||||||
(cb.borrow_mut())(toplevel.clone(), event.clone(), ddata.reborrow());
|
|
||||||
true
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ToplevelStatusListener {
|
|
||||||
_cb: Rc<RefCell<ToplevelStatusCallback>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ToplevelHandling {
|
|
||||||
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
|
|
||||||
where
|
where
|
||||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static;
|
State: Dispatch<ZwlrForeignToplevelManagerV1, GlobalData, State> + '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<_>;
|
let manager = globals.bind(qh, 1..=3, GlobalData)?;
|
||||||
self.status_listeners.borrow_mut().push(Rc::downgrade(&rc));
|
debug!("Bound to ZwlForeignToplevelManagerV1 global");
|
||||||
ToplevelStatusListener { _cb: rc }
|
Ok(Self {
|
||||||
|
manager,
|
||||||
|
_phantom: PhantomData,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listen_for_toplevels<E, F>(env: &Environment<E>, f: F) -> ToplevelStatusListener
|
pub trait ToplevelManagerHandler: Sized {
|
||||||
where
|
/// Advertises a new toplevel.
|
||||||
E: ToplevelHandling,
|
fn toplevel(
|
||||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
&mut self,
|
||||||
{
|
conn: &Connection,
|
||||||
env.with_inner(move |inner| ToplevelHandling::listen(inner, f))
|
qh: &QueueHandle<Self>,
|
||||||
|
manager: ToplevelManagerState,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProvidesBoundGlobal<ZwlrForeignToplevelManagerV1, 3> for ToplevelManagerState {
|
||||||
|
fn bound_global(&self) -> Result<ZwlrForeignToplevelManagerV1, GlobalError> {
|
||||||
|
Ok(self.manager.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, V> Dispatch<ZwlrForeignToplevelManagerV1, GlobalData, D> for ToplevelManagerState<V>
|
||||||
|
where
|
||||||
|
D: Dispatch<ZwlrForeignToplevelManagerV1, GlobalData>
|
||||||
|
+ Dispatch<ZwlrForeignToplevelHandleV1, V>
|
||||||
|
+ ToplevelManagerHandler
|
||||||
|
+ ToplevelHandleHandler
|
||||||
|
+ 'static,
|
||||||
|
V: ToplevelHandleDataExt + Default + 'static + Send + Sync,
|
||||||
|
{
|
||||||
|
event_created_child!(D, ZwlrForeignToplevelManagerV1, [
|
||||||
|
0 => (ZwlrForeignToplevelHandleV1, V::default())
|
||||||
|
]);
|
||||||
|
|
||||||
|
fn event(
|
||||||
|
state: &mut D,
|
||||||
|
toplevel_manager: &ZwlrForeignToplevelManagerV1,
|
||||||
|
event: Event,
|
||||||
|
_data: &GlobalData,
|
||||||
|
conn: &Connection,
|
||||||
|
qhandle: &QueueHandle<D>,
|
||||||
|
) {
|
||||||
|
match event {
|
||||||
|
Event::Toplevel { toplevel: _ } => {
|
||||||
|
state.toplevel(
|
||||||
|
conn,
|
||||||
|
qhandle,
|
||||||
|
ToplevelManagerState {
|
||||||
|
manager: toplevel_manager.clone(),
|
||||||
|
_phantom: PhantomData,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Event::Finished => {
|
||||||
|
warn!("Foreign toplevel manager is no longer valid, but has not been dropped by client. This could cause window tracking issues.");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,84 @@
|
||||||
use std::sync::RwLock;
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
use tokio::sync::broadcast::Sender;
|
|
||||||
use tracing::trace;
|
|
||||||
use super::Env;
|
|
||||||
use handle::{ToplevelEvent, ToplevelChange, ToplevelInfo};
|
|
||||||
use manager::{ToplevelHandling, ToplevelStatusListener};
|
|
||||||
use wayland_client::DispatchData;
|
|
||||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1;
|
|
||||||
use crate::{send, write_lock};
|
|
||||||
|
|
||||||
pub mod handle;
|
pub mod handle;
|
||||||
pub mod manager;
|
pub mod manager;
|
||||||
|
|
||||||
impl ToplevelHandling for Env {
|
use self::handle::ToplevelHandleHandler;
|
||||||
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
|
use self::manager::{ToplevelManagerHandler, ToplevelManagerState};
|
||||||
where
|
use crate::clients::wayland::Environment;
|
||||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
use tracing::{debug, error, trace};
|
||||||
{
|
use wayland_client::{Connection, QueueHandle};
|
||||||
self.toplevel.listen(f)
|
|
||||||
|
use crate::send;
|
||||||
|
pub use handle::{ToplevelHandle, ToplevelInfo};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ToplevelEvent {
|
||||||
|
New(ToplevelHandle),
|
||||||
|
Update(ToplevelHandle),
|
||||||
|
Remove(ToplevelHandle),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToplevelManagerHandler for Environment {
|
||||||
|
fn toplevel(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
_manager: ToplevelManagerState,
|
||||||
|
) {
|
||||||
|
debug!("Manager received new handle");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_toplevels(
|
impl ToplevelHandleHandler for Environment {
|
||||||
toplevels: &RwLock<IndexMap<usize, (ToplevelInfo, ZwlrForeignToplevelHandleV1)>>,
|
fn new_handle(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, handle: ToplevelHandle) {
|
||||||
handle: ZwlrForeignToplevelHandleV1,
|
debug!("Handler received new handle");
|
||||||
event: ToplevelEvent,
|
|
||||||
tx: &Sender<ToplevelEvent>,
|
|
||||||
) {
|
|
||||||
trace!("Received toplevel event: {:?}", event);
|
|
||||||
|
|
||||||
if event.change == ToplevelChange::Close {
|
match handle.info() {
|
||||||
write_lock!(toplevels).remove(&event.toplevel.id);
|
Some(info) => {
|
||||||
} else {
|
trace!("Adding new handle: {info:?}");
|
||||||
write_lock!(toplevels).insert(event.toplevel.id, (event.toplevel.clone(), handle));
|
self.handles.insert(info.id, handle.clone());
|
||||||
|
send!(self.toplevel_tx, ToplevelEvent::New(handle));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("Handle is missing information!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send!(tx, event);
|
fn update_handle(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
handle: ToplevelHandle,
|
||||||
|
) {
|
||||||
|
debug!("Handler received handle update");
|
||||||
|
|
||||||
|
match handle.info() {
|
||||||
|
Some(info) => {
|
||||||
|
trace!("Updating handle: {info:?}");
|
||||||
|
self.handles.insert(info.id, handle.clone());
|
||||||
|
send!(self.toplevel_tx, ToplevelEvent::Update(handle));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("Handle is missing information!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_handle(
|
||||||
|
&mut self,
|
||||||
|
_conn: &Connection,
|
||||||
|
_qh: &QueueHandle<Self>,
|
||||||
|
handle: ToplevelHandle,
|
||||||
|
) {
|
||||||
|
debug!("Handler received handle close");
|
||||||
|
match handle.info() {
|
||||||
|
Some(info) => {
|
||||||
|
self.handles.remove(&info.id);
|
||||||
|
send!(self.toplevel_tx, ToplevelEvent::Remove(handle));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("Handle is missing information!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@ pub enum ExitCode {
|
||||||
Config = 3,
|
Config = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ERR_OUTPUTS: &str = "GTK and Sway are reporting a different set of outputs - this is a severe bug and should never happen";
|
pub const ERR_OUTPUTS: &str = "GTK and Wayland are reporting a different set of outputs - this is a severe bug and should never happen";
|
||||||
pub const ERR_MUTEX_LOCK: &str = "Failed to get lock on Mutex";
|
pub const ERR_MUTEX_LOCK: &str = "Failed to get lock on Mutex";
|
||||||
pub const ERR_READ_LOCK: &str = "Failed to get read lock";
|
pub const ERR_READ_LOCK: &str = "Failed to get read lock";
|
||||||
pub const ERR_WRITE_LOCK: &str = "Failed to get write lock";
|
pub const ERR_WRITE_LOCK: &str = "Failed to get write lock";
|
||||||
pub const ERR_CHANNEL_SEND: &str = "Failed to send message to channel";
|
pub const ERR_CHANNEL_SEND: &str = "Failed to send message to channel";
|
||||||
pub const ERR_CHANNEL_RECV: &str = "Failed to receive message from channel";
|
pub const ERR_CHANNEL_RECV: &str = "Failed to receive message from channel";
|
||||||
|
|
||||||
|
pub const ERR_WAYLAND_DATA: &str = "Failed to get data for Wayland object";
|
||||||
|
|
|
@ -127,7 +127,7 @@ fn create_bars(
|
||||||
wl: &WaylandClient,
|
wl: &WaylandClient,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let outputs = wl.outputs.as_slice();
|
let outputs = wl.get_outputs();
|
||||||
|
|
||||||
debug!("Received {} outputs from Wayland", outputs.len());
|
debug!("Received {} outputs from Wayland", outputs.len());
|
||||||
debug!("Outputs: {:?}", outputs);
|
debug!("Outputs: {:?}", outputs);
|
||||||
|
@ -141,7 +141,8 @@ fn create_bars(
|
||||||
let output = outputs
|
let output = outputs
|
||||||
.get(i as usize)
|
.get(i as usize)
|
||||||
.ok_or_else(|| Report::msg(error::ERR_OUTPUTS))?;
|
.ok_or_else(|| Report::msg(error::ERR_OUTPUTS))?;
|
||||||
let monitor_name = &output.name;
|
|
||||||
|
let Some(monitor_name) = &output.name else { continue };
|
||||||
|
|
||||||
config.monitors.as_ref().map_or_else(
|
config.monitors.as_ref().map_or_else(
|
||||||
|| {
|
|| {
|
||||||
|
|
|
@ -80,7 +80,7 @@ impl Module<Button> for ClipboardModule {
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let mut rx = {
|
let mut rx = {
|
||||||
let client = clipboard::get_client();
|
let client = clipboard::get_client();
|
||||||
client.subscribe(max_items).await
|
client.subscribe(max_items)
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::clients::wayland::{self, ToplevelChange};
|
use crate::clients::wayland::{self, ToplevelEvent};
|
||||||
use crate::config::{CommonConfig, TruncateMode};
|
use crate::config::{CommonConfig, TruncateMode};
|
||||||
use crate::image::ImageProvider;
|
use crate::image::ImageProvider;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||||
use crate::{await_sync, read_lock, send_async};
|
use crate::{send_async, try_send};
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
@ -10,7 +10,7 @@ use gtk::Label;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::error;
|
use tracing::{debug, error};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct FocusedModule {
|
pub struct FocusedModule {
|
||||||
|
@ -49,38 +49,36 @@ impl Module<gtk::Box> for FocusedModule {
|
||||||
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||||
_rx: Receiver<Self::ReceiveMessage>,
|
_rx: Receiver<Self::ReceiveMessage>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let focused = await_sync(async {
|
|
||||||
let wl = wayland::get_client().await;
|
|
||||||
let toplevels = read_lock!(wl.toplevels);
|
|
||||||
|
|
||||||
toplevels
|
|
||||||
.iter()
|
|
||||||
.find(|(_, (top, _))| top.active)
|
|
||||||
.map(|(_, (top, _))| top.clone())
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(top) = focused {
|
|
||||||
tx.try_send(ModuleUpdateEvent::Update((top.title.clone(), top.app_id)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let mut wlrx = {
|
let (mut wlrx, handles) = {
|
||||||
let wl = wayland::get_client().await;
|
let wl = wayland::get_client().await;
|
||||||
wl.subscribe_toplevels()
|
wl.subscribe_toplevels()
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Ok(event) = wlrx.recv().await {
|
let focused = handles.values().find_map(|handle| {
|
||||||
let update = match event.change {
|
handle
|
||||||
ToplevelChange::Focus(focus) => focus,
|
.info()
|
||||||
ToplevelChange::Title(_) => event.toplevel.active,
|
.and_then(|info| if info.focused { Some(info) } else { None })
|
||||||
_ => false,
|
});
|
||||||
};
|
|
||||||
|
|
||||||
if update {
|
if let Some(focused) = focused {
|
||||||
send_async!(
|
try_send!(
|
||||||
tx,
|
tx,
|
||||||
ModuleUpdateEvent::Update((event.toplevel.title, event.toplevel.app_id))
|
ModuleUpdateEvent::Update((focused.title.clone(), focused.app_id))
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Ok(event) = wlrx.recv().await {
|
||||||
|
if let ToplevelEvent::Update(handle) = event {
|
||||||
|
let info = handle.info().unwrap_or_default();
|
||||||
|
|
||||||
|
if info.focused {
|
||||||
|
debug!("Changing focus");
|
||||||
|
send_async!(
|
||||||
|
tx,
|
||||||
|
ModuleUpdateEvent::Update((info.title.clone(), info.app_id.clone()))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use super::open_state::OpenState;
|
use super::open_state::OpenState;
|
||||||
use crate::clients::wayland::ToplevelInfo;
|
use crate::clients::wayland::ToplevelHandle;
|
||||||
use crate::image::ImageProvider;
|
use crate::image::ImageProvider;
|
||||||
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
||||||
use crate::modules::ModuleUpdateEvent;
|
use crate::modules::ModuleUpdateEvent;
|
||||||
use crate::popup::Popup;
|
use crate::popup::Popup;
|
||||||
use crate::{read_lock, try_send};
|
use crate::{read_lock, try_send};
|
||||||
|
use color_eyre::{Report, Result};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, IconTheme, Orientation};
|
use gtk::{Button, IconTheme, Orientation};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
@ -12,6 +13,7 @@ use std::rc::Rc;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
|
@ -34,24 +36,30 @@ impl Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Merges the provided node into this launcher item
|
/// Merges the provided node into this launcher item
|
||||||
pub fn merge_toplevel(&mut self, node: ToplevelInfo) -> Window {
|
pub fn merge_toplevel(&mut self, handle: ToplevelHandle) -> Result<Window> {
|
||||||
let id = node.id;
|
let info = handle
|
||||||
|
.info()
|
||||||
|
.ok_or_else(|| Report::msg("Toplevel is missing associated info"))?;
|
||||||
|
|
||||||
|
let id = info.id;
|
||||||
|
|
||||||
if self.windows.is_empty() {
|
if self.windows.is_empty() {
|
||||||
self.name = node.title.clone();
|
self.name = info.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
let window: Window = node.into();
|
let window = Window::try_from(handle)?;
|
||||||
self.windows.insert(id, window.clone());
|
self.windows.insert(id, window.clone());
|
||||||
|
|
||||||
self.recalculate_open_state();
|
self.recalculate_open_state();
|
||||||
|
|
||||||
window
|
Ok(window)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unmerge_toplevel(&mut self, node: &ToplevelInfo) {
|
pub fn unmerge_toplevel(&mut self, handle: &ToplevelHandle) {
|
||||||
self.windows.remove(&node.id);
|
if let Some(info) = handle.info() {
|
||||||
self.recalculate_open_state();
|
self.windows.remove(&info.id);
|
||||||
|
self.recalculate_open_state();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_window_name(&mut self, window_id: usize, name: String) {
|
pub fn set_window_name(&mut self, window_id: usize, name: String) {
|
||||||
|
@ -87,22 +95,29 @@ impl Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ToplevelInfo> for Item {
|
impl TryFrom<ToplevelHandle> for Item {
|
||||||
fn from(toplevel: ToplevelInfo) -> Self {
|
type Error = Report;
|
||||||
let open_state = OpenState::from_toplevel(&toplevel);
|
|
||||||
let name = toplevel.title.clone();
|
fn try_from(handle: ToplevelHandle) -> std::result::Result<Self, Self::Error> {
|
||||||
let app_id = toplevel.app_id.clone();
|
let info = handle
|
||||||
|
.info()
|
||||||
|
.ok_or_else(|| Report::msg("Toplevel is missing associated info"))?;
|
||||||
|
|
||||||
|
let name = info.title.clone();
|
||||||
|
let app_id = info.app_id.clone();
|
||||||
|
let open_state = OpenState::from(&info);
|
||||||
|
|
||||||
let mut windows = IndexMap::new();
|
let mut windows = IndexMap::new();
|
||||||
windows.insert(toplevel.id, toplevel.into());
|
let window = Window::try_from(handle)?;
|
||||||
|
windows.insert(info.id, window);
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
app_id,
|
app_id,
|
||||||
favorite: false,
|
favorite: false,
|
||||||
open_state,
|
open_state,
|
||||||
windows,
|
windows,
|
||||||
name,
|
name,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,17 +126,30 @@ pub struct Window {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub open_state: OpenState,
|
pub open_state: OpenState,
|
||||||
|
handle: ToplevelHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ToplevelInfo> for Window {
|
impl TryFrom<ToplevelHandle> for Window {
|
||||||
fn from(node: ToplevelInfo) -> Self {
|
type Error = Report;
|
||||||
let open_state = OpenState::from_toplevel(&node);
|
|
||||||
|
|
||||||
Self {
|
fn try_from(handle: ToplevelHandle) -> Result<Self, Self::Error> {
|
||||||
id: node.id,
|
let info = handle
|
||||||
name: node.title,
|
.info()
|
||||||
|
.ok_or_else(|| Report::msg("Toplevel is missing associated info"))?;
|
||||||
|
let open_state = OpenState::from(&info);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
id: info.id,
|
||||||
|
name: info.title,
|
||||||
open_state,
|
open_state,
|
||||||
}
|
handle,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn focus(&self, seat: &WlSeat) {
|
||||||
|
self.handle.focus(seat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,12 @@ mod open_state;
|
||||||
|
|
||||||
use self::item::{Item, ItemButton, Window};
|
use self::item::{Item, ItemButton, Window};
|
||||||
use self::open_state::OpenState;
|
use self::open_state::OpenState;
|
||||||
use crate::clients::wayland::{self, ToplevelChange};
|
use crate::clients::wayland::{self, ToplevelEvent};
|
||||||
use crate::config::CommonConfig;
|
use crate::config::CommonConfig;
|
||||||
use crate::desktop_file::find_desktop_file;
|
use crate::desktop_file::find_desktop_file;
|
||||||
use crate::modules::launcher::item::AppearanceOptions;
|
use crate::modules::launcher::item::AppearanceOptions;
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||||
use crate::{lock, read_lock, try_send, write_lock};
|
use crate::{lock, send_async, try_send, write_lock};
|
||||||
use color_eyre::{Help, Report};
|
use color_eyre::{Help, Report};
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
@ -18,7 +18,6 @@ use serde::Deserialize;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
|
@ -111,70 +110,64 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
|
|
||||||
let items = Arc::new(Mutex::new(items));
|
let items = Arc::new(Mutex::new(items));
|
||||||
|
|
||||||
{
|
|
||||||
let items = Arc::clone(&items);
|
|
||||||
let tx = tx.clone();
|
|
||||||
spawn(async move {
|
|
||||||
let wl = wayland::get_client().await;
|
|
||||||
let open_windows = read_lock!(wl.toplevels);
|
|
||||||
|
|
||||||
let open_windows = open_windows.clone();
|
|
||||||
for (_, (window, _)) in open_windows {
|
|
||||||
let mut items = lock!(items);
|
|
||||||
let item = items.get_mut(&window.app_id);
|
|
||||||
match item {
|
|
||||||
Some(item) => {
|
|
||||||
item.merge_toplevel(window);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
items.insert(window.app_id.clone(), window.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let items = lock!(items);
|
|
||||||
let items = items.iter();
|
|
||||||
for (_, item) in items {
|
|
||||||
tx.try_send(ModuleUpdateEvent::Update(LauncherUpdate::AddItem(
|
|
||||||
item.clone(),
|
|
||||||
)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok::<(), Report>(())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let items2 = Arc::clone(&items);
|
let items2 = Arc::clone(&items);
|
||||||
|
let tx2 = tx.clone();
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let items = items2;
|
let items = items2;
|
||||||
|
let tx = tx2;
|
||||||
|
|
||||||
let mut wlrx = {
|
let (mut wlrx, handles) = {
|
||||||
let wl = wayland::get_client().await;
|
let wl = wayland::get_client().await;
|
||||||
wl.subscribe_toplevels()
|
wl.subscribe_toplevels()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for handle in handles.values() {
|
||||||
|
let Some(info) = handle.info() else { continue };
|
||||||
|
|
||||||
|
let mut items = lock!(items);
|
||||||
|
let item = items.get_mut(&info.app_id);
|
||||||
|
match item {
|
||||||
|
Some(item) => {
|
||||||
|
item.merge_toplevel(handle.clone())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
items.insert(info.app_id.clone(), Item::try_from(handle.clone())?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let items = lock!(items);
|
||||||
|
let items = items.iter();
|
||||||
|
for (_, item) in items {
|
||||||
|
try_send!(
|
||||||
|
tx,
|
||||||
|
ModuleUpdateEvent::Update(LauncherUpdate::AddItem(item.clone()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let send_update = |update: LauncherUpdate| tx.send(ModuleUpdateEvent::Update(update));
|
let send_update = |update: LauncherUpdate| tx.send(ModuleUpdateEvent::Update(update));
|
||||||
|
|
||||||
while let Ok(event) = wlrx.recv().await {
|
while let Ok(event) = wlrx.recv().await {
|
||||||
trace!("event: {:?}", event);
|
trace!("event: {:?}", event);
|
||||||
|
|
||||||
let window = event.toplevel;
|
match event {
|
||||||
let app_id = window.app_id.clone();
|
ToplevelEvent::New(handle) => {
|
||||||
|
let Some(info) = handle.info() else { continue };
|
||||||
|
|
||||||
match event.change {
|
|
||||||
ToplevelChange::New => {
|
|
||||||
let new_item = {
|
let new_item = {
|
||||||
let mut items = lock!(items);
|
let mut items = lock!(items);
|
||||||
let item = items.get_mut(&app_id);
|
let item = items.get_mut(&info.app_id);
|
||||||
match item {
|
match item {
|
||||||
None => {
|
None => {
|
||||||
let item: Item = window.into();
|
let item: Item = handle.try_into()?;
|
||||||
items.insert(app_id.clone(), item.clone());
|
items.insert(info.app_id.clone(), item.clone());
|
||||||
|
|
||||||
ItemOrWindow::Item(item)
|
ItemOrWindow::Item(item)
|
||||||
}
|
}
|
||||||
Some(item) => {
|
Some(item) => {
|
||||||
let window = item.merge_toplevel(window);
|
let window = item.merge_toplevel(handle)?;
|
||||||
ItemOrWindow::Window(window)
|
ItemOrWindow::Window(window)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,20 +178,40 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
send_update(LauncherUpdate::AddItem(item)).await
|
send_update(LauncherUpdate::AddItem(item)).await
|
||||||
}
|
}
|
||||||
ItemOrWindow::Window(window) => {
|
ItemOrWindow::Window(window) => {
|
||||||
send_update(LauncherUpdate::AddWindow(app_id, window)).await
|
send_update(LauncherUpdate::AddWindow(info.app_id.clone(), window))
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
ToplevelChange::Close => {
|
ToplevelEvent::Update(handle) => {
|
||||||
|
let Some(info) = handle.info() else { continue };
|
||||||
|
|
||||||
|
if let Some(item) = lock!(items).get_mut(&info.app_id) {
|
||||||
|
item.set_window_focused(info.id, info.focused);
|
||||||
|
item.set_window_name(info.id, info.title.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
send_update(LauncherUpdate::Focus(info.app_id.clone(), info.focused))
|
||||||
|
.await?;
|
||||||
|
send_update(LauncherUpdate::Title(
|
||||||
|
info.app_id.clone(),
|
||||||
|
info.id,
|
||||||
|
info.title.clone(),
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
ToplevelEvent::Remove(handle) => {
|
||||||
|
let Some(info) = handle.info() else { continue };
|
||||||
|
|
||||||
let remove_item = {
|
let remove_item = {
|
||||||
let mut items = lock!(items);
|
let mut items = lock!(items);
|
||||||
let item = items.get_mut(&app_id);
|
let item = items.get_mut(&info.app_id);
|
||||||
match item {
|
match item {
|
||||||
Some(item) => {
|
Some(item) => {
|
||||||
item.unmerge_toplevel(&window);
|
item.unmerge_toplevel(&handle);
|
||||||
|
|
||||||
if item.windows.is_empty() {
|
if item.windows.is_empty() {
|
||||||
items.remove(&app_id);
|
items.remove(&info.app_id);
|
||||||
Some(ItemOrWindowId::Item)
|
Some(ItemOrWindowId::Item)
|
||||||
} else {
|
} else {
|
||||||
Some(ItemOrWindowId::Window)
|
Some(ItemOrWindowId::Window)
|
||||||
|
@ -210,56 +223,28 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
|
|
||||||
match remove_item {
|
match remove_item {
|
||||||
Some(ItemOrWindowId::Item) => {
|
Some(ItemOrWindowId::Item) => {
|
||||||
send_update(LauncherUpdate::RemoveItem(app_id)).await?;
|
send_update(LauncherUpdate::RemoveItem(info.app_id.clone()))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
Some(ItemOrWindowId::Window) => {
|
Some(ItemOrWindowId::Window) => {
|
||||||
send_update(LauncherUpdate::RemoveWindow(app_id, window.id))
|
send_update(LauncherUpdate::RemoveWindow(
|
||||||
.await?;
|
info.app_id.clone(),
|
||||||
|
info.id,
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ToplevelChange::Focus(focused) => {
|
|
||||||
let mut update_title = false;
|
|
||||||
|
|
||||||
if focused {
|
|
||||||
if let Some(item) = lock!(items).get_mut(&app_id) {
|
|
||||||
item.set_window_focused(window.id, true);
|
|
||||||
|
|
||||||
// might be switching focus between windows of same app
|
|
||||||
if item.windows.len() > 1 {
|
|
||||||
item.set_window_name(window.id, window.title.clone());
|
|
||||||
update_title = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
send_update(LauncherUpdate::Focus(app_id.clone(), focused)).await?;
|
|
||||||
|
|
||||||
if update_title {
|
|
||||||
send_update(LauncherUpdate::Title(app_id, window.id, window.title))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToplevelChange::Title(title) => {
|
|
||||||
if let Some(item) = lock!(items).get_mut(&app_id) {
|
|
||||||
item.set_window_name(window.id, title.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
send_update(LauncherUpdate::Title(app_id, window.id, title)).await?;
|
|
||||||
}
|
|
||||||
ToplevelChange::Fullscreen(_) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok::<(), mpsc::error::SendError<ModuleUpdateEvent<LauncherUpdate>>>(())
|
Ok::<(), Report>(())
|
||||||
});
|
});
|
||||||
|
|
||||||
// listen to ui events
|
// listen to ui events
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
trace!("{:?}", event);
|
|
||||||
|
|
||||||
if let ItemEvent::OpenItem(app_id) = event {
|
if let ItemEvent::OpenItem(app_id) = event {
|
||||||
find_desktop_file(&app_id).map_or_else(
|
find_desktop_file(&app_id).map_or_else(
|
||||||
|| error!("Could not find desktop file for {}", app_id),
|
|| error!("Could not find desktop file for {}", app_id),
|
||||||
|
@ -295,11 +280,12 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(id) = id {
|
if let Some(id) = id {
|
||||||
let toplevels = read_lock!(wl.toplevels);
|
if let Some(window) =
|
||||||
let seat = wl.seats.first().expect("Failed to get Wayland seat");
|
items.iter().find_map(|(_, item)| item.windows.get(&id))
|
||||||
if let Some((_top, handle)) = toplevels.get(&id) {
|
{
|
||||||
handle.activate(seat);
|
let seat = wl.get_seats().pop().expect("Failed to get Wayland seat");
|
||||||
};
|
window.focus(&seat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// roundtrip to immediately send activate event
|
// roundtrip to immediately send activate event
|
||||||
|
@ -456,7 +442,7 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
|
|
||||||
{
|
{
|
||||||
let tx = controller_tx.clone();
|
let tx = controller_tx.clone();
|
||||||
button.connect_clicked(move |button| {
|
button.connect_clicked(move |_| {
|
||||||
try_send!(tx, ItemEvent::FocusWindow(win.id));
|
try_send!(tx, ItemEvent::FocusWindow(win.id));
|
||||||
|
|
||||||
if let Some(win) = button.window() {
|
if let Some(win) = button.window() {
|
||||||
|
@ -548,6 +534,9 @@ impl Module<gtk::Box> for LauncherModule {
|
||||||
/// This is a hacky number derived from
|
/// This is a hacky number derived from
|
||||||
/// "what fits inside the 250px popup"
|
/// "what fits inside the 250px popup"
|
||||||
/// and probably won't hold up with wide fonts.
|
/// and probably won't hold up with wide fonts.
|
||||||
|
///
|
||||||
|
/// TODO: Migrate this to truncate system
|
||||||
|
///
|
||||||
fn clamp(str: &str) -> String {
|
fn clamp(str: &str) -> String {
|
||||||
const MAX_CHARS: usize = 24;
|
const MAX_CHARS: usize = 24;
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,15 @@ pub enum OpenState {
|
||||||
Open { focused: bool },
|
Open { focused: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenState {
|
impl From<&ToplevelInfo> for OpenState {
|
||||||
/// Creates from `SwayNode`
|
fn from(info: &ToplevelInfo) -> Self {
|
||||||
pub const fn from_toplevel(toplevel: &ToplevelInfo) -> Self {
|
|
||||||
Self::Open {
|
Self::Open {
|
||||||
focused: toplevel.active,
|
focused: info.focused,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenState {
|
||||||
/// Creates open with focused
|
/// Creates open with focused
|
||||||
pub const fn focused(focused: bool) -> Self {
|
pub const fn focused(focused: bool) -> Self {
|
||||||
Self::Open { focused }
|
Self::Open { focused }
|
||||||
|
|
Loading…
Add table
Reference in a new issue