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
|
|
@ -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() {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue