mirror of
https://github.com/JakeStanger/ironbar.git
synced 2024-11-22 05:34:35 +03:00
Merge pull request #130 from JakeStanger/refactor/wayland-0.30
Update Wayland libraries
This commit is contained in:
commit
528a8d6dd6
123
Cargo.lock
generated
123
Cargo.lock
generated
@ -1555,6 +1555,7 @@ dependencies = [
|
||||
"walkdir",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"zbus 3.11.1",
|
||||
]
|
||||
|
||||
@ -1824,18 +1825,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "nix"
|
||||
version = "0.25.1"
|
||||
@ -2222,6 +2211,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "quote"
|
||||
version = "0.3.15"
|
||||
@ -2451,6 +2449,12 @@ dependencies = [
|
||||
"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]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
@ -2649,9 +2653,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.16.0"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454"
|
||||
checksum = "e1476c3d89bb67079264b88aaf4f14358353318397e083b7c4e8c14517f55de7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"calloop",
|
||||
@ -2659,11 +2663,14 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memmap2",
|
||||
"nix 0.24.3",
|
||||
"pkg-config",
|
||||
"nix 0.26.2",
|
||||
"thiserror",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3408,72 +3415,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.29.5"
|
||||
name = "wayland-backend"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
|
||||
checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"libc",
|
||||
"nix 0.24.3",
|
||||
"wayland-commons",
|
||||
"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",
|
||||
"io-lifetimes",
|
||||
"nix 0.26.2",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-cursor"
|
||||
version = "0.29.5"
|
||||
name = "wayland-client"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
|
||||
checksum = "85bde68449abab1a808e5227b6e295f4ae3680911eb7711b4a2cb90141edb780"
|
||||
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",
|
||||
"xcursor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.29.5"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
|
||||
checksum = "7fefbeb8a360abe67ab7c2efe1d297a1a50ee011f5460791bc18870c26bb84e2"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"wayland-backend",
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.29.5"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
|
||||
checksum = "4834c14b3edf1d9986c83ca79b1e7e3afbe9874c7c144702f6467063259ce45d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quote 1.0.26",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.29.5"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
|
||||
checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
@ -3701,12 +3724,6 @@ dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "2.3.2"
|
||||
|
@ -62,9 +62,10 @@ indexmap = "1.9.1"
|
||||
dirs = "5.0.0"
|
||||
walkdir = "2.3.2"
|
||||
notify = { version = "5.0.0", default-features = false }
|
||||
wayland-client = "0.29.5"
|
||||
wayland-protocols = { version = "0.29.5", features = ["unstable_protocols", "client"] }
|
||||
smithay-client-toolkit = { version = "0.16.0", default-features = false, features = ["calloop"] }
|
||||
wayland-client = "0.30.0"
|
||||
wayland-protocols = { version = "0.30.0", features = ["unstable", "client"] }
|
||||
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 }
|
||||
|
||||
lazy_static = "1.4.0"
|
||||
@ -75,7 +76,7 @@ cfg-if = "1.0.0"
|
||||
reqwest = { version = "0.11.14", optional = true }
|
||||
|
||||
# clipboard
|
||||
nix = { version = "0.26.2", optional = true }
|
||||
nix = { version = "0.26.2", optional = true, features = ["event"] }
|
||||
|
||||
# clock
|
||||
chrono = { version = "0.4.19", optional = true }
|
||||
|
@ -6,7 +6,7 @@ use lazy_static::lazy_static;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::spawn;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::debug;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClipboardEvent {
|
||||
@ -26,6 +26,8 @@ pub struct ClipboardClient {
|
||||
|
||||
impl ClipboardClient {
|
||||
fn new() -> Self {
|
||||
trace!("Initializing clipboard client");
|
||||
|
||||
let senders = Arc::new(Mutex::new(Vec::<(EventSender, usize)>::new()));
|
||||
|
||||
let cache = Arc::new(Mutex::new(ClipboardCache::new()));
|
||||
@ -35,11 +37,21 @@ impl ClipboardClient {
|
||||
let cache = cache.clone();
|
||||
|
||||
spawn(async move {
|
||||
let mut rx = {
|
||||
let (mut rx, item) = {
|
||||
let wl = wayland::get_client().await;
|
||||
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 {
|
||||
debug!("Received clipboard item (ID: {})", item.id);
|
||||
|
||||
@ -59,7 +71,6 @@ impl ClipboardClient {
|
||||
let iter = senders.iter();
|
||||
for (tx, sender_cache_size) in iter {
|
||||
if cache_size == *sender_cache_size {
|
||||
// let mut cache = lock!(cache);
|
||||
let removed_id = lock!(cache)
|
||||
.remove_ref_first()
|
||||
.expect("Clipboard cache unexpectedly empty");
|
||||
@ -83,18 +94,11 @@ impl ClipboardClient {
|
||||
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 wl = wayland::get_client().await;
|
||||
wl.roundtrip();
|
||||
|
||||
{
|
||||
let mut cache = lock!(self.cache);
|
||||
|
||||
if let Some(item) = wl.get_clipboard() {
|
||||
cache.insert_or_inc_ref(item);
|
||||
}
|
||||
let cache = lock!(self.cache);
|
||||
|
||||
let iter = cache.iter();
|
||||
for (_, (item, _)) in iter {
|
||||
@ -102,10 +106,7 @@ impl ClipboardClient {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut senders = lock!(self.senders);
|
||||
senders.push((tx, cache_size));
|
||||
}
|
||||
lock!(self.senders).push((tx, cache_size));
|
||||
|
||||
rx
|
||||
}
|
||||
@ -171,13 +172,6 @@ impl ClipboardCache {
|
||||
.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`.
|
||||
/// This ignores references.
|
||||
fn remove(&mut self, id: usize) -> Option<Arc<ClipboardItem>> {
|
||||
|
@ -5,6 +5,7 @@ use color_eyre::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
|
||||
use std::collections::HashSet;
|
||||
use std::string;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
@ -259,7 +260,7 @@ impl From<Metadata> for Track {
|
||||
.and_then(mpris::MetadataValue::as_str_array)
|
||||
.and_then(|arr| arr.first().map(|val| (*val).to_string())),
|
||||
track: value.track_number().map(|track| track as u64),
|
||||
cover_path: value.art_url().map(|s| s.to_string()),
|
||||
cover_path: value.art_url().map(string::ToString::to_string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,79 +1,90 @@
|
||||
use super::wlr_foreign_toplevel::{
|
||||
handle::{ToplevelEvent, ToplevelInfo},
|
||||
manager::listen_for_toplevels,
|
||||
};
|
||||
use super::{DData, Env, ToplevelHandler};
|
||||
use crate::{error as err, send};
|
||||
use super::wlr_foreign_toplevel::handle::ToplevelHandle;
|
||||
use super::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
||||
use super::wlr_foreign_toplevel::ToplevelEvent;
|
||||
use super::Environment;
|
||||
use crate::error::ERR_CHANNEL_RECV;
|
||||
use crate::send;
|
||||
use cfg_if::cfg_if;
|
||||
use color_eyre::Report;
|
||||
use indexmap::IndexMap;
|
||||
use smithay_client_toolkit::environment::Environment;
|
||||
use smithay_client_toolkit::output::{with_output_info, OutputInfo};
|
||||
use smithay_client_toolkit::output::{OutputInfo, OutputState};
|
||||
use smithay_client_toolkit::reexports::calloop::channel::{channel, Event, Sender};
|
||||
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::sync::{Arc, RwLock};
|
||||
use tokio::sync::{broadcast, oneshot};
|
||||
use std::sync::mpsc;
|
||||
use tokio::sync::broadcast;
|
||||
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::{ConnectError, Display, EventQueue};
|
||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{
|
||||
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
||||
zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1,
|
||||
};
|
||||
use wayland_client::{Connection, WaylandSource};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "clipboard")] {
|
||||
use super::{ClipboardItem};
|
||||
use super::wlr_data_control::manager::{listen_to_devices, DataControlDeviceHandler};
|
||||
use crate::{read_lock, write_lock};
|
||||
use tokio::spawn;
|
||||
use super::ClipboardItem;
|
||||
use super::wlr_data_control::manager::DataControlDeviceManagerState;
|
||||
use crate::lock;
|
||||
use std::sync::{Arc, Mutex};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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
|
||||
#[cfg(feature = "clipboard")]
|
||||
CopyToClipboard(Arc<ClipboardItem>),
|
||||
/// Forces a dispatch, flushing any currently queued events
|
||||
Refresh,
|
||||
Roundtrip,
|
||||
}
|
||||
|
||||
pub struct WaylandClient {
|
||||
pub outputs: Vec<OutputInfo>,
|
||||
pub seats: Vec<WlSeat>,
|
||||
|
||||
pub toplevels: Arc<RwLock<IndexMap<usize, (ToplevelInfo, ZwlrForeignToplevelHandleV1)>>>,
|
||||
// External channels
|
||||
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
||||
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||
#[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>,
|
||||
}
|
||||
|
||||
impl WaylandClient {
|
||||
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 toplevels = Arc::new(RwLock::new(IndexMap::new()));
|
||||
let toplevels2 = toplevels.clone();
|
||||
let (toplevel_init_tx, toplevel_init_rx) = mpsc::channel();
|
||||
#[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();
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "clipboard")] {
|
||||
let (clipboard_tx, mut clipboard_rx) = broadcast::channel(32);
|
||||
let clipboard = Arc::new(RwLock::new(None));
|
||||
let (clipboard_tx, clipboard_rx) = broadcast::channel(32);
|
||||
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
|
||||
spawn_blocking(move || {
|
||||
let toplevels = toplevels2;
|
||||
let toplevel_tx = toplevel_tx2;
|
||||
#[cfg(feature = "clipboard")]
|
||||
let clipboard_tx = clipboard_tx2;
|
||||
|
||||
let (env, _display, queue) =
|
||||
Self::new_environment().expect("Failed to connect to Wayland compositor");
|
||||
let conn =
|
||||
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 =
|
||||
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)
|
||||
.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");
|
||||
|
||||
let outputs = Self::get_outputs(&env);
|
||||
send!(output_tx, outputs);
|
||||
let loop_handle = event_loop.handle();
|
||||
|
||||
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")]
|
||||
let default_seat = seats[0].detach();
|
||||
let data_control_device_manager_delegate =
|
||||
DataControlDeviceManagerState::bind(&globals, &qh)
|
||||
.expect("data device manager is not available");
|
||||
|
||||
send!(
|
||||
seat_tx,
|
||||
seats
|
||||
.into_iter()
|
||||
.map(|seat| seat.detach())
|
||||
.collect::<Vec<WlSeat>>()
|
||||
);
|
||||
let foreign_toplevel_manager_delegate = ToplevelManagerState::bind(&globals, &qh)
|
||||
.expect("foreign toplevel manager is not available");
|
||||
|
||||
let handle = event_loop.handle();
|
||||
handle
|
||||
.insert_source(ev_rx, move |event, _metadata, ddata| {
|
||||
// let env = &ddata.env;
|
||||
let mut env = Environment {
|
||||
registry_state,
|
||||
output_state: output_delegate,
|
||||
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 {
|
||||
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")]
|
||||
Event::Msg(Request::CopyToClipboard(value)) => {
|
||||
super::wlr_data_control::copy_to_clipboard(
|
||||
&ddata.env,
|
||||
&default_seat,
|
||||
&value,
|
||||
)
|
||||
.expect("Failed to copy to clipboard");
|
||||
env.copy_to_clipboard(value, &qh);
|
||||
}
|
||||
Event::Closed => panic!("Channel unexpectedly closed"),
|
||||
}
|
||||
})
|
||||
.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 {
|
||||
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!(
|
||||
"{:?}",
|
||||
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 {
|
||||
outputs,
|
||||
seats,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard,
|
||||
toplevels,
|
||||
toplevel_tx,
|
||||
_toplevel_rx: toplevel_rx,
|
||||
toplevel_init_rx,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_init_rx,
|
||||
output_rx,
|
||||
seat_rx,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_tx,
|
||||
#[cfg(feature = "clipboard")]
|
||||
_clipboard_rx: clipboard_rx,
|
||||
request_tx: ev_tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe_toplevels(&self) -> broadcast::Receiver<ToplevelEvent> {
|
||||
self.toplevel_tx.subscribe()
|
||||
pub fn subscribe_toplevels(
|
||||
&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")]
|
||||
pub fn subscribe_clipboard(&self) -> broadcast::Receiver<Arc<ClipboardItem>> {
|
||||
self.clipboard_tx.subscribe()
|
||||
pub fn subscribe_clipboard(
|
||||
&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) {
|
||||
send!(self.request_tx, Request::Refresh);
|
||||
trace!("Sending roundtrip request");
|
||||
send!(self.request_tx, Request::Roundtrip);
|
||||
}
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub fn get_clipboard(&self) -> Option<Arc<ClipboardItem>> {
|
||||
let clipboard = read_lock!(self.clipboard);
|
||||
clipboard.as_ref().cloned()
|
||||
pub fn get_outputs(&self) -> Vec<OutputInfo> {
|
||||
trace!("Sending get outputs request");
|
||||
|
||||
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")]
|
||||
pub fn copy_to_clipboard(&self, item: Arc<ClipboardItem>) {
|
||||
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 macros;
|
||||
mod wl_output;
|
||||
mod wl_seat;
|
||||
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 lazy_static::lazy_static;
|
||||
use std::fmt::Debug;
|
||||
use cfg_if::cfg_if;
|
||||
use smithay_client_toolkit::default_environment;
|
||||
use smithay_client_toolkit::environment::Environment;
|
||||
use smithay_client_toolkit::reexports::calloop::RegistrationToken;
|
||||
use wayland_client::{Attached, Interface};
|
||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1;
|
||||
pub use wlr_foreign_toplevel::handle::{ToplevelChange, ToplevelEvent, ToplevelInfo};
|
||||
use wlr_foreign_toplevel::manager::{ToplevelHandler};
|
||||
use lazy_static::lazy_static;
|
||||
use smithay_client_toolkit::output::OutputState;
|
||||
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
||||
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
|
||||
use smithay_client_toolkit::seat::SeatState;
|
||||
use smithay_client_toolkit::{
|
||||
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! {
|
||||
if #[cfg(feature = "clipboard")] {
|
||||
mod wlr_data_control;
|
||||
|
||||
use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
||||
use wlr_data_control::manager::DataControlDeviceHandler;
|
||||
use crate::{delegate_data_control_device, delegate_data_control_device_manager, delegate_data_control_offer, delegate_data_control_source};
|
||||
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 struct DataControlDeviceEntry {
|
||||
seat: WlSeat,
|
||||
device: DataControlDevice,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A utility for lazy-loading globals.
|
||||
/// Taken from `smithay_client_toolkit` where it's not exposed
|
||||
#[derive(Debug)]
|
||||
enum LazyGlobal<I: Interface> {
|
||||
Unknown,
|
||||
Seen { id: u32, version: u32 },
|
||||
Bound(Attached<I>),
|
||||
pub struct Environment {
|
||||
pub registry_state: RegistryState,
|
||||
pub output_state: OutputState,
|
||||
pub seat_state: SeatState,
|
||||
pub foreign_toplevel_manager_state: ToplevelManagerState,
|
||||
#[cfg(feature = "clipboard")]
|
||||
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 {
|
||||
env: Environment<Env>,
|
||||
offer_tokens: HashMap<u128, RegistrationToken>,
|
||||
}
|
||||
// Now we need to say we are delegating the responsibility of output related events for our application data
|
||||
// type to the requisite delegate.
|
||||
delegate_output!(Environment);
|
||||
delegate_seat!(Environment);
|
||||
|
||||
delegate_foreign_toplevel_manager!(Environment);
|
||||
delegate_foreign_toplevel_handle!(Environment);
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "clipboard")] {
|
||||
default_environment!(Env,
|
||||
fields = [
|
||||
toplevel: ToplevelHandler,
|
||||
data_control_device: DataControlDeviceHandler
|
||||
],
|
||||
singles = [
|
||||
ZwlrForeignToplevelManagerV1 => toplevel,
|
||||
ZwlrDataControlManagerV1 => data_control_device
|
||||
],
|
||||
);
|
||||
} else {
|
||||
default_environment!(Env,
|
||||
fields = [
|
||||
toplevel: ToplevelHandler,
|
||||
],
|
||||
singles = [
|
||||
ZwlrForeignToplevelManagerV1 => toplevel,
|
||||
],
|
||||
);
|
||||
delegate_data_control_device_manager!(Environment);
|
||||
delegate_data_control_device!(Environment);
|
||||
delegate_data_control_source!(Environment);
|
||||
delegate_data_control_offer!(Environment);
|
||||
}
|
||||
}
|
||||
|
||||
// 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! {
|
||||
static ref CLIENT: AsyncOnce<WaylandClient> =
|
||||
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::source::DataControlSource;
|
||||
use super::manager::DataControlDeviceManagerState;
|
||||
use super::offer::{
|
||||
DataControlOfferData, DataControlOfferDataExt, DataControlOfferHandler, SelectionOffer,
|
||||
};
|
||||
use crate::error::ERR_WAYLAND_DATA;
|
||||
use crate::lock;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{Attached, DispatchData, Main};
|
||||
use wayland_protocols::wlr::unstable::data_control::v1::client::{
|
||||
use tracing::warn;
|
||||
use wayland_client::{event_created_child, Connection, Dispatch, Proxy, QueueHandle};
|
||||
use wayland_protocols_wlr::data_control::v1::client::{
|
||||
zwlr_data_control_device_v1::{Event, ZwlrDataControlDeviceV1},
|
||||
zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
|
||||
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 {
|
||||
device: ZwlrDataControlDeviceV1,
|
||||
_inner: Arc<Mutex<Inner>>,
|
||||
pub device: ZwlrDataControlDeviceV1,
|
||||
}
|
||||
|
||||
impl DataControlDevice {
|
||||
pub fn init_for_seat<F>(
|
||||
manager: &Attached<ZwlrDataControlManagerV1>,
|
||||
seat: &WlSeat,
|
||||
mut callback: F,
|
||||
) -> Self
|
||||
where
|
||||
F: FnMut(DataControlDeviceEvent, DispatchData) + 'static,
|
||||
{
|
||||
let inner = Arc::new(Mutex::new(Inner { offer: None }));
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DataControlDeviceInner {
|
||||
/// the active selection offer and its data
|
||||
selection_offer: Arc<Mutex<Option<ZwlrDataControlOfferV1>>>,
|
||||
/// the active undetermined offers and their data
|
||||
pub undetermined_offers: Arc<Mutex<Vec<ZwlrDataControlOfferV1>>>,
|
||||
}
|
||||
|
||||
let device = manager.get_data_device(seat);
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DataControlDeviceData {
|
||||
pub(super) inner: Arc<Mutex<DataControlDeviceInner>>,
|
||||
}
|
||||
|
||||
{
|
||||
let inner = inner.clone();
|
||||
device.quick_assign(move |_handle, event, ddata| {
|
||||
let mut inner = lock!(inner);
|
||||
data_control_device_implem(event, &mut inner, &mut callback, ddata);
|
||||
});
|
||||
}
|
||||
pub trait DataControlDeviceDataExt: Send + Sync {
|
||||
type DataControlOfferInner: DataControlOfferDataExt + Send + Sync + 'static;
|
||||
|
||||
Self {
|
||||
device: device.detach(),
|
||||
_inner: inner,
|
||||
}
|
||||
fn data_control_device_data(&self) -> &DataControlDeviceData;
|
||||
|
||||
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>) {
|
||||
self.device
|
||||
.set_selection(source.as_ref().map(|s| &s.source));
|
||||
/// Get the active selection offer if it exists.
|
||||
fn selection_offer(&self) -> Option<SelectionOffer> {
|
||||
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::source::DataControlSource;
|
||||
use smithay_client_toolkit::data_device::WritePipe;
|
||||
use smithay_client_toolkit::environment::{Environment, GlobalHandler};
|
||||
use smithay_client_toolkit::seat::{SeatHandling, SeatListener};
|
||||
use smithay_client_toolkit::MissingGlobal;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::{self, Rc};
|
||||
use tracing::warn;
|
||||
use wayland_client::protocol::wl_registry::WlRegistry;
|
||||
use super::device::{DataControlDevice, DataControlDeviceData, DataControlDeviceDataExt};
|
||||
use super::offer::DataControlOfferData;
|
||||
use super::source::{CopyPasteSource, DataControlSourceData, DataControlSourceDataExt};
|
||||
use smithay_client_toolkit::error::GlobalError;
|
||||
use smithay_client_toolkit::globals::{GlobalData, ProvidesBoundGlobal};
|
||||
use std::marker::PhantomData;
|
||||
use tracing::debug;
|
||||
use wayland_client::globals::{BindError, GlobalList};
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{Attached, DispatchData};
|
||||
use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1;
|
||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||
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 {
|
||||
Ready {
|
||||
manager: Attached<ZwlrDataControlManagerV1>,
|
||||
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>>>>>,
|
||||
},
|
||||
pub struct DataControlDeviceManagerState<V = DataControlOfferData> {
|
||||
manager: ZwlrDataControlManagerV1,
|
||||
_phantom: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl DataControlDeviceHandlerInner {
|
||||
fn init_manager(&mut self, manager: Attached<ZwlrDataControlManagerV1>) {
|
||||
let (seats, status_listeners) = if let Self::Pending {
|
||||
seats,
|
||||
status_listeners,
|
||||
} = self
|
||||
{
|
||||
(std::mem::take(seats), status_listeners.clone())
|
||||
} 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 {
|
||||
impl DataControlDeviceManagerState {
|
||||
pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError>
|
||||
where
|
||||
State: Dispatch<ZwlrDataControlManagerV1, GlobalData, State> + 'static,
|
||||
{
|
||||
let manager = globals.bind(qh, 1..=2, GlobalData)?;
|
||||
debug!("Bound to ZwlDataControlManagerV1 global");
|
||||
Ok(Self {
|
||||
manager,
|
||||
devices,
|
||||
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
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
E: DataControlDeviceHandling,
|
||||
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static,
|
||||
D: Dispatch<ZwlrDataControlManagerV1, GlobalData>,
|
||||
{
|
||||
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,28 @@ pub mod manager;
|
||||
pub mod offer;
|
||||
pub mod source;
|
||||
|
||||
use super::Env;
|
||||
use crate::clients::wayland::DData;
|
||||
use crate::send;
|
||||
use color_eyre::Report;
|
||||
use device::{DataControlDevice, DataControlDeviceEvent};
|
||||
use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
|
||||
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer};
|
||||
use self::source::DataControlSourceHandler;
|
||||
use crate::clients::wayland::Environment;
|
||||
use crate::{lock, send};
|
||||
use device::DataControlDevice;
|
||||
use glib::Bytes;
|
||||
use manager::{DataControlDeviceHandling, DataControlDeviceStatusListener};
|
||||
use smithay_client_toolkit::data_device::WritePipe;
|
||||
use smithay_client_toolkit::environment::Environment;
|
||||
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
||||
use smithay_client_toolkit::MissingGlobal;
|
||||
use source::DataControlSource;
|
||||
use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ};
|
||||
use nix::sys::epoll::{epoll_create, epoll_ctl, epoll_wait, EpollEvent, EpollFlags, EpollOp};
|
||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||
use smithay_client_toolkit::reexports::calloop::RegistrationToken;
|
||||
use std::cmp::min;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::io::{ErrorKind, Read, Write};
|
||||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::UNIX_EPOCH;
|
||||
use tokio::sync::broadcast;
|
||||
use std::{fs, io};
|
||||
use tracing::{debug, error, trace};
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::DispatchData;
|
||||
use wayland_client::{Connection, QueueHandle};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1;
|
||||
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
@ -34,6 +34,11 @@ fn get_id() -> usize {
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub struct SelectionOfferItem {
|
||||
offer: SelectionOffer,
|
||||
token: Option<RegistrationToken>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
pub struct ClipboardItem {
|
||||
pub id: usize,
|
||||
@ -47,77 +52,27 @@ impl PartialEq<Self> for ClipboardItem {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub enum ClipboardValue {
|
||||
Text(String),
|
||||
Image(Bytes),
|
||||
Other,
|
||||
}
|
||||
|
||||
impl DataControlDeviceHandling for Env {
|
||||
fn listen<F>(&mut self, f: F) -> DataControlDeviceStatusListener
|
||||
where
|
||||
F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static,
|
||||
{
|
||||
self.data_control_device.listen(f)
|
||||
}
|
||||
|
||||
fn with_data_control_device<F>(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal>
|
||||
where
|
||||
F: FnOnce(&DataControlDevice),
|
||||
{
|
||||
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:?}");
|
||||
impl Debug for ClipboardValue {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Text(text) => text.clone(),
|
||||
Self::Image(bytes) => {
|
||||
format!("[{} Bytes]", bytes.len())
|
||||
}
|
||||
Self::Other => "[Unknown]".to_string(),
|
||||
}
|
||||
});
|
||||
|
||||
env.with_data_control_device(seat, |device| device.set_selection(&source))
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -133,126 +88,308 @@ enum MimeTypeCategory {
|
||||
}
|
||||
|
||||
impl MimeType {
|
||||
fn parse(mime_types: &[String]) -> Option<Self> {
|
||||
mime_types
|
||||
.iter()
|
||||
.map(|s| s.to_lowercase())
|
||||
.find_map(|mime_type| match mime_type.as_str() {
|
||||
"text"
|
||||
| "string"
|
||||
| "utf8_string"
|
||||
| "text/plain"
|
||||
| "text/plain;charset=utf-8"
|
||||
| "text/plain;charset=iso-8859-1"
|
||||
| "text/plain;charset=us-ascii"
|
||||
| "text/plain;charset=unicode" => Some(Self {
|
||||
value: mime_type,
|
||||
category: MimeTypeCategory::Text,
|
||||
}),
|
||||
"image/png" | "image/jpg" | "image/jpeg" | "image/tiff" | "image/bmp"
|
||||
| "image/x-bmp" | "image/icon" => Some(Self {
|
||||
value: mime_type,
|
||||
category: MimeTypeCategory::Image,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
fn parse(mime_type: &str) -> Option<Self> {
|
||||
match mime_type.to_lowercase().as_str() {
|
||||
"text"
|
||||
| "string"
|
||||
| "utf8_string"
|
||||
| "text/plain"
|
||||
| "text/plain;charset=utf-8"
|
||||
| "text/plain;charset=iso-8859-1"
|
||||
| "text/plain;charset=us-ascii"
|
||||
| "text/plain;charset=unicode" => Some(Self {
|
||||
value: mime_type.to_string(),
|
||||
category: MimeTypeCategory::Text,
|
||||
}),
|
||||
"image/png" | "image/jpg" | "image/jpeg" | "image/tiff" | "image/bmp"
|
||||
| "image/x-bmp" | "image/icon" => Some(Self {
|
||||
value: mime_type.to_string(),
|
||||
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(
|
||||
event: DataControlDeviceEvent,
|
||||
handle: &LoopHandle<DData>,
|
||||
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();
|
||||
impl Environment {
|
||||
pub fn copy_to_clipboard(&mut self, item: Arc<ClipboardItem>, qh: &QueueHandle<Self>) {
|
||||
debug!("Copying item to clipboard: {item:?}");
|
||||
|
||||
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, [INTERNAL_MIME_TYPE, item.mime_type.as_str()]);
|
||||
|
||||
let ddata = ddata
|
||||
.get::<DData>()
|
||||
.expect("Expected dispatch data to exist");
|
||||
source.set_selection(&device.device);
|
||||
self.copy_paste_sources.push(source);
|
||||
|
||||
let handle2 = handle.clone();
|
||||
lock!(self.clipboard).replace(item);
|
||||
}
|
||||
}
|
||||
|
||||
let res = offer.with_mime_types(|mime_types| {
|
||||
debug!("Offer mime types: {mime_types:?}");
|
||||
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> {
|
||||
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()) {
|
||||
debug!("Skipping value provided by bar");
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
let mime_type = MimeType::parse(mime_types);
|
||||
debug!("Detected mime type: {mime_type:?}");
|
||||
if let Some(offer) = data_device.selection_offer() {
|
||||
self.selection_offers
|
||||
.push(SelectionOfferItem { offer, token: None });
|
||||
|
||||
match mime_type {
|
||||
Some(mime_type) => {
|
||||
debug!("[{timestamp}] Sending clipboard read request ({mime_type:?})");
|
||||
let read_pipe = offer.receive(mime_type.value.clone())?;
|
||||
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:?}"),
|
||||
}
|
||||
let cur_offer = self
|
||||
.selection_offers
|
||||
.last_mut()
|
||||
.expect("Failed to get current offer");
|
||||
|
||||
if let Some(src) = ddata.offer_tokens.remove(×tamp) {
|
||||
handle2.remove(src);
|
||||
}
|
||||
})?;
|
||||
|
||||
ddata.offer_tokens.insert(timestamp, source);
|
||||
}
|
||||
None => {
|
||||
let Some(mime_type) = MimeType::parse_multiple(&mime_types) else {
|
||||
lock!(self.clipboard).take();
|
||||
// send an event so the clipboard module is aware it's changed
|
||||
send!(
|
||||
tx,
|
||||
self.clipboard_tx,
|
||||
Arc::new(ClipboardItem {
|
||||
id: usize::MAX,
|
||||
mime_type: String::new(),
|
||||
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> {
|
||||
let value = match mime_type.category {
|
||||
MimeTypeCategory::Text => {
|
||||
let mut txt = String::new();
|
||||
file.read_to_string(&mut txt)?;
|
||||
impl DataControlOfferHandler for Environment {
|
||||
fn offer(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_offer: &mut DataControlDeviceOffer,
|
||||
_mime_type: String,
|
||||
) {
|
||||
debug!("Handler received offer");
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardValue::Text(txt)
|
||||
}
|
||||
MimeTypeCategory::Image => {
|
||||
let mut bytes = vec![];
|
||||
file.read_to_end(&mut bytes)?;
|
||||
let bytes = Bytes::from(&bytes);
|
||||
impl DataControlSourceHandler for Environment {
|
||||
fn accept_mime(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_source: &ZwlrDataControlSourceV1,
|
||||
mime: Option<String>,
|
||||
) {
|
||||
debug!("Accepted mime type: {mime:?}");
|
||||
}
|
||||
|
||||
ClipboardValue::Image(bytes)
|
||||
fn send_request(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
source: &ZwlrDataControlSourceV1,
|
||||
mime: String,
|
||||
write_pipe: WritePipe,
|
||||
) {
|
||||
debug!("Handler received source send request event ({mime})");
|
||||
|
||||
if let Some(item) = lock!(self.clipboard).clone() {
|
||||
let fd = OwnedFd::from(write_pipe);
|
||||
if self
|
||||
.copy_paste_sources
|
||||
.iter_mut()
|
||||
.any(|s| s.inner() == source && MimeType::parse(&mime).is_some())
|
||||
{
|
||||
trace!("Source found, writing to file");
|
||||
|
||||
let mut bytes = match &item.value {
|
||||
ClipboardValue::Text(text) => text.as_bytes(),
|
||||
ClipboardValue::Image(bytes) => bytes.as_ref(),
|
||||
ClipboardValue::Other => panic!(
|
||||
"{:?}",
|
||||
io::Error::new(ErrorKind::Other, "Attempted to copy unsupported mime type",)
|
||||
),
|
||||
};
|
||||
|
||||
let pipe_size = set_pipe_size(fd.as_raw_fd(), bytes.len())
|
||||
.expect("Failed to increase pipe size");
|
||||
let mut file = File::from(fd.try_clone().expect("Failed to clone fd"));
|
||||
|
||||
trace!("Num bytes: {}", bytes.len());
|
||||
|
||||
let mut events = (0..16).map(|_| EpollEvent::empty()).collect::<Vec<_>>();
|
||||
let mut epoll_event = EpollEvent::new(EpollFlags::EPOLLOUT, 0);
|
||||
|
||||
let epoll_fd = epoll_create().unwrap();
|
||||
epoll_ctl(
|
||||
epoll_fd,
|
||||
EpollOp::EpollCtlAdd,
|
||||
fd.as_raw_fd(),
|
||||
&mut epoll_event,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
while !bytes.is_empty() {
|
||||
let chunk = &bytes[..min(pipe_size as usize, bytes.len())];
|
||||
|
||||
trace!("Writing {} bytes ({} remain)", chunk.len(), bytes.len());
|
||||
|
||||
epoll_wait(epoll_fd, &mut events, 100).expect("Failed to wait to epoll");
|
||||
|
||||
match file.write(chunk) {
|
||||
Ok(_) => bytes = &bytes[chunk.len()..],
|
||||
Err(err) => {
|
||||
error!("{err:?}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for chunk in bytes.chunks(pipe_size as usize) {
|
||||
// trace!("Writing chunk");
|
||||
// file.write(chunk).expect("Failed to write chunk to buffer");
|
||||
// file.flush().expect("Failed to flush to file");
|
||||
// }
|
||||
|
||||
// match file.write_vectored(&bytes.chunks(pipe_size as usize).map(IoSlice::new).collect::<Vec<_>>()) {
|
||||
// Ok(_) => debug!("Copied item"),
|
||||
// Err(err) => error!("{err:?}"),
|
||||
// }
|
||||
|
||||
// match file.write_all(bytes) {
|
||||
// Ok(_) => debug!("Copied item"),
|
||||
// Err(err) => error!("{err:?}"),
|
||||
// }
|
||||
} else {
|
||||
error!("Failed to find source");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to increase the fd pipe size to the requested number of bytes.
|
||||
/// The kernel will automatically round this up to the nearest page size.
|
||||
/// If the requested size is larger than the kernel max (normally 1MB),
|
||||
/// it will be clamped at this.
|
||||
///
|
||||
/// Returns the new size if succeeded
|
||||
fn set_pipe_size(fd: RawFd, size: usize) -> io::Result<i32> {
|
||||
// clamp size at kernel max
|
||||
let max_pipe_size = fs::read_to_string("/proc/sys/fs/pipe-max-size")
|
||||
.expect("Failed to find pipe-max-size virtual kernel file")
|
||||
.trim()
|
||||
.parse::<usize>()
|
||||
.expect("Failed to parse pipe-max-size contents");
|
||||
|
||||
let size = min(size, max_pipe_size);
|
||||
|
||||
let curr_size = fcntl(fd, F_GETPIPE_SZ)? as usize;
|
||||
|
||||
trace!("Current pipe size: {curr_size}");
|
||||
|
||||
let new_size = if size > curr_size {
|
||||
trace!("Requesting pipe size increase to (at least): {size}");
|
||||
let res = fcntl(fd, F_SETPIPE_SZ(size as i32))?;
|
||||
trace!("New pipe size: {res}");
|
||||
if res < size as i32 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
res
|
||||
} else {
|
||||
size as i32
|
||||
};
|
||||
|
||||
Ok(ClipboardItem {
|
||||
id: get_id(),
|
||||
value,
|
||||
mime_type: mime_type.value.clone(),
|
||||
})
|
||||
Ok(new_size)
|
||||
}
|
||||
|
@ -1,74 +1,183 @@
|
||||
use super::manager::DataControlDeviceManagerState;
|
||||
use crate::lock;
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::unistd::{close, pipe2};
|
||||
use smithay_client_toolkit::data_device::ReadPipe;
|
||||
use std::io;
|
||||
use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError;
|
||||
use smithay_client_toolkit::data_device_manager::ReadPipe;
|
||||
use std::ops::DerefMut;
|
||||
use std::os::fd::FromRawFd;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tracing::warn;
|
||||
use wayland_client::Main;
|
||||
use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_offer_v1::{
|
||||
use tracing::{debug, warn};
|
||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::{
|
||||
Event, ZwlrDataControlOfferV1,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Inner {
|
||||
mime_types: Vec<String>,
|
||||
pub struct UndeterminedOffer {
|
||||
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)]
|
||||
pub struct DataControlOffer {
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
pub(crate) offer: ZwlrDataControlOfferV1,
|
||||
pub struct SelectionOffer {
|
||||
pub data_offer: ZwlrDataControlOfferV1,
|
||||
}
|
||||
|
||||
impl DataControlOffer {
|
||||
pub(crate) fn new(offer: &Main<ZwlrDataControlOfferV1>) -> Self {
|
||||
let inner = Arc::new(Mutex::new(Inner {
|
||||
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 PartialEq for SelectionOffer {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.data_offer == other.data_offer
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DataControlOffer {
|
||||
fn drop(&mut self) {
|
||||
self.offer.destroy();
|
||||
impl SelectionOffer {
|
||||
pub fn receive(&self, mime_type: String) -> Result<ReadPipe, DataOfferError> {
|
||||
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 {
|
||||
debug!("Adding new offer with type '{mime_type}'");
|
||||
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 std::os::fd::FromRawFd;
|
||||
use wayland_client::{Attached, DispatchData};
|
||||
use wayland_protocols::wlr::unstable::data_control::v1::client::{
|
||||
zwlr_data_control_manager_v1::ZwlrDataControlManagerV1,
|
||||
zwlr_data_control_source_v1::{Event, ZwlrDataControlSourceV1},
|
||||
use super::device::DataControlDevice;
|
||||
use super::manager::DataControlDeviceManagerState;
|
||||
use smithay_client_toolkit::data_device_manager::WritePipe;
|
||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::{
|
||||
Event, ZwlrDataControlSourceV1,
|
||||
};
|
||||
|
||||
fn data_control_source_impl<F>(
|
||||
source: &ZwlrDataControlSourceV1,
|
||||
event: Event,
|
||||
implem: &mut F,
|
||||
ddata: DispatchData,
|
||||
) where
|
||||
F: FnMut(String, WritePipe, DispatchData),
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DataControlSourceData {}
|
||||
|
||||
pub trait DataControlSourceDataExt: Send + Sync {
|
||||
fn data_source_data(&self) -> &DataControlSourceData;
|
||||
}
|
||||
|
||||
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 {
|
||||
Event::Send { mime_type, fd } => {
|
||||
let pipe = unsafe { FromRawFd::from_raw_fd(fd) };
|
||||
implem(mime_type, pipe, ddata);
|
||||
}
|
||||
Event::Cancelled => source.destroy(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataControlSource {
|
||||
pub(crate) source: ZwlrDataControlSourceV1,
|
||||
}
|
||||
|
||||
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(),
|
||||
fn event(
|
||||
state: &mut D,
|
||||
source: &ZwlrDataControlSourceV1,
|
||||
event: <ZwlrDataControlSourceV1 as Proxy>::Event,
|
||||
_data: &U,
|
||||
conn: &Connection,
|
||||
qh: &QueueHandle<D>,
|
||||
) {
|
||||
match event {
|
||||
Event::Send { mime_type, fd } => {
|
||||
state.send_request(conn, qh, source, mime_type, fd.into());
|
||||
}
|
||||
Event::Cancelled => {
|
||||
state.cancelled(conn, qh, source);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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::sync::{Arc, RwLock};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tracing::trace;
|
||||
use wayland_client::{DispatchData, Main};
|
||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{Event, ZwlrForeignToplevelHandleV1};
|
||||
use crate::write_lock;
|
||||
|
||||
const STATE_ACTIVE: u32 = 2;
|
||||
const STATE_FULLSCREEN: u32 = 3;
|
||||
use wayland_client::protocol::wl_output::WlOutput;
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
|
||||
use wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{
|
||||
Event, ZwlrForeignToplevelHandleV1,
|
||||
};
|
||||
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
@ -15,138 +17,168 @@ fn get_id() -> usize {
|
||||
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 id: usize,
|
||||
pub app_id: String,
|
||||
pub title: String,
|
||||
pub active: bool,
|
||||
pub fullscreen: bool,
|
||||
|
||||
ready: bool,
|
||||
pub focused: bool,
|
||||
}
|
||||
|
||||
impl ToplevelInfo {
|
||||
fn new() -> Self {
|
||||
let id = get_id();
|
||||
impl Default for ToplevelInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id,
|
||||
..Default::default()
|
||||
id: get_id(),
|
||||
app_id: String::new(),
|
||||
title: String::new(),
|
||||
fullscreen: false,
|
||||
focused: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Toplevel;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToplevelEvent {
|
||||
pub toplevel: ToplevelInfo,
|
||||
pub change: ToplevelChange,
|
||||
pub trait ToplevelHandleDataExt {
|
||||
fn toplevel_handle_data(&self) -> &ToplevelHandleData;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ToplevelChange {
|
||||
New,
|
||||
Close,
|
||||
Title(String),
|
||||
Focus(bool),
|
||||
Fullscreen(bool),
|
||||
impl ToplevelHandleDataExt for ToplevelHandleData {
|
||||
fn toplevel_handle_data(&self) -> &ToplevelHandleData {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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 {
|
||||
Event::AppId { app_id } => {
|
||||
info.app_id = app_id;
|
||||
None
|
||||
}
|
||||
Event::Title { title } => {
|
||||
info.title = title.clone();
|
||||
let data = data.toplevel_handle_data();
|
||||
|
||||
if info.ready {
|
||||
Some(ToplevelChange::Title(title))
|
||||
} else {
|
||||
None
|
||||
trace!("Processing handle event: {event:?}");
|
||||
|
||||
match event {
|
||||
Event::Title { title } => {
|
||||
lock!(data.inner).pending_info.title = title;
|
||||
}
|
||||
}
|
||||
Event::State { state } => {
|
||||
// state is received as a `Vec<u8>` where every 4 bytes make up a `u32`
|
||||
// the u32 then represents a value in the `State` enum.
|
||||
assert_eq!(state.len() % 4, 0);
|
||||
Event::AppId { app_id } => lock!(data.inner).pending_info.app_id = app_id,
|
||||
Event::State { state } => {
|
||||
// state is received as a `Vec<u8>` where every 4 bytes make up a `u32`
|
||||
// the u32 then represents a value in the `State` enum.
|
||||
assert_eq!(state.len() % 4, 0);
|
||||
let state = (0..state.len() / 4)
|
||||
.map(|i| {
|
||||
let slice: [u8; 4] = state[i * 4..i * 4 + 4]
|
||||
.try_into()
|
||||
.expect("Received invalid state length");
|
||||
u32::from_le_bytes(slice)
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let state = (0..state.len() / 4)
|
||||
.map(|i| {
|
||||
let slice: [u8; 4] = state[i * 4..i * 4 + 4]
|
||||
.try_into()
|
||||
.expect("Received invalid state length");
|
||||
u32::from_le_bytes(slice)
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let new_active = state.contains(&STATE_ACTIVE);
|
||||
let new_fullscreen = state.contains(&STATE_FULLSCREEN);
|
||||
|
||||
let change = if info.ready && new_active != info.active {
|
||||
Some(ToplevelChange::Focus(new_active))
|
||||
} else if info.ready && new_fullscreen != info.fullscreen {
|
||||
Some(ToplevelChange::Fullscreen(new_fullscreen))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
info.active = new_active;
|
||||
info.fullscreen = new_fullscreen;
|
||||
|
||||
change
|
||||
}
|
||||
Event::Closed => {
|
||||
if info.ready {
|
||||
Some(ToplevelChange::Close)
|
||||
} else {
|
||||
None
|
||||
lock!(data.inner).pending_info.focused = state.contains(&STATE_ACTIVE);
|
||||
lock!(data.inner).pending_info.fullscreen = state.contains(&STATE_FULLSCREEN);
|
||||
}
|
||||
}
|
||||
Event::OutputEnter { output: _ }
|
||||
| Event::OutputLeave { output: _ }
|
||||
| Event::Parent { parent: _ } => None,
|
||||
Event::Done => {
|
||||
if info.ready || info.app_id.is_empty() {
|
||||
None
|
||||
} else {
|
||||
info.ready = true;
|
||||
Some(ToplevelChange::New)
|
||||
Event::OutputEnter { output } => lock!(data.inner).output = Some(output),
|
||||
Event::OutputLeave { output: _ } => lock!(data.inner).output = None,
|
||||
Event::Closed => state.remove_handle(
|
||||
conn,
|
||||
qh,
|
||||
ToplevelHandle {
|
||||
handle: handle.clone(),
|
||||
},
|
||||
),
|
||||
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!(),
|
||||
};
|
||||
|
||||
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
|
||||
trace!("Event processed");
|
||||
}
|
||||
}
|
||||
|
@ -1,163 +1,86 @@
|
||||
use super::handle::{Toplevel, ToplevelEvent};
|
||||
use crate::wayland::LazyGlobal;
|
||||
use smithay_client_toolkit::environment::{Environment, GlobalHandler};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::{self, Rc};
|
||||
use tracing::warn;
|
||||
use wayland_client::protocol::wl_registry::WlRegistry;
|
||||
use wayland_client::{Attached, DispatchData};
|
||||
use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{
|
||||
use super::handle::{ToplevelHandleData, ToplevelHandleDataExt, ToplevelHandleHandler};
|
||||
use smithay_client_toolkit::error::GlobalError;
|
||||
use smithay_client_toolkit::globals::{GlobalData, ProvidesBoundGlobal};
|
||||
use std::marker::PhantomData;
|
||||
use tracing::{debug, warn};
|
||||
use wayland_client::globals::{BindError, GlobalList};
|
||||
use wayland_client::{event_created_child, Connection, Dispatch, QueueHandle};
|
||||
use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
||||
zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1,
|
||||
zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1},
|
||||
zwlr_foreign_toplevel_manager_v1::{Event, ZwlrForeignToplevelManagerV1},
|
||||
};
|
||||
|
||||
struct ToplevelHandlerInner {
|
||||
manager: LazyGlobal<ZwlrForeignToplevelManagerV1>,
|
||||
registry: Option<Attached<WlRegistry>>,
|
||||
toplevels: Vec<Toplevel>,
|
||||
pub struct ToplevelManagerState<V = ToplevelHandleData> {
|
||||
manager: ZwlrForeignToplevelManagerV1,
|
||||
_phantom: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl ToplevelHandlerInner {
|
||||
const fn new() -> Self {
|
||||
let toplevels = vec![];
|
||||
|
||||
Self {
|
||||
registry: None,
|
||||
manager: LazyGlobal::Unknown,
|
||||
toplevels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ToplevelHandler {
|
||||
inner: Rc<RefCell<ToplevelHandlerInner>>,
|
||||
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<ToplevelStatusCallback>>>>>,
|
||||
}
|
||||
|
||||
impl ToplevelHandler {
|
||||
pub fn init() -> Self {
|
||||
let inner = Rc::new(RefCell::new(ToplevelHandlerInner::new()));
|
||||
|
||||
Self {
|
||||
inner,
|
||||
status_listeners: Rc::new(RefCell::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalHandler<ZwlrForeignToplevelManagerV1> for ToplevelHandler {
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
_ddata: DispatchData,
|
||||
) {
|
||||
let mut inner = RefCell::borrow_mut(&self.inner);
|
||||
if inner.registry.is_none() {
|
||||
inner.registry = Some(registry);
|
||||
}
|
||||
if 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
|
||||
impl ToplevelManagerState {
|
||||
pub fn bind<State>(globals: &GlobalList, qh: &QueueHandle<State>) -> Result<Self, BindError>
|
||||
where
|
||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static;
|
||||
}
|
||||
|
||||
impl ToplevelHandling for ToplevelHandler {
|
||||
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
|
||||
where
|
||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
||||
State: Dispatch<ZwlrForeignToplevelManagerV1, GlobalData, State> + 'static,
|
||||
{
|
||||
let rc = Rc::new(RefCell::new(f)) as Rc<_>;
|
||||
self.status_listeners.borrow_mut().push(Rc::downgrade(&rc));
|
||||
ToplevelStatusListener { _cb: rc }
|
||||
let manager = globals.bind(qh, 1..=3, GlobalData)?;
|
||||
debug!("Bound to ZwlForeignToplevelManagerV1 global");
|
||||
Ok(Self {
|
||||
manager,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen_for_toplevels<E, F>(env: &Environment<E>, f: F) -> ToplevelStatusListener
|
||||
where
|
||||
E: ToplevelHandling,
|
||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
||||
{
|
||||
env.with_inner(move |inner| ToplevelHandling::listen(inner, f))
|
||||
pub trait ToplevelManagerHandler: Sized {
|
||||
/// Advertises a new toplevel.
|
||||
fn toplevel(
|
||||
&mut self,
|
||||
conn: &Connection,
|
||||
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 manager;
|
||||
|
||||
impl ToplevelHandling for Env {
|
||||
fn listen<F>(&mut self, f: F) -> ToplevelStatusListener
|
||||
where
|
||||
F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static,
|
||||
{
|
||||
self.toplevel.listen(f)
|
||||
use self::handle::ToplevelHandleHandler;
|
||||
use self::manager::{ToplevelManagerHandler, ToplevelManagerState};
|
||||
use crate::clients::wayland::Environment;
|
||||
use tracing::{debug, error, trace};
|
||||
use wayland_client::{Connection, QueueHandle};
|
||||
|
||||
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(
|
||||
toplevels: &RwLock<IndexMap<usize, (ToplevelInfo, ZwlrForeignToplevelHandleV1)>>,
|
||||
handle: ZwlrForeignToplevelHandleV1,
|
||||
event: ToplevelEvent,
|
||||
tx: &Sender<ToplevelEvent>,
|
||||
) {
|
||||
trace!("Received toplevel event: {:?}", event);
|
||||
impl ToplevelHandleHandler for Environment {
|
||||
fn new_handle(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, handle: ToplevelHandle) {
|
||||
debug!("Handler received new handle");
|
||||
|
||||
if event.change == ToplevelChange::Close {
|
||||
write_lock!(toplevels).remove(&event.toplevel.id);
|
||||
} else {
|
||||
write_lock!(toplevels).insert(event.toplevel.id, (event.toplevel.clone(), handle));
|
||||
match handle.info() {
|
||||
Some(info) => {
|
||||
trace!("Adding new handle: {info:?}");
|
||||
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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,17 +37,16 @@ pub enum TransitionType {
|
||||
}
|
||||
|
||||
impl TransitionType {
|
||||
pub fn to_revealer_transition_type(&self, orientation: Orientation) -> RevealerTransitionType {
|
||||
pub const fn to_revealer_transition_type(
|
||||
&self,
|
||||
orientation: Orientation,
|
||||
) -> RevealerTransitionType {
|
||||
match (self, orientation) {
|
||||
(TransitionType::SlideStart, Orientation::Horizontal) => {
|
||||
RevealerTransitionType::SlideLeft
|
||||
}
|
||||
(TransitionType::SlideStart, Orientation::Vertical) => RevealerTransitionType::SlideUp,
|
||||
(TransitionType::SlideEnd, Orientation::Horizontal) => {
|
||||
RevealerTransitionType::SlideRight
|
||||
}
|
||||
(TransitionType::SlideEnd, Orientation::Vertical) => RevealerTransitionType::SlideDown,
|
||||
(TransitionType::Crossfade, _) => RevealerTransitionType::Crossfade,
|
||||
(Self::SlideStart, Orientation::Horizontal) => RevealerTransitionType::SlideLeft,
|
||||
(Self::SlideStart, Orientation::Vertical) => RevealerTransitionType::SlideUp,
|
||||
(Self::SlideEnd, Orientation::Horizontal) => RevealerTransitionType::SlideRight,
|
||||
(Self::SlideEnd, Orientation::Vertical) => RevealerTransitionType::SlideDown,
|
||||
(Self::Crossfade, _) => RevealerTransitionType::Crossfade,
|
||||
_ => RevealerTransitionType::None,
|
||||
}
|
||||
}
|
||||
@ -152,7 +151,7 @@ impl CommonConfig {
|
||||
|
||||
revealer.connect_child_revealed_notify(move |revealer| {
|
||||
if !revealer.reveals_child() {
|
||||
container.hide()
|
||||
container.hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -29,13 +29,12 @@ pub fn find_desktop_file(app_id: &str) -> Option<PathBuf> {
|
||||
for dir in dirs {
|
||||
let mut walker = WalkDir::new(dir).max_depth(5).into_iter();
|
||||
|
||||
let entry = walker.find(|entry| match entry {
|
||||
Ok(entry) => {
|
||||
let entry = walker.find(|entry| {
|
||||
entry.as_ref().map_or(false, |entry| {
|
||||
let file_name = entry.file_name().to_string_lossy().to_lowercase();
|
||||
let test_name = format!("{}.desktop", app_id.to_lowercase());
|
||||
file_name == test_name
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
});
|
||||
|
||||
if let Some(Ok(entry)) = entry {
|
||||
|
@ -55,7 +55,7 @@ impl DynamicString {
|
||||
if let OutputStream::Stdout(out) = out {
|
||||
let mut label_parts = lock!(label_parts);
|
||||
|
||||
let _ = std::mem::replace(&mut label_parts[i], out);
|
||||
let _: String = std::mem::replace(&mut label_parts[i], out);
|
||||
|
||||
let string = label_parts.join("");
|
||||
send!(tx, string);
|
||||
|
@ -5,9 +5,11 @@ pub enum ExitCode {
|
||||
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_READ_LOCK: &str = "Failed to get read 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_RECV: &str = "Failed to receive message from channel";
|
||||
|
||||
pub const ERR_WAYLAND_DATA: &str = "Failed to get data for Wayland object";
|
||||
|
@ -58,7 +58,7 @@ fn install_tracing() -> Result<WorkerGuard> {
|
||||
const DEFAULT_LOG: &str = "info";
|
||||
const DEFAULT_FILE_LOG: &str = "warn";
|
||||
|
||||
let fmt_layer = fmt::layer().with_target(true);
|
||||
let fmt_layer = fmt::layer().with_target(true).with_line_number(true);
|
||||
let filter_layer =
|
||||
EnvFilter::try_from_env("IRONBAR_LOG").or_else(|_| EnvFilter::try_new(DEFAULT_LOG))?;
|
||||
|
||||
|
@ -53,9 +53,10 @@ macro_rules! try_send {
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! lock {
|
||||
($mutex:expr) => {
|
||||
($mutex:expr) => {{
|
||||
tracing::trace!("Locking {}", std::stringify!($mutex));
|
||||
$mutex.lock().expect($crate::error::ERR_MUTEX_LOCK)
|
||||
};
|
||||
}};
|
||||
}
|
||||
|
||||
/// Gets a read lock on a `RwLock`.
|
||||
|
@ -127,7 +127,7 @@ fn create_bars(
|
||||
wl: &WaylandClient,
|
||||
config: &Config,
|
||||
) -> Result<()> {
|
||||
let outputs = wl.outputs.as_slice();
|
||||
let outputs = wl.get_outputs();
|
||||
|
||||
debug!("Received {} outputs from Wayland", outputs.len());
|
||||
debug!("Outputs: {:?}", outputs);
|
||||
@ -141,7 +141,8 @@ fn create_bars(
|
||||
let output = outputs
|
||||
.get(i as usize)
|
||||
.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(
|
||||
|| {
|
||||
|
@ -80,7 +80,7 @@ impl Module<Button> for ClipboardModule {
|
||||
spawn(async move {
|
||||
let mut rx = {
|
||||
let client = clipboard::get_client();
|
||||
client.subscribe(max_items).await
|
||||
client.subscribe(max_items)
|
||||
};
|
||||
|
||||
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::image::ImageProvider;
|
||||
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 glib::Continue;
|
||||
use gtk::prelude::*;
|
||||
@ -10,7 +10,7 @@ use gtk::Label;
|
||||
use serde::Deserialize;
|
||||
use tokio::spawn;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tracing::error;
|
||||
use tracing::{debug, error};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct FocusedModule {
|
||||
@ -49,38 +49,36 @@ impl Module<gtk::Box> for FocusedModule {
|
||||
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
_rx: Receiver<Self::ReceiveMessage>,
|
||||
) -> 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 {
|
||||
let mut wlrx = {
|
||||
let (mut wlrx, handles) = {
|
||||
let wl = wayland::get_client().await;
|
||||
wl.subscribe_toplevels()
|
||||
};
|
||||
|
||||
while let Ok(event) = wlrx.recv().await {
|
||||
let update = match event.change {
|
||||
ToplevelChange::Focus(focus) => focus,
|
||||
ToplevelChange::Title(_) => event.toplevel.active,
|
||||
_ => false,
|
||||
};
|
||||
let focused = handles.values().find_map(|handle| {
|
||||
handle
|
||||
.info()
|
||||
.and_then(|info| if info.focused { Some(info) } else { None })
|
||||
});
|
||||
|
||||
if update {
|
||||
send_async!(
|
||||
tx,
|
||||
ModuleUpdateEvent::Update((event.toplevel.title, event.toplevel.app_id))
|
||||
);
|
||||
if let Some(focused) = focused {
|
||||
try_send!(
|
||||
tx,
|
||||
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 crate::clients::wayland::ToplevelInfo;
|
||||
use crate::clients::wayland::ToplevelHandle;
|
||||
use crate::image::ImageProvider;
|
||||
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
||||
use crate::modules::ModuleUpdateEvent;
|
||||
use crate::popup::Popup;
|
||||
use crate::{read_lock, try_send};
|
||||
use color_eyre::{Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, IconTheme, Orientation};
|
||||
use indexmap::IndexMap;
|
||||
@ -12,6 +13,7 @@ use std::rc::Rc;
|
||||
use std::sync::RwLock;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tracing::error;
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Item {
|
||||
@ -34,24 +36,30 @@ impl Item {
|
||||
}
|
||||
|
||||
/// Merges the provided node into this launcher item
|
||||
pub fn merge_toplevel(&mut self, node: ToplevelInfo) -> Window {
|
||||
let id = node.id;
|
||||
pub fn merge_toplevel(&mut self, handle: ToplevelHandle) -> Result<Window> {
|
||||
let info = handle
|
||||
.info()
|
||||
.ok_or_else(|| Report::msg("Toplevel is missing associated info"))?;
|
||||
|
||||
let id = info.id;
|
||||
|
||||
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.recalculate_open_state();
|
||||
|
||||
window
|
||||
Ok(window)
|
||||
}
|
||||
|
||||
pub fn unmerge_toplevel(&mut self, node: &ToplevelInfo) {
|
||||
self.windows.remove(&node.id);
|
||||
self.recalculate_open_state();
|
||||
pub fn unmerge_toplevel(&mut self, handle: &ToplevelHandle) {
|
||||
if let Some(info) = handle.info() {
|
||||
self.windows.remove(&info.id);
|
||||
self.recalculate_open_state();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_window_name(&mut self, window_id: usize, name: String) {
|
||||
@ -87,22 +95,29 @@ impl Item {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ToplevelInfo> for Item {
|
||||
fn from(toplevel: ToplevelInfo) -> Self {
|
||||
let open_state = OpenState::from_toplevel(&toplevel);
|
||||
let name = toplevel.title.clone();
|
||||
let app_id = toplevel.app_id.clone();
|
||||
impl TryFrom<ToplevelHandle> for Item {
|
||||
type Error = Report;
|
||||
|
||||
fn try_from(handle: ToplevelHandle) -> std::result::Result<Self, Self::Error> {
|
||||
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();
|
||||
windows.insert(toplevel.id, toplevel.into());
|
||||
let window = Window::try_from(handle)?;
|
||||
windows.insert(info.id, window);
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
app_id,
|
||||
favorite: false,
|
||||
open_state,
|
||||
windows,
|
||||
name,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,17 +126,30 @@ pub struct Window {
|
||||
pub id: usize,
|
||||
pub name: String,
|
||||
pub open_state: OpenState,
|
||||
handle: ToplevelHandle,
|
||||
}
|
||||
|
||||
impl From<ToplevelInfo> for Window {
|
||||
fn from(node: ToplevelInfo) -> Self {
|
||||
let open_state = OpenState::from_toplevel(&node);
|
||||
impl TryFrom<ToplevelHandle> for Window {
|
||||
type Error = Report;
|
||||
|
||||
Self {
|
||||
id: node.id,
|
||||
name: node.title,
|
||||
fn try_from(handle: ToplevelHandle) -> Result<Self, Self::Error> {
|
||||
let info = handle
|
||||
.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,
|
||||
}
|
||||
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::open_state::OpenState;
|
||||
use crate::clients::wayland::{self, ToplevelChange};
|
||||
use crate::clients::wayland::{self, ToplevelEvent};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::desktop_file::find_desktop_file;
|
||||
use crate::modules::launcher::item::AppearanceOptions;
|
||||
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 glib::Continue;
|
||||
use gtk::prelude::*;
|
||||
@ -18,7 +18,6 @@ use serde::Deserialize;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::spawn;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
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::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 tx2 = tx.clone();
|
||||
spawn(async move {
|
||||
let items = items2;
|
||||
let tx = tx2;
|
||||
|
||||
let mut wlrx = {
|
||||
let (mut wlrx, handles) = {
|
||||
let wl = wayland::get_client().await;
|
||||
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));
|
||||
|
||||
while let Ok(event) = wlrx.recv().await {
|
||||
trace!("event: {:?}", event);
|
||||
|
||||
let window = event.toplevel;
|
||||
let app_id = window.app_id.clone();
|
||||
match event {
|
||||
ToplevelEvent::New(handle) => {
|
||||
let Some(info) = handle.info() else { continue };
|
||||
|
||||
match event.change {
|
||||
ToplevelChange::New => {
|
||||
let new_item = {
|
||||
let mut items = lock!(items);
|
||||
let item = items.get_mut(&app_id);
|
||||
let item = items.get_mut(&info.app_id);
|
||||
match item {
|
||||
None => {
|
||||
let item: Item = window.into();
|
||||
items.insert(app_id.clone(), item.clone());
|
||||
let item: Item = handle.try_into()?;
|
||||
items.insert(info.app_id.clone(), item.clone());
|
||||
|
||||
ItemOrWindow::Item(item)
|
||||
}
|
||||
Some(item) => {
|
||||
let window = item.merge_toplevel(window);
|
||||
let window = item.merge_toplevel(handle)?;
|
||||
ItemOrWindow::Window(window)
|
||||
}
|
||||
}
|
||||
@ -185,20 +178,40 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
send_update(LauncherUpdate::AddItem(item)).await
|
||||
}
|
||||
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 mut items = lock!(items);
|
||||
let item = items.get_mut(&app_id);
|
||||
let item = items.get_mut(&info.app_id);
|
||||
match item {
|
||||
Some(item) => {
|
||||
item.unmerge_toplevel(&window);
|
||||
item.unmerge_toplevel(&handle);
|
||||
|
||||
if item.windows.is_empty() {
|
||||
items.remove(&app_id);
|
||||
items.remove(&info.app_id);
|
||||
Some(ItemOrWindowId::Item)
|
||||
} else {
|
||||
Some(ItemOrWindowId::Window)
|
||||
@ -210,56 +223,28 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
|
||||
match remove_item {
|
||||
Some(ItemOrWindowId::Item) => {
|
||||
send_update(LauncherUpdate::RemoveItem(app_id)).await?;
|
||||
send_update(LauncherUpdate::RemoveItem(info.app_id.clone()))
|
||||
.await?;
|
||||
}
|
||||
Some(ItemOrWindowId::Window) => {
|
||||
send_update(LauncherUpdate::RemoveWindow(app_id, window.id))
|
||||
.await?;
|
||||
send_update(LauncherUpdate::RemoveWindow(
|
||||
info.app_id.clone(),
|
||||
info.id,
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
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
|
||||
spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
trace!("{:?}", event);
|
||||
|
||||
if let ItemEvent::OpenItem(app_id) = event {
|
||||
find_desktop_file(&app_id).map_or_else(
|
||||
|| error!("Could not find desktop file for {}", app_id),
|
||||
@ -283,6 +268,8 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
send_async!(tx, ModuleUpdateEvent::ClosePopup);
|
||||
|
||||
let wl = wayland::get_client().await;
|
||||
let items = lock!(items);
|
||||
|
||||
@ -295,11 +282,12 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
};
|
||||
|
||||
if let Some(id) = id {
|
||||
let toplevels = read_lock!(wl.toplevels);
|
||||
let seat = wl.seats.first().expect("Failed to get Wayland seat");
|
||||
if let Some((_top, handle)) = toplevels.get(&id) {
|
||||
handle.activate(seat);
|
||||
};
|
||||
if let Some(window) =
|
||||
items.iter().find_map(|(_, item)| item.windows.get(&id))
|
||||
{
|
||||
let seat = wl.get_seats().pop().expect("Failed to get Wayland seat");
|
||||
window.focus(&seat);
|
||||
}
|
||||
}
|
||||
|
||||
// roundtrip to immediately send activate event
|
||||
@ -456,12 +444,8 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
|
||||
{
|
||||
let tx = controller_tx.clone();
|
||||
button.connect_clicked(move |button| {
|
||||
button.connect_clicked(move |_| {
|
||||
try_send!(tx, ItemEvent::FocusWindow(win.id));
|
||||
|
||||
if let Some(win) = button.window() {
|
||||
win.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -548,6 +532,9 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
/// This is a hacky number derived from
|
||||
/// "what fits inside the 250px popup"
|
||||
/// and probably won't hold up with wide fonts.
|
||||
///
|
||||
/// TODO: Migrate this to truncate system
|
||||
///
|
||||
fn clamp(str: &str) -> String {
|
||||
const MAX_CHARS: usize = 24;
|
||||
|
||||
|
@ -7,14 +7,15 @@ pub enum OpenState {
|
||||
Open { focused: bool },
|
||||
}
|
||||
|
||||
impl OpenState {
|
||||
/// Creates from `SwayNode`
|
||||
pub const fn from_toplevel(toplevel: &ToplevelInfo) -> Self {
|
||||
impl From<&ToplevelInfo> for OpenState {
|
||||
fn from(info: &ToplevelInfo) -> Self {
|
||||
Self::Open {
|
||||
focused: toplevel.active,
|
||||
focused: info.focused,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenState {
|
||||
/// Creates open with focused
|
||||
pub const fn focused(focused: bool) -> Self {
|
||||
Self::Open { focused }
|
||||
|
Loading…
Reference in New Issue
Block a user