355 lines
14 KiB
Rust
355 lines
14 KiB
Rust
extern crate smithay_client_toolkit as sctk;
|
|
|
|
use std::io::{BufWriter, Read, Seek, SeekFrom, Write};
|
|
|
|
use sctk::{
|
|
data_device::{DataSourceEvent, ReadPipe},
|
|
environment::Environment,
|
|
primary_selection::PrimarySelectionSourceEvent,
|
|
seat::keyboard::{map_keyboard_repeat, Event as KbEvent, KeyState, RepeatKind},
|
|
shm::MemPool,
|
|
window::{ConceptFrame, Event as WEvent},
|
|
};
|
|
|
|
use sctk::reexports::{
|
|
calloop::{LoopHandle, Source},
|
|
client::{
|
|
protocol::{wl_keyboard, wl_seat, wl_shm, wl_surface},
|
|
DispatchData,
|
|
},
|
|
};
|
|
|
|
sctk::default_environment!(SelectionExample, desktop);
|
|
|
|
// Here the type parameter is a global value that will be shared by
|
|
// all callbacks invoked by the event loop.
|
|
type DData = (Environment<SelectionExample>, Option<WEvent>, Option<Source<ReadPipe>>);
|
|
|
|
fn main() {
|
|
/*
|
|
* Initial setup
|
|
*/
|
|
let (env, display, queue) = sctk::new_default_environment!(SelectionExample, desktop)
|
|
.expect("Unable to connect to a Wayland compositor");
|
|
|
|
/*
|
|
* Prepare a calloop event loop to handle clipboard reading
|
|
*/
|
|
let mut event_loop = sctk::reexports::calloop::EventLoop::<DData>::new().unwrap();
|
|
|
|
// we need a window to receive things actually
|
|
let mut dimensions = (320u32, 240u32);
|
|
let surface = env.create_surface().detach();
|
|
|
|
let mut window = env
|
|
.create_window::<ConceptFrame, _>(
|
|
surface,
|
|
None,
|
|
dimensions,
|
|
move |evt, mut dispatch_data| {
|
|
let (_, next_action, _) = dispatch_data.get::<DData>().unwrap();
|
|
// Keep last event in priority order : Close > Configure > Refresh
|
|
let replace = match (&evt, &*next_action) {
|
|
(_, &None)
|
|
| (_, &Some(WEvent::Refresh))
|
|
| (&WEvent::Configure { .. }, &Some(WEvent::Configure { .. }))
|
|
| (&WEvent::Close, _) => true,
|
|
_ => false,
|
|
};
|
|
if replace {
|
|
*next_action = Some(evt);
|
|
}
|
|
},
|
|
)
|
|
.expect("Failed to create a window !");
|
|
window.set_title("Selection example".to_string());
|
|
|
|
println!("Press c/C p/P to copy/paste from selection/primary clipboard respectively.");
|
|
|
|
let mut pools = env.create_double_pool(|_| {}).expect("Failed to create a memory pool !");
|
|
|
|
let mut seats = Vec::<(String, Option<wl_keyboard::WlKeyboard>)>::new();
|
|
|
|
// first process already existing seats
|
|
for seat in env.get_all_seats() {
|
|
if let Some((has_kbd, name)) = sctk::seat::with_seat_data(&seat, |seat_data| {
|
|
(seat_data.has_keyboard && !seat_data.defunct, seat_data.name.clone())
|
|
}) {
|
|
if has_kbd {
|
|
let my_seat = seat.clone();
|
|
let handle = event_loop.handle();
|
|
match map_keyboard_repeat(
|
|
event_loop.handle(),
|
|
&seat,
|
|
None,
|
|
RepeatKind::System,
|
|
move |event, _, ddata| process_keyboard_event(event, &my_seat, &handle, ddata),
|
|
) {
|
|
Ok((kbd, _)) => {
|
|
seats.push((name, Some(kbd)));
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Failed to map keyboard on seat {} : {:?}.", name, e);
|
|
seats.push((name, None));
|
|
}
|
|
}
|
|
} else {
|
|
seats.push((name, None));
|
|
}
|
|
}
|
|
}
|
|
|
|
// then setup a listener for changes
|
|
let loop_handle = event_loop.handle();
|
|
let _seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
|
|
// find the seat in the vec of seats, or insert it if it is unknown
|
|
let idx = seats.iter().position(|(name, _)| name == &seat_data.name);
|
|
let idx = idx.unwrap_or_else(|| {
|
|
seats.push((seat_data.name.clone(), None));
|
|
seats.len() - 1
|
|
});
|
|
|
|
let (_, ref mut opt_kbd) = &mut seats[idx];
|
|
// we should map a keyboard if the seat has the capability & is not defunct
|
|
if seat_data.has_keyboard && !seat_data.defunct {
|
|
if opt_kbd.is_none() {
|
|
// we should initalize a keyboard
|
|
let my_seat = seat.clone();
|
|
let handle = loop_handle.clone();
|
|
match map_keyboard_repeat(
|
|
handle.clone(),
|
|
&seat,
|
|
None,
|
|
RepeatKind::System,
|
|
move |event, _, ddata| process_keyboard_event(event, &my_seat, &handle, ddata),
|
|
) {
|
|
Ok((kbd, _)) => {
|
|
*opt_kbd = Some(kbd);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Failed to map keyboard on seat {} : {:?}.", seat_data.name, e)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if let Some(kbd) = opt_kbd.take() {
|
|
// the keyboard has been removed, cleanup
|
|
kbd.release();
|
|
}
|
|
}
|
|
});
|
|
|
|
if !env.get_shell().unwrap().needs_configure() {
|
|
// initial draw to bootstrap on wl_shell
|
|
if let Some(pool) = pools.pool() {
|
|
redraw(pool, window.surface(), dimensions).expect("Failed to draw")
|
|
}
|
|
window.refresh();
|
|
}
|
|
|
|
// the data that will be shared to all callbacks
|
|
let mut data: DData = (env, None, None);
|
|
|
|
sctk::WaylandSource::new(queue).quick_insert(event_loop.handle()).unwrap();
|
|
|
|
loop {
|
|
match data.1.take() {
|
|
Some(WEvent::Close) => break,
|
|
Some(WEvent::Refresh) => {
|
|
window.refresh();
|
|
window.surface().commit();
|
|
}
|
|
Some(WEvent::Configure { new_size, .. }) => {
|
|
if let Some((w, h)) = new_size {
|
|
window.resize(w, h);
|
|
dimensions = (w, h)
|
|
}
|
|
window.refresh();
|
|
if let Some(pool) = pools.pool() {
|
|
redraw(pool, window.surface(), dimensions).expect("Failed to draw")
|
|
}
|
|
}
|
|
None => {}
|
|
}
|
|
|
|
// always flush the connection before going to sleep waiting for events
|
|
display.flush().unwrap();
|
|
|
|
event_loop.dispatch(None, &mut data).unwrap();
|
|
}
|
|
}
|
|
|
|
fn process_keyboard_event(
|
|
event: KbEvent,
|
|
seat: &wl_seat::WlSeat,
|
|
handle: &LoopHandle<DData>,
|
|
mut ddata: DispatchData,
|
|
) {
|
|
let (env, _, opt_source) = ddata.get::<DData>().unwrap();
|
|
match event {
|
|
KbEvent::Key { state, utf8: Some(text), serial, .. } => {
|
|
if text == "p" && state == KeyState::Pressed {
|
|
// pressed the 'p' key, try to read contents !
|
|
env.with_data_device(seat, |device| {
|
|
device.with_selection(|offer| {
|
|
let offer = match offer {
|
|
Some(offer) => offer,
|
|
None => {
|
|
println!("No current selection buffer!");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let seat_name =
|
|
sctk::seat::with_seat_data(seat, |data| data.name.clone()).unwrap();
|
|
print!("Current selection buffer mime types on seat '{}': [ ", seat_name);
|
|
let mut has_text = false;
|
|
offer.with_mime_types(|types| {
|
|
for t in types {
|
|
print!("\"{}\", ", t);
|
|
if t == "text/plain;charset=utf-8" {
|
|
has_text = true;
|
|
}
|
|
}
|
|
});
|
|
println!("]");
|
|
if has_text {
|
|
println!("Buffer contains text, going to read it...");
|
|
let reader = offer.receive("text/plain;charset=utf-8".into()).unwrap();
|
|
let src_handle = handle.clone();
|
|
let source = handle
|
|
.insert_source(reader, move |(), file, ddata| {
|
|
let mut txt = String::new();
|
|
file.read_to_string(&mut txt).unwrap();
|
|
println!("Selection contents are: \"{}\"", txt);
|
|
if let Some(src) = ddata.2.take() {
|
|
src_handle.kill(src);
|
|
}
|
|
})
|
|
.unwrap();
|
|
*opt_source = Some(source);
|
|
}
|
|
});
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
if text == "P" && state == KeyState::Pressed {
|
|
env.with_primary_selection(seat, |primary_selection| {
|
|
println!("In primary selection closure");
|
|
primary_selection.with_selection(|offer| {
|
|
let offer = match offer {
|
|
Some(offer) => offer,
|
|
None => {
|
|
println!("No current primary selection buffer!");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let seat_name =
|
|
sctk::seat::with_seat_data(seat, |data| data.name.clone()).unwrap();
|
|
print!(
|
|
"Current primary selection buffer mime type on seat '{}': [ ",
|
|
seat_name
|
|
);
|
|
|
|
let mut has_text = false;
|
|
offer.with_mime_types(|types| {
|
|
for t in types {
|
|
print!("\"{}\", ", t);
|
|
if t == "text/plain;charset=utf-8" {
|
|
has_text = true;
|
|
}
|
|
}
|
|
});
|
|
println!("]");
|
|
if has_text {
|
|
println!("Buffer contains text, going to read it...");
|
|
let reader = offer.receive("text/plain;charset=utf-8".into()).unwrap();
|
|
let src_handle = handle.clone();
|
|
let source = handle
|
|
.insert_source(reader, move |(), file, ddata| {
|
|
let mut txt = String::new();
|
|
file.read_to_string(&mut txt).unwrap();
|
|
println!("Selection contents are: \"{}\"", txt);
|
|
if let Some(src) = ddata.2.take() {
|
|
src_handle.kill(src);
|
|
}
|
|
})
|
|
.unwrap();
|
|
|
|
*opt_source = Some(source);
|
|
}
|
|
})
|
|
})
|
|
.unwrap()
|
|
}
|
|
|
|
if text == "c" && state == KeyState::Pressed {
|
|
let data_source = env.new_data_source(
|
|
vec!["text/plain;charset=utf-8".into()],
|
|
move |event, _| match event {
|
|
DataSourceEvent::Send { mut pipe, .. } => {
|
|
let contents = "Hello from clipboard";
|
|
println!("Setting clipboard to: {}", &contents);
|
|
write!(pipe, "{}", contents).unwrap();
|
|
}
|
|
_ => (),
|
|
},
|
|
);
|
|
|
|
env.with_data_device(&seat, |device| {
|
|
println!("Set selection source");
|
|
device.set_selection(&Some(data_source), serial);
|
|
})
|
|
.unwrap();
|
|
}
|
|
|
|
if text == "C" && state == KeyState::Pressed {
|
|
let data_source = env.new_primary_selection_source(
|
|
vec!["text/plain;charset=utf-8".into()],
|
|
move |event, _| match event {
|
|
PrimarySelectionSourceEvent::Send { mut pipe, .. } => {
|
|
let contents = "Hello from primary selection";
|
|
println!("Setting clipboard primary clipboard to {}", &contents);
|
|
write!(pipe, "{}", contents).unwrap();
|
|
}
|
|
_ => (),
|
|
},
|
|
);
|
|
|
|
env.with_primary_selection(&seat, |device| {
|
|
println!("Set primary selection source");
|
|
device.set_selection(&Some(data_source), serial);
|
|
})
|
|
.unwrap();
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
fn redraw(
|
|
pool: &mut MemPool,
|
|
surface: &wl_surface::WlSurface,
|
|
(buf_x, buf_y): (u32, u32),
|
|
) -> Result<(), ::std::io::Error> {
|
|
// resize the pool if relevant
|
|
pool.resize((4 * buf_x * buf_y) as usize).expect("Failed to resize the memory pool.");
|
|
// write the contents, a nice color gradient =)
|
|
pool.seek(SeekFrom::Start(0))?;
|
|
{
|
|
let mut writer = BufWriter::new(&mut *pool);
|
|
for _ in 0..(buf_x * buf_y) {
|
|
writer.write(&0xFF000000u32.to_ne_bytes())?;
|
|
}
|
|
writer.flush()?;
|
|
}
|
|
// get a buffer and attach it
|
|
let new_buffer =
|
|
pool.buffer(0, buf_x as i32, buf_y as i32, 4 * buf_x as i32, wl_shm::Format::Argb8888);
|
|
surface.attach(Some(&new_buffer), 0, 0);
|
|
surface.commit();
|
|
Ok(())
|
|
}
|