smithay-clipboard-0.7.2/.cargo_vcs_info.json0000644000000001360000000000100144550ustar { "git": { "sha1": "ca11fd0bf6f71654af952ea4d710e8f1c828e600" }, "path_in_vcs": "" }smithay-clipboard-0.7.2/.github/workflows/ci.yml000064400000000000000000000022251046102023000177610ustar 00000000000000name: 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 }} components: clippy - name: Install system dependencies run: sudo apt-get install libxkbcommon-dev libwayland-dev - name: Run tests run: cargo test --verbose - name: Clippy if: matrix.toolchain == 'stable' run: cargo clippy -- -D warnings - name: Check documentation run: cargo doc --no-deps --document-private-items smithay-clipboard-0.7.2/.github/workflows/docs.yml000064400000000000000000000017311046102023000203170ustar 00000000000000name: 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 smithay-clipboard-0.7.2/.gitignore000064400000000000000000000000361046102023000152340ustar 00000000000000/target **/*.rs.bk Cargo.lock smithay-clipboard-0.7.2/CHANGELOG.md000064400000000000000000000060461046102023000150640ustar 00000000000000# Change Log ## 0.7.2 - Update SCTK to 0.19 ## 0.7.1 - Don't panic on display disconnect ## 0.7.0 -- 2023-10-10 - Update SCTK to 0.18 - Fix active polling of the clipboard each 50ms - Fix freeze when copying data larger than the pipe buffer size - Accept text/plain mime type as a fallback ## 0.6.6 -- 2022-06-20 - Update SCTK to 0.16 ## 0.6.5 -- 2021-10-31 - Update SCTK to 0.15, updating wayland-rs to `0.29` ## 0.6.4 -- 2021-06-25 - Update SCTK to 0.14, significantly reducing the depdendency tree ## 0.6.3 -- 2021-02-04 - Consecutive clipboard stores dropped until the application is refocused ## 0.6.2 -- 2020-12-17 - Segfault when dropping clipboard in multithreaded context while main queue is still running ## 0.6.1 -- 2020-10-13 - Crash when failing to write to a clipboard ## 0.6.0 -- 2020-10-03 - Updated smithay-client-toolkit to 0.12 - **Breaking** `Clipboard::new` is now marked with `unsafe` ## 0.5.2 -- 2020-08-30 - Fixed clipboard crashing, when seat has neither keyboard nor pointer focus - Advertise UTF8_STRING mimetype - Fixed crash when writing data to the server fails - Fixed fd leaking from keymap updates ## 0.5.1 -- 2020-07-10 - Fixed clipboard not working, when seat had empty name # 0.5.0 -- 2020-05-20 - Minimal rust version was bumped to 1.41.0 - Add support for `UTF8_STRING` mime type - **Breaking** Clipboard now works only with extern display - **Breaking** Clipboard now works only with last observed seats, instead of optionally accepting seat names ## 0.4.0 -- 2020-03-09 - Fix crash when receiving non-utf8 data - **Breaking** `load` and `load_primary` now return `Result` to indicate errors - Fix clipboard dying after TTY switch ## 0.3.7 -- 2020-02-27 - Only bind seat with version up to 6, as version 7 is not yet supported by SCTK for loading keymaps ## 0.3.6 -- 2019-11-21 - Perform loaded data normalization for text/plain;charset=utf-8 mime type - Fix clipboard throttling ## 0.3.5 -- 2019-09-3 - Fix primary selection storing, when releasing button outside of the surface ## 0.3.4 -- 2019-08-14 - Add fallback to gtk primary selection, when zwp primary selection is not available ## 0.3.3 -- 2019-06-14 - Update nix version to 0.14.1 ## 0.3.2 -- 2019-06-13 - Update smithay-client-toolkit version to 0.6.1 ## 0.3.1 -- 2019-06-08 - Fix primary clipboard storing ## 0.3.0 -- 2019-06-07 - Add support for primary selection through `store_primary()` and `load_primary()` ## 0.2.1 -- 2019-04-27 - Remove dbg! macro from code ## 0.2.0 -- 2019-04-27 - `Clipboard::store()` and `Clipboard::load()` now take a `Option` for the seat name, if no seat name is provided then the name of the last seat to generate an event will be used instead ## 0.1.1 -- 2019-04-24 - Do a sync roundtrip to register avaliable seats on clipboard creation - Collect serials from key and pointer events - Return an empty string for load requests when no seats are avaliable ## 0.1.0 -- 2019-02-14 Initial version, including: - `WaylandClipboard` with `new_threaded()` and `new_threaded_from_external()` - multi seat support smithay-clipboard-0.7.2/Cargo.lock0000644000000334560000000000100124430ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bytemuck" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "calloop" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags", "log", "polling", "rustix", "slab", "thiserror", ] [[package]] name = "calloop-wayland-source" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", "rustix", "wayland-backend", "wayland-client", ] [[package]] name = "cc" version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "concurrent-queue" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "cursor-icon" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] name = "dlib" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ "libloading", ] [[package]] name = "downcast-rs" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "errno" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", "windows-sys", ] [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", "windows-targets", ] [[package]] name = "linux-raw-sys" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed" dependencies = [ "libc", ] [[package]] name = "memmap2" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" dependencies = [ "libc", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "polling" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", "rustix", "tracing", "windows-sys", ] [[package]] name = "proc-macro2" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ "memchr", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rustix" version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay-client-toolkit" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags", "bytemuck", "calloop", "calloop-wayland-source", "cursor-icon", "libc", "log", "memmap2 0.9.4", "pkg-config", "rustix", "thiserror", "wayland-backend", "wayland-client", "wayland-csd-frame", "wayland-cursor", "wayland-protocols", "wayland-protocols-wlr", "wayland-scanner", "xkbcommon", "xkeysym", ] [[package]] name = "smithay-clipboard" version = "0.7.2" dependencies = [ "libc", "smithay-client-toolkit", "wayland-backend", ] [[package]] name = "syn" version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "wayland-backend" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" dependencies = [ "cc", "downcast-rs", "rustix", "scoped-tls", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e63801c85358a431f986cffa74ba9599ff571fc5774ac113ed3b490c19a1133" dependencies = [ "bitflags", "rustix", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-csd-frame" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ "bitflags", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" dependencies = [ "rustix", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83d0f1056570486e26a3773ec633885124d79ae03827de05ba6c85f79904026c" dependencies = [ "bitflags", "wayland-backend", "wayland-client", "wayland-scanner", ] [[package]] name = "wayland-protocols-wlr" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7dab47671043d9f5397035975fe1cac639e5bca5cc0b3c32d09f01612e34d24" dependencies = [ "bitflags", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67da50b9f80159dec0ea4c11c13e24ef9e7574bd6ce24b01860a175010cea565" dependencies = [ "proc-macro2", "quick-xml", "quote", ] [[package]] name = "wayland-sys" version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" dependencies = [ "dlib", "log", "once_cell", "pkg-config", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "xcursor" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" [[package]] name = "xkbcommon" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13867d259930edc7091a6c41b4ce6eee464328c6ff9659b7e4c668ca20d4c91e" dependencies = [ "libc", "memmap2 0.8.0", "xkeysym", ] [[package]] name = "xkeysym" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" dependencies = [ "bytemuck", ] smithay-clipboard-0.7.2/Cargo.toml0000644000000031630000000000100124560ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65.0" name = "smithay-clipboard" version = "0.7.2" authors = [ "Kirill Chibisov ", "Elinor Berger ", ] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Provides access to the wayland clipboard for client applications." documentation = "https://smithay.github.io/smithay-clipboard" readme = "README.md" keywords = [ "clipboard", "wayland", ] license = "MIT" repository = "https://github.com/smithay/smithay-clipboard" [lib] name = "smithay_clipboard" path = "src/lib.rs" [[example]] name = "clipboard" path = "examples/clipboard.rs" [dependencies.libc] version = "0.2.149" [dependencies.sctk] version = "0.19.2" features = ["calloop"] default-features = false package = "smithay-client-toolkit" [dependencies.wayland-backend] version = "0.3.5" features = ["client_system"] default-features = false [dev-dependencies.sctk] version = "0.19.2" features = [ "calloop", "xkbcommon", ] default-features = false package = "smithay-client-toolkit" [features] default = ["dlopen"] dlopen = ["wayland-backend/dlopen"] smithay-clipboard-0.7.2/Cargo.toml.orig000064400000000000000000000016161046102023000161400ustar 00000000000000[package] name = "smithay-clipboard" version = "0.7.2" authors = ["Kirill Chibisov ", "Elinor 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] libc = "0.2.149" sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = ["calloop"] } wayland-backend = { version = "0.3.5", default-features = false, features = ["client_system"] } [dev-dependencies] sctk = { package = "smithay-client-toolkit", version = "0.19.2", default-features = false, features = ["calloop", "xkbcommon"] } [features] default = ["dlopen"] dlopen = ["wayland-backend/dlopen" ] smithay-clipboard-0.7.2/LICENSE000064400000000000000000000020611046102023000142510ustar 00000000000000Copyright (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. smithay-clipboard-0.7.2/README.md000064400000000000000000000016131046102023000145250ustar 00000000000000[![crates.io](https://img.shields.io/crates/v/smithay-clipboard.svg)](https://crates.io/crates/smithay-clipboard) # Smithay Clipboard This crate provides access to the Wayland clipboard for applications already using some sort of GUI toolkit or a windowing library, like [winit](https://github.com/rust-windowing/winit), since you should have some surface around to receive keyboard/pointer events. If you want to access clipboard from the CLI or to write clipboard manager, this is not what you're looking for. ## Documentation The documentation for the master branch is [available online](https://smithay.github.io/smithay-clipboard/). The documentation for the releases can be found on [docs.rs](https://docs.rs/smithay-clipboard). ## Contact Us If you have questions or want to discuss the project with us, join our chatroom on matrix: [#sctk:matrix.org](https://matrix.to/#/#sctk:matrix.org). smithay-clipboard-0.7.2/doc_index.html000064400000000000000000000002051046102023000160640ustar 00000000000000 smithay-clipboard-0.7.2/examples/clipboard.rs000064400000000000000000000303321046102023000173710ustar 00000000000000// 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. use sctk::compositor::{CompositorHandler, CompositorState}; use sctk::output::{OutputHandler, OutputState}; use sctk::reexports::calloop::{EventLoop, LoopHandle}; use sctk::reexports::calloop_wayland_source::WaylandSource; use sctk::reexports::client::globals::registry_queue_init; use sctk::reexports::client::protocol::{wl_keyboard, wl_output, wl_seat, wl_shm, wl_surface}; use sctk::reexports::client::{Connection, Proxy, QueueHandle}; use sctk::registry::{ProvidesRegistryState, RegistryState}; use sctk::seat::keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers}; use sctk::seat::{Capability, SeatHandler, SeatState}; use sctk::shell::xdg::window::{Window, WindowConfigure, WindowDecorations, WindowHandler}; use sctk::shell::xdg::XdgShell; use sctk::shell::WaylandSurface; use sctk::shm::slot::{Buffer, SlotPool}; use sctk::shm::{Shm, ShmHandler}; use sctk::{ delegate_compositor, delegate_keyboard, delegate_output, delegate_registry, delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, registry_handlers, }; use smithay_clipboard::Clipboard; const MIN_DIM_SIZE: usize = 256; fn main() { let connection = Connection::connect_to_env().unwrap(); let (globals, event_queue) = registry_queue_init(&connection).unwrap(); let queue_handle = event_queue.handle(); let mut event_loop: EventLoop = EventLoop::try_new().expect("Failed to initialize the event loop!"); let loop_handle = event_loop.handle(); WaylandSource::new(connection.clone(), event_queue).insert(loop_handle).unwrap(); let compositor = CompositorState::bind(&globals, &queue_handle).expect("wl_compositor not available"); let xdg_shell = XdgShell::bind(&globals, &queue_handle).expect("xdg shell is not available"); let shm = Shm::bind(&globals, &queue_handle).expect("wl shm is not available."); let surface = compositor.create_surface(&queue_handle); let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &queue_handle); window.set_title(String::from("smithay-clipboard example. Press C/c/P/p to copy/paste")); window.set_min_size(Some((MIN_DIM_SIZE as u32, MIN_DIM_SIZE as u32))); window.commit(); let clipboard = unsafe { Clipboard::new(connection.display().id().as_ptr() as *mut _) }; let pool = SlotPool::new(MIN_DIM_SIZE * MIN_DIM_SIZE * 4, &shm).expect("Failed to create pool"); let mut simple_window = SimpleWindow { registry_state: RegistryState::new(&globals), seat_state: SeatState::new(&globals, &queue_handle), output_state: OutputState::new(&globals, &queue_handle), shm, clipboard, exit: false, first_configure: true, pool, width: 256, height: 256, buffer: None, window, keyboard: None, keyboard_focus: false, loop_handle: event_loop.handle(), }; // We don't draw immediately, the configure will notify us when to first draw. loop { event_loop.dispatch(None, &mut simple_window).unwrap(); if simple_window.exit { break; } } } struct SimpleWindow { registry_state: RegistryState, seat_state: SeatState, output_state: OutputState, shm: Shm, clipboard: Clipboard, exit: bool, first_configure: bool, pool: SlotPool, width: u32, height: u32, buffer: Option, window: Window, keyboard: Option, keyboard_focus: bool, loop_handle: LoopHandle<'static, SimpleWindow>, } impl CompositorHandler for SimpleWindow { fn scale_factor_changed( &mut self, _: &Connection, _: &QueueHandle, _: &wl_surface::WlSurface, _: i32, ) { // Not needed for this example. } fn surface_enter( &mut self, _: &Connection, _: &QueueHandle, _: &wl_surface::WlSurface, _: &wl_output::WlOutput, ) { } fn surface_leave( &mut self, _: &Connection, _: &QueueHandle, _: &wl_surface::WlSurface, _: &wl_output::WlOutput, ) { } fn transform_changed( &mut self, _: &Connection, _: &QueueHandle, _: &wl_surface::WlSurface, _: wl_output::Transform, ) { // Not needed for this example. } fn frame( &mut self, conn: &Connection, qh: &QueueHandle, _surface: &wl_surface::WlSurface, _time: u32, ) { self.draw(conn, qh); } } impl OutputHandler for SimpleWindow { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state } fn new_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn update_output( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } fn output_destroyed( &mut self, _conn: &Connection, _qh: &QueueHandle, _output: wl_output::WlOutput, ) { } } impl WindowHandler for SimpleWindow { fn request_close(&mut self, _: &Connection, _: &QueueHandle, _: &Window) { self.exit = true; } fn configure( &mut self, conn: &Connection, qh: &QueueHandle, _window: &Window, configure: WindowConfigure, _serial: u32, ) { println!("Window configured to: {:?}", configure); self.buffer = None; self.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256); self.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256); // Initiate the first draw. if self.first_configure { self.first_configure = false; self.draw(conn, qh); } } } impl SeatHandler for SimpleWindow { fn seat_state(&mut self) -> &mut SeatState { &mut self.seat_state } fn new_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} fn new_capability( &mut self, _conn: &Connection, qh: &QueueHandle, seat: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_none() { println!("Set keyboard capability"); let keyboard = self .seat_state .get_keyboard_with_repeat( qh, &seat, None, self.loop_handle.clone(), Box::new(|_state, _wl_kbd, event| { println!("Repeat: {:?} ", event); }), ) .expect("Failed to create keyboard"); self.keyboard = Some(keyboard); } } fn remove_capability( &mut self, _conn: &Connection, _: &QueueHandle, _: wl_seat::WlSeat, capability: Capability, ) { if capability == Capability::Keyboard && self.keyboard.is_some() { println!("Unset keyboard capability"); self.keyboard.take().unwrap().release(); } } fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, _: wl_seat::WlSeat) {} } impl KeyboardHandler for SimpleWindow { fn enter( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, _: &[u32], keysyms: &[Keysym], ) { if self.window.wl_surface() == surface { println!("Keyboard focus on window with pressed syms: {keysyms:?}"); self.keyboard_focus = true; } } fn leave( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, surface: &wl_surface::WlSurface, _: u32, ) { if self.window.wl_surface() == surface { println!("Release keyboard focus on window"); self.keyboard_focus = false; } } fn press_key( &mut self, _conn: &Connection, _qh: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, event: KeyEvent, ) { match event.utf8.as_deref() { // Paste primary. Some("P") => match self.clipboard.load_primary() { Ok(contents) => println!("Paste from primary clipboard: {contents}"), Err(err) => eprintln!("Error loading from primary clipboard: {err}"), }, // Paste clipboard. Some("p") => match self.clipboard.load() { Ok(contents) => println!("Paste from clipboard: {contents}"), Err(err) => eprintln!("Error loading from clipboard: {err}"), }, // 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(to_store); println!("Copied string into clipboard: {}", to_store); }, _ => (), } } fn release_key( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, _event: KeyEvent, ) { } fn update_modifiers( &mut self, _: &Connection, _: &QueueHandle, _: &wl_keyboard::WlKeyboard, _: u32, _: Modifiers, _: u32, ) { } } impl ShmHandler for SimpleWindow { fn shm_state(&mut self) -> &mut Shm { &mut self.shm } } impl SimpleWindow { pub fn draw(&mut self, _conn: &Connection, qh: &QueueHandle) { let width = self.width; let height = self.height; let stride = self.width as i32 * 4; let buffer = self.buffer.get_or_insert_with(|| { self.pool .create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888) .expect("create buffer") .0 }); let canvas = match self.pool.canvas(buffer) { Some(canvas) => canvas, None => { // This should be rare, but if the compositor has not released the previous // buffer, we need double-buffering. let (second_buffer, canvas) = self .pool .create_buffer( self.width as i32, self.height as i32, stride, wl_shm::Format::Argb8888, ) .expect("create buffer"); *buffer = second_buffer; canvas }, }; // Draw to the window: canvas.chunks_exact_mut(4).enumerate().for_each(|(_, chunk)| { // ARGB color. let color = 0xFF181818u32; let array: &mut [u8; 4] = chunk.try_into().unwrap(); *array = color.to_le_bytes(); }); // Damage the entire window self.window.wl_surface().damage_buffer(0, 0, self.width as i32, self.height as i32); // Request our next frame self.window.wl_surface().frame(qh, self.window.wl_surface().clone()); // Attach and commit to present. buffer.attach_to(self.window.wl_surface()).expect("buffer attach"); self.window.commit(); } } delegate_compositor!(SimpleWindow); delegate_output!(SimpleWindow); delegate_shm!(SimpleWindow); delegate_seat!(SimpleWindow); delegate_keyboard!(SimpleWindow); delegate_xdg_shell!(SimpleWindow); 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 } } smithay-clipboard-0.7.2/rustfmt.toml000064400000000000000000000006651046102023000156550ustar 00000000000000format_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" format_strings = true wrap_comments = true comment_width = 80 edition = "2021" smithay-clipboard-0.7.2/src/lib.rs000064400000000000000000000070401046102023000151510ustar 00000000000000//! Smithay Clipboard //! //! 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}; use sctk::reexports::calloop::channel::{self, Sender}; use sctk::reexports::client::backend::Backend; use sctk::reexports::client::Connection; mod mime; mod state; mod worker; /// Access to a Wayland clipboard. pub struct Clipboard { request_sender: Sender, request_receiver: Receiver>, clipboard_thread: Option>, } impl Clipboard { /// Creates new clipboard which will be running on its own thread with its /// own event queue to handle clipboard requests. /// /// # Safety /// /// `display` must be a valid `*mut wl_display` pointer, and it must remain /// valid for as long as `Clipboard` object is alive. pub unsafe fn new(display: *mut c_void) -> Self { let backend = unsafe { Backend::from_foreign_display(display.cast()) }; let connection = Connection::from_backend(backend); // Create channel to send data to clipboard thread. 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, rx_chan, clipboard_reply_sender); Self { request_receiver, request_sender, clipboard_thread } } /// Load clipboard data. /// /// Loads content from a clipboard on a last observed seat. pub fn load(&self) -> Result { let _ = self.request_sender.send(worker::Command::Load); if let Ok(reply) = self.request_receiver.recv() { reply } else { // The clipboard thread is dead, however we shouldn't crash downstream, so // propogating an error. Err(std::io::Error::new(std::io::ErrorKind::Other, "clipboard is dead.")) } } /// Store to a clipboard. /// /// Stores to a clipboard on a last observed seat. pub fn store>(&self, text: T) { let request = worker::Command::Store(text.into()); let _ = self.request_sender.send(request); } /// Load primary clipboard data. /// /// Loads content from a primary clipboard on a last observed seat. pub fn load_primary(&self) -> Result { let _ = self.request_sender.send(worker::Command::LoadPrimary); if let Ok(reply) = self.request_receiver.recv() { reply } else { // The clipboard thread is dead, however we shouldn't crash downstream, so // propogating an error. Err(std::io::Error::new(std::io::ErrorKind::Other, "clipboard is dead.")) } } /// Store to a primary clipboard. /// /// Stores to a primary clipboard on a last observed seat. pub fn store_primary>(&self, text: T) { let request = worker::Command::StorePrimary(text.into()); let _ = self.request_sender.send(request); } } impl Drop for Clipboard { fn drop(&mut self) { // Shutdown smithay-clipboard. let _ = self.request_sender.send(worker::Command::Exit); if let Some(clipboard_thread) = self.clipboard_thread.take() { let _ = clipboard_thread.join(); } } } smithay-clipboard-0.7.2/src/mime.rs000064400000000000000000000037631046102023000153420ustar 00000000000000/// List of allowed mimes. pub static ALLOWED_MIME_TYPES: [&str; 3] = ["text/plain;charset=utf-8", "UTF8_STRING", "text/plain"]; /// Mime type supported by clipboard. #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub enum MimeType { /// text/plain;charset=utf-8 mime type. /// /// The primary mime type used by most clients TextPlainUtf8 = 0, /// UTF8_STRING mime type. /// /// Some X11 clients are using only this mime type, so we /// should have it as a fallback just in case. Utf8String = 1, /// text/plain mime type. /// /// Fallback without charset parameter. TextPlain = 2, } 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`. pub fn find_allowed(offered_mime_types: &[String]) -> Option { let mut fallback = None; for offered_mime_type in offered_mime_types.iter() { if offered_mime_type == ALLOWED_MIME_TYPES[Self::TextPlainUtf8 as usize] { return Some(Self::TextPlainUtf8); } else if offered_mime_type == ALLOWED_MIME_TYPES[Self::Utf8String as usize] { return Some(Self::Utf8String); } else if offered_mime_type == ALLOWED_MIME_TYPES[Self::TextPlain as usize] { // Only use this mime type as a fallback. fallback = Some(Self::TextPlain); } } fallback } } impl std::fmt::Display for MimeType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", ALLOWED_MIME_TYPES[*self as usize]) } } /// Normalize CR and CRLF into LF. /// /// 'text' mime types require CRLF line ending according to /// RFC-2046, however the platform line terminator and what applications /// expect is LF. pub fn normalize_to_lf(text: String) -> String { text.replace("\r\n", "\n").replace('\r', "\n") } smithay-clipboard-0.7.2/src/state.rs000064400000000000000000000473701046102023000155350ustar 00000000000000use std::borrow::Cow; use std::collections::HashMap; use std::io::{Error, ErrorKind, Read, Result, Write}; use std::mem; use std::os::unix::io::{AsRawFd, 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::calloop::{LoopHandle, PostAction}; 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, Dispatch, Proxy, QueueHandle}; use sctk::reexports::protocols::wp::primary_selection::zv1::client::{ zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, }; 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_else(|| Error::new(ErrorKind::Other, "no events received on any seat"))?; let seat = self .seats .get_mut(latest) .ok_or_else(|| 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()) .ok_or_else(|| Error::new(ErrorKind::Other, "selection is empty"))?; let mime_type = selection.with_mime_types(MimeType::find_allowed).ok_or_else(|| { 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()) .ok_or_else(|| Error::new(ErrorKind::Other, "selection is empty"))?; let mime_type = selection.with_mime_types(MimeType::find_allowed).ok_or_else(|| { 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() }; 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 | MimeType::TextPlain => { 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; }, }; } }); 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() }; loop { match file.write(&contents[written..]) { Ok(n) if written + n == contents.len() => { written += n; break PostAction::Remove; }, Ok(n) => written += n, Err(err) if err.kind() == ErrorKind::WouldBlock => break PostAction::Continue, Err(_) => break PostAction::Remove, } } }); } } 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, _: f64, _: f64, _: &WlSurface, ) { } fn leave(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice) {} fn motion(&mut self, _: &Connection, _: &QueueHandle, _: &WlDataDevice, _: f64, _: f64) {} 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(()) } smithay-clipboard-0.7.2/src/worker.rs000064400000000000000000000065131046102023000157200ustar 00000000000000use 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 its own `EventQueue` 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, event_queue).insert(loop_handle).unwrap(); loop { if event_loop.dispatch(None, &mut state).is_err() || state.exit { break; } } }