Initial commit

This commit is contained in:
Lucas Timmins 2019-02-10 01:28:53 +08:00
commit 17011ea9f7
10 changed files with 510 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

76
.travis.yml Normal file
View file

@ -0,0 +1,76 @@
language: rust
# only cache cargo subcommand binaries and wayland libs .so
# the build artifacts take a lot of space and are slower to
# cache than to actually rebuild anyway...
# We need to cache the whole .cargo directory to keep the
# .crates.toml file.
cache:
directories:
- /home/travis/.cargo
# But don't cache the cargo registry
before_cache:
- rm -rf /home/travis/.cargo/registry
dist: trusty
sudo: required
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
include:
- rust: stable
env: BUILD_FMT=1
- rust: stable
env: BUILD_DOC=1
branches:
only:
- master
before_script:
- cargo fetch
- |
if [ -n "$BUILD_FMT" ]; then
rustup component add rustfmt-preview
elif [ -n "$BUILD_DOC" ]; then
echo "Building doc, nothing to install..."
fi
os:
- linux
script:
- |
if [ -n "$BUILD_FMT" ]; then
cargo fmt -- --check
elif [ -n "$BUILD_DOC" ]; then
cargo doc --no-deps --all-features
fi
after_success:
- |
if [ -n "$BUILD_DOC" ]; then
cp ./doc_index.html ./target/doc/index.html
fi
deploy:
provider: pages
skip_cleanup: true
github_token: $GITHUB_TOKEN
local_dir: "target/doc"
on:
branch: master
rust: stable
condition: $BUILD_DOC = 1
notifications:
webhooks:
urls:
- "https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MGxldmFucyUzQXNhZmFyYWRlZy5uZXQvJTIxRkt4aGprSUNwakJWelZlQ2RGJTNBc2FmYXJhZGVnLm5ldA"
on_success: change
on_failure: always
on_start: never

0
CHANGELOG.md Normal file
View file

9
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,9 @@
# Contributing
Smithay Wayland Clipboard is open to contributions from anyone.
## Smithay Project
There is a Matrix room dedicated to the Smithay project:
[#smithay:matrix.org](https://matrix.to/#/#smithay:matrix.org). If you don't want to use matrix, this room is
also bridged to gitter: https://gitter.im/smithay/Lobby.

11
Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "smithay-wayland-clipboard"
version = "0.1.0"
authors = ["Lucas Timmins <timmins.s.lucas@gmail.com>"]
edition = "2018"
[dependencies]
sctk = { package = "smithay-client-toolkit", version = "0.5" }
[dev-dependencies]
andrew = "0.2"

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2018 Lucas Timmins & Victor Berger
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

13
README.md Normal file
View file

@ -0,0 +1,13 @@
[![crates.io](http://meritbadge.herokuapp.com/smithay-wayland-clipboard)](https://crates.io/crates/smithay-wayland-clipboard)
[![Build Status](https://travis-ci.org/Smithay/wayland-clipboard.svg?branch=master)](https://travis-ci.org/Smithay/wayland-clipboard)
# Smithay Wayland Clipboard
This crate provides access to the wayland clipboard with only requirement being a WlDisplay object.
## Documentation
The documentation for the master branch is [available online](https://smithay.github.io/wayland-clipboard/).
The documentation for the releases can be found on [docs.rs](https://docs.rs/smithay-wayland-clipboard).

6
doc_index.html Normal file
View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv=refresh content=0;url=smithay_wayland_clipboard/index.html />
</head>
</html>

214
examples/clipboard.rs Normal file
View file

@ -0,0 +1,214 @@
use std::io::{Read, Seek, SeekFrom, Write};
use std::sync::{Arc, Mutex};
use sctk::keyboard::{map_keyboard_auto, Event as KbEvent, KeyState};
use sctk::utils::{DoubleMemPool, MemPool};
use sctk::window::{ConceptFrame, Event as WEvent, Window};
use sctk::Environment;
use sctk::reexports::client::protocol::{wl_shm, wl_surface};
use sctk::reexports::client::{Display, NewProxy};
use andrew::shapes::rectangle;
use andrew::text;
use andrew::text::fontconfig;
fn main() {
let (display, mut event_queue) =
Display::connect_to_env().expect("Failed to connect to the wayland server.");
let env = Environment::from_display(&*display, &mut event_queue).unwrap();
let mut clipboard = wayland_clipboard::WaylandClipboard::new_threaded(
display.get_display_ptr() as *mut std::ffi::c_void,
);
let cb_contents = Arc::new(Mutex::new(String::new()));
let seat = env
.manager
.instantiate_range(1, 6, NewProxy::implement_dummy)
.unwrap();
let cb_contents_clone = cb_contents.clone();
map_keyboard_auto(&seat, move |event: KbEvent, _| {
if let KbEvent::Key {
state: KeyState::Pressed,
utf8: Some(text),
..
} = event
{
if text == " " {
*cb_contents_clone.lock().unwrap() = dbg!(clipboard.load());
} else if text == "s" {
clipboard
.store("This is an example text thats been copied to the wayland clipboard :)");
}
}
})
.unwrap();
let mut dimensions = (320u32, 240u32);
let surface = env
.compositor
.create_surface(NewProxy::implement_dummy)
.unwrap();
let next_action = Arc::new(Mutex::new(None::<WEvent>));
let waction = next_action.clone();
let mut window = Window::<ConceptFrame>::init_from_env(&env, surface, dimensions, move |evt| {
let mut next_action = waction.lock().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.new_seat(&seat);
window.set_title("Clipboard".to_string());
let mut pools = DoubleMemPool::new(&env.shm, || {}).expect("Failed to create a memory pool !");
let mut font_data = Vec::new();
std::fs::File::open(
&fontconfig::FontConfig::new()
.unwrap()
.get_regular_family_fonts("sans")
.unwrap()[0],
)
.unwrap()
.read_to_end(&mut font_data)
.unwrap();
if !env.shell.needs_configure() {
// initial draw to bootstrap on wl_shell
if let Some(pool) = pools.pool() {
redraw(
pool,
window.surface(),
dimensions,
&font_data,
"".to_string(),
);
}
window.refresh();
}
loop {
match next_action.lock().unwrap().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,
&font_data,
cb_contents.lock().unwrap().clone(),
);
}
}
None => {}
}
event_queue.dispatch().unwrap();
}
}
fn redraw(
pool: &mut MemPool,
surface: &wl_surface::WlSurface,
dimensions: (u32, u32),
font_data: &[u8],
cb_contents: String,
) {
let (buf_x, buf_y) = (dimensions.0 as usize, dimensions.1 as usize);
pool.resize(4 * buf_x * buf_y)
.expect("Failed to resize the memory pool.");
let mut buf: Vec<u8> = vec![0; 4 * buf_x * buf_y];
let mut canvas =
andrew::Canvas::new(&mut buf, buf_x, buf_y, 4 * buf_x, andrew::Endian::native());
let bg = rectangle::Rectangle::new((0, 0), (buf_x, buf_y), None, Some([255, 170, 20, 45]));
canvas.draw(&bg);
let text_box = rectangle::Rectangle::new(
(buf_x / 30, buf_y / 35),
(buf_x - 2 * (buf_x / 30), (buf_x as f32 / 14.) as usize),
Some((3, [255, 255, 255, 255], rectangle::Sides::ALL, Some(4))),
None,
);
canvas.draw(&text_box);
let helper_text = text::Text::new(
(buf_x / 25, buf_y / 30),
[255, 255, 255, 255],
font_data,
buf_x as f32 / 40.,
2.0,
"Press space to draw clipboard contents",
);
canvas.draw(&helper_text);
let helper_text = text::Text::new(
(buf_x / 25, buf_y / 15),
[255, 255, 255, 255],
font_data,
buf_x as f32 / 40.,
2.0,
"Press 's' to store example text to clipboard",
);
canvas.draw(&helper_text);
for i in (0..cb_contents.len()).step_by(36) {
let content = if cb_contents.len() < i + 36 {
cb_contents[i..].to_string()
} else {
cb_contents[i..i + 36].to_string()
};
let text = text::Text::new(
(
buf_x / 10,
buf_y / 8 + (i as f32 * buf_y as f32 / 1000.) as usize,
),
[255, 255, 255, 255],
font_data,
buf_x as f32 / 40.,
2.0,
content,
);
canvas.draw(&text);
}
pool.seek(SeekFrom::Start(0)).unwrap();
pool.write_all(canvas.buffer).unwrap();
pool.flush().unwrap();
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();
}

159
src/lib.rs Normal file
View file

@ -0,0 +1,159 @@
//! Smithay Wayland Clipboard
//!
//! Provides access to the wayland clipboard with only requirement being a WlDisplay
//! object
//!
//! ```no_run
//! let (display, mut event_queue) =
//! Display::connect_to_env().expect("Failed to connect to the wayland server.");
//! let mut clipboard = wayland_clipboard::WaylandClipboard::new_threaded(
//! display.get_display_ptr() as *mut std::ffi::c_void,
//! );
//! clipboard.store("Test data");
//! println!(clipboard.load());
//! ```
#![warn(missing_docs)]
use std::io::{Read, Write};
use std::os::raw::c_void;
use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::thread::sleep;
use std::time::Duration;
use sctk::data_device::DataDevice;
use sctk::data_device::DataSource;
use sctk::data_device::DataSourceEvent;
use sctk::keyboard::{map_keyboard_auto, Event as KbEvent};
use sctk::reexports::client::Display;
use sctk::wayland_client::sys::client::wl_display;
use sctk::Environment;
enum WaylandRequest {
Store(String),
Load,
Kill,
}
/// Object representing the Wayland clipboard
pub struct WaylandClipboard {
request_send: mpsc::Sender<WaylandRequest>,
load_recv: mpsc::Receiver<String>,
}
impl Drop for WaylandClipboard {
fn drop(&mut self) {
self.request_send.send(WaylandRequest::Kill).unwrap()
}
}
impl WaylandClipboard {
/// Creates a new WaylandClipboard object
///
/// Spawns a new thread to dispatch messages to the wayland server every
/// 50ms to ensure the server can read stored data
pub fn new_threaded(wayland_display: *mut c_void) -> Self {
let (request_send, request_recv) = mpsc::channel::<WaylandRequest>();
let (load_send, load_recv) = mpsc::channel();
let wayland_display = unsafe { (wayland_display as *mut wl_display).as_mut().unwrap() };
std::thread::spawn(move || {
let (display, mut event_queue) =
unsafe { Display::from_external_display(wayland_display as *mut wl_display) };
let env = Environment::from_display(&*display, &mut event_queue).unwrap();
let seat = env
.manager
.instantiate_range(1, 6, |seat| seat.implement_dummy())
.unwrap();
let device = DataDevice::init_for_seat(&env.data_device_manager, &seat, |_| {});
let enter_serial = Arc::new(Mutex::new(None));
let my_enter_serial = enter_serial.clone();
let _keyboard = map_keyboard_auto(&seat, move |event, _| {
if let KbEvent::Enter { serial, .. } = event {
*(my_enter_serial.lock().unwrap()) = Some(serial);
}
});
loop {
if let Ok(request) = request_recv.try_recv() {
match request {
WaylandRequest::Load => {
// Load
let mut reader = None;
device.with_selection(|offer| {
if let Some(offer) = offer {
offer.with_mime_types(|types| {
for t in types {
if t == "text/plain;charset=utf-8" {
reader = Some(
offer
.receive("text/plain;charset=utf-8".into())
.unwrap(),
);
}
}
});
}
});
event_queue.sync_roundtrip().unwrap();
if let Some(mut reader) = reader {
let mut contents = String::new();
reader.read_to_string(&mut contents).unwrap();
load_send.send(contents).unwrap();
} else {
load_send.send("".to_string()).unwrap();
}
}
WaylandRequest::Store(contents) => {
let data_source = DataSource::new(
&env.data_device_manager,
&["text/plain;charset=utf-8"],
move |source_event| {
if let DataSourceEvent::Send { mut pipe, .. } = source_event {
write!(pipe, "{}", contents).unwrap();
}
},
);
if let Some(enter_serial) = *enter_serial.lock().unwrap() {
device.set_selection(&Some(data_source), enter_serial);
}
event_queue.sync_roundtrip().unwrap();
}
WaylandRequest::Kill => break,
}
}
event_queue.dispatch_pending().unwrap();
sleep(Duration::from_millis(50));
}
});
WaylandClipboard {
request_send,
load_recv,
}
}
/// Returns text from the wayland clipboard
///
/// Only works when the window connected to the WlDisplay has
/// keyboard focus
pub fn load(&mut self) -> String {
self.request_send.send(WaylandRequest::Load).unwrap();
self.load_recv.recv().unwrap()
}
/// Stores text in the wayland clipboard
///
/// Only works when the window connected to the WlDisplay has
/// keyboard focus
pub fn store<S: Into<String>>(&mut self, text: S) {
self.request_send
.send(WaylandRequest::Store(text.into()))
.unwrap()
}
}