Sync clipboard between X11 and Wayland
You would not believe how much work this was. Closes #23
This commit is contained in:
parent
601223d3ae
commit
5e7f2df05e
14 changed files with 1703 additions and 189 deletions
|
|
@ -8,7 +8,11 @@ use wayland_client::protocol::{
|
|||
wl_registry::WlRegistry, wl_seat::WlSeat, wl_shm::WlShm, wl_shm_pool::WlShmPool,
|
||||
wl_surface::WlSurface, wl_touch::WlTouch,
|
||||
};
|
||||
use wayland_client::{delegate_noop, Connection, Dispatch, EventQueue, Proxy, QueueHandle};
|
||||
use wayland_client::{
|
||||
delegate_noop,
|
||||
globals::{registry_queue_init, Global, GlobalList, GlobalListContents},
|
||||
Connection, Dispatch, EventQueue, Proxy, QueueHandle,
|
||||
};
|
||||
use wayland_protocols::wp::relative_pointer::zv1::client::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
zwp_relative_pointer_v1::ZwpRelativePointerV1,
|
||||
|
|
@ -40,17 +44,16 @@ use wayland_protocols::{
|
|||
use wayland_server::protocol as server;
|
||||
use wl_drm::client::wl_drm::WlDrm;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalData {
|
||||
pub name: u32,
|
||||
pub interface: String,
|
||||
pub version: u32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Globals {
|
||||
pub(crate) events: Vec<(ObjectKey, ObjectEvent)>,
|
||||
pub new_globals: Vec<GlobalData>,
|
||||
pub new_globals: Vec<Global>,
|
||||
pub selection: Option<wayland_client::protocol::wl_data_device::WlDataDevice>,
|
||||
pub selection_requests: Vec<(
|
||||
String,
|
||||
smithay_client_toolkit::data_device_manager::WritePipe,
|
||||
)>,
|
||||
pub cancelled: bool,
|
||||
}
|
||||
|
||||
pub type ClientQueueHandle = QueueHandle<Globals>;
|
||||
|
|
@ -65,7 +68,7 @@ pub struct ClientState {
|
|||
pub queue: EventQueue<Globals>,
|
||||
pub qh: ClientQueueHandle,
|
||||
pub globals: Globals,
|
||||
pub registry: WlRegistry,
|
||||
pub global_list: GlobalList,
|
||||
}
|
||||
|
||||
impl ClientState {
|
||||
|
|
@ -76,20 +79,16 @@ impl ClientState {
|
|||
Connection::connect_to_env()
|
||||
}
|
||||
.unwrap();
|
||||
let mut queue = connection.new_event_queue::<Globals>();
|
||||
let (global_list, queue) = registry_queue_init::<Globals>(&connection).unwrap();
|
||||
let globals = Globals::default();
|
||||
let qh = queue.handle();
|
||||
let mut globals = Globals::default();
|
||||
|
||||
let registry = connection.display().get_registry(&qh, ());
|
||||
// Get initial globals
|
||||
queue.roundtrip(&mut globals).unwrap();
|
||||
|
||||
Self {
|
||||
connection,
|
||||
queue,
|
||||
qh,
|
||||
globals,
|
||||
registry,
|
||||
global_list,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -109,12 +108,12 @@ delegate_noop!(Globals: WpViewport);
|
|||
delegate_noop!(Globals: ZxdgOutputManagerV1);
|
||||
delegate_noop!(Globals: ZwpPointerConstraintsV1);
|
||||
|
||||
impl Dispatch<WlRegistry, ()> for Globals {
|
||||
impl Dispatch<WlRegistry, GlobalListContents> for Globals {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &WlRegistry,
|
||||
event: <WlRegistry as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &GlobalListContents,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
) {
|
||||
|
|
@ -124,7 +123,7 @@ impl Dispatch<WlRegistry, ()> for Globals {
|
|||
version,
|
||||
} = event
|
||||
{
|
||||
state.new_globals.push(GlobalData {
|
||||
state.new_globals.push(Global {
|
||||
name,
|
||||
interface,
|
||||
version,
|
||||
|
|
|
|||
128
src/data_device.rs
Normal file
128
src/data_device.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
use crate::clientside::Globals;
|
||||
use smithay_client_toolkit::{
|
||||
data_device_manager::{
|
||||
data_device::DataDeviceHandler, data_offer::DataOfferHandler,
|
||||
data_source::DataSourceHandler,
|
||||
},
|
||||
delegate_data_device,
|
||||
};
|
||||
delegate_data_device!(Globals);
|
||||
|
||||
impl DataDeviceHandler for Globals {
|
||||
fn selection(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
data_device: &wayland_client::protocol::wl_data_device::WlDataDevice,
|
||||
) {
|
||||
self.selection = Some(data_device.clone());
|
||||
}
|
||||
|
||||
fn drop_performed(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_data_device::WlDataDevice,
|
||||
) {
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_data_device::WlDataDevice,
|
||||
) {
|
||||
}
|
||||
|
||||
fn leave(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_data_device::WlDataDevice,
|
||||
) {
|
||||
}
|
||||
|
||||
fn enter(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_data_device::WlDataDevice,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl DataSourceHandler for Globals {
|
||||
fn send_request(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_data_source::WlDataSource,
|
||||
mime: String,
|
||||
fd: smithay_client_toolkit::data_device_manager::WritePipe,
|
||||
) {
|
||||
self.selection_requests.push((mime, fd));
|
||||
}
|
||||
|
||||
fn cancelled(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_data_source::WlDataSource,
|
||||
) {
|
||||
self.cancelled = true;
|
||||
}
|
||||
|
||||
fn action(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_data_source::WlDataSource,
|
||||
_: wayland_client::protocol::wl_data_device_manager::DndAction,
|
||||
) {
|
||||
}
|
||||
|
||||
fn dnd_finished(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_data_source::WlDataSource,
|
||||
) {
|
||||
}
|
||||
|
||||
fn dnd_dropped(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_data_source::WlDataSource,
|
||||
) {
|
||||
}
|
||||
|
||||
fn accept_mime(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &wayland_client::protocol::wl_data_source::WlDataSource,
|
||||
_: Option<String>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl DataOfferHandler for Globals {
|
||||
fn selected_action(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &mut smithay_client_toolkit::data_device_manager::data_offer::DragOffer,
|
||||
_: wayland_client::protocol::wl_data_device_manager::DndAction,
|
||||
) {
|
||||
}
|
||||
|
||||
fn source_actions(
|
||||
&mut self,
|
||||
_: &wayland_client::Connection,
|
||||
_: &wayland_client::QueueHandle<Self>,
|
||||
_: &mut smithay_client_toolkit::data_device_manager::data_offer::DragOffer,
|
||||
_: wayland_client::protocol::wl_data_device_manager::DndAction,
|
||||
) {
|
||||
}
|
||||
}
|
||||
17
src/lib.rs
17
src/lib.rs
|
|
@ -1,4 +1,5 @@
|
|||
mod clientside;
|
||||
mod data_device;
|
||||
mod server;
|
||||
pub mod xstate;
|
||||
|
||||
|
|
@ -16,6 +17,7 @@ use xcb::x;
|
|||
|
||||
pub trait XConnection: Sized + 'static {
|
||||
type ExtraData: FromServerState<Self>;
|
||||
type MimeTypeData: MimeTypeData;
|
||||
|
||||
fn root_window(&self) -> x::Window;
|
||||
fn set_window_dims(&mut self, window: x::Window, dims: PendingSurfaceState);
|
||||
|
|
@ -28,6 +30,11 @@ pub trait FromServerState<C: XConnection> {
|
|||
fn create(state: &ServerState<C>) -> Self;
|
||||
}
|
||||
|
||||
pub trait MimeTypeData {
|
||||
fn name(&self) -> &str;
|
||||
fn data(&self) -> &[u8];
|
||||
}
|
||||
|
||||
type RealServerState = ServerState<Arc<xcb::Connection>>;
|
||||
|
||||
pub trait RunData {
|
||||
|
|
@ -152,12 +159,18 @@ pub fn main(data: impl RunData) -> Option<()> {
|
|||
server_state.atoms = Some(xstate.atoms.clone());
|
||||
}
|
||||
|
||||
if let Some(state) = &mut xstate {
|
||||
state.handle_events(&mut server_state);
|
||||
if let Some(xstate) = &mut xstate {
|
||||
xstate.handle_events(&mut server_state);
|
||||
}
|
||||
|
||||
display.dispatch_clients(&mut server_state).unwrap();
|
||||
server_state.run();
|
||||
display.flush_clients().unwrap();
|
||||
|
||||
if let Some(xstate) = &mut xstate {
|
||||
if let Some(sel) = server_state.new_selection() {
|
||||
xstate.set_clipboard(sel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use super::*;
|
||||
use log::{debug, error, trace, warn};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use wayland_client::globals::Global;
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
|
||||
|
|
@ -994,7 +995,7 @@ impl<T: Proxy> Default for ClientGlobalWrapper<T> {
|
|||
|
||||
macro_rules! global_dispatch_no_events {
|
||||
($server:ty, $client:ty) => {
|
||||
impl<C: XConnection> GlobalDispatch<$server, GlobalData> for ServerState<C>
|
||||
impl<C: XConnection> GlobalDispatch<$server, Global> for ServerState<C>
|
||||
where
|
||||
ServerState<C>: Dispatch<$server, ClientGlobalWrapper<$client>>,
|
||||
Globals: wayland_client::Dispatch<$client, ()>,
|
||||
|
|
@ -1004,7 +1005,7 @@ macro_rules! global_dispatch_no_events {
|
|||
_: &DisplayHandle,
|
||||
_: &wayland_server::Client,
|
||||
resource: wayland_server::New<$server>,
|
||||
data: &GlobalData,
|
||||
data: &Global,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
let client = ClientGlobalWrapper::<$client>::default();
|
||||
|
|
@ -1014,8 +1015,9 @@ macro_rules! global_dispatch_no_events {
|
|||
.set(
|
||||
state
|
||||
.clientside
|
||||
.registry
|
||||
.bind(data.name, server.version(), &state.qh, ()),
|
||||
.global_list
|
||||
.registry()
|
||||
.bind::<$client, _, _>(data.name, server.version(), &state.qh, ()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
|
@ -1025,7 +1027,7 @@ macro_rules! global_dispatch_no_events {
|
|||
|
||||
macro_rules! global_dispatch_with_events {
|
||||
($server:ty, $client:ty) => {
|
||||
impl<C: XConnection> GlobalDispatch<$server, GlobalData> for ServerState<C>
|
||||
impl<C: XConnection> GlobalDispatch<$server, Global> for ServerState<C>
|
||||
where
|
||||
$server: Resource,
|
||||
$client: Proxy,
|
||||
|
|
@ -1038,17 +1040,16 @@ macro_rules! global_dispatch_with_events {
|
|||
_: &DisplayHandle,
|
||||
_: &wayland_server::Client,
|
||||
resource: wayland_server::New<$server>,
|
||||
data: &GlobalData,
|
||||
data: &Global,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let server = data_init.init(resource, key);
|
||||
let client = state.clientside.registry.bind::<$client, _, _>(
|
||||
data.name,
|
||||
server.version(),
|
||||
&state.qh,
|
||||
key,
|
||||
);
|
||||
let client = state
|
||||
.clientside
|
||||
.global_list
|
||||
.registry()
|
||||
.bind::<$client, _, _>(data.name, server.version(), &state.qh, key);
|
||||
GenericObject { server, client }.into()
|
||||
});
|
||||
}
|
||||
|
|
@ -1070,7 +1071,36 @@ global_dispatch_no_events!(
|
|||
);
|
||||
global_dispatch_no_events!(PointerConstraintsServer, PointerConstraintsClient);
|
||||
|
||||
global_dispatch_with_events!(WlSeat, client::wl_seat::WlSeat);
|
||||
impl<C: XConnection> GlobalDispatch<WlSeat, Global> for ServerState<C>
|
||||
where
|
||||
WlSeat: Resource,
|
||||
client::wl_seat::WlSeat: Proxy,
|
||||
ServerState<C>: Dispatch<WlSeat, ObjectKey>,
|
||||
Globals: wayland_client::Dispatch<client::wl_seat::WlSeat, ObjectKey>,
|
||||
GenericObject<WlSeat, client::wl_seat::WlSeat>: Into<Object>,
|
||||
{
|
||||
fn bind(
|
||||
state: &mut Self,
|
||||
_: &DisplayHandle,
|
||||
_: &wayland_server::Client,
|
||||
resource: wayland_server::New<WlSeat>,
|
||||
data: &Global,
|
||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||
) {
|
||||
state.objects.insert_with_key(|key| {
|
||||
let server = data_init.init(resource, key);
|
||||
let client = state
|
||||
.clientside
|
||||
.global_list
|
||||
.registry()
|
||||
.bind::<client::wl_seat::WlSeat, _, _>(data.name, server.version(), &state.qh, key);
|
||||
if let Some(c) = &mut state.clipboard_data {
|
||||
c.device = Some(c.manager.get_data_device(&state.qh, &client));
|
||||
}
|
||||
GenericObject { server, client }.into()
|
||||
});
|
||||
}
|
||||
}
|
||||
global_dispatch_with_events!(WlOutput, client::wl_output::WlOutput);
|
||||
global_dispatch_with_events!(WlDrmServer, WlDrmClient);
|
||||
|
||||
|
|
|
|||
|
|
@ -493,45 +493,51 @@ impl HandleEvent for Keyboard {
|
|||
type Event = client::wl_keyboard::Event;
|
||||
|
||||
fn handle_event<C: XConnection>(&mut self, event: Self::Event, state: &mut ServerState<C>) {
|
||||
simple_event_shunt! {
|
||||
self.server, event: client::wl_keyboard::Event => [
|
||||
Keymap {
|
||||
|format| convert_wenum(format),
|
||||
|fd| fd.as_fd(),
|
||||
size
|
||||
},
|
||||
Enter {
|
||||
serial,
|
||||
|surface| state.get_server_surface_from_client(surface),
|
||||
keys
|
||||
},
|
||||
Leave {
|
||||
serial,
|
||||
|surface| {
|
||||
if !surface.is_alive() {
|
||||
return;
|
||||
match event {
|
||||
client::wl_keyboard::Event::Enter {
|
||||
serial,
|
||||
surface,
|
||||
keys,
|
||||
} => {
|
||||
state.last_kb_serial = Some(serial);
|
||||
self.server
|
||||
.enter(serial, state.get_server_surface_from_client(surface), keys);
|
||||
}
|
||||
_ => simple_event_shunt! {
|
||||
self.server, event: client::wl_keyboard::Event => [
|
||||
Keymap {
|
||||
|format| convert_wenum(format),
|
||||
|fd| fd.as_fd(),
|
||||
size
|
||||
},
|
||||
Leave {
|
||||
serial,
|
||||
|surface| {
|
||||
if !surface.is_alive() {
|
||||
return;
|
||||
}
|
||||
state.get_server_surface_from_client(surface)
|
||||
}
|
||||
state.get_server_surface_from_client(surface)
|
||||
},
|
||||
Key {
|
||||
serial,
|
||||
time,
|
||||
key,
|
||||
|state| convert_wenum(state)
|
||||
},
|
||||
Modifiers {
|
||||
serial,
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group
|
||||
},
|
||||
RepeatInfo {
|
||||
rate,
|
||||
delay
|
||||
}
|
||||
},
|
||||
Key {
|
||||
serial,
|
||||
time,
|
||||
key,
|
||||
|state| convert_wenum(state)
|
||||
},
|
||||
Modifiers {
|
||||
serial,
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group
|
||||
},
|
||||
RepeatInfo {
|
||||
rate,
|
||||
delay
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,21 @@ use self::event::*;
|
|||
use super::FromServerState;
|
||||
use crate::clientside::*;
|
||||
use crate::xstate::{Atoms, WindowDims, WmHints, WmName, WmNormalHints};
|
||||
use crate::XConnection;
|
||||
use crate::{MimeTypeData, XConnection};
|
||||
use log::{debug, warn};
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use slotmap::{new_key_type, HopSlotMap, SparseSecondaryMap};
|
||||
use smithay_client_toolkit::data_device_manager::{
|
||||
data_device::DataDevice, data_offer::SelectionOffer, data_source::CopyPasteSource,
|
||||
DataDeviceManagerState,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::os::fd::{AsFd, BorrowedFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use wayland_client::{protocol as client, Proxy};
|
||||
use std::rc::Rc;
|
||||
use wayland_client::{globals::Global, protocol as client, Proxy};
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
linux_dmabuf::zv1::{client as c_dmabuf, server as s_dmabuf},
|
||||
|
|
@ -372,6 +379,39 @@ impl ObjectMapExt for ObjectMap {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_globals<'a, C: XConnection>(
|
||||
dh: &DisplayHandle,
|
||||
globals: impl IntoIterator<Item = &'a Global>,
|
||||
) {
|
||||
for global in globals {
|
||||
macro_rules! server_global {
|
||||
($($global:ty),+) => {
|
||||
match global.interface {
|
||||
$(
|
||||
ref x if x == <$global>::interface().name => {
|
||||
dh.create_global::<ServerState<C>, $global, Global>(global.version, global.clone());
|
||||
}
|
||||
)+
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server_global![
|
||||
WlCompositor,
|
||||
WlShm,
|
||||
WlSeat,
|
||||
WlOutput,
|
||||
ZwpRelativePointerManagerV1,
|
||||
WlDrmServer,
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
ZxdgOutputManagerV1,
|
||||
s_vp::wp_viewporter::WpViewporter,
|
||||
ZwpPointerConstraintsV1
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
new_key_type! {
|
||||
pub struct ObjectKey;
|
||||
}
|
||||
|
|
@ -383,42 +423,43 @@ pub struct ServerState<C: XConnection> {
|
|||
associated_windows: SparseSecondaryMap<ObjectKey, x::Window>,
|
||||
windows: HashMap<x::Window, WindowData>,
|
||||
|
||||
xdg_wm_base: XdgWmBase,
|
||||
qh: ClientQueueHandle,
|
||||
to_focus: Option<x::Window>,
|
||||
last_focused_toplevel: Option<x::Window>,
|
||||
connection: Option<C>,
|
||||
}
|
||||
|
||||
const XDG_WM_BASE_VERSION: u32 = 2;
|
||||
xdg_wm_base: XdgWmBase,
|
||||
clipboard_data: Option<ClipboardData<C::MimeTypeData>>,
|
||||
last_kb_serial: Option<u32>,
|
||||
}
|
||||
|
||||
impl<C: XConnection> ServerState<C> {
|
||||
pub fn new(dh: DisplayHandle, server_connection: Option<UnixStream>) -> Self {
|
||||
let mut clientside = ClientState::new(server_connection);
|
||||
let clientside = ClientState::new(server_connection);
|
||||
let qh = clientside.qh.clone();
|
||||
|
||||
let xdg_pos = clientside
|
||||
.globals
|
||||
.new_globals
|
||||
.iter()
|
||||
.position(|g| g.interface == XdgWmBase::interface().name)
|
||||
.expect("Did not get an xdg_wm_base global");
|
||||
|
||||
let data = clientside.globals.new_globals.swap_remove(xdg_pos);
|
||||
|
||||
assert!(
|
||||
data.version >= XDG_WM_BASE_VERSION,
|
||||
"xdg_wm_base older than version {XDG_WM_BASE_VERSION}"
|
||||
);
|
||||
|
||||
let xdg_wm_base =
|
||||
clientside
|
||||
.registry
|
||||
.bind::<XdgWmBase, _, _>(data.name, XDG_WM_BASE_VERSION, &qh, ());
|
||||
let xdg_wm_base = clientside
|
||||
.global_list
|
||||
.bind::<XdgWmBase, _, _>(&qh, 2..=6, ())
|
||||
.expect("Could not bind XdgWmBase");
|
||||
let manager = DataDeviceManagerState::bind(&clientside.global_list, &qh)
|
||||
.inspect_err(|e| {
|
||||
warn!("Could not bind data device manager ({e:?}). Clipboard will not work.")
|
||||
})
|
||||
.ok();
|
||||
let clipboard_data = manager.map(|manager| ClipboardData {
|
||||
manager,
|
||||
device: None,
|
||||
source: None::<CopyPasteData<C::MimeTypeData>>,
|
||||
});
|
||||
|
||||
dh.create_global::<Self, XwaylandShellV1, _>(1, ());
|
||||
clientside
|
||||
.global_list
|
||||
.contents()
|
||||
.with_list(|globals| handle_globals::<C>(&dh, globals));
|
||||
|
||||
let mut ret = Self {
|
||||
Self {
|
||||
windows: HashMap::new(),
|
||||
clientside,
|
||||
atoms: None,
|
||||
|
|
@ -430,9 +471,9 @@ impl<C: XConnection> ServerState<C> {
|
|||
objects: Default::default(),
|
||||
associated_windows: Default::default(),
|
||||
xdg_wm_base,
|
||||
};
|
||||
ret.handle_new_globals();
|
||||
ret
|
||||
clipboard_data,
|
||||
last_kb_serial: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clientside_fd(&self) -> BorrowedFd<'_> {
|
||||
|
|
@ -451,33 +492,7 @@ impl<C: XConnection> ServerState<C> {
|
|||
|
||||
fn handle_new_globals(&mut self) {
|
||||
let globals = std::mem::take(&mut self.clientside.globals.new_globals);
|
||||
for data in globals {
|
||||
macro_rules! server_global {
|
||||
($($global:ty),+) => {
|
||||
match data.interface {
|
||||
$(
|
||||
ref x if x == <$global>::interface().name => {
|
||||
self.dh.create_global::<Self, $global, GlobalData>(data.version, data);
|
||||
}
|
||||
)+
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server_global![
|
||||
WlCompositor,
|
||||
WlShm,
|
||||
WlSeat,
|
||||
WlOutput,
|
||||
ZwpRelativePointerManagerV1,
|
||||
WlDrmServer,
|
||||
s_dmabuf::zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1,
|
||||
ZxdgOutputManagerV1,
|
||||
s_vp::wp_viewporter::WpViewporter,
|
||||
ZwpPointerConstraintsV1
|
||||
];
|
||||
}
|
||||
handle_globals::<C>(&self.dh, globals.iter());
|
||||
}
|
||||
|
||||
fn get_object_from_client_object<T, P: Proxy>(&self, proxy: &P) -> &T
|
||||
|
|
@ -638,6 +653,24 @@ impl<C: XConnection> ServerState<C> {
|
|||
let _ = self.windows.remove(&window);
|
||||
}
|
||||
|
||||
pub(crate) fn set_copy_paste_source(&mut self, mime_types: Rc<Vec<C::MimeTypeData>>) {
|
||||
if let Some(d) = &mut self.clipboard_data {
|
||||
let src = d
|
||||
.manager
|
||||
.create_copy_paste_source(&self.qh, mime_types.iter().map(|m| m.name()));
|
||||
let data = CopyPasteData::X11 {
|
||||
inner: src,
|
||||
data: mime_types,
|
||||
};
|
||||
let CopyPasteData::X11 { inner, .. } = d.source.insert(data) else {
|
||||
unreachable!();
|
||||
};
|
||||
if let Some(serial) = self.last_kb_serial.as_ref().copied() {
|
||||
inner.set_selection(d.device.as_ref().unwrap(), serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
if let Some(r) = self.clientside.queue.prepare_read() {
|
||||
let fd = r.connection_fd();
|
||||
|
|
@ -651,7 +684,6 @@ impl<C: XConnection> ServerState<C> {
|
|||
.dispatch_pending(&mut self.clientside.globals)
|
||||
.unwrap();
|
||||
self.handle_clientside_events();
|
||||
self.clientside.queue.flush().unwrap();
|
||||
}
|
||||
|
||||
pub fn handle_clientside_events(&mut self) {
|
||||
|
|
@ -676,9 +708,52 @@ impl<C: XConnection> ServerState<C> {
|
|||
}
|
||||
}
|
||||
|
||||
self.handle_clipboard_events();
|
||||
self.clientside.queue.flush().unwrap();
|
||||
}
|
||||
|
||||
pub fn new_selection(&mut self) -> Option<ForeignSelection> {
|
||||
self.clipboard_data.as_mut().and_then(|c| {
|
||||
c.source.take().and_then(|s| match s {
|
||||
CopyPasteData::Foreign(f) => Some(f),
|
||||
CopyPasteData::X11 { .. } => {
|
||||
c.source = Some(s);
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_clipboard_events(&mut self) {
|
||||
let globals = &mut self.clientside.globals;
|
||||
|
||||
if let Some(clipboard) = self.clipboard_data.as_mut() {
|
||||
for (mime_type, mut fd) in std::mem::take(&mut globals.selection_requests) {
|
||||
let CopyPasteData::X11 { data, .. } = clipboard.source.as_ref().unwrap() else {
|
||||
unreachable!()
|
||||
};
|
||||
let pos = data.iter().position(|m| m.name() == mime_type).unwrap();
|
||||
if let Err(e) = fd.write_all(data[pos].data()) {
|
||||
warn!("Failed to write selection data: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
if clipboard.source.is_none() || globals.cancelled {
|
||||
if globals.selection.take().is_some() {
|
||||
let device = clipboard.device.as_ref().unwrap();
|
||||
let offer = device.data().selection_offer().unwrap();
|
||||
let mime_types: Box<[String]> = offer.with_mime_types(|mimes| mimes.into());
|
||||
let foreign = ForeignSelection {
|
||||
mime_types,
|
||||
inner: offer,
|
||||
};
|
||||
clipboard.source = Some(CopyPasteData::Foreign(foreign));
|
||||
}
|
||||
globals.cancelled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_role_window(&mut self, window: x::Window, surface_key: ObjectKey) {
|
||||
let surface: &mut SurfaceData = self.objects[surface_key].as_mut();
|
||||
surface.window = Some(window);
|
||||
|
|
@ -837,3 +912,42 @@ pub struct PendingSurfaceState {
|
|||
pub width: i32,
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
struct ClipboardData<M: MimeTypeData> {
|
||||
manager: DataDeviceManagerState,
|
||||
device: Option<DataDevice>,
|
||||
source: Option<CopyPasteData<M>>,
|
||||
}
|
||||
|
||||
pub struct ForeignSelection {
|
||||
pub mime_types: Box<[String]>,
|
||||
inner: SelectionOffer,
|
||||
}
|
||||
|
||||
impl ForeignSelection {
|
||||
pub(crate) fn receive(
|
||||
&self,
|
||||
mime_type: String,
|
||||
state: &ServerState<impl XConnection>,
|
||||
) -> Vec<u8> {
|
||||
let mut pipe = self.inner.receive(mime_type).unwrap();
|
||||
state.clientside.queue.flush().unwrap();
|
||||
let mut data = Vec::new();
|
||||
pipe.read_to_end(&mut data).unwrap();
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ForeignSelection {
|
||||
fn drop(&mut self) {
|
||||
self.inner.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
enum CopyPasteData<M: MimeTypeData> {
|
||||
X11 {
|
||||
inner: CopyPasteSource,
|
||||
data: Rc<Vec<M>>,
|
||||
},
|
||||
Foreign(ForeignSelection),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::xstate::{SetState, WmName};
|
|||
use paste::paste;
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use std::collections::HashMap;
|
||||
use std::os::fd::BorrowedFd;
|
||||
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use wayland_client::{
|
||||
|
|
@ -12,8 +12,9 @@ use wayland_client::{
|
|||
wl_buffer::WlBuffer,
|
||||
wl_compositor::WlCompositor,
|
||||
wl_display::WlDisplay,
|
||||
wl_keyboard::WlKeyboard,
|
||||
wl_registry::WlRegistry,
|
||||
wl_seat::WlSeat,
|
||||
wl_seat::{self, WlSeat},
|
||||
wl_shm::{Format, WlShm},
|
||||
wl_shm_pool::WlShmPool,
|
||||
wl_surface::WlSurface,
|
||||
|
|
@ -85,7 +86,8 @@ with_optional! {
|
|||
struct Compositor {
|
||||
compositor: TestObject<WlCompositor>,
|
||||
shm: TestObject<WlShm>,
|
||||
shell: TestObject<XwaylandShellV1>
|
||||
shell: TestObject<XwaylandShellV1>,
|
||||
seat: TestObject<WlSeat>
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -140,7 +142,7 @@ impl FakeXConnection {
|
|||
fn window(&mut self, window: Window) -> &mut WindowData {
|
||||
self.windows
|
||||
.get_mut(&window)
|
||||
.expect(&format!("Unknown window: {window:?}"))
|
||||
.unwrap_or_else(|| panic!("Unknown window: {window:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -155,13 +157,22 @@ impl Default for FakeXConnection {
|
|||
}
|
||||
|
||||
impl super::FromServerState<FakeXConnection> for () {
|
||||
fn create(_: &FakeServerState) -> Self {
|
||||
()
|
||||
fn create(_: &FakeServerState) -> Self {}
|
||||
}
|
||||
|
||||
impl crate::MimeTypeData for testwl::PasteData {
|
||||
fn name(&self) -> &str {
|
||||
&self.mime_type
|
||||
}
|
||||
|
||||
fn data(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
impl super::XConnection for FakeXConnection {
|
||||
type ExtraData = ();
|
||||
type MimeTypeData = testwl::PasteData;
|
||||
fn root_window(&self) -> Window {
|
||||
self.root
|
||||
}
|
||||
|
|
@ -270,12 +281,7 @@ impl TestFixture {
|
|||
self.run();
|
||||
|
||||
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
|
||||
assert!(events.len() > 0);
|
||||
|
||||
let bind_req = |name, interface, version| Req::<WlRegistry>::Bind {
|
||||
name,
|
||||
id: (interface, version),
|
||||
};
|
||||
assert!(!events.is_empty());
|
||||
|
||||
for event in events {
|
||||
if let Ev::<WlRegistry>::Global {
|
||||
|
|
@ -284,23 +290,34 @@ impl TestFixture {
|
|||
version,
|
||||
} = event
|
||||
{
|
||||
let bind_req = |interface| Req::<WlRegistry>::Bind {
|
||||
name,
|
||||
id: (interface, version),
|
||||
};
|
||||
|
||||
match interface {
|
||||
x if x == WlCompositor::interface().name => {
|
||||
ret.compositor = Some(TestObject::from_request(
|
||||
®istry.obj,
|
||||
bind_req(name, WlCompositor::interface(), version),
|
||||
bind_req(WlCompositor::interface()),
|
||||
));
|
||||
}
|
||||
x if x == WlShm::interface().name => {
|
||||
ret.shm = Some(TestObject::from_request(
|
||||
®istry.obj,
|
||||
bind_req(name, WlShm::interface(), version),
|
||||
bind_req(WlShm::interface()),
|
||||
));
|
||||
}
|
||||
x if x == XwaylandShellV1::interface().name => {
|
||||
ret.shell = Some(TestObject::from_request(
|
||||
®istry.obj,
|
||||
bind_req(name, XwaylandShellV1::interface(), version),
|
||||
bind_req(XwaylandShellV1::interface()),
|
||||
));
|
||||
}
|
||||
x if x == WlSeat::interface().name => {
|
||||
ret.seat = Some(TestObject::from_request(
|
||||
®istry.obj,
|
||||
bind_req(WlSeat::interface()),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -500,7 +517,7 @@ impl TestFixture {
|
|||
};
|
||||
let dims = data.dims;
|
||||
self.new_window(window, true, data, None);
|
||||
self.map_window(&comp, window, &surface.obj, &buffer);
|
||||
self.map_window(comp, window, &surface.obj, &buffer);
|
||||
self.run();
|
||||
|
||||
let popup_id = self.check_new_surface();
|
||||
|
|
@ -734,7 +751,7 @@ fn pass_through_globals() {
|
|||
TestObject::<WlRegistry>::from_request(&display, Req::<WlDisplay>::GetRegistry {});
|
||||
f.run();
|
||||
let events = std::mem::take(&mut *registry.data.events.lock().unwrap());
|
||||
assert!(events.len() > 0);
|
||||
assert!(!events.is_empty());
|
||||
for event in events {
|
||||
let Ev::<WlRegistry>::Global { interface, .. } = event else {
|
||||
unreachable!();
|
||||
|
|
@ -969,6 +986,138 @@ fn window_group_properties() {
|
|||
assert_eq!(data.toplevel().title, Some("window".into()));
|
||||
assert_eq!(data.toplevel().app_id, Some("class".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_from_x11() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
TestObject::<WlKeyboard>::from_request(&comp.seat.obj, wl_seat::Request::GetKeyboard {});
|
||||
let win = unsafe { Window::new(1) };
|
||||
let (_surface, _id) = f.create_toplevel(&comp, win);
|
||||
|
||||
let mimes = std::rc::Rc::new(vec![
|
||||
testwl::PasteData {
|
||||
mime_type: "text".to_string(),
|
||||
data: b"abc".to_vec(),
|
||||
},
|
||||
testwl::PasteData {
|
||||
mime_type: "data".to_string(),
|
||||
data: vec![1, 2, 3, 4, 6, 10],
|
||||
},
|
||||
]);
|
||||
|
||||
f.exwayland.set_copy_paste_source(mimes.clone());
|
||||
f.run();
|
||||
|
||||
let server_mimes = f.testwl.data_source_mimes();
|
||||
for mime in mimes.iter() {
|
||||
assert!(server_mimes.contains(&mime.mime_type));
|
||||
}
|
||||
|
||||
let data = f.testwl.paste_data();
|
||||
f.run();
|
||||
let data = data.resolve();
|
||||
assert_eq!(*mimes, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_from_wayland() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
TestObject::<WlKeyboard>::from_request(&comp.seat.obj, wl_seat::Request::GetKeyboard {});
|
||||
let win = unsafe { Window::new(1) };
|
||||
let (_surface, _id) = f.create_toplevel(&comp, win);
|
||||
|
||||
let mimes = vec![
|
||||
testwl::PasteData {
|
||||
mime_type: "text".to_string(),
|
||||
data: b"abc".to_vec(),
|
||||
},
|
||||
testwl::PasteData {
|
||||
mime_type: "data".to_string(),
|
||||
data: vec![1, 2, 3, 4, 6, 10],
|
||||
},
|
||||
];
|
||||
f.testwl.create_data_offer(mimes.clone());
|
||||
f.run();
|
||||
|
||||
let selection = f.exwayland.new_selection().expect("No new selection");
|
||||
for mime in &mimes {
|
||||
let data = std::thread::scope(|s| {
|
||||
// receive requires a queue flush - dispatch testwl from another thread
|
||||
s.spawn(|| {
|
||||
let pollfd = unsafe { BorrowedFd::borrow_raw(f.testwl.poll_fd().as_raw_fd()) };
|
||||
let mut pollfd = [PollFd::from_borrowed_fd(pollfd, PollFlags::IN)];
|
||||
if poll(&mut pollfd, 100).unwrap() == 0 {
|
||||
panic!("Did not get events for testwl!");
|
||||
}
|
||||
f.testwl.dispatch();
|
||||
while poll(&mut pollfd, 100).unwrap() > 0 {
|
||||
f.testwl.dispatch();
|
||||
}
|
||||
});
|
||||
selection.receive(mime.mime_type.clone(), &f.exwayland)
|
||||
});
|
||||
f.run();
|
||||
assert_eq!(data, mime.data);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clipboard_x11_then_wayland() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
TestObject::<WlKeyboard>::from_request(&comp.seat.obj, wl_seat::Request::GetKeyboard {});
|
||||
let win = unsafe { Window::new(1) };
|
||||
let (_surface, _id) = f.create_toplevel(&comp, win);
|
||||
|
||||
let x11data = std::rc::Rc::new(vec![
|
||||
testwl::PasteData {
|
||||
mime_type: "text".to_string(),
|
||||
data: b"abc".to_vec(),
|
||||
},
|
||||
testwl::PasteData {
|
||||
mime_type: "data".to_string(),
|
||||
data: vec![1, 2, 3, 4, 6, 10],
|
||||
},
|
||||
]);
|
||||
|
||||
f.exwayland.set_copy_paste_source(x11data.clone());
|
||||
f.run();
|
||||
|
||||
let waylanddata = vec![
|
||||
testwl::PasteData {
|
||||
mime_type: "asdf".to_string(),
|
||||
data: b"fdaa".to_vec(),
|
||||
},
|
||||
testwl::PasteData {
|
||||
mime_type: "boing".to_string(),
|
||||
data: vec![10, 20, 40, 50],
|
||||
},
|
||||
];
|
||||
f.testwl.create_data_offer(waylanddata.clone());
|
||||
f.run();
|
||||
f.run();
|
||||
|
||||
let selection = f.exwayland.new_selection().expect("No new selection");
|
||||
for mime in &waylanddata {
|
||||
let data = std::thread::scope(|s| {
|
||||
// receive requires a queue flush - dispatch testwl from another thread
|
||||
s.spawn(|| {
|
||||
let pollfd = unsafe { BorrowedFd::borrow_raw(f.testwl.poll_fd().as_raw_fd()) };
|
||||
let mut pollfd = [PollFd::from_borrowed_fd(pollfd, PollFlags::IN)];
|
||||
if poll(&mut pollfd, 100).unwrap() == 0 {
|
||||
panic!("Did not get events for testwl!");
|
||||
}
|
||||
f.testwl.dispatch();
|
||||
while poll(&mut pollfd, 100).unwrap() > 0 {
|
||||
f.testwl.dispatch();
|
||||
}
|
||||
});
|
||||
selection.receive(mime.mime_type.clone(), &f.exwayland)
|
||||
});
|
||||
f.run();
|
||||
assert_eq!(data, mime.data);
|
||||
}
|
||||
}
|
||||
|
||||
/// See Pointer::handle_event for an explanation.
|
||||
#[test]
|
||||
fn popup_pointer_motion_workaround() {}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
mod selection;
|
||||
use selection::{SelectionData, SelectionTarget};
|
||||
|
||||
use crate::server::WindowAttributes;
|
||||
use bitflags::bitflags;
|
||||
use log::{debug, trace, warn};
|
||||
|
|
@ -7,12 +10,6 @@ use std::sync::Arc;
|
|||
use xcb::{x, Xid, XidNew};
|
||||
use xcb_util_cursor::{Cursor, CursorContext};
|
||||
|
||||
pub struct XState {
|
||||
pub connection: Arc<xcb::Connection>,
|
||||
root: x::Window,
|
||||
pub atoms: Atoms,
|
||||
}
|
||||
|
||||
// Sometimes we'll get events on windows that have already been destroyed
|
||||
#[derive(Debug)]
|
||||
enum MaybeBadWindow {
|
||||
|
|
@ -105,6 +102,14 @@ impl WmName {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct XState {
|
||||
pub connection: Arc<xcb::Connection>,
|
||||
pub atoms: Atoms,
|
||||
root: x::Window,
|
||||
wm_window: x::Window,
|
||||
selection_data: SelectionData,
|
||||
}
|
||||
|
||||
impl XState {
|
||||
pub fn new(fd: BorrowedFd) -> Self {
|
||||
let connection = Arc::new(xcb::Connection::connect_to_fd(fd.as_raw_fd(), None).unwrap());
|
||||
|
|
@ -144,10 +149,14 @@ impl XState {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
let wm_window = connection.generate_id();
|
||||
|
||||
let mut r = Self {
|
||||
connection,
|
||||
wm_window,
|
||||
root,
|
||||
atoms,
|
||||
selection_data: Default::default(),
|
||||
};
|
||||
r.create_ewmh_window();
|
||||
r
|
||||
|
|
@ -166,11 +175,10 @@ impl XState {
|
|||
}
|
||||
|
||||
fn create_ewmh_window(&mut self) {
|
||||
let window = self.connection.generate_id();
|
||||
self.connection
|
||||
.send_and_check_request(&x::CreateWindow {
|
||||
depth: 0,
|
||||
wid: window,
|
||||
wid: self.wm_window,
|
||||
parent: self.root,
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
|
@ -183,29 +191,31 @@ impl XState {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
self.set_root_property(self.atoms.wm_check, x::ATOM_WINDOW, &[window]);
|
||||
self.set_root_property(self.atoms.wm_check, x::ATOM_WINDOW, &[self.wm_window]);
|
||||
self.set_root_property(self.atoms.active_win, x::ATOM_WINDOW, &[x::Window::none()]);
|
||||
self.set_root_property(self.atoms.supported, x::ATOM_ATOM, &[self.atoms.active_win]);
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
window: self.wm_window,
|
||||
property: self.atoms.wm_check,
|
||||
r#type: x::ATOM_WINDOW,
|
||||
data: &[window],
|
||||
data: &[self.wm_window],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
window: self.wm_window,
|
||||
property: self.atoms.net_wm_name,
|
||||
r#type: x::ATOM_STRING,
|
||||
data: b"exwayland wm",
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.set_clipboard_owner(x::CURRENT_TIME);
|
||||
}
|
||||
|
||||
pub fn handle_events(&mut self, server_state: &mut super::RealServerState) {
|
||||
|
|
@ -225,6 +235,11 @@ impl XState {
|
|||
}
|
||||
while let Some(event) = self.connection.poll_for_event().unwrap() {
|
||||
trace!("x11 event: {event:?}");
|
||||
|
||||
if self.handle_selection_event(&event, server_state) {
|
||||
continue;
|
||||
}
|
||||
|
||||
match event {
|
||||
xcb::Event::X(x::Event::CreateNotify(e)) => {
|
||||
debug!("new window: {:?}", e);
|
||||
|
|
@ -383,6 +398,14 @@ impl XState {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_atom_name(&self, atom: x::Atom) -> String {
|
||||
self.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::GetAtomName { atom }))
|
||||
.unwrap()
|
||||
.name()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn get_window_attributes(&self, window: x::Window) -> XResult<WindowAttributes> {
|
||||
let geometry = self.connection.send_request(&x::GetGeometry {
|
||||
drawable: x::Drawable::Window(window),
|
||||
|
|
@ -564,7 +587,7 @@ impl XState {
|
|||
server_state: &mut super::RealServerState,
|
||||
) {
|
||||
if event.state() != x::Property::NewValue {
|
||||
println!("ignoring non newvalue for property {:?}", event.atom());
|
||||
debug!("ignoring non newvalue for property {:?}", event.atom());
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -597,15 +620,11 @@ impl XState {
|
|||
}
|
||||
_ => {
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
let prop = self
|
||||
.connection
|
||||
.wait_for_reply(
|
||||
self.connection
|
||||
.send_request(&x::GetAtomName { atom: event.atom() }),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
debug!("changed property {:?} for {:?}", prop.name(), window);
|
||||
debug!(
|
||||
"changed property {:?} for {:?}",
|
||||
self.get_atom_name(event.atom()),
|
||||
window
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -629,6 +648,11 @@ xcb::atoms_struct! {
|
|||
pub client_list => b"_NET_CLIENT_LIST" only_if_exists = false,
|
||||
pub supported => b"_NET_SUPPORTED" only_if_exists = false,
|
||||
pub utf8_string => b"UTF8_STRING" only_if_exists = false,
|
||||
pub clipboard => b"CLIPBOARD" only_if_exists = false,
|
||||
pub targets => b"TARGETS" only_if_exists = false,
|
||||
pub multiple => b"MULTIPLE" only_if_exists = false,
|
||||
pub timestamp => b"TIMESTAMP" only_if_exists = false,
|
||||
pub selection_reply => b"_selection_reply" only_if_exists = false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -746,6 +770,7 @@ impl TryFrom<u32> for SetState {
|
|||
|
||||
impl super::XConnection for Arc<xcb::Connection> {
|
||||
type ExtraData = Atoms;
|
||||
type MimeTypeData = SelectionTarget;
|
||||
|
||||
fn root_window(&self) -> x::Window {
|
||||
self.get_setup().roots().next().unwrap().root()
|
||||
387
src/xstate/selection.rs
Normal file
387
src/xstate/selection.rs
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
use super::XState;
|
||||
use crate::server::ForeignSelection;
|
||||
use crate::{MimeTypeData, RealServerState};
|
||||
use log::{debug, warn};
|
||||
use std::rc::Rc;
|
||||
use xcb::x;
|
||||
|
||||
enum TargetValue {
|
||||
U8(Vec<u8>),
|
||||
U16(Vec<u16>),
|
||||
U32(Vec<u32>),
|
||||
Foreign,
|
||||
}
|
||||
|
||||
pub struct SelectionTarget {
|
||||
name: String,
|
||||
atom: x::Atom,
|
||||
value: Option<TargetValue>,
|
||||
}
|
||||
|
||||
impl MimeTypeData for SelectionTarget {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn data(&self) -> &[u8] {
|
||||
match self.value.as_ref() {
|
||||
Some(TargetValue::U8(v)) => v,
|
||||
other => {
|
||||
if let Some(other) = other {
|
||||
warn!(
|
||||
"Unexpectedly requesting data from mime type with data type {} - nothing will be copied",
|
||||
std::any::type_name_of_val(other)
|
||||
);
|
||||
}
|
||||
&[]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct SelectionData {
|
||||
clear_time: Option<u32>,
|
||||
mime_types: Rc<Vec<SelectionTarget>>,
|
||||
/// List of property on self.wm_window and corresponding index in mime_types
|
||||
mime_destinations: Vec<(x::Atom, usize)>,
|
||||
foreign_data: Option<ForeignSelection>,
|
||||
}
|
||||
|
||||
impl XState {
|
||||
pub(crate) fn set_clipboard_owner(&mut self, time: u32) {
|
||||
self.connection
|
||||
.send_and_check_request(&x::SetSelectionOwner {
|
||||
owner: self.wm_window,
|
||||
selection: self.atoms.clipboard,
|
||||
time,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let reply = self
|
||||
.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::GetSelectionOwner {
|
||||
selection: self.atoms.clipboard,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
if reply.owner() != self.wm_window {
|
||||
warn!(
|
||||
"Could not get CLIPBOARD selection (owned by {:?})",
|
||||
reply.owner()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_clipboard(&mut self, selection: ForeignSelection) {
|
||||
let types = selection
|
||||
.mime_types
|
||||
.iter()
|
||||
.map(|mime| {
|
||||
let atom = self
|
||||
.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::InternAtom {
|
||||
only_if_exists: false,
|
||||
name: mime.as_bytes(),
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
SelectionTarget {
|
||||
name: mime.clone(),
|
||||
atom: atom.atom(),
|
||||
value: Some(TargetValue::Foreign),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.selection_data.mime_types = Rc::new(types);
|
||||
self.selection_data.foreign_data = Some(selection);
|
||||
}
|
||||
|
||||
pub(crate) fn handle_selection_event(
|
||||
&mut self,
|
||||
event: &xcb::Event,
|
||||
server_state: &mut RealServerState,
|
||||
) -> bool {
|
||||
match event {
|
||||
xcb::Event::X(x::Event::SelectionClear(e)) => {
|
||||
if e.selection() != self.atoms.clipboard {
|
||||
warn!(
|
||||
"Got SelectionClear for unexpected atom {}, ignoring",
|
||||
self.get_atom_name(e.selection())
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// get the mime types
|
||||
self.connection
|
||||
.send_and_check_request(&x::ConvertSelection {
|
||||
requestor: self.wm_window,
|
||||
selection: self.atoms.clipboard,
|
||||
target: self.atoms.targets,
|
||||
property: self.atoms.selection_reply,
|
||||
time: e.time(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.selection_data.clear_time = Some(e.time());
|
||||
}
|
||||
xcb::Event::X(x::Event::SelectionNotify(e)) => {
|
||||
if e.property() == x::ATOM_NONE {
|
||||
warn!("selection notify fail?");
|
||||
return true;
|
||||
}
|
||||
|
||||
match e.target() {
|
||||
x if x == self.atoms.targets => self.handle_target_list(e.property()),
|
||||
x if x == self.atoms.multiple => self.handle_new_clipboard_data(server_state),
|
||||
atom => {
|
||||
warn!(
|
||||
"unexpected SelectionNotify type: {}",
|
||||
self.get_atom_name(atom)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
xcb::Event::X(x::Event::SelectionRequest(e)) => {
|
||||
let send_notify = |property| {
|
||||
self.connection
|
||||
.send_and_check_request(&x::SendEvent {
|
||||
propagate: false,
|
||||
destination: x::SendEventDest::Window(e.requestor()),
|
||||
event_mask: x::EventMask::empty(),
|
||||
event: &x::SelectionNotifyEvent::new(
|
||||
e.time(),
|
||||
e.requestor(),
|
||||
e.selection(),
|
||||
e.target(),
|
||||
property,
|
||||
),
|
||||
})
|
||||
.unwrap();
|
||||
};
|
||||
let refuse = || send_notify(x::ATOM_NONE);
|
||||
let success = || send_notify(e.property());
|
||||
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
let target = self.get_atom_name(e.target());
|
||||
debug!("Got selection request for target {target}");
|
||||
}
|
||||
|
||||
if e.property() == x::ATOM_NONE {
|
||||
debug!("refusing - property is set to none");
|
||||
refuse();
|
||||
return true;
|
||||
}
|
||||
|
||||
match e.target() {
|
||||
x if x == self.atoms.targets => {
|
||||
let atoms: Box<[x::Atom]> = self
|
||||
.selection_data
|
||||
.mime_types
|
||||
.iter()
|
||||
.map(|t| t.atom)
|
||||
.collect();
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: e.requestor(),
|
||||
property: e.property(),
|
||||
r#type: x::ATOM_ATOM,
|
||||
data: &atoms,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
success();
|
||||
}
|
||||
other => {
|
||||
let Some(target) = self
|
||||
.selection_data
|
||||
.mime_types
|
||||
.iter()
|
||||
.find(|t| t.atom == other)
|
||||
else {
|
||||
debug!("refusing selection requst because given atom could not be found ({other:?})");
|
||||
refuse();
|
||||
return true;
|
||||
};
|
||||
|
||||
macro_rules! set_property {
|
||||
($data:expr) => {
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: e.requestor(),
|
||||
property: e.property(),
|
||||
r#type: target.atom,
|
||||
data: $data,
|
||||
})
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
match target.value.as_ref().unwrap() {
|
||||
TargetValue::U8(v) => set_property!(v),
|
||||
TargetValue::U16(v) => set_property!(v),
|
||||
TargetValue::U32(v) => set_property!(v),
|
||||
TargetValue::Foreign => {
|
||||
let data = self
|
||||
.selection_data
|
||||
.foreign_data
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.receive(target.name.clone(), server_state);
|
||||
set_property!(&data);
|
||||
}
|
||||
}
|
||||
|
||||
success();
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn handle_target_list(&mut self, dest_property: x::Atom) {
|
||||
let reply = self
|
||||
.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::GetProperty {
|
||||
delete: true,
|
||||
window: self.wm_window,
|
||||
property: dest_property,
|
||||
r#type: x::ATOM_ATOM,
|
||||
long_offset: 0,
|
||||
long_length: 20,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let targets: &[x::Atom] = reply.value();
|
||||
let target_props: Box<[x::Atom]> = targets
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|atom| ![self.atoms.targets, self.atoms.multiple].contains(atom))
|
||||
.enumerate()
|
||||
.flat_map(|(idx, target)| {
|
||||
let name = [b"dest", idx.to_string().as_bytes()].concat();
|
||||
let reply = self
|
||||
.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::InternAtom {
|
||||
name: &name,
|
||||
only_if_exists: false,
|
||||
}))
|
||||
.unwrap();
|
||||
let dest = reply.atom();
|
||||
|
||||
[target, dest]
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window: self.wm_window,
|
||||
property: self.atoms.selection_reply,
|
||||
r#type: x::ATOM_ATOM,
|
||||
data: &target_props,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::ConvertSelection {
|
||||
requestor: self.wm_window,
|
||||
selection: self.atoms.clipboard,
|
||||
target: self.atoms.multiple,
|
||||
property: self.atoms.selection_reply,
|
||||
time: self.selection_data.clear_time.as_ref().copied().unwrap(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let (types, dests) = target_props
|
||||
.chunks_exact(2)
|
||||
.enumerate()
|
||||
.map(|(idx, atoms)| {
|
||||
let [target, property] = atoms.try_into().unwrap();
|
||||
let name = self
|
||||
.connection
|
||||
.wait_for_reply(
|
||||
self.connection
|
||||
.send_request(&x::GetAtomName { atom: target }),
|
||||
)
|
||||
.unwrap();
|
||||
let name = name.name().to_string();
|
||||
let target = SelectionTarget {
|
||||
atom: target,
|
||||
name,
|
||||
value: None,
|
||||
};
|
||||
let dest = (property, idx);
|
||||
(target, dest)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
self.selection_data.mime_types = Rc::new(types);
|
||||
self.selection_data.mime_destinations = dests;
|
||||
}
|
||||
|
||||
fn handle_new_clipboard_data(&mut self, server_state: &mut RealServerState) {
|
||||
for (property, idx) in std::mem::take(&mut self.selection_data.mime_destinations) {
|
||||
let types = Rc::get_mut(&mut self.selection_data.mime_types).unwrap();
|
||||
let target = &mut types[idx];
|
||||
let data = {
|
||||
if target.atom == self.atoms.timestamp {
|
||||
TargetValue::U32(vec![self
|
||||
.selection_data
|
||||
.clear_time
|
||||
.as_ref()
|
||||
.copied()
|
||||
.unwrap()])
|
||||
} else {
|
||||
let reply = self
|
||||
.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::GetProperty {
|
||||
delete: true,
|
||||
window: self.wm_window,
|
||||
property,
|
||||
r#type: x::ATOM_ANY,
|
||||
long_offset: 0,
|
||||
long_length: u32::MAX,
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
match reply.format() {
|
||||
8 => TargetValue::U8(reply.value().to_vec()),
|
||||
16 => TargetValue::U16(reply.value().to_vec()),
|
||||
32 => TargetValue::U32(reply.value().to_vec()),
|
||||
other => {
|
||||
let atom = target.atom;
|
||||
let target = self.get_atom_name(atom);
|
||||
let ty = if reply.r#type() == x::ATOM_NONE {
|
||||
"None".to_string()
|
||||
} else {
|
||||
self.get_atom_name(reply.r#type())
|
||||
};
|
||||
warn!("unexpected format: {other} (atom: {target}, type: {ty:?}, property: {property:?}) - copies as this type will fail!");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
target.value = Some(data);
|
||||
}
|
||||
|
||||
self.connection
|
||||
.send_and_check_request(&x::DeleteProperty {
|
||||
window: self.wm_window,
|
||||
property: self.atoms.selection_reply,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.set_clipboard_owner(self.selection_data.clear_time.unwrap());
|
||||
server_state.set_copy_paste_source(Rc::clone(&self.selection_data.mime_types));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue