Vendor things
This commit is contained in:
parent
5deceec006
commit
977e3c17e5
19434 changed files with 10682014 additions and 0 deletions
227
third-party/vendor/smithay-client-toolkit/src/data_device/device.rs
vendored
Normal file
227
third-party/vendor/smithay-client-toolkit/src/data_device/device.rs
vendored
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
use wayland_client::{
|
||||
protocol::{wl_data_device, wl_data_device_manager, wl_data_offer, wl_seat, wl_surface},
|
||||
DispatchData, Main,
|
||||
};
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::{DataOffer, DataSource, DndAction};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
selection: Option<DataOffer>,
|
||||
current_dnd: Option<DataOffer>,
|
||||
known_offers: Vec<DataOffer>,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn new_offer(&mut self, offer: Main<wl_data_offer::WlDataOffer>) {
|
||||
self.known_offers.push(DataOffer::new(offer));
|
||||
}
|
||||
|
||||
fn set_selection(&mut self, offer: Option<wl_data_offer::WlDataOffer>) {
|
||||
if let Some(offer) = offer {
|
||||
if let Some(id) = self.known_offers.iter().position(|o| o.offer == offer) {
|
||||
self.selection = Some(self.known_offers.swap_remove(id));
|
||||
} else {
|
||||
panic!("Compositor set an unknown data_offer for selection.");
|
||||
}
|
||||
} else {
|
||||
// drop the current offer if any
|
||||
self.selection = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn set_dnd(&mut self, offer: Option<wl_data_offer::WlDataOffer>) {
|
||||
if let Some(offer) = offer {
|
||||
if let Some(id) = self.known_offers.iter().position(|o| o.offer == offer) {
|
||||
self.current_dnd = Some(self.known_offers.swap_remove(id));
|
||||
} else {
|
||||
panic!("Compositor set an unknown data_offer for selection.");
|
||||
}
|
||||
} else {
|
||||
// drop the current offer if any
|
||||
self.current_dnd = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle to support data exchange on a given seat
|
||||
///
|
||||
/// This type provides you with functionality to send and receive
|
||||
/// data through drag'n'drop or copy/paste actions. It is associated
|
||||
/// with a seat upon creation.
|
||||
#[derive(Debug)]
|
||||
pub struct DataDevice {
|
||||
device: wl_data_device::WlDataDevice,
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
}
|
||||
|
||||
/// Possible events generated during a drag'n'drop session
|
||||
#[derive(Debug)]
|
||||
pub enum DndEvent<'a> {
|
||||
/// A new drag'n'drop entered your surfaces
|
||||
Enter {
|
||||
/// The associated data offer
|
||||
///
|
||||
/// Is None if it is an internal drag'n'drop you started with
|
||||
/// no source. See `DataDevice::start_drag` for details.
|
||||
offer: Option<&'a DataOffer>,
|
||||
/// A serial associated with the entry of this dnd
|
||||
serial: u32,
|
||||
/// The entered surface
|
||||
surface: wl_surface::WlSurface,
|
||||
/// horizontal location on the surface
|
||||
x: f64,
|
||||
/// vertical location on the surface
|
||||
y: f64,
|
||||
},
|
||||
/// The drag'n'drop offer moved on the surface
|
||||
Motion {
|
||||
/// The associated data offer
|
||||
///
|
||||
/// Is None if it is an internal drag'n'drop you started with
|
||||
/// no source. See `DataDevice::start_drag` for details.
|
||||
offer: Option<&'a DataOffer>,
|
||||
/// The time of this motion
|
||||
time: u32,
|
||||
/// new horizontal location
|
||||
x: f64,
|
||||
/// new vertical location
|
||||
y: f64,
|
||||
},
|
||||
/// The drag'n'drop offer left your surface
|
||||
Leave,
|
||||
/// The drag'n'drop was dropped on your surface
|
||||
Drop {
|
||||
/// The associated data offer
|
||||
///
|
||||
/// Is None if it is an internal drag'n'drop you started with
|
||||
/// no source. See `DataDevice::start_drag` for details.
|
||||
offer: Option<&'a DataOffer>,
|
||||
},
|
||||
}
|
||||
|
||||
fn data_device_implem<F>(
|
||||
event: wl_data_device::Event,
|
||||
inner: &mut Inner,
|
||||
implem: &mut F,
|
||||
ddata: DispatchData,
|
||||
) where
|
||||
for<'a> F: FnMut(DndEvent<'a>, DispatchData),
|
||||
{
|
||||
use self::wl_data_device::Event;
|
||||
|
||||
match event {
|
||||
Event::DataOffer { id } => inner.new_offer(id),
|
||||
Event::Enter { serial, surface, x, y, id } => {
|
||||
inner.set_dnd(id);
|
||||
implem(
|
||||
DndEvent::Enter { serial, surface, x, y, offer: inner.current_dnd.as_ref() },
|
||||
ddata,
|
||||
);
|
||||
}
|
||||
Event::Motion { time, x, y } => {
|
||||
implem(DndEvent::Motion { x, y, time, offer: inner.current_dnd.as_ref() }, ddata);
|
||||
}
|
||||
Event::Leave => implem(DndEvent::Leave, ddata),
|
||||
Event::Drop => {
|
||||
implem(DndEvent::Drop { offer: inner.current_dnd.as_ref() }, ddata);
|
||||
}
|
||||
Event::Selection { id } => inner.set_selection(id),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
impl DataDevice {
|
||||
/// Create the DataDevice helper for this seat.
|
||||
///
|
||||
/// You need to provide an implementation that will handle drag'n'drop
|
||||
/// events.
|
||||
pub fn init_for_seat<F>(
|
||||
manager: &wl_data_device_manager::WlDataDeviceManager,
|
||||
seat: &wl_seat::WlSeat,
|
||||
mut callback: F,
|
||||
) -> DataDevice
|
||||
where
|
||||
for<'a> F: FnMut(DndEvent<'a>, DispatchData) + 'static,
|
||||
{
|
||||
let inner = Arc::new(Mutex::new(Inner {
|
||||
selection: None,
|
||||
current_dnd: None,
|
||||
known_offers: Vec::new(),
|
||||
}));
|
||||
|
||||
let inner2 = inner.clone();
|
||||
let device = manager.get_data_device(seat);
|
||||
device.quick_assign(move |_, evt, ddata| {
|
||||
let mut inner = inner2.lock().unwrap();
|
||||
data_device_implem(evt, &mut *inner, &mut callback, ddata);
|
||||
});
|
||||
|
||||
DataDevice { device: device.detach(), inner }
|
||||
}
|
||||
|
||||
/// Start a drag'n'drop offer
|
||||
///
|
||||
/// You need to specify the origin surface, as well a serial associated
|
||||
/// to an implicit grab on this surface (for example received by a pointer click).
|
||||
///
|
||||
/// An optional `DataSource` can be provided. If it is `None`, this drag'n'drop will
|
||||
/// be considered as internal to your application, and other applications will not be
|
||||
/// notified of it. You are then responsible for acting accordingly on drop.
|
||||
///
|
||||
/// You also need to specify which possible drag'n'drop actions are associated to this
|
||||
/// drag (copy, move, or ask), the final action will be chosen by the target and/or
|
||||
/// compositor.
|
||||
///
|
||||
/// You can finally provide a surface that will be used as an icon associated with
|
||||
/// this drag'n'drop for user visibility.
|
||||
pub fn start_drag(
|
||||
&self,
|
||||
origin: &wl_surface::WlSurface,
|
||||
source: Option<DataSource>,
|
||||
actions: DndAction,
|
||||
icon: Option<&wl_surface::WlSurface>,
|
||||
serial: u32,
|
||||
) {
|
||||
if let Some(source) = source {
|
||||
source.source.set_actions(actions);
|
||||
self.device.start_drag(Some(&source.source), origin, icon, serial);
|
||||
} else {
|
||||
self.device.start_drag(None, origin, icon, serial);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide a data source as the new content for the selection
|
||||
///
|
||||
/// Correspond to traditional copy/paste behavior. Setting the
|
||||
/// source to `None` will clear the selection.
|
||||
pub fn set_selection(&self, source: &Option<DataSource>, serial: u32) {
|
||||
self.device.set_selection(source.as_ref().map(|s| &s.source), serial);
|
||||
}
|
||||
|
||||
/// Access the `DataOffer` currently associated with the selection buffer
|
||||
pub fn with_selection<F, T>(&self, f: F) -> T
|
||||
where
|
||||
F: FnOnce(Option<&DataOffer>) -> T,
|
||||
{
|
||||
let inner = self.inner.lock().unwrap();
|
||||
f(inner.selection.as_ref())
|
||||
}
|
||||
|
||||
/// Access the `DataOffer` currently associated with current DnD
|
||||
pub fn with_dnd<F, T>(&self, f: F) -> T
|
||||
where
|
||||
F: FnOnce(Option<&DataOffer>) -> T,
|
||||
{
|
||||
let inner = self.inner.lock().unwrap();
|
||||
f(inner.current_dnd.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DataDevice {
|
||||
fn drop(&mut self) {
|
||||
self.device.release();
|
||||
}
|
||||
}
|
||||
294
third-party/vendor/smithay-client-toolkit/src/data_device/mod.rs
vendored
Normal file
294
third-party/vendor/smithay-client-toolkit/src/data_device/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
//! Helpers to handle data device related actions
|
||||
|
||||
use std::{cell::RefCell, fmt, rc::Rc};
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_data_device_manager, wl_registry, wl_seat},
|
||||
Attached, DispatchData,
|
||||
};
|
||||
|
||||
pub use wayland_client::protocol::wl_data_device_manager::DndAction;
|
||||
|
||||
use crate::MissingGlobal;
|
||||
|
||||
mod device;
|
||||
mod offer;
|
||||
mod source;
|
||||
|
||||
pub use self::device::{DataDevice, DndEvent};
|
||||
pub use self::offer::{DataOffer, ReadPipe};
|
||||
pub use self::source::{DataSource, DataSourceEvent, WritePipe};
|
||||
|
||||
type DDCallback = dyn FnMut(wl_seat::WlSeat, DndEvent, DispatchData);
|
||||
|
||||
enum DDInner {
|
||||
Ready {
|
||||
mgr: Attached<wl_data_device_manager::WlDataDeviceManager>,
|
||||
devices: Vec<(wl_seat::WlSeat, DataDevice)>,
|
||||
callback: Rc<RefCell<Box<DDCallback>>>,
|
||||
},
|
||||
Pending {
|
||||
seats: Vec<wl_seat::WlSeat>,
|
||||
},
|
||||
}
|
||||
|
||||
impl DDInner {
|
||||
fn init_dd_mgr(&mut self, mgr: Attached<wl_data_device_manager::WlDataDeviceManager>) {
|
||||
let seats = if let DDInner::Pending { seats } = self {
|
||||
::std::mem::take(seats)
|
||||
} else {
|
||||
log::warn!("Ignoring second wl_data_device_manager.");
|
||||
return;
|
||||
};
|
||||
|
||||
let mut devices = Vec::new();
|
||||
|
||||
let callback = Rc::new(RefCell::new(Box::new(|_, _: DndEvent, _: DispatchData| {})
|
||||
as Box<dyn FnMut(_, DndEvent, DispatchData)>));
|
||||
|
||||
for seat in seats {
|
||||
let cb = callback.clone();
|
||||
let my_seat = seat.clone();
|
||||
let device = DataDevice::init_for_seat(&mgr, &seat, move |event, dispatch_data| {
|
||||
(cb.borrow_mut())(my_seat.clone(), event, dispatch_data);
|
||||
});
|
||||
devices.push((seat.clone(), device));
|
||||
}
|
||||
|
||||
*self = DDInner::Ready { mgr, devices, callback };
|
||||
}
|
||||
|
||||
// A potential new seat is seen
|
||||
//
|
||||
// should do nothing if the seat is already known
|
||||
fn new_seat(&mut self, seat: &wl_seat::WlSeat) {
|
||||
match self {
|
||||
DDInner::Ready { mgr, devices, callback } => {
|
||||
if devices.iter().any(|(s, _)| s == seat) {
|
||||
// the seat already exists, nothing to do
|
||||
return;
|
||||
}
|
||||
let cb = callback.clone();
|
||||
let my_seat = seat.clone();
|
||||
let device = DataDevice::init_for_seat(mgr, seat, move |event, dispatch_data| {
|
||||
(cb.borrow_mut())(my_seat.clone(), event, dispatch_data);
|
||||
});
|
||||
devices.push((seat.clone(), device));
|
||||
}
|
||||
DDInner::Pending { seats } => {
|
||||
seats.push(seat.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_seat(&mut self, seat: &wl_seat::WlSeat) {
|
||||
match self {
|
||||
DDInner::Ready { devices, .. } => devices.retain(|(s, _)| s != seat),
|
||||
DDInner::Pending { seats } => seats.retain(|s| s != seat),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mgr(&self) -> Option<Attached<wl_data_device_manager::WlDataDeviceManager>> {
|
||||
match self {
|
||||
DDInner::Ready { mgr, .. } => Some(mgr.clone()),
|
||||
DDInner::Pending { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_callback<F: FnMut(wl_seat::WlSeat, DndEvent, DispatchData) + 'static>(
|
||||
&mut self,
|
||||
cb: F,
|
||||
) -> Result<(), MissingGlobal> {
|
||||
match self {
|
||||
DDInner::Ready { callback, .. } => {
|
||||
*(callback.borrow_mut()) = Box::new(cb);
|
||||
Ok(())
|
||||
}
|
||||
DDInner::Pending { .. } => Err(MissingGlobal),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_device<F: FnOnce(&DataDevice)>(
|
||||
&self,
|
||||
seat: &wl_seat::WlSeat,
|
||||
f: F,
|
||||
) -> Result<(), MissingGlobal> {
|
||||
match self {
|
||||
DDInner::Pending { .. } => Err(MissingGlobal),
|
||||
DDInner::Ready { devices, .. } => {
|
||||
for (s, device) in devices {
|
||||
if s == seat {
|
||||
f(device);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(MissingGlobal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for DDInner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Ready { mgr, devices, .. } => f
|
||||
.debug_struct("Ready")
|
||||
.field("mgr", mgr)
|
||||
.field("devices", devices)
|
||||
.field("callback", &"Fn() -> { ... }")
|
||||
.finish(),
|
||||
Self::Pending { seats } => f.debug_struct("Pending").field("seats", seats).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handler for data devices
|
||||
///
|
||||
/// It provides automatic tracking of data device for each available seat,
|
||||
/// allowing you to manipulate selection clipboard and drag&drop manipulations.
|
||||
///
|
||||
/// It is automatically included in the [`default_environment!`](../macro.default_environment.html).
|
||||
#[derive(Debug)]
|
||||
pub struct DataDeviceHandler {
|
||||
inner: Rc<RefCell<DDInner>>,
|
||||
_listener: crate::seat::SeatListener,
|
||||
}
|
||||
|
||||
impl DataDeviceHandler {
|
||||
/// Initialize a data device handler
|
||||
///
|
||||
/// It needs access to a seat handler in order to track
|
||||
/// the creation and removal of seats.
|
||||
pub fn init<S>(seat_handler: &mut S) -> DataDeviceHandler
|
||||
where
|
||||
S: crate::seat::SeatHandling,
|
||||
{
|
||||
let inner = Rc::new(RefCell::new(DDInner::Pending { seats: Vec::new() }));
|
||||
|
||||
let seat_inner = inner.clone();
|
||||
let 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)
|
||||
}
|
||||
});
|
||||
|
||||
DataDeviceHandler { inner, _listener: listener }
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::environment::GlobalHandler<wl_data_device_manager::WlDataDeviceManager>
|
||||
for DataDeviceHandler
|
||||
{
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
_: DispatchData,
|
||||
) {
|
||||
// data device manager is supported until version 3
|
||||
let version = std::cmp::min(version, 3);
|
||||
let ddmgr = registry.bind::<wl_data_device_manager::WlDataDeviceManager>(version, id);
|
||||
self.inner.borrow_mut().init_dd_mgr((*ddmgr).clone());
|
||||
}
|
||||
fn get(&self) -> Option<Attached<wl_data_device_manager::WlDataDeviceManager>> {
|
||||
self.inner.borrow().get_mgr()
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface trait to forward the data device handler capability
|
||||
///
|
||||
/// You need to implement this trait for your environment struct, by
|
||||
/// delegating it to its `DataDeviceHandler` field in order to get the
|
||||
/// associated methods on your [`Environment`](../environment/struct.environment.html).
|
||||
pub trait DataDeviceHandling {
|
||||
/// Set the global drag'n'drop callback
|
||||
///
|
||||
/// Returns an error if the `wl_data_device_manager` global is missing.
|
||||
fn set_callback<F: FnMut(wl_seat::WlSeat, DndEvent, DispatchData) + 'static>(
|
||||
&mut self,
|
||||
callback: F,
|
||||
) -> Result<(), MissingGlobal>;
|
||||
|
||||
/// Access the data device associated with a seat
|
||||
///
|
||||
/// Returns an error if the seat is not found (for example if it has since been removed by
|
||||
/// the server) or if the `wl_data_device_manager` global is missing.
|
||||
fn with_device<F: FnOnce(&DataDevice)>(
|
||||
&self,
|
||||
seat: &wl_seat::WlSeat,
|
||||
f: F,
|
||||
) -> Result<(), MissingGlobal>;
|
||||
}
|
||||
|
||||
impl DataDeviceHandling for DataDeviceHandler {
|
||||
fn set_callback<F: FnMut(wl_seat::WlSeat, DndEvent, DispatchData) + 'static>(
|
||||
&mut self,
|
||||
callback: F,
|
||||
) -> Result<(), MissingGlobal> {
|
||||
self.inner.borrow_mut().set_callback(callback)
|
||||
}
|
||||
|
||||
fn with_device<F: FnOnce(&DataDevice)>(
|
||||
&self,
|
||||
seat: &wl_seat::WlSeat,
|
||||
f: F,
|
||||
) -> Result<(), MissingGlobal> {
|
||||
self.inner.borrow().with_device(seat, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> crate::environment::Environment<E>
|
||||
where
|
||||
E: crate::environment::GlobalHandler<wl_data_device_manager::WlDataDeviceManager>,
|
||||
{
|
||||
/// Create a new data source
|
||||
///
|
||||
/// This data source is the basic object for offering content to other clients,
|
||||
/// be it for clipboard selection or as drag'n'drop content.
|
||||
///
|
||||
/// Once this source is created, you will need to give it to a
|
||||
/// [`DataDevice`](../data_device/struct.DataDevice.html)
|
||||
/// to start interaction.
|
||||
pub fn new_data_source<F>(&self, mime_types: Vec<String>, callback: F) -> DataSource
|
||||
where
|
||||
F: FnMut(DataSourceEvent, DispatchData) + 'static,
|
||||
{
|
||||
let ddmgr = self.require_global::<wl_data_device_manager::WlDataDeviceManager>();
|
||||
DataSource::new(&ddmgr, mime_types, callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> crate::environment::Environment<E>
|
||||
where
|
||||
E: DataDeviceHandling,
|
||||
{
|
||||
/// Set the data device callback
|
||||
///
|
||||
/// This callback will be invoked whenever some drag'n'drop action is done onto one of
|
||||
/// your surfaces.
|
||||
///
|
||||
/// You should set it before entering your main loop, to ensure you will not miss any events.
|
||||
///
|
||||
/// Returns an error if the compositor did not advertise a data device capability.
|
||||
pub fn set_data_device_callback<F: FnMut(wl_seat::WlSeat, DndEvent, DispatchData) + 'static>(
|
||||
&mut self,
|
||||
callback: F,
|
||||
) -> Result<(), MissingGlobal> {
|
||||
self.with_inner(|inner| inner.set_callback(callback))
|
||||
}
|
||||
|
||||
/// Access the data device associated with a seat
|
||||
///
|
||||
/// Returns an error if the seat is not found (for example if it has since been removed by
|
||||
/// the server) or if the `wl_data_device_manager` global is missing.
|
||||
pub fn with_data_device<F: FnOnce(&DataDevice)>(
|
||||
&self,
|
||||
seat: &wl_seat::WlSeat,
|
||||
f: F,
|
||||
) -> Result<(), MissingGlobal> {
|
||||
self.with_inner(|inner| inner.with_device(seat, f))
|
||||
}
|
||||
}
|
||||
274
third-party/vendor/smithay-client-toolkit/src/data_device/offer.rs
vendored
Normal file
274
third-party/vendor/smithay-client-toolkit/src/data_device/offer.rs
vendored
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
use std::{
|
||||
fs, io,
|
||||
os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use wayland_client::protocol::wl_data_device_manager::DndAction;
|
||||
use wayland_client::protocol::wl_data_offer;
|
||||
use wayland_client::Main;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
mime_types: Vec<String>,
|
||||
actions: DndAction,
|
||||
current_action: DndAction,
|
||||
serial: u32,
|
||||
}
|
||||
|
||||
/// A data offer for receiving data though copy/paste or
|
||||
/// drag and drop
|
||||
#[derive(Debug)]
|
||||
pub struct DataOffer {
|
||||
pub(crate) offer: wl_data_offer::WlDataOffer,
|
||||
inner: Arc<Mutex<Inner>>,
|
||||
}
|
||||
|
||||
impl DataOffer {
|
||||
pub(crate) fn new(offer: Main<wl_data_offer::WlDataOffer>) -> DataOffer {
|
||||
let inner = Arc::new(Mutex::new(Inner {
|
||||
mime_types: Vec::new(),
|
||||
actions: DndAction::None,
|
||||
current_action: DndAction::None,
|
||||
serial: 0,
|
||||
}));
|
||||
let inner2 = inner.clone();
|
||||
offer.quick_assign(move |_, event, _| {
|
||||
use self::wl_data_offer::Event;
|
||||
let mut inner = inner2.lock().unwrap();
|
||||
match event {
|
||||
Event::Offer { mime_type } => {
|
||||
inner.mime_types.push(mime_type);
|
||||
}
|
||||
Event::SourceActions { source_actions } => {
|
||||
inner.actions = source_actions;
|
||||
}
|
||||
Event::Action { dnd_action } => {
|
||||
inner.current_action = dnd_action;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
|
||||
DataOffer { offer: offer.detach(), inner }
|
||||
}
|
||||
|
||||
/// Access the list of mime types proposed by this offer
|
||||
pub fn with_mime_types<F, T>(&self, f: F) -> T
|
||||
where
|
||||
F: FnOnce(&[String]) -> T,
|
||||
{
|
||||
let inner = self.inner.lock().unwrap();
|
||||
f(&inner.mime_types)
|
||||
}
|
||||
|
||||
/// Get the list of available actions for this offer
|
||||
pub fn get_available_actions(&self) -> DndAction {
|
||||
self.inner.lock().unwrap().actions
|
||||
}
|
||||
|
||||
/// Get the currently set final action for this offer
|
||||
pub fn get_current_action(&self) -> DndAction {
|
||||
self.inner.lock().unwrap().current_action
|
||||
}
|
||||
|
||||
/// Accept a mime type for receiving data through this offer
|
||||
pub fn accept(&self, mime_type: Option<String>) {
|
||||
let serial = self.inner.lock().unwrap().serial;
|
||||
self.offer.accept(serial, 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(&self, mime_type: String) -> std::io::Result<ReadPipe> {
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::unistd::{close, pipe2};
|
||||
// create a pipe
|
||||
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?;
|
||||
|
||||
self.offer.receive(mime_type, writefd);
|
||||
|
||||
if let Err(err) = close(writefd) {
|
||||
log::warn!("Failed to close write pipe: {}", err);
|
||||
}
|
||||
|
||||
Ok(unsafe { FromRawFd::from_raw_fd(readfd) })
|
||||
}
|
||||
|
||||
/// Receive data to the write end of a raw file descriptor. If you have the read end, you can read from it.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The provided file destructor must be a valid FD for writing, and will be closed
|
||||
/// once the contents are written.
|
||||
pub unsafe fn receive_to_fd(&self, mime_type: String, writefd: RawFd) {
|
||||
use nix::unistd::close;
|
||||
|
||||
self.offer.receive(mime_type, writefd);
|
||||
|
||||
if let Err(err) = close(writefd) {
|
||||
log::warn!("Failed to close write pipe: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
/// Notify the send and compositor of the dnd actions you accept
|
||||
///
|
||||
/// You need to provide the set of supported actions, as well as
|
||||
/// a single preferred action.
|
||||
pub fn set_actions(&self, supported: DndAction, preferred: DndAction) {
|
||||
self.offer.set_actions(supported, preferred);
|
||||
}
|
||||
|
||||
/// Notify that you are finished with this offer, and will no longer
|
||||
/// be using it
|
||||
///
|
||||
/// Note that it is a protocol error to finish if no action or mime
|
||||
/// type was accepted.
|
||||
pub fn finish(&self) {
|
||||
self.offer.finish();
|
||||
self.offer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DataOffer {
|
||||
fn drop(&mut self) {
|
||||
self.offer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// A file descriptor that can only be read from
|
||||
///
|
||||
/// If the `calloop` cargo feature is enabled, this can be used
|
||||
/// as an `EventSource` in a calloop event loop.
|
||||
#[derive(Debug)]
|
||||
pub struct ReadPipe {
|
||||
#[cfg(feature = "calloop")]
|
||||
file: calloop::generic::Generic<fs::File>,
|
||||
#[cfg(not(feature = "calloop"))]
|
||||
file: fs::File,
|
||||
}
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
impl io::Read for ReadPipe {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.file.file.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "calloop"))]
|
||||
impl io::Read for ReadPipe {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.file.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
impl FromRawFd for ReadPipe {
|
||||
unsafe fn from_raw_fd(fd: RawFd) -> ReadPipe {
|
||||
ReadPipe {
|
||||
file: calloop::generic::Generic::new(
|
||||
FromRawFd::from_raw_fd(fd),
|
||||
calloop::Interest::READ,
|
||||
calloop::Mode::Level,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "calloop"))]
|
||||
impl FromRawFd for ReadPipe {
|
||||
unsafe fn from_raw_fd(fd: RawFd) -> ReadPipe {
|
||||
ReadPipe { file: FromRawFd::from_raw_fd(fd) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
impl AsRawFd for ReadPipe {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.file.file.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "calloop"))]
|
||||
impl AsRawFd for ReadPipe {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.file.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
impl IntoRawFd for ReadPipe {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.file.file.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "calloop"))]
|
||||
impl IntoRawFd for ReadPipe {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.file.into_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
impl calloop::EventSource for ReadPipe {
|
||||
type Event = ();
|
||||
type Error = std::io::Error;
|
||||
type Metadata = fs::File;
|
||||
type Ret = ();
|
||||
|
||||
fn process_events<F>(
|
||||
&mut self,
|
||||
readiness: calloop::Readiness,
|
||||
token: calloop::Token,
|
||||
mut callback: F,
|
||||
) -> std::io::Result<calloop::PostAction>
|
||||
where
|
||||
F: FnMut((), &mut fs::File),
|
||||
{
|
||||
self.file.process_events(readiness, token, |_, file| {
|
||||
callback((), file);
|
||||
Ok(calloop::PostAction::Continue)
|
||||
})
|
||||
}
|
||||
|
||||
fn register(
|
||||
&mut self,
|
||||
poll: &mut calloop::Poll,
|
||||
token_factory: &mut calloop::TokenFactory,
|
||||
) -> calloop::Result<()> {
|
||||
self.file.register(poll, token_factory)
|
||||
}
|
||||
|
||||
fn reregister(
|
||||
&mut self,
|
||||
poll: &mut calloop::Poll,
|
||||
token_factory: &mut calloop::TokenFactory,
|
||||
) -> calloop::Result<()> {
|
||||
self.file.reregister(poll, token_factory)
|
||||
}
|
||||
|
||||
fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> {
|
||||
self.file.unregister(poll)
|
||||
}
|
||||
}
|
||||
170
third-party/vendor/smithay-client-toolkit/src/data_device/source.rs
vendored
Normal file
170
third-party/vendor/smithay-client-toolkit/src/data_device/source.rs
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
use wayland_client::{
|
||||
protocol::{wl_data_device_manager, wl_data_source},
|
||||
Attached, DispatchData,
|
||||
};
|
||||
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
|
||||
use std::{fs, io};
|
||||
|
||||
/// A data source for sending data though copy/paste or
|
||||
/// drag and drop
|
||||
#[derive(Debug)]
|
||||
pub struct DataSource {
|
||||
pub(crate) source: wl_data_source::WlDataSource,
|
||||
}
|
||||
|
||||
/// Possible events a data source needs to react to
|
||||
#[derive(Debug)]
|
||||
pub enum DataSourceEvent {
|
||||
/// Write the offered data for selected mime type
|
||||
///
|
||||
/// This can happen several times during a dnd setup,
|
||||
/// and does not mean the action is finished.
|
||||
Send {
|
||||
/// Requested mime type
|
||||
mime_type: String,
|
||||
/// Pipe to write into
|
||||
pipe: WritePipe,
|
||||
},
|
||||
/// Target mime type
|
||||
///
|
||||
/// Notifies that the target accepted a given mime type.
|
||||
/// You can use it to provide feedback (changing the icon
|
||||
/// of the drag'n'drop for example).
|
||||
///
|
||||
/// Can be `None` if the current target does not accept any of the
|
||||
/// proposed mime types.
|
||||
///
|
||||
/// This event can be emitted several times during the process
|
||||
Target {
|
||||
/// The type accepted by the target
|
||||
mime_type: Option<String>,
|
||||
},
|
||||
/// Notifies of the current selected action for the drag'n'drop
|
||||
///
|
||||
/// Can only happen for data sources used during a drag'n'drop.
|
||||
///
|
||||
/// This can change several times, the last received defines which action
|
||||
/// should actually be taken.
|
||||
Action {
|
||||
/// The action chosen by the target
|
||||
action: wl_data_device_manager::DndAction,
|
||||
},
|
||||
/// The action using this data source was cancelled.
|
||||
///
|
||||
/// Once this event is received, the `DataSource` can not be used any more,
|
||||
/// and you should drop it for cleanup.
|
||||
///
|
||||
/// Happens if the user cancels the current drag'n'drop, or replaces the
|
||||
/// selection buffer.
|
||||
Cancelled,
|
||||
/// The user performed the "drop" during a drag'n'drop
|
||||
///
|
||||
/// This does not mean the operation is finished (the operation can still
|
||||
/// be cancelled afterwards).
|
||||
///
|
||||
/// You are not guaranteed to receive this event at some point, as the compositor
|
||||
/// may cancel the action before the user drops.
|
||||
///
|
||||
/// This event can only be generated on sources used for drag'n'drop, not
|
||||
/// selection sources.
|
||||
Dropped,
|
||||
/// The action is finished, this data source will not be used any more
|
||||
///
|
||||
/// If the selected drag'n'drop action was "move", you can now delete the
|
||||
/// underlying resource.
|
||||
///
|
||||
/// This event can only be generated on sources used for drag'n'drop, not
|
||||
/// selection sources.
|
||||
Finished,
|
||||
}
|
||||
|
||||
fn data_source_impl<Impl>(
|
||||
evt: wl_data_source::Event,
|
||||
source: &wl_data_source::WlDataSource,
|
||||
implem: &mut Impl,
|
||||
ddata: DispatchData,
|
||||
) where
|
||||
Impl: FnMut(DataSourceEvent, DispatchData),
|
||||
{
|
||||
use self::wl_data_source::Event;
|
||||
let event = match evt {
|
||||
Event::Target { mime_type } => DataSourceEvent::Target { mime_type },
|
||||
Event::Send { mime_type, fd } => {
|
||||
DataSourceEvent::Send { mime_type, pipe: unsafe { FromRawFd::from_raw_fd(fd) } }
|
||||
}
|
||||
Event::Action { dnd_action } => DataSourceEvent::Action { action: dnd_action },
|
||||
Event::Cancelled => {
|
||||
source.destroy();
|
||||
DataSourceEvent::Cancelled
|
||||
}
|
||||
Event::DndDropPerformed => DataSourceEvent::Dropped,
|
||||
Event::DndFinished => {
|
||||
source.destroy();
|
||||
DataSourceEvent::Finished
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
implem(event, ddata);
|
||||
}
|
||||
|
||||
impl DataSource {
|
||||
/// Create a new data source
|
||||
///
|
||||
/// You'll then need to provide it to a data device to send it
|
||||
/// either via selection (aka copy/paste) or via a drag and drop.
|
||||
pub fn new<F, S, It>(
|
||||
mgr: &Attached<wl_data_device_manager::WlDataDeviceManager>,
|
||||
mime_types: It,
|
||||
mut callback: F,
|
||||
) -> DataSource
|
||||
where
|
||||
F: FnMut(DataSourceEvent, DispatchData) + 'static,
|
||||
S: Into<String>,
|
||||
It: IntoIterator<Item = S>,
|
||||
{
|
||||
let source = mgr.create_data_source();
|
||||
source.quick_assign(move |source, evt, dispatch_data| {
|
||||
data_source_impl(evt, &source, &mut callback, dispatch_data)
|
||||
});
|
||||
|
||||
for mime in mime_types {
|
||||
source.offer(mime.into());
|
||||
}
|
||||
|
||||
DataSource { source: source.detach() }
|
||||
}
|
||||
}
|
||||
|
||||
/// A file descriptor that can only be written to
|
||||
#[derive(Debug)]
|
||||
pub struct WritePipe {
|
||||
file: fs::File,
|
||||
}
|
||||
|
||||
impl io::Write for WritePipe {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.file.write(buf)
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.file.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRawFd for WritePipe {
|
||||
unsafe fn from_raw_fd(fd: RawFd) -> WritePipe {
|
||||
WritePipe { file: FromRawFd::from_raw_fd(fd) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for WritePipe {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.file.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoRawFd for WritePipe {
|
||||
fn into_raw_fd(self) -> RawFd {
|
||||
self.file.into_raw_fd()
|
||||
}
|
||||
}
|
||||
395
third-party/vendor/smithay-client-toolkit/src/environment.rs
vendored
Normal file
395
third-party/vendor/smithay-client-toolkit/src/environment.rs
vendored
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
//! Environment management utilities
|
||||
//!
|
||||
//! This module provide the tools to automatically bind the wayland global objects you need in your program.
|
||||
//!
|
||||
//! At the heart of this is the `environment!` macro, which allows you to signal the globals you need
|
||||
//! and a struct to manage them as they are signaled in the registry.
|
||||
//!
|
||||
//! ## Global handlers
|
||||
//!
|
||||
//! Wayland globals are split in two kinds, that we will call here "single" globals and "multi" globals.
|
||||
//!
|
||||
//! - "single" globals represent a capability of the server. They are generally signaled in the registry
|
||||
//! from the start and never removed. They are signaled a single time. Examples of these globals are
|
||||
//! `wl_compositor`, `wl_shm` or `xdg_wm_base`.
|
||||
//! - "multi" globals represent a resource that the server gives you access to. These globals can be
|
||||
//! created or removed during the run of the program, and may exist as more than one instance, each
|
||||
//! representing a different physical resource. Examples of such globals are `wl_output` or `wl_seat`.
|
||||
//!
|
||||
//! The objects you need to handle these globals must implement one the two traits
|
||||
//! [`GlobalHandler<I>`](trait.GlobalHandler.html) or [`MultiGlobalHandler<I>`](trait.MultiGlobalHandler.html),
|
||||
//! depending on the kind of globals it will handle. These objects are responsible for binding the globals
|
||||
//! from the registry, and assigning them to filters to receive their events as necessary.
|
||||
//!
|
||||
//! This module provides a generic implementation of the [`GlobalHandler<I>`](trait.GlobalHandler.html) trait
|
||||
//! as [`SimpleGlobal<I>`](struct.SimpleGlobal.html). It can manage "single" globals that do not generate
|
||||
//! events, and thus require no filter.
|
||||
//!
|
||||
//! ## the `environment!` macro
|
||||
//!
|
||||
//! This macro is at the core of this module. See its documentation for details about how to
|
||||
//! use it: [`environment!`](../macro.environment.html). You can alternatively use the
|
||||
//! [`default_environment!`](../macro.default_environment.html) macro to quickly setup things and bring
|
||||
//! in all SCTK modules.
|
||||
|
||||
use std::io::Result;
|
||||
use std::rc::Rc;
|
||||
use std::{cell::RefCell, fmt};
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_display, wl_registry},
|
||||
Attached, DispatchData, EventQueue, GlobalEvent, GlobalManager, Interface, Proxy,
|
||||
};
|
||||
|
||||
/*
|
||||
* Traits definitions
|
||||
*/
|
||||
|
||||
/// Required trait for implementing a handler for "single" globals
|
||||
pub trait GlobalHandler<I: Interface> {
|
||||
/// This global was created and signaled in the registry with given id and version
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
ddata: DispatchData,
|
||||
);
|
||||
/// Access the global if it was signaled
|
||||
fn get(&self) -> Option<Attached<I>>;
|
||||
}
|
||||
|
||||
/// Required trait for implementing a handler for "multi" globals
|
||||
pub trait MultiGlobalHandler<I: Interface> {
|
||||
/// A new instance of this global was created with given id and version
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
ddata: DispatchData,
|
||||
);
|
||||
/// The instance with given id was removed
|
||||
fn removed(&mut self, id: u32, ddata: DispatchData);
|
||||
/// Access all the currently existing instances
|
||||
fn get_all(&self) -> Vec<Attached<I>>;
|
||||
}
|
||||
|
||||
/*
|
||||
* General Environment<E>
|
||||
*/
|
||||
|
||||
/// A Wayland Environment
|
||||
///
|
||||
/// This struct is generated by the `environment!` macro, see module-level documentation
|
||||
/// for more details about this.
|
||||
///
|
||||
/// This is the central point for accessing globals for your Wayland app. Any global that has
|
||||
/// previously been declared in the `environment!` macro can be access from this type via the
|
||||
/// `get_global`, `required_global` and `get_all_globals` methods.
|
||||
///
|
||||
/// This `Environment` is a handle that can be cloned.
|
||||
pub struct Environment<E> {
|
||||
/// The underlying `GlobalManager`, if you need to do manual interaction with the
|
||||
/// registry. See `wayland-client` documentation for details.
|
||||
pub manager: GlobalManager,
|
||||
inner: Rc<RefCell<E>>,
|
||||
}
|
||||
|
||||
impl<E: InnerEnv + 'static> Environment<E> {
|
||||
/// Create new `Environment`
|
||||
///
|
||||
/// This requires access to a `wl_display` attached to the `event_queue`.
|
||||
/// You also need to provide an instance of the inner environment type declared
|
||||
/// using the [`environment!`](../macro.environment.html) macro.
|
||||
///
|
||||
/// If you instead used the [`default_environment!`](../macro.default_environment.html), then
|
||||
/// you need to initialize your `Environment` using the
|
||||
/// [`new_default_environment!`](../macro.new_default_environment.html) macro.
|
||||
///
|
||||
/// `std::io::Error` could be returned if initial roundtrips to the server failed.
|
||||
///
|
||||
/// If this call indefinitely blocks when doing initial roundtrips this can only be
|
||||
/// caused by server bugs.
|
||||
pub fn new(
|
||||
display: &Attached<wl_display::WlDisplay>,
|
||||
queue: &mut EventQueue,
|
||||
env: E,
|
||||
) -> Result<Environment<E>> {
|
||||
let environment = Self::new_pending(display, env);
|
||||
|
||||
// Fully initialize the environment.
|
||||
queue.sync_roundtrip(&mut (), |event, _, _| {
|
||||
panic!(
|
||||
"Encountered unhandled event during initial roundtrip ({}::{})",
|
||||
event.interface, event.name
|
||||
);
|
||||
})?;
|
||||
queue.sync_roundtrip(&mut (), |event, _, _| {
|
||||
panic!(
|
||||
"Encountered unhandled event during initial roundtrip ({}::{})",
|
||||
event.interface, event.name
|
||||
);
|
||||
})?;
|
||||
|
||||
Ok(environment)
|
||||
}
|
||||
|
||||
/// Create new pending `Environment`
|
||||
///
|
||||
/// This requires access to a `wl_display` attached to an event queue (on which the main SCTK logic
|
||||
/// will be attached). You also need to provide an instance of the inner environment type declared
|
||||
/// using the [`environment!`](../macro.environment.html) macro.
|
||||
///
|
||||
/// If you instead used the [`default_environment!`](../macro.default_environment.html), then you need
|
||||
/// to initialize your `Environment` using the
|
||||
/// [`new_default_environment!`](../macro.new_default_environment.html) macro.
|
||||
///
|
||||
/// You should prefer to use `Environment::new`, unless you want to control initialization
|
||||
/// manually or you create additional environment meaning that the initialization may be fine
|
||||
/// with just `dispatch_pending` of the event queue, instead of two roundtrips to
|
||||
/// fully initialize environment. If you manually initialize your environment two sync
|
||||
/// roundtrips are required.
|
||||
pub fn new_pending(display: &Attached<wl_display::WlDisplay>, env: E) -> Environment<E> {
|
||||
let inner = Rc::new(RefCell::new(env));
|
||||
|
||||
let my_inner = inner.clone();
|
||||
let my_cb = move |event, registry, ddata: DispatchData| {
|
||||
let mut inner = my_inner.borrow_mut();
|
||||
inner.process_event(event, registry, ddata);
|
||||
};
|
||||
|
||||
let manager = GlobalManager::new_with_cb(display, my_cb);
|
||||
|
||||
Self { manager, inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Environment<E> {
|
||||
/// Access a "single" global
|
||||
///
|
||||
/// This method allows you to access any "single" global that has previously
|
||||
/// been declared in the `environment!` macro. It is forwarded to the `get()`
|
||||
/// method of the appropriate `GlobalHandler`.
|
||||
///
|
||||
/// It returns `None` if the global has not (yet) been signaled by the registry.
|
||||
pub fn get_global<I: Interface>(&self) -> Option<Attached<I>>
|
||||
where
|
||||
E: GlobalHandler<I>,
|
||||
{
|
||||
self.inner.borrow().get()
|
||||
}
|
||||
|
||||
/// Access a "single" global or panic
|
||||
///
|
||||
/// This method is similar to `get_global`, but will panic with a detailed error
|
||||
/// message if the requested global was not advertized by the server.
|
||||
pub fn require_global<I: Interface>(&self) -> Attached<I>
|
||||
where
|
||||
E: GlobalHandler<I>,
|
||||
{
|
||||
match self.inner.borrow().get() {
|
||||
Some(g) => g,
|
||||
None => panic!("[SCTK] A missing global was required: {}", I::NAME),
|
||||
}
|
||||
}
|
||||
|
||||
/// Access all instances of a "multi" global
|
||||
///
|
||||
/// This will return a `Vec` containing all currently existing instances of the
|
||||
/// requested "multi" global that has been previously declared in the `environment!`
|
||||
/// macro. It is forwarded to the `get_all()` method of the appropriate
|
||||
/// `MultiGlobalHandler`.
|
||||
pub fn get_all_globals<I: Interface>(&self) -> Vec<Attached<I>>
|
||||
where
|
||||
E: MultiGlobalHandler<I>,
|
||||
{
|
||||
self.inner.borrow().get_all()
|
||||
}
|
||||
|
||||
/// Access the inner environment
|
||||
///
|
||||
/// This gives your access, via a closure, to the inner type you declared
|
||||
/// via the [`environment!`](../macro.environment.html) or
|
||||
/// [`default_environment!`](../macro.default_environment.html) macro.
|
||||
///
|
||||
/// This method returns the return value of your closure.
|
||||
pub fn with_inner<T, F: FnOnce(&mut E) -> T>(&self, f: F) -> T {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
f(&mut *inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Clone for Environment<E> {
|
||||
fn clone(&self) -> Environment<E> {
|
||||
Environment { manager: self.manager.clone(), inner: self.inner.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> fmt::Debug for Environment<E>
|
||||
where
|
||||
E: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Environment")
|
||||
.field("manager", &self.manager)
|
||||
.field("inner", &self.inner)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal trait for the `Environment` logic
|
||||
///
|
||||
/// This trait is automatically implemented by the [`environment!`](../macro.environment.html)
|
||||
/// macro, you should not implement it manually unless you seriously want to.
|
||||
pub trait InnerEnv {
|
||||
/// Process a `GlobalEvent`
|
||||
fn process_event(
|
||||
&mut self,
|
||||
event: GlobalEvent,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
data: DispatchData,
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Simple handlers
|
||||
*/
|
||||
|
||||
/// A minimalist global handler for "single" globals
|
||||
///
|
||||
/// This handler will simply register the global as soon as the registry signals
|
||||
/// it, and do nothing more.
|
||||
///
|
||||
/// It is appropriate for globals that never generate events, like `wl_compositor`
|
||||
/// or `wl_data_device_manager`.
|
||||
#[derive(Debug)]
|
||||
pub struct SimpleGlobal<I: Interface> {
|
||||
global: Option<Attached<I>>,
|
||||
}
|
||||
|
||||
impl<I: Interface> SimpleGlobal<I> {
|
||||
/// Create a new handler
|
||||
pub fn new() -> SimpleGlobal<I> {
|
||||
SimpleGlobal { global: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Interface + Clone + From<Proxy<I>> + AsRef<Proxy<I>>> GlobalHandler<I> for SimpleGlobal<I> {
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
_: DispatchData,
|
||||
) {
|
||||
let version = I::VERSION.min(version);
|
||||
self.global = Some((*registry.bind::<I>(version, id)).clone())
|
||||
}
|
||||
fn get(&self) -> Option<Attached<I>> {
|
||||
self.global.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* environment! macro
|
||||
*/
|
||||
|
||||
/// Macro for declaring an environment
|
||||
///
|
||||
/// It needs to be used in conjunction with a a `struct` you declared, which will serve as the inner
|
||||
/// environment and hold the handlers for your globals.
|
||||
///
|
||||
/// The macro is invoked as such:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # extern crate smithay_client_toolkit as sctk;
|
||||
/// # use sctk::reexports::client::protocol::{wl_compositor::WlCompositor, wl_subcompositor::WlSubcompositor, wl_output::WlOutput};
|
||||
/// # use sctk::environment::SimpleGlobal;
|
||||
/// # use sctk::environment;
|
||||
/// # use sctk::output::OutputHandler;
|
||||
/// struct MyEnv {
|
||||
/// compositor: SimpleGlobal<WlCompositor>,
|
||||
/// subcompositor: SimpleGlobal<WlSubcompositor>,
|
||||
/// outputs: OutputHandler
|
||||
/// }
|
||||
///
|
||||
/// environment!(MyEnv,
|
||||
/// singles = [
|
||||
/// WlCompositor => compositor,
|
||||
/// WlSubcompositor => subcompositor,
|
||||
/// ],
|
||||
/// multis = [
|
||||
/// WlOutput => outputs,
|
||||
/// ]
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// This will define how your `MyEnv` struct is able to manage the `WlCompositor`, `WlSubcompositor` and
|
||||
/// `WlOutput` globals. For each global, you need to provide a pattern
|
||||
/// `$type => $name` where:
|
||||
///
|
||||
/// - `$type` is the type (implementing the `Interface` trait from `wayland-client`) representing a global
|
||||
/// - `$name` is the name of the field of `MyEnv` that is in charge of managing this global, implementing the
|
||||
/// appropriate `GlobalHandler` or `MultiGlobalHandler` trait
|
||||
///
|
||||
/// It is possible to route several globals to the same field as long as it implements all the appropriate traits.
|
||||
#[macro_export]
|
||||
macro_rules! environment {
|
||||
($env_name:ident,
|
||||
singles = [$($sty:ty => $sname:ident),* $(,)?],
|
||||
multis = [$($mty:ty => $mname:ident),* $(,)?]$(,)?
|
||||
) => {
|
||||
impl $crate::environment::InnerEnv for $env_name {
|
||||
fn process_event(
|
||||
&mut self,
|
||||
event: $crate::reexports::client::GlobalEvent,
|
||||
registry: $crate::reexports::client::Attached<$crate::reexports::client::protocol::wl_registry::WlRegistry>,
|
||||
ddata: $crate::reexports::client::DispatchData,
|
||||
) {
|
||||
match event {
|
||||
$crate::reexports::client::GlobalEvent::New { id, interface, version } => match &interface[..] {
|
||||
$(
|
||||
<$sty as $crate::reexports::client::Interface>::NAME => $crate::environment::GlobalHandler::<$sty>::created(&mut self.$sname, registry, id, version, ddata),
|
||||
)*
|
||||
$(
|
||||
<$mty as $crate::reexports::client::Interface>::NAME => $crate::environment::MultiGlobalHandler::<$mty>::created(&mut self.$mname, registry, id, version, ddata),
|
||||
)*
|
||||
_ => { /* ignore unkown globals */ }
|
||||
},
|
||||
$crate::reexports::client::GlobalEvent::Removed { id, interface } => match &interface[..] {
|
||||
$(
|
||||
<$mty as $crate::reexports::client::Interface>::NAME => $crate::environment::MultiGlobalHandler::<$mty>::removed(&mut self.$mname, id, ddata),
|
||||
)*
|
||||
_ => { /* ignore unknown globals */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
impl $crate::environment::GlobalHandler<$sty> for $env_name {
|
||||
fn created(&mut self, registry: $crate::reexports::client::Attached<$crate::reexports::client::protocol::wl_registry::WlRegistry>, id: u32, version: u32, ddata: $crate::reexports::client::DispatchData) {
|
||||
$crate::environment::GlobalHandler::<$sty>::created(&mut self.$sname, registry, id, version, ddata)
|
||||
}
|
||||
fn get(&self) -> Option<$crate::reexports::client::Attached<$sty>> {
|
||||
$crate::environment::GlobalHandler::<$sty>::get(&self.$sname)
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
$(
|
||||
impl $crate::environment::MultiGlobalHandler<$mty> for $env_name {
|
||||
fn created(&mut self, registry: $crate::reexports::client::Attached<$crate::reexports::client::protocol::wl_registry::WlRegistry>, id: u32, version: u32, ddata: $crate::reexports::client::DispatchData) {
|
||||
$crate::environment::MultiGlobalHandler::<$mty>::created(&mut self.$mname, registry, id, version, ddata)
|
||||
}
|
||||
fn removed(&mut self, id: u32, ddata: $crate::reexports::client::DispatchData) {
|
||||
$crate::environment::MultiGlobalHandler::<$mty>::removed(&mut self.$mname, id, ddata)
|
||||
}
|
||||
fn get_all(&self) -> Vec<$crate::reexports::client::Attached<$mty>> {
|
||||
$crate::environment::MultiGlobalHandler::<$mty>::get_all(&self.$mname)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
178
third-party/vendor/smithay-client-toolkit/src/event_loop.rs
vendored
Normal file
178
third-party/vendor/smithay-client-toolkit/src/event_loop.rs
vendored
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
use std::{io, os::unix::io::RawFd};
|
||||
|
||||
use calloop::{
|
||||
generic::Generic, EventSource, InsertError, Interest, LoopHandle, Mode, PostAction,
|
||||
RegistrationToken, TokenFactory,
|
||||
};
|
||||
|
||||
use wayland_client::{EventQueue, ReadEventsGuard};
|
||||
|
||||
/// An adapter to insert a Wayland `EventQueue` into a calloop event loop
|
||||
///
|
||||
/// This is a struct that implements `calloop::EventSource`. It generates an
|
||||
/// event whenever events need to be dispatched. At this point your calloop callback
|
||||
/// will be given access to the `EventQueue` and you should call `.dispatch_pending()`
|
||||
/// and forward its return value, allowing you to handle orphan events as you prefer.
|
||||
///
|
||||
/// If you don't use orphan events, the `quick_insert` method will directly
|
||||
/// insert the source into a provided `LoopHandle` with an adapter which will panic
|
||||
/// whenever an oprhan event is encountered.
|
||||
#[derive(Debug)]
|
||||
pub struct WaylandSource {
|
||||
queue: EventQueue,
|
||||
fd: Generic<RawFd>,
|
||||
read_guard: Option<ReadEventsGuard>,
|
||||
}
|
||||
|
||||
impl WaylandSource {
|
||||
/// Wrap an `EventQueue` as a `WaylandSource`.
|
||||
pub fn new(queue: EventQueue) -> WaylandSource {
|
||||
let fd = queue.display().get_connection_fd();
|
||||
WaylandSource { queue, fd: Generic::new(fd, Interest::READ, Mode::Level), read_guard: None }
|
||||
}
|
||||
|
||||
/// Insert this source into given event loop with an adapter that panics on orphan events
|
||||
///
|
||||
/// The adapter will pass the event loop's global shared data as `dispatch_data` too all
|
||||
/// callbacks.
|
||||
pub fn quick_insert<Data: 'static>(
|
||||
self,
|
||||
handle: LoopHandle<Data>,
|
||||
) -> Result<RegistrationToken, InsertError<WaylandSource>> {
|
||||
handle.insert_source(self, |(), queue, ddata| {
|
||||
queue.dispatch_pending(ddata, |event, object, _| {
|
||||
panic!(
|
||||
"[calloop] Encountered an orphan event: {}@{} : {}",
|
||||
event.interface,
|
||||
object.as_ref().id(),
|
||||
event.name
|
||||
);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Access the underlying event queue
|
||||
///
|
||||
/// This method can be used if you need to access the underlying `EventQueue` while this
|
||||
/// `WaylandSource` is currently inserted in an event loop.
|
||||
///
|
||||
/// Note that you should be careful when interacting with it if you invoke methods that
|
||||
/// interact with the wayland socket (such as `dispatch()` or `prepare_read()`). These may
|
||||
/// interefere with the proper waking up of this event source in the event loop.
|
||||
pub fn queue(&mut self) -> &mut EventQueue {
|
||||
&mut self.queue
|
||||
}
|
||||
}
|
||||
|
||||
impl EventSource for WaylandSource {
|
||||
type Event = ();
|
||||
type Error = std::io::Error;
|
||||
type Metadata = EventQueue;
|
||||
type Ret = std::io::Result<u32>;
|
||||
|
||||
fn process_events<F>(
|
||||
&mut self,
|
||||
readiness: calloop::Readiness,
|
||||
token: calloop::Token,
|
||||
mut callback: F,
|
||||
) -> std::io::Result<PostAction>
|
||||
where
|
||||
F: FnMut((), &mut EventQueue) -> std::io::Result<u32>,
|
||||
{
|
||||
let queue = &mut self.queue;
|
||||
let read_guard = &mut self.read_guard;
|
||||
self.fd.process_events(readiness, token, |_, _| {
|
||||
// 1. read events from the socket if any are available
|
||||
if let Some(guard) = read_guard.take() {
|
||||
// might be None if some other thread read events before us, concurently
|
||||
if let Err(e) = guard.read_events() {
|
||||
if e.kind() != io::ErrorKind::WouldBlock {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. dispatch any pending event in the queue (that's callback's job)
|
||||
loop {
|
||||
match queue.prepare_read() {
|
||||
Some(guard) => {
|
||||
*read_guard = Some(guard);
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
callback((), queue)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3. Once dispatching is finished, flush the responses to the compositor
|
||||
if let Err(e) = queue.display().flush() {
|
||||
if e.kind() != io::ErrorKind::WouldBlock {
|
||||
// in case of error, forward it and fast-exit
|
||||
return Err(e);
|
||||
}
|
||||
// WouldBlock error means the compositor could not process all our messages
|
||||
// quickly. Either it is slowed down or we are a spammer.
|
||||
// Should not really happen, if it does we do nothing and will flush again later
|
||||
}
|
||||
Ok(PostAction::Continue)
|
||||
})
|
||||
}
|
||||
|
||||
fn register(
|
||||
&mut self,
|
||||
poll: &mut calloop::Poll,
|
||||
token_factory: &mut TokenFactory,
|
||||
) -> calloop::Result<()> {
|
||||
self.fd.register(poll, token_factory)
|
||||
}
|
||||
|
||||
fn reregister(
|
||||
&mut self,
|
||||
poll: &mut calloop::Poll,
|
||||
token_factory: &mut TokenFactory,
|
||||
) -> calloop::Result<()> {
|
||||
self.fd.reregister(poll, token_factory)
|
||||
}
|
||||
|
||||
fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> {
|
||||
self.fd.unregister(poll)
|
||||
}
|
||||
|
||||
fn pre_run<F>(&mut self, mut callback: F) -> calloop::Result<()>
|
||||
where
|
||||
F: FnMut((), &mut EventQueue) -> std::io::Result<u32>,
|
||||
{
|
||||
debug_assert!(self.read_guard.is_none());
|
||||
// flush the display before starting to poll
|
||||
if let Err(e) = self.queue.display().flush() {
|
||||
if e.kind() != io::ErrorKind::WouldBlock {
|
||||
// in case of error, don't prepare a read, if the error is persitent,
|
||||
// it'll trigger in other wayland methods anyway
|
||||
log::error!("Error trying to flush the wayland display: {}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.queue.prepare_read() {
|
||||
Some(guard) => {
|
||||
self.read_guard = Some(guard);
|
||||
break;
|
||||
}
|
||||
None => {
|
||||
callback((), &mut self.queue)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn post_run<F>(&mut self, _: F) -> calloop::Result<()>
|
||||
where
|
||||
F: FnMut((), &mut EventQueue) -> std::io::Result<u32>,
|
||||
{
|
||||
// the destructor of ReadEventsGuard does the cleanup
|
||||
self.read_guard = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
9
third-party/vendor/smithay-client-toolkit/src/lazy_global.rs
vendored
Normal file
9
third-party/vendor/smithay-client-toolkit/src/lazy_global.rs
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use wayland_client::{Attached, Interface};
|
||||
|
||||
/// An utility for lazy-loading globals.
|
||||
#[derive(Debug)]
|
||||
pub enum LazyGlobal<I: Interface> {
|
||||
Unknown,
|
||||
Seen { id: u32, version: u32 },
|
||||
Bound(Attached<I>),
|
||||
}
|
||||
411
third-party/vendor/smithay-client-toolkit/src/lib.rs
vendored
Normal file
411
third-party/vendor/smithay-client-toolkit/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
//! Smithay Client Toolkit
|
||||
//!
|
||||
//! Provides various utilities and abstractions for comunicating with various
|
||||
//! Wayland compositors.
|
||||
//!
|
||||
//! ## `Environment`
|
||||
//!
|
||||
//! The crate is structured around the [`Environment`](environment/struct.Environment.html) type,
|
||||
//! which binds the wayland globals for you using a set of modular handlers. This type is used in conjunction
|
||||
//! with the [`environment!`](macro.environment.html) if you want full control, or by using the
|
||||
//! [`default_environment!`](macro.default_environment.html) macro to automatically bring in all
|
||||
//! SCTK modules.
|
||||
//!
|
||||
//! The various modules work by adding methods to the [`Environment`](environment/struct.Environment.html)
|
||||
//! type, giving you more capabilities as more modules are activated.
|
||||
//!
|
||||
//! ## Event Loops
|
||||
//!
|
||||
//! SCTK integrates with `calloop` to provide an event loop abstraction. Indeed most Wayland
|
||||
//! apps will need to handle more event sources than the single Wayland connection. These are
|
||||
//! necessary to handle things like keyboard repetition, copy-paste, or animated cursors.
|
||||
//!
|
||||
//! [`WaylandSource`](struct.WaylandSource.html) is an adapter to insert a Wayland `EventQueue` into
|
||||
//! a calloop event loop. And some of the modules of SCTK will provide you with other event sources
|
||||
//! that you need to insert into calloop for them to work correctly.
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
#![allow(clippy::new_without_default)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate dlib;
|
||||
|
||||
/// Re-exports of some crates, for convenience
|
||||
pub mod reexports {
|
||||
#[cfg(feature = "calloop")]
|
||||
pub use calloop;
|
||||
pub use wayland_client as client;
|
||||
pub use wayland_protocols as protocols;
|
||||
}
|
||||
|
||||
pub mod data_device;
|
||||
pub mod environment;
|
||||
mod lazy_global;
|
||||
pub mod output;
|
||||
pub mod primary_selection;
|
||||
pub mod seat;
|
||||
pub mod shell;
|
||||
pub mod shm;
|
||||
pub mod window;
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
mod event_loop;
|
||||
mod surface;
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
pub use event_loop::WaylandSource;
|
||||
pub use surface::{get_surface_outputs, get_surface_scale_factor};
|
||||
|
||||
#[macro_export]
|
||||
/// Declare a batteries-included SCTK environment
|
||||
///
|
||||
/// Similar to the [`environment!`](macro.environment.html) macro, but creates the type for you and
|
||||
/// includes all the handlers provided by SCTK, for use with the rest of the library. Its sister
|
||||
/// macro [`new_default_environment!`](macro.new_default_environment.html) needs to be used to
|
||||
/// initialize it.
|
||||
///
|
||||
/// This includes handlers for the following globals:
|
||||
///
|
||||
/// - `wl_compositor` as a [`SimpleGlobal`](environment/struct.SimpleGlobal.html)
|
||||
/// - `wl_data_device_manager` as a [`DataDeviceHandler`](data_device/struct.DataDeviceHandler.html)
|
||||
/// - `wl_output` with the [`OutputHandler`](output/struct.OutputHandler.html)
|
||||
/// - `wl_seat` with the [`SeatHandler`](seat/struct.SeatHandler.html)
|
||||
/// - `wl_subcompositor` as a [`SimpleGlobal`](environment/struct.SimpleGlobal.html)
|
||||
/// - `wl_shm` as a [`ShmHandler`](shm/struct.ShmHandler.html)
|
||||
/// - `zwp` and `gtk` primary selection device manager as a [`PrimarySelectionHandler`](primary_selection/struct.PrimarySelectionHandler.html)
|
||||
///
|
||||
/// If you don't need to add anything more, using it is as simple as:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use smithay_client_toolkit::default_environment;
|
||||
/// default_environment!(MyEnv);
|
||||
/// ```
|
||||
///
|
||||
/// The macro also provides some presets including more globals depending on your use-case:
|
||||
///
|
||||
/// - the `desktop` preset, invoked as `default_environment!(MyEnv, desktop);` additionally
|
||||
/// includes:
|
||||
/// - `xdg_shell` and `wl_shell` with the [`ShellHandler`](shell/struct.ShellHandler.html)
|
||||
/// - `xdg_decoration_manager` as a [`SimpleGlobal`](environment/struct.SimpleGlobal.html)
|
||||
///
|
||||
/// You can also add the `fields` argument to add additional fields to the generated struct, and
|
||||
/// the `singles` and `multis` arguments to route additional globals like with the
|
||||
/// [`environment!`](macro.environment.html) macro. These three fields are optional, but they must
|
||||
/// appear in this order, and after the optional preset
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use smithay_client_toolkit::default_environment;
|
||||
/// default_environment!(MyEnv,
|
||||
/// desktop, // the chosen preset, can be ommited
|
||||
/// fields=[
|
||||
/// somefield: u32,
|
||||
/// otherfield: String,
|
||||
/// ],
|
||||
/// singles=[
|
||||
/// // Add some routing here
|
||||
/// ],
|
||||
/// multis=[
|
||||
/// // add some routing here
|
||||
/// ]
|
||||
/// );
|
||||
/// ```
|
||||
macro_rules! default_environment {
|
||||
($env_name:ident, desktop
|
||||
$(,fields = [$($fname:ident : $fty:ty),* $(,)?])?
|
||||
$(,singles = [$($sty:ty => $sname: ident),* $(,)?])?
|
||||
$(,multis = [$($mty:ty => $mname:ident),* $(,)?])?
|
||||
$(,)?
|
||||
) => {
|
||||
$crate::default_environment!($env_name,
|
||||
fields=[
|
||||
// shell
|
||||
sctk_shell: $crate::shell::ShellHandler,
|
||||
// decoration
|
||||
sctk_decoration_mgr: $crate::environment::SimpleGlobal<$crate::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
|
||||
// others
|
||||
$($($fname : $fty,)*)?
|
||||
],
|
||||
singles = [
|
||||
// shell globals
|
||||
$crate::reexports::client::protocol::wl_shell::WlShell => sctk_shell,
|
||||
$crate::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase => sctk_shell,
|
||||
$crate::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6 => sctk_shell,
|
||||
// decoration
|
||||
$crate::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1 => sctk_decoration_mgr,
|
||||
// others
|
||||
$($($sty => $sname,)*)?
|
||||
],
|
||||
multis = [ $($($mty => $mname,)*)? ],
|
||||
);
|
||||
|
||||
// Shell utility
|
||||
impl $crate::shell::ShellHandling for $env_name {
|
||||
fn get_shell(&self) -> Option<$crate::shell::Shell> {
|
||||
self.sctk_shell.get_shell()
|
||||
}
|
||||
}
|
||||
};
|
||||
($env_name:ident
|
||||
$(,fields = [$($fname:ident : $fty:ty),* $(,)?])?
|
||||
$(,singles = [$($sty:ty => $sname:ident),* $(,)?])?
|
||||
$(,multis = [$($mty:ty => $mname:ident),* $(,)?])?
|
||||
$(,)?
|
||||
) => {
|
||||
/*
|
||||
* Declare the type
|
||||
*/
|
||||
pub struct $env_name {
|
||||
// SimpleGlobals
|
||||
sctk_compositor: $crate::environment::SimpleGlobal<$crate::reexports::client::protocol::wl_compositor::WlCompositor>,
|
||||
sctk_subcompositor: $crate::environment::SimpleGlobal<$crate::reexports::client::protocol::wl_subcompositor::WlSubcompositor>,
|
||||
// shm
|
||||
sctk_shm: $crate::shm::ShmHandler,
|
||||
// output
|
||||
sctk_outputs: $crate::output::OutputHandler,
|
||||
// seat
|
||||
sctk_seats: $crate::seat::SeatHandler,
|
||||
// data device
|
||||
sctk_data_device_manager: $crate::data_device::DataDeviceHandler,
|
||||
// primary selection
|
||||
sctk_primary_selection_manager: $crate::primary_selection::PrimarySelectionHandler,
|
||||
// user added
|
||||
$($(
|
||||
$fname : $fty,
|
||||
)*)?
|
||||
}
|
||||
|
||||
// SHM utility
|
||||
impl $crate::shm::ShmHandling for $env_name {
|
||||
fn shm_formats(&self) -> Vec<$crate::reexports::client::protocol::wl_shm::Format> {
|
||||
self.sctk_shm.shm_formats()
|
||||
}
|
||||
}
|
||||
|
||||
// Seat utility
|
||||
impl $crate::seat::SeatHandling for $env_name {
|
||||
fn listen<F>(&mut self, f: F) -> $crate::seat::SeatListener
|
||||
where F: FnMut(
|
||||
$crate::reexports::client::Attached<$crate::reexports::client::protocol::wl_seat::WlSeat>,
|
||||
&$crate::seat::SeatData,
|
||||
$crate::reexports::client::DispatchData
|
||||
) + 'static
|
||||
{
|
||||
self.sctk_seats.listen(f)
|
||||
}
|
||||
}
|
||||
|
||||
// Output utility
|
||||
impl $crate::output::OutputHandling for $env_name {
|
||||
fn listen<F>(&mut self, f: F) -> $crate::output::OutputStatusListener
|
||||
where F: FnMut(
|
||||
$crate::reexports::client::protocol::wl_output::WlOutput,
|
||||
&$crate::output::OutputInfo,
|
||||
$crate::reexports::client::DispatchData,
|
||||
) + 'static
|
||||
{
|
||||
self.sctk_outputs.listen(f)
|
||||
}
|
||||
}
|
||||
|
||||
// Data device utility
|
||||
impl $crate::data_device::DataDeviceHandling for $env_name {
|
||||
fn set_callback<F>(&mut self, callback: F) -> ::std::result::Result<(), $crate::MissingGlobal>
|
||||
where F: FnMut(
|
||||
$crate::reexports::client::protocol::wl_seat::WlSeat,
|
||||
$crate::data_device::DndEvent,
|
||||
$crate::reexports::client::DispatchData
|
||||
) + 'static
|
||||
{
|
||||
self.sctk_data_device_manager.set_callback(callback)
|
||||
}
|
||||
|
||||
fn with_device<F: FnOnce(&$crate::data_device::DataDevice)>(
|
||||
&self,
|
||||
seat: &$crate::reexports::client::protocol::wl_seat::WlSeat,
|
||||
f: F
|
||||
) -> ::std::result::Result<(), $crate::MissingGlobal> {
|
||||
self.sctk_data_device_manager.with_device(seat, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Primary selection utility
|
||||
impl $crate::primary_selection::PrimarySelectionHandling for $env_name {
|
||||
fn with_primary_selection<F>(
|
||||
&self,
|
||||
seat: &$crate::reexports::client::protocol::wl_seat::WlSeat,
|
||||
f: F,
|
||||
) -> ::std::result::Result<(), $crate::MissingGlobal>
|
||||
where F: FnOnce(&$crate::primary_selection::PrimarySelectionDevice)
|
||||
{
|
||||
self.sctk_primary_selection_manager.with_primary_selection(seat, f)
|
||||
}
|
||||
|
||||
fn get_primary_selection_manager(&self) -> Option<$crate::primary_selection::PrimarySelectionDeviceManager> {
|
||||
self.sctk_primary_selection_manager.get_primary_selection_manager()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Final macro delegation
|
||||
//
|
||||
$crate::environment!($env_name,
|
||||
singles = [
|
||||
// SimpleGlobals
|
||||
$crate::reexports::client::protocol::wl_compositor::WlCompositor => sctk_compositor,
|
||||
$crate::reexports::client::protocol::wl_subcompositor::WlSubcompositor => sctk_subcompositor,
|
||||
// shm
|
||||
$crate::reexports::client::protocol::wl_shm::WlShm => sctk_shm,
|
||||
// data device
|
||||
$crate::reexports::client::protocol::wl_data_device_manager::WlDataDeviceManager => sctk_data_device_manager,
|
||||
// primary selection
|
||||
$crate::reexports::protocols::unstable::primary_selection::v1::client::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1 => sctk_primary_selection_manager,
|
||||
$crate::reexports::protocols::misc::gtk_primary_selection::client::gtk_primary_selection_device_manager::GtkPrimarySelectionDeviceManager => sctk_primary_selection_manager,
|
||||
// user added
|
||||
$($($sty => $sname),*)?
|
||||
],
|
||||
multis = [
|
||||
// output globals
|
||||
$crate::reexports::client::protocol::wl_output::WlOutput => sctk_outputs,
|
||||
// seat globals
|
||||
$crate::reexports::client::protocol::wl_seat::WlSeat => sctk_seats,
|
||||
// user added
|
||||
$($($mty => $mname),*)?
|
||||
]
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
/// Initialize a batteries-included SCTK environment
|
||||
///
|
||||
/// Sister macro of [`default_environment!`](macro.default_environment.html). You need
|
||||
/// to use it to initialize the environment instead of
|
||||
/// [`Envrionment::init`](environment/struct.Environment.html). It has the same semantics.
|
||||
///
|
||||
/// If a preset was used for [`default_environment!`](macro.default_environment.html), it
|
||||
/// must be provided here as well.
|
||||
///
|
||||
/// The macro will automatically setup a Wayland connection and evaluate to a `Result`
|
||||
/// containing either `Ok((env, display, queue))`, providing you the initialized `Environment`
|
||||
/// as well as the wayland `Display` and `EventQueue` associated to it, or to an error
|
||||
/// if the connection failed.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use smithay_client_toolkit::{default_environment, new_default_environment};
|
||||
/// # default_environment!(MyEnv, desktop, fields=[somefield: u32, otherfield: String]);
|
||||
/// let (env, display, queue) = new_default_environment!(MyEnv,
|
||||
/// desktop, // the optional preset
|
||||
/// /* initializers for your extra fields if any, can be ommited if no fields are added */
|
||||
/// fields=[
|
||||
/// somefield: 42,
|
||||
/// otherfield: String::from("Hello World"),
|
||||
/// ]
|
||||
/// ).expect("Unable to connect to the wayland compositor");
|
||||
/// ```
|
||||
///
|
||||
/// If you instead want the macro to use some pre-existing display and event queue, you can
|
||||
/// add the `with` argument providing them. In that case the macro will evaluate to
|
||||
/// a `Result<Environment, io::Error>`, forwarding to you any error that may have occured
|
||||
/// during the initial roundtrips.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use smithay_client_toolkit::{default_environment, new_default_environment};
|
||||
/// # default_environment!(MyEnv, desktop, fields=[somefield: u32, otherfield: String]);
|
||||
/// # let display = smithay_client_toolkit::reexports::client::Display::connect_to_env().unwrap();
|
||||
/// # let mut queue = display.create_event_queue();
|
||||
/// let env = new_default_environment!(MyEnv,
|
||||
/// desktop, // the optional preset
|
||||
/// with=(display, queue), // the display and event queue to use
|
||||
/// /* initializers for your extra fields if any, can be ommited if no fields are added */
|
||||
/// fields=[
|
||||
/// somefield: 42,
|
||||
/// otherfield: String::from("Hello World"),
|
||||
/// ]
|
||||
/// ).expect("Initial roundtrips failed!");
|
||||
/// ```
|
||||
macro_rules! new_default_environment {
|
||||
($env_name:ident, desktop
|
||||
$(, with=($display:expr, $queue:expr))?
|
||||
$(,fields = [$($fname:ident : $fval:expr),* $(,)?])?
|
||||
$(,)?
|
||||
) => {
|
||||
$crate::new_default_environment!($env_name,
|
||||
$(with=($display, $queue),)?
|
||||
fields = [
|
||||
sctk_shell: $crate::shell::ShellHandler::new(),
|
||||
sctk_decoration_mgr: $crate::environment::SimpleGlobal::new(),
|
||||
$($(
|
||||
$fname: $fval,
|
||||
)*)?
|
||||
]
|
||||
)
|
||||
};
|
||||
($env_name:ident, with=($display:expr, $queue:expr)
|
||||
$(,fields = [$($fname:ident : $fval:expr),* $(,)?])?
|
||||
$(,)?
|
||||
) => {
|
||||
{
|
||||
let mut sctk_seats = $crate::seat::SeatHandler::new();
|
||||
let sctk_data_device_manager = $crate::data_device::DataDeviceHandler::init(&mut sctk_seats);
|
||||
let sctk_primary_selection_manager = $crate::primary_selection::PrimarySelectionHandler::init(&mut sctk_seats);
|
||||
|
||||
let display = $crate::reexports::client::Proxy::clone(&$display);
|
||||
let env = $crate::environment::Environment::new(&display.attach($queue.token()), &mut $queue,$env_name {
|
||||
sctk_compositor: $crate::environment::SimpleGlobal::new(),
|
||||
sctk_subcompositor: $crate::environment::SimpleGlobal::new(),
|
||||
sctk_shm: $crate::shm::ShmHandler::new(),
|
||||
sctk_outputs: $crate::output::OutputHandler::new(),
|
||||
sctk_seats,
|
||||
sctk_data_device_manager,
|
||||
sctk_primary_selection_manager,
|
||||
$($(
|
||||
$fname: $fval,
|
||||
)*)?
|
||||
});
|
||||
|
||||
if let Ok(env) = env.as_ref() {
|
||||
// Bind primary selection manager.
|
||||
let _psm = env.get_primary_selection_manager();
|
||||
}
|
||||
|
||||
env
|
||||
}
|
||||
};
|
||||
($env_name:ident
|
||||
$(,fields = [$($fname:ident : $fval:expr),* $(,)?])?
|
||||
$(,)?
|
||||
) => {
|
||||
$crate::reexports::client::Display::connect_to_env().and_then(|display| {
|
||||
let mut queue = display.create_event_queue();
|
||||
let ret = $crate::new_default_environment!(
|
||||
$env_name,
|
||||
with=(display, queue),
|
||||
fields=[$($($fname: $fval),*)?],
|
||||
);
|
||||
match ret {
|
||||
Ok(env) => Ok((env, display, queue)),
|
||||
Err(e) => {
|
||||
if let Some(perr) = display.protocol_error() {
|
||||
panic!("[SCTK] A protocol error occured during initial setup: {}", perr);
|
||||
} else {
|
||||
// For some other reason the connection with the compositor was lost
|
||||
// This should not arrive unless maybe the compositor was shutdown during
|
||||
// the initial setup...
|
||||
Err($crate::reexports::client::ConnectError::NoCompositorListening)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/// An error representing the fact that a required global was missing
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct MissingGlobal;
|
||||
|
||||
impl std::error::Error for MissingGlobal {}
|
||||
|
||||
impl std::fmt::Display for MissingGlobal {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("missing global")
|
||||
}
|
||||
}
|
||||
698
third-party/vendor/smithay-client-toolkit/src/output.rs
vendored
Normal file
698
third-party/vendor/smithay-client-toolkit/src/output.rs
vendored
Normal file
|
|
@ -0,0 +1,698 @@
|
|||
//! Types and functions related to graphical outputs
|
||||
//!
|
||||
//! This modules provides two main elements. The first is the
|
||||
//! [`OutputHandler`](struct.OutputHandler.html) type, which is a
|
||||
//! [`MultiGlobalHandler`](../environment/trait.MultiGlobalHandler.html) for
|
||||
//! use with the [`init_environment!`](../macro.init_environment.html) macro. It is automatically
|
||||
//! included if you use the [`new_default_environment!`](../macro.new_default_environment.html).
|
||||
//!
|
||||
//! The second is the [`with_output_info`](fn.with_output_info.html) with allows you to
|
||||
//! access the information associated to this output, as an [`OutputInfo`](struct.OutputInfo.html).
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt,
|
||||
rc::{self, Rc},
|
||||
sync::{self, Arc, Mutex},
|
||||
};
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{
|
||||
wl_output::{self, Event, WlOutput},
|
||||
wl_registry,
|
||||
},
|
||||
Attached, DispatchData, Main,
|
||||
};
|
||||
|
||||
use wayland_protocols::unstable::xdg_output::v1::client::{
|
||||
zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||
zxdg_output_v1::{self, ZxdgOutputV1},
|
||||
};
|
||||
|
||||
pub use wayland_client::protocol::wl_output::{Subpixel, Transform};
|
||||
|
||||
/// A possible mode for an output
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Mode {
|
||||
/// Number of pixels of this mode in format `(width, height)`
|
||||
///
|
||||
/// for example `(1920, 1080)`
|
||||
pub dimensions: (i32, i32),
|
||||
/// Refresh rate for this mode, in mHz
|
||||
pub refresh_rate: i32,
|
||||
/// Whether this is the current mode for this output
|
||||
pub is_current: bool,
|
||||
/// Whether this is the preferred mode for this output
|
||||
pub is_preferred: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[non_exhaustive]
|
||||
/// Compiled information about an output
|
||||
pub struct OutputInfo {
|
||||
/// The ID of this output as a global
|
||||
pub id: u32,
|
||||
/// The model name of this output as advertised by the server
|
||||
pub model: String,
|
||||
/// The make name of this output as advertised by the server
|
||||
pub make: String,
|
||||
/// The name of this output as advertised by the server
|
||||
///
|
||||
/// Each name is unique among all wl_output globals, but if a wl_output
|
||||
/// global is destroyed the same name may be reused later. The names will
|
||||
/// also remain consistent across sessions with the same hardware and
|
||||
/// software configuration.
|
||||
///
|
||||
/// Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
|
||||
/// not assume that the name is a reflection of an underlying DRM connector,
|
||||
/// X11 connection, etc.
|
||||
///
|
||||
/// Note that this is not filled in by version 3 of the wl_output protocol,
|
||||
/// but it has been proposed for inclusion in version 4. Until then, it is
|
||||
/// only filled in if your environment has an [XdgOutputHandler] global
|
||||
/// handler for [ZxdgOutputManagerV1].
|
||||
pub name: String,
|
||||
/// The description of this output as advertised by the server
|
||||
///
|
||||
/// The description is a UTF-8 string with no convention defined for its
|
||||
/// contents. The description is not guaranteed to be unique among all
|
||||
/// wl_output globals. Examples might include 'Foocorp 11" Display' or
|
||||
/// 'Virtual X11 output via :1'.
|
||||
///
|
||||
/// Note that this is not filled in by version 3 of the wl_output protocol,
|
||||
/// but it has been proposed for inclusion in version 4. Until then, it is
|
||||
/// only filled in if your environment has an [XdgOutputHandler] global
|
||||
/// handler for [ZxdgOutputManagerV1].
|
||||
pub description: String,
|
||||
/// Location of the top-left corner of this output in compositor
|
||||
/// space
|
||||
///
|
||||
/// Note that the compositor may decide to always report (0,0) if
|
||||
/// it decides clients are not allowed to know this information.
|
||||
pub location: (i32, i32),
|
||||
/// Physical dimensions of this output, in unspecified units
|
||||
pub physical_size: (i32, i32),
|
||||
/// The subpixel layout for this output
|
||||
pub subpixel: Subpixel,
|
||||
/// The current transformation applied to this output
|
||||
///
|
||||
/// You can pre-render your buffers taking this information
|
||||
/// into account and advertising it via `wl_buffer.set_tranform`
|
||||
/// for better performances.
|
||||
pub transform: Transform,
|
||||
/// The scaling factor of this output
|
||||
///
|
||||
/// Any buffer whose scaling factor does not match the one
|
||||
/// of the output it is displayed on will be rescaled accordingly.
|
||||
///
|
||||
/// For example, a buffer of scaling factor 1 will be doubled in
|
||||
/// size if the output scaling factor is 2.
|
||||
pub scale_factor: i32,
|
||||
/// Possible modes for an output
|
||||
pub modes: Vec<Mode>,
|
||||
/// Has this output been unadvertized by the registry
|
||||
///
|
||||
/// If this is the case, it has become inert, you might want to
|
||||
/// call its `release()` method if you don't plan to use it any
|
||||
/// longer.
|
||||
pub obsolete: bool,
|
||||
}
|
||||
|
||||
impl OutputInfo {
|
||||
fn new(id: u32) -> OutputInfo {
|
||||
OutputInfo {
|
||||
id,
|
||||
model: String::new(),
|
||||
make: String::new(),
|
||||
name: String::new(),
|
||||
description: String::new(),
|
||||
location: (0, 0),
|
||||
physical_size: (0, 0),
|
||||
subpixel: Subpixel::Unknown,
|
||||
transform: Transform::Normal,
|
||||
scale_factor: 1,
|
||||
modes: Vec::new(),
|
||||
obsolete: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OutputCallback = dyn Fn(WlOutput, &OutputInfo, DispatchData) + Send + Sync;
|
||||
|
||||
enum OutputData {
|
||||
Ready {
|
||||
info: OutputInfo,
|
||||
callbacks: Vec<sync::Weak<OutputCallback>>,
|
||||
},
|
||||
Pending {
|
||||
id: u32,
|
||||
has_xdg: bool,
|
||||
events: Vec<Event>,
|
||||
callbacks: Vec<sync::Weak<OutputCallback>>,
|
||||
},
|
||||
PendingXDG {
|
||||
info: OutputInfo,
|
||||
callbacks: Vec<sync::Weak<OutputCallback>>,
|
||||
},
|
||||
}
|
||||
|
||||
type OutputStatusCallback = dyn FnMut(WlOutput, &OutputInfo, DispatchData) + 'static;
|
||||
|
||||
/// A handler for `wl_output`
|
||||
///
|
||||
/// This handler can be used for managing `wl_output` in the
|
||||
/// [`init_environment!`](../macro.init_environment.html) macro, and is automatically
|
||||
/// included in [`new_default_environment!`](../macro.new_default_environment.html).
|
||||
///
|
||||
/// It aggregates the output information and makes it available via the
|
||||
/// [`with_output_info`](fn.with_output_info.html) function.
|
||||
pub struct OutputHandler {
|
||||
outputs: Vec<(u32, Attached<WlOutput>)>,
|
||||
status_listeners: Rc<RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>>,
|
||||
xdg_listener: Option<rc::Weak<RefCell<XdgOutputHandlerInner>>>,
|
||||
}
|
||||
|
||||
impl OutputHandler {
|
||||
/// Create a new instance of this handler
|
||||
pub fn new() -> OutputHandler {
|
||||
OutputHandler {
|
||||
outputs: Vec::new(),
|
||||
status_listeners: Rc::new(RefCell::new(Vec::new())),
|
||||
xdg_listener: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::environment::MultiGlobalHandler<WlOutput> for OutputHandler {
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
_: DispatchData,
|
||||
) {
|
||||
// We currently support wl_output up to version 4
|
||||
let version = std::cmp::min(version, 4);
|
||||
let output = registry.bind::<WlOutput>(version, id);
|
||||
let has_xdg = if let Some(xdg) = self.xdg_listener.as_ref().and_then(rc::Weak::upgrade) {
|
||||
xdg.borrow_mut().new_xdg_output(&output, &self.status_listeners)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if version > 1 {
|
||||
// wl_output.done event was only added at version 2
|
||||
// In case of an old version 1, we just behave as if it was send at the start
|
||||
output.as_ref().user_data().set_threadsafe(|| {
|
||||
Mutex::new(OutputData::Pending { id, has_xdg, events: vec![], callbacks: vec![] })
|
||||
});
|
||||
} else {
|
||||
output.as_ref().user_data().set_threadsafe(|| {
|
||||
Mutex::new(OutputData::Ready { info: OutputInfo::new(id), callbacks: vec![] })
|
||||
});
|
||||
}
|
||||
let status_listeners_handle = self.status_listeners.clone();
|
||||
let xdg_listener_handle = self.xdg_listener.clone();
|
||||
output.quick_assign(move |output, event, ddata| {
|
||||
process_output_event(
|
||||
output,
|
||||
event,
|
||||
ddata,
|
||||
&status_listeners_handle,
|
||||
&xdg_listener_handle,
|
||||
)
|
||||
});
|
||||
self.outputs.push((id, (*output).clone()));
|
||||
}
|
||||
fn removed(&mut self, id: u32, mut ddata: DispatchData) {
|
||||
let status_listeners_handle = &self.status_listeners;
|
||||
let xdg_listener_handle = &self.xdg_listener;
|
||||
self.outputs.retain(|(i, o)| {
|
||||
if *i != id {
|
||||
true
|
||||
} else {
|
||||
make_obsolete(o, ddata.reborrow(), status_listeners_handle, xdg_listener_handle);
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
fn get_all(&self) -> Vec<Attached<WlOutput>> {
|
||||
self.outputs.iter().map(|(_, o)| o.clone()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for OutputHandler {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("OutputHandler")
|
||||
.field("outputs", &self.outputs)
|
||||
.field("status_listeners", &"Fn() -> { ... }")
|
||||
.field("xdg_listener", &self.xdg_listener)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn process_output_event(
|
||||
output: Main<WlOutput>,
|
||||
event: Event,
|
||||
mut ddata: DispatchData,
|
||||
listeners: &Rc<RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>>,
|
||||
xdg_listener: &Option<rc::Weak<RefCell<XdgOutputHandlerInner>>>,
|
||||
) {
|
||||
let udata_mutex = output
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<Mutex<OutputData>>()
|
||||
.expect("SCTK: wl_output has invalid UserData");
|
||||
let mut udata = udata_mutex.lock().unwrap();
|
||||
if let Event::Done = event {
|
||||
let (id, has_xdg, pending_events, mut callbacks) = match *udata {
|
||||
OutputData::Pending { id, has_xdg, events: ref mut v, callbacks: ref mut cb } => {
|
||||
(id, has_xdg, std::mem::take(v), std::mem::take(cb))
|
||||
}
|
||||
OutputData::PendingXDG { ref mut info, ref mut callbacks } => {
|
||||
notify(&output, info, ddata.reborrow(), callbacks);
|
||||
notify_status_listeners(&output, info, ddata, listeners);
|
||||
let info = info.clone();
|
||||
let callbacks = std::mem::take(callbacks);
|
||||
*udata = OutputData::Ready { info, callbacks };
|
||||
return;
|
||||
}
|
||||
OutputData::Ready { ref mut info, ref mut callbacks } => {
|
||||
// a Done event on an output that is already ready was due to a
|
||||
// status change (which was already merged)
|
||||
notify(&output, info, ddata, callbacks);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut info = OutputInfo::new(id);
|
||||
for evt in pending_events {
|
||||
merge_event(&mut info, evt);
|
||||
}
|
||||
notify(&output, &info, ddata.reborrow(), &mut callbacks);
|
||||
if let Some(xdg) = xdg_listener.as_ref().and_then(rc::Weak::upgrade) {
|
||||
if has_xdg || xdg.borrow_mut().new_xdg_output(&output, listeners) {
|
||||
*udata = OutputData::PendingXDG { info, callbacks };
|
||||
return;
|
||||
}
|
||||
}
|
||||
notify_status_listeners(&output, &info, ddata, listeners);
|
||||
*udata = OutputData::Ready { info, callbacks };
|
||||
} else {
|
||||
match *udata {
|
||||
OutputData::Pending { events: ref mut v, .. } => v.push(event),
|
||||
OutputData::PendingXDG { ref mut info, .. }
|
||||
| OutputData::Ready { ref mut info, .. } => {
|
||||
merge_event(info, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_obsolete(
|
||||
output: &Attached<WlOutput>,
|
||||
mut ddata: DispatchData,
|
||||
listeners: &RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>,
|
||||
xdg_listener: &Option<rc::Weak<RefCell<XdgOutputHandlerInner>>>,
|
||||
) {
|
||||
let udata_mutex = output
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<Mutex<OutputData>>()
|
||||
.expect("SCTK: wl_output has invalid UserData");
|
||||
let mut udata = udata_mutex.lock().unwrap();
|
||||
if let Some(xdg) = xdg_listener.as_ref().and_then(rc::Weak::upgrade) {
|
||||
xdg.borrow_mut().destroy_xdg_output(output);
|
||||
}
|
||||
let (id, mut callbacks) = match *udata {
|
||||
OutputData::PendingXDG { ref mut info, ref mut callbacks }
|
||||
| OutputData::Ready { ref mut info, ref mut callbacks } => {
|
||||
info.obsolete = true;
|
||||
notify(output, info, ddata.reborrow(), callbacks);
|
||||
notify_status_listeners(output, info, ddata, listeners);
|
||||
return;
|
||||
}
|
||||
OutputData::Pending { id, callbacks: ref mut cb, .. } => (id, std::mem::take(cb)),
|
||||
};
|
||||
let mut info = OutputInfo::new(id);
|
||||
info.obsolete = true;
|
||||
notify(output, &info, ddata.reborrow(), &mut callbacks);
|
||||
notify_status_listeners(output, &info, ddata, listeners);
|
||||
*udata = OutputData::Ready { info, callbacks };
|
||||
}
|
||||
|
||||
fn merge_event(info: &mut OutputInfo, event: Event) {
|
||||
match event {
|
||||
Event::Geometry {
|
||||
x,
|
||||
y,
|
||||
physical_width,
|
||||
physical_height,
|
||||
subpixel,
|
||||
model,
|
||||
make,
|
||||
transform,
|
||||
} => {
|
||||
info.location = (x, y);
|
||||
info.physical_size = (physical_width, physical_height);
|
||||
info.subpixel = subpixel;
|
||||
info.transform = transform;
|
||||
info.model = model;
|
||||
info.make = make;
|
||||
}
|
||||
Event::Scale { factor } => {
|
||||
info.scale_factor = factor;
|
||||
}
|
||||
Event::Mode { width, height, refresh, flags } => {
|
||||
let mut found = false;
|
||||
if let Some(mode) = info
|
||||
.modes
|
||||
.iter_mut()
|
||||
.find(|m| m.dimensions == (width, height) && m.refresh_rate == refresh)
|
||||
{
|
||||
// this mode already exists, update it
|
||||
mode.is_preferred = flags.contains(wl_output::Mode::Preferred);
|
||||
mode.is_current = flags.contains(wl_output::Mode::Current);
|
||||
found = true;
|
||||
}
|
||||
if !found {
|
||||
// otherwise, add it
|
||||
info.modes.push(Mode {
|
||||
dimensions: (width, height),
|
||||
refresh_rate: refresh,
|
||||
is_preferred: flags.contains(wl_output::Mode::Preferred),
|
||||
is_current: flags.contains(wl_output::Mode::Current),
|
||||
})
|
||||
}
|
||||
}
|
||||
Event::Name { name } => {
|
||||
info.name = name;
|
||||
}
|
||||
Event::Description { description } => {
|
||||
info.description = description;
|
||||
}
|
||||
// ignore all other events
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn notify(
|
||||
output: &WlOutput,
|
||||
info: &OutputInfo,
|
||||
mut ddata: DispatchData,
|
||||
callbacks: &mut Vec<sync::Weak<OutputCallback>>,
|
||||
) {
|
||||
callbacks.retain(|weak| {
|
||||
if let Some(arc) = sync::Weak::upgrade(weak) {
|
||||
(*arc)(output.clone(), info, ddata.reborrow());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn notify_status_listeners(
|
||||
output: &WlOutput,
|
||||
info: &OutputInfo,
|
||||
mut ddata: DispatchData,
|
||||
listeners: &RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>,
|
||||
) {
|
||||
// Notify the callbacks listening for new outputs
|
||||
listeners.borrow_mut().retain(|lst| {
|
||||
if let Some(cb) = rc::Weak::upgrade(lst) {
|
||||
(cb.borrow_mut())(output.clone(), info, ddata.reborrow());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Access the info associated with this output
|
||||
///
|
||||
/// The provided closure is given the [`OutputInfo`](struct.OutputInfo.html) as argument,
|
||||
/// and its return value is returned from this function.
|
||||
///
|
||||
/// If the provided `WlOutput` has not yet been initialized or is not managed by SCTK, `None` is returned.
|
||||
///
|
||||
/// If the output has been removed by the compositor, the `obsolete` field of the `OutputInfo`
|
||||
/// will be set to `true`. This handler will not automatically detroy the output by calling its
|
||||
/// `release` method, to avoid interfering with your logic.
|
||||
pub fn with_output_info<T, F: FnOnce(&OutputInfo) -> T>(output: &WlOutput, f: F) -> Option<T> {
|
||||
if let Some(udata_mutex) = output.as_ref().user_data().get::<Mutex<OutputData>>() {
|
||||
let udata = udata_mutex.lock().unwrap();
|
||||
match *udata {
|
||||
OutputData::PendingXDG { ref info, .. } | OutputData::Ready { ref info, .. } => {
|
||||
Some(f(info))
|
||||
}
|
||||
OutputData::Pending { .. } => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a listener to this output
|
||||
///
|
||||
/// The provided closure will be called whenever a property of the output changes,
|
||||
/// including when it is removed by the compositor (in this case it'll be marked as
|
||||
/// obsolete).
|
||||
///
|
||||
/// The returned [`OutputListener`](struct.OutputListener) keeps your callback alive,
|
||||
/// dropping it will disable the callback and free the closure.
|
||||
pub fn add_output_listener<F: Fn(WlOutput, &OutputInfo, DispatchData) + Send + Sync + 'static>(
|
||||
output: &WlOutput,
|
||||
f: F,
|
||||
) -> OutputListener {
|
||||
let arc = Arc::new(f) as Arc<_>;
|
||||
|
||||
if let Some(udata_mutex) = output.as_ref().user_data().get::<Mutex<OutputData>>() {
|
||||
let mut udata = udata_mutex.lock().unwrap();
|
||||
|
||||
match *udata {
|
||||
OutputData::Pending { ref mut callbacks, .. }
|
||||
| OutputData::PendingXDG { ref mut callbacks, .. }
|
||||
| OutputData::Ready { ref mut callbacks, .. } => {
|
||||
callbacks.push(Arc::downgrade(&arc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OutputListener { _cb: arc }
|
||||
}
|
||||
|
||||
/// A handle to an output listener callback
|
||||
///
|
||||
/// Dropping it disables the associated callback and frees the closure.
|
||||
pub struct OutputListener {
|
||||
_cb: Arc<dyn Fn(WlOutput, &OutputInfo, DispatchData) + Send + Sync + 'static>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for OutputListener {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("OutputListener").field("_cb", &"fn() -> { ... }").finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to an output status callback
|
||||
///
|
||||
/// Dropping it disables the associated callback and frees the closure.
|
||||
pub struct OutputStatusListener {
|
||||
_cb: Rc<RefCell<OutputStatusCallback>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for OutputStatusListener {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("OutputStatusListener").field("_cb", &"fn() -> { ... }").finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait representing the OutputHandler functions
|
||||
///
|
||||
/// Implementing this trait on your inner environment struct used with the
|
||||
/// [`environment!`](../macro.environment.html) by delegating it to its
|
||||
/// [`OutputHandler`](struct.OutputHandler.html) field will make available the output-associated
|
||||
/// method on your [`Environment`](../environment/struct.Environment.html).
|
||||
pub trait OutputHandling {
|
||||
/// Insert a listener for output creation and removal events
|
||||
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData) + 'static>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> OutputStatusListener;
|
||||
}
|
||||
|
||||
impl OutputHandling for OutputHandler {
|
||||
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData) + 'static>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> OutputStatusListener {
|
||||
let rc = Rc::new(RefCell::new(f)) as Rc<_>;
|
||||
self.status_listeners.borrow_mut().push(Rc::downgrade(&rc));
|
||||
OutputStatusListener { _cb: rc }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: OutputHandling> crate::environment::Environment<E> {
|
||||
/// Insert a new listener for outputs
|
||||
///
|
||||
/// The provided closure will be invoked whenever a `wl_output` is created or removed.
|
||||
///
|
||||
/// Note that if outputs already exist when this callback is setup, it'll not be invoked on them.
|
||||
/// For you to be notified of them as well, you need to first process them manually by calling
|
||||
/// `.get_all_outputs()`.
|
||||
///
|
||||
/// The returned [`OutputStatusListener`](../output/struct.OutputStatusListener.hmtl) keeps your
|
||||
/// callback alive, dropping it will disable it.
|
||||
#[must_use = "the returned OutputStatusListener keeps your callback alive, dropping it will disable it"]
|
||||
pub fn listen_for_outputs<F: FnMut(WlOutput, &OutputInfo, DispatchData) + 'static>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> OutputStatusListener {
|
||||
self.with_inner(move |inner| OutputHandling::listen(inner, f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: crate::environment::MultiGlobalHandler<WlOutput>> crate::environment::Environment<E> {
|
||||
/// Shorthand method to retrieve the list of outputs
|
||||
pub fn get_all_outputs(&self) -> Vec<WlOutput> {
|
||||
self.get_all_globals::<WlOutput>().into_iter().map(|o| o.detach()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A handler for `zxdg_output_manager_v1`
|
||||
///
|
||||
/// This handler adds additional information to the OutputInfo struct that is
|
||||
/// available through the xdg_output interface. Because this requires binding
|
||||
/// the two handlers together when they are being created, it does not work with
|
||||
/// [`new_default_environment!`](../macro.new_default_environment.html); you
|
||||
/// must use [`default_environment!`](../macro.default_environment.html) and
|
||||
/// create the [OutputHandler] outside the constructor.
|
||||
///
|
||||
/// ```no_compile
|
||||
/// let (sctk_outputs, sctk_xdg_out) = smithay_client_toolkit::output::XdgOutputHandler::new_output_handlers();
|
||||
///
|
||||
/// let env = smithay_client_toolkit::environment::Environment::new(&wl_display, &mut wl_queue, Globals {
|
||||
/// sctk_compositor: SimpleGlobal::new(),
|
||||
/// sctk_shm: smithay_client_toolkit::shm::ShmHandler::new(),
|
||||
/// sctk_seats : smithay_client_toolkit::seat::SeatHandler::new(),
|
||||
/// sctk_shell : smithay_client_toolkit::shell::ShellHandler::new(),
|
||||
/// sctk_outputs,
|
||||
/// sctk_xdg_out,
|
||||
/// // ...
|
||||
/// })?;
|
||||
///
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct XdgOutputHandler {
|
||||
inner: Rc<RefCell<XdgOutputHandlerInner>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct XdgOutputHandlerInner {
|
||||
xdg_manager: Option<Attached<ZxdgOutputManagerV1>>,
|
||||
outputs: Vec<(WlOutput, Attached<ZxdgOutputV1>)>,
|
||||
}
|
||||
|
||||
impl XdgOutputHandler {
|
||||
/// Create a new instance of this handler bound to the given OutputHandler.
|
||||
pub fn new(output_handler: &mut OutputHandler) -> Self {
|
||||
let inner =
|
||||
Rc::new(RefCell::new(XdgOutputHandlerInner { xdg_manager: None, outputs: Vec::new() }));
|
||||
output_handler.xdg_listener = Some(Rc::downgrade(&inner));
|
||||
XdgOutputHandler { inner }
|
||||
}
|
||||
|
||||
/// Helper function to create a bound pair of OutputHandler and XdgOutputHandler.
|
||||
pub fn new_output_handlers() -> (OutputHandler, Self) {
|
||||
let mut oh = OutputHandler::new();
|
||||
let xh = XdgOutputHandler::new(&mut oh);
|
||||
(oh, xh)
|
||||
}
|
||||
}
|
||||
|
||||
impl XdgOutputHandlerInner {
|
||||
fn new_xdg_output(
|
||||
&mut self,
|
||||
output: &WlOutput,
|
||||
listeners: &Rc<RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>>,
|
||||
) -> bool {
|
||||
if let Some(xdg_manager) = &self.xdg_manager {
|
||||
let xdg_main = xdg_manager.get_xdg_output(output);
|
||||
let wl_out = output.clone();
|
||||
let listeners = listeners.clone();
|
||||
xdg_main.quick_assign(move |_xdg_out, event, ddata| {
|
||||
process_xdg_event(&wl_out, event, ddata, &listeners)
|
||||
});
|
||||
self.outputs.push((output.clone(), xdg_main.into()));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
fn destroy_xdg_output(&mut self, output: &WlOutput) {
|
||||
self.outputs.retain(|(out, xdg_out)| {
|
||||
if out.as_ref().is_alive() && out != output {
|
||||
true
|
||||
} else {
|
||||
xdg_out.destroy();
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn process_xdg_event(
|
||||
wl_out: &WlOutput,
|
||||
event: zxdg_output_v1::Event,
|
||||
mut ddata: DispatchData,
|
||||
listeners: &RefCell<Vec<rc::Weak<RefCell<OutputStatusCallback>>>>,
|
||||
) {
|
||||
use zxdg_output_v1::Event;
|
||||
let udata_mutex = wl_out
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<Mutex<OutputData>>()
|
||||
.expect("SCTK: wl_output has invalid UserData");
|
||||
let mut udata = udata_mutex.lock().unwrap();
|
||||
let (info, callbacks, pending) = match &mut *udata {
|
||||
OutputData::Ready { info, callbacks } => (info, callbacks, false),
|
||||
OutputData::PendingXDG { info, callbacks } => (info, callbacks, true),
|
||||
OutputData::Pending { .. } => unreachable!(),
|
||||
};
|
||||
match event {
|
||||
Event::Name { name } => {
|
||||
info.name = name;
|
||||
}
|
||||
Event::Description { description } => {
|
||||
info.description = description;
|
||||
}
|
||||
Event::Done => {
|
||||
notify(wl_out, info, ddata.reborrow(), callbacks);
|
||||
if pending {
|
||||
notify_status_listeners(wl_out, info, ddata, listeners);
|
||||
let info = info.clone();
|
||||
let callbacks = std::mem::take(callbacks);
|
||||
*udata = OutputData::Ready { info, callbacks };
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::environment::GlobalHandler<ZxdgOutputManagerV1> for XdgOutputHandler {
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
_: DispatchData,
|
||||
) {
|
||||
let version = std::cmp::min(version, 3);
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let xdg_manager: Main<ZxdgOutputManagerV1> = registry.bind(version, id);
|
||||
inner.xdg_manager = Some(xdg_manager.into());
|
||||
}
|
||||
fn get(&self) -> Option<Attached<ZxdgOutputManagerV1>> {
|
||||
let inner = self.inner.borrow();
|
||||
inner.xdg_manager.clone()
|
||||
}
|
||||
}
|
||||
166
third-party/vendor/smithay-client-toolkit/src/primary_selection/device.rs
vendored
Normal file
166
third-party/vendor/smithay-client-toolkit/src/primary_selection/device.rs
vendored
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use wayland_protocols::{
|
||||
misc::gtk_primary_selection::client::gtk_primary_selection_device::{
|
||||
self, GtkPrimarySelectionDevice,
|
||||
},
|
||||
unstable::primary_selection::v1::client::zwp_primary_selection_device_v1::{
|
||||
self, ZwpPrimarySelectionDeviceV1,
|
||||
},
|
||||
};
|
||||
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
|
||||
use crate::primary_selection::offer::PrimarySelectionOfferImpl;
|
||||
use crate::primary_selection::source::PrimarySelectionSourceImpl;
|
||||
|
||||
use super::PrimarySelectionDeviceManager;
|
||||
use super::PrimarySelectionOffer;
|
||||
use super::PrimarySelectionSource;
|
||||
|
||||
/// Handle to support primary selection on a given seat.
|
||||
///
|
||||
/// This type provides you with copy/paste actions. It is associated with a seat upon creation.
|
||||
#[derive(Debug)]
|
||||
pub struct PrimarySelectionDevice {
|
||||
device: PrimarySelectionDeviceImpl,
|
||||
inner: Arc<Mutex<PrimarySelectionDeviceInner>>,
|
||||
}
|
||||
|
||||
/// Possible supported primary selection devices.
|
||||
#[derive(Debug)]
|
||||
enum PrimarySelectionDeviceImpl {
|
||||
Zwp(ZwpPrimarySelectionDeviceV1),
|
||||
Gtk(GtkPrimarySelectionDevice),
|
||||
}
|
||||
|
||||
/// Inner state for `PrimarySelectionDevice`.
|
||||
#[derive(Debug)]
|
||||
struct PrimarySelectionDeviceInner {
|
||||
/// Current selection.
|
||||
selection: Option<PrimarySelectionOffer>,
|
||||
|
||||
/// List of known offers.
|
||||
know_offers: Vec<PrimarySelectionOffer>,
|
||||
}
|
||||
|
||||
impl PrimarySelectionDeviceInner {
|
||||
/// Provide a primary selection source as the new content for the primary selection.
|
||||
///
|
||||
/// Correspond to traditional copy/paste behavior. Setting the source to `None` will clear
|
||||
/// the selection.
|
||||
fn set_selection(&mut self, offer: Option<PrimarySelectionOfferImpl>) {
|
||||
let offer = match offer {
|
||||
Some(offer) => offer,
|
||||
None => {
|
||||
// Drop the current offer if any.
|
||||
self.selection = None;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(id) = self.know_offers.iter().position(|o| o.offer == offer) {
|
||||
self.selection = Some(self.know_offers.swap_remove(id));
|
||||
} else {
|
||||
panic!("Compositor set an unknown primary offer for a primary selection.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PrimarySelectionDevice {
|
||||
fn drop(&mut self) {
|
||||
match self.device {
|
||||
PrimarySelectionDeviceImpl::Zwp(ref device) => device.destroy(),
|
||||
PrimarySelectionDeviceImpl::Gtk(ref device) => device.destroy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimarySelectionDevice {
|
||||
/// Create the `PrimarySelectionDevice` helper for this seat.
|
||||
pub fn init_for_seat(manager: &PrimarySelectionDeviceManager, seat: &WlSeat) -> Self {
|
||||
let inner = Arc::new(Mutex::new(PrimarySelectionDeviceInner {
|
||||
selection: None,
|
||||
know_offers: Vec::new(),
|
||||
}));
|
||||
|
||||
let inner2 = inner.clone();
|
||||
|
||||
let device = match manager {
|
||||
PrimarySelectionDeviceManager::Zwp(zwp_manager) => {
|
||||
let device = zwp_manager.get_device(seat);
|
||||
|
||||
device.quick_assign(move |_, event, _| {
|
||||
let mut inner = inner2.lock().unwrap();
|
||||
|
||||
use zwp_primary_selection_device_v1::Event;
|
||||
match event {
|
||||
Event::DataOffer { offer } => {
|
||||
inner.know_offers.push(PrimarySelectionOffer::from_zwp(offer))
|
||||
}
|
||||
Event::Selection { id } => {
|
||||
let id = id.map(PrimarySelectionOfferImpl::Zwp);
|
||||
inner.set_selection(id);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
|
||||
PrimarySelectionDeviceImpl::Zwp(device.detach())
|
||||
}
|
||||
PrimarySelectionDeviceManager::Gtk(gtk_manager) => {
|
||||
let device = gtk_manager.get_device(seat);
|
||||
|
||||
device.quick_assign(move |_, event, _| {
|
||||
let mut inner = inner2.lock().unwrap();
|
||||
|
||||
use gtk_primary_selection_device::Event;
|
||||
match event {
|
||||
Event::DataOffer { offer } => {
|
||||
inner.know_offers.push(PrimarySelectionOffer::from_gtk(offer))
|
||||
}
|
||||
Event::Selection { id } => {
|
||||
let id = id.map(PrimarySelectionOfferImpl::Gtk);
|
||||
inner.set_selection(id);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
PrimarySelectionDeviceImpl::Gtk(device.detach())
|
||||
}
|
||||
};
|
||||
|
||||
Self { device, inner }
|
||||
}
|
||||
|
||||
/// Provide a primary selection source as the new content for the primary selection.
|
||||
///
|
||||
/// Correspond to traditional copy/paste behavior. Setting the source to `None` will clear
|
||||
/// the selection.
|
||||
pub fn set_selection(&self, source: &Option<PrimarySelectionSource>, serial: u32) {
|
||||
match self.device {
|
||||
PrimarySelectionDeviceImpl::Zwp(ref device) => {
|
||||
let source = source.as_ref().map(|source| match source.source {
|
||||
PrimarySelectionSourceImpl::Zwp(ref source) => source,
|
||||
// We can't reach `Gtk` source in `Zwp`.
|
||||
_ => unreachable!(),
|
||||
});
|
||||
device.set_selection(source, serial);
|
||||
}
|
||||
PrimarySelectionDeviceImpl::Gtk(ref device) => {
|
||||
let source = source.as_ref().map(|source| match source.source {
|
||||
PrimarySelectionSourceImpl::Gtk(ref source) => source,
|
||||
// We can't reach `Zwp` source in `Gtk`.
|
||||
_ => unreachable!(),
|
||||
});
|
||||
device.set_selection(source, serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the `PrimarySelectionOffer` currently associated with the primary selection buffer.
|
||||
pub fn with_selection<F: FnOnce(Option<&PrimarySelectionOffer>) -> T, T>(&self, f: F) -> T {
|
||||
let inner = self.inner.lock().unwrap();
|
||||
f(inner.selection.as_ref())
|
||||
}
|
||||
}
|
||||
351
third-party/vendor/smithay-client-toolkit/src/primary_selection/mod.rs
vendored
Normal file
351
third-party/vendor/smithay-client-toolkit/src/primary_selection/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
//! Helpers to handle primary selection related actions.
|
||||
//!
|
||||
//! If you're not using [`default_environment!`](../macro.default_environment.html) you should
|
||||
//! call `get_primary_selection_manager` to bind proper primary selection manager.
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use wayland_protocols::misc::gtk_primary_selection::client::gtk_primary_selection_device_manager::GtkPrimarySelectionDeviceManager;
|
||||
use wayland_protocols::unstable::primary_selection::v1::client::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1;
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_registry::WlRegistry, wl_seat::WlSeat},
|
||||
Attached, DispatchData,
|
||||
};
|
||||
|
||||
use crate::lazy_global::LazyGlobal;
|
||||
use crate::seat::{SeatHandling, SeatListener};
|
||||
use crate::{environment::GlobalHandler, MissingGlobal};
|
||||
|
||||
mod device;
|
||||
mod offer;
|
||||
mod source;
|
||||
|
||||
pub use self::device::PrimarySelectionDevice;
|
||||
pub use self::offer::PrimarySelectionOffer;
|
||||
pub use self::source::{PrimarySelectionSource, PrimarySelectionSourceEvent};
|
||||
|
||||
/// A handler for primary selection.
|
||||
///
|
||||
/// It provides automatic tracking of primary selection device for each available seat,
|
||||
/// allowing you to manipulate the primary selection clipboard.
|
||||
///
|
||||
/// It's automatically included in the [`default_environment!`](../macro.default_environment.html).
|
||||
#[derive(Debug)]
|
||||
pub struct PrimarySelectionHandler {
|
||||
inner: Rc<RefCell<PrimarySelectionDeviceManagerInner>>,
|
||||
_listener: SeatListener,
|
||||
}
|
||||
|
||||
/// Possible supported primary selection protocols
|
||||
#[derive(Debug)]
|
||||
pub enum PrimarySelectionDeviceManager {
|
||||
/// The current standard `primary_selection` protocol.
|
||||
Zwp(Attached<ZwpPrimarySelectionDeviceManagerV1>),
|
||||
/// The old `gtk_primary_selection` protocol, which is still used by GTK.
|
||||
Gtk(Attached<GtkPrimarySelectionDeviceManager>),
|
||||
}
|
||||
|
||||
impl PrimarySelectionHandler {
|
||||
/// Initialize a primary selection handler.
|
||||
///
|
||||
/// In requires the access to the seat handler in order to track the creation and removal of
|
||||
/// seats.
|
||||
pub fn init<S: SeatHandling>(seat_handler: &mut S) -> Self {
|
||||
let inner = Rc::new(RefCell::new(PrimarySelectionDeviceManagerInner {
|
||||
registry: None,
|
||||
zwp_mgr: LazyGlobal::Unknown,
|
||||
gtk_mgr: LazyGlobal::Unknown,
|
||||
state: PrimarySelectionDeviceManagerInitState::Pending { seats: Vec::new() },
|
||||
}));
|
||||
|
||||
// Listen for a new seat events to add new primary selection devices on the fly.
|
||||
let seat_inner = inner.clone();
|
||||
let 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, _listener: listener }
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface trait to forward the primary selection device handler capability.
|
||||
///
|
||||
/// You need to implement this trait for your environment struct, by delegating it
|
||||
/// to its `PrimarySelectionHandler` field in order to get the associated methods
|
||||
/// on your [`Environment`](../environment/struct.environment.html).
|
||||
pub trait PrimarySelectionHandling {
|
||||
/// Access the primary selection associated with a seat.
|
||||
///
|
||||
/// Returns an error if the seat is not found (for example if it has since been removed by
|
||||
/// the server) or if the `zwp_primary_selection_device_manager_v1` or
|
||||
/// `gtk_primary_selection_device_manager` globals are missing.
|
||||
fn with_primary_selection<F: FnOnce(&PrimarySelectionDevice)>(
|
||||
&self,
|
||||
seat: &WlSeat,
|
||||
f: F,
|
||||
) -> Result<(), MissingGlobal>;
|
||||
|
||||
/// Get the best available primary selection device manager protocol.
|
||||
///
|
||||
/// Returns `None` if no primary selection device manager was advertised.
|
||||
fn get_primary_selection_manager(&self) -> Option<PrimarySelectionDeviceManager>;
|
||||
}
|
||||
|
||||
impl<E: PrimarySelectionHandling> crate::environment::Environment<E> {
|
||||
/// Get the best available primary selection device manager protocol.
|
||||
///
|
||||
/// Returns `None` if no primary selection device manager was advertised.
|
||||
pub fn get_primary_selection_manager(&self) -> Option<PrimarySelectionDeviceManager> {
|
||||
self.with_inner(|manager| manager.get_primary_selection_manager())
|
||||
}
|
||||
|
||||
/// Access the primary selection associated with a seat.
|
||||
///
|
||||
/// Returns an error if the seat is not found (for example if it has since been removed by
|
||||
/// the server) of if the `zwp_primary_selection_device_manager_v1` or
|
||||
/// `gtk_primary_selection_device_manager` globals are missing.
|
||||
pub fn with_primary_selection<F: FnOnce(&PrimarySelectionDevice)>(
|
||||
&self,
|
||||
seat: &WlSeat,
|
||||
f: F,
|
||||
) -> Result<(), MissingGlobal> {
|
||||
self.with_inner(|inner| inner.with_primary_selection(seat, f))
|
||||
}
|
||||
|
||||
/// Create a new primary selection source.
|
||||
///
|
||||
/// This primary selection source is the basic object for offering primary selection clipboard
|
||||
/// to other clients.
|
||||
///
|
||||
/// Once this source is created, you will need to give it to a
|
||||
/// [`PrimarySelectionDevice`](../primary_selection/struct.PrimarySelectionDevice.html)
|
||||
/// to start interaction.
|
||||
pub fn new_primary_selection_source<F>(
|
||||
&self,
|
||||
mime_types: Vec<String>,
|
||||
callback: F,
|
||||
) -> PrimarySelectionSource
|
||||
where
|
||||
F: FnMut(PrimarySelectionSourceEvent, DispatchData) + 'static,
|
||||
{
|
||||
let manager = match self.get_primary_selection_manager() {
|
||||
Some(manager) => manager,
|
||||
None => panic!("[SCTK] primary selection was required"),
|
||||
};
|
||||
|
||||
PrimarySelectionSource::new(&manager, mime_types, callback)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimarySelectionHandling for PrimarySelectionHandler {
|
||||
/// Get the best available primary selection device manager protocol.
|
||||
///
|
||||
/// Returns `None` if no primary selection device manager was advertised.
|
||||
fn get_primary_selection_manager(&self) -> Option<PrimarySelectionDeviceManager> {
|
||||
GlobalHandler::<ZwpPrimarySelectionDeviceManagerV1>::get(self)
|
||||
.map(PrimarySelectionDeviceManager::Zwp)
|
||||
.or_else(|| {
|
||||
GlobalHandler::<GtkPrimarySelectionDeviceManager>::get(self)
|
||||
.map(PrimarySelectionDeviceManager::Gtk)
|
||||
})
|
||||
}
|
||||
|
||||
/// Access the primary selection associated with a seat.
|
||||
///
|
||||
/// Returns an error if the seat is not found (for example if it has since been removed by
|
||||
/// the server) of if the `zwp_primary_selection_device_manager_v1` or
|
||||
/// `gtk_primary_selection_device_manager` globals are missing.
|
||||
fn with_primary_selection<F: FnOnce(&PrimarySelectionDevice)>(
|
||||
&self,
|
||||
seat: &WlSeat,
|
||||
f: F,
|
||||
) -> Result<(), MissingGlobal> {
|
||||
self.inner.borrow().with_primary_selection(seat, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialization phase of `PrimarySelectionDeviceManagerInner`.
|
||||
#[derive(Debug)]
|
||||
enum PrimarySelectionDeviceManagerInitState {
|
||||
Ready { manager: PrimarySelectionDeviceManager, devices: Vec<(WlSeat, PrimarySelectionDevice)> },
|
||||
Pending { seats: Vec<WlSeat> },
|
||||
}
|
||||
|
||||
/// Inner mutable state for `PrimarySelectionHandler`.
|
||||
#[derive(Debug)]
|
||||
struct PrimarySelectionDeviceManagerInner {
|
||||
registry: Option<Attached<WlRegistry>>,
|
||||
zwp_mgr: LazyGlobal<ZwpPrimarySelectionDeviceManagerV1>,
|
||||
gtk_mgr: LazyGlobal<GtkPrimarySelectionDeviceManager>,
|
||||
pub state: PrimarySelectionDeviceManagerInitState,
|
||||
}
|
||||
|
||||
impl PrimarySelectionDeviceManagerInner {
|
||||
/// Initialize `PrimarySelectionDeviceManager` and setup `PrimarySelectionDevice` for a
|
||||
/// registered seats, if we have pending `PrimarySelectionDeviceManager`.
|
||||
fn init_selection_manager(&mut self, manager: PrimarySelectionDeviceManager) {
|
||||
let seats =
|
||||
if let PrimarySelectionDeviceManagerInitState::Pending { seats } = &mut self.state {
|
||||
std::mem::take(seats)
|
||||
} else {
|
||||
log::warn!("Ignoring second primary selection manager.");
|
||||
return;
|
||||
};
|
||||
|
||||
let mut devices = Vec::new();
|
||||
|
||||
// Create primary selection devices for each seat.
|
||||
for seat in seats {
|
||||
let device = PrimarySelectionDevice::init_for_seat(&manager, &seat);
|
||||
devices.push((seat.clone(), device));
|
||||
}
|
||||
|
||||
// Mark the state as `Ready`, so we can use our primary selection manager.
|
||||
self.state = PrimarySelectionDeviceManagerInitState::Ready { devices, manager }
|
||||
}
|
||||
|
||||
/// Handle addition of a new seat.
|
||||
fn new_seat(&mut self, seat: &WlSeat) {
|
||||
match &mut self.state {
|
||||
PrimarySelectionDeviceManagerInitState::Ready { devices, manager } => {
|
||||
if devices.iter().any(|(s, _)| s == seat) {
|
||||
// The seat already exists, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize primary selection device for a new seat.
|
||||
let device = PrimarySelectionDevice::init_for_seat(manager, seat);
|
||||
|
||||
devices.push((seat.clone(), device));
|
||||
}
|
||||
PrimarySelectionDeviceManagerInitState::Pending { seats } => {
|
||||
seats.push(seat.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle removal of a seat.
|
||||
fn remove_seat(&mut self, seat: &WlSeat) {
|
||||
match &mut self.state {
|
||||
PrimarySelectionDeviceManagerInitState::Ready { devices, .. } => {
|
||||
devices.retain(|(s, _)| s != seat)
|
||||
}
|
||||
PrimarySelectionDeviceManagerInitState::Pending { seats } => {
|
||||
seats.retain(|s| s != seat)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the primary selection associated with a seat.
|
||||
///
|
||||
/// Returns an error if the seat is not found (for example if it has since been removed by
|
||||
/// the server) of if the `zwp_primary_selection_device_manager_v1` or
|
||||
/// `gtk_primary_selection_device_manager` globals are missing.
|
||||
fn with_primary_selection<F: FnOnce(&PrimarySelectionDevice)>(
|
||||
&self,
|
||||
seat: &WlSeat,
|
||||
f: F,
|
||||
) -> Result<(), MissingGlobal> {
|
||||
match &self.state {
|
||||
PrimarySelectionDeviceManagerInitState::Pending { .. } => Err(MissingGlobal),
|
||||
PrimarySelectionDeviceManagerInitState::Ready { devices, .. } => {
|
||||
for (s, device) in devices {
|
||||
if s == seat {
|
||||
f(device);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
Err(MissingGlobal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalHandler<ZwpPrimarySelectionDeviceManagerV1> for PrimarySelectionHandler {
|
||||
fn created(&mut self, registry: Attached<WlRegistry>, id: u32, version: u32, _: DispatchData) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if inner.registry.is_none() {
|
||||
inner.registry = Some(registry);
|
||||
}
|
||||
|
||||
if let LazyGlobal::Unknown = inner.zwp_mgr {
|
||||
// Mark global as seen.
|
||||
inner.zwp_mgr = LazyGlobal::Seen { id, version };
|
||||
} else {
|
||||
log::warn!(
|
||||
"Compositor advertised zwp_primary_selection_device_manager_v1 multiple\
|
||||
times, ignoring."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self) -> Option<Attached<ZwpPrimarySelectionDeviceManagerV1>> {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
match inner.zwp_mgr {
|
||||
LazyGlobal::Bound(ref mgr) => Some(mgr.clone()),
|
||||
LazyGlobal::Unknown => None,
|
||||
LazyGlobal::Seen { id, version } => {
|
||||
// Registry cannot be `None` if we've seen the global.
|
||||
let registry = inner.registry.as_ref().unwrap();
|
||||
|
||||
// Bind zwp primary selection.
|
||||
let version = std::cmp::min(1, version);
|
||||
let mgr = registry.bind::<ZwpPrimarySelectionDeviceManagerV1>(version, id);
|
||||
let manager = PrimarySelectionDeviceManager::Zwp((*mgr).clone());
|
||||
|
||||
// Init zwp selection manager.
|
||||
inner.init_selection_manager(manager);
|
||||
|
||||
inner.zwp_mgr = LazyGlobal::Bound((*mgr).clone());
|
||||
Some((*mgr).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalHandler<GtkPrimarySelectionDeviceManager> for PrimarySelectionHandler {
|
||||
fn created(&mut self, registry: Attached<WlRegistry>, id: u32, version: u32, _: DispatchData) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if inner.registry.is_none() {
|
||||
inner.registry = Some(registry);
|
||||
}
|
||||
if let LazyGlobal::Unknown = inner.gtk_mgr {
|
||||
// Mark global as seen.
|
||||
inner.gtk_mgr = LazyGlobal::Seen { id, version };
|
||||
} else {
|
||||
log::warn!(
|
||||
"Compositor advertised gtk_primary_selection_device_manager multiple times,\
|
||||
ignoring."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self) -> Option<Attached<GtkPrimarySelectionDeviceManager>> {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
match inner.gtk_mgr {
|
||||
LazyGlobal::Bound(ref mgr) => Some(mgr.clone()),
|
||||
LazyGlobal::Unknown => None,
|
||||
LazyGlobal::Seen { id, version } => {
|
||||
// Registry cannot be `None` if we've seen the global.
|
||||
let registry = inner.registry.as_ref().unwrap();
|
||||
|
||||
// Bind gtk primary selection.
|
||||
let version = std::cmp::min(1, version);
|
||||
let mgr = registry.bind::<GtkPrimarySelectionDeviceManager>(version, id);
|
||||
let manager = PrimarySelectionDeviceManager::Gtk((*mgr).clone());
|
||||
|
||||
// Init gtk selection manager.
|
||||
inner.init_selection_manager(manager);
|
||||
|
||||
inner.gtk_mgr = LazyGlobal::Bound((*mgr).clone());
|
||||
Some((*mgr).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
125
third-party/vendor/smithay-client-toolkit/src/primary_selection/offer.rs
vendored
Normal file
125
third-party/vendor/smithay-client-toolkit/src/primary_selection/offer.rs
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
use std::os::unix::io::FromRawFd;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use wayland_client::Main;
|
||||
|
||||
use wayland_protocols::{
|
||||
misc::gtk_primary_selection::client::gtk_primary_selection_offer::{
|
||||
self, GtkPrimarySelectionOffer,
|
||||
},
|
||||
unstable::primary_selection::v1::client::zwp_primary_selection_offer_v1::{
|
||||
self, ZwpPrimarySelectionOfferV1,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::data_device::ReadPipe;
|
||||
|
||||
/// A primary selection offer for receiving data through copy/paste.
|
||||
#[derive(Debug)]
|
||||
pub struct PrimarySelectionOffer {
|
||||
pub(crate) offer: PrimarySelectionOfferImpl,
|
||||
inner: Arc<Mutex<PrimarySelectionOfferInner>>,
|
||||
}
|
||||
|
||||
impl PrimarySelectionOffer {
|
||||
/// Access the list of mime types proposed by this offer.
|
||||
pub fn with_mime_types<F, T>(&self, f: F) -> T
|
||||
where
|
||||
F: FnOnce(&[String]) -> T,
|
||||
{
|
||||
let inner = self.inner.lock().unwrap();
|
||||
f(&inner.mime_types)
|
||||
}
|
||||
|
||||
/// Request to receive the data of a given mime type.
|
||||
///
|
||||
/// Note that you should **not** read the contents right away in a blocking way,
|
||||
/// as you may deadlock your application.
|
||||
pub fn receive(&self, mime_type: String) -> Result<ReadPipe, std::io::Error> {
|
||||
use nix::fcntl::OFlag;
|
||||
use nix::unistd::{close, pipe2};
|
||||
// create a pipe
|
||||
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?;
|
||||
|
||||
match &self.offer {
|
||||
PrimarySelectionOfferImpl::Zwp(offer) => {
|
||||
offer.receive(mime_type, writefd);
|
||||
}
|
||||
PrimarySelectionOfferImpl::Gtk(offer) => {
|
||||
offer.receive(mime_type, writefd);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = close(writefd) {
|
||||
log::warn!("Failed to close write pipe: {}", err);
|
||||
}
|
||||
|
||||
Ok(unsafe { FromRawFd::from_raw_fd(readfd) })
|
||||
}
|
||||
|
||||
/// Initialize `PrimarySelectionOffer` from the `Zwp` offer.
|
||||
pub(crate) fn from_zwp(offer: Main<ZwpPrimarySelectionOfferV1>) -> Self {
|
||||
let inner = Arc::new(Mutex::new(PrimarySelectionOfferInner::new()));
|
||||
let inner2 = inner.clone();
|
||||
|
||||
offer.quick_assign(move |_, event, _| {
|
||||
use zwp_primary_selection_offer_v1::Event;
|
||||
let mut inner = inner2.lock().unwrap();
|
||||
match event {
|
||||
Event::Offer { mime_type } => {
|
||||
inner.mime_types.push(mime_type);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
|
||||
Self { offer: PrimarySelectionOfferImpl::Zwp(offer.detach()), inner }
|
||||
}
|
||||
|
||||
/// Initialize `PrimarySelectionOffer` from the `Gtk` offer.
|
||||
pub(crate) fn from_gtk(offer: Main<GtkPrimarySelectionOffer>) -> Self {
|
||||
let inner = Arc::new(Mutex::new(PrimarySelectionOfferInner::new()));
|
||||
let inner2 = inner.clone();
|
||||
|
||||
offer.quick_assign(move |_, event, _| {
|
||||
use gtk_primary_selection_offer::Event;
|
||||
let mut inner = inner2.lock().unwrap();
|
||||
match event {
|
||||
Event::Offer { mime_type } => {
|
||||
inner.mime_types.push(mime_type);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
|
||||
Self { offer: PrimarySelectionOfferImpl::Gtk(offer.detach()), inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PrimarySelectionOffer {
|
||||
fn drop(&mut self) {
|
||||
match &self.offer {
|
||||
PrimarySelectionOfferImpl::Zwp(offer) => offer.destroy(),
|
||||
PrimarySelectionOfferImpl::Gtk(offer) => offer.destroy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inner state for `PrimarySelectionOffer`.
|
||||
#[derive(Debug, Default)]
|
||||
struct PrimarySelectionOfferInner {
|
||||
mime_types: Vec<String>,
|
||||
}
|
||||
|
||||
impl PrimarySelectionOfferInner {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible supported primary selection offers.
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub(crate) enum PrimarySelectionOfferImpl {
|
||||
Zwp(ZwpPrimarySelectionOfferV1),
|
||||
Gtk(GtkPrimarySelectionOffer),
|
||||
}
|
||||
138
third-party/vendor/smithay-client-toolkit/src/primary_selection/source.rs
vendored
Normal file
138
third-party/vendor/smithay-client-toolkit/src/primary_selection/source.rs
vendored
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
use wayland_protocols::unstable::primary_selection::v1::client::zwp_primary_selection_source_v1::{
|
||||
self, ZwpPrimarySelectionSourceV1,
|
||||
};
|
||||
|
||||
use wayland_protocols::misc::gtk_primary_selection::client::gtk_primary_selection_source::{
|
||||
self, GtkPrimarySelectionSource,
|
||||
};
|
||||
|
||||
use crate::data_device::WritePipe;
|
||||
|
||||
use std::os::unix::io::FromRawFd;
|
||||
|
||||
use wayland_client::DispatchData;
|
||||
|
||||
use super::PrimarySelectionDeviceManager;
|
||||
|
||||
/// A primary selection source for sending data through copy/paste.
|
||||
#[derive(Debug)]
|
||||
pub struct PrimarySelectionSource {
|
||||
pub(crate) source: PrimarySelectionSourceImpl,
|
||||
}
|
||||
|
||||
/// Possible events a primary selection source needs to react to.
|
||||
#[derive(Debug)]
|
||||
pub enum PrimarySelectionSourceEvent {
|
||||
/// Write the offered data for selected mime type.
|
||||
Send {
|
||||
/// Requested mime type.
|
||||
mime_type: String,
|
||||
/// Pipe to write into.
|
||||
pipe: WritePipe,
|
||||
},
|
||||
|
||||
/// The action using the primary selection source was cancelled.
|
||||
///
|
||||
/// Once this event is received, the `PrimarySelectionSource` can not be used any more,
|
||||
/// and you should drop it for cleanup.
|
||||
///
|
||||
/// Happens if the user replaces primary selection buffer.
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
impl PrimarySelectionSource {
|
||||
/// Create a new primary selection source.
|
||||
///
|
||||
/// You'll then need to provide a primary selection device to send via selection.
|
||||
pub fn new<F, S, It>(
|
||||
manager: &PrimarySelectionDeviceManager,
|
||||
mime_types: It,
|
||||
mut callback: F,
|
||||
) -> Self
|
||||
where
|
||||
F: FnMut(PrimarySelectionSourceEvent, DispatchData) + 'static,
|
||||
S: Into<String>,
|
||||
It: IntoIterator<Item = S>,
|
||||
{
|
||||
match manager {
|
||||
PrimarySelectionDeviceManager::Zwp(ref manager) => {
|
||||
let source = manager.create_source();
|
||||
source.quick_assign(move |source, event, dispatch_data| {
|
||||
zwp_primary_source_imp(&source, event, dispatch_data, &mut callback);
|
||||
});
|
||||
|
||||
for mime in mime_types {
|
||||
source.offer(mime.into());
|
||||
}
|
||||
|
||||
Self { source: PrimarySelectionSourceImpl::Zwp(source.detach()) }
|
||||
}
|
||||
PrimarySelectionDeviceManager::Gtk(ref manager) => {
|
||||
let source = manager.create_source();
|
||||
source.quick_assign(move |source, event, dispatch_data| {
|
||||
gtk_primary_source_imp(&source, event, dispatch_data, &mut callback);
|
||||
});
|
||||
|
||||
for mime in mime_types {
|
||||
source.offer(mime.into());
|
||||
}
|
||||
|
||||
Self { source: PrimarySelectionSourceImpl::Gtk(source.detach()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible supported primary selection sources.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum PrimarySelectionSourceImpl {
|
||||
Zwp(ZwpPrimarySelectionSourceV1),
|
||||
Gtk(GtkPrimarySelectionSource),
|
||||
}
|
||||
|
||||
fn gtk_primary_source_imp<Impl>(
|
||||
source: &GtkPrimarySelectionSource,
|
||||
event: gtk_primary_selection_source::Event,
|
||||
dispatch_data: DispatchData,
|
||||
implem: &mut Impl,
|
||||
) where
|
||||
Impl: FnMut(PrimarySelectionSourceEvent, DispatchData),
|
||||
{
|
||||
use gtk_primary_selection_source::Event;
|
||||
let event = match event {
|
||||
Event::Send { mime_type, fd } => PrimarySelectionSourceEvent::Send {
|
||||
mime_type,
|
||||
pipe: unsafe { FromRawFd::from_raw_fd(fd) },
|
||||
},
|
||||
Event::Cancelled => {
|
||||
source.destroy();
|
||||
PrimarySelectionSourceEvent::Cancelled
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
implem(event, dispatch_data);
|
||||
}
|
||||
|
||||
fn zwp_primary_source_imp<Impl>(
|
||||
source: &ZwpPrimarySelectionSourceV1,
|
||||
event: zwp_primary_selection_source_v1::Event,
|
||||
dispatch_data: DispatchData,
|
||||
implem: &mut Impl,
|
||||
) where
|
||||
Impl: FnMut(PrimarySelectionSourceEvent, DispatchData),
|
||||
{
|
||||
use zwp_primary_selection_source_v1::Event;
|
||||
let event = match event {
|
||||
Event::Send { mime_type, fd } => PrimarySelectionSourceEvent::Send {
|
||||
mime_type,
|
||||
pipe: unsafe { FromRawFd::from_raw_fd(fd) },
|
||||
},
|
||||
Event::Cancelled => {
|
||||
source.destroy();
|
||||
PrimarySelectionSourceEvent::Cancelled
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
implem(event, dispatch_data);
|
||||
}
|
||||
266
third-party/vendor/smithay-client-toolkit/src/seat/keyboard/ffi.rs
vendored
Normal file
266
third-party/vendor/smithay-client-toolkit/src/seat/keyboard/ffi.rs
vendored
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
#![allow(dead_code, non_camel_case_types, clippy::identity_op)]
|
||||
|
||||
use std::os::raw::{c_char, c_int, c_void, c_uint};
|
||||
|
||||
pub const XKB_MOD_NAME_SHIFT : &[u8] = b"Shift\0";
|
||||
pub const XKB_MOD_NAME_CAPS : &[u8] = b"Lock\0";
|
||||
pub const XKB_MOD_NAME_CTRL : &[u8] = b"Control\0";
|
||||
pub const XKB_MOD_NAME_ALT : &[u8] = b"Mod1\0";
|
||||
pub const XKB_MOD_NAME_NUM : &[u8] = b"Mod2\0";
|
||||
pub const XKB_MOD_NAME_LOGO : &[u8] = b"Mod4\0";
|
||||
|
||||
pub const XKB_LED_NAME_CAPS : &[u8] = b"Caps Lock\0";
|
||||
pub const XKB_LED_NAME_NUM : &[u8] = b"Num Lock\0";
|
||||
pub const XKB_LED_NAME_SCROLL : &[u8] = b"Scroll Lock\0";
|
||||
|
||||
pub struct xkb_context;
|
||||
pub struct xkb_keymap;
|
||||
pub struct xkb_state;
|
||||
pub struct xkb_compose_table;
|
||||
pub struct xkb_compose_state;
|
||||
|
||||
pub type xkb_keycode_t = u32;
|
||||
pub type xkb_keysym_t = u32;
|
||||
pub type xkb_layout_index_t = u32;
|
||||
pub type xkb_layout_mask_t = u32;
|
||||
pub type xkb_level_index_t = u32;
|
||||
pub type xkb_mod_index_t = u32;
|
||||
pub type xkb_mod_mask_t = u32;
|
||||
pub type xkb_led_index_t = u32;
|
||||
pub type xkb_led_mask_t = u32;
|
||||
|
||||
pub const XKB_KEYCODE_INVALID :u32 = 0xffff_ffff;
|
||||
pub const XKB_LAYOUT_INVALID :u32 = 0xffff_ffff;
|
||||
pub const XKB_LEVEL_INVALID :u32 = 0xffff_ffff;
|
||||
pub const XKB_MOD_INVALID :u32 = 0xffff_ffff;
|
||||
pub const XKB_LED_INVALID :u32 = 0xffff_ffff;
|
||||
pub const XKB_KEYCODE_MAX :u32 = 0xffff_fffe;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct xkb_rule_names {
|
||||
pub rules: *const c_char,
|
||||
pub model: *const c_char ,
|
||||
pub layout: *const c_char,
|
||||
pub variant: *const c_char,
|
||||
pub options: *const c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_keysym_flags {
|
||||
/** Do not apply any flags. */
|
||||
XKB_KEYSYM_NO_FLAGS = 0,
|
||||
/** Find keysym by case-insensitive search. */
|
||||
XKB_KEYSYM_CASE_INSENSITIVE = 1 << 0
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_context_flags {
|
||||
/** Do not apply any context flags. */
|
||||
XKB_CONTEXT_NO_FLAGS = 0,
|
||||
/** Create this context with an empty include path. */
|
||||
XKB_CONTEXT_NO_DEFAULT_INCLUDES = 1 << 0,
|
||||
/**
|
||||
* Don't take RMLVO names from the environment.
|
||||
* @since 0.3.0
|
||||
*/
|
||||
XKB_CONTEXT_NO_ENVIRONMENT_NAMES = 1 << 1
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_log_level {
|
||||
/** Log critical internal errors only. */
|
||||
XKB_LOG_LEVEL_CRITICAL = 10,
|
||||
/** Log all errors. */
|
||||
XKB_LOG_LEVEL_ERROR = 20,
|
||||
/** Log warnings and errors. */
|
||||
XKB_LOG_LEVEL_WARNING = 30,
|
||||
/** Log information, warnings, and errors. */
|
||||
XKB_LOG_LEVEL_INFO = 40,
|
||||
/** Log everything. */
|
||||
XKB_LOG_LEVEL_DEBUG = 50
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_keymap_compile_flags {
|
||||
/** Do not apply any flags. */
|
||||
XKB_KEYMAP_COMPILE_NO_FLAGS = 0,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_keymap_format {
|
||||
/** Cannot be used for creation */
|
||||
XKB_KEYMAP_USE_ORIGINAL_FORMAT = 0,
|
||||
/** The current/classic XKB text format, as generated by xkbcomp -xkb. */
|
||||
XKB_KEYMAP_FORMAT_TEXT_V1 = 1,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_key_direction {
|
||||
/** The key was released. */
|
||||
XKB_KEY_UP,
|
||||
/** The key was pressed. */
|
||||
XKB_KEY_DOWN
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_compose_compile_flags {
|
||||
XKB_COMPOSE_COMPILE_NO_FLAGS = 0
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_compose_format {
|
||||
XKB_COMPOSE_FORMAT_TEXT_V1 = 1
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_compose_state_flags {
|
||||
XKB_COMPOSE_STATE_NO_FLAGS = 0
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_compose_status {
|
||||
XKB_COMPOSE_NOTHING,
|
||||
XKB_COMPOSE_COMPOSING,
|
||||
XKB_COMPOSE_COMPOSED,
|
||||
XKB_COMPOSE_CANCELLED
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum xkb_compose_feed_result {
|
||||
XKB_COMPOSE_FEED_IGNORED,
|
||||
XKB_COMPOSE_FEED_ACCEPTED
|
||||
}
|
||||
|
||||
bitflags::bitflags!(
|
||||
pub struct xkb_state_component: u32 {
|
||||
/** Depressed modifiers, i.e. a key is physically holding them. */
|
||||
const XKB_STATE_MODS_DEPRESSED = (1 << 0);
|
||||
/** Latched modifiers, i.e. will be unset after the next non-modifier
|
||||
* key press. */
|
||||
const XKB_STATE_MODS_LATCHED = (1 << 1);
|
||||
/** Locked modifiers, i.e. will be unset after the key provoking the
|
||||
* lock has been pressed again. */
|
||||
const XKB_STATE_MODS_LOCKED = (1 << 2);
|
||||
/** Effective modifiers, i.e. currently active and affect key
|
||||
* processing (derived from the other state components).
|
||||
* Use this unless you explictly care how the state came about. */
|
||||
const XKB_STATE_MODS_EFFECTIVE = (1 << 3);
|
||||
/** Depressed layout, i.e. a key is physically holding it. */
|
||||
const XKB_STATE_LAYOUT_DEPRESSED = (1 << 4);
|
||||
/** Latched layout, i.e. will be unset after the next non-modifier
|
||||
* key press. */
|
||||
const XKB_STATE_LAYOUT_LATCHED = (1 << 5);
|
||||
/** Locked layout, i.e. will be unset after the key provoking the lock
|
||||
* has been pressed again. */
|
||||
const XKB_STATE_LAYOUT_LOCKED = (1 << 6);
|
||||
/** Effective layout, i.e. currently active and affects key processing
|
||||
* (derived from the other state components).
|
||||
* Use this unless you explictly care how the state came about. */
|
||||
const XKB_STATE_LAYOUT_EFFECTIVE = (1 << 7);
|
||||
/** LEDs (derived from the other state components). */
|
||||
const XKB_STATE_LEDS = (1 << 8);
|
||||
}
|
||||
);
|
||||
|
||||
external_library!(XkbCommon, "xkbcommon",
|
||||
functions:
|
||||
fn xkb_keysym_get_name(xkb_keysym_t, *mut c_char, usize) -> c_int,
|
||||
fn xkb_keysym_from_name(*const c_char, xkb_keysym_flags) -> xkb_keysym_t,
|
||||
fn xkb_keysym_to_utf8(xkb_keysym_t, *mut c_char, usize) -> c_int,
|
||||
fn xkb_keysym_to_utf32(xkb_keysym_t) -> u32,
|
||||
fn xkb_context_new(xkb_context_flags) -> *mut xkb_context,
|
||||
fn xkb_context_ref(*mut xkb_context) -> *mut xkb_context,
|
||||
fn xkb_context_unref(*mut xkb_context) -> (),
|
||||
fn xkb_context_set_user_data(*mut xkb_context, *mut c_void) -> (),
|
||||
fn xkb_context_get_user_data(*mut xkb_context) -> *mut c_void,
|
||||
fn xkb_context_include_path_append(*mut xkb_context, *const c_char) -> c_int,
|
||||
fn xkb_context_include_path_append_default(*mut xkb_context) -> c_int,
|
||||
fn xkb_context_include_path_reset_defaults(*mut xkb_context) -> c_int,
|
||||
fn xkb_context_include_path_clear(*mut xkb_context) -> (),
|
||||
fn xkb_context_num_include_paths(*mut xkb_context) -> c_uint,
|
||||
fn xkb_context_include_path_get(*mut xkb_context, c_uint) -> *const c_char,
|
||||
fn xkb_context_set_log_level(*mut xkb_context, xkb_log_level) -> (),
|
||||
fn xkb_context_get_log_level(*mut xkb_context) -> xkb_log_level,
|
||||
fn xkb_context_set_log_verbosity(*mut xkb_context, c_int) -> (),
|
||||
fn xkb_context_get_log_verbosity(*mut xkb_context) -> c_int,
|
||||
fn xkb_keymap_new_from_names(*mut xkb_context,
|
||||
*const xkb_rule_names,
|
||||
xkb_keymap_compile_flags
|
||||
) -> *mut xkb_keymap,
|
||||
fn xkb_keymap_new_from_string(*mut xkb_context,
|
||||
*const c_char,
|
||||
xkb_keymap_format,
|
||||
xkb_keymap_compile_flags
|
||||
) -> *mut xkb_keymap,
|
||||
fn xkb_keymap_new_from_buffer(*mut xkb_context,
|
||||
*const c_char,
|
||||
usize,
|
||||
xkb_keymap_format,
|
||||
xkb_keymap_compile_flags
|
||||
) -> *mut xkb_keymap,
|
||||
fn xkb_keymap_ref(*mut xkb_keymap) -> *mut xkb_keymap,
|
||||
fn xkb_keymap_unref(*mut xkb_keymap) -> (),
|
||||
fn xkb_keymap_get_as_string(*mut xkb_keymap, xkb_keymap_format) -> *const c_char,
|
||||
fn xkb_keymap_key_repeats(*mut xkb_keymap, xkb_keycode_t) -> c_int,
|
||||
|
||||
fn xkb_state_new(*mut xkb_keymap) -> *mut xkb_state,
|
||||
fn xkb_state_ref(*mut xkb_state) -> *mut xkb_state,
|
||||
fn xkb_state_unref(*mut xkb_state) -> (),
|
||||
fn xkb_state_update_mask(*mut xkb_state,
|
||||
xkb_mod_mask_t,
|
||||
xkb_mod_mask_t,
|
||||
xkb_mod_mask_t,
|
||||
xkb_layout_index_t,
|
||||
xkb_layout_index_t,
|
||||
xkb_layout_index_t
|
||||
) -> xkb_state_component,
|
||||
fn xkb_state_update_key(*mut xkb_state,
|
||||
xkb_keycode_t,
|
||||
xkb_key_direction
|
||||
) -> xkb_state_component,
|
||||
fn xkb_state_key_get_syms(*mut xkb_state,
|
||||
xkb_keycode_t,
|
||||
*const *mut xkb_keysym_t
|
||||
) -> c_int,
|
||||
fn xkb_state_key_get_utf8(*mut xkb_state,
|
||||
xkb_keycode_t,
|
||||
*mut c_char,
|
||||
usize
|
||||
) -> c_int,
|
||||
fn xkb_state_key_get_utf32(*mut xkb_state, xkb_keycode_t) -> u32,
|
||||
fn xkb_state_key_get_one_sym(*mut xkb_state, xkb_keycode_t) -> xkb_keysym_t,
|
||||
fn xkb_state_mod_name_is_active(*mut xkb_state, *const c_char, xkb_state_component) -> c_int,
|
||||
fn xkb_compose_table_new_from_locale(*mut xkb_context, *const c_char, xkb_compose_compile_flags) -> *mut xkb_compose_table,
|
||||
fn xkb_compose_table_unref(*mut xkb_compose_table) -> (),
|
||||
fn xkb_compose_state_new(*mut xkb_compose_table, xkb_compose_state_flags) -> *mut xkb_compose_state,
|
||||
fn xkb_compose_state_unref(*mut xkb_compose_state) -> (),
|
||||
fn xkb_compose_state_feed(*mut xkb_compose_state, xkb_keysym_t) -> xkb_compose_feed_result,
|
||||
fn xkb_compose_state_reset(*mut xkb_compose_state) -> (),
|
||||
fn xkb_compose_state_get_status(*mut xkb_compose_state) -> xkb_compose_status,
|
||||
fn xkb_compose_state_get_utf8(*mut xkb_compose_state, *mut c_char, usize) -> c_int,
|
||||
fn xkb_compose_state_get_one_sym(*mut xkb_compose_state) -> xkb_keysym_t,
|
||||
);
|
||||
|
||||
#[cfg(feature = "dlopen")]
|
||||
lazy_static::lazy_static!(
|
||||
pub static ref XKBCOMMON_OPTION: Option<XkbCommon> = unsafe {
|
||||
XkbCommon::open("libxkbcommon.so.0")
|
||||
.or_else(|_| XkbCommon::open("libxkbcommon.so"))
|
||||
.ok()
|
||||
};
|
||||
pub static ref XKBCOMMON_HANDLE: &'static XkbCommon = {
|
||||
XKBCOMMON_OPTION.as_ref().expect("Library libxkbcommon.so could not be loaded.")
|
||||
};
|
||||
);
|
||||
3005
third-party/vendor/smithay-client-toolkit/src/seat/keyboard/keysyms.rs
vendored
Normal file
3005
third-party/vendor/smithay-client-toolkit/src/seat/keyboard/keysyms.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
647
third-party/vendor/smithay-client-toolkit/src/seat/keyboard/mod.rs
vendored
Normal file
647
third-party/vendor/smithay-client-toolkit/src/seat/keyboard/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,647 @@
|
|||
//! Utilities for keymap interpretation of keyboard input
|
||||
//!
|
||||
//! This module provides an implementation for `wl_keyboard`
|
||||
//! objects using `libxkbcommon` to interpret the keyboard input
|
||||
//! given the user keymap.
|
||||
//!
|
||||
//! The entry point of this module is the [`map_keyboard`](fn.map_keyboard.html)
|
||||
//! function which, given a `wl_seat` and a callback, setup keymap interpretation
|
||||
//! and key repetition for the `wl_keyboard` of this seat.
|
||||
//!
|
||||
//! Key repetition relies on an event source, that needs to be inserted in your
|
||||
//! calloop event loop. Not doing so will prevent key repetition to work
|
||||
//! (but the rest of the functionnality will not be affected).
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
use calloop::{timer::Timer, RegistrationToken};
|
||||
#[cfg(feature = "calloop")]
|
||||
use std::num::NonZeroU32;
|
||||
#[cfg(feature = "calloop")]
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
convert::TryInto,
|
||||
fs::File,
|
||||
os::unix::io::{FromRawFd, RawFd},
|
||||
rc::Rc,
|
||||
time::Instant,
|
||||
};
|
||||
pub use wayland_client::protocol::wl_keyboard::KeyState;
|
||||
use wayland_client::{
|
||||
protocol::{wl_keyboard, wl_seat, wl_surface},
|
||||
Attached,
|
||||
};
|
||||
|
||||
#[rustfmt::skip]
|
||||
mod ffi;
|
||||
mod state;
|
||||
#[rustfmt::skip]
|
||||
pub mod keysyms;
|
||||
|
||||
use self::state::KbState;
|
||||
pub use self::state::{ModifiersState, RMLVO};
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
const MICROS_IN_SECOND: u32 = 1000000;
|
||||
|
||||
/// Possible kinds of key repetition
|
||||
#[derive(Debug)]
|
||||
pub enum RepeatKind {
|
||||
/// keys will be repeated at a set rate and delay
|
||||
Fixed {
|
||||
/// The number of repetitions per second that should occur.
|
||||
rate: u32,
|
||||
/// delay (in milliseconds) between a key press and the start of repetition
|
||||
delay: u32,
|
||||
},
|
||||
/// keys will be repeated at a rate and delay set by the wayland server
|
||||
System,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// An error that occurred while trying to initialize a mapped keyboard
|
||||
pub enum Error {
|
||||
/// libxkbcommon is not available
|
||||
XKBNotFound,
|
||||
/// Provided RMLVO specified a keymap that would not be loaded
|
||||
BadNames,
|
||||
/// The provided seat does not have the keyboard capability
|
||||
NoKeyboard,
|
||||
/// Failed to init timers for repetition
|
||||
TimerError(std::io::Error),
|
||||
}
|
||||
|
||||
/// Events received from a mapped keyboard
|
||||
#[derive(Debug)]
|
||||
pub enum Event<'a> {
|
||||
/// The keyboard focus has entered a surface
|
||||
Enter {
|
||||
/// serial number of the event
|
||||
serial: u32,
|
||||
/// surface that was entered
|
||||
surface: wl_surface::WlSurface,
|
||||
/// raw values of the currently pressed keys
|
||||
rawkeys: &'a [u32],
|
||||
/// interpreted symbols of the currently pressed keys
|
||||
keysyms: &'a [u32],
|
||||
},
|
||||
/// The keyboard focus has left a surface
|
||||
Leave {
|
||||
/// serial number of the event
|
||||
serial: u32,
|
||||
/// surface that was left
|
||||
surface: wl_surface::WlSurface,
|
||||
},
|
||||
/// The key modifiers have changed state
|
||||
Modifiers {
|
||||
/// current state of the modifiers
|
||||
modifiers: ModifiersState,
|
||||
},
|
||||
/// A key event occurred
|
||||
Key {
|
||||
/// serial number of the event
|
||||
serial: u32,
|
||||
/// time at which the keypress occurred
|
||||
time: u32,
|
||||
/// raw value of the key
|
||||
rawkey: u32,
|
||||
/// interpreted symbol of the key
|
||||
keysym: u32,
|
||||
/// new state of the key
|
||||
state: KeyState,
|
||||
/// utf8 interpretation of the entered text
|
||||
///
|
||||
/// will always be `None` on key release events
|
||||
utf8: Option<String>,
|
||||
},
|
||||
/// A key repetition event
|
||||
Repeat {
|
||||
/// time at which the repetition occured
|
||||
time: u32,
|
||||
/// raw value of the key
|
||||
rawkey: u32,
|
||||
/// interpreted symbol of the key
|
||||
keysym: u32,
|
||||
/// utf8 interpretation of the entered text
|
||||
utf8: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Implement a keyboard for keymap translation with key repetition
|
||||
///
|
||||
/// This requires you to provide a callback to receive the events after they
|
||||
/// have been interpreted with the keymap.
|
||||
///
|
||||
/// The keymap will be loaded from the provided RMLVO rules, or from the compositor
|
||||
/// provided keymap if `None`.
|
||||
///
|
||||
/// Returns an error if xkbcommon could not be initialized, the RMLVO specification
|
||||
/// contained invalid values, or if the provided seat does not have keyboard capability.
|
||||
///
|
||||
/// **Note:** This adapter does not handle key repetition. See `map_keyboard_repeat` for that.
|
||||
pub fn map_keyboard<F>(
|
||||
seat: &Attached<wl_seat::WlSeat>,
|
||||
rmlvo: Option<RMLVO>,
|
||||
callback: F,
|
||||
) -> Result<wl_keyboard::WlKeyboard, Error>
|
||||
where
|
||||
F: FnMut(Event<'_>, wl_keyboard::WlKeyboard, wayland_client::DispatchData<'_>) + 'static,
|
||||
{
|
||||
let has_kbd = super::with_seat_data(seat, |data| data.has_keyboard).unwrap_or(false);
|
||||
let keyboard = if has_kbd {
|
||||
seat.get_keyboard()
|
||||
} else {
|
||||
return Err(Error::NoKeyboard);
|
||||
};
|
||||
|
||||
let state = Rc::new(RefCell::new(rmlvo.map(KbState::from_rmlvo).unwrap_or_else(KbState::new)?));
|
||||
|
||||
let callback = Rc::new(RefCell::new(callback)) as Rc<RefCell<_>>;
|
||||
|
||||
// prepare the handler
|
||||
let mut kbd_handler = KbdHandler {
|
||||
callback,
|
||||
state,
|
||||
#[cfg(feature = "calloop")]
|
||||
repeat: None,
|
||||
};
|
||||
|
||||
keyboard.quick_assign(move |keyboard, event, data| {
|
||||
kbd_handler.event(keyboard.detach(), event, data)
|
||||
});
|
||||
|
||||
Ok(keyboard.detach())
|
||||
}
|
||||
|
||||
/// Implement a keyboard for keymap translation with key repetition
|
||||
///
|
||||
/// This requires you to provide a callback to receive the events after they
|
||||
/// have been interpreted with the keymap.
|
||||
///
|
||||
/// The keymap will be loaded from the provided RMLVO rules, or from the compositor
|
||||
/// provided keymap if `None`.
|
||||
///
|
||||
/// Returns an error if xkbcommon could not be initialized, the RMLVO specification
|
||||
/// contained invalid values, or if the provided seat does not have keyboard capability.
|
||||
///
|
||||
/// **Note:** The keyboard repetition handling requires the `calloop` cargo feature.
|
||||
#[cfg(feature = "calloop")]
|
||||
pub fn map_keyboard_repeat<F, Data: 'static>(
|
||||
loop_handle: calloop::LoopHandle<'static, Data>,
|
||||
seat: &Attached<wl_seat::WlSeat>,
|
||||
rmlvo: Option<RMLVO>,
|
||||
repeatkind: RepeatKind,
|
||||
callback: F,
|
||||
) -> Result<wl_keyboard::WlKeyboard, Error>
|
||||
where
|
||||
F: FnMut(Event<'_>, wl_keyboard::WlKeyboard, wayland_client::DispatchData<'_>) + 'static,
|
||||
{
|
||||
let has_kbd = super::with_seat_data(seat, |data| data.has_keyboard).unwrap_or(false);
|
||||
let keyboard = if has_kbd {
|
||||
seat.get_keyboard()
|
||||
} else {
|
||||
return Err(Error::NoKeyboard);
|
||||
};
|
||||
|
||||
let state = Rc::new(RefCell::new(rmlvo.map(KbState::from_rmlvo).unwrap_or_else(KbState::new)?));
|
||||
|
||||
let callback = Rc::new(RefCell::new(callback)) as Rc<RefCell<_>>;
|
||||
|
||||
let repeat = match repeatkind {
|
||||
RepeatKind::System => RepeatDetails { locked: false, gap: None, delay: 200 },
|
||||
RepeatKind::Fixed { rate, delay } => {
|
||||
let gap = rate_to_gap(rate as i32);
|
||||
RepeatDetails { locked: true, gap, delay }
|
||||
}
|
||||
};
|
||||
|
||||
// Prepare the repetition handling.
|
||||
let mut handler = KbdHandler {
|
||||
callback: callback.clone(),
|
||||
state,
|
||||
repeat: Some(KbdRepeat {
|
||||
start_timer: {
|
||||
let my_loop_handle = loop_handle.clone();
|
||||
Box::new(move |source| {
|
||||
let my_callback = callback.clone();
|
||||
my_loop_handle
|
||||
.insert_source(source, move |event, kbd, ddata| {
|
||||
(my_callback.borrow_mut())(
|
||||
event,
|
||||
kbd.clone(),
|
||||
wayland_client::DispatchData::wrap(ddata),
|
||||
)
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
},
|
||||
stop_timer: Box::new(move |token| loop_handle.remove(token)),
|
||||
current_repeat: Rc::new(RefCell::new(None)),
|
||||
current_timer: Cell::new(None),
|
||||
details: repeat,
|
||||
}),
|
||||
};
|
||||
|
||||
keyboard
|
||||
.quick_assign(move |keyboard, event, data| handler.event(keyboard.detach(), event, data));
|
||||
|
||||
Ok(keyboard.detach())
|
||||
}
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
fn rate_to_gap(rate: i32) -> Option<NonZeroU32> {
|
||||
if rate <= 0 {
|
||||
None
|
||||
} else if MICROS_IN_SECOND < rate as u32 {
|
||||
NonZeroU32::new(1)
|
||||
} else {
|
||||
NonZeroU32::new(MICROS_IN_SECOND / rate as u32)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Classic handling
|
||||
*/
|
||||
|
||||
type KbdCallback = dyn FnMut(Event<'_>, wl_keyboard::WlKeyboard, wayland_client::DispatchData<'_>);
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
struct RepeatDetails {
|
||||
locked: bool,
|
||||
/// Gap between key presses in microseconds.
|
||||
///
|
||||
/// If the `gap` is `None`, it means that repeat is disabled.
|
||||
gap: Option<NonZeroU32>,
|
||||
/// Delay before starting key repeat in milliseconds.
|
||||
delay: u32,
|
||||
}
|
||||
|
||||
struct KbdHandler {
|
||||
state: Rc<RefCell<KbState>>,
|
||||
callback: Rc<RefCell<KbdCallback>>,
|
||||
#[cfg(feature = "calloop")]
|
||||
repeat: Option<KbdRepeat>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
struct KbdRepeat {
|
||||
start_timer: Box<dyn Fn(RepeatSource) -> RegistrationToken>,
|
||||
stop_timer: Box<dyn Fn(RegistrationToken)>,
|
||||
current_timer: Cell<Option<RegistrationToken>>,
|
||||
current_repeat: Rc<RefCell<Option<RepeatData>>>,
|
||||
details: RepeatDetails,
|
||||
}
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
impl KbdRepeat {
|
||||
fn start_repeat(
|
||||
&self,
|
||||
key: u32,
|
||||
keyboard: wl_keyboard::WlKeyboard,
|
||||
time: u32,
|
||||
state: Rc<RefCell<KbState>>,
|
||||
) {
|
||||
// Start a new repetition, overwriting the previous ones
|
||||
if let Some(timer) = self.current_timer.replace(None) {
|
||||
(self.stop_timer)(timer);
|
||||
}
|
||||
|
||||
// Handle disabled repeat rate.
|
||||
let gap = match self.details.gap {
|
||||
Some(gap) => Duration::from_micros(gap.get() as u64),
|
||||
None => return,
|
||||
};
|
||||
|
||||
let now = Instant::now();
|
||||
*self.current_repeat.borrow_mut() = Some(RepeatData {
|
||||
keyboard,
|
||||
keycode: key,
|
||||
gap,
|
||||
start_protocol_time: time,
|
||||
start_instant: now,
|
||||
});
|
||||
let token = (self.start_timer)(RepeatSource {
|
||||
timer: Timer::from_deadline(now + Duration::from_millis(self.details.delay as u64)),
|
||||
current_repeat: self.current_repeat.clone(),
|
||||
state,
|
||||
});
|
||||
self.current_timer.set(Some(token));
|
||||
}
|
||||
|
||||
fn stop_repeat(&self, key: u32) {
|
||||
// only cancel if the released key is the currently repeating key
|
||||
let mut guard = self.current_repeat.borrow_mut();
|
||||
let stop = (*guard).as_ref().map(|d| d.keycode == key).unwrap_or(false);
|
||||
if stop {
|
||||
if let Some(timer) = self.current_timer.replace(None) {
|
||||
(self.stop_timer)(timer);
|
||||
}
|
||||
*guard = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_all_repeat(&self) {
|
||||
if let Some(timer) = self.current_timer.replace(None) {
|
||||
(self.stop_timer)(timer);
|
||||
}
|
||||
*self.current_repeat.borrow_mut() = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
impl Drop for KbdRepeat {
|
||||
fn drop(&mut self) {
|
||||
self.stop_all_repeat();
|
||||
}
|
||||
}
|
||||
|
||||
impl KbdHandler {
|
||||
fn event(
|
||||
&mut self,
|
||||
kbd: wl_keyboard::WlKeyboard,
|
||||
event: wl_keyboard::Event,
|
||||
dispatch_data: wayland_client::DispatchData,
|
||||
) {
|
||||
use wl_keyboard::Event;
|
||||
|
||||
match event {
|
||||
Event::Keymap { format, fd, size } => self.keymap(kbd, format, fd, size),
|
||||
Event::Enter { serial, surface, keys } => {
|
||||
self.enter(kbd, serial, surface, keys, dispatch_data)
|
||||
}
|
||||
Event::Leave { serial, surface } => self.leave(kbd, serial, surface, dispatch_data),
|
||||
Event::Key { serial, time, key, state } => {
|
||||
self.key(kbd, serial, time, key, state, dispatch_data)
|
||||
}
|
||||
Event::Modifiers { mods_depressed, mods_latched, mods_locked, group, .. } => {
|
||||
self.modifiers(kbd, mods_depressed, mods_latched, mods_locked, group, dispatch_data)
|
||||
}
|
||||
Event::RepeatInfo { rate, delay } => self.repeat_info(kbd, rate, delay),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn keymap(
|
||||
&mut self,
|
||||
_: wl_keyboard::WlKeyboard,
|
||||
format: wl_keyboard::KeymapFormat,
|
||||
fd: RawFd,
|
||||
size: u32,
|
||||
) {
|
||||
let fd = unsafe { File::from_raw_fd(fd) };
|
||||
let mut state = self.state.borrow_mut();
|
||||
if state.locked() {
|
||||
// state is locked, ignore keymap updates
|
||||
return;
|
||||
}
|
||||
if state.ready() {
|
||||
// new keymap, we first deinit to free resources
|
||||
unsafe {
|
||||
state.de_init();
|
||||
}
|
||||
}
|
||||
match format {
|
||||
wl_keyboard::KeymapFormat::XkbV1 => unsafe {
|
||||
state.init_with_fd(fd, size as usize);
|
||||
},
|
||||
wl_keyboard::KeymapFormat::NoKeymap => {
|
||||
// TODO: how to handle this (hopefully never occuring) case?
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn enter(
|
||||
&mut self,
|
||||
object: wl_keyboard::WlKeyboard,
|
||||
serial: u32,
|
||||
surface: wl_surface::WlSurface,
|
||||
keys: Vec<u8>,
|
||||
dispatch_data: wayland_client::DispatchData,
|
||||
) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
let rawkeys = keys
|
||||
.chunks_exact(4)
|
||||
.map(|c| u32::from_ne_bytes(c.try_into().unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
let keys: Vec<u32> = rawkeys.iter().map(|k| state.get_one_sym_raw(*k)).collect();
|
||||
(self.callback.borrow_mut())(
|
||||
Event::Enter { serial, surface, rawkeys: &rawkeys, keysyms: &keys },
|
||||
object,
|
||||
dispatch_data,
|
||||
);
|
||||
}
|
||||
|
||||
fn leave(
|
||||
&mut self,
|
||||
object: wl_keyboard::WlKeyboard,
|
||||
serial: u32,
|
||||
surface: wl_surface::WlSurface,
|
||||
dispatch_data: wayland_client::DispatchData,
|
||||
) {
|
||||
#[cfg(feature = "calloop")]
|
||||
{
|
||||
if let Some(ref mut repeat) = self.repeat {
|
||||
repeat.stop_all_repeat();
|
||||
}
|
||||
}
|
||||
(self.callback.borrow_mut())(Event::Leave { serial, surface }, object, dispatch_data);
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "calloop"), allow(unused_variables))]
|
||||
fn key(
|
||||
&mut self,
|
||||
object: wl_keyboard::WlKeyboard,
|
||||
serial: u32,
|
||||
time: u32,
|
||||
key: u32,
|
||||
key_state: wl_keyboard::KeyState,
|
||||
dispatch_data: wayland_client::DispatchData,
|
||||
) {
|
||||
let (sym, utf8, repeats) = {
|
||||
let mut state = self.state.borrow_mut();
|
||||
// Get the values to generate a key event
|
||||
let sym = state.get_one_sym_raw(key);
|
||||
let utf8 = if key_state == wl_keyboard::KeyState::Pressed {
|
||||
match state.compose_feed(sym) {
|
||||
Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => {
|
||||
if let Some(status) = state.compose_status() {
|
||||
match status {
|
||||
ffi::xkb_compose_status::XKB_COMPOSE_COMPOSED => {
|
||||
state.compose_get_utf8()
|
||||
}
|
||||
ffi::xkb_compose_status::XKB_COMPOSE_NOTHING => {
|
||||
state.get_utf8_raw(key)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
state.get_utf8_raw(key)
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
// XKB_COMPOSE_FEED_IGNORED
|
||||
None
|
||||
}
|
||||
None => {
|
||||
// XKB COMPOSE is not initialized
|
||||
state.get_utf8_raw(key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let repeats = unsafe { state.key_repeats(key + 8) };
|
||||
(sym, utf8, repeats)
|
||||
};
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
{
|
||||
if let Some(ref mut repeat_handle) = self.repeat {
|
||||
if repeats {
|
||||
if key_state == wl_keyboard::KeyState::Pressed {
|
||||
repeat_handle.start_repeat(key, object.clone(), time, self.state.clone());
|
||||
} else {
|
||||
repeat_handle.stop_repeat(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(self.callback.borrow_mut())(
|
||||
Event::Key { serial, time, rawkey: key, keysym: sym, state: key_state, utf8 },
|
||||
object,
|
||||
dispatch_data,
|
||||
);
|
||||
}
|
||||
|
||||
fn modifiers(
|
||||
&mut self,
|
||||
object: wl_keyboard::WlKeyboard,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
group: u32,
|
||||
dispatch_data: wayland_client::DispatchData,
|
||||
) {
|
||||
{
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.update_modifiers(mods_depressed, mods_latched, mods_locked, group);
|
||||
(self.callback.borrow_mut())(
|
||||
Event::Modifiers { modifiers: state.mods_state() },
|
||||
object,
|
||||
dispatch_data,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "calloop"), allow(unused_variables))]
|
||||
fn repeat_info(&mut self, _: wl_keyboard::WlKeyboard, rate: i32, delay: i32) {
|
||||
#[cfg(feature = "calloop")]
|
||||
{
|
||||
if let Some(ref mut repeat_handle) = self.repeat {
|
||||
if !repeat_handle.details.locked {
|
||||
repeat_handle.details.gap = rate_to_gap(rate);
|
||||
repeat_handle.details.delay = delay as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Repeat handling
|
||||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg(feature = "calloop")]
|
||||
struct RepeatData {
|
||||
keyboard: wl_keyboard::WlKeyboard,
|
||||
keycode: u32,
|
||||
/// Gap between key presses
|
||||
gap: Duration,
|
||||
start_protocol_time: u32,
|
||||
start_instant: Instant,
|
||||
}
|
||||
|
||||
/// An event source managing the key repetition of a keyboard
|
||||
///
|
||||
/// It is given to you from [`map_keyboard`](fn.map_keyboard.html), and you need to
|
||||
/// insert it in your calloop event loop if you want to have functionning key repetition.
|
||||
///
|
||||
/// If don't want key repetition you can just drop it.
|
||||
///
|
||||
/// This source will not directly generate calloop events, and the callback provided to
|
||||
/// `EventLoopHandle::insert_source()` will be ignored. Instead it triggers the
|
||||
/// callback you provided to [`map_keyboard`](fn.map_keyboard.html).
|
||||
#[cfg(feature = "calloop")]
|
||||
#[derive(Debug)]
|
||||
pub struct RepeatSource {
|
||||
timer: calloop::timer::Timer,
|
||||
state: Rc<RefCell<KbState>>,
|
||||
current_repeat: Rc<RefCell<Option<RepeatData>>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "calloop")]
|
||||
impl calloop::EventSource for RepeatSource {
|
||||
type Event = Event<'static>;
|
||||
type Metadata = wl_keyboard::WlKeyboard;
|
||||
type Error = <calloop::timer::Timer as calloop::EventSource>::Error;
|
||||
type Ret = ();
|
||||
|
||||
fn process_events<F>(
|
||||
&mut self,
|
||||
readiness: calloop::Readiness,
|
||||
token: calloop::Token,
|
||||
mut callback: F,
|
||||
) -> std::io::Result<calloop::PostAction>
|
||||
where
|
||||
F: FnMut(Event<'static>, &mut wl_keyboard::WlKeyboard),
|
||||
{
|
||||
let current_repeat = &self.current_repeat;
|
||||
let state = &self.state;
|
||||
self.timer.process_events(readiness, token, |last_trigger, &mut ()| {
|
||||
if let Some(ref mut data) = *current_repeat.borrow_mut() {
|
||||
// there is something to repeat
|
||||
let mut state = state.borrow_mut();
|
||||
let keysym = state.get_one_sym_raw(data.keycode);
|
||||
let utf8 = state.get_utf8_raw(data.keycode);
|
||||
// Notify the callback.
|
||||
callback(
|
||||
Event::Repeat {
|
||||
time: data.start_protocol_time
|
||||
+ (last_trigger - data.start_instant).as_millis() as u32,
|
||||
rawkey: data.keycode,
|
||||
keysym,
|
||||
utf8,
|
||||
},
|
||||
&mut data.keyboard,
|
||||
);
|
||||
// Schedule the next timeout.
|
||||
calloop::timer::TimeoutAction::ToInstant(last_trigger + data.gap)
|
||||
} else {
|
||||
calloop::timer::TimeoutAction::Drop
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn register(
|
||||
&mut self,
|
||||
poll: &mut calloop::Poll,
|
||||
token_factory: &mut calloop::TokenFactory,
|
||||
) -> calloop::Result<()> {
|
||||
self.timer.register(poll, token_factory)
|
||||
}
|
||||
|
||||
fn reregister(
|
||||
&mut self,
|
||||
poll: &mut calloop::Poll,
|
||||
token_factory: &mut calloop::TokenFactory,
|
||||
) -> calloop::Result<()> {
|
||||
self.timer.reregister(poll, token_factory)
|
||||
}
|
||||
|
||||
fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> {
|
||||
self.timer.unregister(poll)
|
||||
}
|
||||
}
|
||||
433
third-party/vendor/smithay-client-toolkit/src/seat/keyboard/state.rs
vendored
Normal file
433
third-party/vendor/smithay-client-toolkit/src/seat/keyboard/state.rs
vendored
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
use memmap2::MmapOptions;
|
||||
use std::{env, ffi::CString, fs::File, os::raw::c_char, os::unix::ffi::OsStringExt, ptr};
|
||||
|
||||
#[cfg(feature = "dlopen")]
|
||||
use super::ffi::XKBCOMMON_HANDLE as XKBH;
|
||||
#[cfg(not(feature = "dlopen"))]
|
||||
use super::ffi::*;
|
||||
use super::ffi::{self, xkb_state_component};
|
||||
use super::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct KbState {
|
||||
xkb_context: *mut ffi::xkb_context,
|
||||
xkb_keymap: *mut ffi::xkb_keymap,
|
||||
xkb_state: *mut ffi::xkb_state,
|
||||
xkb_compose_table: *mut ffi::xkb_compose_table,
|
||||
xkb_compose_state: *mut ffi::xkb_compose_state,
|
||||
mods_state: ModifiersState,
|
||||
locked: bool,
|
||||
}
|
||||
|
||||
/// The RMLVO description of a keymap
|
||||
///
|
||||
/// All fields are optional, and the system default
|
||||
/// will be used if set to `None`.
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct RMLVO {
|
||||
/// The rules file to use
|
||||
pub rules: Option<String>,
|
||||
/// The keyboard model by which to interpret keycodes and LEDs
|
||||
pub model: Option<String>,
|
||||
/// A comma separated list of layouts (languages) to include in the keymap
|
||||
pub layout: Option<String>,
|
||||
/// A comma separated list of variants, one per layout, which may modify or
|
||||
/// augment the respective layout in various ways
|
||||
pub variant: Option<String>,
|
||||
/// A comma separated list of options, through which the user specifies
|
||||
/// non-layout related preferences, like which key combinations are
|
||||
/// used for switching layouts, or which key is the Compose key.
|
||||
pub options: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents the current state of the keyboard modifiers
|
||||
///
|
||||
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
|
||||
///
|
||||
/// For some modifiers, this means that the key is currently pressed, others are toggled
|
||||
/// (like caps lock).
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct ModifiersState {
|
||||
/// The "control" key
|
||||
pub ctrl: bool,
|
||||
/// The "alt" key
|
||||
pub alt: bool,
|
||||
/// The "shift" key
|
||||
pub shift: bool,
|
||||
/// The "Caps lock" key
|
||||
pub caps_lock: bool,
|
||||
/// The "logo" key
|
||||
///
|
||||
/// Also known as the "windows" key on most keyboards
|
||||
pub logo: bool,
|
||||
/// The "Num lock" key
|
||||
pub num_lock: bool,
|
||||
}
|
||||
|
||||
impl ModifiersState {
|
||||
fn new() -> ModifiersState {
|
||||
ModifiersState::default()
|
||||
}
|
||||
|
||||
fn update_with(&mut self, state: *mut ffi::xkb_state) {
|
||||
self.ctrl = unsafe {
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_state_mod_name_is_active,
|
||||
state,
|
||||
ffi::XKB_MOD_NAME_CTRL.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE
|
||||
) > 0
|
||||
};
|
||||
self.alt = unsafe {
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_state_mod_name_is_active,
|
||||
state,
|
||||
ffi::XKB_MOD_NAME_ALT.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE
|
||||
) > 0
|
||||
};
|
||||
self.shift = unsafe {
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_state_mod_name_is_active,
|
||||
state,
|
||||
ffi::XKB_MOD_NAME_SHIFT.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE
|
||||
) > 0
|
||||
};
|
||||
self.caps_lock = unsafe {
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_state_mod_name_is_active,
|
||||
state,
|
||||
ffi::XKB_MOD_NAME_CAPS.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE
|
||||
) > 0
|
||||
};
|
||||
self.logo = unsafe {
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_state_mod_name_is_active,
|
||||
state,
|
||||
ffi::XKB_MOD_NAME_LOGO.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE
|
||||
) > 0
|
||||
};
|
||||
self.num_lock = unsafe {
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_state_mod_name_is_active,
|
||||
state,
|
||||
ffi::XKB_MOD_NAME_NUM.as_ptr() as *const c_char,
|
||||
xkb_state_component::XKB_STATE_MODS_EFFECTIVE
|
||||
) > 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl KbState {
|
||||
pub(crate) fn update_modifiers(
|
||||
&mut self,
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
group: u32,
|
||||
) {
|
||||
if !self.ready() {
|
||||
return;
|
||||
}
|
||||
let mask = unsafe {
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_state_update_mask,
|
||||
self.xkb_state,
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
0,
|
||||
0,
|
||||
group
|
||||
)
|
||||
};
|
||||
if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) {
|
||||
// effective value of mods have changed, we need to update our state
|
||||
self.mods_state.update_with(self.xkb_state);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_one_sym_raw(&mut self, keycode: u32) -> u32 {
|
||||
if !self.ready() {
|
||||
return 0;
|
||||
}
|
||||
unsafe { ffi_dispatch!(XKBH, xkb_state_key_get_one_sym, self.xkb_state, keycode + 8) }
|
||||
}
|
||||
|
||||
pub(crate) fn get_utf8_raw(&mut self, keycode: u32) -> Option<String> {
|
||||
if !self.ready() {
|
||||
return None;
|
||||
}
|
||||
let size = unsafe {
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_state_key_get_utf8,
|
||||
self.xkb_state,
|
||||
keycode + 8,
|
||||
ptr::null_mut(),
|
||||
0
|
||||
)
|
||||
} + 1;
|
||||
if size <= 1 {
|
||||
return None;
|
||||
};
|
||||
let mut buffer = vec![0; size as usize];
|
||||
unsafe {
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_state_key_get_utf8,
|
||||
self.xkb_state,
|
||||
keycode + 8,
|
||||
buffer.as_mut_ptr() as *mut _,
|
||||
size as usize
|
||||
);
|
||||
};
|
||||
// remove the final `\0`
|
||||
buffer.pop();
|
||||
// libxkbcommon will always provide valid UTF8
|
||||
Some(unsafe { String::from_utf8_unchecked(buffer) })
|
||||
}
|
||||
|
||||
pub(crate) fn compose_feed(&mut self, keysym: u32) -> Option<ffi::xkb_compose_feed_result> {
|
||||
if !self.ready() || self.xkb_compose_state.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(unsafe { ffi_dispatch!(XKBH, xkb_compose_state_feed, self.xkb_compose_state, keysym) })
|
||||
}
|
||||
|
||||
pub(crate) fn compose_status(&mut self) -> Option<ffi::xkb_compose_status> {
|
||||
if !self.ready() || self.xkb_compose_state.is_null() {
|
||||
return None;
|
||||
}
|
||||
Some(unsafe { ffi_dispatch!(XKBH, xkb_compose_state_get_status, self.xkb_compose_state) })
|
||||
}
|
||||
|
||||
pub(crate) fn compose_get_utf8(&mut self) -> Option<String> {
|
||||
if !self.ready() || self.xkb_compose_state.is_null() {
|
||||
return None;
|
||||
}
|
||||
let size = unsafe {
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_compose_state_get_utf8,
|
||||
self.xkb_compose_state,
|
||||
ptr::null_mut(),
|
||||
0
|
||||
)
|
||||
} + 1;
|
||||
if size <= 1 {
|
||||
return None;
|
||||
};
|
||||
let mut buffer = vec![0; size as usize];
|
||||
unsafe {
|
||||
buffer.set_len(size as usize);
|
||||
ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_compose_state_get_utf8,
|
||||
self.xkb_compose_state,
|
||||
buffer.as_mut_ptr() as *mut _,
|
||||
size as usize
|
||||
);
|
||||
};
|
||||
// remove the final `\0`
|
||||
buffer.pop();
|
||||
// libxkbcommon will always provide valid UTF8
|
||||
Some(unsafe { String::from_utf8_unchecked(buffer) })
|
||||
}
|
||||
|
||||
pub(crate) fn new() -> Result<KbState, Error> {
|
||||
#[cfg(feature = "dlopen")]
|
||||
{
|
||||
if ffi::XKBCOMMON_OPTION.as_ref().is_none() {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
}
|
||||
let context = unsafe {
|
||||
ffi_dispatch!(XKBH, xkb_context_new, ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS)
|
||||
};
|
||||
if context.is_null() {
|
||||
return Err(Error::XKBNotFound);
|
||||
}
|
||||
|
||||
let mut me = KbState {
|
||||
xkb_context: context,
|
||||
xkb_keymap: ptr::null_mut(),
|
||||
xkb_state: ptr::null_mut(),
|
||||
xkb_compose_table: ptr::null_mut(),
|
||||
xkb_compose_state: ptr::null_mut(),
|
||||
mods_state: ModifiersState::new(),
|
||||
locked: false,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
me.init_compose();
|
||||
}
|
||||
|
||||
Ok(me)
|
||||
}
|
||||
|
||||
pub(crate) fn from_rmlvo(rmlvo: RMLVO) -> Result<KbState, Error> {
|
||||
fn to_cstring(s: Option<String>) -> Result<Option<CString>, Error> {
|
||||
s.map_or(Ok(None), |s| CString::new(s).map(Option::Some)).map_err(|_| Error::BadNames)
|
||||
}
|
||||
|
||||
let mut state = KbState::new()?;
|
||||
|
||||
let rules = to_cstring(rmlvo.rules)?;
|
||||
let model = to_cstring(rmlvo.model)?;
|
||||
let layout = to_cstring(rmlvo.layout)?;
|
||||
let variant = to_cstring(rmlvo.variant)?;
|
||||
let options = to_cstring(rmlvo.options)?;
|
||||
|
||||
let xkb_names = ffi::xkb_rule_names {
|
||||
rules: rules.map_or(ptr::null(), |s| s.as_ptr()),
|
||||
model: model.map_or(ptr::null(), |s| s.as_ptr()),
|
||||
layout: layout.map_or(ptr::null(), |s| s.as_ptr()),
|
||||
variant: variant.map_or(ptr::null(), |s| s.as_ptr()),
|
||||
options: options.map_or(ptr::null(), |s| s.as_ptr()),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
state.init_with_rmlvo(xkb_names)?;
|
||||
}
|
||||
|
||||
state.locked = true;
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init_compose(&mut self) {
|
||||
let locale = env::var_os("LC_ALL")
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LC_CTYPE"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.or_else(|| env::var_os("LANG"))
|
||||
.and_then(|v| if v.is_empty() { None } else { Some(v) })
|
||||
.unwrap_or_else(|| "C".into());
|
||||
let locale = CString::new(locale.into_vec()).unwrap();
|
||||
|
||||
let compose_table = ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_compose_table_new_from_locale,
|
||||
self.xkb_context,
|
||||
locale.as_ptr(),
|
||||
ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS
|
||||
);
|
||||
|
||||
if compose_table.is_null() {
|
||||
// init of compose table failed, continue without compose
|
||||
return;
|
||||
}
|
||||
|
||||
let compose_state = ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_compose_state_new,
|
||||
compose_table,
|
||||
ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS
|
||||
);
|
||||
|
||||
if compose_state.is_null() {
|
||||
// init of compose state failed, continue without compose
|
||||
ffi_dispatch!(XKBH, xkb_compose_table_unref, compose_table);
|
||||
return;
|
||||
}
|
||||
|
||||
self.xkb_compose_table = compose_table;
|
||||
self.xkb_compose_state = compose_state;
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn post_init(&mut self, keymap: *mut ffi::xkb_keymap) {
|
||||
let state = ffi_dispatch!(XKBH, xkb_state_new, keymap);
|
||||
self.xkb_keymap = keymap;
|
||||
self.xkb_state = state;
|
||||
self.mods_state.update_with(state);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn de_init(&mut self) {
|
||||
ffi_dispatch!(XKBH, xkb_state_unref, self.xkb_state);
|
||||
self.xkb_state = ptr::null_mut();
|
||||
ffi_dispatch!(XKBH, xkb_keymap_unref, self.xkb_keymap);
|
||||
self.xkb_keymap = ptr::null_mut();
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init_with_fd(&mut self, fd: File, size: usize) {
|
||||
let map = MmapOptions::new().len(size).map(&fd).unwrap();
|
||||
|
||||
let keymap = ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_keymap_new_from_string,
|
||||
self.xkb_context,
|
||||
map.as_ptr() as *const _,
|
||||
ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1,
|
||||
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS
|
||||
);
|
||||
|
||||
if keymap.is_null() {
|
||||
panic!("Received invalid keymap from compositor.");
|
||||
}
|
||||
|
||||
self.post_init(keymap);
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn init_with_rmlvo(
|
||||
&mut self,
|
||||
names: ffi::xkb_rule_names,
|
||||
) -> Result<(), Error> {
|
||||
let keymap = ffi_dispatch!(
|
||||
XKBH,
|
||||
xkb_keymap_new_from_names,
|
||||
self.xkb_context,
|
||||
&names,
|
||||
ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS
|
||||
);
|
||||
|
||||
if keymap.is_null() {
|
||||
return Err(Error::BadNames);
|
||||
}
|
||||
|
||||
self.post_init(keymap);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn key_repeats(&mut self, xkb_keycode_t: ffi::xkb_keycode_t) -> bool {
|
||||
ffi_dispatch!(XKBH, xkb_keymap_key_repeats, self.xkb_keymap, xkb_keycode_t) == 1
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn ready(&self) -> bool {
|
||||
!self.xkb_state.is_null()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn locked(&self) -> bool {
|
||||
self.locked
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn mods_state(&self) -> ModifiersState {
|
||||
self.mods_state
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for KbState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
ffi_dispatch!(XKBH, xkb_compose_state_unref, self.xkb_compose_state);
|
||||
ffi_dispatch!(XKBH, xkb_compose_table_unref, self.xkb_compose_table);
|
||||
ffi_dispatch!(XKBH, xkb_state_unref, self.xkb_state);
|
||||
ffi_dispatch!(XKBH, xkb_keymap_unref, self.xkb_keymap);
|
||||
ffi_dispatch!(XKBH, xkb_context_unref, self.xkb_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
308
third-party/vendor/smithay-client-toolkit/src/seat/mod.rs
vendored
Normal file
308
third-party/vendor/smithay-client-toolkit/src/seat/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
//! Types for automatically handling seats
|
||||
//!
|
||||
//! This modules provides a `SeatHandler` for use with the
|
||||
//! [`environment!`](../macro.environment.html) macro. It is automatically inserted
|
||||
//! in the [`default_environment!`](../macro.default_environment.html).
|
||||
//!
|
||||
//! This handler tracks the capability of the seats declared by the compositor,
|
||||
//! and gives you the possibility to register callbacks that will be invoked whenever
|
||||
//! a new seat is created of the state of a seat changes, via the
|
||||
//! [`Environment::listen_for_seats`](../environment/struct.Environment.html) method.
|
||||
//!
|
||||
//! **Note:** if you don't use the [`default_environment!`](../macro.default_environment.html),
|
||||
//! you'll need to implement the [`SeatHandling`](trait.SeatHandling.hmtl) on your
|
||||
//! environment struct to access the added methods on
|
||||
//! [`Environment`](../environment/struct.Environment.html).
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt::{self, Debug, Formatter},
|
||||
rc::{Rc, Weak},
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use bitflags::bitflags;
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_registry, wl_seat},
|
||||
Attached, DispatchData, Main,
|
||||
};
|
||||
|
||||
pub mod keyboard;
|
||||
pub mod pointer;
|
||||
|
||||
type SeatCallback = dyn FnMut(Attached<wl_seat::WlSeat>, &SeatData, DispatchData) + 'static;
|
||||
|
||||
/// The metadata associated with a seat
|
||||
#[derive(Clone)]
|
||||
pub struct SeatData {
|
||||
/// The name of this seat
|
||||
///
|
||||
/// It can be used as an identifier for the seat
|
||||
pub name: String,
|
||||
/// Whether this seat has a pointer available
|
||||
pub has_pointer: bool,
|
||||
/// Whether this seat has a keyboard available
|
||||
pub has_keyboard: bool,
|
||||
/// Whether this seat has a touchscreen available
|
||||
pub has_touch: bool,
|
||||
/// Whether this seat has been removed from the registry
|
||||
///
|
||||
/// Once a seat is removed, you will no longer receive any
|
||||
/// event on any of its associated devices (pointer, keyboard or touch).
|
||||
///
|
||||
/// You can thus cleanup all your state associated with this seat.
|
||||
pub defunct: bool,
|
||||
|
||||
/// State of readiness of the data.
|
||||
state: SeatDataState,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
struct SeatDataState: u8 {
|
||||
const NEW = 0b00000000;
|
||||
const GOT_NAME = 0b00000001;
|
||||
const GOT_CAPABILITIES = 0b00000010;
|
||||
const READY = Self::GOT_NAME.bits | Self::GOT_CAPABILITIES.bits;
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for SeatData {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SeatData")
|
||||
.field("name", &self.name)
|
||||
.field("has_pointer", &self.has_pointer)
|
||||
.field("has_keyboard", &self.has_keyboard)
|
||||
.field("has_touch", &self.has_touch)
|
||||
.field("defunct", &self.defunct)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl SeatData {
|
||||
fn new() -> SeatData {
|
||||
SeatData {
|
||||
name: String::new(),
|
||||
has_pointer: false,
|
||||
has_keyboard: false,
|
||||
has_touch: false,
|
||||
defunct: false,
|
||||
state: SeatDataState::NEW,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple handler for seats
|
||||
///
|
||||
/// This handler will manage seats and track their capabilities.
|
||||
///
|
||||
/// You can register callbacks using the [`SeatHandling::listen`](trait.SeatHandling.html)
|
||||
/// to be notified whenever a seat is created, destroyed, or its capabilities change.
|
||||
pub struct SeatHandler {
|
||||
seats: Vec<(u32, Attached<wl_seat::WlSeat>)>,
|
||||
listeners: Rc<RefCell<Vec<Weak<RefCell<SeatCallback>>>>>,
|
||||
}
|
||||
|
||||
impl SeatHandler {
|
||||
/// Create a new SeatHandler
|
||||
pub fn new() -> SeatHandler {
|
||||
SeatHandler { seats: Vec::new(), listeners: Rc::new(RefCell::new(Vec::new())) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SeatHandler {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SeatHandler")
|
||||
.field("seats", &self.seats)
|
||||
.field("listeners", &"Fn(..) -> { ... }")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to an seat listener callback
|
||||
///
|
||||
/// Dropping it disables the associated callback and frees the closure.
|
||||
pub struct SeatListener {
|
||||
_cb: Rc<RefCell<SeatCallback>>,
|
||||
}
|
||||
|
||||
impl crate::environment::MultiGlobalHandler<wl_seat::WlSeat> for SeatHandler {
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
_: DispatchData,
|
||||
) {
|
||||
// Seat is supported up to version 6
|
||||
let version = std::cmp::min(version, 6);
|
||||
let seat = registry.bind::<wl_seat::WlSeat>(version, id);
|
||||
seat.as_ref().user_data().set_threadsafe(|| Mutex::new(SeatData::new()));
|
||||
let cb_listeners = self.listeners.clone();
|
||||
seat.quick_assign(move |seat, event, ddata| {
|
||||
process_seat_event(seat, event, &cb_listeners, ddata)
|
||||
});
|
||||
self.seats.push((id, (*seat).clone()));
|
||||
}
|
||||
fn removed(&mut self, id: u32, mut ddata: DispatchData) {
|
||||
let mut listeners = self.listeners.borrow_mut();
|
||||
self.seats.retain(|&(i, ref seat)| {
|
||||
if i != id {
|
||||
true
|
||||
} else {
|
||||
// This data must be `Mutex<SeatData>` if this seat is in our vec
|
||||
let data = seat.as_ref().user_data().get::<Mutex<SeatData>>().unwrap();
|
||||
let mut guard = data.lock().unwrap();
|
||||
guard.defunct = true;
|
||||
// notify the listeners that the seat is dead
|
||||
listeners.retain(|lst| {
|
||||
if let Some(cb) = Weak::upgrade(lst) {
|
||||
(cb.borrow_mut())(seat.clone(), &*guard, ddata.reborrow());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
fn get_all(&self) -> Vec<Attached<wl_seat::WlSeat>> {
|
||||
self.seats.iter().map(|(_, s)| s.clone()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SeatListener {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SeatListener").field("_cb", &"Fn(..) -> { ... }").finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn process_seat_event(
|
||||
seat: Main<wl_seat::WlSeat>,
|
||||
event: wl_seat::Event,
|
||||
listeners: &RefCell<Vec<Weak<RefCell<SeatCallback>>>>,
|
||||
mut ddata: DispatchData,
|
||||
) {
|
||||
let new_data = {
|
||||
let data = seat.as_ref().user_data().get::<Mutex<SeatData>>().unwrap();
|
||||
let mut guard = data.lock().unwrap();
|
||||
match event {
|
||||
wl_seat::Event::Name { name } => {
|
||||
guard.state.set(SeatDataState::GOT_NAME, true);
|
||||
guard.name = name;
|
||||
}
|
||||
wl_seat::Event::Capabilities { capabilities } => {
|
||||
guard.state.set(SeatDataState::GOT_CAPABILITIES, true);
|
||||
guard.has_pointer = capabilities.contains(wl_seat::Capability::Pointer);
|
||||
guard.has_keyboard = capabilities.contains(wl_seat::Capability::Keyboard);
|
||||
guard.has_touch = capabilities.contains(wl_seat::Capability::Touch);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
guard.clone()
|
||||
};
|
||||
|
||||
if new_data.state.contains(SeatDataState::READY) {
|
||||
listeners.borrow_mut().retain(|lst| {
|
||||
if let Some(cb) = Weak::upgrade(lst) {
|
||||
(cb.borrow_mut())((*seat).clone(), &new_data, ddata.reborrow());
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the copy of the data associated with this seat
|
||||
///
|
||||
/// If the provided `WlSeat` has not yet been initialized or is not managed by SCTK, `None` is returned.
|
||||
///
|
||||
/// If the seat has been removed by the compositor, the `defunct` field of the `SeatData`
|
||||
/// will be set to `true`. This handler will not automatically detroy the output by calling its
|
||||
/// `release` method, to avoid interfering with your logic.
|
||||
pub fn clone_seat_data(seat: &wl_seat::WlSeat) -> Option<SeatData> {
|
||||
if let Some(udata_mutex) = seat.as_ref().user_data().get::<Mutex<SeatData>>() {
|
||||
let udata = udata_mutex.lock().unwrap();
|
||||
Some(udata.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the data associated with this seat
|
||||
///
|
||||
/// The provided closure is given the [`SeatData`](struct.SeatData.html) as argument,
|
||||
/// and its return value is returned from this function.
|
||||
///
|
||||
/// If the provided `WlSeat` has not yet been initialized or is not managed by SCTK, `None` is returned.
|
||||
///
|
||||
/// If the seat has been removed by the compositor, the `defunct` field of the `SeatData`
|
||||
/// will be set to `true`. This handler will not automatically detroy the output by calling its
|
||||
/// `release` method, to avoid interfering with your logic.
|
||||
pub fn with_seat_data<T, F: FnOnce(&SeatData) -> T>(seat: &wl_seat::WlSeat, f: F) -> Option<T> {
|
||||
if let Some(udata_mutex) = seat.as_ref().user_data().get::<Mutex<SeatData>>() {
|
||||
let udata = udata_mutex.lock().unwrap();
|
||||
Some(f(&*udata))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait representing the SeatHandler functions
|
||||
///
|
||||
/// Implementing this trait on your inner environment struct used with the
|
||||
/// [`environment!`](../macro.environment.html) by delegating it to its
|
||||
/// [`SeatHandler`](struct.SeatHandler.html) field will make available the seat-associated
|
||||
/// method on your [`Environment`](../environment/struct.Environment.html).
|
||||
pub trait SeatHandling {
|
||||
/// Insert a listener for seat events
|
||||
fn listen<F: FnMut(Attached<wl_seat::WlSeat>, &SeatData, DispatchData) + 'static>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> SeatListener;
|
||||
}
|
||||
|
||||
impl SeatHandling for SeatHandler {
|
||||
fn listen<F: FnMut(Attached<wl_seat::WlSeat>, &SeatData, DispatchData) + 'static>(
|
||||
&mut self,
|
||||
f: F,
|
||||
) -> SeatListener {
|
||||
let rc = Rc::new(RefCell::new(f)) as Rc<_>;
|
||||
self.listeners.borrow_mut().push(Rc::downgrade(&rc));
|
||||
SeatListener { _cb: rc }
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: SeatHandling> crate::environment::Environment<E> {
|
||||
/// Insert a new listener for seats
|
||||
///
|
||||
/// The provided closure will be invoked whenever a `wl_seat` is made available,
|
||||
/// removed, or see its capabilities changed.
|
||||
///
|
||||
/// Note that if seats already exist when this callback is setup, it'll not be invoked on them.
|
||||
/// For you to be notified of them as well, you need to first process them manually by calling
|
||||
/// `.get_all_seats()`.
|
||||
///
|
||||
/// The returned [`SeatListener`](../seat/struct.SeatListener.hmtl) keeps your callback alive,
|
||||
/// dropping it will disable it.
|
||||
#[must_use = "the returned SeatListener keeps your callback alive, dropping it will disable it"]
|
||||
pub fn listen_for_seats<
|
||||
F: FnMut(Attached<wl_seat::WlSeat>, &SeatData, DispatchData) + 'static,
|
||||
>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> SeatListener {
|
||||
self.with_inner(move |inner| SeatHandling::listen(inner, f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: crate::environment::MultiGlobalHandler<wl_seat::WlSeat>>
|
||||
crate::environment::Environment<E>
|
||||
{
|
||||
/// Shorthand method to retrieve the list of seats
|
||||
pub fn get_all_seats(&self) -> Vec<Attached<wl_seat::WlSeat>> {
|
||||
self.get_all_globals::<wl_seat::WlSeat>().into_iter().collect()
|
||||
}
|
||||
}
|
||||
5
third-party/vendor/smithay-client-toolkit/src/seat/pointer/mod.rs
vendored
Normal file
5
third-party/vendor/smithay-client-toolkit/src/seat/pointer/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
//! Utilities to work with pointers and their icons
|
||||
|
||||
mod theme;
|
||||
|
||||
pub use self::theme::{ThemeManager, ThemeSpec, ThemedPointer};
|
||||
275
third-party/vendor/smithay-client-toolkit/src/seat/pointer/theme.rs
vendored
Normal file
275
third-party/vendor/smithay-client-toolkit/src/seat/pointer/theme.rs
vendored
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
fmt,
|
||||
ops::Deref,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
use wayland_client::{
|
||||
protocol::{wl_compositor, wl_pointer, wl_seat, wl_shm, wl_surface},
|
||||
Attached, DispatchData,
|
||||
};
|
||||
use wayland_cursor::{Cursor, CursorTheme};
|
||||
|
||||
/// The specification of a cursor theme to be used by the ThemeManager
|
||||
#[derive(Debug)]
|
||||
pub enum ThemeSpec<'a> {
|
||||
/// Use this specific theme with given base size
|
||||
Precise {
|
||||
/// Name of the cursor theme to use
|
||||
name: &'a str,
|
||||
/// Base size of the cursor images
|
||||
///
|
||||
/// This is the size that will be used on monitors with a scale
|
||||
/// factor of 1. Cursor images sizes will be multiples of this
|
||||
/// base size on HiDPI outputs.
|
||||
size: u32,
|
||||
},
|
||||
/// Use the system provided theme
|
||||
///
|
||||
/// In this case SCTK will read the `XCURSOR_THEME` and
|
||||
/// `XCURSOR_SIZE` environment variables to figure out the
|
||||
/// theme to use.
|
||||
System,
|
||||
}
|
||||
|
||||
/// Wrapper managing a system theme for pointer images
|
||||
///
|
||||
/// You can use it to initialize new pointers in order
|
||||
/// to theme them.
|
||||
///
|
||||
/// Is is also clone-able in case you need to handle several
|
||||
/// pointer theming from different places.
|
||||
///
|
||||
/// Note that it is however neither `Send` nor `Sync`
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ThemeManager {
|
||||
themes: Rc<RefCell<ScaledThemeList>>,
|
||||
compositor: Attached<wl_compositor::WlCompositor>,
|
||||
}
|
||||
|
||||
impl ThemeManager {
|
||||
/// Load a system pointer theme
|
||||
///
|
||||
/// Will use the default theme of the system if name is `None`.
|
||||
pub fn init(
|
||||
theme: ThemeSpec,
|
||||
compositor: Attached<wl_compositor::WlCompositor>,
|
||||
shm: Attached<wl_shm::WlShm>,
|
||||
) -> ThemeManager {
|
||||
ThemeManager { compositor, themes: Rc::new(RefCell::new(ScaledThemeList::new(theme, shm))) }
|
||||
}
|
||||
|
||||
/// Wrap a pointer to theme it
|
||||
pub fn theme_pointer(&self, pointer: wl_pointer::WlPointer) -> ThemedPointer {
|
||||
let surface = self.compositor.create_surface();
|
||||
let inner = Rc::new(RefCell::new(PointerInner {
|
||||
surface: surface.detach(),
|
||||
themes: self.themes.clone(),
|
||||
last_serial: 0,
|
||||
current_cursor: "left_ptr".into(),
|
||||
scale_factor: 1,
|
||||
}));
|
||||
let my_pointer = pointer.clone();
|
||||
let winner = Rc::downgrade(&inner);
|
||||
crate::surface::setup_surface(
|
||||
surface,
|
||||
Some(move |scale_factor, _, _: DispatchData| {
|
||||
if let Some(inner) = Weak::upgrade(&winner) {
|
||||
let mut inner = inner.borrow_mut();
|
||||
inner.scale_factor = scale_factor;
|
||||
// we can't handle errors here, so ignore it
|
||||
// worst that can happen is cursor drawn with the wrong
|
||||
// scale factor
|
||||
let _ = inner.update_cursor(&my_pointer);
|
||||
}
|
||||
}),
|
||||
);
|
||||
ThemedPointer { pointer, inner }
|
||||
}
|
||||
|
||||
/// Initialize a new pointer as a ThemedPointer with an adapter implementation
|
||||
///
|
||||
/// You need to provide an implementation as if implementing a `wl_pointer`, but
|
||||
/// it will receive as `meta` argument a `ThemedPointer` wrapping your pointer,
|
||||
/// rather than a `WlPointer`.
|
||||
pub fn theme_pointer_with_impl<F>(
|
||||
&self,
|
||||
seat: &Attached<wl_seat::WlSeat>,
|
||||
mut callback: F,
|
||||
) -> ThemedPointer
|
||||
where
|
||||
F: FnMut(wl_pointer::Event, ThemedPointer, DispatchData) + 'static,
|
||||
{
|
||||
let surface = self.compositor.create_surface();
|
||||
let inner = Rc::new(RefCell::new(PointerInner {
|
||||
surface: surface.detach(),
|
||||
themes: self.themes.clone(),
|
||||
last_serial: 0,
|
||||
current_cursor: "left_ptr".into(),
|
||||
scale_factor: 1,
|
||||
}));
|
||||
|
||||
let inner2 = inner.clone();
|
||||
let pointer = seat.get_pointer();
|
||||
pointer.quick_assign(move |ptr, event, ddata| {
|
||||
callback(event, ThemedPointer { pointer: ptr.detach(), inner: inner2.clone() }, ddata)
|
||||
});
|
||||
|
||||
let winner = Rc::downgrade(&inner);
|
||||
let my_pointer = pointer.clone();
|
||||
crate::surface::setup_surface(
|
||||
surface,
|
||||
Some(move |scale_factor, _, _: DispatchData| {
|
||||
if let Some(inner) = Weak::upgrade(&winner) {
|
||||
let mut inner = inner.borrow_mut();
|
||||
inner.scale_factor = scale_factor;
|
||||
// we can't handle errors here, so ignore it
|
||||
// worst that can happen is cursor drawn with the wrong
|
||||
// scale factor
|
||||
let _ = inner.update_cursor(&my_pointer);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
ThemedPointer { pointer: pointer.detach(), inner }
|
||||
}
|
||||
}
|
||||
|
||||
struct ScaledThemeList {
|
||||
shm: Attached<wl_shm::WlShm>,
|
||||
name: String,
|
||||
size: u32,
|
||||
themes: Vec<(u32, CursorTheme)>,
|
||||
}
|
||||
|
||||
impl ScaledThemeList {
|
||||
fn new(theme: ThemeSpec, shm: Attached<wl_shm::WlShm>) -> ScaledThemeList {
|
||||
let (name, size) = match theme {
|
||||
ThemeSpec::Precise { name, size } => (name.into(), size),
|
||||
ThemeSpec::System => {
|
||||
let name = std::env::var("XCURSOR_THEME").ok().unwrap_or_else(|| "default".into());
|
||||
let size =
|
||||
std::env::var("XCURSOR_SIZE").ok().and_then(|s| s.parse().ok()).unwrap_or(24);
|
||||
(name, size)
|
||||
}
|
||||
};
|
||||
ScaledThemeList { shm, name, size, themes: vec![] }
|
||||
}
|
||||
|
||||
fn get_cursor(&mut self, name: &str, scale: u32) -> Option<&Cursor> {
|
||||
// Check if we already loaded the theme for this scale factor
|
||||
let opt_index = self.themes.iter().position(|&(s, _)| s == scale);
|
||||
if let Some(idx) = opt_index {
|
||||
self.themes[idx].1.get_cursor(name)
|
||||
} else {
|
||||
let new_theme = CursorTheme::load_from_name(&self.name, self.size * scale, &self.shm);
|
||||
self.themes.push((scale, new_theme));
|
||||
self.themes.last_mut().unwrap().1.get_cursor(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ScaledThemeList {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ScaledThemeList")
|
||||
.field("shm", &self.shm)
|
||||
.field("name", &self.name)
|
||||
.field("size", &self.size)
|
||||
// Wayland-cursor needs to implement debug
|
||||
.field("themes", &"[...]")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PointerInner {
|
||||
surface: wl_surface::WlSurface,
|
||||
themes: Rc<RefCell<ScaledThemeList>>,
|
||||
current_cursor: String,
|
||||
last_serial: u32,
|
||||
scale_factor: i32,
|
||||
}
|
||||
|
||||
impl PointerInner {
|
||||
fn update_cursor(&self, pointer: &wl_pointer::WlPointer) -> Result<(), CursorNotFound> {
|
||||
let mut themes = self.themes.borrow_mut();
|
||||
let scale = self.scale_factor as u32;
|
||||
let cursor = themes.get_cursor(&self.current_cursor, scale).ok_or(CursorNotFound)?;
|
||||
let image = &cursor[0];
|
||||
let (w, h) = image.dimensions();
|
||||
let (hx, hy) = image.hotspot();
|
||||
self.surface.set_buffer_scale(scale as i32);
|
||||
self.surface.attach(Some(image), 0, 0);
|
||||
if self.surface.as_ref().version() >= 4 {
|
||||
self.surface.damage_buffer(0, 0, w as i32, h as i32);
|
||||
} else {
|
||||
// surface is old and does not support damage_buffer, so we damage
|
||||
// in surface coordinates and hope it is not rescaled
|
||||
self.surface.damage(0, 0, w as i32 / scale as i32, h as i32 / scale as i32);
|
||||
}
|
||||
self.surface.commit();
|
||||
pointer.set_cursor(
|
||||
self.last_serial,
|
||||
Some(&self.surface),
|
||||
hx as i32 / scale as i32,
|
||||
hy as i32 / scale as i32,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper of a themed pointer
|
||||
///
|
||||
/// You can access the underlying `wl_pointer::WlPointer` via
|
||||
/// deref. It will *not* release the proxy when dropped.
|
||||
///
|
||||
/// Just like `Proxy`, this is a `Rc`-like wrapper. You can clone it
|
||||
/// to have several handles to the same theming machinery of a pointer.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ThemedPointer {
|
||||
pointer: wl_pointer::WlPointer,
|
||||
inner: Rc<RefCell<PointerInner>>,
|
||||
}
|
||||
|
||||
impl ThemedPointer {
|
||||
/// Change the cursor to the given cursor name
|
||||
///
|
||||
/// Possible names depend on the theme. Does nothing and returns
|
||||
/// `Err` if given name is not available.
|
||||
///
|
||||
/// If this is done as an answer to an input event, you need to provide
|
||||
/// the associated serial otherwise the server may ignore the request.
|
||||
pub fn set_cursor(&self, name: &str, serial: Option<u32>) -> Result<(), CursorNotFound> {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if let Some(s) = serial {
|
||||
inner.last_serial = s;
|
||||
}
|
||||
inner.current_cursor = name.into();
|
||||
inner.update_cursor(&self.pointer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ThemedPointer {
|
||||
type Target = wl_pointer::WlPointer;
|
||||
fn deref(&self) -> &wl_pointer::WlPointer {
|
||||
&self.pointer
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PointerInner {
|
||||
fn drop(&mut self) {
|
||||
self.surface.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// An error representing the fact that the required cursor was not found
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct CursorNotFound;
|
||||
|
||||
impl std::error::Error for CursorNotFound {}
|
||||
|
||||
impl std::fmt::Display for CursorNotFound {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("cursor not found")
|
||||
}
|
||||
}
|
||||
381
third-party/vendor/smithay-client-toolkit/src/shell/mod.rs
vendored
Normal file
381
third-party/vendor/smithay-client-toolkit/src/shell/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
//! Unified shell surface handling
|
||||
//!
|
||||
//! This module provides an abstraction unifying the various iterations of
|
||||
//! the shell surface protocols (`wl_shell`, `zxdg_shell_v6` and `xdg_shell`,
|
||||
//! the current standard).
|
||||
//!
|
||||
//! This abstraction only manages the protocol part of shell surfaces. If you're
|
||||
//! looking for a more battery-included abstraction for creating windows,
|
||||
//! consider the `Window` type.
|
||||
use std::{cell::RefCell, fmt};
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_output, wl_registry, wl_seat, wl_shell, wl_surface},
|
||||
Attached, DispatchData,
|
||||
};
|
||||
|
||||
pub use wayland_protocols::xdg_shell::client::xdg_toplevel::State;
|
||||
use wayland_protocols::{
|
||||
unstable::xdg_shell::v6::client::zxdg_shell_v6,
|
||||
xdg_shell::client::{xdg_toplevel, xdg_wm_base},
|
||||
};
|
||||
|
||||
use crate::environment::{Environment, GlobalHandler};
|
||||
|
||||
mod wl;
|
||||
mod xdg;
|
||||
mod zxdg;
|
||||
|
||||
use crate::lazy_global::LazyGlobal;
|
||||
|
||||
/// Possible events generated by a shell surface that you need to handle
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Event {
|
||||
/// The state of your window has been changed
|
||||
Configure {
|
||||
/// Optional new size for your shell surface
|
||||
///
|
||||
/// This is the new size of the contents of your shell surface
|
||||
/// as suggested by the server. You can ignore it and choose
|
||||
/// a new size if you want better control on the possible
|
||||
/// sizes of your shell surface.
|
||||
///
|
||||
/// In all cases, these events can be generated in large batches
|
||||
/// during an interactive resize, and you should buffer them before
|
||||
/// processing them. You only need to handle the last one of a batch.
|
||||
new_size: Option<(u32, u32)>,
|
||||
/// New combination of states of your window
|
||||
///
|
||||
/// Typically tells you if your surface is active/inactive, maximized,
|
||||
/// etc...
|
||||
states: Vec<State>,
|
||||
},
|
||||
/// A close request has been received
|
||||
///
|
||||
/// Most likely the user has clicked on the close button of the decorations
|
||||
/// or something equivalent
|
||||
Close,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Possible supported shell protocols
|
||||
pub enum Shell {
|
||||
/// The current standard `xdg_shell` protocol.
|
||||
Xdg(Attached<xdg_wm_base::XdgWmBase>),
|
||||
/// A previous iteration of the `xdg_shell` protocol.
|
||||
///
|
||||
/// It has been replaced by the stable `xdg_shell`, and is only present here for
|
||||
/// compatibility purposes.
|
||||
Zxdg(Attached<zxdg_shell_v6::ZxdgShellV6>),
|
||||
/// The legacy `wl_shell`.
|
||||
///
|
||||
/// It is deprecated and only present here for compatibility purposes.
|
||||
Wl(Attached<wl_shell::WlShell>),
|
||||
}
|
||||
|
||||
impl Shell {
|
||||
/// Check if the shell in use needs you to wait for a `configure` event
|
||||
/// before you are allowed to draw.
|
||||
pub fn needs_configure(&self) -> bool {
|
||||
match self {
|
||||
Shell::Wl(_) => false,
|
||||
Shell::Xdg(_) => true,
|
||||
Shell::Zxdg(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_shell_surface<F>(
|
||||
shell: &Shell,
|
||||
surface: &wl_surface::WlSurface,
|
||||
callback: F,
|
||||
) -> Box<dyn ShellSurface>
|
||||
where
|
||||
F: FnMut(Event, DispatchData) + 'static,
|
||||
{
|
||||
match *shell {
|
||||
Shell::Wl(ref shell) => Box::new(wl::Wl::create(surface, shell, callback)) as Box<_>,
|
||||
Shell::Xdg(ref shell) => Box::new(xdg::Xdg::create(surface, shell, callback)) as Box<_>,
|
||||
Shell::Zxdg(ref shell) => Box::new(zxdg::Zxdg::create(surface, shell, callback)) as Box<_>,
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait abstracting over shell surface protocols
|
||||
///
|
||||
/// This trait's API is designed to reflect the behavior of the current standard
|
||||
/// shell surface protocol: `xdg_shell`. Compatibility implementations are
|
||||
/// provided for older protocols.
|
||||
pub trait ShellSurface: fmt::Debug + Send + Sync {
|
||||
/// Resizes the shell surface
|
||||
fn resize(&self, seat: &wl_seat::WlSeat, serial: u32, edges: xdg_toplevel::ResizeEdge);
|
||||
/// Moves the shell surface
|
||||
fn move_(&self, seat: &wl_seat::WlSeat, serial: u32);
|
||||
/// Set the title of the shell surface
|
||||
fn set_title(&self, title: String);
|
||||
/// Set the app id of the shell surface
|
||||
fn set_app_id(&self, app_id: String);
|
||||
/// Make fullscreen
|
||||
fn set_fullscreen(&self, output: Option<&wl_output::WlOutput>);
|
||||
/// Unset fullscreen
|
||||
fn unset_fullscreen(&self);
|
||||
/// Maximize surface
|
||||
fn set_maximized(&self);
|
||||
/// Unmaximize surface
|
||||
fn unset_maximized(&self);
|
||||
/// Minimize surface
|
||||
fn set_minimized(&self);
|
||||
/// Set geometry
|
||||
fn set_geometry(&self, x: i32, y: i32, width: i32, height: i32);
|
||||
/// Set minimum surface size
|
||||
fn set_min_size(&self, size: Option<(i32, i32)>);
|
||||
/// Set maximum surface size
|
||||
fn set_max_size(&self, size: Option<(i32, i32)>);
|
||||
/// Show window menu.
|
||||
fn show_window_menu(&self, seat: &wl_seat::WlSeat, serial: u32, x: i32, y: i32);
|
||||
/// Retrive the `XdgToplevel` proxy if the underlying shell surface
|
||||
/// uses the `xdg_shell` protocol.
|
||||
///
|
||||
/// This allows interactions with other protocol extensions, like
|
||||
/// `xdg_decoratins` for example.
|
||||
fn get_xdg(&self) -> Option<&xdg_toplevel::XdgToplevel>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ShellInner {
|
||||
registry: Option<Attached<wl_registry::WlRegistry>>,
|
||||
wl_shell: LazyGlobal<wl_shell::WlShell>,
|
||||
xdg_shell: LazyGlobal<xdg_wm_base::XdgWmBase>,
|
||||
zxdg_shell: LazyGlobal<zxdg_shell_v6::ZxdgShellV6>,
|
||||
}
|
||||
|
||||
/// A handler for shells
|
||||
///
|
||||
/// For use with the [`environment!`](../macro.environment.html) macro. It is already
|
||||
/// automatically included if you use the [`default_environment!`](../macro.default_environment.hmtl).
|
||||
///
|
||||
/// To use it, you need to set it as a handler for the shells you want to support (`xdg_wm_base`,
|
||||
/// `zxdg_shell_v6` and/or `wl_shell`). You can then implement the
|
||||
/// [`ShellHandling`](trait.ShellHandling.html) by delegating it, to get the shell-related methods on
|
||||
/// [`Environment`](../environment/struct.environment.html)
|
||||
///
|
||||
/// ```no_run
|
||||
/// # extern crate smithay_client_toolkit as sctk;
|
||||
/// # use sctk::environment;
|
||||
/// # use sctk::environment::Environment;
|
||||
/// # use sctk::shell::*;
|
||||
/// # use sctk::reexports::client::protocol::wl_shell;
|
||||
/// # use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base;
|
||||
/// # use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6;
|
||||
/// # let display = sctk::reexports::client::Display::connect_to_env().unwrap();
|
||||
/// # let mut queue = display.create_event_queue();
|
||||
/// # let attached_display = display.attach(queue.token());
|
||||
/// struct MyEnv {
|
||||
/// my_shell: ShellHandler
|
||||
/// }
|
||||
///
|
||||
/// environment!(MyEnv,
|
||||
/// singles=[
|
||||
/// wl_shell::WlShell => my_shell,
|
||||
/// xdg_wm_base::XdgWmBase => my_shell,
|
||||
/// zxdg_shell_v6::ZxdgShellV6 => my_shell
|
||||
/// ],
|
||||
/// multis=[],
|
||||
/// );
|
||||
///
|
||||
/// impl ShellHandling for MyEnv {
|
||||
/// fn get_shell(&self) -> Option<Shell> {
|
||||
/// // delegate the impl to the stored handler
|
||||
/// self.my_shell.get_shell()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let env = Environment::new(&attached_display, &mut queue, MyEnv {
|
||||
/// my_shell: ShellHandler::new()
|
||||
/// });
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct ShellHandler {
|
||||
inner: RefCell<ShellInner>,
|
||||
}
|
||||
|
||||
impl ShellHandler {
|
||||
/// Create a new handler
|
||||
pub fn new() -> ShellHandler {
|
||||
ShellHandler {
|
||||
inner: RefCell::new(ShellInner {
|
||||
registry: None,
|
||||
wl_shell: LazyGlobal::Unknown,
|
||||
xdg_shell: LazyGlobal::Unknown,
|
||||
zxdg_shell: LazyGlobal::Unknown,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalHandler<wl_shell::WlShell> for ShellHandler {
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
_: DispatchData,
|
||||
) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if inner.registry.is_none() {
|
||||
inner.registry = Some(registry);
|
||||
}
|
||||
if let LazyGlobal::Unknown = inner.wl_shell {
|
||||
inner.wl_shell = LazyGlobal::Seen { id, version };
|
||||
} else {
|
||||
log::warn!("Compositor advertised wl_shell multiple times, ignoring.")
|
||||
}
|
||||
}
|
||||
fn get(&self) -> Option<Attached<wl_shell::WlShell>> {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
match inner.wl_shell {
|
||||
LazyGlobal::Bound(ref shell) => Some(shell.clone()),
|
||||
LazyGlobal::Unknown => None,
|
||||
LazyGlobal::Seen { id, .. } => {
|
||||
// registry cannot be None if we have seen the global
|
||||
let registry = inner.registry.as_ref().unwrap();
|
||||
// only version 1 of wl_shell is supported
|
||||
let shell = registry.bind::<wl_shell::WlShell>(1, id);
|
||||
inner.wl_shell = LazyGlobal::Bound((*shell).clone());
|
||||
Some((*shell).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalHandler<xdg_wm_base::XdgWmBase> for ShellHandler {
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
_: DispatchData,
|
||||
) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if inner.registry.is_none() {
|
||||
inner.registry = Some(registry);
|
||||
}
|
||||
if let LazyGlobal::Unknown = inner.xdg_shell {
|
||||
inner.xdg_shell = LazyGlobal::Seen { id, version };
|
||||
} else {
|
||||
log::warn!("Compositor advertised xdg_wm_base multiple times, ignoring.")
|
||||
}
|
||||
}
|
||||
fn get(&self) -> Option<Attached<xdg_wm_base::XdgWmBase>> {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
match inner.xdg_shell {
|
||||
LazyGlobal::Bound(ref shell) => Some(shell.clone()),
|
||||
LazyGlobal::Unknown => None,
|
||||
LazyGlobal::Seen { version, id } => {
|
||||
// registry cannot be None if we have seen the global
|
||||
let registry = inner.registry.as_ref().unwrap();
|
||||
// we currently support xdg_shell up to version 2
|
||||
let version = std::cmp::min(2, version);
|
||||
let shell = registry.bind::<xdg_wm_base::XdgWmBase>(version, id);
|
||||
shell.quick_assign(|shell, event, _| {
|
||||
if let xdg_wm_base::Event::Ping { serial } = event {
|
||||
shell.pong(serial);
|
||||
}
|
||||
});
|
||||
inner.xdg_shell = LazyGlobal::Bound((*shell).clone());
|
||||
Some((*shell).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalHandler<zxdg_shell_v6::ZxdgShellV6> for ShellHandler {
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
version: u32,
|
||||
_: DispatchData,
|
||||
) {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if inner.registry.is_none() {
|
||||
inner.registry = Some(registry);
|
||||
}
|
||||
if let LazyGlobal::Unknown = inner.zxdg_shell {
|
||||
inner.zxdg_shell = LazyGlobal::Seen { id, version };
|
||||
} else {
|
||||
log::warn!("Compositor advertised zxdg_shell_v6 multiple times, ignoring.")
|
||||
}
|
||||
}
|
||||
fn get(&self) -> Option<Attached<zxdg_shell_v6::ZxdgShellV6>> {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
match inner.zxdg_shell {
|
||||
LazyGlobal::Bound(ref shell) => Some(shell.clone()),
|
||||
LazyGlobal::Unknown => None,
|
||||
LazyGlobal::Seen { id, .. } => {
|
||||
// registry cannot be None if we have seen the global
|
||||
let registry = inner.registry.as_ref().unwrap();
|
||||
// only version 1 of zxdg_shell_v6 is supported
|
||||
let shell = registry.bind::<zxdg_shell_v6::ZxdgShellV6>(1, id);
|
||||
shell.quick_assign(|shell, event, _| {
|
||||
if let zxdg_shell_v6::Event::Ping { serial } = event {
|
||||
shell.pong(serial);
|
||||
}
|
||||
});
|
||||
inner.zxdg_shell = LazyGlobal::Bound((*shell).clone());
|
||||
Some((*shell).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellHandling for ShellHandler {
|
||||
fn get_shell(&self) -> Option<Shell> {
|
||||
GlobalHandler::<xdg_wm_base::XdgWmBase>::get(self)
|
||||
.map(Shell::Xdg)
|
||||
.or_else(|| GlobalHandler::<zxdg_shell_v6::ZxdgShellV6>::get(self).map(Shell::Zxdg))
|
||||
.or_else(|| GlobalHandler::<wl_shell::WlShell>::get(self).map(Shell::Wl))
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trait for delegating shell handling
|
||||
///
|
||||
/// If you don't use [`declare_default_environment!`](../macro.declare_default_environment.html) but still
|
||||
/// want to use the shell helpers provided here, you need to implement this trait for your
|
||||
/// [`declare_environment!`](../macro.declare_environment.html)-generated type, by delegating it to one
|
||||
/// of the handlers you provided for the different shells.
|
||||
pub trait ShellHandling {
|
||||
/// Get the best available shell
|
||||
fn get_shell(&self) -> Option<Shell>;
|
||||
}
|
||||
|
||||
impl<E: ShellHandling> Environment<E> {
|
||||
/// Get the best available shell protocol
|
||||
///
|
||||
/// Returns `None` if no shell was advertised.
|
||||
pub fn get_shell(&self) -> Option<Shell> {
|
||||
self.with_inner(|extras| extras.get_shell())
|
||||
}
|
||||
/// Create a new shell surface for this surface
|
||||
///
|
||||
/// This helper abstracts over the `xdg_shell` protocol and its precursors (`zxdg_shell_v6`
|
||||
/// and `wl_shell`) for retro-compatibility. It'll attempt to use them in this order.
|
||||
///
|
||||
/// You need to provide a closure that will process the events generated by the shell surface.
|
||||
///
|
||||
/// *Panic*
|
||||
///
|
||||
/// This function will panic if no supported shell was advertised by the compositor.
|
||||
pub fn create_shell_surface<F>(
|
||||
&self,
|
||||
surface: &wl_surface::WlSurface,
|
||||
f: F,
|
||||
) -> Box<dyn ShellSurface>
|
||||
where
|
||||
F: FnMut(Event, DispatchData) + 'static,
|
||||
{
|
||||
let shell = self
|
||||
.get_shell()
|
||||
.expect("SCTK: trying to create a shell surface without any supported shell.");
|
||||
create_shell_surface(&shell, surface, f)
|
||||
}
|
||||
}
|
||||
116
third-party/vendor/smithay-client-toolkit/src/shell/wl.rs
vendored
Normal file
116
third-party/vendor/smithay-client-toolkit/src/shell/wl.rs
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
use wayland_client::{
|
||||
protocol::{wl_output, wl_seat, wl_shell, wl_shell_surface, wl_surface},
|
||||
DispatchData,
|
||||
};
|
||||
|
||||
use wayland_protocols::xdg_shell::client::xdg_toplevel;
|
||||
|
||||
use super::{Event, ShellSurface};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Wl {
|
||||
shell_surface: wl_shell_surface::WlShellSurface,
|
||||
}
|
||||
|
||||
impl Wl {
|
||||
pub(crate) fn create<Impl>(
|
||||
surface: &wl_surface::WlSurface,
|
||||
shell: &wl_shell::WlShell,
|
||||
mut implementation: Impl,
|
||||
) -> Wl
|
||||
where
|
||||
Impl: FnMut(Event, DispatchData) + 'static,
|
||||
{
|
||||
let shell_surface = shell.get_shell_surface(surface);
|
||||
shell_surface.quick_assign(move |shell_surface, event, ddata| match event {
|
||||
wl_shell_surface::Event::Ping { serial } => {
|
||||
shell_surface.pong(serial);
|
||||
}
|
||||
wl_shell_surface::Event::Configure { width, height, .. } => {
|
||||
use std::cmp::max;
|
||||
implementation(
|
||||
Event::Configure {
|
||||
new_size: Some((max(width, 1) as u32, max(height, 1) as u32)),
|
||||
states: Vec::new(),
|
||||
},
|
||||
ddata,
|
||||
);
|
||||
}
|
||||
wl_shell_surface::Event::PopupDone => {
|
||||
unreachable!();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
shell_surface.set_toplevel();
|
||||
Wl { shell_surface: shell_surface.detach() }
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellSurface for Wl {
|
||||
fn resize(&self, seat: &wl_seat::WlSeat, serial: u32, edges: xdg_toplevel::ResizeEdge) {
|
||||
let edges = match edges {
|
||||
xdg_toplevel::ResizeEdge::None => wl_shell_surface::Resize::None,
|
||||
xdg_toplevel::ResizeEdge::Top => wl_shell_surface::Resize::Top,
|
||||
xdg_toplevel::ResizeEdge::Left => wl_shell_surface::Resize::Left,
|
||||
xdg_toplevel::ResizeEdge::Right => wl_shell_surface::Resize::Right,
|
||||
xdg_toplevel::ResizeEdge::Bottom => wl_shell_surface::Resize::Bottom,
|
||||
xdg_toplevel::ResizeEdge::TopLeft => wl_shell_surface::Resize::TopLeft,
|
||||
xdg_toplevel::ResizeEdge::TopRight => wl_shell_surface::Resize::TopRight,
|
||||
xdg_toplevel::ResizeEdge::BottomLeft => wl_shell_surface::Resize::BottomLeft,
|
||||
xdg_toplevel::ResizeEdge::BottomRight => wl_shell_surface::Resize::BottomRight,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.shell_surface.resize(seat, serial, edges);
|
||||
}
|
||||
|
||||
fn move_(&self, seat: &wl_seat::WlSeat, serial: u32) {
|
||||
self.shell_surface._move(seat, serial);
|
||||
}
|
||||
|
||||
fn set_title(&self, title: String) {
|
||||
self.shell_surface.set_title(title);
|
||||
}
|
||||
|
||||
fn set_app_id(&self, app_id: String) {
|
||||
self.shell_surface.set_class(app_id);
|
||||
}
|
||||
fn set_fullscreen(&self, output: Option<&wl_output::WlOutput>) {
|
||||
self.shell_surface.set_fullscreen(wl_shell_surface::FullscreenMethod::Default, 0, output)
|
||||
}
|
||||
|
||||
fn unset_fullscreen(&self) {
|
||||
self.shell_surface.set_toplevel();
|
||||
}
|
||||
|
||||
fn set_maximized(&self) {
|
||||
self.shell_surface.set_maximized(None);
|
||||
}
|
||||
|
||||
fn unset_maximized(&self) {
|
||||
self.shell_surface.set_toplevel();
|
||||
}
|
||||
|
||||
fn show_window_menu(&self, _: &wl_seat::WlSeat, _: u32, _: i32, _: i32) {
|
||||
/* not available */
|
||||
}
|
||||
|
||||
fn set_minimized(&self) {
|
||||
/* not available */
|
||||
}
|
||||
|
||||
fn set_geometry(&self, _: i32, _: i32, _: i32, _: i32) {
|
||||
/* not available */
|
||||
}
|
||||
|
||||
fn set_min_size(&self, _: Option<(i32, i32)>) {
|
||||
/* not available */
|
||||
}
|
||||
|
||||
fn set_max_size(&self, _: Option<(i32, i32)>) {
|
||||
/* not available */
|
||||
}
|
||||
|
||||
fn get_xdg(&self) -> Option<&xdg_toplevel::XdgToplevel> {
|
||||
None
|
||||
}
|
||||
}
|
||||
141
third-party/vendor/smithay-client-toolkit/src/shell/xdg.rs
vendored
Normal file
141
third-party/vendor/smithay-client-toolkit/src/shell/xdg.rs
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
use std::{cell::RefCell, convert::TryInto, rc::Rc};
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_output, wl_seat, wl_surface},
|
||||
DispatchData,
|
||||
};
|
||||
|
||||
use wayland_protocols::xdg_shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base};
|
||||
|
||||
use super::{Event, ShellSurface};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Xdg {
|
||||
surface: xdg_surface::XdgSurface,
|
||||
toplevel: xdg_toplevel::XdgToplevel,
|
||||
}
|
||||
|
||||
impl Xdg {
|
||||
pub(crate) fn create<Impl>(
|
||||
surface: &wl_surface::WlSurface,
|
||||
shell: &xdg_wm_base::XdgWmBase,
|
||||
implementation: Impl,
|
||||
) -> Xdg
|
||||
where
|
||||
Impl: FnMut(Event, DispatchData) + 'static,
|
||||
{
|
||||
let pending_configure = Rc::new(RefCell::new(None));
|
||||
let pending_configure_2 = pending_configure.clone();
|
||||
|
||||
let implementation = Rc::new(RefCell::new(implementation));
|
||||
let implementation_2 = implementation.clone();
|
||||
let xdgs = shell.get_xdg_surface(surface);
|
||||
xdgs.quick_assign(move |xdgs, evt, ddata| match evt {
|
||||
xdg_surface::Event::Configure { serial } => {
|
||||
xdgs.ack_configure(serial);
|
||||
if let Some((new_size, states)) = pending_configure_2.borrow_mut().take() {
|
||||
(implementation_2.borrow_mut())(Event::Configure { new_size, states }, ddata);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
let toplevel = xdgs.get_toplevel();
|
||||
toplevel.quick_assign(move |_, evt, ddata| {
|
||||
match evt {
|
||||
xdg_toplevel::Event::Close => (implementation.borrow_mut())(Event::Close, ddata),
|
||||
xdg_toplevel::Event::Configure { width, height, states } => {
|
||||
use std::cmp::max;
|
||||
let new_size = if width == 0 || height == 0 {
|
||||
// if either w or h is zero, then we get to choose our size
|
||||
None
|
||||
} else {
|
||||
Some((max(width, 1) as u32, max(height, 1) as u32))
|
||||
};
|
||||
let translated_states = states
|
||||
.chunks_exact(4)
|
||||
.map(|c| u32::from_ne_bytes(c.try_into().unwrap()))
|
||||
.flat_map(xdg_toplevel::State::from_raw)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
*pending_configure.borrow_mut() = Some((new_size, translated_states));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
surface.commit();
|
||||
Xdg { surface: xdgs.detach(), toplevel: toplevel.detach() }
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellSurface for Xdg {
|
||||
fn resize(&self, seat: &wl_seat::WlSeat, serial: u32, edges: xdg_toplevel::ResizeEdge) {
|
||||
self.toplevel.resize(seat, serial, edges);
|
||||
}
|
||||
|
||||
fn move_(&self, seat: &wl_seat::WlSeat, serial: u32) {
|
||||
self.toplevel._move(seat, serial);
|
||||
}
|
||||
|
||||
fn set_title(&self, title: String) {
|
||||
self.toplevel.set_title(title);
|
||||
}
|
||||
|
||||
fn set_app_id(&self, app_id: String) {
|
||||
self.toplevel.set_app_id(app_id);
|
||||
}
|
||||
|
||||
fn set_fullscreen(&self, output: Option<&wl_output::WlOutput>) {
|
||||
self.toplevel.set_fullscreen(output)
|
||||
}
|
||||
|
||||
fn unset_fullscreen(&self) {
|
||||
self.toplevel.unset_fullscreen();
|
||||
}
|
||||
|
||||
fn set_maximized(&self) {
|
||||
self.toplevel.set_maximized();
|
||||
}
|
||||
|
||||
fn unset_maximized(&self) {
|
||||
self.toplevel.unset_maximized();
|
||||
}
|
||||
|
||||
fn set_minimized(&self) {
|
||||
self.toplevel.set_minimized();
|
||||
}
|
||||
|
||||
fn show_window_menu(&self, seat: &wl_seat::WlSeat, serial: u32, x: i32, y: i32) {
|
||||
self.toplevel.show_window_menu(seat, serial, x, y);
|
||||
}
|
||||
|
||||
fn set_geometry(&self, x: i32, y: i32, width: i32, height: i32) {
|
||||
self.surface.set_window_geometry(x, y, width, height);
|
||||
}
|
||||
|
||||
fn set_min_size(&self, size: Option<(i32, i32)>) {
|
||||
if let Some((w, h)) = size {
|
||||
self.toplevel.set_min_size(w, h);
|
||||
} else {
|
||||
self.toplevel.set_min_size(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_max_size(&self, size: Option<(i32, i32)>) {
|
||||
if let Some((w, h)) = size {
|
||||
self.toplevel.set_max_size(w, h);
|
||||
} else {
|
||||
self.toplevel.set_max_size(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_xdg(&self) -> Option<&xdg_toplevel::XdgToplevel> {
|
||||
Some(&self.toplevel)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Xdg {
|
||||
fn drop(&mut self) {
|
||||
self.toplevel.destroy();
|
||||
self.surface.destroy();
|
||||
}
|
||||
}
|
||||
146
third-party/vendor/smithay-client-toolkit/src/shell/zxdg.rs
vendored
Normal file
146
third-party/vendor/smithay-client-toolkit/src/shell/zxdg.rs
vendored
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
use std::{cell::RefCell, convert::TryInto, rc::Rc};
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_output, wl_seat, wl_surface},
|
||||
DispatchData,
|
||||
};
|
||||
|
||||
use wayland_protocols::{
|
||||
unstable::xdg_shell::v6::client::{zxdg_shell_v6, zxdg_surface_v6, zxdg_toplevel_v6},
|
||||
xdg_shell::client::xdg_toplevel,
|
||||
};
|
||||
|
||||
use super::{Event, ShellSurface};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Zxdg {
|
||||
surface: zxdg_surface_v6::ZxdgSurfaceV6,
|
||||
toplevel: zxdg_toplevel_v6::ZxdgToplevelV6,
|
||||
}
|
||||
|
||||
impl Zxdg {
|
||||
pub(crate) fn create<Impl>(
|
||||
surface: &wl_surface::WlSurface,
|
||||
shell: &zxdg_shell_v6::ZxdgShellV6,
|
||||
implementation: Impl,
|
||||
) -> Zxdg
|
||||
where
|
||||
Impl: FnMut(Event, DispatchData) + 'static,
|
||||
{
|
||||
let pending_configure = Rc::new(RefCell::new(None));
|
||||
let pending_configure_2 = pending_configure.clone();
|
||||
|
||||
let implementation = Rc::new(RefCell::new(implementation));
|
||||
let implementation_2 = implementation.clone();
|
||||
let xdgs = shell.get_xdg_surface(surface);
|
||||
xdgs.quick_assign(move |xdgs, evt, ddata| match evt {
|
||||
zxdg_surface_v6::Event::Configure { serial } => {
|
||||
xdgs.ack_configure(serial);
|
||||
if let Some((new_size, states)) = pending_configure_2.borrow_mut().take() {
|
||||
(implementation_2.borrow_mut())(Event::Configure { new_size, states }, ddata);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
let toplevel = xdgs.get_toplevel();
|
||||
toplevel.quick_assign(move |_, evt, ddata| {
|
||||
match evt {
|
||||
zxdg_toplevel_v6::Event::Close => {
|
||||
(implementation.borrow_mut())(Event::Close, ddata)
|
||||
}
|
||||
zxdg_toplevel_v6::Event::Configure { width, height, states } => {
|
||||
use std::cmp::max;
|
||||
let new_size = if width == 0 || height == 0 {
|
||||
// if either w or h is zero, then we get to choose our size
|
||||
None
|
||||
} else {
|
||||
Some((max(width, 1) as u32, max(height, 1) as u32))
|
||||
};
|
||||
let translated_states = states
|
||||
.chunks_exact(4)
|
||||
.map(|c| u32::from_ne_bytes(c.try_into().unwrap()))
|
||||
.flat_map(xdg_toplevel::State::from_raw)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
*pending_configure.borrow_mut() = Some((new_size, translated_states));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
surface.commit();
|
||||
Zxdg { surface: xdgs.detach(), toplevel: toplevel.detach() }
|
||||
}
|
||||
}
|
||||
|
||||
impl ShellSurface for Zxdg {
|
||||
fn resize(&self, seat: &wl_seat::WlSeat, serial: u32, edges: xdg_toplevel::ResizeEdge) {
|
||||
self.toplevel.resize(seat, serial, edges as u32);
|
||||
}
|
||||
|
||||
fn move_(&self, seat: &wl_seat::WlSeat, serial: u32) {
|
||||
self.toplevel._move(seat, serial);
|
||||
}
|
||||
|
||||
fn set_title(&self, title: String) {
|
||||
self.toplevel.set_title(title);
|
||||
}
|
||||
|
||||
fn set_app_id(&self, app_id: String) {
|
||||
self.toplevel.set_app_id(app_id);
|
||||
}
|
||||
|
||||
fn set_fullscreen(&self, output: Option<&wl_output::WlOutput>) {
|
||||
self.toplevel.set_fullscreen(output)
|
||||
}
|
||||
|
||||
fn unset_fullscreen(&self) {
|
||||
self.toplevel.unset_fullscreen();
|
||||
}
|
||||
|
||||
fn set_maximized(&self) {
|
||||
self.toplevel.set_maximized();
|
||||
}
|
||||
|
||||
fn unset_maximized(&self) {
|
||||
self.toplevel.unset_maximized();
|
||||
}
|
||||
|
||||
fn set_minimized(&self) {
|
||||
self.toplevel.set_minimized();
|
||||
}
|
||||
|
||||
fn show_window_menu(&self, seat: &wl_seat::WlSeat, serial: u32, x: i32, y: i32) {
|
||||
self.toplevel.show_window_menu(seat, serial, x, y);
|
||||
}
|
||||
|
||||
fn set_geometry(&self, x: i32, y: i32, width: i32, height: i32) {
|
||||
self.surface.set_window_geometry(x, y, width, height);
|
||||
}
|
||||
|
||||
fn set_min_size(&self, size: Option<(i32, i32)>) {
|
||||
if let Some((w, h)) = size {
|
||||
self.toplevel.set_min_size(w, h);
|
||||
} else {
|
||||
self.toplevel.set_min_size(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_max_size(&self, size: Option<(i32, i32)>) {
|
||||
if let Some((w, h)) = size {
|
||||
self.toplevel.set_max_size(w, h);
|
||||
} else {
|
||||
self.toplevel.set_max_size(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_xdg(&self) -> Option<&xdg_toplevel::XdgToplevel> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Zxdg {
|
||||
fn drop(&mut self) {
|
||||
self.toplevel.destroy();
|
||||
self.surface.destroy();
|
||||
}
|
||||
}
|
||||
557
third-party/vendor/smithay-client-toolkit/src/shm/mempool.rs
vendored
Normal file
557
third-party/vendor/smithay-client-toolkit/src/shm/mempool.rs
vendored
Normal file
|
|
@ -0,0 +1,557 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::CStr,
|
||||
fmt,
|
||||
fs::File,
|
||||
io,
|
||||
os::unix::io::{FromRawFd, RawFd},
|
||||
rc::Rc,
|
||||
time::SystemTime,
|
||||
time::UNIX_EPOCH,
|
||||
};
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use nix::sys::memfd;
|
||||
use nix::{
|
||||
errno::Errno,
|
||||
fcntl,
|
||||
sys::{mman, stat},
|
||||
unistd,
|
||||
};
|
||||
|
||||
use memmap2::MmapMut;
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_buffer, wl_shm, wl_shm_pool},
|
||||
Attached, Main,
|
||||
};
|
||||
|
||||
/// A Double memory pool, for convenient double-buffering
|
||||
///
|
||||
/// This type wraps two internal memory pool, and can be
|
||||
/// use for conveniently implementing double-buffering in your
|
||||
/// apps.
|
||||
///
|
||||
/// DoubleMemPool requires a implementation that is called when
|
||||
/// one of the two internal memory pools becomes free after None
|
||||
/// was returned from the `pool()` method.
|
||||
#[derive(Debug)]
|
||||
pub struct DoubleMemPool {
|
||||
pool1: MemPool,
|
||||
pool2: MemPool,
|
||||
free: Rc<RefCell<bool>>,
|
||||
}
|
||||
|
||||
impl DoubleMemPool {
|
||||
/// Create a double memory pool
|
||||
pub fn new<F>(shm: Attached<wl_shm::WlShm>, callback: F) -> io::Result<DoubleMemPool>
|
||||
where
|
||||
F: FnMut(wayland_client::DispatchData) + 'static,
|
||||
{
|
||||
let free = Rc::new(RefCell::new(true));
|
||||
let callback = Rc::new(RefCell::new(callback));
|
||||
let my_free = free.clone();
|
||||
let my_callback = callback.clone();
|
||||
let pool1 = MemPool::new(shm.clone(), move |ddata| {
|
||||
let signal = {
|
||||
let mut my_free = my_free.borrow_mut();
|
||||
if !*my_free {
|
||||
*my_free = true;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if signal {
|
||||
(my_callback.borrow_mut())(ddata);
|
||||
}
|
||||
})?;
|
||||
let my_free = free.clone();
|
||||
let pool2 = MemPool::new(shm, move |ddata| {
|
||||
let signal = {
|
||||
let mut my_free = my_free.borrow_mut();
|
||||
if !*my_free {
|
||||
*my_free = true;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if signal {
|
||||
(callback.borrow_mut())(ddata);
|
||||
}
|
||||
})?;
|
||||
Ok(DoubleMemPool { pool1, pool2, free })
|
||||
}
|
||||
|
||||
/// This method checks both its internal memory pools and returns
|
||||
/// one if that pool does not contain any buffers that are still in use
|
||||
/// by the server. If both the memory pools contain buffers that are currently
|
||||
/// in use by the server None will be returned.
|
||||
pub fn pool(&mut self) -> Option<&mut MemPool> {
|
||||
if !self.pool1.is_used() {
|
||||
Some(&mut self.pool1)
|
||||
} else if !self.pool2.is_used() {
|
||||
Some(&mut self.pool2)
|
||||
} else {
|
||||
*self.free.borrow_mut() = false;
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
file: File,
|
||||
len: usize,
|
||||
pool: Main<wl_shm_pool::WlShmPool>,
|
||||
mmap: MmapMut,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn new(shm: Attached<wl_shm::WlShm>) -> io::Result<Self> {
|
||||
let mem_fd = create_shm_fd()?;
|
||||
let mem_file = unsafe { File::from_raw_fd(mem_fd) };
|
||||
mem_file.set_len(4096)?;
|
||||
|
||||
let pool = shm.create_pool(mem_fd, 4096);
|
||||
|
||||
let mmap = unsafe { MmapMut::map_mut(&mem_file).unwrap() };
|
||||
|
||||
Ok(Inner { file: mem_file, len: 4096, pool, mmap })
|
||||
}
|
||||
|
||||
fn resize(&mut self, newsize: usize) -> io::Result<()> {
|
||||
if newsize > self.len {
|
||||
self.file.set_len(newsize as u64)?;
|
||||
self.pool.resize(newsize as i32);
|
||||
self.len = newsize;
|
||||
self.mmap = unsafe { MmapMut::map_mut(&self.file).unwrap() };
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
self.pool.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper handling an SHM memory pool backed by a shared memory file
|
||||
///
|
||||
/// This wrapper handles for you the creation of the shared memory file and its synchronization
|
||||
/// with the protocol.
|
||||
///
|
||||
/// Mempool internally tracks the release of the buffers by the compositor. As such, creating a buffer
|
||||
/// that is not commited to a surface (and then never released by the server) would cause the Mempool
|
||||
/// to be stuck believing it is still in use.
|
||||
///
|
||||
/// Mempool will also handle the destruction of buffers and as such the `destroy()` method should not
|
||||
/// be used on buffers created from Mempool.
|
||||
///
|
||||
/// Overwriting the contents of the memory pool before it is completely freed may cause graphical
|
||||
/// glitches due to the possible corruption of data while the compositor is reading it.
|
||||
///
|
||||
/// Mempool requires a callback that will be called when the pool becomes free, this
|
||||
/// happens when all the pools buffers are released by the server.
|
||||
pub struct MemPool {
|
||||
inner: Inner,
|
||||
buffer_count: Rc<RefCell<u32>>,
|
||||
callback: Rc<RefCell<dyn FnMut(wayland_client::DispatchData)>>,
|
||||
}
|
||||
|
||||
impl MemPool {
|
||||
/// Create a new memory pool associated with given shm
|
||||
pub fn new<F>(shm: Attached<wl_shm::WlShm>, callback: F) -> io::Result<MemPool>
|
||||
where
|
||||
F: FnMut(wayland_client::DispatchData) + 'static,
|
||||
{
|
||||
Ok(MemPool {
|
||||
inner: Inner::new(shm)?,
|
||||
buffer_count: Rc::new(RefCell::new(0)),
|
||||
callback: Rc::new(RefCell::new(callback)) as Rc<RefCell<_>>,
|
||||
})
|
||||
}
|
||||
|
||||
/// Resize the memory pool
|
||||
///
|
||||
/// This affect the size as it is seen by the wayland server. Even
|
||||
/// if you extend the temporary file size by writing to it, you need to
|
||||
/// call this method otherwise the server won't see the new size.
|
||||
///
|
||||
/// Memory pools can only be extented, as such this method will do nothing
|
||||
/// if the requested new size is smaller than the current size.
|
||||
///
|
||||
/// This method allows you to ensure the underlying pool is large enough to
|
||||
/// hold what you want to write to it.
|
||||
pub fn resize(&mut self, newsize: usize) -> io::Result<()> {
|
||||
self.inner.resize(newsize)
|
||||
}
|
||||
|
||||
/// Create a new buffer to this pool
|
||||
///
|
||||
/// The parameters are:
|
||||
///
|
||||
/// - `offset`: the offset (in bytes) from the beginning of the pool at which this
|
||||
/// buffer starts
|
||||
/// - `width`: the width of this buffer (in pixels)
|
||||
/// - `height`: the height of this buffer (in pixels)
|
||||
/// - `stride`: distance (in bytes) between the beginning of a row and the next one
|
||||
/// - `format`: the encoding format of the pixels. Using a format that was not
|
||||
/// advertised to the `wl_shm` global by the server is a protocol error and will
|
||||
/// terminate your connection
|
||||
pub fn buffer(
|
||||
&self,
|
||||
offset: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
stride: i32,
|
||||
format: wl_shm::Format,
|
||||
) -> wl_buffer::WlBuffer {
|
||||
*self.buffer_count.borrow_mut() += 1;
|
||||
let my_buffer_count = self.buffer_count.clone();
|
||||
let my_callback = self.callback.clone();
|
||||
let buffer = self.inner.pool.create_buffer(offset, width, height, stride, format);
|
||||
buffer.quick_assign(move |buffer, event, dispatch_data| match event {
|
||||
wl_buffer::Event::Release => {
|
||||
buffer.destroy();
|
||||
let new_count = {
|
||||
// borrow the buffer_count for as short as possible, in case
|
||||
// the user wants to create a new buffer from the callback
|
||||
let mut my_buffer_count = my_buffer_count.borrow_mut();
|
||||
*my_buffer_count -= 1;
|
||||
*my_buffer_count
|
||||
};
|
||||
if new_count == 0 {
|
||||
(my_callback.borrow_mut())(dispatch_data);
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
(*buffer).clone().detach()
|
||||
}
|
||||
|
||||
/// Uses the memmap2 crate to map the underlying shared memory file
|
||||
pub fn mmap(&mut self) -> &mut MmapMut {
|
||||
&mut self.inner.mmap
|
||||
}
|
||||
|
||||
/// Returns true if the pool contains buffers that are currently in use by the server
|
||||
pub fn is_used(&self) -> bool {
|
||||
*self.buffer_count.borrow() != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MemPool {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MemPool")
|
||||
.field("inner", &self.inner)
|
||||
.field("buffer_count", &self.buffer_count)
|
||||
.field("callback", &"Fn() -> { ... }")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for MemPool {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
io::Write::write(&mut self.inner.file, buf)
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
io::Write::flush(&mut self.inner.file)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Seek for MemPool {
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||
io::Seek::seek(&mut self.inner.file, pos)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper handling an SHM memory pool backed by a shared memory file
|
||||
///
|
||||
/// This wrapper handles the creation of the shared memory file, its synchronization with the
|
||||
/// protocol, and the allocation of buffers within the pool.
|
||||
///
|
||||
/// AutoMemPool internally tracks the release of the buffers by the compositor. As such, creating a
|
||||
/// buffer that is not committed to a surface (and then never released by the server) would result
|
||||
/// in that memory being unavailable for the rest of the pool's lifetime.
|
||||
///
|
||||
/// AutoMemPool will also handle the destruction of buffers; do not call destroy() on the returned
|
||||
/// WlBuffer objects.
|
||||
///
|
||||
/// The default alignment of returned buffers is 16 bytes; this can be changed by using the
|
||||
/// explicit with_min_align constructor.
|
||||
#[derive(Debug)]
|
||||
pub struct AutoMemPool {
|
||||
inner: Inner,
|
||||
align: usize,
|
||||
free_list: Rc<RefCell<Vec<(usize, usize)>>>,
|
||||
}
|
||||
|
||||
impl AutoMemPool {
|
||||
/// Create a new memory pool associated with the given shm
|
||||
pub fn new(shm: Attached<wl_shm::WlShm>) -> io::Result<AutoMemPool> {
|
||||
Self::with_min_align(shm, 16)
|
||||
}
|
||||
|
||||
/// Create a new memory pool associated with the given shm.
|
||||
///
|
||||
/// All buffers will be aligned to at least the value of (align), which must be a power of two
|
||||
/// not greater than 4096.
|
||||
pub fn with_min_align(shm: Attached<wl_shm::WlShm>, align: usize) -> io::Result<AutoMemPool> {
|
||||
assert!(align.is_power_of_two());
|
||||
assert!(align <= 4096);
|
||||
let inner = Inner::new(shm)?;
|
||||
let free_list = Rc::new(RefCell::new(vec![(0, inner.len)]));
|
||||
Ok(AutoMemPool { inner, align, free_list })
|
||||
}
|
||||
|
||||
/// Resize the memory pool
|
||||
///
|
||||
/// This is normally done automatically, but can be used to avoid multiple resizes.
|
||||
pub fn resize(&mut self, new_size: usize) -> io::Result<()> {
|
||||
let old_size = self.inner.len;
|
||||
if old_size >= new_size {
|
||||
return Ok(());
|
||||
}
|
||||
self.inner.resize(new_size)?;
|
||||
// add the new memory to the freelist
|
||||
let mut free = self.free_list.borrow_mut();
|
||||
if let Some((off, len)) = free.last_mut() {
|
||||
if *off + *len == old_size {
|
||||
*len += new_size - old_size;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
free.push((old_size, new_size - old_size));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn alloc(&mut self, size: usize) -> io::Result<usize> {
|
||||
let mut free = self.free_list.borrow_mut();
|
||||
for (offset, len) in free.iter_mut() {
|
||||
if *len >= size {
|
||||
let rv = *offset;
|
||||
*len -= size;
|
||||
*offset += size;
|
||||
return Ok(rv);
|
||||
}
|
||||
}
|
||||
let mut rv = self.inner.len;
|
||||
let mut pop_tail = false;
|
||||
if let Some((start, len)) = free.last() {
|
||||
if start + len == self.inner.len {
|
||||
rv -= len;
|
||||
pop_tail = true;
|
||||
}
|
||||
}
|
||||
// resize like Vec::reserve, always at least doubling
|
||||
let target = std::cmp::max(rv + size, self.inner.len * 2);
|
||||
self.inner.resize(target)?;
|
||||
// adjust the end of the freelist here
|
||||
if pop_tail {
|
||||
free.pop();
|
||||
}
|
||||
if target > rv + size {
|
||||
free.push((rv + size, target - rv - size));
|
||||
}
|
||||
Ok(rv)
|
||||
}
|
||||
|
||||
fn free(free_list: &RefCell<Vec<(usize, usize)>>, mut offset: usize, mut len: usize) {
|
||||
let mut free = free_list.borrow_mut();
|
||||
let mut nf = Vec::with_capacity(free.len() + 1);
|
||||
for &(ioff, ilen) in free.iter() {
|
||||
if ioff + ilen == offset {
|
||||
offset = ioff;
|
||||
len += ilen;
|
||||
continue;
|
||||
}
|
||||
if ioff == offset + len {
|
||||
len += ilen;
|
||||
continue;
|
||||
}
|
||||
if ioff > offset + len && len != 0 {
|
||||
nf.push((offset, len));
|
||||
len = 0;
|
||||
}
|
||||
if ilen != 0 {
|
||||
nf.push((ioff, ilen));
|
||||
}
|
||||
}
|
||||
if len != 0 {
|
||||
nf.push((offset, len));
|
||||
}
|
||||
*free = nf;
|
||||
}
|
||||
|
||||
/// Create a new buffer in this pool
|
||||
///
|
||||
/// The parameters are:
|
||||
///
|
||||
/// - `width`: the width of this buffer (in pixels)
|
||||
/// - `height`: the height of this buffer (in pixels)
|
||||
/// - `stride`: distance (in bytes) between the beginning of a row and the next one
|
||||
/// - `format`: the encoding format of the pixels. Using a format that was not
|
||||
/// advertised to the `wl_shm` global by the server is a protocol error and will
|
||||
/// terminate your connection
|
||||
pub fn buffer(
|
||||
&mut self,
|
||||
width: i32,
|
||||
height: i32,
|
||||
stride: i32,
|
||||
format: wl_shm::Format,
|
||||
) -> io::Result<(&mut [u8], wl_buffer::WlBuffer)> {
|
||||
let len = (height as usize) * (stride as usize);
|
||||
let alloc_len = (len + self.align - 1) & !(self.align - 1);
|
||||
let offset = self.alloc(alloc_len)?;
|
||||
let offset_i = offset as i32;
|
||||
let buffer = self.inner.pool.create_buffer(offset_i, width, height, stride, format);
|
||||
let free_list = self.free_list.clone();
|
||||
buffer.quick_assign(move |buffer, event, _| match event {
|
||||
wl_buffer::Event::Release => {
|
||||
buffer.destroy();
|
||||
Self::free(&free_list, offset, alloc_len);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
Ok((&mut self.inner.mmap[offset..][..len], buffer.detach()))
|
||||
}
|
||||
|
||||
/// Try drawing with the given closure
|
||||
///
|
||||
/// This is identical to buffer(), but will only actually create the WlBuffer if the draw
|
||||
/// closure succeeds. Otherwise, the buffer is freed immediately instead of waiting for a
|
||||
/// Release event that will never be sent if the WlBuffer is not used.
|
||||
pub fn try_draw<F, E>(
|
||||
&mut self,
|
||||
width: i32,
|
||||
height: i32,
|
||||
stride: i32,
|
||||
format: wl_shm::Format,
|
||||
draw: F,
|
||||
) -> Result<wl_buffer::WlBuffer, E>
|
||||
where
|
||||
F: FnOnce(&mut [u8]) -> Result<(), E>,
|
||||
E: From<io::Error>,
|
||||
{
|
||||
let len = (height as usize) * (stride as usize);
|
||||
let alloc_len = (len + self.align - 1) & !(self.align - 1);
|
||||
let offset = self.alloc(alloc_len)?;
|
||||
let offset_i = offset as i32;
|
||||
if let Err(e) = draw(&mut self.inner.mmap[offset..][..len]) {
|
||||
Self::free(&self.free_list, offset, alloc_len);
|
||||
return Err(e);
|
||||
}
|
||||
let buffer = self.inner.pool.create_buffer(offset_i, width, height, stride, format);
|
||||
let free_list = self.free_list.clone();
|
||||
buffer.quick_assign(move |buffer, event, _| match event {
|
||||
wl_buffer::Event::Release => {
|
||||
buffer.destroy();
|
||||
Self::free(&free_list, offset, alloc_len);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
Ok(buffer.detach())
|
||||
}
|
||||
}
|
||||
|
||||
fn create_shm_fd() -> io::Result<RawFd> {
|
||||
// Only try memfd on linux
|
||||
#[cfg(target_os = "linux")]
|
||||
loop {
|
||||
match memfd::memfd_create(
|
||||
CStr::from_bytes_with_nul(b"smithay-client-toolkit\0").unwrap(),
|
||||
memfd::MemFdCreateFlag::MFD_CLOEXEC | memfd::MemFdCreateFlag::MFD_ALLOW_SEALING,
|
||||
) {
|
||||
Ok(fd) => {
|
||||
// this is only an optimization, so ignore errors
|
||||
let _ = fcntl::fcntl(
|
||||
fd,
|
||||
fcntl::F_ADD_SEALS(
|
||||
fcntl::SealFlag::F_SEAL_SHRINK | fcntl::SealFlag::F_SEAL_SEAL,
|
||||
),
|
||||
);
|
||||
return Ok(fd);
|
||||
}
|
||||
Err(Errno::EINTR) => continue,
|
||||
Err(Errno::ENOSYS) => break,
|
||||
Err(errno) => return Err(errno.into()),
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to using shm_open
|
||||
let sys_time = SystemTime::now();
|
||||
let mut mem_file_handle = format!(
|
||||
"/smithay-client-toolkit-{}",
|
||||
sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
|
||||
);
|
||||
loop {
|
||||
match mman::shm_open(
|
||||
mem_file_handle.as_str(),
|
||||
fcntl::OFlag::O_CREAT
|
||||
| fcntl::OFlag::O_EXCL
|
||||
| fcntl::OFlag::O_RDWR
|
||||
| fcntl::OFlag::O_CLOEXEC,
|
||||
stat::Mode::S_IRUSR | stat::Mode::S_IWUSR,
|
||||
) {
|
||||
Ok(fd) => match mman::shm_unlink(mem_file_handle.as_str()) {
|
||||
Ok(_) => return Ok(fd),
|
||||
Err(errno) => match unistd::close(fd) {
|
||||
Ok(_) => return Err(errno.into()),
|
||||
Err(errno) => return Err(errno.into()),
|
||||
},
|
||||
},
|
||||
Err(Errno::EEXIST) => {
|
||||
// If a file with that handle exists then change the handle
|
||||
mem_file_handle = format!(
|
||||
"/smithay-client-toolkit-{}",
|
||||
sys_time.duration_since(UNIX_EPOCH).unwrap().subsec_nanos()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Err(Errno::EINTR) => continue,
|
||||
Err(errno) => return Err(errno.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> crate::environment::Environment<E>
|
||||
where
|
||||
E: crate::environment::GlobalHandler<wl_shm::WlShm>,
|
||||
{
|
||||
/// Create a simple memory pool
|
||||
///
|
||||
/// This memory pool track the usage of the buffers created from it,
|
||||
/// and invokes your callback when the compositor has finished using
|
||||
/// all of them.
|
||||
pub fn create_simple_pool<F>(&self, callback: F) -> io::Result<MemPool>
|
||||
where
|
||||
F: FnMut(wayland_client::DispatchData) + 'static,
|
||||
{
|
||||
MemPool::new(self.require_global::<wl_shm::WlShm>(), callback)
|
||||
}
|
||||
|
||||
/// Create a double memory pool
|
||||
///
|
||||
/// This can be used for double-buffered drawing. The memory pool
|
||||
/// is backed by two different SHM segments, which are used in alternance.
|
||||
///
|
||||
/// The provided callback is triggered when one of the pools becomes unused again
|
||||
/// after you tried to draw while both where in use.
|
||||
pub fn create_double_pool<F>(&self, callback: F) -> io::Result<DoubleMemPool>
|
||||
where
|
||||
F: FnMut(wayland_client::DispatchData) + 'static,
|
||||
{
|
||||
DoubleMemPool::new(self.require_global::<wl_shm::WlShm>(), callback)
|
||||
}
|
||||
|
||||
/// Create an automatic memory pool
|
||||
///
|
||||
/// This pool will allocate more memory as needed in order to satisfy buffer requests, and will
|
||||
/// return memory to the pool when the compositor has finished using the memory.
|
||||
pub fn create_auto_pool(&self) -> io::Result<AutoMemPool> {
|
||||
AutoMemPool::new(self.require_global::<wl_shm::WlShm>())
|
||||
}
|
||||
}
|
||||
80
third-party/vendor/smithay-client-toolkit/src/shm/mod.rs
vendored
Normal file
80
third-party/vendor/smithay-client-toolkit/src/shm/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
//! Various small utilities helping you to write clients
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_registry, wl_shm},
|
||||
Attached, DispatchData,
|
||||
};
|
||||
|
||||
mod mempool;
|
||||
|
||||
pub use self::mempool::{AutoMemPool, DoubleMemPool, MemPool};
|
||||
pub use wl_shm::Format;
|
||||
|
||||
/// A handler for the `wl_shm` global
|
||||
///
|
||||
/// This handler is automatically included in the
|
||||
/// [`default_environment!`](../macro.default_environment.html).
|
||||
#[derive(Debug)]
|
||||
pub struct ShmHandler {
|
||||
shm: Option<Attached<wl_shm::WlShm>>,
|
||||
formats: Rc<RefCell<Vec<wl_shm::Format>>>,
|
||||
}
|
||||
|
||||
impl ShmHandler {
|
||||
/// Create a new ShmHandler
|
||||
pub fn new() -> ShmHandler {
|
||||
ShmHandler { shm: None, formats: Rc::new(RefCell::new(vec![])) }
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::environment::GlobalHandler<wl_shm::WlShm> for ShmHandler {
|
||||
fn created(
|
||||
&mut self,
|
||||
registry: Attached<wl_registry::WlRegistry>,
|
||||
id: u32,
|
||||
_version: u32,
|
||||
_: DispatchData,
|
||||
) {
|
||||
// only shm verison 1 is supported
|
||||
let shm = registry.bind::<wl_shm::WlShm>(1, id);
|
||||
let my_formats = self.formats.clone();
|
||||
shm.quick_assign(move |_, event, _| match event {
|
||||
wl_shm::Event::Format { format } => {
|
||||
my_formats.borrow_mut().push(format);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
});
|
||||
self.shm = Some((*shm).clone());
|
||||
}
|
||||
fn get(&self) -> Option<Attached<wl_shm::WlShm>> {
|
||||
self.shm.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface trait to forward the shm handler capability
|
||||
///
|
||||
/// You need to implement this trait for you environment struct, by
|
||||
/// delegating it to its `ShmHandler` field in order to get the
|
||||
/// associated methods on your [`Environment`](../environment/struct.environment.html).
|
||||
pub trait ShmHandling {
|
||||
/// Access the list of SHM formats supported by the compositor
|
||||
fn shm_formats(&self) -> Vec<wl_shm::Format>;
|
||||
}
|
||||
|
||||
impl ShmHandling for ShmHandler {
|
||||
fn shm_formats(&self) -> Vec<wl_shm::Format> {
|
||||
self.formats.borrow().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> crate::environment::Environment<E>
|
||||
where
|
||||
E: ShmHandling,
|
||||
{
|
||||
/// Access the list of SHM formats supported by the compositor
|
||||
pub fn shm_formats(&self) -> Vec<wl_shm::Format> {
|
||||
self.with_inner(|inner| inner.shm_formats())
|
||||
}
|
||||
}
|
||||
188
third-party/vendor/smithay-client-toolkit/src/surface.rs
vendored
Normal file
188
third-party/vendor/smithay-client-toolkit/src/surface.rs
vendored
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
use std::{cell::RefCell, rc::Rc, sync::Mutex};
|
||||
|
||||
use wayland_client::{
|
||||
protocol::{wl_compositor, wl_output, wl_surface},
|
||||
Attached, DispatchData, Main,
|
||||
};
|
||||
|
||||
use crate::output::{add_output_listener, with_output_info, OutputListener};
|
||||
|
||||
pub(crate) struct SurfaceUserData {
|
||||
scale_factor: i32,
|
||||
outputs: Vec<(wl_output::WlOutput, i32, OutputListener)>,
|
||||
}
|
||||
|
||||
impl SurfaceUserData {
|
||||
fn new() -> Self {
|
||||
SurfaceUserData { scale_factor: 1, outputs: Vec::new() }
|
||||
}
|
||||
|
||||
pub(crate) fn enter<F>(
|
||||
&mut self,
|
||||
output: wl_output::WlOutput,
|
||||
surface: wl_surface::WlSurface,
|
||||
callback: &Option<Rc<RefCell<F>>>,
|
||||
) where
|
||||
F: FnMut(i32, wl_surface::WlSurface, DispatchData) + 'static,
|
||||
{
|
||||
let output_scale = with_output_info(&output, |info| info.scale_factor).unwrap_or(1);
|
||||
let my_surface = surface.clone();
|
||||
// Use a UserData to safely share the callback with the other thread
|
||||
let my_callback = wayland_client::UserData::new();
|
||||
if let Some(ref cb) = callback {
|
||||
my_callback.set(|| cb.clone());
|
||||
}
|
||||
let listener = add_output_listener(&output, move |output, info, ddata| {
|
||||
let mut user_data = my_surface
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<Mutex<SurfaceUserData>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap();
|
||||
// update the scale factor of the relevant output
|
||||
for (ref o, ref mut factor, _) in user_data.outputs.iter_mut() {
|
||||
if o.as_ref().equals(output.as_ref()) {
|
||||
if info.obsolete {
|
||||
// an output that no longer exists is marked by a scale factor of -1
|
||||
*factor = -1;
|
||||
} else {
|
||||
*factor = info.scale_factor;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// recompute the scale factor with the new info
|
||||
let callback = my_callback.get::<Rc<RefCell<F>>>().cloned();
|
||||
let old_scale_factor = user_data.scale_factor;
|
||||
let new_scale_factor = user_data.recompute_scale_factor();
|
||||
drop(user_data);
|
||||
if let Some(ref cb) = callback {
|
||||
if old_scale_factor != new_scale_factor {
|
||||
(cb.borrow_mut())(new_scale_factor, surface.clone(), ddata);
|
||||
}
|
||||
}
|
||||
});
|
||||
self.outputs.push((output, output_scale, listener));
|
||||
}
|
||||
|
||||
pub(crate) fn leave(&mut self, output: &wl_output::WlOutput) {
|
||||
self.outputs.retain(|(ref output2, _, _)| !output.as_ref().equals(output2.as_ref()));
|
||||
}
|
||||
|
||||
fn recompute_scale_factor(&mut self) -> i32 {
|
||||
let mut new_scale_factor = 1;
|
||||
self.outputs.retain(|&(_, output_scale, _)| {
|
||||
if output_scale > 0 {
|
||||
new_scale_factor = ::std::cmp::max(new_scale_factor, output_scale);
|
||||
true
|
||||
} else {
|
||||
// cleanup obsolete output
|
||||
false
|
||||
}
|
||||
});
|
||||
if self.outputs.is_empty() {
|
||||
// don't update the scale factor if we are not displayed on any output
|
||||
return self.scale_factor;
|
||||
}
|
||||
self.scale_factor = new_scale_factor;
|
||||
new_scale_factor
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn setup_surface<F>(
|
||||
surface: Main<wl_surface::WlSurface>,
|
||||
callback: Option<F>,
|
||||
) -> Attached<wl_surface::WlSurface>
|
||||
where
|
||||
F: FnMut(i32, wl_surface::WlSurface, DispatchData) + 'static,
|
||||
{
|
||||
let callback = callback.map(|c| Rc::new(RefCell::new(c)));
|
||||
surface.quick_assign(move |surface, event, ddata| {
|
||||
let mut user_data =
|
||||
surface.as_ref().user_data().get::<Mutex<SurfaceUserData>>().unwrap().lock().unwrap();
|
||||
match event {
|
||||
wl_surface::Event::Enter { output } => {
|
||||
// Passing the callback to be added to output listener
|
||||
user_data.enter(output, surface.detach(), &callback);
|
||||
}
|
||||
wl_surface::Event::Leave { output } => {
|
||||
user_data.leave(&output);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let old_scale_factor = user_data.scale_factor;
|
||||
let new_scale_factor = user_data.recompute_scale_factor();
|
||||
drop(user_data);
|
||||
if let Some(ref cb) = callback {
|
||||
if old_scale_factor != new_scale_factor {
|
||||
(cb.borrow_mut())(new_scale_factor, surface.detach(), ddata);
|
||||
}
|
||||
}
|
||||
});
|
||||
surface.as_ref().user_data().set_threadsafe(|| Mutex::new(SurfaceUserData::new()));
|
||||
surface.into()
|
||||
}
|
||||
|
||||
impl<E: crate::environment::GlobalHandler<wl_compositor::WlCompositor>>
|
||||
crate::environment::Environment<E>
|
||||
{
|
||||
/// Create a DPI-aware surface
|
||||
///
|
||||
/// This surface will track the outputs it is being displayed on, and compute the
|
||||
/// optimal scale factor for these. You can access them using
|
||||
/// [`get_surface_scale_factor`](../fn.get_surface_scale_factor.html) and
|
||||
/// [`get_surface_outputs`](../fn.get_surface_outputs.html).
|
||||
pub fn create_surface(&self) -> Attached<wl_surface::WlSurface> {
|
||||
let compositor = self.require_global::<wl_compositor::WlCompositor>();
|
||||
setup_surface(compositor.create_surface(), None::<fn(_, _, DispatchData)>)
|
||||
}
|
||||
|
||||
/// Create a DPI-aware surface with callbacks
|
||||
///
|
||||
/// This method is like `create_surface`, but the provided callback will also be
|
||||
/// notified whenever the scale factor of this surface change, if you don't want to have to
|
||||
/// periodically check it.
|
||||
pub fn create_surface_with_scale_callback<
|
||||
F: FnMut(i32, wl_surface::WlSurface, DispatchData) + 'static,
|
||||
>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> Attached<wl_surface::WlSurface> {
|
||||
let compositor = self.require_global::<wl_compositor::WlCompositor>();
|
||||
setup_surface(compositor.create_surface(), Some(f))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current suggested scale factor of a surface.
|
||||
///
|
||||
/// Panics if the surface was not created using `Environment::create_surface` or
|
||||
/// `Environment::create_surface_with_dpi_callback`.
|
||||
pub fn get_surface_scale_factor(surface: &wl_surface::WlSurface) -> i32 {
|
||||
surface
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<Mutex<SurfaceUserData>>()
|
||||
.expect("SCTK: Surface was not created by SCTK.")
|
||||
.lock()
|
||||
.unwrap()
|
||||
.scale_factor
|
||||
}
|
||||
|
||||
/// Returns a list of outputs the surface is displayed on.
|
||||
///
|
||||
/// Panics if the surface was not created using `Environment::create_surface` or
|
||||
/// `Environment::create_surface_with_dpi_callback`.
|
||||
pub fn get_surface_outputs(surface: &wl_surface::WlSurface) -> Vec<wl_output::WlOutput> {
|
||||
surface
|
||||
.as_ref()
|
||||
.user_data()
|
||||
.get::<Mutex<SurfaceUserData>>()
|
||||
.expect("SCTK: Surface was not created by SCTK.")
|
||||
.lock()
|
||||
.unwrap()
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|(ref output, _, _)| output.clone())
|
||||
.collect()
|
||||
}
|
||||
972
third-party/vendor/smithay-client-toolkit/src/window/fallback_frame.rs
vendored
Normal file
972
third-party/vendor/smithay-client-toolkit/src/window/fallback_frame.rs
vendored
Normal file
|
|
@ -0,0 +1,972 @@
|
|||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
use wayland_client::protocol::{
|
||||
wl_compositor, wl_pointer, wl_seat, wl_shm, wl_subcompositor, wl_subsurface, wl_surface,
|
||||
};
|
||||
use wayland_client::{Attached, DispatchData};
|
||||
|
||||
use log::error;
|
||||
|
||||
use super::{ButtonState, Frame, FrameRequest, State, WindowState};
|
||||
use crate::seat::pointer::{ThemeManager, ThemeSpec, ThemedPointer};
|
||||
use crate::shm::AutoMemPool;
|
||||
|
||||
/*
|
||||
* Drawing theme definitions
|
||||
*/
|
||||
|
||||
const BORDER_SIZE: u32 = 4;
|
||||
const HEADER_SIZE: u32 = 24;
|
||||
|
||||
const BTN_ICON_COLOR: u32 = 0xFF1E1E1E;
|
||||
const BTN_HOVER_BG: u32 = 0xFFA8A8A8;
|
||||
|
||||
const PRIMARY_COLOR_ACTIVE: u32 = 0xFFE6E6E6;
|
||||
const PRIMARY_COLOR_INACTIVE: u32 = 0xFFDCDCDC;
|
||||
|
||||
/*
|
||||
* Utilities
|
||||
*/
|
||||
|
||||
const HEAD: usize = 0;
|
||||
const TOP: usize = 1;
|
||||
const BOTTOM: usize = 2;
|
||||
const LEFT: usize = 3;
|
||||
const RIGHT: usize = 4;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum Location {
|
||||
None,
|
||||
Head,
|
||||
Top,
|
||||
TopRight,
|
||||
Right,
|
||||
BottomRight,
|
||||
Bottom,
|
||||
BottomLeft,
|
||||
Left,
|
||||
TopLeft,
|
||||
Button(UIButton),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum UIButton {
|
||||
Minimize,
|
||||
Maximize,
|
||||
Close,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Part {
|
||||
surface: wl_surface::WlSurface,
|
||||
subsurface: wl_subsurface::WlSubsurface,
|
||||
}
|
||||
|
||||
impl Part {
|
||||
fn new(
|
||||
parent: &wl_surface::WlSurface,
|
||||
compositor: &Attached<wl_compositor::WlCompositor>,
|
||||
subcompositor: &Attached<wl_subcompositor::WlSubcompositor>,
|
||||
inner: Option<Rc<RefCell<Inner>>>,
|
||||
) -> Part {
|
||||
let surface = if let Some(inner) = inner {
|
||||
crate::surface::setup_surface(
|
||||
compositor.create_surface(),
|
||||
Some(move |dpi, surface: wl_surface::WlSurface, ddata: DispatchData| {
|
||||
surface.set_buffer_scale(dpi);
|
||||
surface.commit();
|
||||
(inner.borrow_mut().implem)(FrameRequest::Refresh, 0, ddata);
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
crate::surface::setup_surface(
|
||||
compositor.create_surface(),
|
||||
Some(move |dpi, surface: wl_surface::WlSurface, _ddata: DispatchData| {
|
||||
surface.set_buffer_scale(dpi);
|
||||
surface.commit();
|
||||
}),
|
||||
)
|
||||
};
|
||||
|
||||
let surface = surface.detach();
|
||||
|
||||
let subsurface = subcompositor.get_subsurface(&surface, parent);
|
||||
|
||||
Part { surface, subsurface: subsurface.detach() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Part {
|
||||
fn drop(&mut self) {
|
||||
self.subsurface.destroy();
|
||||
self.surface.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
struct PointerUserData {
|
||||
location: Location,
|
||||
position: (f64, f64),
|
||||
seat: wl_seat::WlSeat,
|
||||
}
|
||||
|
||||
/*
|
||||
* The core frame
|
||||
*/
|
||||
|
||||
struct Inner {
|
||||
parts: Vec<Part>,
|
||||
size: (u32, u32),
|
||||
resizable: bool,
|
||||
theme_over_surface: bool,
|
||||
implem: Box<dyn FnMut(FrameRequest, u32, DispatchData)>,
|
||||
maximized: bool,
|
||||
fullscreened: bool,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn find_surface(&self, surface: &wl_surface::WlSurface) -> Location {
|
||||
if self.parts.is_empty() {
|
||||
return Location::None;
|
||||
}
|
||||
|
||||
if surface.as_ref().equals(self.parts[HEAD].surface.as_ref()) {
|
||||
Location::Head
|
||||
} else if surface.as_ref().equals(self.parts[TOP].surface.as_ref()) {
|
||||
Location::Top
|
||||
} else if surface.as_ref().equals(self.parts[BOTTOM].surface.as_ref()) {
|
||||
Location::Bottom
|
||||
} else if surface.as_ref().equals(self.parts[LEFT].surface.as_ref()) {
|
||||
Location::Left
|
||||
} else if surface.as_ref().equals(self.parts[RIGHT].surface.as_ref()) {
|
||||
Location::Right
|
||||
} else {
|
||||
Location::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Inner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Inner")
|
||||
.field("parts", &self.parts)
|
||||
.field("size", &self.size)
|
||||
.field("resizable", &self.resizable)
|
||||
.field("theme_over_surface", &self.theme_over_surface)
|
||||
.field("implem", &"FnMut(FrameRequest, u32, DispatchData) -> { ... }")
|
||||
.field("maximized", &self.maximized)
|
||||
.field("fullscreened", &self.fullscreened)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn precise_location(old: Location, width: u32, x: f64, y: f64) -> Location {
|
||||
match old {
|
||||
Location::Head | Location::Button(_) => find_button(x, y, width),
|
||||
|
||||
Location::Top | Location::TopLeft | Location::TopRight => {
|
||||
if x <= f64::from(BORDER_SIZE) {
|
||||
Location::TopLeft
|
||||
} else if x >= f64::from(width + BORDER_SIZE) {
|
||||
Location::TopRight
|
||||
} else {
|
||||
Location::Top
|
||||
}
|
||||
}
|
||||
|
||||
Location::Bottom | Location::BottomLeft | Location::BottomRight => {
|
||||
if x <= f64::from(BORDER_SIZE) {
|
||||
Location::BottomLeft
|
||||
} else if x >= f64::from(width + BORDER_SIZE) {
|
||||
Location::BottomRight
|
||||
} else {
|
||||
Location::Bottom
|
||||
}
|
||||
}
|
||||
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_button(x: f64, y: f64, w: u32) -> Location {
|
||||
if (w >= HEADER_SIZE)
|
||||
&& (x >= f64::from(w - HEADER_SIZE))
|
||||
&& (x <= f64::from(w))
|
||||
&& (y <= f64::from(HEADER_SIZE))
|
||||
&& (y >= f64::from(0))
|
||||
{
|
||||
// first button
|
||||
Location::Button(UIButton::Close)
|
||||
} else if (w >= 2 * HEADER_SIZE)
|
||||
&& (x >= f64::from(w - 2 * HEADER_SIZE))
|
||||
&& (x <= f64::from(w - HEADER_SIZE))
|
||||
&& (y <= f64::from(HEADER_SIZE))
|
||||
&& (y >= f64::from(0))
|
||||
{
|
||||
// second button
|
||||
Location::Button(UIButton::Maximize)
|
||||
} else if (w >= 3 * HEADER_SIZE)
|
||||
&& (x >= f64::from(w - 3 * HEADER_SIZE))
|
||||
&& (x <= f64::from(w - 2 * HEADER_SIZE))
|
||||
&& (y <= f64::from(HEADER_SIZE))
|
||||
&& (y >= f64::from(0))
|
||||
{
|
||||
// third button
|
||||
Location::Button(UIButton::Minimize)
|
||||
} else {
|
||||
Location::Head
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple set of decorations that can be used as a fallback
|
||||
///
|
||||
/// This class drawn some simple and minimalistic decorations around
|
||||
/// a window so that it remains possible to interact with the window
|
||||
/// even when server-side decorations are not available.
|
||||
///
|
||||
/// `FallbackFrame` is hiding its `ClientSide` decorations
|
||||
/// in a `Fullscreen` state and brings them back if those are
|
||||
/// visible when unsetting `Fullscreen` state.
|
||||
#[derive(Debug)]
|
||||
pub struct FallbackFrame {
|
||||
base_surface: wl_surface::WlSurface,
|
||||
compositor: Attached<wl_compositor::WlCompositor>,
|
||||
subcompositor: Attached<wl_subcompositor::WlSubcompositor>,
|
||||
inner: Rc<RefCell<Inner>>,
|
||||
pool: AutoMemPool,
|
||||
active: WindowState,
|
||||
hidden: bool,
|
||||
pointers: Vec<ThemedPointer>,
|
||||
themer: ThemeManager,
|
||||
surface_version: u32,
|
||||
}
|
||||
|
||||
impl Frame for FallbackFrame {
|
||||
type Error = ::std::io::Error;
|
||||
type Config = ();
|
||||
fn init(
|
||||
base_surface: &wl_surface::WlSurface,
|
||||
compositor: &Attached<wl_compositor::WlCompositor>,
|
||||
subcompositor: &Attached<wl_subcompositor::WlSubcompositor>,
|
||||
shm: &Attached<wl_shm::WlShm>,
|
||||
theme_manager: Option<ThemeManager>,
|
||||
implementation: Box<dyn FnMut(FrameRequest, u32, DispatchData)>,
|
||||
) -> Result<FallbackFrame, ::std::io::Error> {
|
||||
let (themer, theme_over_surface) = if let Some(theme_manager) = theme_manager {
|
||||
(theme_manager, false)
|
||||
} else {
|
||||
(ThemeManager::init(ThemeSpec::System, compositor.clone(), shm.clone()), true)
|
||||
};
|
||||
|
||||
let inner = Rc::new(RefCell::new(Inner {
|
||||
parts: vec![],
|
||||
size: (1, 1),
|
||||
resizable: true,
|
||||
implem: implementation,
|
||||
theme_over_surface,
|
||||
maximized: false,
|
||||
fullscreened: false,
|
||||
}));
|
||||
|
||||
let pool = AutoMemPool::new(shm.clone())?;
|
||||
|
||||
Ok(FallbackFrame {
|
||||
base_surface: base_surface.clone(),
|
||||
compositor: compositor.clone(),
|
||||
subcompositor: subcompositor.clone(),
|
||||
inner,
|
||||
pool,
|
||||
active: WindowState::Inactive,
|
||||
hidden: true,
|
||||
pointers: Vec::new(),
|
||||
themer,
|
||||
surface_version: compositor.as_ref().version(),
|
||||
})
|
||||
}
|
||||
|
||||
fn new_seat(&mut self, seat: &Attached<wl_seat::WlSeat>) {
|
||||
use self::wl_pointer::Event;
|
||||
let inner = self.inner.clone();
|
||||
let pointer = self.themer.theme_pointer_with_impl(
|
||||
seat,
|
||||
move |event, pointer: ThemedPointer, ddata: DispatchData| {
|
||||
let data: &RefCell<PointerUserData> = pointer.as_ref().user_data().get().unwrap();
|
||||
let mut data = data.borrow_mut();
|
||||
let mut inner = inner.borrow_mut();
|
||||
match event {
|
||||
Event::Enter { serial, surface, surface_x, surface_y } => {
|
||||
data.location = precise_location(
|
||||
inner.find_surface(&surface),
|
||||
inner.size.0,
|
||||
surface_x,
|
||||
surface_y,
|
||||
);
|
||||
data.position = (surface_x, surface_y);
|
||||
change_pointer(&pointer, &inner, data.location, Some(serial))
|
||||
}
|
||||
Event::Leave { serial, .. } => {
|
||||
data.location = Location::None;
|
||||
change_pointer(&pointer, &inner, data.location, Some(serial));
|
||||
(inner.implem)(FrameRequest::Refresh, 0, ddata);
|
||||
}
|
||||
Event::Motion { surface_x, surface_y, .. } => {
|
||||
data.position = (surface_x, surface_y);
|
||||
let newpos =
|
||||
precise_location(data.location, inner.size.0, surface_x, surface_y);
|
||||
if newpos != data.location {
|
||||
match (newpos, data.location) {
|
||||
(Location::Button(_), _) | (_, Location::Button(_)) => {
|
||||
// pointer movement involves a button, request refresh
|
||||
(inner.implem)(FrameRequest::Refresh, 0, ddata);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
// we changed of part of the decoration, pointer image
|
||||
// may need to be changed
|
||||
data.location = newpos;
|
||||
change_pointer(&pointer, &inner, data.location, None)
|
||||
}
|
||||
}
|
||||
Event::Button { serial, button, state, .. } => {
|
||||
if state == wl_pointer::ButtonState::Pressed {
|
||||
let request = match button {
|
||||
// Left mouse button.
|
||||
0x110 => request_for_location_on_lmb(
|
||||
&data,
|
||||
inner.maximized,
|
||||
inner.resizable,
|
||||
),
|
||||
// Right mouse button.
|
||||
0x111 => request_for_location_on_rmb(&data),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(request) = request {
|
||||
(inner.implem)(request, serial, ddata);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
);
|
||||
pointer.as_ref().user_data().set(|| {
|
||||
RefCell::new(PointerUserData {
|
||||
location: Location::None,
|
||||
position: (0.0, 0.0),
|
||||
seat: seat.detach(),
|
||||
})
|
||||
});
|
||||
self.pointers.push(pointer);
|
||||
}
|
||||
|
||||
fn remove_seat(&mut self, seat: &wl_seat::WlSeat) {
|
||||
self.pointers.retain(|pointer| {
|
||||
let user_data = pointer.as_ref().user_data().get::<RefCell<PointerUserData>>().unwrap();
|
||||
let guard = user_data.borrow_mut();
|
||||
if &guard.seat == seat {
|
||||
pointer.release();
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn set_states(&mut self, states: &[State]) -> bool {
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
let mut need_redraw = false;
|
||||
|
||||
// Process active.
|
||||
let new_active = if states.contains(&State::Activated) {
|
||||
WindowState::Active
|
||||
} else {
|
||||
WindowState::Inactive
|
||||
};
|
||||
need_redraw |= new_active != self.active;
|
||||
self.active = new_active;
|
||||
|
||||
// Process maximized.
|
||||
let new_maximized = states.contains(&State::Maximized);
|
||||
need_redraw |= new_maximized != inner.maximized;
|
||||
inner.maximized = new_maximized;
|
||||
|
||||
// Process fullscreened.
|
||||
let new_fullscreened = states.contains(&State::Fullscreen);
|
||||
need_redraw |= new_fullscreened != inner.fullscreened;
|
||||
inner.fullscreened = new_fullscreened;
|
||||
|
||||
need_redraw
|
||||
}
|
||||
|
||||
fn set_hidden(&mut self, hidden: bool) {
|
||||
self.hidden = hidden;
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if !self.hidden {
|
||||
if inner.parts.is_empty() {
|
||||
inner.parts = vec![
|
||||
Part::new(
|
||||
&self.base_surface,
|
||||
&self.compositor,
|
||||
&self.subcompositor,
|
||||
Some(Rc::clone(&self.inner)),
|
||||
),
|
||||
Part::new(&self.base_surface, &self.compositor, &self.subcompositor, None),
|
||||
Part::new(&self.base_surface, &self.compositor, &self.subcompositor, None),
|
||||
Part::new(&self.base_surface, &self.compositor, &self.subcompositor, None),
|
||||
Part::new(&self.base_surface, &self.compositor, &self.subcompositor, None),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
inner.parts.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn set_resizable(&mut self, resizable: bool) {
|
||||
self.inner.borrow_mut().resizable = resizable;
|
||||
}
|
||||
|
||||
fn resize(&mut self, newsize: (u32, u32)) {
|
||||
self.inner.borrow_mut().size = newsize;
|
||||
}
|
||||
|
||||
fn redraw(&mut self) {
|
||||
let inner = self.inner.borrow_mut();
|
||||
|
||||
// Don't draw borders if the frame explicitly hidden or fullscreened.
|
||||
if self.hidden || inner.fullscreened {
|
||||
// Don't draw the borders.
|
||||
for p in inner.parts.iter() {
|
||||
p.surface.attach(None, 0, 0);
|
||||
p.surface.commit();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// `parts` can't be empty here, since the initial state for `self.hidden` is true, and
|
||||
// they will be created once `self.hidden` will become `false`.
|
||||
let parts = &inner.parts;
|
||||
|
||||
let scales: Vec<u32> = parts
|
||||
.iter()
|
||||
.map(|part| crate::surface::get_surface_scale_factor(&part.surface) as u32)
|
||||
.collect();
|
||||
|
||||
let (width, height) = inner.size;
|
||||
|
||||
// Use header scale for all the thing.
|
||||
let header_scale = scales[HEAD];
|
||||
|
||||
let scaled_header_height = HEADER_SIZE * header_scale;
|
||||
let scaled_header_width = width * header_scale;
|
||||
|
||||
{
|
||||
// Create the buffers and draw
|
||||
let color = if self.active == WindowState::Active {
|
||||
PRIMARY_COLOR_ACTIVE.to_ne_bytes()
|
||||
} else {
|
||||
PRIMARY_COLOR_INACTIVE.to_ne_bytes()
|
||||
};
|
||||
|
||||
// -> head-subsurface
|
||||
if let Ok((canvas, buffer)) = self.pool.buffer(
|
||||
scaled_header_width as i32,
|
||||
scaled_header_height as i32,
|
||||
4 * scaled_header_width as i32,
|
||||
wl_shm::Format::Argb8888,
|
||||
) {
|
||||
for pixel in canvas.chunks_exact_mut(4) {
|
||||
pixel[0] = color[0];
|
||||
pixel[1] = color[1];
|
||||
pixel[2] = color[2];
|
||||
pixel[3] = color[3];
|
||||
}
|
||||
|
||||
draw_buttons(
|
||||
canvas,
|
||||
width,
|
||||
header_scale,
|
||||
inner.resizable,
|
||||
self.active,
|
||||
&self
|
||||
.pointers
|
||||
.iter()
|
||||
.flat_map(|p| {
|
||||
if p.as_ref().is_alive() {
|
||||
let data: &RefCell<PointerUserData> =
|
||||
p.as_ref().user_data().get().unwrap();
|
||||
Some(data.borrow().location)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<Location>>(),
|
||||
);
|
||||
|
||||
parts[HEAD].subsurface.set_position(0, -(HEADER_SIZE as i32));
|
||||
parts[HEAD].surface.attach(Some(&buffer), 0, 0);
|
||||
if self.surface_version >= 4 {
|
||||
parts[HEAD].surface.damage_buffer(
|
||||
0,
|
||||
0,
|
||||
scaled_header_width as i32,
|
||||
scaled_header_height as i32,
|
||||
);
|
||||
} else {
|
||||
// surface is old and does not support damage_buffer, so we damage
|
||||
// in surface coordinates and hope it is not rescaled
|
||||
parts[HEAD].surface.damage(0, 0, width as i32, HEADER_SIZE as i32);
|
||||
}
|
||||
parts[HEAD].surface.commit();
|
||||
}
|
||||
|
||||
// -> top-subsurface
|
||||
if let Ok((canvas, buffer)) = self.pool.buffer(
|
||||
((width + 2 * BORDER_SIZE) * scales[TOP]) as i32,
|
||||
(BORDER_SIZE * scales[TOP]) as i32,
|
||||
(4 * scales[TOP] * (width + 2 * BORDER_SIZE)) as i32,
|
||||
wl_shm::Format::Argb8888,
|
||||
) {
|
||||
for pixel in canvas.chunks_exact_mut(4) {
|
||||
pixel[0] = color[0];
|
||||
pixel[1] = color[1];
|
||||
pixel[2] = color[2];
|
||||
pixel[3] = color[3];
|
||||
}
|
||||
parts[TOP].subsurface.set_position(
|
||||
-(BORDER_SIZE as i32),
|
||||
-(HEADER_SIZE as i32 + BORDER_SIZE as i32),
|
||||
);
|
||||
parts[TOP].surface.attach(Some(&buffer), 0, 0);
|
||||
if self.surface_version >= 4 {
|
||||
parts[TOP].surface.damage_buffer(
|
||||
0,
|
||||
0,
|
||||
((width + 2 * BORDER_SIZE) * scales[TOP]) as i32,
|
||||
(BORDER_SIZE * scales[TOP]) as i32,
|
||||
);
|
||||
} else {
|
||||
// surface is old and does not support damage_buffer, so we damage
|
||||
// in surface coordinates and hope it is not rescaled
|
||||
parts[TOP].surface.damage(
|
||||
0,
|
||||
0,
|
||||
(width + 2 * BORDER_SIZE) as i32,
|
||||
BORDER_SIZE as i32,
|
||||
);
|
||||
}
|
||||
parts[TOP].surface.commit();
|
||||
}
|
||||
|
||||
// -> bottom-subsurface
|
||||
if let Ok((canvas, buffer)) = self.pool.buffer(
|
||||
((width + 2 * BORDER_SIZE) * scales[BOTTOM]) as i32,
|
||||
(BORDER_SIZE * scales[BOTTOM]) as i32,
|
||||
(4 * scales[BOTTOM] * (width + 2 * BORDER_SIZE)) as i32,
|
||||
wl_shm::Format::Argb8888,
|
||||
) {
|
||||
for pixel in canvas.chunks_exact_mut(4) {
|
||||
pixel[0] = color[0];
|
||||
pixel[1] = color[1];
|
||||
pixel[2] = color[2];
|
||||
pixel[3] = color[3];
|
||||
}
|
||||
parts[BOTTOM].subsurface.set_position(-(BORDER_SIZE as i32), height as i32);
|
||||
parts[BOTTOM].surface.attach(Some(&buffer), 0, 0);
|
||||
if self.surface_version >= 4 {
|
||||
parts[BOTTOM].surface.damage_buffer(
|
||||
0,
|
||||
0,
|
||||
((width + 2 * BORDER_SIZE) * scales[BOTTOM]) as i32,
|
||||
(BORDER_SIZE * scales[BOTTOM]) as i32,
|
||||
);
|
||||
} else {
|
||||
// surface is old and does not support damage_buffer, so we damage
|
||||
// in surface coordinates and hope it is not rescaled
|
||||
parts[BOTTOM].surface.damage(
|
||||
0,
|
||||
0,
|
||||
(width + 2 * BORDER_SIZE) as i32,
|
||||
BORDER_SIZE as i32,
|
||||
);
|
||||
}
|
||||
parts[BOTTOM].surface.commit();
|
||||
}
|
||||
|
||||
// -> left-subsurface
|
||||
if let Ok((canvas, buffer)) = self.pool.buffer(
|
||||
(BORDER_SIZE * scales[LEFT]) as i32,
|
||||
((height + HEADER_SIZE) * scales[LEFT]) as i32,
|
||||
4 * (BORDER_SIZE * scales[LEFT]) as i32,
|
||||
wl_shm::Format::Argb8888,
|
||||
) {
|
||||
for pixel in canvas.chunks_exact_mut(4) {
|
||||
pixel[0] = color[0];
|
||||
pixel[1] = color[1];
|
||||
pixel[2] = color[2];
|
||||
pixel[3] = color[3];
|
||||
}
|
||||
parts[LEFT].subsurface.set_position(-(BORDER_SIZE as i32), -(HEADER_SIZE as i32));
|
||||
parts[LEFT].surface.attach(Some(&buffer), 0, 0);
|
||||
if self.surface_version >= 4 {
|
||||
parts[LEFT].surface.damage_buffer(
|
||||
0,
|
||||
0,
|
||||
(BORDER_SIZE * scales[LEFT]) as i32,
|
||||
((height + HEADER_SIZE) * scales[LEFT]) as i32,
|
||||
);
|
||||
} else {
|
||||
// surface is old and does not support damage_buffer, so we damage
|
||||
// in surface coordinates and hope it is not rescaled
|
||||
parts[LEFT].surface.damage(
|
||||
0,
|
||||
0,
|
||||
BORDER_SIZE as i32,
|
||||
(height + HEADER_SIZE) as i32,
|
||||
);
|
||||
}
|
||||
parts[LEFT].surface.commit();
|
||||
}
|
||||
|
||||
// -> right-subsurface
|
||||
if let Ok((canvas, buffer)) = self.pool.buffer(
|
||||
(BORDER_SIZE * scales[RIGHT]) as i32,
|
||||
((height + HEADER_SIZE) * scales[RIGHT]) as i32,
|
||||
4 * (BORDER_SIZE * scales[RIGHT]) as i32,
|
||||
wl_shm::Format::Argb8888,
|
||||
) {
|
||||
for pixel in canvas.chunks_exact_mut(4) {
|
||||
pixel[0] = color[0];
|
||||
pixel[1] = color[1];
|
||||
pixel[2] = color[2];
|
||||
pixel[3] = color[3];
|
||||
}
|
||||
parts[RIGHT].subsurface.set_position(width as i32, -(HEADER_SIZE as i32));
|
||||
parts[RIGHT].surface.attach(Some(&buffer), 0, 0);
|
||||
if self.surface_version >= 4 {
|
||||
parts[RIGHT].surface.damage_buffer(
|
||||
0,
|
||||
0,
|
||||
(BORDER_SIZE * scales[RIGHT]) as i32,
|
||||
((height + HEADER_SIZE) * scales[RIGHT]) as i32,
|
||||
);
|
||||
} else {
|
||||
// surface is old and does not support damage_buffer, so we damage
|
||||
// in surface coordinates and hope it is not rescaled
|
||||
parts[RIGHT].surface.damage(
|
||||
0,
|
||||
0,
|
||||
BORDER_SIZE as i32,
|
||||
(height + HEADER_SIZE) as i32,
|
||||
);
|
||||
}
|
||||
parts[RIGHT].surface.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn subtract_borders(&self, width: i32, height: i32) -> (i32, i32) {
|
||||
if self.hidden || self.inner.borrow().fullscreened {
|
||||
(width, height)
|
||||
} else {
|
||||
(width - 2 * BORDER_SIZE as i32, height - HEADER_SIZE as i32 - 2 * BORDER_SIZE as i32)
|
||||
}
|
||||
}
|
||||
|
||||
fn add_borders(&self, width: i32, height: i32) -> (i32, i32) {
|
||||
if self.hidden || self.inner.borrow().fullscreened {
|
||||
(width, height)
|
||||
} else {
|
||||
(width + 2 * BORDER_SIZE as i32, height + HEADER_SIZE as i32 + 2 * BORDER_SIZE as i32)
|
||||
}
|
||||
}
|
||||
|
||||
fn location(&self) -> (i32, i32) {
|
||||
if self.hidden || self.inner.borrow().fullscreened {
|
||||
(0, 0)
|
||||
} else {
|
||||
(-(BORDER_SIZE as i32), -(HEADER_SIZE as i32 + BORDER_SIZE as i32))
|
||||
}
|
||||
}
|
||||
|
||||
fn set_config(&mut self, _config: ()) {}
|
||||
|
||||
fn set_title(&mut self, _title: String) {}
|
||||
}
|
||||
|
||||
impl Drop for FallbackFrame {
|
||||
fn drop(&mut self) {
|
||||
for ptr in self.pointers.drain(..) {
|
||||
if ptr.as_ref().version() >= 3 {
|
||||
ptr.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn change_pointer(pointer: &ThemedPointer, inner: &Inner, location: Location, serial: Option<u32>) {
|
||||
// Prevent theming of the surface if it was requested.
|
||||
if !inner.theme_over_surface && location == Location::None {
|
||||
return;
|
||||
}
|
||||
|
||||
let name = match location {
|
||||
// If we can't resize a frame we shouldn't show resize cursors.
|
||||
_ if !inner.resizable => "left_ptr",
|
||||
Location::Top => "top_side",
|
||||
Location::TopRight => "top_right_corner",
|
||||
Location::Right => "right_side",
|
||||
Location::BottomRight => "bottom_right_corner",
|
||||
Location::Bottom => "bottom_side",
|
||||
Location::BottomLeft => "bottom_left_corner",
|
||||
Location::Left => "left_side",
|
||||
Location::TopLeft => "top_left_corner",
|
||||
_ => "left_ptr",
|
||||
};
|
||||
|
||||
if pointer.set_cursor(name, serial).is_err() {
|
||||
error!("Failed to set cursor");
|
||||
}
|
||||
}
|
||||
|
||||
fn request_for_location_on_lmb(
|
||||
pointer_data: &PointerUserData,
|
||||
maximized: bool,
|
||||
resizable: bool,
|
||||
) -> Option<FrameRequest> {
|
||||
use wayland_protocols::xdg_shell::client::xdg_toplevel::ResizeEdge;
|
||||
match pointer_data.location {
|
||||
Location::Top if resizable => {
|
||||
Some(FrameRequest::Resize(pointer_data.seat.clone(), ResizeEdge::Top))
|
||||
}
|
||||
Location::TopLeft if resizable => {
|
||||
Some(FrameRequest::Resize(pointer_data.seat.clone(), ResizeEdge::TopLeft))
|
||||
}
|
||||
Location::Left if resizable => {
|
||||
Some(FrameRequest::Resize(pointer_data.seat.clone(), ResizeEdge::Left))
|
||||
}
|
||||
Location::BottomLeft if resizable => {
|
||||
Some(FrameRequest::Resize(pointer_data.seat.clone(), ResizeEdge::BottomLeft))
|
||||
}
|
||||
Location::Bottom if resizable => {
|
||||
Some(FrameRequest::Resize(pointer_data.seat.clone(), ResizeEdge::Bottom))
|
||||
}
|
||||
Location::BottomRight if resizable => {
|
||||
Some(FrameRequest::Resize(pointer_data.seat.clone(), ResizeEdge::BottomRight))
|
||||
}
|
||||
Location::Right if resizable => {
|
||||
Some(FrameRequest::Resize(pointer_data.seat.clone(), ResizeEdge::Right))
|
||||
}
|
||||
Location::TopRight if resizable => {
|
||||
Some(FrameRequest::Resize(pointer_data.seat.clone(), ResizeEdge::TopRight))
|
||||
}
|
||||
Location::Head => Some(FrameRequest::Move(pointer_data.seat.clone())),
|
||||
Location::Button(UIButton::Close) => Some(FrameRequest::Close),
|
||||
Location::Button(UIButton::Maximize) => {
|
||||
if maximized {
|
||||
Some(FrameRequest::UnMaximize)
|
||||
} else {
|
||||
Some(FrameRequest::Maximize)
|
||||
}
|
||||
}
|
||||
Location::Button(UIButton::Minimize) => Some(FrameRequest::Minimize),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn request_for_location_on_rmb(pointer_data: &PointerUserData) -> Option<FrameRequest> {
|
||||
match pointer_data.location {
|
||||
Location::Head | Location::Button(_) => Some(FrameRequest::ShowMenu(
|
||||
pointer_data.seat.clone(),
|
||||
pointer_data.position.0 as i32,
|
||||
// We must offset it by header size for precise position.
|
||||
pointer_data.position.1 as i32 - HEADER_SIZE as i32,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_buttons(
|
||||
canvas: &mut [u8],
|
||||
width: u32,
|
||||
scale: u32,
|
||||
maximizable: bool,
|
||||
state: WindowState,
|
||||
mouses: &[Location],
|
||||
) {
|
||||
let scale = scale as usize;
|
||||
|
||||
if width >= HEADER_SIZE {
|
||||
// Draw the close button
|
||||
let btn_state = if mouses.iter().any(|&l| l == Location::Button(UIButton::Close)) {
|
||||
ButtonState::Hovered
|
||||
} else {
|
||||
ButtonState::Idle
|
||||
};
|
||||
|
||||
if state == WindowState::Active && btn_state == ButtonState::Hovered {
|
||||
draw_button(canvas, 0, scale, width as usize, BTN_HOVER_BG.to_ne_bytes());
|
||||
}
|
||||
draw_icon(canvas, width as usize, 0, scale, BTN_ICON_COLOR.to_ne_bytes(), Icon::Close);
|
||||
}
|
||||
|
||||
if width as usize >= 2 * HEADER_SIZE as usize {
|
||||
let btn_state = if !maximizable {
|
||||
ButtonState::Disabled
|
||||
} else if mouses.iter().any(|&l| l == Location::Button(UIButton::Maximize)) {
|
||||
ButtonState::Hovered
|
||||
} else {
|
||||
ButtonState::Idle
|
||||
};
|
||||
|
||||
if state == WindowState::Active && btn_state == ButtonState::Hovered {
|
||||
draw_button(
|
||||
canvas,
|
||||
HEADER_SIZE as usize,
|
||||
scale,
|
||||
width as usize,
|
||||
BTN_HOVER_BG.to_ne_bytes(),
|
||||
);
|
||||
}
|
||||
draw_icon(
|
||||
canvas,
|
||||
width as usize,
|
||||
HEADER_SIZE as usize,
|
||||
scale,
|
||||
BTN_ICON_COLOR.to_ne_bytes(),
|
||||
Icon::Maximize,
|
||||
);
|
||||
}
|
||||
|
||||
if width as usize >= 3 * HEADER_SIZE as usize {
|
||||
let btn_state = if mouses.iter().any(|&l| l == Location::Button(UIButton::Minimize)) {
|
||||
ButtonState::Hovered
|
||||
} else {
|
||||
ButtonState::Idle
|
||||
};
|
||||
|
||||
if state == WindowState::Active && btn_state == ButtonState::Hovered {
|
||||
draw_button(
|
||||
canvas,
|
||||
2 * HEADER_SIZE as usize,
|
||||
scale,
|
||||
width as usize,
|
||||
BTN_HOVER_BG.to_ne_bytes(),
|
||||
);
|
||||
}
|
||||
draw_icon(
|
||||
canvas,
|
||||
width as usize,
|
||||
2 * HEADER_SIZE as usize,
|
||||
scale,
|
||||
BTN_ICON_COLOR.to_ne_bytes(),
|
||||
Icon::Minimize,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum Icon {
|
||||
Close,
|
||||
Maximize,
|
||||
Minimize,
|
||||
}
|
||||
|
||||
fn draw_button(canvas: &mut [u8], x_offset: usize, scale: usize, width: usize, btn_color: [u8; 4]) {
|
||||
let h = HEADER_SIZE as usize;
|
||||
let x_start = width - h - x_offset;
|
||||
// main square
|
||||
for y in 0..h * scale {
|
||||
let canvas =
|
||||
&mut canvas[(x_start + y * width) * 4 * scale..(x_start + y * width + h) * scale * 4];
|
||||
for pixel in canvas.chunks_exact_mut(4) {
|
||||
pixel[0] = btn_color[0];
|
||||
pixel[1] = btn_color[1];
|
||||
pixel[2] = btn_color[2];
|
||||
pixel[3] = btn_color[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_icon(
|
||||
canvas: &mut [u8],
|
||||
width: usize,
|
||||
x_offset: usize,
|
||||
scale: usize,
|
||||
icon_color: [u8; 4],
|
||||
icon: Icon,
|
||||
) {
|
||||
let h = HEADER_SIZE as usize;
|
||||
let sh = scale * h;
|
||||
let x_start = width - h - x_offset;
|
||||
|
||||
match icon {
|
||||
Icon::Close => {
|
||||
// Draw black rectangle
|
||||
for y in sh / 4..3 * sh / 4 {
|
||||
let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale
|
||||
..(x_start + y * width + 3 * h / 4) * 4 * scale];
|
||||
for pixel in line.chunks_exact_mut(4) {
|
||||
pixel[0] = icon_color[0];
|
||||
pixel[1] = icon_color[1];
|
||||
pixel[2] = icon_color[2];
|
||||
pixel[3] = icon_color[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
Icon::Maximize => {
|
||||
// Draw an empty rectangle
|
||||
for y in 2 * sh / 8..3 * sh / 8 {
|
||||
let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale
|
||||
..(x_start + y * width + 3 * h / 4) * 4 * scale];
|
||||
for pixel in line.chunks_exact_mut(4) {
|
||||
pixel[0] = icon_color[0];
|
||||
pixel[1] = icon_color[1];
|
||||
pixel[2] = icon_color[2];
|
||||
pixel[3] = icon_color[3];
|
||||
}
|
||||
}
|
||||
for y in 3 * sh / 8..5 * sh / 8 {
|
||||
let line = &mut canvas[(x_start + y * width + 2 * h / 8) * 4 * scale
|
||||
..(x_start + y * width + 3 * h / 8) * 4 * scale];
|
||||
for pixel in line.chunks_exact_mut(4) {
|
||||
pixel[0] = icon_color[0];
|
||||
pixel[1] = icon_color[1];
|
||||
pixel[2] = icon_color[2];
|
||||
pixel[3] = icon_color[3];
|
||||
}
|
||||
let line = &mut canvas[(x_start + y * width + 5 * h / 8) * 4 * scale
|
||||
..(x_start + y * width + 6 * h / 8) * 4 * scale];
|
||||
for pixel in line.chunks_exact_mut(4) {
|
||||
pixel[0] = icon_color[0];
|
||||
pixel[1] = icon_color[1];
|
||||
pixel[2] = icon_color[2];
|
||||
pixel[3] = icon_color[3];
|
||||
}
|
||||
}
|
||||
for y in 5 * sh / 8..6 * sh / 8 {
|
||||
let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale
|
||||
..(x_start + y * width + 3 * h / 4) * 4 * scale];
|
||||
for pixel in line.chunks_exact_mut(4) {
|
||||
pixel[0] = icon_color[0];
|
||||
pixel[1] = icon_color[1];
|
||||
pixel[2] = icon_color[2];
|
||||
pixel[3] = icon_color[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
Icon::Minimize => {
|
||||
// Draw an underline
|
||||
for y in 5 * sh / 8..3 * sh / 4 {
|
||||
let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale
|
||||
..(x_start + y * width + 3 * h / 4) * 4 * scale];
|
||||
for pixel in line.chunks_exact_mut(4) {
|
||||
pixel[0] = icon_color[0];
|
||||
pixel[1] = icon_color[1];
|
||||
pixel[2] = icon_color[2];
|
||||
pixel[3] = icon_color[3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
857
third-party/vendor/smithay-client-toolkit/src/window/mod.rs
vendored
Normal file
857
third-party/vendor/smithay-client-toolkit/src/window/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,857 @@
|
|||
//! Window abstraction
|
||||
use std::cell::RefCell;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use wayland_client::protocol::{
|
||||
wl_compositor, wl_output, wl_seat, wl_shm, wl_subcompositor, wl_surface,
|
||||
};
|
||||
use wayland_client::{Attached, DispatchData};
|
||||
|
||||
use wayland_protocols::xdg_shell::client::xdg_toplevel::ResizeEdge;
|
||||
pub use wayland_protocols::xdg_shell::client::xdg_toplevel::State;
|
||||
|
||||
use wayland_protocols::unstable::xdg_decoration::v1::client::{
|
||||
zxdg_decoration_manager_v1::ZxdgDecorationManagerV1,
|
||||
zxdg_toplevel_decoration_v1::{self, ZxdgToplevelDecorationV1},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
environment::{Environment, GlobalHandler, MultiGlobalHandler},
|
||||
seat::pointer::ThemeManager,
|
||||
shell,
|
||||
};
|
||||
|
||||
mod fallback_frame;
|
||||
pub use self::fallback_frame::FallbackFrame;
|
||||
|
||||
// Defines the minimum window size. Minimum width is set to 2 pixels to circumvent
|
||||
// a bug in mutter - https://gitlab.gnome.org/GNOME/mutter/issues/259
|
||||
const MIN_WINDOW_SIZE: (u32, u32) = (2, 1);
|
||||
|
||||
/// Represents the status of a button
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ButtonState {
|
||||
/// Button is being hovered over by pointer
|
||||
Hovered,
|
||||
/// Button is not being hovered over by pointer
|
||||
Idle,
|
||||
/// Button is disabled
|
||||
Disabled,
|
||||
}
|
||||
|
||||
/// Represents the status of a window
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum WindowState {
|
||||
/// The window is active, in the foreground
|
||||
Active,
|
||||
/// The window is inactive, in the background
|
||||
Inactive,
|
||||
}
|
||||
|
||||
impl From<bool> for WindowState {
|
||||
fn from(b: bool) -> WindowState {
|
||||
if b {
|
||||
WindowState::Active
|
||||
} else {
|
||||
WindowState::Inactive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WindowState> for bool {
|
||||
fn from(s: WindowState) -> bool {
|
||||
match s {
|
||||
WindowState::Active => true,
|
||||
WindowState::Inactive => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible events generated by a window that you need to handle
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Event {
|
||||
/// The state of your window has been changed
|
||||
Configure {
|
||||
/// Optional new size for your *inner* surface
|
||||
///
|
||||
/// This is the new size of the contents of your window
|
||||
/// as suggested by the server. You can ignore it and choose
|
||||
/// a new size if you want better control on the possible
|
||||
/// sizes of your window.
|
||||
///
|
||||
/// The size is expressed in logical pixels, you need to multiply it by
|
||||
/// your buffer scale to get the actual number of pixels to draw.
|
||||
///
|
||||
/// In all cases, these events can be generated in large batches
|
||||
/// during an interactive resize, and you should buffer them before
|
||||
/// processing them. You only need to handle the last one of a batch.
|
||||
new_size: Option<(u32, u32)>,
|
||||
/// New combination of states of your window
|
||||
///
|
||||
/// Typically tells you if your surface is active/inactive, maximized,
|
||||
/// etc...
|
||||
states: Vec<State>,
|
||||
},
|
||||
/// A close request has been received
|
||||
///
|
||||
/// Most likely the user has clicked on the close button of the decorations
|
||||
/// or something equivalent
|
||||
Close,
|
||||
/// The decorations need to be refreshed
|
||||
Refresh,
|
||||
}
|
||||
|
||||
/// Possible decoration modes for a Window
|
||||
///
|
||||
/// This represents what your application requests from the server.
|
||||
///
|
||||
/// In any case, the compositor may override your requests. In that case SCTK
|
||||
/// will follow its decision.
|
||||
///
|
||||
/// If you don't care about it, you should use `FollowServer` (which is the
|
||||
/// SCTK default). It'd be the most ergonomic for your users.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Decorations {
|
||||
/// Request server-side decorations
|
||||
ServerSide,
|
||||
/// Force using the client-side `Frame`
|
||||
ClientSide,
|
||||
/// Follow the preference of the compositor
|
||||
FollowServer,
|
||||
/// Don't decorate the Window
|
||||
None,
|
||||
}
|
||||
|
||||
struct WindowInner<F> {
|
||||
frame: Rc<RefCell<F>>,
|
||||
shell_surface: Arc<Box<dyn shell::ShellSurface>>,
|
||||
user_impl: Box<dyn FnMut(Event, DispatchData)>,
|
||||
min_size: (u32, u32),
|
||||
max_size: Option<(u32, u32)>,
|
||||
current_size: (u32, u32),
|
||||
old_size: Option<(u32, u32)>,
|
||||
decorated: bool,
|
||||
}
|
||||
|
||||
impl<F> fmt::Debug for WindowInner<F>
|
||||
where
|
||||
F: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("WindowInner")
|
||||
.field("frame", &self.frame)
|
||||
.field("shell_surface", &self.shell_surface)
|
||||
.field("user_impl", &"Fn() -> { ... }")
|
||||
.field("min_size", &self.min_size)
|
||||
.field("max_size", &self.max_size)
|
||||
.field("current_size", &self.current_size)
|
||||
.field("old_size", &self.old_size)
|
||||
.field("decorated", &self.decorated)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A window
|
||||
///
|
||||
/// This wrapper handles for you the decoration of your window
|
||||
/// and the interaction with the server regarding the shell protocol.
|
||||
///
|
||||
/// You are still entirely responsible for drawing the contents of your
|
||||
/// window.
|
||||
///
|
||||
/// Note also that as the dimensions of wayland surfaces is defined by
|
||||
/// their attached buffer, you need to keep the decorations in sync with
|
||||
/// your contents via the `resize(..)` method.
|
||||
///
|
||||
/// Different kind of decorations can be used by customizing the type
|
||||
/// parameter. A few are provided in this crate if the `frames` cargo feature
|
||||
/// is enabled, but any type implementing the `Frame` trait can do.
|
||||
pub struct Window<F: Frame> {
|
||||
frame: Rc<RefCell<F>>,
|
||||
surface: wl_surface::WlSurface,
|
||||
decoration: Option<ZxdgToplevelDecorationV1>,
|
||||
shell_surface: Arc<Box<dyn shell::ShellSurface>>,
|
||||
inner: Rc<RefCell<Option<WindowInner<F>>>>,
|
||||
_seat_listener: crate::seat::SeatListener,
|
||||
}
|
||||
|
||||
impl<F: Frame + 'static> Window<F> {
|
||||
/// Create a new window wrapping a given wayland surface as its main content and
|
||||
/// following the compositor's preference regarding server-side decorations
|
||||
///
|
||||
/// It can fail if the initialization of the frame fails (for example if the
|
||||
/// frame class fails to initialize its SHM).
|
||||
///
|
||||
/// Providing non `None` value for `theme_manager` should prevent theming pointer
|
||||
/// over the `surface`.
|
||||
fn init_with_decorations<Impl, E>(
|
||||
env: &crate::environment::Environment<E>,
|
||||
surface: wl_surface::WlSurface,
|
||||
theme_manager: Option<ThemeManager>,
|
||||
initial_dims: (u32, u32),
|
||||
implementation: Impl,
|
||||
) -> Result<Window<F>, F::Error>
|
||||
where
|
||||
Impl: FnMut(Event, DispatchData) + 'static,
|
||||
E: GlobalHandler<wl_compositor::WlCompositor>
|
||||
+ GlobalHandler<wl_subcompositor::WlSubcompositor>
|
||||
+ GlobalHandler<wl_shm::WlShm>
|
||||
+ crate::shell::ShellHandling
|
||||
+ MultiGlobalHandler<wl_seat::WlSeat>
|
||||
+ GlobalHandler<ZxdgDecorationManagerV1>
|
||||
+ crate::seat::SeatHandling,
|
||||
{
|
||||
let compositor = env.require_global::<wl_compositor::WlCompositor>();
|
||||
let subcompositor = env.require_global::<wl_subcompositor::WlSubcompositor>();
|
||||
let shm = env.require_global::<wl_shm::WlShm>();
|
||||
let shell = env
|
||||
.get_shell()
|
||||
.expect("[SCTK] Cannot create a window if the compositor advertized no shell.");
|
||||
|
||||
let inner = Rc::new(RefCell::new(None::<WindowInner<F>>));
|
||||
let frame_inner = inner.clone();
|
||||
let shell_inner = inner.clone();
|
||||
let mut frame = F::init(
|
||||
&surface,
|
||||
&compositor,
|
||||
&subcompositor,
|
||||
&shm,
|
||||
theme_manager,
|
||||
Box::new(move |req, serial, ddata: DispatchData| {
|
||||
if let Some(ref mut inner) = *shell_inner.borrow_mut() {
|
||||
match req {
|
||||
FrameRequest::Minimize => inner.shell_surface.set_minimized(),
|
||||
FrameRequest::Maximize => inner.shell_surface.set_maximized(),
|
||||
FrameRequest::UnMaximize => inner.shell_surface.unset_maximized(),
|
||||
FrameRequest::Move(seat) => inner.shell_surface.move_(&seat, serial),
|
||||
FrameRequest::Resize(seat, edges) => {
|
||||
inner.shell_surface.resize(&seat, serial, edges)
|
||||
}
|
||||
FrameRequest::ShowMenu(seat, x, y) => {
|
||||
inner.shell_surface.show_window_menu(&seat, serial, x, y)
|
||||
}
|
||||
FrameRequest::Close => (inner.user_impl)(Event::Close, ddata),
|
||||
FrameRequest::Refresh => (inner.user_impl)(Event::Refresh, ddata),
|
||||
}
|
||||
}
|
||||
}) as Box<_>,
|
||||
)?;
|
||||
|
||||
let decoration_mgr = env.get_global::<ZxdgDecorationManagerV1>();
|
||||
if decoration_mgr.is_none() {
|
||||
// We don't have ServerSide decorations, so we'll be using CSD, and so should
|
||||
// mark frame as not hidden.
|
||||
frame.set_hidden(false);
|
||||
}
|
||||
|
||||
frame.resize(initial_dims);
|
||||
let frame = Rc::new(RefCell::new(frame));
|
||||
let shell_surface = Arc::new(shell::create_shell_surface(
|
||||
&shell,
|
||||
&surface,
|
||||
move |event, mut ddata: DispatchData| {
|
||||
let mut frame_inner = frame_inner.borrow_mut();
|
||||
let mut inner = match frame_inner.as_mut() {
|
||||
Some(inner) => inner,
|
||||
None => return,
|
||||
};
|
||||
|
||||
match event {
|
||||
shell::Event::Configure { states, mut new_size } => {
|
||||
let mut frame = inner.frame.borrow_mut();
|
||||
|
||||
// Populate frame changes. We should do it before performing new_size
|
||||
// recalculation, since we should account for a fullscreen state.
|
||||
let need_refresh = frame.set_states(&states);
|
||||
|
||||
// Clamp size.
|
||||
new_size = new_size.map(|(w, h)| {
|
||||
use std::cmp::{max, min};
|
||||
let (mut w, mut h) = frame.subtract_borders(w as i32, h as i32);
|
||||
let (minw, minh) = inner.min_size;
|
||||
w = max(w, minw as i32);
|
||||
h = max(h, minh as i32);
|
||||
if let Some((maxw, maxh)) = inner.max_size {
|
||||
w = min(w, maxw as i32);
|
||||
h = min(h, maxh as i32);
|
||||
}
|
||||
(max(w, 1) as u32, max(h, 1) as u32)
|
||||
});
|
||||
|
||||
// Check whether we should save old size for later restoration.
|
||||
let should_stash_size = states
|
||||
.iter()
|
||||
.find(|s| {
|
||||
matches!(
|
||||
*s,
|
||||
State::Maximized
|
||||
| State::Fullscreen
|
||||
| State::TiledTop
|
||||
| State::TiledRight
|
||||
| State::TiledBottom
|
||||
| State::TiledLeft
|
||||
)
|
||||
})
|
||||
.map(|_| true)
|
||||
.unwrap_or(false);
|
||||
|
||||
if should_stash_size {
|
||||
if inner.old_size.is_none() {
|
||||
// We are getting maximized/fullscreened, store the size for
|
||||
// restoration.
|
||||
inner.old_size = Some(inner.current_size);
|
||||
}
|
||||
} else if new_size.is_none() {
|
||||
// We are getting de-maximized/de-fullscreened/un-tiled, restore the
|
||||
// size, if we were not previously maximized/fullscreened, old_size is
|
||||
// None and this does nothing.
|
||||
new_size = inner.old_size.take();
|
||||
} else {
|
||||
// We are neither maximized nor fullscreened, but are given a size,
|
||||
// respect it and forget about the old size.
|
||||
inner.old_size = None;
|
||||
}
|
||||
|
||||
if need_refresh {
|
||||
(inner.user_impl)(Event::Refresh, ddata.reborrow());
|
||||
}
|
||||
(inner.user_impl)(Event::Configure { states, new_size }, ddata);
|
||||
}
|
||||
shell::Event::Close => {
|
||||
(inner.user_impl)(Event::Close, ddata);
|
||||
}
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
// setup size and geometry
|
||||
{
|
||||
let frame = frame.borrow_mut();
|
||||
let (minw, minh) =
|
||||
frame.add_borders(MIN_WINDOW_SIZE.0 as i32, MIN_WINDOW_SIZE.1 as i32);
|
||||
shell_surface.set_min_size(Some((minw, minh)));
|
||||
let (w, h) = frame.add_borders(initial_dims.0 as i32, initial_dims.1 as i32);
|
||||
let (x, y) = frame.location();
|
||||
shell_surface.set_geometry(x, y, w, h);
|
||||
}
|
||||
|
||||
// initial seat setup
|
||||
let mut seats = Vec::<wl_seat::WlSeat>::new();
|
||||
for seat in env.get_all_seats() {
|
||||
crate::seat::with_seat_data(&seat, |seat_data| {
|
||||
if seat_data.has_pointer && !seat_data.defunct {
|
||||
seats.push(seat.detach());
|
||||
frame.borrow_mut().new_seat(&seat);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// setup seat_listener
|
||||
let seat_frame = frame.clone();
|
||||
let seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
|
||||
let is_known = seats.contains(&seat);
|
||||
if !is_known && seat_data.has_pointer && !seat_data.defunct {
|
||||
seat_frame.borrow_mut().new_seat(&seat);
|
||||
seats.push(seat.detach());
|
||||
} else if is_known && ((!seat_data.has_pointer) || seat_data.defunct) {
|
||||
seat_frame.borrow_mut().remove_seat(&seat);
|
||||
seats.retain(|s| s != &*seat);
|
||||
}
|
||||
});
|
||||
|
||||
*inner.borrow_mut() = Some(WindowInner {
|
||||
frame: frame.clone(),
|
||||
shell_surface: shell_surface.clone(),
|
||||
user_impl: Box::new(implementation) as Box<_>,
|
||||
min_size: (MIN_WINDOW_SIZE.0, MIN_WINDOW_SIZE.1),
|
||||
max_size: None,
|
||||
current_size: initial_dims,
|
||||
old_size: None,
|
||||
decorated: true,
|
||||
});
|
||||
|
||||
// Setup window decorations if applicable.
|
||||
let decoration = Self::setup_decorations_handler(
|
||||
&decoration_mgr,
|
||||
&shell_surface,
|
||||
frame.clone(),
|
||||
inner.clone(),
|
||||
);
|
||||
|
||||
let window = Window {
|
||||
frame,
|
||||
shell_surface,
|
||||
decoration,
|
||||
surface,
|
||||
inner,
|
||||
_seat_listener: seat_listener,
|
||||
};
|
||||
|
||||
Ok(window)
|
||||
}
|
||||
|
||||
/// Setup handling for zxdg_toplevel_decoration_v1 in case protocol is available.
|
||||
fn setup_decorations_handler(
|
||||
decoration_mgr: &Option<Attached<ZxdgDecorationManagerV1>>,
|
||||
shell_surface: &Arc<Box<dyn shell::ShellSurface>>,
|
||||
decoration_frame: Rc<RefCell<F>>,
|
||||
decoration_inner: Rc<RefCell<Option<WindowInner<F>>>>,
|
||||
) -> Option<ZxdgToplevelDecorationV1> {
|
||||
let (toplevel, mgr) = match (shell_surface.get_xdg(), decoration_mgr) {
|
||||
(Some(toplevel), Some(ref mgr)) => (toplevel, mgr),
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let decoration = mgr.get_toplevel_decoration(toplevel);
|
||||
|
||||
decoration.quick_assign(move |_, event, _| {
|
||||
use self::zxdg_toplevel_decoration_v1::{Event, Mode};
|
||||
let mode = if let Event::Configure { mode } = event { mode } else { unreachable!() };
|
||||
|
||||
match mode {
|
||||
Mode::ServerSide => {
|
||||
decoration_frame.borrow_mut().set_hidden(true);
|
||||
}
|
||||
Mode::ClientSide => {
|
||||
let want_decorate = decoration_inner
|
||||
.borrow_mut()
|
||||
.as_ref()
|
||||
.map(|inner| inner.decorated)
|
||||
.unwrap_or(false);
|
||||
decoration_frame.borrow_mut().set_hidden(!want_decorate);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
|
||||
Some(decoration.detach())
|
||||
}
|
||||
|
||||
/// Access the surface wrapped in this Window
|
||||
pub fn surface(&self) -> &wl_surface::WlSurface {
|
||||
&self.surface
|
||||
}
|
||||
|
||||
/// Refreshes the frame
|
||||
///
|
||||
/// Redraws the frame to match its requested state (dimensions, presence/
|
||||
/// absence of decorations, ...)
|
||||
///
|
||||
/// You need to call this method after every change to the dimensions or state
|
||||
/// of the decorations of your window, otherwise the drawn decorations may go
|
||||
/// out of sync with the state of your content.
|
||||
///
|
||||
/// Your implementation will also receive `Refresh` events when the frame requests
|
||||
/// to be redrawn (to provide some frame animations for example).
|
||||
pub fn refresh(&mut self) {
|
||||
self.frame.borrow_mut().redraw();
|
||||
}
|
||||
|
||||
/// Set a short title for the window.
|
||||
///
|
||||
/// This string may be used to identify the surface in a task bar, window list, or other
|
||||
/// user interface elements provided by the compositor.
|
||||
///
|
||||
/// You need to call `refresh()` afterwards for this to properly
|
||||
/// take effect.
|
||||
pub fn set_title(&self, mut title: String) {
|
||||
// Truncate the title to at most 1024 bytes, so that it does not blow up the protocol
|
||||
// messages
|
||||
if title.len() > 1024 {
|
||||
let mut new_len = 1024;
|
||||
while !title.is_char_boundary(new_len) {
|
||||
new_len -= 1;
|
||||
}
|
||||
title.truncate(new_len);
|
||||
}
|
||||
self.frame.borrow_mut().set_title(title.clone());
|
||||
self.shell_surface.set_title(title);
|
||||
}
|
||||
|
||||
/// Set an app id for the surface.
|
||||
///
|
||||
/// The surface class identifies the general class of applications to which the surface
|
||||
/// belongs.
|
||||
///
|
||||
/// Several wayland compositors will try to find a `.desktop` file matching this name
|
||||
/// to find metadata about your apps.
|
||||
pub fn set_app_id(&self, app_id: String) {
|
||||
self.shell_surface.set_app_id(app_id);
|
||||
}
|
||||
|
||||
/// Set whether the window should be decorated or not.
|
||||
///
|
||||
/// If `zxdg_toplevel_decoration_v1` object is presented and alive, requesting `None`
|
||||
/// decorations will result in setting `ClientSide` decorations with hidden frame, and if
|
||||
/// `ClientSide` decorations were requested, it'll result in destroying
|
||||
/// `zxdg_toplevel_decoration_v1` object, meaning that you won't be able to get `ServerSide`
|
||||
/// decorations back.
|
||||
///
|
||||
/// In case `zxdg_toplevel_decoration_v1` is not available or the corresponding object is not
|
||||
/// alive anymore, `decorate` with `ServerSide` or `FollowServer` values will always result in
|
||||
/// `ClientSide` decorations being used.
|
||||
///
|
||||
/// You need to call `refresh()` afterwards for this to properly
|
||||
/// take effect.
|
||||
pub fn set_decorate(&mut self, decorate: Decorations) {
|
||||
use self::zxdg_toplevel_decoration_v1::Mode;
|
||||
|
||||
// Update inner.decorated state.
|
||||
if let Some(inner) = self.inner.borrow_mut().as_mut() {
|
||||
if Decorations::None == decorate {
|
||||
inner.decorated = false;
|
||||
} else {
|
||||
inner.decorated = true;
|
||||
}
|
||||
}
|
||||
|
||||
match self.decoration.as_ref() {
|
||||
// Server side decorations are there.
|
||||
Some(decoration) => {
|
||||
match decorate {
|
||||
Decorations::ClientSide => {
|
||||
// The user explicitly requested `ClientSide` decorations, we should destroy
|
||||
// the server side decorations if some are presented.
|
||||
decoration.destroy();
|
||||
self.decoration = None;
|
||||
self.frame.borrow_mut().set_hidden(false);
|
||||
}
|
||||
Decorations::ServerSide => {
|
||||
decoration.set_mode(Mode::ServerSide);
|
||||
}
|
||||
Decorations::FollowServer => {
|
||||
decoration.unset_mode();
|
||||
}
|
||||
Decorations::None => {
|
||||
// The user explicitly requested `None` decorations, however
|
||||
// since we can't destroy and recreate decoration object on the fly switch
|
||||
// them to `ClientSide` with the hidden frame. The server is free to ignore
|
||||
// us with such request, but not that we can do much about it.
|
||||
decoration.set_mode(Mode::ClientSide);
|
||||
self.frame.borrow_mut().set_hidden(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Server side decorations are not presented or were destroyed.
|
||||
None => {
|
||||
match decorate {
|
||||
// We map `ServerSide` and `FollowServer` decorations to `ClientSide`, since
|
||||
// server side decorations are no longer available.
|
||||
Decorations::ClientSide
|
||||
| Decorations::ServerSide
|
||||
| Decorations::FollowServer => {
|
||||
self.frame.borrow_mut().set_hidden(false);
|
||||
}
|
||||
Decorations::None => {
|
||||
self.frame.borrow_mut().set_hidden(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the window should be resizeable by the user
|
||||
///
|
||||
/// This is not an hard blocking, as the compositor can always
|
||||
/// resize you forcibly if it wants. However it signals it that
|
||||
/// you don't want this window to be resized.
|
||||
///
|
||||
/// Additionally, the decorations will stop suggesting the user
|
||||
/// to resize by dragging the borders if you set the window as
|
||||
/// non-resizable.
|
||||
///
|
||||
/// When re-activating resizability, any previously set min/max
|
||||
/// sizes are restored.
|
||||
pub fn set_resizable(&self, resizable: bool) {
|
||||
let mut frame = self.frame.borrow_mut();
|
||||
frame.set_resizable(resizable);
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if let Some(ref mut inner) = *inner {
|
||||
if resizable {
|
||||
// restore the min/max sizes
|
||||
self.shell_surface.set_min_size(
|
||||
Some(inner.min_size).map(|(w, h)| frame.add_borders(w as i32, h as i32)),
|
||||
);
|
||||
self.shell_surface.set_max_size(
|
||||
inner.max_size.map(|(w, h)| frame.add_borders(w as i32, h as i32)),
|
||||
);
|
||||
} else {
|
||||
// Lock the min/max sizes to current size.
|
||||
let (w, h) = inner.current_size;
|
||||
self.shell_surface.set_min_size(Some(frame.add_borders(w as i32, h as i32)));
|
||||
self.shell_surface.set_max_size(Some(frame.add_borders(w as i32, h as i32)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize the decorations
|
||||
///
|
||||
/// You should call this whenever you change the size of the contents
|
||||
/// of your window, with the new _inner size_ of your window.
|
||||
///
|
||||
/// This size is expressed in logical pixels, like the one received
|
||||
/// in [`Event::Configure`](enum.Event.html).
|
||||
///
|
||||
/// You need to call `refresh()` afterwards for this to properly
|
||||
/// take effect.
|
||||
pub fn resize(&mut self, w: u32, h: u32) {
|
||||
use std::cmp::max;
|
||||
let w = max(w, 1);
|
||||
let h = max(h, 1);
|
||||
if let Some(ref mut inner) = *self.inner.borrow_mut() {
|
||||
inner.current_size = (w, h);
|
||||
}
|
||||
let mut frame = self.frame.borrow_mut();
|
||||
frame.resize((w, h));
|
||||
let (w, h) = frame.add_borders(w as i32, h as i32);
|
||||
let (x, y) = frame.location();
|
||||
self.shell_surface.set_geometry(x, y, w, h);
|
||||
}
|
||||
|
||||
/// Request the window to be maximized
|
||||
pub fn set_maximized(&self) {
|
||||
self.shell_surface.set_maximized();
|
||||
}
|
||||
|
||||
/// Request the window to be un-maximized
|
||||
pub fn unset_maximized(&self) {
|
||||
self.shell_surface.unset_maximized();
|
||||
}
|
||||
|
||||
/// Request the window to be minimized
|
||||
pub fn set_minimized(&self) {
|
||||
self.shell_surface.set_minimized();
|
||||
}
|
||||
|
||||
/// Request the window to be set fullscreen
|
||||
///
|
||||
/// Note: The decorations hiding behavior is `Frame` dependant.
|
||||
/// To check whether you need to hide them consult your frame documentation.
|
||||
pub fn set_fullscreen(&self, output: Option<&wl_output::WlOutput>) {
|
||||
self.shell_surface.set_fullscreen(output);
|
||||
}
|
||||
|
||||
/// Request the window to quit fullscreen mode
|
||||
pub fn unset_fullscreen(&self) {
|
||||
self.shell_surface.unset_fullscreen();
|
||||
}
|
||||
|
||||
/// Sets the minimum possible size for this window
|
||||
///
|
||||
/// Provide either a tuple `Some((width, height))` or `None` to unset the
|
||||
/// minimum size.
|
||||
///
|
||||
/// Setting either value in the tuple to `0` means that this axis should not
|
||||
/// be limited.
|
||||
///
|
||||
/// The provided size is the interior size, not counting decorations.
|
||||
///
|
||||
/// This size is expressed in logical pixels, like the one received
|
||||
/// in [`Event::Configure`](enum.Event.html).
|
||||
pub fn set_min_size(&mut self, size: Option<(u32, u32)>) {
|
||||
let (w, h) = size.unwrap_or(MIN_WINDOW_SIZE);
|
||||
let (w, h) = self.frame.borrow_mut().add_borders(w as i32, h as i32);
|
||||
self.shell_surface.set_min_size(Some((w, h)));
|
||||
if let Some(ref mut inner) = *self.inner.borrow_mut() {
|
||||
inner.min_size = size.unwrap_or(MIN_WINDOW_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the maximum possible size for this window
|
||||
///
|
||||
/// Provide either a tuple `Some((width, height))` or `None` to unset the
|
||||
/// maximum size.
|
||||
///
|
||||
/// Setting either value in the tuple to `0` means that this axis should not
|
||||
/// be limited.
|
||||
///
|
||||
/// The provided size is the interior size, not counting decorations.
|
||||
///
|
||||
/// This size is expressed in logical pixels, like the one received
|
||||
/// in [`Event::Configure`](enum.Event.html).
|
||||
pub fn set_max_size(&mut self, size: Option<(u32, u32)>) {
|
||||
let max_size = size.map(|(w, h)| self.frame.borrow_mut().add_borders(w as i32, h as i32));
|
||||
self.shell_surface.set_max_size(max_size);
|
||||
if let Some(ref mut inner) = *self.inner.borrow_mut() {
|
||||
inner.max_size = size.map(|(w, h)| (w as u32, h as u32));
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the frame configuration for the window
|
||||
///
|
||||
/// This allows to configure the frame at runtime if it supports
|
||||
/// it. See the documentation of your `Frame` implementation for
|
||||
/// details about what configuration it supports.
|
||||
pub fn set_frame_config(&mut self, config: F::Config) {
|
||||
self.frame.borrow_mut().set_config(config)
|
||||
}
|
||||
|
||||
/// Start an interactive, user-driven move of the surface
|
||||
///
|
||||
/// This request must be used in response to some sort of user action
|
||||
/// like a button press, key press, or touch down event. The passed
|
||||
/// serial is used to determine the type of interactive move (touch,
|
||||
/// pointer, etc).
|
||||
///
|
||||
/// The server may ignore move requests depending on the state of
|
||||
/// the surface (e.g. fullscreen or maximized), or if the passed serial
|
||||
/// is no longer valid.
|
||||
pub fn start_interactive_move(&self, seat: &wl_seat::WlSeat, serial: u32) {
|
||||
self.shell_surface.move_(seat, serial);
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Frame> Drop for Window<F> {
|
||||
fn drop(&mut self) {
|
||||
self.inner.borrow_mut().take();
|
||||
|
||||
// Destroy decorations manager, so the inner frame could be dropped.
|
||||
if let Some(decoration) = self.decoration.take() {
|
||||
decoration.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Frame> fmt::Debug for Window<F>
|
||||
where
|
||||
F: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Window")
|
||||
.field("frame", &self.frame)
|
||||
.field("surface", &self.surface)
|
||||
.field("decoration", &self.decoration)
|
||||
.field("shell_surface", &self.shell_surface)
|
||||
.field("inner", &self.inner)
|
||||
.field("_seat_listener", &self._seat_listener)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Request generated by a Frame
|
||||
///
|
||||
/// These requests are generated by a Frame and the Window will
|
||||
/// forward them appropriately to the server.
|
||||
#[derive(Debug)]
|
||||
pub enum FrameRequest {
|
||||
/// The window should be minimized
|
||||
Minimize,
|
||||
/// The window should be maximized
|
||||
Maximize,
|
||||
/// The window should be unmaximized
|
||||
UnMaximize,
|
||||
/// The window should be closed
|
||||
Close,
|
||||
/// An interactive move should be started
|
||||
Move(wl_seat::WlSeat),
|
||||
/// An interactive resize should be started
|
||||
Resize(wl_seat::WlSeat, ResizeEdge),
|
||||
/// Show window menu.
|
||||
ShowMenu(wl_seat::WlSeat, i32, i32),
|
||||
/// The frame requests to be refreshed
|
||||
Refresh,
|
||||
}
|
||||
|
||||
/// Interface for defining the drawing of decorations
|
||||
///
|
||||
/// A type implementing this trait can be used to define custom
|
||||
/// decorations additionnaly to the ones provided by this crate
|
||||
/// and be used with `Window`.
|
||||
pub trait Frame: Sized {
|
||||
/// Type of errors that may occur when attempting to create a frame
|
||||
type Error;
|
||||
/// Configuration for this frame
|
||||
type Config;
|
||||
/// Initialize the Frame.
|
||||
///
|
||||
/// Providing non `None` to `theme_manager` should prevent `Frame` to theme pointer
|
||||
/// over `base_surface` surface.
|
||||
fn init(
|
||||
base_surface: &wl_surface::WlSurface,
|
||||
compositor: &Attached<wl_compositor::WlCompositor>,
|
||||
subcompositor: &Attached<wl_subcompositor::WlSubcompositor>,
|
||||
shm: &Attached<wl_shm::WlShm>,
|
||||
theme_manager: Option<ThemeManager>,
|
||||
callback: Box<dyn FnMut(FrameRequest, u32, DispatchData)>,
|
||||
) -> Result<Self, Self::Error>;
|
||||
/// Set the Window XDG states for the frame
|
||||
///
|
||||
/// This notably includes information about whether the window is
|
||||
/// maximized, active, or tiled, and can affect the way decorations
|
||||
/// are drawn.
|
||||
///
|
||||
/// Calling this should *not* trigger a redraw, but return `true` if
|
||||
/// a redraw is needed.
|
||||
fn set_states(&mut self, states: &[State]) -> bool;
|
||||
/// Hide or show the decorations
|
||||
///
|
||||
/// Calling this should *not* trigger a redraw
|
||||
fn set_hidden(&mut self, hidden: bool);
|
||||
/// Set whether interactive resize hints should be displayed
|
||||
/// and reacted to
|
||||
fn set_resizable(&mut self, resizable: bool);
|
||||
/// Notify that a new wl_seat should be handled
|
||||
///
|
||||
/// This seat is guaranteed to have pointer capability
|
||||
fn new_seat(&mut self, seat: &Attached<wl_seat::WlSeat>);
|
||||
/// Notify that this seat has lost the pointer capability or
|
||||
/// has been lost
|
||||
fn remove_seat(&mut self, seat: &wl_seat::WlSeat);
|
||||
/// Change the size of the decorations
|
||||
///
|
||||
/// Calling this should *not* trigger a redraw
|
||||
fn resize(&mut self, newsize: (u32, u32));
|
||||
/// Redraw the decorations
|
||||
fn redraw(&mut self);
|
||||
/// Subtracts the border dimensions from the given dimensions.
|
||||
fn subtract_borders(&self, width: i32, height: i32) -> (i32, i32);
|
||||
/// Adds the border dimensions to the given dimensions.
|
||||
fn add_borders(&self, width: i32, height: i32) -> (i32, i32);
|
||||
/// Returns the coordinates of the top-left corner of the borders relative to the content
|
||||
///
|
||||
/// Values should thus be negative
|
||||
fn location(&self) -> (i32, i32) {
|
||||
(0, 0)
|
||||
}
|
||||
/// Sets the configuration for the frame
|
||||
fn set_config(&mut self, config: Self::Config);
|
||||
|
||||
/// Sets the frames title
|
||||
fn set_title(&mut self, title: String);
|
||||
}
|
||||
|
||||
impl<E> Environment<E>
|
||||
where
|
||||
E: GlobalHandler<wl_compositor::WlCompositor>
|
||||
+ GlobalHandler<wl_subcompositor::WlSubcompositor>
|
||||
+ GlobalHandler<wl_shm::WlShm>
|
||||
+ crate::shell::ShellHandling
|
||||
+ MultiGlobalHandler<wl_seat::WlSeat>
|
||||
+ GlobalHandler<ZxdgDecorationManagerV1>
|
||||
+ crate::seat::SeatHandling,
|
||||
{
|
||||
/// Create a new window wrapping given surface
|
||||
///
|
||||
/// This window handles decorations for you, this includes
|
||||
/// drawing them if the compositor doe snot support them, resizing interactions
|
||||
/// and moving the window. It also provides close/maximize/minimize buttons.
|
||||
///
|
||||
/// Many interactions still require your input, and are given to you via the
|
||||
/// callback you need to provide.
|
||||
pub fn create_window<F: Frame + 'static, CB>(
|
||||
&self,
|
||||
surface: wl_surface::WlSurface,
|
||||
theme_manager: Option<ThemeManager>,
|
||||
initial_dims: (u32, u32),
|
||||
callback: CB,
|
||||
) -> Result<Window<F>, F::Error>
|
||||
where
|
||||
CB: FnMut(Event, DispatchData) + 'static,
|
||||
{
|
||||
Window::<F>::init_with_decorations(self, surface, theme_manager, initial_dims, callback)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue