Initial commit
This commit is contained in:
commit
17011ea9f7
10 changed files with 510 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
76
.travis.yml
Normal file
76
.travis.yml
Normal 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
0
CHANGELOG.md
Normal file
9
CONTRIBUTING.md
Normal file
9
CONTRIBUTING.md
Normal 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
11
Cargo.toml
Normal 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
19
LICENSE
Normal 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
13
README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[](https://crates.io/crates/smithay-wayland-clipboard)
|
||||
[](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
6
doc_index.html
Normal 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
214
examples/clipboard.rs
Normal 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
159
src/lib.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue