2023-04-29 22:08:02 +01:00
|
|
|
mod macros;
|
|
|
|
mod wl_output;
|
|
|
|
mod wl_seat;
|
2022-10-04 23:26:07 +01:00
|
|
|
|
2024-06-20 20:58:41 +01:00
|
|
|
use crate::error::{ExitCode, ERR_CHANNEL_RECV};
|
2024-01-14 15:42:07 +00:00
|
|
|
use crate::{arc_mut, lock, register_client, send, spawn, spawn_blocking};
|
2024-06-20 20:58:41 +01:00
|
|
|
use std::process::exit;
|
2024-01-07 23:50:10 +00:00
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
|
|
|
|
use calloop_channel::Event::Msg;
|
2023-02-25 14:30:45 +00:00
|
|
|
use cfg_if::cfg_if;
|
2024-01-07 23:50:10 +00:00
|
|
|
use color_eyre::Report;
|
2024-01-11 23:22:18 +00:00
|
|
|
use smithay_client_toolkit::output::OutputState;
|
2024-01-07 23:50:10 +00:00
|
|
|
use smithay_client_toolkit::reexports::calloop::channel as calloop_channel;
|
|
|
|
use smithay_client_toolkit::reexports::calloop::{EventLoop, LoopHandle};
|
|
|
|
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
|
2023-04-29 22:08:02 +01:00
|
|
|
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,
|
|
|
|
};
|
2024-01-07 23:50:10 +00:00
|
|
|
use tokio::sync::{broadcast, mpsc};
|
|
|
|
use tracing::{debug, error, trace};
|
|
|
|
use wayland_client::globals::registry_queue_init;
|
|
|
|
use wayland_client::{Connection, QueueHandle};
|
2024-01-13 22:08:31 -03:00
|
|
|
pub use wl_output::{OutputEvent, OutputEventType};
|
2022-10-04 23:26:07 +01:00
|
|
|
|
2024-01-13 22:08:31 -03:00
|
|
|
cfg_if! {
|
|
|
|
if #[cfg(any(feature = "focused", feature = "launcher"))] {
|
|
|
|
mod wlr_foreign_toplevel;
|
|
|
|
use crate::{delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager};
|
|
|
|
use wlr_foreign_toplevel::manager::ToplevelManagerState;
|
|
|
|
pub use wlr_foreign_toplevel::{ToplevelEvent, ToplevelHandle, ToplevelInfo};
|
2024-01-07 23:50:10 +00:00
|
|
|
|
2024-01-13 22:08:31 -03:00
|
|
|
}
|
|
|
|
}
|
2022-10-04 23:26:07 +01:00
|
|
|
|
2023-02-25 14:30:45 +00:00
|
|
|
cfg_if! {
|
|
|
|
if #[cfg(feature = "clipboard")] {
|
|
|
|
mod wlr_data_control;
|
|
|
|
|
2023-04-29 22:08:02 +01:00
|
|
|
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;
|
2024-01-11 23:22:18 +00:00
|
|
|
use wayland_client::protocol::wl_seat::WlSeat;
|
2023-04-29 22:08:02 +01:00
|
|
|
|
2023-02-25 14:30:45 +00:00
|
|
|
pub use wlr_data_control::{ClipboardItem, ClipboardValue};
|
2023-04-29 22:08:02 +01:00
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
#[derive(Debug)]
|
2023-04-29 22:08:02 +01:00
|
|
|
pub struct DataControlDeviceEntry {
|
|
|
|
seat: WlSeat,
|
|
|
|
device: DataControlDevice,
|
|
|
|
}
|
2023-02-25 14:30:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Event {
|
|
|
|
Output(OutputEvent),
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
Toplevel(ToplevelEvent),
|
2023-04-29 22:08:02 +01:00
|
|
|
#[cfg(feature = "clipboard")]
|
2024-01-07 23:50:10 +00:00
|
|
|
Clipboard(ClipboardItem),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Request {
|
|
|
|
Roundtrip,
|
|
|
|
|
2024-01-11 23:22:18 +00:00
|
|
|
#[cfg(feature = "ipc")]
|
2024-01-07 23:50:10 +00:00
|
|
|
OutputInfoAll,
|
2023-04-29 22:08:02 +01:00
|
|
|
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
ToplevelInfoAll,
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(feature = "launcher")]
|
2024-01-07 23:50:10 +00:00
|
|
|
ToplevelFocus(usize),
|
2024-11-10 16:20:28 +11:00
|
|
|
#[cfg(feature = "launcher")]
|
|
|
|
ToplevelMinimize(usize),
|
2022-10-04 23:26:07 +01:00
|
|
|
|
2023-04-29 22:08:02 +01:00
|
|
|
#[cfg(feature = "clipboard")]
|
2024-01-07 23:50:10 +00:00
|
|
|
CopyToClipboard(ClipboardItem),
|
2023-04-29 22:08:02 +01:00
|
|
|
#[cfg(feature = "clipboard")]
|
2024-01-07 23:50:10 +00:00
|
|
|
ClipboardItem,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Response {
|
|
|
|
/// An empty success response
|
|
|
|
Ok,
|
|
|
|
|
2024-01-11 23:22:18 +00:00
|
|
|
#[cfg(feature = "ipc")]
|
|
|
|
OutputInfoAll(Vec<smithay_client_toolkit::output::OutputInfo>),
|
2024-01-07 23:50:10 +00:00
|
|
|
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
ToplevelInfoAll(Vec<ToplevelInfo>),
|
|
|
|
|
2023-04-29 22:08:02 +01:00
|
|
|
#[cfg(feature = "clipboard")]
|
2024-01-07 23:50:10 +00:00
|
|
|
ClipboardItem(Option<ClipboardItem>),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2024-03-29 00:29:27 +00:00
|
|
|
#[allow(dead_code)]
|
2024-01-07 23:50:10 +00:00
|
|
|
struct BroadcastChannel<T>(broadcast::Sender<T>, Arc<Mutex<broadcast::Receiver<T>>>);
|
|
|
|
|
|
|
|
impl<T> From<(broadcast::Sender<T>, broadcast::Receiver<T>)> for BroadcastChannel<T> {
|
|
|
|
fn from(value: (broadcast::Sender<T>, broadcast::Receiver<T>)) -> Self {
|
2024-02-01 22:39:43 +00:00
|
|
|
Self(value.0, arc_mut!(value.1))
|
2024-01-07 23:50:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Client {
|
|
|
|
tx: calloop_channel::Sender<Request>,
|
|
|
|
rx: Arc<Mutex<std::sync::mpsc::Receiver<Response>>>,
|
|
|
|
|
|
|
|
output_channel: BroadcastChannel<OutputEvent>,
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
toplevel_channel: BroadcastChannel<ToplevelEvent>,
|
2023-04-29 22:08:02 +01:00
|
|
|
#[cfg(feature = "clipboard")]
|
2024-01-07 23:50:10 +00:00
|
|
|
clipboard_channel: BroadcastChannel<ClipboardItem>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Client {
|
|
|
|
pub(crate) fn new() -> Self {
|
|
|
|
let (event_tx, mut event_rx) = mpsc::channel(32);
|
|
|
|
|
|
|
|
let (request_tx, request_rx) = calloop_channel::channel();
|
|
|
|
let (response_tx, response_rx) = std::sync::mpsc::channel();
|
|
|
|
|
|
|
|
let output_channel = broadcast::channel(32);
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
let toplevel_channel = broadcast::channel(32);
|
|
|
|
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
let clipboard_channel = broadcast::channel(32);
|
|
|
|
|
2024-01-14 15:42:07 +00:00
|
|
|
spawn_blocking(move || {
|
2024-01-07 23:50:10 +00:00
|
|
|
Environment::spawn(event_tx, request_rx, response_tx);
|
|
|
|
});
|
|
|
|
|
|
|
|
// listen to events
|
|
|
|
{
|
|
|
|
let output_tx = output_channel.0.clone();
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
let toplevel_tx = toplevel_channel.0.clone();
|
|
|
|
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
let clipboard_tx = clipboard_channel.0.clone();
|
2023-04-29 22:08:02 +01:00
|
|
|
|
2024-01-14 15:42:07 +00:00
|
|
|
spawn(async move {
|
2024-01-07 23:50:10 +00:00
|
|
|
while let Some(event) = event_rx.recv().await {
|
|
|
|
match event {
|
|
|
|
Event::Output(event) => send!(output_tx, event),
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
Event::Toplevel(event) => send!(toplevel_tx, event),
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
Event::Clipboard(item) => send!(clipboard_tx, item),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Self {
|
|
|
|
tx: request_tx,
|
|
|
|
rx: arc_mut!(response_rx),
|
|
|
|
|
|
|
|
output_channel: output_channel.into(),
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
toplevel_channel: toplevel_channel.into(),
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
clipboard_channel: clipboard_channel.into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sends a request to the environment event loop,
|
|
|
|
/// and returns the response.
|
|
|
|
fn send_request(&self, request: Request) -> Response {
|
|
|
|
send!(self.tx, request);
|
|
|
|
lock!(self.rx).recv().expect(ERR_CHANNEL_RECV)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sends a round-trip request to the client,
|
|
|
|
/// forcing it to send/receive any events in the queue.
|
|
|
|
pub(crate) fn roundtrip(&self) -> Response {
|
|
|
|
self.send_request(Request::Roundtrip)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Environment {
|
|
|
|
registry_state: RegistryState,
|
|
|
|
output_state: OutputState,
|
|
|
|
seat_state: SeatState,
|
|
|
|
|
|
|
|
queue_handle: QueueHandle<Self>,
|
|
|
|
loop_handle: LoopHandle<'static, Self>,
|
|
|
|
|
|
|
|
event_tx: mpsc::Sender<Event>,
|
|
|
|
response_tx: std::sync::mpsc::Sender<Response>,
|
|
|
|
|
|
|
|
// local state
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
handles: Vec<ToplevelHandle>,
|
|
|
|
|
|
|
|
// -- clipboard --
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
data_control_device_manager_state: DataControlDeviceManagerState,
|
|
|
|
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
data_control_devices: Vec<DataControlDeviceEntry>,
|
2023-04-29 22:08:02 +01:00
|
|
|
#[cfg(feature = "clipboard")]
|
2024-01-07 23:50:10 +00:00
|
|
|
copy_paste_sources: Vec<CopyPasteSource>,
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
selection_offers: Vec<SelectionOfferItem>,
|
|
|
|
|
|
|
|
// local state
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
clipboard: Arc<Mutex<Option<ClipboardItem>>>,
|
2023-02-25 14:30:45 +00:00
|
|
|
}
|
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
delegate_registry!(Environment);
|
|
|
|
|
2023-04-29 22:08:02 +01:00
|
|
|
delegate_output!(Environment);
|
|
|
|
delegate_seat!(Environment);
|
|
|
|
|
2024-01-13 22:08:31 -03:00
|
|
|
cfg_if! {
|
|
|
|
if #[cfg(any(feature = "focused", feature = "launcher"))] {
|
|
|
|
delegate_foreign_toplevel_manager!(Environment);
|
|
|
|
delegate_foreign_toplevel_handle!(Environment);
|
|
|
|
}
|
|
|
|
}
|
2023-04-29 22:08:02 +01:00
|
|
|
|
2023-02-25 14:30:45 +00:00
|
|
|
cfg_if! {
|
|
|
|
if #[cfg(feature = "clipboard")] {
|
2023-04-29 22:08:02 +01:00
|
|
|
delegate_data_control_device_manager!(Environment);
|
|
|
|
delegate_data_control_device!(Environment);
|
|
|
|
delegate_data_control_offer!(Environment);
|
2024-01-07 23:50:10 +00:00
|
|
|
delegate_data_control_source!(Environment);
|
2023-04-29 22:08:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
impl Environment {
|
|
|
|
pub fn spawn(
|
|
|
|
event_tx: mpsc::Sender<Event>,
|
|
|
|
request_rx: calloop_channel::Channel<Request>,
|
|
|
|
response_tx: std::sync::mpsc::Sender<Response>,
|
|
|
|
) {
|
|
|
|
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::<Self>::try_new().expect("Failed to create new event loop");
|
|
|
|
|
|
|
|
WaylandSource::new(conn, queue)
|
|
|
|
.insert(event_loop.handle())
|
|
|
|
.expect("Failed to insert Wayland event queue into event loop");
|
|
|
|
|
|
|
|
let loop_handle = event_loop.handle();
|
|
|
|
|
|
|
|
// Initialize the registry handling
|
|
|
|
// so other parts of Smithay's client toolkit may bind globals.
|
|
|
|
let registry_state = RegistryState::new(&globals);
|
|
|
|
|
|
|
|
let output_state = OutputState::new(&globals, &qh);
|
|
|
|
let seat_state = SeatState::new(&globals, &qh);
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
ToplevelManagerState::bind(&globals, &qh)
|
|
|
|
.expect("to bind to wlr_foreign_toplevel_manager global");
|
|
|
|
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
let data_control_device_manager_state = DataControlDeviceManagerState::bind(&globals, &qh)
|
|
|
|
.expect("to bind to wlr_data_control_device_manager global");
|
|
|
|
|
|
|
|
let mut env = Self {
|
|
|
|
registry_state,
|
|
|
|
output_state,
|
|
|
|
seat_state,
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
data_control_device_manager_state,
|
|
|
|
queue_handle: qh,
|
|
|
|
loop_handle: loop_handle.clone(),
|
|
|
|
event_tx,
|
|
|
|
response_tx,
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
handles: vec![],
|
|
|
|
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
data_control_devices: vec![],
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
copy_paste_sources: vec![],
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
selection_offers: vec![],
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
clipboard: arc_mut!(None),
|
|
|
|
};
|
|
|
|
|
|
|
|
loop_handle
|
|
|
|
.insert_source(request_rx, Self::on_request)
|
|
|
|
.expect("to be able to insert source");
|
|
|
|
|
|
|
|
loop {
|
|
|
|
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")
|
|
|
|
);
|
2024-06-20 20:58:41 +01:00
|
|
|
|
|
|
|
exit(ExitCode::WaylandDispatchError as i32)
|
2024-01-07 23:50:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Processes a request from the client
|
|
|
|
/// and sends the response.
|
|
|
|
fn on_request(event: calloop_channel::Event<Request>, _metadata: &mut (), env: &mut Self) {
|
|
|
|
trace!("Request: {event:?}");
|
|
|
|
|
|
|
|
match event {
|
|
|
|
Msg(Request::Roundtrip) => {
|
|
|
|
debug!("received roundtrip request");
|
|
|
|
send!(env.response_tx, Response::Ok);
|
|
|
|
}
|
2024-01-11 23:22:18 +00:00
|
|
|
#[cfg(feature = "ipc")]
|
2024-01-07 23:50:10 +00:00
|
|
|
Msg(Request::OutputInfoAll) => {
|
|
|
|
let infos = env.output_info_all();
|
|
|
|
send!(env.response_tx, Response::OutputInfoAll(infos));
|
|
|
|
}
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(any(feature = "focused", feature = "launcher"))]
|
2024-01-07 23:50:10 +00:00
|
|
|
Msg(Request::ToplevelInfoAll) => {
|
|
|
|
let infos = env
|
|
|
|
.handles
|
|
|
|
.iter()
|
|
|
|
.filter_map(ToplevelHandle::info)
|
|
|
|
.collect();
|
|
|
|
send!(env.response_tx, Response::ToplevelInfoAll(infos));
|
|
|
|
}
|
2024-01-13 22:08:31 -03:00
|
|
|
#[cfg(feature = "launcher")]
|
2024-01-07 23:50:10 +00:00
|
|
|
Msg(Request::ToplevelFocus(id)) => {
|
|
|
|
let handle = env
|
|
|
|
.handles
|
|
|
|
.iter()
|
|
|
|
.find(|handle| handle.info().map_or(false, |info| info.id == id));
|
2024-01-13 22:08:31 -03:00
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
if let Some(handle) = handle {
|
|
|
|
let seat = env.default_seat();
|
|
|
|
handle.focus(&seat);
|
|
|
|
}
|
2024-01-13 22:08:31 -03:00
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
send!(env.response_tx, Response::Ok);
|
|
|
|
}
|
2024-11-10 16:20:28 +11:00
|
|
|
#[cfg(feature = "launcher")]
|
|
|
|
Msg(Request::ToplevelMinimize(id)) => {
|
|
|
|
let handle = env
|
|
|
|
.handles
|
|
|
|
.iter()
|
|
|
|
.find(|handle| handle.info().map_or(false, |info| info.id == id));
|
|
|
|
|
|
|
|
if let Some(handle) = handle {
|
|
|
|
handle.minimize();
|
|
|
|
}
|
|
|
|
|
|
|
|
send!(env.response_tx, Response::Ok);
|
|
|
|
}
|
2024-01-07 23:50:10 +00:00
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
Msg(Request::CopyToClipboard(item)) => {
|
|
|
|
env.copy_to_clipboard(item);
|
|
|
|
send!(env.response_tx, Response::Ok);
|
|
|
|
}
|
|
|
|
#[cfg(feature = "clipboard")]
|
|
|
|
Msg(Request::ClipboardItem) => {
|
|
|
|
let item = lock!(env.clipboard).clone();
|
|
|
|
send!(env.response_tx, Response::ClipboardItem(item));
|
|
|
|
}
|
|
|
|
calloop_channel::Event::Closed => error!("request channel unexpectedly closed"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-29 22:08:02 +01:00
|
|
|
|
|
|
|
impl ProvidesRegistryState for Environment {
|
|
|
|
fn registry(&mut self) -> &mut RegistryState {
|
|
|
|
&mut self.registry_state
|
2022-10-04 23:26:07 +01:00
|
|
|
}
|
2023-04-29 22:08:02 +01:00
|
|
|
registry_handlers![OutputState, SeatState];
|
2022-10-04 23:26:07 +01:00
|
|
|
}
|
|
|
|
|
2024-01-07 23:50:10 +00:00
|
|
|
register_client!(Client, wayland);
|