diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d8d16f6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +env: + RUST_BACKTRACE: 1 + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Cdebuginfo=0 --deny=warnings" + RUSTDOCFLAGS: "--deny=warnings" + +jobs: + fmt: + name: Check Formatting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: nightly + components: rustfmt + - name: Check Formatting + run: cargo +nightly fmt --all -- --check + + tests: + name: Tests + runs-on: ubuntu-latest + strategy: + matrix: + rust_version: ["1.65", stable, nightly] + + steps: + - uses: actions/checkout@v3 + + - uses: hecrj/setup-rust-action@v1 + with: + rust-version: ${{ matrix.rust_version }} + + - name: Check documentation + run: cargo doc --features=log --no-deps --document-private-items + + - name: Run tests + run: cargo test --verbose --features=log diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..5ea3836 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,45 @@ +name: Deploy Docs to GitHub Pages + +on: + push: + branches: + - master + +jobs: + doc: + name: Documentation on Github Pages + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Cargo cache + uses: actions/cache@v1 + with: + path: ~/.cargo + key: cargo-stable + + - name: Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Install system dependencies + run: sudo apt-get install libxkbcommon-dev libwayland-dev + + - name: Build Documentation + uses: actions-rs/cargo@v1 + with: + command: doc + args: --no-deps + + - name: Setup index + run: cp ./doc_index.html ./target/doc/index.html + + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./target/doc diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 57de05f..0000000 --- a/.travis.yml +++ /dev/null @@ -1,78 +0,0 @@ -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 - else - cargo check - 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba8023..15bd276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- Update SCTK to 0.18 +- Fix active polling of the clipboard each 50ms +- Fix freeze when copying data larger than the pipe buffer size + ## 0.6.6 -- 2022-06-20 - Update SCTK to 0.16 diff --git a/Cargo.toml b/Cargo.toml index 1d05db6..372afa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,23 @@ [package] name = "smithay-clipboard" version = "0.6.6" -authors = ["Kirill Chibisov ", "Victor Berger ", "Lucas Timmins "] +authors = ["Kirill Chibisov ", "Victor Berger "] edition = "2021" description = "Provides access to the wayland clipboard for client applications." repository = "https://github.com/smithay/smithay-clipboard" documentation = "https://smithay.github.io/smithay-clipboard" license = "MIT" keywords = ["clipboard", "wayland"] +rust-version = "1.65.0" [dependencies] -sctk = { package = "smithay-client-toolkit", path = "../client-toolkit", default-features = false } +libc = "0.2.149" +sctk = { package = "smithay-client-toolkit", git = "https://github.com/kchibisov/client-toolkit", branch = "improve-selection", default-features = false, features = ["calloop"] } wayland-backend = { version = "0.3.0", default_features = false, features = ["client_system"] } [dev-dependencies] -sctk = { package = "smithay-client-toolkit", path = "../client-toolkit", default-features = false, features = ["calloop", "xkbcommon"] } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/kchibisov/client-toolkit", branch = "improve-selection", default-features = false, features = ["calloop", "xkbcommon"] } -# [features] -# default = ["dlopen"] -# dlopen = ["wayland-backend/dlopen" ] +[features] +default = ["dlopen"] +dlopen = ["wayland-backend/dlopen" ] diff --git a/examples/clipboard.rs b/examples/clipboard.rs index 0f8077b..c54674c 100644 --- a/examples/clipboard.rs +++ b/examples/clipboard.rs @@ -1,6 +1,6 @@ // The example just demonstrates how to integrate the smithay-clipboard into the -// application. For more details on what is going on, consult the `smithay-client-toolkit` -// examples. +// application. For more details on what is going on, consult the +// `smithay-client-toolkit` examples. use std::convert::TryInto; @@ -277,7 +277,7 @@ impl KeyboardHandler for SimpleWindow { _: u32, event: KeyEvent, ) { - match event.utf8.as_ref().map(|s| s.as_str()) { + match event.utf8.as_deref() { // Paste primary. Some("P") => { let contents = self @@ -285,7 +285,7 @@ impl KeyboardHandler for SimpleWindow { .load_primary() .unwrap_or_else(|_| String::from("failed to load primary clipboard")); println!("Paste from primary clipboard: {contents}"); - } + }, // Paste clipboard. Some("p") => { let contents = self @@ -293,19 +293,19 @@ impl KeyboardHandler for SimpleWindow { .load() .unwrap_or_else(|_| String::from("failed to load primary clipboard")); println!("Paste from clipboard: {contents}"); - } + }, // Copy primary. Some("C") => { let to_store = "Copy primary"; self.clipboard.store_primary(to_store); println!("Copied string into primary clipboard: {}", to_store); - } + }, // Copy clipboard. Some("c") => { let to_store = "Copy"; - self.clipboard.store_primary(to_store); + self.clipboard.store(to_store); println!("Copied string into clipboard: {}", to_store); - } + }, _ => (), } } @@ -366,7 +366,7 @@ impl SimpleWindow { .expect("create buffer"); *buffer = second_buffer; canvas - } + }, }; // Draw to the window: @@ -403,8 +403,9 @@ delegate_xdg_window!(SimpleWindow); delegate_registry!(SimpleWindow); impl ProvidesRegistryState for SimpleWindow { + registry_handlers![OutputState, SeatState,]; + fn registry(&mut self) -> &mut RegistryState { &mut self.registry_state } - registry_handlers![OutputState, SeatState,]; } diff --git a/rustfmt.toml b/rustfmt.toml index 76cd741..8057fee 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,16 @@ -use_small_heuristics = "Max" +format_code_in_doc_comments = true +match_block_trailing_comma = true +condense_wildcard_suffixes = true use_field_init_shorthand = true +normalize_doc_attributes = true +overflow_delimited_expr = true imports_granularity = "Module" +use_small_heuristics = "Max" +normalize_comments = true +reorder_impl_items = true +use_try_shorthand = true newline_style = "Unix" -edition = "2018" +format_strings = true +wrap_comments = true +comment_width = 80 +edition = "2021" diff --git a/src/env.rs b/src/env.rs deleted file mode 100644 index b7dc89c..0000000 --- a/src/env.rs +++ /dev/null @@ -1,79 +0,0 @@ -use sctk::data_device::{DataDevice, DataDeviceHandler, DataDeviceHandling, DndEvent}; -use sctk::primary_selection::{ - PrimarySelectionDevice, PrimarySelectionDeviceManager, PrimarySelectionHandler, - PrimarySelectionHandling, -}; -use sctk::reexports::client::protocol::wl_seat::WlSeat; -use sctk::reexports::client::{Attached, DispatchData}; -use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener}; -use sctk::MissingGlobal; - -/// Environemt setup for smithay-clipboard. -pub struct SmithayClipboard { - seats: SeatHandler, - primary_selection_manager: PrimarySelectionHandler, - data_device_manager: DataDeviceHandler, -} - -impl SmithayClipboard { - /// Create new environment. - pub fn new() -> Self { - let mut seats = SeatHandler::new(); - let data_device_manager = DataDeviceHandler::init(&mut seats); - let primary_selection_manager = PrimarySelectionHandler::init(&mut seats); - Self { seats, primary_selection_manager, data_device_manager } - } -} - -// Seat handling for data device manager and primary selection. -impl SeatHandling for SmithayClipboard { - fn listen, &SeatData, DispatchData) + 'static>( - &mut self, - f: F, - ) -> SeatListener { - self.seats.listen(f) - } -} - -impl PrimarySelectionHandling for SmithayClipboard { - fn with_primary_selection( - &self, - seat: &WlSeat, - f: F, - ) -> Result<(), MissingGlobal> { - self.primary_selection_manager.with_primary_selection(seat, f) - } - - fn get_primary_selection_manager(&self) -> Option { - self.primary_selection_manager.get_primary_selection_manager() - } -} - -impl DataDeviceHandling for SmithayClipboard { - fn set_callback(&mut self, callback: F) -> Result<(), MissingGlobal> - where - F: FnMut(WlSeat, DndEvent, DispatchData) + 'static, - { - self.data_device_manager.set_callback(callback) - } - - fn with_device( - &self, - seat: &WlSeat, - f: F, - ) -> Result<(), MissingGlobal> { - self.data_device_manager.with_device(seat, f) - } -} - -// Setup globals. -sctk::environment!(SmithayClipboard, - singles = [ - sctk::reexports::protocols::unstable::primary_selection::v1::client::zwp_primary_selection_device_manager_v1::ZwpPrimarySelectionDeviceManagerV1 => primary_selection_manager, - sctk::reexports::protocols::misc::gtk_primary_selection::client::gtk_primary_selection_device_manager::GtkPrimarySelectionDeviceManager => primary_selection_manager, - sctk::reexports::client::protocol::wl_data_device_manager::WlDataDeviceManager => data_device_manager, - ], -multis = [ - WlSeat => seats, -] -); diff --git a/src/lib.rs b/src/lib.rs index 1338a41..1884eb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,19 @@ //! Smithay Clipboard //! -//! Provides access to the Wayland clipboard for gui applications. The user should have surface -//! around. +//! Provides access to the Wayland clipboard for gui applications. The user +//! should have surface around. #![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)] use std::ffi::c_void; use std::io::Result; -use std::sync::mpsc::{self, Receiver, Sender}; +use std::sync::mpsc::{self, Receiver}; +use sctk::reexports::calloop::channel::{self, Sender}; use sctk::reexports::client::backend::Backend; use sctk::reexports::client::Connection; -// mod env; -// mod mime; +mod mime; +mod state; mod worker; /// Access to a Wayland clipboard. @@ -23,8 +24,8 @@ pub struct Clipboard { } impl Clipboard { - /// Creates new clipboard which will be running on its own thread with its own event queue to - /// handle clipboard requests. + /// Creates new clipboard which will be running on its own thread with its + /// own event queue to handle clipboard requests. /// /// # Safety /// @@ -35,13 +36,12 @@ impl Clipboard { let connection = Connection::from_backend(backend); // Create channel to send data to clipboard thread. - let (request_sender, clipboard_request_receiver) = mpsc::channel(); + let (request_sender, rx_chan) = channel::channel(); // Create channel to get data from the clipboard thread. let (clipboard_reply_sender, request_receiver) = mpsc::channel(); let name = String::from("smithay-clipboard"); - let clipboard_thread = - worker::spawn(name, connection, clipboard_request_receiver, clipboard_reply_sender); + let clipboard_thread = worker::spawn(name, connection, rx_chan, clipboard_reply_sender); Self { request_receiver, request_sender, clipboard_thread } } diff --git a/src/mime.rs b/src/mime.rs index f786ab6..f3fc9a7 100644 --- a/src/mime.rs +++ b/src/mime.rs @@ -1,5 +1,5 @@ /// List of allowed mimes. -static ALLOWED_MIME_TYPES: [&str; 2] = ["text/plain;charset=utf-8", "UTF8_STRING"]; +pub static ALLOWED_MIME_TYPES: [&str; 2] = ["text/plain;charset=utf-8", "UTF8_STRING"]; /// Mime type supported by clipboard. #[derive(Clone, Copy, Eq, PartialEq, Debug)] @@ -18,8 +18,8 @@ pub enum MimeType { impl MimeType { /// Find first allowed mime type among the `offered_mime_types`. /// - /// `find_allowed()` searches for mime type clipboard supports, if we have a match, - /// returns `Some(MimeType)`, otherwise `None`. + /// `find_allowed()` searches for mime type clipboard supports, if we have a + /// match, returns `Some(MimeType)`, otherwise `None`. pub fn find_allowed(offered_mime_types: &[String]) -> Option { for offered_mime_type in offered_mime_types.iter() { if offered_mime_type == ALLOWED_MIME_TYPES[Self::TextPlainUtf8 as usize] { diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..77dd83a --- /dev/null +++ b/src/state.rs @@ -0,0 +1,585 @@ +use std::borrow::Cow; +use std::collections::HashMap; +use std::io::{Error, ErrorKind, Read, Result, Write}; +use std::mem; +use std::os::fd::AsRawFd; +use std::os::unix::io::RawFd; +use std::rc::Rc; +use std::sync::mpsc::Sender; + +use sctk::data_device_manager::data_device::{DataDevice, DataDeviceHandler}; +use sctk::data_device_manager::data_offer::{DataOfferError, DataOfferHandler, DragOffer}; +use sctk::data_device_manager::data_source::{CopyPasteSource, DataSourceHandler}; +use sctk::data_device_manager::{DataDeviceManagerState, WritePipe}; +use sctk::primary_selection::device::{PrimarySelectionDevice, PrimarySelectionDeviceHandler}; +use sctk::primary_selection::selection::{PrimarySelectionSource, PrimarySelectionSourceHandler}; +use sctk::primary_selection::PrimarySelectionManagerState; +use sctk::registry::{ProvidesRegistryState, RegistryState}; +use sctk::seat::pointer::{PointerData, PointerEvent, PointerEventKind, PointerHandler}; +use sctk::seat::{Capability, SeatHandler, SeatState}; +use sctk::{ + delegate_data_device, delegate_pointer, delegate_primary_selection, delegate_registry, + delegate_seat, registry_handlers, +}; + +use sctk::reexports::client::globals::GlobalList; +use sctk::reexports::client::protocol::wl_data_device::WlDataDevice; +use sctk::reexports::client::protocol::wl_data_device_manager::DndAction; +use sctk::reexports::client::protocol::wl_data_source::WlDataSource; +use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; +use sctk::reexports::client::protocol::wl_pointer::WlPointer; +use sctk::reexports::client::protocol::wl_seat::WlSeat; +use sctk::reexports::client::{Connection, QueueHandle, Dispatch, Proxy}; +use sctk::reexports::protocols::wp::primary_selection::zv1::client::{ + zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, + zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, +}; +use sctk::reexports::calloop::{LoopHandle, PostAction}; +use wayland_backend::client::ObjectId; + +use crate::mime::{normalize_to_lf, MimeType, ALLOWED_MIME_TYPES}; + +pub struct State { + pub primary_selection_manager_state: Option, + pub data_device_manager_state: Option, + pub reply_tx: Sender>, + pub exit: bool, + + registry_state: RegistryState, + seat_state: SeatState, + + seats: HashMap, + /// The latest seat which got an event. + latest_seat: Option, + + loop_handle: LoopHandle<'static, Self>, + queue_handle: QueueHandle, + + primary_sources: Vec, + primary_selection_content: Rc<[u8]>, + + data_sources: Vec, + data_selection_content: Rc<[u8]>, +} + +impl State { + #[must_use] + pub fn new( + globals: &GlobalList, + queue_handle: &QueueHandle, + loop_handle: LoopHandle<'static, Self>, + reply_tx: Sender>, + ) -> Option { + let mut seats = HashMap::new(); + + let data_device_manager_state = DataDeviceManagerState::bind(globals, queue_handle).ok(); + let primary_selection_manager_state = + PrimarySelectionManagerState::bind(globals, queue_handle).ok(); + + // When both globals are not available nothing could be done. + if data_device_manager_state.is_none() && primary_selection_manager_state.is_none() { + return None; + } + + let seat_state = SeatState::new(globals, queue_handle); + for seat in seat_state.seats() { + seats.insert(seat.id(), Default::default()); + } + + Some(Self { + registry_state: RegistryState::new(globals), + primary_selection_content: Rc::from([]), + data_selection_content: Rc::from([]), + queue_handle: queue_handle.clone(), + primary_selection_manager_state, + primary_sources: Vec::new(), + data_device_manager_state, + data_sources: Vec::new(), + latest_seat: None, + loop_handle, + exit: false, + seat_state, + reply_tx, + seats, + }) + } + + /// Store selection for the given target. + /// + /// Selection source is only created when `Some(())` is returned. + pub fn store_selection(&mut self, ty: SelectionTarget, contents: String) -> Option<()> { + let latest = self.latest_seat.as_ref()?; + let seat = self.seats.get_mut(latest)?; + + if !seat.has_focus { + return None; + } + + let contents = Rc::from(contents.into_bytes()); + + match ty { + SelectionTarget::Clipboard => { + let mgr = self.data_device_manager_state.as_ref()?; + self.data_selection_content = contents; + let source = + mgr.create_copy_paste_source(&self.queue_handle, ALLOWED_MIME_TYPES.iter()); + source.set_selection(seat.data_device.as_ref().unwrap(), seat.latest_serial); + self.data_sources.push(source); + }, + SelectionTarget::Primary => { + let mgr = self.primary_selection_manager_state.as_ref()?; + self.primary_selection_content = contents; + let source = + mgr.create_selection_source(&self.queue_handle, ALLOWED_MIME_TYPES.iter()); + source.set_selection(seat.primary_device.as_ref().unwrap(), seat.latest_serial); + self.primary_sources.push(source); + }, + } + + Some(()) + } + + /// Load selection for the given target. + pub fn load_selection(&mut self, ty: SelectionTarget) -> Result<()> { + let latest = self + .latest_seat + .as_ref() + .ok_or(Error::new(ErrorKind::Other, "no events received on any seat"))?; + let seat = + self.seats.get_mut(latest).ok_or(Error::new(ErrorKind::Other, "active seat lost"))?; + + if !seat.has_focus { + return Err(Error::new(ErrorKind::Other, "client doesn't have focus")); + } + + let (read_pipe, mime_type) = match ty { + SelectionTarget::Clipboard => { + let selection = seat + .data_device + .as_ref() + .and_then(|data| data.data().selection_offer()) + .unwrap(); + + let mime_type = selection + .with_mime_types(MimeType::find_allowed) + .ok_or(Error::new(ErrorKind::NotFound, "supported mime-type is not found"))?; + + ( + selection.receive(mime_type.to_string()).map_err(|err| match err { + DataOfferError::InvalidReceive => { + Error::new(ErrorKind::Other, "offer is not ready yet") + }, + DataOfferError::Io(err) => err, + })?, + mime_type, + ) + }, + SelectionTarget::Primary => { + let selection = seat + .primary_device + .as_ref() + .and_then(|data| data.data().selection_offer()) + .unwrap(); + + let mime_type = selection + .with_mime_types(MimeType::find_allowed) + .ok_or(Error::new(ErrorKind::NotFound, "supported mime-type is not found"))?; + + (selection.receive(mime_type.to_string())?, mime_type) + }, + }; + + // Mark FD as non-blocking so we won't block ourselves. + unsafe { + set_non_blocking(read_pipe.as_raw_fd())?; + } + + let mut reader_buffer = [0; 4096]; + let mut content = Vec::new(); + let _ = self.loop_handle.insert_source(read_pipe, move |_, file, state| { + let file = unsafe { file.get_mut() }; + let action = loop { + match file.read(&mut reader_buffer) { + Ok(0) => { + let utf8 = String::from_utf8_lossy(&content); + let content = match utf8 { + Cow::Borrowed(_) => { + // Don't clone the read data. + let mut to_send = Vec::new(); + mem::swap(&mut content, &mut to_send); + String::from_utf8(to_send).unwrap() + }, + Cow::Owned(content) => content, + }; + + // Post-process the content according to mime type. + let content = match mime_type { + MimeType::TextPlainUtf8 => normalize_to_lf(content), + MimeType::Utf8String => content, + }; + + let _ = state.reply_tx.send(Ok(content)); + break PostAction::Remove; + }, + Ok(n) => content.extend_from_slice(&reader_buffer[..n]), + Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue, + Err(err) => { + let _ = state.reply_tx.send(Err(err)); + break PostAction::Remove; + }, + }; + }; + action + }); + + Ok(()) + } + + fn send_request(&mut self, ty: SelectionTarget, write_pipe: WritePipe, mime: String) { + // We can only send strings, so don't do anything with the mime-type. + if MimeType::find_allowed(&[mime]).is_none() { + return; + } + + // Mark FD as non-blocking so we won't block ourselves. + unsafe { + if set_non_blocking(write_pipe.as_raw_fd()).is_err() { + return; + } + } + + // Don't access the content on the state directly, since it could change during + // the send. + let contents = match ty { + SelectionTarget::Clipboard => self.data_selection_content.clone(), + SelectionTarget::Primary => self.primary_selection_content.clone(), + }; + + let mut written = 0; + let _ = self.loop_handle.insert_source(write_pipe, move |_, file, _| { + let file = unsafe { file.get_mut() }; + let (n, action) = match file.write(&contents[written..]) { + Ok(n) if written + n == contents.len() => (n, PostAction::Remove), + Ok(n) => (n, PostAction::Continue), + Err(err) if err.kind() == ErrorKind::WouldBlock => (0, PostAction::Continue), + Err(_) => (0, PostAction::Remove), + }; + + written += n; + + action + }); + } +} + +impl SeatHandler for State { + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat_state + } + + fn new_seat(&mut self, _: &Connection, _: &QueueHandle, seat: WlSeat) { + self.seats.insert(seat.id(), Default::default()); + } + + fn new_capability( + &mut self, + _: &Connection, + qh: &QueueHandle, + seat: WlSeat, + capability: Capability, + ) { + let seat_state = self.seats.get_mut(&seat.id()).unwrap(); + + match capability { + Capability::Keyboard => { + seat_state.keyboard = Some(seat.get_keyboard(qh, seat.id())); + + // Selection sources are tied to the keyboard, so add/remove decives + // when we gain/loss capability. + + if seat_state.data_device.is_none() && self.data_device_manager_state.is_some() { + seat_state.data_device = self + .data_device_manager_state + .as_ref() + .map(|mgr| mgr.get_data_device(qh, &seat)); + } + + if seat_state.primary_device.is_none() + && self.primary_selection_manager_state.is_some() + { + seat_state.primary_device = self + .primary_selection_manager_state + .as_ref() + .map(|mgr| mgr.get_selection_device(qh, &seat)); + } + }, + Capability::Pointer => { + seat_state.pointer = self.seat_state.get_pointer(qh, &seat).ok(); + }, + _ => (), + } + } + + fn remove_capability( + &mut self, + _: &Connection, + _: &QueueHandle, + seat: WlSeat, + capability: Capability, + ) { + let seat_state = self.seats.get_mut(&seat.id()).unwrap(); + match capability { + Capability::Keyboard => { + seat_state.data_device = None; + seat_state.primary_device = None; + + if let Some(keyboard) = seat_state.keyboard.take() { + if keyboard.version() >= 3 { + keyboard.release() + } + } + }, + Capability::Pointer => { + if let Some(pointer) = seat_state.pointer.take() { + if pointer.version() >= 3 { + pointer.release() + } + } + }, + _ => (), + } + } + + fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, seat: WlSeat) { + self.seats.remove(&seat.id()); + } +} + +impl PointerHandler for State { + fn pointer_frame( + &mut self, + _: &Connection, + _: &QueueHandle, + pointer: &WlPointer, + events: &[PointerEvent], + ) { + let seat = pointer.data::().unwrap().seat(); + let seat_id = seat.id(); + let seat_state = match self.seats.get_mut(&seat_id) { + Some(seat_state) => seat_state, + None => return, + }; + + let mut updated_serial = false; + for event in events { + match event.kind { + PointerEventKind::Press { serial, .. } + | PointerEventKind::Release { serial, .. } => { + updated_serial = true; + seat_state.latest_serial = serial; + }, + _ => (), + } + } + + // Only update the seat we're using when the serial got updated. + if updated_serial { + self.latest_seat = Some(seat_id); + } + } +} + +impl DataDeviceHandler for State { + fn enter(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) {} + + fn leave(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) {} + + fn motion(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) {} + + fn drop_performed(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) {} + + // The selection is finished and ready to be used. + fn selection(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) {} +} + +impl DataSourceHandler for State { + fn send_request( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlDataSource, + mime: String, + write_pipe: WritePipe, + ) { + self.send_request(SelectionTarget::Clipboard, write_pipe, mime) + } + + fn cancelled(&mut self, _: &Connection, _: &QueueHandle, deleted: &WlDataSource) { + self.data_sources.retain(|source| source.inner() != deleted) + } + + fn accept_mime( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &WlDataSource, + _: Option, + ) { + } + + fn dnd_dropped(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataSource) {} + + fn action(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataSource, _: DndAction) {} + + fn dnd_finished(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataSource) {} +} + +impl DataOfferHandler for State { + fn source_actions( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &mut DragOffer, + _: DndAction, + ) { + } + + fn selected_action( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &mut DragOffer, + _: DndAction, + ) { + } +} + +impl ProvidesRegistryState for State { + registry_handlers![SeatState]; + + fn registry(&mut self) -> &mut RegistryState { + &mut self.registry_state + } +} + +impl PrimarySelectionDeviceHandler for State { + fn selection( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &ZwpPrimarySelectionDeviceV1, + ) { + } +} + +impl PrimarySelectionSourceHandler for State { + fn send_request( + &mut self, + _: &Connection, + _: &QueueHandle, + _: &ZwpPrimarySelectionSourceV1, + mime: String, + write_pipe: WritePipe, + ) { + self.send_request(SelectionTarget::Primary, write_pipe, mime); + } + + fn cancelled( + &mut self, + _: &Connection, + _: &QueueHandle, + deleted: &ZwpPrimarySelectionSourceV1, + ) { + self.primary_sources.retain(|source| source.inner() != deleted) + } +} + +impl Dispatch for State { + fn event( + state: &mut State, + _: &WlKeyboard, + event: ::Event, + data: &ObjectId, + _: &Connection, + _: &QueueHandle, + ) { + use sctk::reexports::client::protocol::wl_keyboard::Event as WlKeyboardEvent; + let seat_state = match state.seats.get_mut(data) { + Some(seat_state) => seat_state, + None => return, + }; + match event { + WlKeyboardEvent::Key { serial, .. } | WlKeyboardEvent::Modifiers { serial, .. } => { + seat_state.latest_serial = serial; + state.latest_seat = Some(data.clone()); + }, + // NOTE both selections rely on keyboard focus. + WlKeyboardEvent::Enter { serial, .. } => { + seat_state.latest_serial = serial; + seat_state.has_focus = true; + }, + WlKeyboardEvent::Leave { .. } => { + seat_state.latest_serial = 0; + seat_state.has_focus = false; + }, + _ => (), + } + } +} + +delegate_seat!(State); +delegate_pointer!(State); +delegate_data_device!(State); +delegate_primary_selection!(State); +delegate_registry!(State); + +#[derive(Debug, Clone, Copy)] +pub enum SelectionTarget { + /// The target is clipboard selection. + Clipboard, + /// The target is primary selection. + Primary, +} + +#[derive(Debug, Default)] +struct ClipboardSeatState { + keyboard: Option, + pointer: Option, + data_device: Option, + primary_device: Option, + has_focus: bool, + + /// The latest serial used to set the selection content. + latest_serial: u32, +} + +impl Drop for ClipboardSeatState { + fn drop(&mut self) { + if let Some(keyboard) = self.keyboard.take() { + if keyboard.version() >= 3 { + keyboard.release(); + } + } + + if let Some(pointer) = self.pointer.take() { + if pointer.version() >= 3 { + pointer.release(); + } + } + } +} + +unsafe fn set_non_blocking(raw_fd: RawFd) -> std::io::Result<()> { + let flags = libc::fcntl(raw_fd, libc::F_GETFL); + + if flags < 0 { + return Err(std::io::Error::last_os_error()); + } + + let result = libc::fcntl(raw_fd, libc::F_SETFL, flags | libc::O_NONBLOCK); + if result < 0 { + return Err(std::io::Error::last_os_error()); + } + + Ok(()) +} diff --git a/src/worker.rs b/src/worker.rs new file mode 100644 index 0000000..9638310 --- /dev/null +++ b/src/worker.rs @@ -0,0 +1,104 @@ +use std::io::{Error, ErrorKind, Result}; +use std::sync::mpsc::Sender; + +use sctk::reexports::calloop::channel::Channel; +use sctk::reexports::calloop::{channel, EventLoop}; +use sctk::reexports::calloop_wayland_source::WaylandSource; +use sctk::reexports::client::globals::registry_queue_init; +use sctk::reexports::client::Connection; + +use crate::state::{SelectionTarget, State}; + +/// Spawn a clipboard worker, which dispatches it's own `EventQueue` each 50ms +/// and handles clipboard requests. +pub fn spawn( + name: String, + display: Connection, + rx_chan: Channel, + worker_replier: Sender>, +) -> Option> { + std::thread::Builder::new() + .name(name) + .spawn(move || { + worker_impl(display, rx_chan, worker_replier); + }) + .ok() +} + +/// Clipboard worker thread command. +#[derive(Eq, PartialEq)] +pub enum Command { + /// Store data to a clipboard. + Store(String), + /// Store data to a primary selection. + StorePrimary(String), + /// Load data from a clipboard. + Load, + /// Load primary selection. + LoadPrimary, + /// Shutdown the worker. + Exit, +} + +/// Handle clipboard requests. +fn worker_impl( + connection: Connection, + rx_chan: Channel, + reply_tx: Sender>, +) { + let (globals, event_queue) = match registry_queue_init(&connection) { + Ok(data) => data, + Err(_) => return, + }; + + let mut event_loop = EventLoop::::try_new().unwrap(); + let loop_handle = event_loop.handle(); + + let mut state = match State::new(&globals, &event_queue.handle(), loop_handle.clone(), reply_tx) + { + Some(state) => state, + None => return, + }; + + loop_handle + .insert_source(rx_chan, |event, _, state| { + if let channel::Event::Msg(event) = event { + match event { + Command::StorePrimary(contents) => { + state.store_selection(SelectionTarget::Primary, contents); + }, + Command::Store(contents) => { + state.store_selection(SelectionTarget::Clipboard, contents); + }, + Command::Load if state.data_device_manager_state.is_some() => { + if let Err(err) = state.load_selection(SelectionTarget::Clipboard) { + let _ = state.reply_tx.send(Err(err)); + } + }, + Command::LoadPrimary if state.data_device_manager_state.is_some() => { + if let Err(err) = state.load_selection(SelectionTarget::Primary) { + let _ = state.reply_tx.send(Err(err)); + } + }, + Command::Load | Command::LoadPrimary => { + let _ = state.reply_tx.send(Err(Error::new( + ErrorKind::Other, + "requested selection is not supported", + ))); + }, + Command::Exit => state.exit = true, + } + } + }) + .unwrap(); + + WaylandSource::new(connection.clone(), event_queue).insert(loop_handle).unwrap(); + + loop { + event_loop.dispatch(None, &mut state).unwrap(); + + if state.exit { + break; + } + } +} diff --git a/src/worker/dispatch_data.rs b/src/worker/dispatch_data.rs deleted file mode 100644 index b1d8fce..0000000 --- a/src/worker/dispatch_data.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::collections::VecDeque; -use std::slice::IterMut; - -use sctk::reexports::client::protocol::wl_seat::WlSeat; - -use super::seat_data::SeatData; - -/// Data to track latest seat and serial for clipboard requests. -pub struct ClipboardDispatchData { - /// Seats that our application encountered. The first seat is the latest one we've encountered. - observed_seats: VecDeque<(WlSeat, u32)>, - - /// All the seats that were advertised. - seats: Vec, -} - -impl ClipboardDispatchData { - /// Builds new `ClipboardDispatchData` with all fields equal to `None`. - pub fn new(seats: Vec) -> Self { - Self { observed_seats: Default::default(), seats } - } - - /// Returns the requested seat's data or adds a new one. - pub fn get_seat_data_or_add(&mut self, seat: WlSeat) -> &mut SeatData { - let pos = self.seats.iter().position(|st| st.seat == seat); - let index = pos.unwrap_or_else(|| { - self.seats.push(SeatData::new(seat, None, None)); - self.seats.len() - 1 - }); - - &mut self.seats[index] - } - - pub fn seats(&mut self) -> IterMut<'_, SeatData> { - self.seats.iter_mut() - } - - /// Set the last observed seat. - pub fn set_last_observed_seat(&mut self, seat: WlSeat, serial: u32) { - // Assure each seat exists only once. - self.remove_observed_seat(&seat); - - // Add the seat to front, making it the latest observed one. - self.observed_seats.push_front((seat, serial)); - } - - /// Remove the given seat from the observed seats. - pub fn remove_observed_seat(&mut self, seat: &WlSeat) { - if let Some(pos) = self.observed_seats.iter().position(|st| &st.0 == seat) { - self.observed_seats.remove(pos); - } - } - - /// Return the last observed seat and the serial. - pub fn last_observed_seat(&self) -> Option<&(WlSeat, u32)> { - self.observed_seats.front() - } -} diff --git a/src/worker/handlers.rs b/src/worker/handlers.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/worker/mod.rs b/src/worker/mod.rs deleted file mode 100644 index 36a7680..0000000 --- a/src/worker/mod.rs +++ /dev/null @@ -1,614 +0,0 @@ -// use std::io::prelude::*; -use std::io::Result; -use std::sync::mpsc::{Receiver, Sender}; -use std::time::Duration; - -use sctk::data_device_manager::data_device::DataDeviceHandler; -use sctk::data_device_manager::data_offer::DataOfferHandler; -use sctk::data_device_manager::data_source::DataSourceHandler; -use sctk::data_device_manager::DataDeviceManagerState; -use sctk::primary_selection::device::PrimarySelectionDeviceHandler; -use sctk::primary_selection::selection::PrimarySelectionSourceHandler; -use sctk::primary_selection::PrimarySelectionManagerState; -use sctk::registry::{ProvidesRegistryState, RegistryState}; -use sctk::seat::pointer::PointerHandler; -use sctk::seat::{self, SeatHandler, SeatState}; -use sctk::{ - delegate_data_device, delegate_pointer, delegate_primary_selection, delegate_registry, - delegate_seat, registry_handlers, -}; - -use sctk::reexports::client::globals::registry_queue_init; -use sctk::reexports::client::globals::GlobalList; -use sctk::reexports::client::protocol::wl_data_device::WlDataDevice; -use sctk::reexports::client::protocol::wl_data_device_manager::DndAction; -use sctk::reexports::client::protocol::wl_data_source::WlDataSource; -use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; -use sctk::reexports::client::protocol::wl_pointer::WlPointer; -use sctk::reexports::client::protocol::wl_seat::WlSeat; -use sctk::reexports::client::protocol::wl_surface::WlSurface; -use sctk::reexports::client::{Connection, QueueHandle, Dispatch}; -use sctk::reexports::protocols::wp::primary_selection::zv1::client::{ - zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, - zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, -}; - -// use crate::mime::{self, MimeType}; - -mod dispatch_data; -mod seat_data; -mod sleep_amount_tracker; - -// use dispatch_data::ClipboardDispatchData; -// use seat_data::SeatData; -use sleep_amount_tracker::SleepAmountTracker; - -/// Max time clipboard thread can sleep. -const MAX_TIME_TO_SLEEP: u8 = 50; - -/// Max warm wakeups. -const MAX_WARM_WAKEUPS: u8 = 16; - -/// Spawn a clipboard worker, which dispatches it's own `EventQueue` each 50ms and handles -/// clipboard requests. -pub fn spawn( - name: String, - display: Connection, - worker_receiver: Receiver, - worker_replier: Sender>, -) -> Option> { - std::thread::Builder::new() - .name(name) - .spawn(move || { - worker_impl(display, worker_receiver, worker_replier); - }) - .ok() -} - -/// Clipboard worker thread command. -#[derive(Eq, PartialEq)] -pub enum Command { - /// Store data to a clipboard. - Store(String), - /// Store data to a primary selection. - StorePrimary(String), - /// Load data from a clipboard. - Load, - /// Load primary selection. - LoadPrimary, - /// Shutdown the worker. - Exit, -} -/// Handle clipboard requests. -fn worker_impl( - connection: Connection, - request_rx: Receiver, - reply_tx: Sender>, -) { - // let queue = connection.new_event_queue(); - let (globals, mut event_queue) = match registry_queue_init(&connection) { - Ok(data) => data, - Err(_) => return, - }; - - let mut state = SmithayClipboardState::new(&globals, &event_queue.handle()); - - // TODO use WaylandSource. - - // Setup sleep amount tracker. - let mut sa_tracker = SleepAmountTracker::new(MAX_TIME_TO_SLEEP, MAX_WARM_WAKEUPS); - - loop { - // Try to get event from the user. - if let Ok(request) = request_rx.try_recv() { - // Break early on to handle shutdown gracefully, otherwise we can crash on - // `sync_roundtrip`, if client closed connection to a server before releasing the - // clipboard. - if request == Command::Exit { - break; - } - // Reset the time we're sleeping. - sa_tracker.reset_sleep(); - - event_queue.roundtrip(&mut state).unwrap(); - // if event_queue.sync_roundtrip(&mut dispatch_data, |_, _, _| unimplemented!()).is_err() - // && (request == Command::LoadPrimary || request == Command::Load) - // { - // handlers::reply_error(&reply_tx, "primary clipboard is not available."); - // break; - // } - - // Get latest observed seat and serial. - let (seat, serial) = match state.seat_states.get(state.latest_seat_idx) { - Some(data) => (data.seat.clone(), data.serial), - None => { - // handlers::reply_error(&reply_tx, "no focus on a seat."); - continue; - } - }; - - // Handle requests. - match request { - Command::Load => {} - Command::Store(contents) => {} - Command::LoadPrimary => {} - Command::StorePrimary(contents) => {} - _ => unreachable!(), - } - } - - // TODO flush? - - let pending_events = match event_queue.dispatch_pending(&mut state) { - Ok(pending_events) => pending_events, - Err(_) => break, - }; - - // If some application is trying to spam us when there're no seats, it's likely that - // someone is trying to paste from us. - // TODO - if state.seat_states.get(1337).is_none() && pending_events != 0 { - sa_tracker.reset_sleep(); - } else { - // Time for thread to sleep. - let tts = sa_tracker.sleep_amount(); - if tts > 0 { - std::thread::sleep(Duration::from_millis(tts as _)); - } - - sa_tracker.increase_sleep(); - } - } - // let mut queue = display.create_event_queue(); - // let display_proxy = display.attach(queue.token()); - - // let env = match Environment::new(&display_proxy, &mut queue, SmithayClipboard::new()) { - // Ok(env) => env, - // // We shouldn't crash the application if we've failed to create environment. - // Err(_) => return, - // }; - - // // Get data device manager. - // let data_device_manager = env.get_global::(); - - // // Get primary selection device manager. - // let primary_selection_manager = env.get_primary_selection_manager(); - - // // Both clipboards are not available, spin the loop and reply to a clipboard master. - // if data_device_manager.is_none() && primary_selection_manager.is_none() { - // loop { - // if let Ok(event) = request_rx.recv() { - // match event { - // Command::Exit => { - // return; - // } - // _ => { - // // Reply with error - // handlers::reply_error(&reply_tx, "Clipboard are missing."); - // } - // } - // } - // } - // } - - // // Track seats. - // let mut seats = Vec::::new(); - - // for seat in env.get_all_seats() { - // let seat_data = match seat::clone_seat_data(&seat) { - // Some(seat_data) => { - // // Handle defunct seats early on. - // if seat_data.defunct { - // seats.push(SeatData::new(seat.detach(), None, None)); - // continue; - // } - - // seat_data - // } - // _ => continue, - // }; - - // let keyboard = if seat_data.has_keyboard { - // let keyboard = seat.get_keyboard(); - // let seat_clone = seat.clone(); - - // keyboard.quick_assign(move |_keyboard, event, dispatch_data| { - // handlers::keyboard_handler(seat_clone.detach(), event, dispatch_data); - // }); - - // Some(keyboard.detach()) - // } else { - // None - // }; - - // let pointer = if seat_data.has_pointer { - // let pointer = seat.get_pointer(); - // let seat_clone = seat.clone(); - - // pointer.quick_assign(move |_pointer, event, dispatch_data| { - // handlers::pointer_handler(seat_clone.detach(), event, dispatch_data); - // }); - - // Some(pointer.detach()) - // } else { - // None - // }; - - // // Track the seat. - // seats.push(SeatData::new(seat.detach(), keyboard, pointer)); - // } - - // // Listen for seats. - // let listener = env.listen_for_seats(move |seat, seat_data, mut dispatch_data| { - // let dispatch_data = match dispatch_data.get::() { - // Some(dispatch_data) => dispatch_data, - // None => return, - // }; - - // let seat_resources = dispatch_data.get_seat_data_or_add(seat.detach()); - - // if seat_data.has_keyboard && !seat_data.defunct { - // if seat_resources.keyboard.is_none() { - // let keyboard = seat.get_keyboard(); - // let seat_clone = seat.clone(); - - // keyboard.quick_assign(move |_keyboard, event, dispatch_data| { - // handlers::keyboard_handler(seat_clone.detach(), event, dispatch_data); - // }); - - // seat_resources.keyboard = Some(keyboard.detach()); - // } - // } else { - // // Clean up. - // if let Some(keyboard) = seat_resources.keyboard.take() { - // if keyboard.as_ref().version() >= 3 { - // keyboard.release(); - // } - // } - // } - - // if seat_data.has_pointer && !seat_data.defunct { - // if seat_resources.pointer.is_none() { - // let pointer = seat.get_pointer(); - - // pointer.quick_assign(move |_pointer, event, dispatch_data| { - // handlers::pointer_handler(seat.detach(), event, dispatch_data); - // }); - - // seat_resources.pointer = Some(pointer.detach()); - // } - // } else if let Some(pointer) = seat_resources.pointer.take() { - // // Clean up. - // if pointer.as_ref().version() >= 3 { - // pointer.release(); - // } - // } - // }); - - // // Flush the display. - // let _ = queue.display().flush(); - - // let mut dispatch_data = ClipboardDispatchData::new(seats); - - // // While everything inside this block is safe, the logic is generally unsafe, since we must - // // drop every proxy on the current `queue`, since dropping it in multithreaded context - // // could result in use-after-free in libwayland-client. - // // - // // For more see https://gitlab.freedesktop.org/wayland/wayland/-/issues/13. - // #[allow(unused_unsafe)] - // unsafe { - // for seat in dispatch_data.seats() { - // if let Some(pointer) = seat.pointer.take() { - // if pointer.as_ref().version() >= 3 { - // pointer.release(); - // } - // } - // if let Some(keyboard) = seat.keyboard.take() { - // if keyboard.as_ref().version() >= 3 { - // keyboard.release(); - // } - // } - // } - // std::mem::drop(listener); - - // let _ = queue.sync_roundtrip(&mut dispatch_data, |_, _, _| unimplemented!()); - // let _ = queue.display().flush(); - // } -} - -struct ClipboardSeatState { - seat: WlSeat, - keyboard: Option, - pointer: Option, - serial: u32, -} - -struct SmithayClipboardState { - registry_state: RegistryState, - seat_state: SeatState, - data_device_manager_state: DataDeviceManagerState, - primary_selection_manager_state: PrimarySelectionManagerState, - seat_states: Vec, - latest_seat_idx: usize, - exit: bool, -} - -impl SmithayClipboardState { - fn new(globals: &GlobalList, queue_handle: &QueueHandle) -> Self { - // TODO: error handling - Self { - registry_state: RegistryState::new(globals), - seat_state: SeatState::new(globals, queue_handle), - data_device_manager_state: DataDeviceManagerState::bind(globals, queue_handle).unwrap(), - primary_selection_manager_state: PrimarySelectionManagerState::bind( - globals, - queue_handle, - ) - .unwrap(), - exit: false, - seat_states: Vec::new(), - latest_seat_idx: 0, - } - } -} - -impl SeatHandler for SmithayClipboardState { - fn seat_state(&mut self) -> &mut seat::SeatState { - todo!() - } - - fn new_seat( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - seat: WlSeat, - ) { - todo!() - } - - fn new_capability( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - seat: WlSeat, - capability: seat::Capability, - ) { - todo!() - } - - fn remove_capability( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - seat: WlSeat, - capability: seat::Capability, - ) { - todo!() - } - - fn remove_seat( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - seat: WlSeat, - ) { - todo!() - } -} - -impl PointerHandler for SmithayClipboardState { - fn pointer_frame( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - pointer: &WlPointer, - events: &[seat::pointer::PointerEvent], - ) { - todo!() - } -} - -impl DataDeviceHandler for SmithayClipboardState { - fn enter( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - data_device: &WlDataDevice, - ) { - todo!() - } - - fn leave( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - data_device: &WlDataDevice, - ) { - todo!() - } - - fn motion( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - data_device: &WlDataDevice, - ) { - todo!() - } - - fn selection( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - data_device: &WlDataDevice, - ) { - todo!() - } - - fn drop_performed( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - data_device: &WlDataDevice, - ) { - todo!() - } -} - -impl DataSourceHandler for SmithayClipboardState { - fn accept_mime( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - source: &WlDataSource, - mime: Option, - ) { - todo!() - } - - fn send_request( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - source: &WlDataSource, - mime: String, - fd: sctk::data_device_manager::WritePipe, - ) { - todo!() - } - - fn cancelled( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - source: &WlDataSource, - ) { - todo!() - } - - fn dnd_dropped( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - source: &WlDataSource, - ) { - todo!() - } - - fn dnd_finished( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - source: &WlDataSource, - ) { - todo!() - } - - fn action( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - source: &WlDataSource, - action: DndAction, - ) { - todo!() - } -} - -impl DataOfferHandler for SmithayClipboardState { - fn source_actions( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - offer: &mut sctk::data_device_manager::data_offer::DragOffer, - actions: DndAction, - ) { - todo!() - } - - fn selected_action( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - offer: &mut sctk::data_device_manager::data_offer::DragOffer, - actions: DndAction, - ) { - todo!() - } -} - -impl ProvidesRegistryState for SmithayClipboardState { - fn registry(&mut self) -> &mut sctk::registry::RegistryState { - // &mut self.registry_state - todo!() - } - - registry_handlers![SeatState]; -} - -impl PrimarySelectionDeviceHandler for SmithayClipboardState { - fn selection( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - primary_selection_device: &ZwpPrimarySelectionDeviceV1, - ) { - todo!() - } -} - -impl PrimarySelectionSourceHandler for SmithayClipboardState { - fn send_request( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - source: &ZwpPrimarySelectionSourceV1, - mime: String, - write_pipe: sctk::data_device_manager::WritePipe, - ) { - todo!() - } - - fn cancelled( - &mut self, - conn: &Connection, - qh: &sctk::reexports::client::QueueHandle, - source: &ZwpPrimarySelectionSourceV1, - ) { - todo!() - } -} - -impl Dispatch for SmithayClipboardState { - fn event( - state: &mut SmithayClipboardState, - proxy: &WlKeyboard, - event: ::Event, - data: &(), - conn: &Connection, - qhandle: &QueueHandle, - ) { - use sctk::reexports::client::protocol::wl_keyboard::Event as WlKeyboardEvent; - match event { - WlKeyboardEvent::Enter { serial, surface, keys } => (), - WlKeyboardEvent::Leave { serial, surface } => (), - WlKeyboardEvent::Key { serial, time, key, state } => (), - WlKeyboardEvent::Modifiers { - serial, - mods_depressed, - mods_latched, - mods_locked, - group, - } => (), - WlKeyboardEvent::Keymap { format, fd, size } => (), - WlKeyboardEvent::RepeatInfo { rate, delay } => (), - _ => unreachable!(), - } - } -} - -delegate_seat!(SmithayClipboardState); -delegate_pointer!(SmithayClipboardState); -delegate_data_device!(SmithayClipboardState); -delegate_primary_selection!(SmithayClipboardState); -delegate_registry!(SmithayClipboardState); diff --git a/src/worker/seat_data.rs b/src/worker/seat_data.rs deleted file mode 100644 index c6374f7..0000000 --- a/src/worker/seat_data.rs +++ /dev/null @@ -1,16 +0,0 @@ -use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; -use sctk::reexports::client::protocol::wl_pointer::WlPointer; -use sctk::reexports::client::protocol::wl_seat::WlSeat; - -/// Data to track seat capability changes and handle release of the objects. -pub struct SeatData { - pub seat: WlSeat, - pub keyboard: Option, - pub pointer: Option, -} - -impl SeatData { - pub fn new(seat: WlSeat, keyboard: Option, pointer: Option) -> Self { - SeatData { seat, keyboard, pointer } - } -} diff --git a/src/worker/sleep_amount_tracker.rs b/src/worker/sleep_amount_tracker.rs deleted file mode 100644 index 28ec2ed..0000000 --- a/src/worker/sleep_amount_tracker.rs +++ /dev/null @@ -1,55 +0,0 @@ -/// Handles dynamic thread sleep time. -pub struct SleepAmountTracker { - /// The maximum amount of time for thread to sleep. - max_time_to_sleep: u8, - /// Current time to block the thread. - time_to_sleep: u8, - /// The current warm wakeup number. - warm_wakeup: u8, - /// The maximum amount of clipboard wakeups in a row with low sleep amount. - max_warm_wakeups: u8, -} - -impl SleepAmountTracker { - /// Build new tracker for sleep amount. - /// - /// `max_time_to_sleep` - maximum sleep value for a thread. - /// `` - pub fn new(max_time_to_sleep: u8, max_warm_wakeups: u8) -> Self { - Self { max_time_to_sleep, max_warm_wakeups, warm_wakeup: 0, time_to_sleep: 0 } - } - - /// Reset the current sleep amount to 0ms. - #[inline] - pub fn reset_sleep(&mut self) { - self.time_to_sleep = 0; - } - - /// Adjust the sleep amount. - #[inline] - pub fn increase_sleep(&mut self) { - if self.time_to_sleep == 0 { - // Reset `time_to_sleep` to one, so we can reach `max_time_to_sleep`. - self.time_to_sleep = 1; - // Reset `warm_wakeup` count. - self.warm_wakeup = 0; - - return; - } - - if self.warm_wakeup < self.max_warm_wakeups { - // Handled warm wake up. - self.warm_wakeup += 1; - } else if self.time_to_sleep < self.max_warm_wakeups { - // The aim of this different sleep times is to provide a good performance under - // high the load and not waste system resources too much when idle. - self.time_to_sleep = std::cmp::min(2 * self.time_to_sleep, self.max_time_to_sleep); - } - } - - /// Get the current time to sleep in ms. - #[inline] - pub fn sleep_amount(&self) -> u8 { - self.time_to_sleep - } -}