├── .editorconfig ├── .gitignore ├── BUILDING.md ├── COPYING ├── README.md ├── completions ├── bash │ ├── meson.build │ ├── wl-copy │ └── wl-paste ├── fish │ ├── meson.build │ ├── wl-copy.fish │ └── wl-paste.fish ├── meson.build └── zsh │ ├── _wl-copy │ ├── _wl-paste │ └── meson.build ├── data ├── meson.build ├── wl-clipboard.1 ├── wl-copy.1 └── wl-paste.1 ├── meson.build ├── meson_options.txt ├── src ├── includes │ ├── selection-protocols.h │ └── shell-protocols.h ├── meson.build ├── protocol │ ├── gtk-primary-selection.xml │ ├── gtk-shell.xml │ ├── meson.build │ └── wlr-data-control-unstable-v1.xml ├── types │ ├── copy-action.c │ ├── copy-action.h │ ├── device-manager.c │ ├── device-manager.h │ ├── device.c │ ├── device.h │ ├── keyboard.c │ ├── keyboard.h │ ├── offer.c │ ├── offer.h │ ├── popup-surface.c │ ├── popup-surface.h │ ├── registry.c │ ├── registry.h │ ├── seat.c │ ├── seat.h │ ├── shell-surface.c │ ├── shell-surface.h │ ├── shell.c │ ├── shell.h │ ├── source.c │ └── source.h ├── util │ ├── files.c │ ├── files.h │ ├── misc.c │ ├── misc.h │ ├── string.c │ └── string.h ├── wl-copy.c └── wl-paste.c └── subprojects ├── expat.wrap ├── libffi.wrap ├── wayland-protocols.wrap └── wayland.wrap /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [**.{c,h}] 12 | max_line_length = 80 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Building 2 | 3 | wl-clipboard is a simple Meson project, so building it is just: 4 | 5 | ```bash 6 | # Clone: 7 | $ git clone https://github.com/bugaevc/wl-clipboard.git 8 | $ cd wl-clipboard 9 | 10 | # Build: 11 | $ meson setup build 12 | $ cd build 13 | $ ninja 14 | 15 | # Install 16 | $ sudo meson install 17 | ``` 18 | 19 | wl-clipboard supports Linux and BSD systems, and is also known to work on 20 | Mac OS X and GNU Hurd. The only mandatory dependency is the `wayland-client` 21 | library (try package named `wayland-devel` or `libwayland-dev`). 22 | 23 | Optional (but highly recommended) dependencies for building: 24 | * `wayland-scanner` 25 | * `wayland-protocols` (version 1.12 or later) 26 | 27 | If these are found during configuration, wl-clipboard gets built with 28 | additional protocols support, which enables features such as primary selection 29 | support and `--watch` mode. If they are not found, Meson can optionally build 30 | them on the spot as subprojects. 31 | 32 | Note that many compositors have dropped support for the `wl_shell` interface, 33 | which means wl-clipboard will not work under them unless built with both 34 | `wayland-scanner` and `wayland-protocols`. For this reason, you have to 35 | explicitly opt into allowing building without these dependencies by specifying 36 | `-D protocols=auto` (or `-D protocols=disabled`) when configuring with Meson. 37 | 38 | Optional dependencies for running: 39 | * `xdg-mime` for content type inference in `wl-copy` (try package named 40 | `xdg-utils`) 41 | * `/etc/mime.types` file for type inference in `wl-paste` (try package named 42 | `mime-support` or `mailcap`) 43 | 44 | If you're packaging wl-clipboard for a distribution, please consider making 45 | packages providing `xdg-mime` and `/etc/mime.types` *weak* dependencies of the 46 | package providing wl-clipboard, meaning ones that get installed along with 47 | wl-clipboard by default, but are not strictly required by it. 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wl-clipboard: Wayland clipboard utilities 2 | 3 | This project implements two command-line Wayland clipboard utilities, `wl-copy` 4 | and `wl-paste`, that let you easily copy data between the clipboard and Unix 5 | pipes, sockets, files and so on. 6 | 7 | ```bash 8 | # Copy a simple text message: 9 | $ wl-copy Hello world! 10 | 11 | # Copy the list of files in ~/Downloads: 12 | $ ls ~/Downloads | wl-copy 13 | 14 | # Copy an image: 15 | $ wl-copy < ~/Pictures/photo.png 16 | 17 | # Copy the previous command: 18 | $ wl-copy "!!" 19 | 20 | # Paste to a file: 21 | $ wl-paste > clipboard.txt 22 | 23 | # Sort clipboard contents: 24 | $ wl-paste | sort | wl-copy 25 | 26 | # Upload clipboard contents to a pastebin on each change: 27 | $ wl-paste --watch nc paste.example.org 5555 28 | ``` 29 | 30 | Please see the wl-clipboard(1) man page for more details. 31 | 32 | # Installing 33 | 34 | wl-clipboard is likely available in your favorite Linux or BSD distro. For 35 | building from source, see [BUILDING.md](BUILDING.md). 36 | 37 | # License 38 | 39 | wl-clipboard is free software, available under the GNU General Public License 40 | version 3 or later. 41 | 42 | # Related projects 43 | 44 | * [wl-clipboard-x11](https://github.com/brunelli/wl-clipboard-x11): A wrapper to 45 | use wl-clipboard as a drop-in replacement to X11 clipboard tools. 46 | * [wl-clipboard-rs](https://github.com/YaLTeR/wl-clipboard-rs): A Rust crate 47 | (library) for working with the Wayland clipboard which includes a 48 | reimplementation of `wl-copy` and `wl-paste`. 49 | -------------------------------------------------------------------------------- /completions/bash/meson.build: -------------------------------------------------------------------------------- 1 | bash_completion_dir = join_paths(get_option('datadir'), 'bash-completion', 'completions') 2 | 3 | install_data('wl-copy', install_dir: bash_completion_dir) 4 | install_data('wl-paste', install_dir: bash_completion_dir) 5 | -------------------------------------------------------------------------------- /completions/bash/wl-copy: -------------------------------------------------------------------------------- 1 | # wl-copy(1) completion 2 | 3 | _wl_clipboard_list_seats() { 4 | weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}' 5 | } 6 | 7 | _wl_clipboard_complete_types() { 8 | local cur prev types 9 | cur="${COMP_WORDS[COMP_CWORD]}" 10 | prev="${COMP_WORDS[COMP_CWORD-1]}" 11 | types="$1" 12 | if [ "${cur:0:1}" = \' -o "${cur:0:1}" = \" ]; then 13 | COMPREPLY=($(compgen -W "$types" -- "$cur")) 14 | else 15 | COMPREPLY=($(compgen -W "$types" -- "$cur" | sed 's/;/\\;/g')) 16 | fi 17 | } 18 | 19 | _wl_copy_completion() { 20 | compopt +o default 21 | local cur prev opts types seats 22 | 23 | cur="${COMP_WORDS[COMP_CWORD]}" 24 | prev="${COMP_WORDS[COMP_CWORD-1]}" 25 | 26 | case "$cur" in 27 | "<"*) 28 | compopt -o default 29 | COMPREPLY=() 30 | return 31 | ;; 32 | esac 33 | 34 | case "$prev" in 35 | "<"*) 36 | compopt -o default 37 | COMPREPLY=() 38 | ;; 39 | -t | -[a-z]*t | --type) 40 | types="$(sed -e 's/#.*$//' -e '/^$/d' -e 's/^\(\S\+\).*/\1/' /etc/mime.types 2>/dev/null)" 41 | _wl_clipboard_complete_types "$types" 42 | ;; 43 | -s | -[a-z]*s | --seat) 44 | seats="$(_wl_clipboard_list_seats)" 45 | COMPREPLY=($(compgen -W "$seats" -- "$cur")) 46 | ;; 47 | *) 48 | opts="-o --paste-once " 49 | opts+="-f --foreground " 50 | opts+="-c --clear " 51 | opts+="-p --primary " 52 | opts+="-n --trim-newline " 53 | opts+="-t --type " 54 | opts+="-s --seat " 55 | opts+="-v --version " 56 | opts+="-h --help " 57 | COMPREPLY=($(compgen -W "$opts" -- "$cur")) 58 | ;; 59 | esac 60 | } 61 | 62 | complete -o default -F _wl_copy_completion wl-copy 63 | -------------------------------------------------------------------------------- /completions/bash/wl-paste: -------------------------------------------------------------------------------- 1 | # wl-paste(1) completion 2 | 3 | _wl_clipboard_list_seats() { 4 | weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}' 5 | } 6 | 7 | _wl_clipboard_complete_types() { 8 | local cur prev types 9 | cur="${COMP_WORDS[COMP_CWORD]}" 10 | prev="${COMP_WORDS[COMP_CWORD-1]}" 11 | types="$1" 12 | if [ "${cur:0:1}" = \' -o "${cur:0:1}" = \" ]; then 13 | COMPREPLY=($(compgen -W "$types" -- "$cur")) 14 | else 15 | COMPREPLY=($(compgen -W "$types" -- "$cur" | sed 's/;/\\;/g')) 16 | fi 17 | } 18 | 19 | _wl_clipboard_complete_paste_types() { 20 | local i cur primary seat cmd types 21 | for (( i = 0; i < "${#COMP_WORDS[@]}"; ++i )); do 22 | cur="${COMP_WORDS[i]}" 23 | case "$cur" in 24 | -p* | -[a-z]*p* | --primary) 25 | primary="yes" 26 | ;; 27 | esac 28 | case "$cur" in 29 | -s | -[a-z]*s | --seat) 30 | seat="${COMP_WORDS[i+1]}" 31 | ;; 32 | -s* | -[a-z]s*) 33 | seat=$(echo "$cur" | sed 's/-[^s]*s//') 34 | ;; 35 | esac 36 | done 37 | cmd="${COMP_WORDS[0]}" 38 | if [ -n "$primary" ]; then 39 | cmd="$cmd -p" 40 | fi 41 | if [ -n "$seat" ]; then 42 | cmd="$cmd -s $seat" 43 | fi 44 | cmd="$cmd -l" 45 | types="$($cmd 2>/dev/null)" 46 | _wl_clipboard_complete_types "$types" 47 | } 48 | 49 | _wl_paste_completion() { 50 | compopt +o default 51 | local cur prev opts types seats offset 52 | 53 | for (( offset=1; offset < COMP_CWORD; offset++ )); do 54 | cur="${COMP_WORDS[offset]}" 55 | if [ "$cur" = "-w" -o "$cur" = "--watch" ]; then 56 | _command_offset $(($offset+1)) 57 | return 58 | fi 59 | done 60 | 61 | cur="${COMP_WORDS[COMP_CWORD]}" 62 | prev="${COMP_WORDS[COMP_CWORD-1]}" 63 | 64 | case "$cur" in 65 | ">"*) 66 | compopt -o default 67 | COMPREPLY=() 68 | return 69 | ;; 70 | esac 71 | 72 | case "$prev" in 73 | ">"*) 74 | compopt -o default 75 | COMPREPLY=() 76 | ;; 77 | -t | -[a-z]*t | --type) 78 | _wl_clipboard_complete_paste_types 79 | ;; 80 | -s | -[a-z]*s | --seat) 81 | seats="$(_wl_clipboard_list_seats)" 82 | COMPREPLY=($(compgen -W "$seats" -- "$cur")) 83 | ;; 84 | *) 85 | opts="-n --no-newline " 86 | opts+="-l --list-types " 87 | opts+="-p --primary " 88 | opts+="-w --watch " 89 | opts+="-t --type " 90 | opts+="-s --seat " 91 | opts+="-v --version " 92 | opts+="-h --help " 93 | COMPREPLY=($(compgen -W "$opts" -- "$cur")) 94 | ;; 95 | esac 96 | } 97 | 98 | complete -o default -F _wl_paste_completion wl-paste 99 | -------------------------------------------------------------------------------- /completions/fish/meson.build: -------------------------------------------------------------------------------- 1 | fish_completion_dir = get_option('fishcompletiondir') 2 | fish = dependency('fish', required : false) 3 | 4 | if fish_completion_dir == '' and fish.found() 5 | if meson.version().version_compare('>= 0.58') 6 | fish_completion_dir = fish.get_variable('completionsdir') 7 | else 8 | fish_completion_dir = fish.get_pkgconfig_variable('completionsdir') 9 | endif 10 | elif fish_completion_dir == '' 11 | # Fish does not look in /usr/local/, which is the default prefix, 12 | # so we cannot use get_option("datadir"). Instead, they recommend 13 | # custom completions to be installed into /usr/share/fish/vendor_completions.d, 14 | # so we use that. Note that this is a fallback in case you neither 15 | # have fish installed, nor have specified a path explicitly 16 | fish_completion_dir = '/usr/share/fish/vendor_completions.d' 17 | endif 18 | 19 | if fish_completion_dir != 'no' 20 | install_data('wl-copy.fish', install_dir: fish_completion_dir) 21 | install_data('wl-paste.fish', install_dir: fish_completion_dir) 22 | endif 23 | -------------------------------------------------------------------------------- /completions/fish/wl-copy.fish: -------------------------------------------------------------------------------- 1 | function __wayland_seats --description 'Print all wayland seats' 2 | if type -q weston-info 3 | weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}' 4 | else if type -q loginctl 5 | loginctl --no-legend --no-pager list-seats 2>/dev/null 6 | else 7 | # Fallback seat 8 | echo "seat0" 9 | end 10 | 11 | end 12 | 13 | complete -c wl-copy -x 14 | complete -c wl-copy -s h -l help -d 'Display a help message' 15 | complete -c wl-copy -s v -l version -d 'Display version info' 16 | complete -c wl-copy -s o -l paste-once -d 'Only serve one paste request and then exit' 17 | complete -c wl-copy -s c -l clear -d 'Instead of copying anything, clear the clipboard' 18 | complete -c wl-copy -s p -l primary -d 'Use the "primary" clipboard' 19 | complete -c wl-copy -s n -l trim-newline -d 'Do not copy the trailing newline character' 20 | complete -c wl-copy -s t -l type -x -d 'Override the inferred MIME type for the content' -a "(__fish_print_xdg_mimetypes)" 21 | complete -c wl-copy -s s -l seat -x -d 'Pick the seat to work with' -a "(__wayland_seats)" 22 | -------------------------------------------------------------------------------- /completions/fish/wl-paste.fish: -------------------------------------------------------------------------------- 1 | function __wayland_seats --description 'Print all wayland seats' 2 | if type -q weston-info 3 | weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}' 4 | else if type -q loginctl 5 | loginctl --no-legend --no-pager list-seats 2>/dev/null 6 | else 7 | # Fallback seat 8 | echo "seat0" 9 | end 10 | end 11 | 12 | function __wl_paste_types --description 'Print types with context' 13 | set -l type (commandline -o) 14 | 15 | for i in (seq (count $type)) 16 | if [ "$type[$i]" = '--primary' ]; or [ "$type[$i]" = '-p' ] 17 | set clip "primary" 18 | else if [ "$type[$i]" = '--seat' ]; or [ "$type[$i]" = '-s' ] 19 | set seat $type[(math $i + 1)] 20 | end 21 | end 22 | 23 | # Note fish does not handle passing unset variables 24 | # to commands well, thus setting clip to "--primary" and passing 25 | # that to wl-paste wont work, so if statements are used instead 26 | if test -n "$seat"; and test -n "$clip" 27 | wl-paste 2>/dev/null --seat "$seat" -p -l 28 | else if test -n "$seat" 29 | wl-paste 2>/dev/null --seat "$seat" -l 30 | else if test -n "$clip" 31 | wl-paste 2>/dev/null -p -l 32 | else 33 | wl-paste 2>/dev/null -l 34 | end 35 | end 36 | 37 | complete -c wl-paste -f 38 | complete -c wl-paste -f -n '__fish_contains_opt -s w watch' -a "(__fish_complete_subcommand -- -s --seat -t --type)" 39 | complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s h -l help -d 'Display a help message' 40 | complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s v -l version -d 'Display version info' 41 | complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s n -l no-newline -d 'Do not append a newline character' 42 | complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s l -l list-types -d 'Instead of pasting, list the offered types' 43 | complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s p -l primary -d 'Use the "primary" clipboard' 44 | complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s w -l watch -d 'Run a command each time the selection changes' 45 | complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s t -l type -x -d 'Override the inferred MIME type for the content' -a "(__wl_paste_types)" 46 | complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s s -l seat -x -d 'Pick the seat to work with' -a "(__wayland_seats)" 47 | -------------------------------------------------------------------------------- /completions/meson.build: -------------------------------------------------------------------------------- 1 | subdir('bash') 2 | subdir('zsh') 3 | subdir('fish') 4 | -------------------------------------------------------------------------------- /completions/zsh/_wl-copy: -------------------------------------------------------------------------------- 1 | #compdef wl-copy 2 | 3 | __xdg_mimetypes() { 4 | local expl SEARCH=${XDG_DATA_HOME:-$HOME/.local/share} 5 | SEARCH+=:${XDG_DATA_DIRS:-/usr/share:/usr/local/share} 6 | 7 | local -a _all_mimetypes 8 | for dir in ${(s.:.)SEARCH}; do 9 | [[ -f "${dir}/mime/types" ]] && 10 | _all_mimetypes+=(${(f)"$(<"${dir}/mime/types")"}) 11 | done 12 | 13 | _wanted mimetypes expl 'mimetypes' _multi_parts / _all_mimetypes 14 | } 15 | 16 | __all_seats() { 17 | local -a seats 18 | if (( $+commands[weston-info] && $+commands[sed] )); then 19 | seats=( ${(@f)"$(weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}')"} ) 20 | elif (( $+commands[loginctl] )); then 21 | seats=( ${(@f)"$(loginctl --no-legend --no-pager list-seats 2>/dev/null)"} ) 22 | fi 23 | 24 | if [[ -z $seats ]]; then 25 | # seat0 is always a vaild seat and covers most cases, so its a good fallback. 26 | compadd "$@" - seat0 27 | else 28 | compadd "$@" -a seats 29 | fi 30 | } 31 | 32 | _arguments -S -s \ 33 | {-o,--paste-once}'[Only serve one paste request and then exit]' \ 34 | {-f,--foreground}'[Stay in the foreground instead of forking]' \ 35 | {-c,--clear}'[Instead of copying anything, clear the clipboard]' \ 36 | {-p,--primary}'[Use the "primary" clipboard]' \ 37 | {-n,--trim-newline}'[Do not copy the trailing newline character]' \ 38 | {-t+,--type=}'[Override the inferred MIME type for the content]:mimetype:__xdg_mimetypes' \ 39 | {-s+,--seat=}'[Pick the seat to work with]:seat:__all_seats' \ 40 | {-v,--version}'[Display version info]' \ 41 | {-h,--help}'[Display a help message]' \ 42 | '*::text' 43 | -------------------------------------------------------------------------------- /completions/zsh/_wl-paste: -------------------------------------------------------------------------------- 1 | #compdef wl-paste 2 | 3 | local expl 4 | typeset -A opt_args 5 | 6 | _wl-paste_types() { 7 | local -a types args 8 | args=( 9 | ${(kv)opt_args[(I)-p|--primary]} 10 | ${(kv)opt_args[(I)-s|--seat]} 11 | ) 12 | types=( ${(@f)"$(wl-paste $args -l 2>/dev/null)"} ) 13 | 14 | local expl 15 | _wanted types expl 'type' compadd "$@" -a types 16 | } 17 | 18 | __all_seats() { 19 | local -a seats 20 | if (( $+commands[weston-info] && $+commands[sed] )); then 21 | seats=( ${(@f)"$(weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}')"} ) 22 | elif (( $+commands[loginctl] )); then 23 | seats=( ${(@f)"$(loginctl --no-legend --no-pager list-seats 2>/dev/null)"} ) 24 | fi 25 | 26 | if [[ -z $seats ]]; then 27 | # seat0 covers most cases, so its a good fallback. 28 | compadd "$@" - seat0 29 | else 30 | compadd "$@" -a seats 31 | fi 32 | } 33 | 34 | _arguments -S -s \ 35 | {-n,--no-newline}'[Do not append a newline character]' \ 36 | {-l,--list-types}'[Instead of pasting, list the offered types]' \ 37 | {-p,--primary}'[Use the "primary" clipboard]' \ 38 | {-w,--watch}'[Run a command wach time the selection changes]:*::command:_normal' \ 39 | {-t+,--type=}'[Override the inferred MIME type for the content]:mimetype:_wl-paste_types' \ 40 | {-s+,--seat=}'[Pick the seat to work with]:seat:__all_seats' \ 41 | {-v,--version}'[Display version info]' \ 42 | {-h,--help}'[Display a help message]' 43 | -------------------------------------------------------------------------------- /completions/zsh/meson.build: -------------------------------------------------------------------------------- 1 | zsh_completion_dir = get_option('zshcompletiondir') 2 | if zsh_completion_dir == '' 3 | zsh_completion_dir = join_paths(get_option('datadir'), 'zsh', 'site-functions') 4 | endif 5 | 6 | if zsh_completion_dir != 'no' 7 | install_data('_wl-copy', install_dir: zsh_completion_dir) 8 | install_data('_wl-paste', install_dir: zsh_completion_dir) 9 | endif 10 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | install_man('wl-copy.1') 2 | install_man('wl-paste.1') 3 | install_man('wl-clipboard.1') -------------------------------------------------------------------------------- /data/wl-clipboard.1: -------------------------------------------------------------------------------- 1 | .TH WL-CLIPBOARD 1 2025-03-24 wl-clipboard 2 | .SH NAME 3 | wl-clipboard \- Wayland copy and paste command line utilities 4 | .SH SYNOPSIS 5 | .B wl-copy 6 | [\fB\-\-primary\fR] 7 | [\fB\-\-type \fImime/type\fR] 8 | [\fItext\fR...] 9 | .PP 10 | .B wl-paste 11 | [\fB\-\-primary\fR] 12 | [\fB\-\-type \fImime/type\fR] 13 | .PP 14 | Only the most useful options are listed here; see below for the full list. 15 | .SH DESCRIPTION 16 | \fBwl-copy\fR copies the given \fItext\fR to the Wayland clipboard. 17 | If no \fItext\fR is given, \fBwl-copy\fR copies data from its standard input. 18 | .PP 19 | \fBwl-paste\fR pastes data from the Wayland clipboard to its standard output. 20 | .PP 21 | Although \fBwl-copy\fR and \fBwl-paste\fR are particularly optimized for plain 22 | text and other textual content formats, they fully support content of arbitrary 23 | MIME types. \fBwl-copy\fR automatically infers the type of the copied content by 24 | running \fBxdg-mime\fR(1) on it. \fBwl-paste\fR tries its best to pick a type to 25 | paste based on the list of offered MIME types and the extension of the file it's 26 | pasting into. If you're not satisfied with the type they pick or don't want to 27 | rely on this implicit type inference, you can explicitly specify the type to use 28 | with the \fB\-\-type\fR option. 29 | .SH OPTIONS 30 | To parse options, wl-clipboard uses the 31 | .BR getopt (3) 32 | library routines, whose features depend on the C library in use. In particular, 33 | it may be possible to specify \fB\-\-\fR as an argument on its own to prevent 34 | any further arguments from getting parsed as options (which lets you copy text 35 | containing words that start with the \fB-\fR sign), and to shorten long options 36 | to their unambiguous prefixes. 37 | .TP 38 | \fB\-p\fR, \fB\-\-primary 39 | Use the "primary" clipboard instead of the regular clipboard. 40 | .TP 41 | \fB\-o\fR, \fB\-\-paste-once\fR (for \fBwl-copy\fR) 42 | Only serve one paste request and then exit. Unless a clipboard manager 43 | specifically designed to prevent this is in use, this has the effect of clearing 44 | the clipboard after the first paste, which is useful for copying sensitive data 45 | such as passwords. Note that this may break pasting into some clients that 46 | expect to be able to paste multiple times, in particular pasting into XWayland 47 | windows is known to break when this option is used. 48 | .TP 49 | \fB\-f\fR, \fB\-\-foreground\fR (for \fBwl-copy\fR) 50 | By default, \fBwl-copy\fR forks and serves data requests in the background; this 51 | option overrides that behavior, causing \fBwl-copy\fR to run in the foreground. 52 | .TP 53 | \fB\-c\fR, \fB\-\-clear\fR (for \fBwl-copy\fR) 54 | Instead of copying anything, clear the clipboard so that nothing is copied. 55 | .TP 56 | \fB\-n\fR, \fB\-\-trim-newline\fR (for \fBwl-copy\fR) 57 | Do not copy the trailing newline character if it is present in the input file. 58 | .TP 59 | \fB\-n\fR, \fB\-\-no-newline\fR (for \fBwl-paste\fR) 60 | Do not append a newline character after the pasted clipboard content. This 61 | option is automatically enabled for non-text content types and when using the 62 | \fB\-\-watch\fR mode. 63 | .TP 64 | \fB\-t\fI mime/type\fR, \fB\-\-type\fI mime/type 65 | Override the automatically selected MIME type. For \fBwl-copy\fR this option 66 | controls which type \fBwl-copy\fR will offer the content as. For \fBwl-paste\fR 67 | it controls which of the offered types \fBwl-paste\fR will request the content 68 | in. In addition to specific MIME types such as \fIimage/png\fR, \fBwl-paste\fR 69 | also accepts generic type names such as \fItext\fR and \fIimage\fR which make it 70 | automatically pick some offered MIME type that matches the given generic name. 71 | .TP 72 | \fB\-s\fI seat-name\fR, \fB\-\-seat\fI seat-name 73 | Specify which seat \fBwl-copy\fR and \fBwl-paste\fR should work with. Wayland 74 | natively supports multi-seat configurations where each seat gets its own mouse 75 | pointer, keyboard focus, and among other things its own separate clipboard. The 76 | name of the default seat is likely \fIdefault\fR or \fIseat0\fR, and additional 77 | seat names normally come from the 78 | .BR udev (7) 79 | property \fBENV{WL_SEAT}\fR. You can view the list of the currently available 80 | seats as advertised by the compositor using the 81 | .BR weston-info (1) 82 | tool. If you don't specify the seat name explicitly, \fBwl-copy\fR and 83 | \fBwl-paste\fR will pick a seat arbitrarily. If you are using a single-seat 84 | system, there is little reason to use this option. 85 | .TP 86 | \fB\-l\fR, \fB\-\-list-types\fR (for \fBwl-paste\fR) 87 | Instead of pasting the selection, output the list of MIME types it is offered 88 | in. 89 | .TP 90 | \fB\-w\fI command\fR..., \fB\-\-watch \fIcommand\fR... (for \fBwl-paste\fR) 91 | Instead of pasting once and exiting, continuously watch the clipboard for 92 | changes, and run the specified \fIcommand\fR each time a new selection appears. 93 | The spawned process can read the clipboard contents from its standard input. 94 | \fBwl-paste\fR also sets the \fBCLIPBOARD_STATE\fR variable in the environment 95 | of the spawned processes (see below). 96 | .IP 97 | This mode requires a compositor that supports the wlroots data-control protocol. 98 | .TP 99 | \fB\-\-sensitive\fR (for \fBwl-copy\fR) 100 | Hint that the data being copied contains passwords, keys, or other sensitive 101 | content. Some clipboard managers may react by not persisting the copied data in 102 | the clipboard history. This corresponds to \fBCLIPBOARD_STATE=sensitive\fR (see 103 | below). 104 | .TP 105 | \fB\-v\fR, \fB\-\-version 106 | Display the version of wl-clipboard and some short info about its license. 107 | .TP 108 | \fB\-h\fR, \fB\-\-help 109 | Display a short help message listing the available options. 110 | .SH ENVIRONMENT 111 | .TP 112 | .B WAYLAND_DISPLAY 113 | Specifies what Wayland server \fBwl-copy\fR and \fBwl-paste\fR should connect 114 | to. This is the same environment variable that you pass to other Wayland 115 | clients, such as graphical applications, that connect to this Wayland server. It 116 | is normally set up automatically by the graphical session and the Wayland 117 | compositor. See 118 | .BR wl_display_connect (3) 119 | for more details. 120 | .TP 121 | .B WAYLAND_DEBUG 122 | When set to \fB1\fR, causes the \fBwayland-client\fR(7) library to log every 123 | interaction \fBwl-copy\fR and \fBwl-paste\fR make with the Wayland compositor to 124 | stderr. 125 | .TP 126 | .B CLIPBOARD_STATE 127 | Set by \fBwl-paste\fR for the spawned command in \fB\-\-watch\fR mode. Currently 128 | the following possible values are \fIdefined\fR: 129 | .RS 130 | .TP 131 | CLIPBOARD_STATE=\fBdata 132 | Indicates that the clipboard contains data that the spawned command can read 133 | from its standard input. This is the most common case. 134 | .TP 135 | CLIPBOARD_STATE=\fBnil 136 | Indicates that the clipboard is empty. In this case the spawned command's 137 | standard input will be attached to \fI/dev/null\fR. Note that this is subtly 138 | different from the clipboard containing zero-sized data (which can be achieved, 139 | for instance, by running \fBwl-copy < /dev/null\fR). 140 | .TP 141 | CLIPBOARD_STATE=\fBclear 142 | Indicates that the clipboard is empty because of an explicit clear request, such 143 | as after running \fBwl-copy --clear\fR. As for \fBnil\fR, the command's standard 144 | input will be attached to \fI/dev/null\fR. 145 | .TP 146 | CLIPBOARD_STATE=\fBsensitive 147 | Indicates that the clipboard contains sensitive data such as a password or a 148 | key. It is probably best to avoid visibly displaying or persistently saving 149 | clipboard contents. 150 | .RE 151 | .IP 152 | Any client programs implementing the \fBCLIPBOARD_STATE\fR protocol are 153 | encouraged to implement proper support for all the values listed above, as well 154 | as to fall back to some sensible behavior if \fBCLIPBOARD_STATE\fR is unset or 155 | set to some unrecognized value (this is to leave the design space open for 156 | future extensions). However, the currently existing Wayland clipboard protocols 157 | don't let wl-clipboard identify the cases where \fBclear\fR and \fBsensitive\fR 158 | values should be set. For this reason, currently, wl-clipboard never actually 159 | sets \fBCLIPBOARD_STATE\fR to \fBclear\fR, and only sets it to \fBsensitive\fR 160 | when it enounters \fBx-kde-passwordManagerHint\fR among the MIME types. 161 | .IP 162 | The \fBCLIPBOARD_STATE\fR protocol was intentionally designed to not be specific 163 | to either wl-clipboard or Wayland; in fact, other clipboard tools are encouraged 164 | to implement the same protocol. Currently, the SerenityOS 165 | .BR paste (1) 166 | utility is known to implement the same \fBCLIPBOARD_STATE\fR protocol. 167 | .SH FILES 168 | .TP 169 | .I /etc/mime.types 170 | If present, read by \fBwl-paste\fR to infer the MIME type to paste in based on 171 | the file name extension of its standard output. 172 | .SH BUGS 173 | Unless the Wayland compositor implements the wlroots data-control protocol, 174 | wl-clipboard has to resort to using a hack to access the clipboard: it will 175 | briefly pop up a tiny transparent surface (window). On some desktop 176 | environments (in particular when using tiling window managers), this can cause 177 | visual issues such as brief flashing. In some cases the Wayland compositor 178 | doesn't give focus to the popup surface, which prevents wl-clipboard from 179 | accessing the clipboard and manifests as a hang. 180 | .PP 181 | There is currently no way to copy data in multiple MIME types, such as multiple 182 | image formats, at the same time. 183 | .br 184 | See 185 | .nh 186 | . 187 | .hy 188 | .PP 189 | wl-clipboard is not always able to detect that a MIME type is textual, which may 190 | break pasting into clients that expect textual formats, not 191 | \fIapplication/something\fR. The workaround, same as for all format inference 192 | issues, is to specify the desired MIME type explicitly, such as 193 | \fBwl-copy \-\-type\fI text/plain\fR. 194 | .PP 195 | \fBwl-copy \-\-clear\fR and \fBwl-copy \-\-paste-once\fR don't always interact 196 | well with clipboard managers that are overeager to preserve clipboard contents. 197 | .PP 198 | Applications written using the GTK 3 toolkit copy text with \(dq\er\en\(dq (also 199 | known as CR LF) line endings, which takes most other software by surprise. 200 | wl-cipboard does nothing to rectify this. The recommended workaround is piping 201 | \fBwl-paste\fR output through 202 | .BR dos2unix (1) 203 | when pasting from a GTK 3 application. 204 | .br 205 | See 206 | .nh 207 | . 208 | .hy 209 | .PP 210 | When trying to paste content copied with \fBwl-copy\fR, \fBwl-copy\fR does not 211 | check whether the requested MIME type is among those it has offered, and always 212 | provides the same data in response. 213 | .SH EXAMPLES 214 | .TP 215 | Copy a simple text message: 216 | $ 217 | .BI wl-copy " Hello world!" 218 | .TP 219 | Copy a message starting with dashes: 220 | .BI "wl-copy -- " --long 221 | .TP 222 | Copy the list of files in \fI~/Downloads\fR: 223 | $ 224 | .IB "ls ~/Downloads" " | wl-copy" 225 | .TP 226 | Copy an image: 227 | $ 228 | .BI "wl-copy < " ~/Pictures/photo.png 229 | .TP 230 | Copy the previous command: 231 | $ 232 | .B wl-copy \(dq!!\(dq 233 | .TP 234 | Paste to a file, without appending a newline: 235 | $ 236 | .BI "wl-paste \-n > " clipboard.txt 237 | .TP 238 | Sort clipboard contents: 239 | $ 240 | .B wl-paste | sort | wl-copy 241 | .TP 242 | Upload clipboard contents to a pastebin on each change: 243 | $ 244 | .BI "wl-paste --watch nc " "paste.example.org 5555 245 | .SH AUTHOR 246 | Written by Sergey Bugaev. 247 | .SH REPORTING BUGS 248 | Report wl-clipboard bugs to 249 | .br 250 | Please make sure to mention which Wayland compositor you are using, and attach 251 | \fBWAYLAND_DEBUG=1\fR debugging logs of wl-clipboard. 252 | .SH COPYRIGHT 253 | Copyright \(co 2018-2025 Sergey Bugaev. 254 | License GPLv3+: GNU GPL version 3 or later . 255 | .br 256 | This is free software: you are free to change and redistribute it. 257 | There is NO WARRANTY, to the extent permitted by law. 258 | .SH SEE ALSO 259 | .BR xclip (1), 260 | .BR xsel (1), 261 | .BR wl-clipboard-x11 (1) 262 | -------------------------------------------------------------------------------- /data/wl-copy.1: -------------------------------------------------------------------------------- 1 | .so man1/wl-clipboard.1 -------------------------------------------------------------------------------- /data/wl-paste.1: -------------------------------------------------------------------------------- 1 | .so man1/wl-clipboard.1 -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('wl-clipboard', 'c', 2 | version: '2.2.1', 3 | license: 'GPL-3.0-or-later', 4 | meson_version: '>= 0.47.0', 5 | default_options: 'c_std=gnu99' 6 | ) 7 | 8 | subdir('src') 9 | subdir('data') 10 | subdir('completions') 11 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('protocols', 2 | type: 'feature', 3 | value: 'enabled') 4 | option('zshcompletiondir', 5 | type: 'string', 6 | value: '', 7 | description: 'Directory for zsh completions. Set "no" to disable.' 8 | ) 9 | option('fishcompletiondir', 10 | type: 'string', 11 | value: '', 12 | description: 'Directory for fish completions. Set "no" to disable.' 13 | ) 14 | -------------------------------------------------------------------------------- /src/includes/selection-protocols.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef INCLUDES_SELECTION_PROTOCOLS_H 20 | #define INCLUDES_SELECTION_PROTOCOLS_H 21 | 22 | #include "config.h" 23 | 24 | #include 25 | 26 | #ifdef HAVE_WP_PRIMARY_SELECTION 27 | # include "wp-primary-selection.h" 28 | #endif 29 | 30 | #ifdef HAVE_GTK_PRIMARY_SELECTION 31 | # include "gtk-primary-selection.h" 32 | #endif 33 | 34 | #ifdef HAVE_WLR_DATA_CONTROL 35 | # include "wlr-data-control.h" 36 | #endif 37 | 38 | #ifdef HAVE_EXT_DATA_CONTROL 39 | # include "ext-data-control.h" 40 | #endif 41 | 42 | #endif /* INCLUDES_SELECTION_PROTOCOLS_H */ 43 | -------------------------------------------------------------------------------- /src/includes/shell-protocols.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef INCLUDES_SHELL_PROTOCOLS_H 20 | #define INCLUDES_SHELL_PROTOCOLS_H 21 | 22 | #include "config.h" 23 | 24 | #include 25 | 26 | #ifdef HAVE_XDG_SHELL 27 | # include "xdg-shell.h" 28 | #endif 29 | 30 | #ifdef HAVE_GTK_SHELL 31 | # include "gtk-shell.h" 32 | #endif 33 | 34 | /* Not strictly speaking a shell */ 35 | #ifdef HAVE_XDG_ACTIVATION 36 | # include "xdg-activation.h" 37 | #endif 38 | 39 | #endif /* INCLUDES_SHELL_PROTOCOLS_H */ 40 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | wayland = dependency( 2 | 'wayland-client', 3 | default_options: ['tests=false', 'documentation=false', 'dtd_validation=false'] 4 | ) 5 | 6 | cc = meson.get_compiler('c') 7 | have_memfd = cc.has_header_symbol('sys/syscall.h', 'SYS_memfd_create') 8 | have_shm_anon = cc.has_header_symbol('sys/mman.h', 'SHM_ANON') 9 | 10 | conf_data = configuration_data() 11 | 12 | conf_data.set('HAVE_MEMFD', have_memfd) 13 | conf_data.set('HAVE_SHM_ANON', have_shm_anon) 14 | 15 | subdir('protocol') 16 | 17 | configure_file(output: 'config.h', configuration: conf_data) 18 | 19 | lib = static_library( 20 | 'wl-clipboard', 21 | protocol_headers, 22 | 23 | 'includes/shell-protocols.h', 24 | 'includes/selection-protocols.h', 25 | 26 | 'util/string.h', 27 | 'util/string.c', 28 | 'util/files.h', 29 | 'util/files.c', 30 | 'util/misc.h', 31 | 'util/misc.c', 32 | 33 | 'types/source.h', 34 | 'types/source.c', 35 | 'types/offer.h', 36 | 'types/offer.c', 37 | 'types/device.h', 38 | 'types/device.c', 39 | 'types/device-manager.h', 40 | 'types/device-manager.c', 41 | 'types/keyboard.h', 42 | 'types/keyboard.c', 43 | 'types/seat.h', 44 | 'types/seat.c', 45 | 'types/shell.h', 46 | 'types/shell.c', 47 | 'types/shell-surface.h', 48 | 'types/shell-surface.c', 49 | 'types/popup-surface.h', 50 | 'types/popup-surface.c', 51 | 'types/registry.h', 52 | 'types/registry.c', 53 | 'types/copy-action.h', 54 | 'types/copy-action.c', 55 | dependencies: wayland, 56 | link_with: protocol_deps 57 | ) 58 | 59 | executable( 60 | 'wl-copy', 61 | 'wl-copy.c', 62 | protocol_headers, 63 | dependencies: wayland, 64 | link_with: lib, 65 | install: true 66 | ) 67 | executable( 68 | 'wl-paste', 69 | 'wl-paste.c', 70 | protocol_headers, 71 | dependencies: wayland, 72 | link_with: lib, 73 | install: true 74 | ) 75 | -------------------------------------------------------------------------------- /src/protocol/gtk-primary-selection.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2015, 2016 Red Hat 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the "Software"), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice (including the next 14 | paragraph) shall be included in all copies or substantial portions of the 15 | Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | 26 | 27 | This protocol provides the ability to have a primary selection device to 28 | match that of the X server. This primary selection is a shortcut to the 29 | common clipboard selection, where text just needs to be selected in order 30 | to allow copying it elsewhere. The de facto way to perform this action 31 | is the middle mouse button, although it is not limited to this one. 32 | 33 | Clients wishing to honor primary selection should create a primary 34 | selection source and set it as the selection through 35 | wp_primary_selection_device.set_selection whenever the text selection 36 | changes. In order to minimize calls in pointer-driven text selection, 37 | it should happen only once after the operation finished. Similarly, 38 | a NULL source should be set when text is unselected. 39 | 40 | wp_primary_selection_offer objects are first announced through the 41 | wp_primary_selection_device.data_offer event. Immediately after this event, 42 | the primary data offer will emit wp_primary_selection_offer.offer events 43 | to let know of the mime types being offered. 44 | 45 | When the primary selection changes, the client with the keyboard focus 46 | will receive wp_primary_selection_device.selection events. Only the client 47 | with the keyboard focus will receive such events with a non-NULL 48 | wp_primary_selection_offer. Across keyboard focus changes, previously 49 | focused clients will receive wp_primary_selection_device.events with a 50 | NULL wp_primary_selection_offer. 51 | 52 | In order to request the primary selection data, the client must pass 53 | a recent serial pertaining to the press event that is triggering the 54 | operation, if the compositor deems the serial valid and recent, the 55 | wp_primary_selection_source.send event will happen in the other end 56 | to let the transfer begin. The client owning the primary selection 57 | should write the requested data, and close the file descriptor 58 | immediately. 59 | 60 | If the primary selection owner client disappeared during the transfer, 61 | the client reading the data will receive a 62 | wp_primary_selection_device.selection event with a NULL 63 | wp_primary_selection_offer, the client should take this as a hint 64 | to finish the reads related to the no longer existing offer. 65 | 66 | The primary selection owner should be checking for errors during 67 | writes, merely cancelling the ongoing transfer if any happened. 68 | 69 | 70 | 71 | 72 | The primary selection device manager is a singleton global object that 73 | provides access to the primary selection. It allows to create 74 | wp_primary_selection_source objects, as well as retrieving the per-seat 75 | wp_primary_selection_device objects. 76 | 77 | 78 | 79 | 80 | Create a new primary selection source. 81 | 82 | 83 | 84 | 85 | 86 | 87 | Create a new data device for a given seat. 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | Destroy the primary selection device manager. 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Replaces the current selection. The previous owner of the primary selection 104 | will receive a wp_primary_selection_source.cancelled event. 105 | 106 | To unset the selection, set the source to NULL. 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | Introduces a new wp_primary_selection_offer object that may be used 115 | to receive the current primary selection. Immediately following this 116 | event, the new wp_primary_selection_offer object will send 117 | wp_primary_selection_offer.offer events to describe the offered mime 118 | types. 119 | 120 | 121 | 122 | 123 | 124 | 125 | The wp_primary_selection_device.selection event is sent to notify the 126 | client of a new primary selection. This event is sent after the 127 | wp_primary_selection.data_offer event introducing this object, and after 128 | the offer has announced its mimetypes through 129 | wp_primary_selection_offer.offer. 130 | 131 | The data_offer is valid until a new offer or NULL is received 132 | or until the client loses keyboard focus. The client must destroy the 133 | previous selection data_offer, if any, upon receiving this event. 134 | 135 | 136 | 137 | 138 | 139 | 140 | Destroy the primary selection device. 141 | 142 | 143 | 144 | 145 | 146 | 147 | A wp_primary_selection_offer represents an offer to transfer the contents 148 | of the primary selection clipboard to the client. Similar to 149 | wl_data_offer, the offer also describes the mime types that the source 150 | will transferthat the 151 | data can be converted to and provides the mechanisms for transferring the 152 | data directly to the client. 153 | 154 | 155 | 156 | 157 | To transfer the contents of the primary selection clipboard, the client 158 | issues this request and indicates the mime type that it wants to 159 | receive. The transfer happens through the passed file descriptor 160 | (typically created with the pipe system call). The source client writes 161 | the data in the mime type representation requested and then closes the 162 | file descriptor. 163 | 164 | The receiving client reads from the read end of the pipe until EOF and 165 | closes its end, at which point the transfer is complete. 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | Destroy the primary selection offer. 174 | 175 | 176 | 177 | 178 | 179 | Sent immediately after creating announcing the wp_primary_selection_offer 180 | through wp_primary_selection_device.data_offer. One event is sent per 181 | offered mime type. 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | The source side of a wp_primary_selection_offer, it provides a way to 190 | describe the offered data and respond to requests to transfer the 191 | requested contents of the primary selection clipboard. 192 | 193 | 194 | 195 | 196 | This request adds a mime type to the set of mime types advertised to 197 | targets. Can be called several times to offer multiple types. 198 | 199 | 200 | 201 | 202 | 203 | 204 | Destroy the primary selection source. 205 | 206 | 207 | 208 | 209 | 210 | Request for the current primary selection contents from the client. 211 | Send the specified mime type over the passed file descriptor, then 212 | close it. 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | This primary selection source is no longer valid. The client should 221 | clean up and destroy this primary selection source. 222 | 223 | 224 | 225 | 226 | -------------------------------------------------------------------------------- /src/protocol/gtk-shell.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | gtk_shell is a protocol extension providing additional features for 6 | clients implementing it. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/protocol/meson.build: -------------------------------------------------------------------------------- 1 | protocols_opt = get_option('protocols') 2 | wayland_scanner = find_program('wayland-scanner', required: protocols_opt, native: true) 3 | wayland_protocols = dependency( 4 | 'wayland-protocols', 5 | version: '>= 1.12', 6 | required: protocols_opt, 7 | default_options: ['tests=false'] 8 | ) 9 | 10 | if wayland_scanner.found() 11 | have_xdg_shell = wayland_protocols.found() 12 | have_wp_primary_selection = wayland_protocols.found() and wayland_protocols.version().version_compare('>= 1.17') 13 | have_xdg_activation = wayland_protocols.found() and wayland_protocols.version().version_compare('>= 1.21') 14 | # these are bundled 15 | have_gtk_primary_selection = true 16 | have_wlr_data_control = true 17 | have_ext_data_control = wayland_protocols.found() and wayland_protocols.version().version_compare('>= 1.39') 18 | have_gtk_shell = true 19 | 20 | if wayland.version().version_compare('>= 1.15') 21 | scanner_code_command = 'private-code' 22 | else 23 | scanner_code_command = 'code' 24 | endif 25 | else 26 | have_xdg_shell = false 27 | have_wp_primary_selection = false 28 | have_xdg_activation = false 29 | have_gtk_primary_selection = false 30 | have_wlr_data_control = false 31 | have_ext_data_control = false 32 | have_gtk_shell = false 33 | endif 34 | 35 | conf_data.set('PROJECT_VERSION', '"@0@"'.format(meson.project_version())) 36 | conf_data.set('HAVE_XDG_SHELL', have_xdg_shell) 37 | conf_data.set('HAVE_WP_PRIMARY_SELECTION', have_wp_primary_selection) 38 | conf_data.set('HAVE_GTK_PRIMARY_SELECTION', have_gtk_primary_selection) 39 | conf_data.set('HAVE_WLR_DATA_CONTROL', have_wlr_data_control) 40 | conf_data.set('HAVE_EXT_DATA_CONTROL', have_ext_data_control) 41 | conf_data.set('HAVE_XDG_ACTIVATION', have_xdg_activation) 42 | conf_data.set('HAVE_GTK_SHELL', have_gtk_shell) 43 | 44 | if wayland_protocols.found() 45 | if meson.version().version_compare('>= 0.58') 46 | protocols_path = wayland_protocols.get_variable('pkgdatadir') 47 | else 48 | protocols_path = wayland_protocols.get_pkgconfig_variable('pkgdatadir') 49 | endif 50 | endif 51 | 52 | protocols = [] 53 | 54 | if not have_xdg_shell 55 | warning('Building without xdg-shell support, wl-clipboard will be unable to work on some compositors') 56 | else 57 | xdg_shell_xml = join_paths(protocols_path, 'stable', 'xdg-shell', 'xdg-shell.xml') 58 | protocols += [['xdg-shell', xdg_shell_xml]] 59 | endif 60 | 61 | if have_wp_primary_selection 62 | wp_primary_selection_xml = join_paths(protocols_path, 'unstable', 'primary-selection', 'primary-selection-unstable-v1.xml') 63 | protocols += [['wp-primary-selection', wp_primary_selection_xml]] 64 | endif 65 | 66 | if not have_gtk_primary_selection 67 | warning('Building without primary selection support') 68 | else 69 | gtk_primary_selection_xml = 'gtk-primary-selection.xml' 70 | protocols += [['gtk-primary-selection', gtk_primary_selection_xml]] 71 | endif 72 | 73 | if have_wlr_data_control 74 | wlr_data_control_xml = 'wlr-data-control-unstable-v1.xml' 75 | protocols += [['wlr-data-control', wlr_data_control_xml]] 76 | endif 77 | 78 | if have_ext_data_control 79 | ext_data_control_xml = join_paths(protocols_path, 'staging', 'ext-data-control', 'ext-data-control-v1.xml') 80 | protocols += [['ext-data-control', ext_data_control_xml]] 81 | endif 82 | 83 | if have_xdg_activation 84 | xdg_activation_xml = join_paths(protocols_path, 'staging', 'xdg-activation', 'xdg-activation-v1.xml') 85 | protocols += [['xdg-activation', xdg_activation_xml]] 86 | endif 87 | 88 | if have_gtk_shell 89 | gtk_shell_xml = 'gtk-shell.xml' 90 | protocols += [['gtk-shell', gtk_shell_xml]] 91 | endif 92 | 93 | 94 | protocol_deps = [] 95 | protocol_headers = [] 96 | 97 | foreach protocol : protocols 98 | name = protocol[0] 99 | xml = protocol[1] 100 | header = custom_target(name + ' client header', 101 | input: xml, output: name + '.h', 102 | command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'] 103 | ) 104 | impl = custom_target(name + ' code', 105 | input: xml, output: name + '.c', 106 | command: [wayland_scanner, scanner_code_command, '@INPUT@', '@OUTPUT@'] 107 | ) 108 | protocol_headers += header 109 | lib = static_library(name, impl, header, dependencies: wayland) 110 | protocol_deps += lib 111 | endforeach 112 | -------------------------------------------------------------------------------- /src/protocol/wlr-data-control-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2018 Simon Ser 5 | Copyright © 2019 Ivan Molodetskikh 6 | 7 | Permission to use, copy, modify, distribute, and sell this 8 | software and its documentation for any purpose is hereby granted 9 | without fee, provided that the above copyright notice appear in 10 | all copies and that both that copyright notice and this permission 11 | notice appear in supporting documentation, and that the name of 12 | the copyright holders not be used in advertising or publicity 13 | pertaining to distribution of the software without specific, 14 | written prior permission. The copyright holders make no 15 | representations about the suitability of this software for any 16 | purpose. It is provided "as is" without express or implied 17 | warranty. 18 | 19 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 20 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 21 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 24 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 25 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 26 | THIS SOFTWARE. 27 | 28 | 29 | 30 | This protocol allows a privileged client to control data devices. In 31 | particular, the client will be able to manage the current selection and take 32 | the role of a clipboard manager. 33 | 34 | Warning! The protocol described in this file is experimental and 35 | backward incompatible changes may be made. Backward compatible changes 36 | may be added together with the corresponding interface version bump. 37 | Backward incompatible changes are done by bumping the version number in 38 | the protocol and interface names and resetting the interface version. 39 | Once the protocol is to be declared stable, the 'z' prefix and the 40 | version number in the protocol and interface names are removed and the 41 | interface version number is reset. 42 | 43 | 44 | 45 | 46 | This interface is a manager that allows creating per-seat data device 47 | controls. 48 | 49 | 50 | 51 | 52 | Create a new data source. 53 | 54 | 56 | 57 | 58 | 59 | 60 | Create a data device that can be used to manage a seat's selection. 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | All objects created by the manager will still remain valid, until their 69 | appropriate destroy request has been called. 70 | 71 | 72 | 73 | 74 | 75 | 76 | This interface allows a client to manage a seat's selection. 77 | 78 | When the seat is destroyed, this object becomes inert. 79 | 80 | 81 | 82 | 83 | This request asks the compositor to set the selection to the data from 84 | the source on behalf of the client. 85 | 86 | The given source may not be used in any further set_selection or 87 | set_primary_selection requests. Attempting to use a previously used 88 | source is a protocol error. 89 | 90 | To unset the selection, set the source to NULL. 91 | 92 | 94 | 95 | 96 | 97 | 98 | Destroys the data device object. 99 | 100 | 101 | 102 | 103 | 104 | The data_offer event introduces a new wlr_data_control_offer object, 105 | which will subsequently be used in either the 106 | wlr_data_control_device.selection event (for the regular clipboard 107 | selections) or the wlr_data_control_device.primary_selection event (for 108 | the primary clipboard selections). Immediately following the 109 | wlr_data_control_device.data_offer event, the new data_offer object 110 | will send out wlr_data_control_offer.offer events to describe the MIME 111 | types it offers. 112 | 113 | 114 | 115 | 116 | 117 | 118 | The selection event is sent out to notify the client of a new 119 | wlr_data_control_offer for the selection for this device. The 120 | wlr_data_control_device.data_offer and the wlr_data_control_offer.offer 121 | events are sent out immediately before this event to introduce the data 122 | offer object. The selection event is sent to a client when a new 123 | selection is set. The wlr_data_control_offer is valid until a new 124 | wlr_data_control_offer or NULL is received. The client must destroy the 125 | previous selection wlr_data_control_offer, if any, upon receiving this 126 | event. 127 | 128 | The first selection event is sent upon binding the 129 | wlr_data_control_device object. 130 | 131 | 133 | 134 | 135 | 136 | 137 | This data control object is no longer valid and should be destroyed by 138 | the client. 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | The primary_selection event is sent out to notify the client of a new 147 | wlr_data_control_offer for the primary selection for this device. The 148 | wlr_data_control_device.data_offer and the wlr_data_control_offer.offer 149 | events are sent out immediately before this event to introduce the data 150 | offer object. The primary_selection event is sent to a client when a 151 | new primary selection is set. The wlr_data_control_offer is valid until 152 | a new wlr_data_control_offer or NULL is received. The client must 153 | destroy the previous primary selection wlr_data_control_offer, if any, 154 | upon receiving this event. 155 | 156 | If the compositor supports primary selection, the first 157 | primary_selection event is sent upon binding the 158 | wlr_data_control_device object. 159 | 160 | 162 | 163 | 164 | 165 | 166 | This request asks the compositor to set the primary selection to the 167 | data from the source on behalf of the client. 168 | 169 | The given source may not be used in any further set_selection or 170 | set_primary_selection requests. Attempting to use a previously used 171 | source is a protocol error. 172 | 173 | To unset the primary selection, set the source to NULL. 174 | 175 | The compositor will ignore this request if it does not support primary 176 | selection. 177 | 178 | 180 | 181 | 182 | 183 | 185 | 186 | 187 | 188 | 189 | 190 | The wlr_data_control_source object is the source side of a 191 | wlr_data_control_offer. It is created by the source client in a data 192 | transfer and provides a way to describe the offered data and a way to 193 | respond to requests to transfer the data. 194 | 195 | 196 | 197 | 199 | 200 | 201 | 202 | 203 | This request adds a MIME type to the set of MIME types advertised to 204 | targets. Can be called several times to offer multiple types. 205 | 206 | Calling this after wlr_data_control_device.set_selection is a protocol 207 | error. 208 | 209 | 211 | 212 | 213 | 214 | 215 | Destroys the data source object. 216 | 217 | 218 | 219 | 220 | 221 | Request for data from the client. Send the data as the specified MIME 222 | type over the passed file descriptor, then close it. 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | This data source is no longer valid. The data source has been replaced 231 | by another data source. 232 | 233 | The client should clean up and destroy this data source. 234 | 235 | 236 | 237 | 238 | 239 | 240 | A wlr_data_control_offer represents a piece of data offered for transfer 241 | by another client (the source client). The offer describes the different 242 | MIME types that the data can be converted to and provides the mechanism 243 | for transferring the data directly from the source client. 244 | 245 | 246 | 247 | 248 | To transfer the offered data, the client issues this request and 249 | indicates the MIME type it wants to receive. The transfer happens 250 | through the passed file descriptor (typically created with the pipe 251 | system call). The source client writes the data in the MIME type 252 | representation requested and then closes the file descriptor. 253 | 254 | The receiving client reads from the read end of the pipe until EOF and 255 | then closes its end, at which point the transfer is complete. 256 | 257 | This request may happen multiple times for different MIME types. 258 | 259 | 261 | 262 | 263 | 264 | 265 | 266 | Destroys the data offer object. 267 | 268 | 269 | 270 | 271 | 272 | Sent immediately after creating the wlr_data_control_offer object. 273 | One event per offered MIME type. 274 | 275 | 276 | 277 | 278 | 279 | -------------------------------------------------------------------------------- /src/types/copy-action.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/copy-action.h" 20 | #include "types/device.h" 21 | #include "types/source.h" 22 | #include "types/popup-surface.h" 23 | 24 | #include "util/misc.h" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | static void do_set_selection(struct copy_action *self, uint32_t serial) { 36 | /* Set the selection and make sure it reaches 37 | * the display before we do anything else, 38 | * such as destroying the surface or exiting. 39 | * Make sure to unset the callback to prevent 40 | * reentrancy issues. 41 | */ 42 | if (self->popup_surface != NULL) { 43 | self->popup_surface->on_focus = NULL; 44 | self->popup_surface->data = NULL; 45 | } 46 | device_set_selection(self->device, self->source, serial, self->primary); 47 | wl_display_roundtrip(self->device->wl_display); 48 | 49 | /* Now, if we have used a popup surface, destroy it */ 50 | if (self->popup_surface != NULL) { 51 | popup_surface_destroy(self->popup_surface); 52 | self->popup_surface = NULL; 53 | } 54 | 55 | /* And invoke the callback */ 56 | if (self->did_set_selection_callback != NULL) { 57 | self->did_set_selection_callback(self); 58 | } 59 | } 60 | 61 | static void on_focus( 62 | struct popup_surface *popup_surface, 63 | uint32_t serial 64 | ) { 65 | struct copy_action *self = (struct copy_action *) popup_surface->data; 66 | do_set_selection(self, serial); 67 | } 68 | 69 | static void do_send(struct source *source, const char *mime_type, int fd) { 70 | struct copy_action *self = source->data; 71 | 72 | /* Unset O_NONBLOCK */ 73 | fcntl(fd, F_SETFL, 0); 74 | 75 | if (!strcmp(mime_type, x_kde_password_manager_hint)) { 76 | /* We always respond to x-kde-passwordManagerHint, 77 | * even though we only offer it when --sensitive 78 | * is set. 79 | */ 80 | const char *data = self->sensitive ? "secret" : "public"; 81 | write(fd, data, strlen(data)); 82 | close(fd); 83 | /* This does not count as pasting, so don't invoke 84 | * our pasted_callback, just return. 85 | */ 86 | return; 87 | } 88 | 89 | if (self->fd_to_copy_from != -1) { 90 | /* Copy the file to the given file descriptor 91 | * by spawning an appropriate cat process. 92 | */ 93 | pid_t pid = fork(); 94 | if (pid < 0) { 95 | perror("fork"); 96 | close(fd); 97 | return; 98 | } 99 | if (pid == 0) { 100 | dup2(self->fd_to_copy_from, STDIN_FILENO); 101 | close(self->fd_to_copy_from); 102 | dup2(fd, STDOUT_FILENO); 103 | close(fd); 104 | signal(SIGHUP, SIG_DFL); 105 | signal(SIGPIPE, SIG_DFL); 106 | execlp("cat", "cat", NULL); 107 | perror("exec cat"); 108 | exit(1); 109 | } 110 | close(fd); 111 | /* Wait for the cat process to exit. This effectively 112 | * means waiting for the other side to read the whole 113 | * file. In theory, a malicious client could perform a 114 | * denial-of-serivice attack against us. Perhaps we 115 | * should switch to an asynchronous child waiting scheme 116 | * instead. 117 | */ 118 | waitpid(pid, NULL, 0); 119 | /* Seek back to the beginning of the file */ 120 | off_t rc = lseek(self->fd_to_copy_from, 0, SEEK_SET); 121 | if (rc < 0) { 122 | perror("lseek"); 123 | } 124 | } else { 125 | /* We'll perform the copy ourselves */ 126 | FILE *f = fdopen(fd, "w"); 127 | if (f == NULL) { 128 | perror("fdopen"); 129 | close(fd); 130 | return; 131 | } 132 | 133 | if (self->data_to_copy.ptr != NULL) { 134 | /* Just copy the given chunk of data */ 135 | fwrite(self->data_to_copy.ptr, 1, self->data_to_copy.len, f); 136 | } else if (self->argv_to_copy != NULL) { 137 | /* Copy an argv-style string array, 138 | * inserting spaces between items. 139 | */ 140 | int is_first = 1; 141 | for (argv_t word = self->argv_to_copy; *word != NULL; word++) { 142 | if (!is_first) { 143 | fwrite(" ", 1, 1, f); 144 | } 145 | is_first = 0; 146 | fwrite(*word, 1, strlen(*word), f); 147 | } 148 | } else { 149 | bail("Unreachable: nothing to copy"); 150 | } 151 | 152 | fclose(f); 153 | } 154 | 155 | 156 | if (self->pasted_callback != NULL) { 157 | self->pasted_callback(self); 158 | } 159 | } 160 | 161 | static void forward_cancel(struct source *source) { 162 | struct copy_action *self = source->data; 163 | if (self->cancelled_callback != NULL) { 164 | self->cancelled_callback(self); 165 | } 166 | } 167 | 168 | void copy_action_init(struct copy_action *self) { 169 | if (self->source != NULL) { 170 | self->source->send_callback = do_send; 171 | self->source->cancelled_callback = forward_cancel; 172 | self->source->data = self; 173 | } 174 | /* See if we can just set the selection directly */ 175 | if (!self->device->needs_popup_surface) { 176 | /* If we can, it doesn't actually require 177 | * a serial, so passing zero will do. 178 | */ 179 | do_set_selection(self, 0); 180 | } else { 181 | /* If we cannot, schedule to do it later, 182 | * when our popup surface gains keyboard focus. 183 | */ 184 | self->popup_surface->on_focus = on_focus; 185 | self->popup_surface->data = self; 186 | popup_surface_init(self->popup_surface); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/types/copy-action.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_COPY_ACTION_H 20 | #define TYPES_COPY_ACTION_H 21 | 22 | #include "util/string.h" 23 | 24 | #include 25 | 26 | struct device; 27 | struct source; 28 | struct popup_surface; 29 | 30 | struct copy_action { 31 | /* These fields are initialized by the creator */ 32 | struct device *device; 33 | struct source *source; 34 | struct popup_surface *popup_surface; 35 | int primary; 36 | int sensitive; 37 | 38 | void (*did_set_selection_callback)(struct copy_action *self); 39 | void (*pasted_callback)(struct copy_action *self); 40 | void (*cancelled_callback)(struct copy_action *self); 41 | 42 | /* Exactly one of these fields must be non-null if the source 43 | * is non-null, otherwise all these fields must be null. 44 | * The null value for fd_to_copy_from is -1. 45 | */ 46 | int fd_to_copy_from; 47 | argv_t argv_to_copy; 48 | struct { 49 | const char *ptr; 50 | size_t len; 51 | } data_to_copy; 52 | }; 53 | 54 | void copy_action_init(struct copy_action *self); 55 | 56 | #endif /* TYPES_COPY_ACTION_H */ 57 | -------------------------------------------------------------------------------- /src/types/device-manager.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/device-manager.h" 20 | #include "types/device.h" 21 | #include "types/source.h" 22 | #include "types/seat.h" 23 | #include "includes/selection-protocols.h" 24 | 25 | #include 26 | 27 | 28 | struct source *device_manager_create_source(struct device_manager *self) { 29 | return self->do_create_source(self); 30 | } 31 | 32 | struct device *device_manager_get_device( 33 | struct device_manager *self, 34 | struct seat *seat 35 | ) { 36 | return self->do_get_device(self, seat); 37 | } 38 | 39 | /* Macros to reduce implementation boilerplate */ 40 | 41 | #define CREATE_SOURCE(type, source_type, method_name) \ 42 | static struct source *device_manager_ ## type ## _do_create_source( \ 43 | struct device_manager *self \ 44 | ) { \ 45 | struct type *proxy = (struct type *) self->proxy; \ 46 | struct source *source = calloc(1, sizeof(struct source)); \ 47 | source->proxy = (struct wl_proxy *) type ## _ ## method_name(proxy); \ 48 | source_init_ ## source_type(source); \ 49 | return source; \ 50 | } 51 | 52 | #define GET_DEVICE(type, device_type, method_name) \ 53 | static struct device *device_manager_ ## type ## _do_get_device( \ 54 | struct device_manager *self, \ 55 | struct seat *seat_wrapper \ 56 | ) { \ 57 | struct type *proxy = (struct type *) self->proxy; \ 58 | struct wl_seat *seat = (struct wl_seat *) seat_wrapper->proxy; \ 59 | struct device *device = calloc(1, sizeof(struct device)); \ 60 | device->proxy = (struct wl_proxy *) type ## _ ## method_name(proxy, seat); \ 61 | device->wl_display = self->wl_display; \ 62 | device_init_ ## device_type(device); \ 63 | return device; \ 64 | } 65 | 66 | #define INIT(type) \ 67 | void device_manager_init_ ## type(struct device_manager *self) { \ 68 | self->do_create_source = device_manager_ ## type ## _do_create_source; \ 69 | self->do_get_device = device_manager_ ## type ## _do_get_device; \ 70 | } 71 | 72 | 73 | /* Core Wayland implementation */ 74 | 75 | CREATE_SOURCE(wl_data_device_manager, wl_data_source, create_data_source) 76 | GET_DEVICE(wl_data_device_manager, wl_data_device, get_data_device) 77 | INIT(wl_data_device_manager) 78 | 79 | 80 | /* gtk-primary-selection implementation */ 81 | 82 | #ifdef HAVE_GTK_PRIMARY_SELECTION 83 | 84 | CREATE_SOURCE( 85 | gtk_primary_selection_device_manager, 86 | gtk_primary_selection_source, 87 | create_source 88 | ) 89 | 90 | GET_DEVICE( 91 | gtk_primary_selection_device_manager, 92 | gtk_primary_selection_device, 93 | get_device 94 | ) 95 | 96 | INIT(gtk_primary_selection_device_manager) 97 | 98 | #endif /* HAVE_GTK_PRIMARY_SELECTION */ 99 | 100 | 101 | /* wp-primary-selection implementation */ 102 | 103 | #ifdef HAVE_WP_PRIMARY_SELECTION 104 | 105 | CREATE_SOURCE( 106 | zwp_primary_selection_device_manager_v1, 107 | zwp_primary_selection_source_v1, 108 | create_source 109 | ) 110 | 111 | GET_DEVICE( 112 | zwp_primary_selection_device_manager_v1, 113 | zwp_primary_selection_device_v1, 114 | get_device 115 | ) 116 | 117 | INIT(zwp_primary_selection_device_manager_v1) 118 | 119 | #endif /* HAVE_WP_PRIMARY_SELECTION */ 120 | 121 | 122 | /* wlr-data-control implementation */ 123 | 124 | #ifdef HAVE_WLR_DATA_CONTROL 125 | 126 | CREATE_SOURCE( 127 | zwlr_data_control_manager_v1, 128 | zwlr_data_control_source_v1, 129 | create_data_source 130 | ) 131 | 132 | GET_DEVICE( 133 | zwlr_data_control_manager_v1, 134 | zwlr_data_control_device_v1, 135 | get_data_device 136 | ) 137 | 138 | INIT(zwlr_data_control_manager_v1) 139 | 140 | #endif /* HAVE_WLR_DATA_CONTROL */ 141 | 142 | /* ext-data-control implementation */ 143 | 144 | #ifdef HAVE_EXT_DATA_CONTROL 145 | 146 | CREATE_SOURCE( 147 | ext_data_control_manager_v1, 148 | ext_data_control_source_v1, 149 | create_data_source 150 | ) 151 | 152 | GET_DEVICE( 153 | ext_data_control_manager_v1, 154 | ext_data_control_device_v1, 155 | get_data_device 156 | ) 157 | 158 | INIT(ext_data_control_manager_v1) 159 | 160 | #endif /* HAVE_EXT_DATA_CONTROL */ 161 | -------------------------------------------------------------------------------- /src/types/device-manager.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_DEVICE_MANAGER_H 20 | #define TYPES_DEVICE_MANAGER_H 21 | 22 | #include "includes/selection-protocols.h" 23 | 24 | #include 25 | 26 | struct seat; 27 | struct device; 28 | struct source; 29 | 30 | struct device_manager { 31 | /* These fields are initialized by the creator */ 32 | struct wl_proxy *proxy; 33 | struct wl_display *wl_display; 34 | 35 | /* These fields are initialized by the implementation */ 36 | struct source *(*do_create_source)(struct device_manager *self); 37 | struct device *(*do_get_device)( 38 | struct device_manager *self, 39 | struct seat *seat 40 | ); 41 | }; 42 | 43 | struct source *device_manager_create_source(struct device_manager *self); 44 | 45 | struct device *device_manager_get_device( 46 | struct device_manager *self, 47 | struct seat *seat 48 | ); 49 | 50 | /* Initializers */ 51 | 52 | void device_manager_init_wl_data_device_manager(struct device_manager *self); 53 | 54 | #ifdef HAVE_GTK_PRIMARY_SELECTION 55 | void device_manager_init_gtk_primary_selection_device_manager( 56 | struct device_manager *self 57 | ); 58 | #endif 59 | 60 | #ifdef HAVE_WP_PRIMARY_SELECTION 61 | void device_manager_init_zwp_primary_selection_device_manager_v1( 62 | struct device_manager *self 63 | ); 64 | #endif 65 | 66 | #ifdef HAVE_WLR_DATA_CONTROL 67 | void device_manager_init_zwlr_data_control_manager_v1( 68 | struct device_manager *self 69 | ); 70 | #endif 71 | 72 | #ifdef HAVE_EXT_DATA_CONTROL 73 | void device_manager_init_ext_data_control_manager_v1( 74 | struct device_manager *self 75 | ); 76 | #endif 77 | 78 | #endif /* TYPES_DEVICE_MANAGER_H */ 79 | -------------------------------------------------------------------------------- /src/types/device.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "config.h" 20 | #include "types/device.h" 21 | #include "types/offer.h" 22 | #include "types/source.h" 23 | #include "includes/selection-protocols.h" 24 | 25 | #include 26 | #include 27 | 28 | enum TriState { 29 | Unknown, 30 | Yes, 31 | No 32 | }; 33 | 34 | int device_supports_selection(struct device *self, int primary) { 35 | return self->supports_selection(self, primary); 36 | } 37 | 38 | void device_set_selection( 39 | struct device *self, 40 | struct source *source, 41 | uint32_t serial, 42 | int primary 43 | ) { 44 | self->do_set_selection(self, source, serial, primary); 45 | } 46 | 47 | /* Macros to reduce implementation boilerplate */ 48 | 49 | #define SUPPORTS_SELECTION(type, expr) \ 50 | static int device_supports_selection_on_ ## type( \ 51 | struct device *self, \ 52 | int primary \ 53 | ) { \ 54 | return expr; \ 55 | } 56 | 57 | #define SET_SELECTION_IMPL(type, source_type, impl) \ 58 | static void device_set_selection_on_ ## type( \ 59 | struct device *self, \ 60 | struct source *source_wrapper, \ 61 | uint32_t serial, \ 62 | int primary \ 63 | ) { \ 64 | struct type *device = (struct type *) self->proxy; \ 65 | struct source_type *source = NULL; \ 66 | if (source_wrapper != NULL) { \ 67 | source = (struct source_type *) source_wrapper->proxy; \ 68 | } \ 69 | impl \ 70 | } 71 | 72 | #define DATA_OFFER_HANDLER(type, offer_type) \ 73 | static void type ## _data_offer_handler( \ 74 | void *data, \ 75 | struct type *device, \ 76 | struct offer_type *offer_proxy \ 77 | ) { \ 78 | struct device *self = data; \ 79 | struct offer *offer = calloc(1, sizeof(struct offer)); \ 80 | offer->proxy = (struct wl_proxy *) offer_proxy; \ 81 | offer_init_ ## offer_type(offer); \ 82 | if (self->new_offer_callback) { \ 83 | self->new_offer_callback(offer); \ 84 | } \ 85 | } 86 | 87 | #define SELECTION_HANDLER(type, offer_type, selection_event, primary) \ 88 | static void type ## _ ## selection_event ## _handler( \ 89 | void *data, \ 90 | struct type *device, \ 91 | struct offer_type *offer_proxy \ 92 | ) { \ 93 | struct device *self = data; \ 94 | struct offer *offer = NULL; \ 95 | if (offer_proxy != NULL) { \ 96 | offer = wl_proxy_get_user_data((struct wl_proxy *) offer_proxy); \ 97 | } \ 98 | if (self->selection_callback) { \ 99 | self->selection_callback(offer, primary); \ 100 | } \ 101 | } 102 | 103 | #define INIT(type, needs_surface) \ 104 | void device_init_ ## type(struct device *self) { \ 105 | struct type *device = (struct type *) self->proxy; \ 106 | type ## _add_listener(device, &type ## _listener, self); \ 107 | self->supports_selection = device_supports_selection_on_ ## type; \ 108 | self->needs_popup_surface = needs_surface; \ 109 | self->do_set_selection = device_set_selection_on_ ## type; \ 110 | } 111 | 112 | 113 | /* Core Wayland implementation */ 114 | 115 | SUPPORTS_SELECTION(wl_data_device, !primary) 116 | 117 | SET_SELECTION_IMPL(wl_data_device, wl_data_source, { 118 | wl_data_device_set_selection(device, source, serial); 119 | }) 120 | 121 | DATA_OFFER_HANDLER(wl_data_device, wl_data_offer) 122 | 123 | SELECTION_HANDLER(wl_data_device, wl_data_offer, selection, 0) 124 | 125 | static const struct wl_data_device_listener wl_data_device_listener = { 126 | .data_offer = wl_data_device_data_offer_handler, 127 | .selection = wl_data_device_selection_handler 128 | }; 129 | 130 | INIT(wl_data_device, 1) 131 | 132 | 133 | /* gtk-primary-selection implementation */ 134 | 135 | #ifdef HAVE_GTK_PRIMARY_SELECTION 136 | 137 | SUPPORTS_SELECTION(gtk_primary_selection_device, primary) 138 | 139 | SET_SELECTION_IMPL(gtk_primary_selection_device, gtk_primary_selection_source, { 140 | gtk_primary_selection_device_set_selection(device, source, serial); 141 | }) 142 | 143 | DATA_OFFER_HANDLER(gtk_primary_selection_device, gtk_primary_selection_offer) 144 | 145 | SELECTION_HANDLER( 146 | gtk_primary_selection_device, 147 | gtk_primary_selection_offer, 148 | selection, 149 | 1 150 | ) 151 | 152 | static const struct gtk_primary_selection_device_listener 153 | gtk_primary_selection_device_listener = { 154 | .data_offer = gtk_primary_selection_device_data_offer_handler, 155 | .selection = gtk_primary_selection_device_selection_handler 156 | }; 157 | 158 | INIT(gtk_primary_selection_device, 1) 159 | 160 | #endif /* HAVE_GTK_PRIMARY_SELECTION */ 161 | 162 | 163 | /* wp-primary-selection implementation */ 164 | 165 | #ifdef HAVE_WP_PRIMARY_SELECTION 166 | 167 | SUPPORTS_SELECTION(zwp_primary_selection_device_v1, primary) 168 | 169 | SET_SELECTION_IMPL( 170 | zwp_primary_selection_device_v1, 171 | zwp_primary_selection_source_v1, 172 | { 173 | zwp_primary_selection_device_v1_set_selection(device, source, serial); 174 | } 175 | ) 176 | 177 | DATA_OFFER_HANDLER( 178 | zwp_primary_selection_device_v1, 179 | zwp_primary_selection_offer_v1 180 | ) 181 | 182 | SELECTION_HANDLER( 183 | zwp_primary_selection_device_v1, 184 | zwp_primary_selection_offer_v1, 185 | selection, 186 | 1 187 | ) 188 | 189 | static const struct zwp_primary_selection_device_v1_listener 190 | zwp_primary_selection_device_v1_listener = { 191 | .data_offer = zwp_primary_selection_device_v1_data_offer_handler, 192 | .selection = zwp_primary_selection_device_v1_selection_handler 193 | }; 194 | 195 | INIT(zwp_primary_selection_device_v1, 1) 196 | 197 | #endif /* HAVE_WP_PRIMARY_SELECTION */ 198 | 199 | 200 | /* wlr-data-control implementation */ 201 | 202 | #ifdef HAVE_WLR_DATA_CONTROL 203 | 204 | /* Whether wlr-data-control supports primary selection */ 205 | static enum TriState device_wlr_supports_primary_selection = Unknown; 206 | static int device_get_wlr_supports_selection(struct device *self, int primary) { 207 | if (!primary) { 208 | return 1; 209 | } 210 | 211 | if (device_wlr_supports_primary_selection == Yes) { 212 | return 1; 213 | } else if (device_wlr_supports_primary_selection == No) { 214 | return 0; 215 | } 216 | 217 | wl_display_roundtrip(self->wl_display); 218 | 219 | if (device_wlr_supports_primary_selection == Yes) { 220 | return 1; 221 | } else { 222 | device_wlr_supports_primary_selection = No; 223 | return 0; 224 | } 225 | } 226 | 227 | SUPPORTS_SELECTION( 228 | zwlr_data_control_device_v1, 229 | device_get_wlr_supports_selection(self, primary) 230 | ) 231 | 232 | SET_SELECTION_IMPL(zwlr_data_control_device_v1, zwlr_data_control_source_v1, { 233 | if (!primary) { 234 | zwlr_data_control_device_v1_set_selection(device, source); 235 | } else { 236 | zwlr_data_control_device_v1_set_primary_selection(device, source); 237 | } 238 | }) 239 | 240 | DATA_OFFER_HANDLER(zwlr_data_control_device_v1, zwlr_data_control_offer_v1) 241 | 242 | SELECTION_HANDLER( 243 | zwlr_data_control_device_v1, 244 | zwlr_data_control_offer_v1, 245 | selection, 246 | 0 247 | ) 248 | 249 | static void zwlr_data_control_device_v1_primary_selection_handler( 250 | void *data, 251 | struct zwlr_data_control_device_v1 *device, 252 | struct zwlr_data_control_offer_v1 *offer_proxy 253 | ) { 254 | device_wlr_supports_primary_selection = Yes; 255 | struct device *self = data; 256 | struct offer *offer = NULL; 257 | if (offer_proxy != NULL) { 258 | offer = wl_proxy_get_user_data((struct wl_proxy *) offer_proxy); 259 | } 260 | if (self->selection_callback != NULL) { 261 | self->selection_callback(offer, 1); 262 | } 263 | } 264 | 265 | static const struct zwlr_data_control_device_v1_listener 266 | zwlr_data_control_device_v1_listener = { 267 | .data_offer = zwlr_data_control_device_v1_data_offer_handler, 268 | .selection = zwlr_data_control_device_v1_selection_handler, 269 | .primary_selection = zwlr_data_control_device_v1_primary_selection_handler 270 | }; 271 | 272 | INIT(zwlr_data_control_device_v1, 0) 273 | 274 | #endif /* HAVE_WLR_DATA_CONTROL */ 275 | 276 | 277 | /* ext-data-control implementation */ 278 | 279 | #ifdef HAVE_EXT_DATA_CONTROL 280 | 281 | /* Whether ext-data-control supports primary selection */ 282 | static enum TriState device_ext_supports_primary_selection = Unknown; 283 | static int device_get_ext_supports_selection(struct device *self, int primary) { 284 | if (!primary) { 285 | return 1; 286 | } 287 | 288 | if (device_ext_supports_primary_selection == Yes) { 289 | return 1; 290 | } else if (device_ext_supports_primary_selection == No) { 291 | return 0; 292 | } 293 | 294 | wl_display_roundtrip(self->wl_display); 295 | 296 | if (device_ext_supports_primary_selection == Yes) { 297 | return 1; 298 | } else { 299 | device_ext_supports_primary_selection = No; 300 | return 0; 301 | } 302 | } 303 | 304 | SUPPORTS_SELECTION( 305 | ext_data_control_device_v1, 306 | device_get_ext_supports_selection(self, primary) 307 | ) 308 | 309 | SET_SELECTION_IMPL(ext_data_control_device_v1, ext_data_control_source_v1, { 310 | if (!primary) { 311 | ext_data_control_device_v1_set_selection(device, source); 312 | } else { 313 | ext_data_control_device_v1_set_primary_selection(device, source); 314 | } 315 | }) 316 | 317 | DATA_OFFER_HANDLER(ext_data_control_device_v1, ext_data_control_offer_v1) 318 | 319 | SELECTION_HANDLER( 320 | ext_data_control_device_v1, 321 | ext_data_control_offer_v1, 322 | selection, 323 | 0 324 | ) 325 | 326 | static void ext_data_control_device_v1_primary_selection_handler( 327 | void *data, 328 | struct ext_data_control_device_v1 *device, 329 | struct ext_data_control_offer_v1 *offer_proxy 330 | ) { 331 | device_ext_supports_primary_selection = Yes; 332 | struct device *self = data; 333 | struct offer *offer = NULL; 334 | if (offer_proxy != NULL) { 335 | offer = wl_proxy_get_user_data((struct wl_proxy *) offer_proxy); 336 | } 337 | if (self->selection_callback != NULL) { 338 | self->selection_callback(offer, 1); 339 | } 340 | } 341 | 342 | static const struct ext_data_control_device_v1_listener 343 | ext_data_control_device_v1_listener = { 344 | .data_offer = ext_data_control_device_v1_data_offer_handler, 345 | .selection = ext_data_control_device_v1_selection_handler, 346 | .primary_selection = ext_data_control_device_v1_primary_selection_handler 347 | }; 348 | 349 | INIT(ext_data_control_device_v1, 0) 350 | 351 | #endif /* HAVE_EXT_DATA_CONTROL */ 352 | -------------------------------------------------------------------------------- /src/types/device.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_DEVICE_H 20 | #define TYPES_DEVICE_H 21 | 22 | #include "includes/selection-protocols.h" 23 | 24 | #include 25 | 26 | struct offer; 27 | struct source; 28 | 29 | struct device { 30 | /* These fields are initialized by the creator */ 31 | void (*new_offer_callback)(struct offer *offer); 32 | void (*selection_callback)(struct offer *offer, int primary); 33 | void *data; 34 | 35 | struct wl_proxy *proxy; 36 | struct wl_display *wl_display; 37 | 38 | /* These fields are initialized by the implementation */ 39 | int (*supports_selection)(struct device *self, int primary); 40 | int needs_popup_surface; 41 | void (*do_set_selection)( 42 | struct device *self, 43 | struct source *source, 44 | uint32_t serial, 45 | int primary 46 | ); 47 | }; 48 | 49 | int device_supports_selection(struct device *self, int primary); 50 | 51 | void device_set_selection( 52 | struct device *self, 53 | struct source *source, 54 | uint32_t serial, 55 | int primary 56 | ); 57 | 58 | /* Initializers */ 59 | 60 | void device_init_wl_data_device(struct device *self); 61 | 62 | #ifdef HAVE_GTK_PRIMARY_SELECTION 63 | void device_init_gtk_primary_selection_device(struct device *self); 64 | #endif 65 | 66 | #ifdef HAVE_WP_PRIMARY_SELECTION 67 | void device_init_zwp_primary_selection_device_v1(struct device *self); 68 | #endif 69 | 70 | #ifdef HAVE_WLR_DATA_CONTROL 71 | void device_init_zwlr_data_control_device_v1(struct device *self); 72 | #endif 73 | 74 | #ifdef HAVE_EXT_DATA_CONTROL 75 | void device_init_ext_data_control_device_v1(struct device *self); 76 | #endif 77 | 78 | #endif /* TYPES_DEVICE_H */ 79 | -------------------------------------------------------------------------------- /src/types/keyboard.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/keyboard.h" 20 | 21 | #include 22 | 23 | static void wl_keyboard_keymap_handler( 24 | void *data, 25 | struct wl_keyboard *keyboard, 26 | uint32_t format, 27 | int fd, 28 | uint32_t size 29 | ) { 30 | close(fd); 31 | } 32 | 33 | static void wl_keyboard_enter_handler( 34 | void *data, 35 | struct wl_keyboard *keyboard, 36 | uint32_t serial, 37 | struct wl_surface *surface, 38 | struct wl_array *keys 39 | ) { 40 | struct keyboard *self = (struct keyboard *) data; 41 | if (self->on_focus != NULL) { 42 | self->on_focus(self, serial); 43 | } 44 | } 45 | 46 | static void wl_keyboard_leave_handler( 47 | void *data, 48 | struct wl_keyboard *keyboard, 49 | uint32_t serial, 50 | struct wl_surface *surface 51 | ) {} 52 | 53 | static void wl_keyboard_key_handler( 54 | void *data, 55 | struct wl_keyboard *keyboard, 56 | uint32_t serial, 57 | uint32_t time, 58 | uint32_t key, 59 | uint32_t state 60 | ) {} 61 | 62 | static void wl_keyboard_modifiers_handler( 63 | void *data, 64 | struct wl_keyboard *keyboard, 65 | uint32_t serial, 66 | uint32_t mods_depressed, 67 | uint32_t mods_latched, 68 | uint32_t mods_locked, 69 | uint32_t group 70 | ) {} 71 | 72 | static const struct wl_keyboard_listener wl_keyboard_listener = { 73 | .keymap = wl_keyboard_keymap_handler, 74 | .enter = wl_keyboard_enter_handler, 75 | .leave = wl_keyboard_leave_handler, 76 | .key = wl_keyboard_key_handler, 77 | .modifiers = wl_keyboard_modifiers_handler, 78 | }; 79 | 80 | void keyboard_init(struct keyboard *self) { 81 | wl_keyboard_add_listener(self->proxy, &wl_keyboard_listener, self); 82 | } 83 | -------------------------------------------------------------------------------- /src/types/keyboard.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_KEYBOARD_H 20 | #define TYPES_KEYBOARD_H 21 | 22 | #include 23 | #include 24 | 25 | struct keyboard { 26 | /* These fields are initialized by the creator */ 27 | struct wl_keyboard *proxy; 28 | void (*on_focus)(struct keyboard *self, uint32_t serial); 29 | void *data; 30 | }; 31 | 32 | void keyboard_init(struct keyboard *self); 33 | 34 | #endif /* TYPES_KEYBOARD_H */ 35 | -------------------------------------------------------------------------------- /src/types/offer.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/offer.h" 20 | #include "includes/selection-protocols.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | void offer_receive(struct offer *self, const char *mime_type, int fd) { 27 | self->do_receive(self->proxy, mime_type, fd); 28 | } 29 | 30 | void offer_destroy(struct offer *self) { 31 | self->do_destroy(self->proxy); 32 | wl_array_release(&self->offered_mime_types); 33 | free(self); 34 | } 35 | 36 | 37 | /* Macros to reduce implementation boilerplate */ 38 | 39 | #define OFFER_HANDLER(type) \ 40 | static void type ## _offer_handler( \ 41 | void *data, \ 42 | struct type *proxy, \ 43 | const char *mime_type \ 44 | ) { \ 45 | struct offer *self = data; \ 46 | char *ptr = wl_array_add( \ 47 | &self->offered_mime_types, \ 48 | strlen(mime_type) + 1 \ 49 | ); \ 50 | strcpy(ptr, mime_type); \ 51 | } 52 | 53 | #define LISTENER(type) \ 54 | static const struct type ## _listener type ## _listener = { \ 55 | .offer = type ## _offer_handler \ 56 | }; 57 | 58 | #define INIT(type) \ 59 | void offer_init_ ## type(struct offer *self) { \ 60 | self->do_receive = \ 61 | (void (*)(struct wl_proxy *, const char *, int)) type ## _receive; \ 62 | self->do_destroy = (void (*)(struct wl_proxy *)) type ## _destroy; \ 63 | wl_array_init(&self->offered_mime_types); \ 64 | struct type *proxy = (struct type *) self->proxy; \ 65 | type ## _add_listener(proxy, &type ## _listener, self); \ 66 | } 67 | 68 | 69 | /* Core Wayland implementation */ 70 | 71 | OFFER_HANDLER(wl_data_offer) 72 | LISTENER(wl_data_offer) 73 | INIT(wl_data_offer) 74 | 75 | 76 | /* gtk-primary-selection implementation */ 77 | 78 | #ifdef HAVE_GTK_PRIMARY_SELECTION 79 | 80 | OFFER_HANDLER(gtk_primary_selection_offer) 81 | LISTENER(gtk_primary_selection_offer) 82 | INIT(gtk_primary_selection_offer) 83 | 84 | #endif /* HAVE_GTK_PRIMARY_SELECTION */ 85 | 86 | 87 | /* wp-primary-selection implementation */ 88 | 89 | #ifdef HAVE_WP_PRIMARY_SELECTION 90 | 91 | OFFER_HANDLER(zwp_primary_selection_offer_v1) 92 | LISTENER(zwp_primary_selection_offer_v1) 93 | INIT(zwp_primary_selection_offer_v1) 94 | 95 | #endif /* HAVE_WP_PRIMARY_SELECTION */ 96 | 97 | 98 | /* wlr-data-control implementation */ 99 | 100 | #ifdef HAVE_WLR_DATA_CONTROL 101 | 102 | OFFER_HANDLER(zwlr_data_control_offer_v1) 103 | LISTENER(zwlr_data_control_offer_v1) 104 | INIT(zwlr_data_control_offer_v1) 105 | 106 | #endif /* HAVE_WLR_DATA_CONTROL */ 107 | 108 | 109 | /* ext-data-control implementation */ 110 | 111 | #ifdef HAVE_EXT_DATA_CONTROL 112 | 113 | OFFER_HANDLER(ext_data_control_offer_v1) 114 | LISTENER(ext_data_control_offer_v1) 115 | INIT(ext_data_control_offer_v1) 116 | 117 | #endif /* HAVE_EXT_DATA_CONTROL */ 118 | -------------------------------------------------------------------------------- /src/types/offer.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_OFFER_H 20 | #define TYPES_OFFER_H 21 | 22 | #include "includes/selection-protocols.h" 23 | 24 | #include 25 | #include 26 | 27 | struct offer { 28 | /* This field is initialized by the creator */ 29 | struct wl_proxy *proxy; 30 | 31 | /* These fields are initialized by the implementation */ 32 | void (*do_receive)(struct wl_proxy *proxy, const char *mime_type, int fd); 33 | void (*do_destroy)(struct wl_proxy *proxy); 34 | struct wl_array offered_mime_types; 35 | }; 36 | 37 | #define offer_for_each_mime_type(offer, mime_type) \ 38 | for ( \ 39 | const char *mime_type = offer->offered_mime_types.data; \ 40 | mime_type != (const char *) offer->offered_mime_types.data + \ 41 | offer->offered_mime_types.size; \ 42 | mime_type += strlen(mime_type) + 1 \ 43 | ) 44 | 45 | void offer_receive(struct offer *self, const char *mime_type, int fd); 46 | void offer_destroy(struct offer *self); 47 | 48 | /* Initializers */ 49 | 50 | void offer_init_wl_data_offer(struct offer *self); 51 | 52 | #ifdef HAVE_GTK_PRIMARY_SELECTION 53 | void offer_init_gtk_primary_selection_offer(struct offer *self); 54 | #endif 55 | 56 | #ifdef HAVE_WP_PRIMARY_SELECTION 57 | void offer_init_zwp_primary_selection_offer_v1(struct offer *self); 58 | #endif 59 | 60 | #ifdef HAVE_WLR_DATA_CONTROL 61 | void offer_init_zwlr_data_control_offer_v1(struct offer *self); 62 | #endif 63 | 64 | #ifdef HAVE_EXT_DATA_CONTROL 65 | void offer_init_ext_data_control_offer_v1(struct offer *self); 66 | #endif 67 | 68 | #endif /* TYPES_OFFER_H */ 69 | -------------------------------------------------------------------------------- /src/types/popup-surface.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/popup-surface.h" 20 | #include "types/registry.h" 21 | #include "types/seat.h" 22 | #include "types/keyboard.h" 23 | #include "types/shell.h" 24 | #include "types/shell-surface.h" 25 | #include "util/files.h" 26 | #include "util/misc.h" 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | static void forward_on_focus( 33 | struct keyboard *keyboard, 34 | uint32_t serial 35 | ) { 36 | struct popup_surface *self = (struct popup_surface *) keyboard->data; 37 | if (self->on_focus != NULL) { 38 | self->on_focus(self, serial); 39 | } 40 | } 41 | 42 | void popup_surface_init(struct popup_surface *self) { 43 | self->shell = registry_find_shell(self->registry); 44 | if (self->shell == NULL) { 45 | complain_about_missing_shell(); 46 | } 47 | 48 | self->keyboard = seat_get_keyboard(self->seat); 49 | if (self->keyboard == NULL) { 50 | bail("This seat has no keyboard"); 51 | } 52 | self->keyboard->on_focus = forward_on_focus; 53 | self->keyboard->data = self; 54 | 55 | /* Make sure that we get the keyboard 56 | * object before we create the surface, 57 | * so that we get the enter event. 58 | */ 59 | wl_display_dispatch(self->registry->wl_display); 60 | 61 | 62 | struct wl_compositor *wl_compositor = self->registry->wl_compositor; 63 | if (wl_compositor == NULL) { 64 | complain_about_missing_global("wl_compositor"); 65 | } 66 | self->wl_surface = wl_compositor_create_surface(wl_compositor); 67 | self->shell_surface = shell_create_shell_surface( 68 | self->shell, 69 | self->wl_surface 70 | ); 71 | 72 | #ifdef HAVE_GTK_SHELL 73 | if (self->registry->gtk_shell1 != NULL) { 74 | self->gtk_surface = gtk_shell1_get_gtk_surface( 75 | self->registry->gtk_shell1, 76 | self->wl_surface 77 | ); 78 | } 79 | #endif 80 | 81 | /* Signal that the surface is ready to be configured */ 82 | wl_surface_commit(self->wl_surface); 83 | wl_display_roundtrip(self->registry->wl_display); 84 | 85 | if (self->wl_surface == NULL) { 86 | /* It's possible that we were given focus 87 | * (without ever commiting a buffer) during 88 | * the above roundtrip, in which case we have 89 | * already fired the callback and have likely 90 | * already destroyed the surface. No need to 91 | * do anything further in that case. 92 | */ 93 | free(self); 94 | return; 95 | } 96 | 97 | /* Remember that after this point, we should 98 | * free() self when it gets destroyed. 99 | */ 100 | self->should_free_self = 1; 101 | 102 | int width = 1; 103 | int height = 1; 104 | int stride = width * 4; 105 | int size = stride * height; 106 | 107 | /* Open an anonymous file and write some zero bytes to it */ 108 | int fd = create_anonymous_file(); 109 | int rc = ftruncate(fd, size); 110 | if (rc < 0) { 111 | perror("ftruncate"); 112 | } 113 | 114 | /* Create a shared memory pool */ 115 | struct wl_shm *wl_shm = self->registry->wl_shm; 116 | if (wl_shm == NULL) { 117 | complain_about_missing_global("wl_shm"); 118 | } 119 | struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(wl_shm, fd, size); 120 | 121 | /* Allocate the buffer in that pool */ 122 | struct wl_buffer *wl_buffer = wl_shm_pool_create_buffer( 123 | wl_shm_pool, 124 | 0, 125 | width, 126 | height, 127 | stride, 128 | WL_SHM_FORMAT_ARGB8888 129 | ); 130 | /* We're using ARGB, so zero bytes mean 131 | * a fully transparent pixel, which happens 132 | * to be exactly what we want. 133 | */ 134 | 135 | wl_surface_attach(self->wl_surface, wl_buffer, 0, 0); 136 | wl_surface_damage(self->wl_surface, 0, 0, width, height); 137 | 138 | /* Ask the compositor nicely to give us focus */ 139 | #ifdef HAVE_GTK_SHELL 140 | if (self->gtk_surface != NULL) { 141 | gtk_surface1_present(self->gtk_surface, 0); 142 | } 143 | #endif 144 | #ifdef HAVE_XDG_ACTIVATION 145 | if (self->registry->xdg_activation_v1 != NULL) { 146 | /* See if someone was kind enough to leave 147 | * some tokens for us in the environment. 148 | */ 149 | const char *token = getenv("XDG_ACTIVATION_TOKEN"); 150 | if (token == NULL) { 151 | token = getenv("DESKTOP_STARTUP_ID"); 152 | } 153 | if (token != NULL) { 154 | xdg_activation_v1_activate( 155 | self->registry->xdg_activation_v1, 156 | token, 157 | self->wl_surface 158 | ); 159 | } 160 | } 161 | #endif 162 | 163 | wl_surface_commit(self->wl_surface); 164 | } 165 | 166 | void popup_surface_destroy(struct popup_surface *self) { 167 | /* We cannot destroy the keyboard 168 | * because it acts as a listener, 169 | * and there's no way to reset that. 170 | * So just unreference ourselves. 171 | */ 172 | self->keyboard->on_focus = NULL; 173 | self->keyboard->data = NULL; 174 | 175 | shell_surface_destroy(self->shell_surface); 176 | #ifdef HAVE_GTK_SHELL 177 | if (self->gtk_surface != NULL) { 178 | if ( 179 | gtk_surface1_get_version(self->gtk_surface) >= 180 | GTK_SURFACE1_RELEASE_SINCE_VERSION 181 | ) { 182 | gtk_surface1_release(self->gtk_surface); 183 | } else { 184 | gtk_surface1_destroy(self->gtk_surface); 185 | } 186 | } 187 | #endif 188 | wl_surface_destroy(self->wl_surface); 189 | /* Let popup_surface_init() know we're already done */ 190 | self->wl_surface = NULL; 191 | free(self->shell); 192 | 193 | if (self->should_free_self) { 194 | free(self); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/types/popup-surface.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_POPUP_SURFACE_H 20 | #define TYPES_POPUP_SURFACE_H 21 | 22 | #include "includes/shell-protocols.h" 23 | 24 | #include 25 | 26 | struct registry; 27 | struct shell; 28 | struct shell_surface; 29 | struct seat; 30 | struct keyboard; 31 | 32 | struct popup_surface { 33 | /* These fields are initialized by the creator */ 34 | struct registry *registry; 35 | struct seat *seat; 36 | void (*on_focus)(struct popup_surface *self, uint32_t serial); 37 | void *data; 38 | 39 | /* These fields are initialized by the implementation */ 40 | struct shell *shell; 41 | struct shell_surface *shell_surface; 42 | struct wl_surface *wl_surface; 43 | struct keyboard *keyboard; 44 | int should_free_self; 45 | 46 | #ifdef HAVE_GTK_SHELL 47 | struct gtk_surface1 *gtk_surface; 48 | #endif 49 | }; 50 | 51 | void popup_surface_init(struct popup_surface *self); 52 | void popup_surface_destroy(struct popup_surface *self); 53 | 54 | #endif /* TYPES_POPUP_SURFACE_H */ 55 | -------------------------------------------------------------------------------- /src/types/registry.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/registry.h" 20 | #include "types/seat.h" 21 | #include "types/shell.h" 22 | #include "types/device-manager.h" 23 | #include "includes/shell-protocols.h" 24 | #include "includes/selection-protocols.h" 25 | #include "util/misc.h" 26 | 27 | #include 28 | #include 29 | 30 | #define BIND(interface_name, known_version) \ 31 | if (strcmp(interface, #interface_name) == 0) { \ 32 | if (version >= (known_version)) { \ 33 | self->interface_name = wl_registry_bind( \ 34 | wl_registry, \ 35 | name, \ 36 | &interface_name ## _interface, \ 37 | known_version \ 38 | ); \ 39 | } \ 40 | } 41 | 42 | static void wl_registry_global_handler( 43 | void *data, 44 | struct wl_registry *wl_registry, 45 | uint32_t name, 46 | const char *interface, 47 | uint32_t version 48 | ) { 49 | struct registry *self = (struct registry *) data; 50 | 51 | BIND(wl_compositor, 2) 52 | BIND(wl_shm, 1) 53 | 54 | #ifdef HAVE_XDG_ACTIVATION 55 | BIND(xdg_activation_v1, 1) 56 | #endif 57 | 58 | /* Shells */ 59 | 60 | BIND(wl_shell, 1) 61 | 62 | #ifdef HAVE_XDG_SHELL 63 | BIND(xdg_wm_base, 1) 64 | #endif 65 | 66 | #ifdef HAVE_GTK_SHELL 67 | BIND(gtk_shell1, 4) 68 | #endif 69 | 70 | /* Device managers */ 71 | 72 | BIND(wl_data_device_manager, 1) 73 | 74 | #ifdef HAVE_GTK_PRIMARY_SELECTION 75 | BIND(gtk_primary_selection_device_manager, 1) 76 | #endif 77 | 78 | #ifdef HAVE_WP_PRIMARY_SELECTION 79 | BIND(zwp_primary_selection_device_manager_v1, 1) 80 | #endif 81 | 82 | #ifdef HAVE_WLR_DATA_CONTROL 83 | BIND(zwlr_data_control_manager_v1, version > 2 ? 2 : version) 84 | #endif 85 | 86 | #ifdef HAVE_EXT_DATA_CONTROL 87 | BIND(ext_data_control_manager_v1, 1) 88 | #endif 89 | 90 | if (strcmp(interface, "wl_seat") == 0 && version >= 2) { 91 | struct seat *seat = calloc(1, sizeof(struct seat)); 92 | seat->proxy = wl_registry_bind( 93 | wl_registry, 94 | name, 95 | &wl_seat_interface, 96 | 2 97 | ); 98 | seat_init(seat); 99 | struct seat **ptr = wl_array_add(&self->seats, sizeof(struct seat *)); 100 | *ptr = seat; 101 | } 102 | } 103 | 104 | static void wl_registry_global_remove_handler( 105 | void *data, 106 | struct wl_registry *wl_registry, 107 | uint32_t name 108 | ) {} 109 | 110 | static const struct wl_registry_listener wl_registry_listener = { 111 | .global = wl_registry_global_handler, 112 | .global_remove = wl_registry_global_remove_handler 113 | }; 114 | 115 | void registry_init(struct registry *self) { 116 | self->proxy = wl_display_get_registry(self->wl_display); 117 | wl_registry_add_listener(self->proxy, &wl_registry_listener, self); 118 | } 119 | 120 | struct shell *registry_find_shell(struct registry *self) { 121 | struct shell *shell = calloc(1, sizeof(struct shell)); 122 | 123 | if (self->wl_shell != NULL) { 124 | shell->proxy = (struct wl_proxy *) self->wl_shell; 125 | shell_init_wl_shell(shell); 126 | return shell; 127 | } 128 | 129 | #ifdef HAVE_XDG_SHELL 130 | if (self->xdg_wm_base != NULL) { 131 | shell->proxy = (struct wl_proxy *) self->xdg_wm_base; 132 | shell_init_xdg_shell(shell); 133 | return shell; 134 | } 135 | #endif 136 | 137 | free(shell); 138 | return NULL; 139 | } 140 | 141 | #define TRY(type) \ 142 | if (self->type != NULL) { \ 143 | device_manager->proxy = (struct wl_proxy *) self->type; \ 144 | device_manager_init_ ## type(device_manager); \ 145 | return device_manager; \ 146 | } 147 | 148 | struct device_manager *registry_find_device_manager( 149 | struct registry *self, 150 | int primary 151 | ) { 152 | struct device_manager *device_manager 153 | = calloc(1, sizeof(struct device_manager)); 154 | device_manager->wl_display = self->wl_display; 155 | 156 | /* For regular selection, we just look at the three supported 157 | * protocols. We prefer ext-data-control or wlr-data-control, 158 | * as they don't require us to use the popup surface hack. 159 | */ 160 | 161 | if (!primary) { 162 | #ifdef HAVE_EXT_DATA_CONTROL 163 | TRY(ext_data_control_manager_v1) 164 | #endif 165 | #ifdef HAVE_WLR_DATA_CONTROL 166 | TRY(zwlr_data_control_manager_v1) 167 | #endif 168 | TRY(wl_data_device_manager) 169 | 170 | free(device_manager); 171 | return NULL; 172 | } 173 | 174 | /* For primary selection, it's a bit more complicated. We also 175 | * prefer wlr-data-control, but we don't know in advance whether 176 | * the compositor supports primary selection, as unlike with 177 | * other protocols here, the mere presence of wlr-data-control 178 | * does not imply primary selection support. However, we assume 179 | * that if a compositor supports primary selection at all, then 180 | * if it supports wlr-data-control v2 it also supports primary 181 | * selection over wlr-data-control; which is only reasonable. 182 | * 183 | * The same goes for the newer ext-data-control, which has 184 | * potential support for primary selection since v1, so no need 185 | * for the version check there. 186 | */ 187 | 188 | #ifdef HAVE_EXT_DATA_CONTROL 189 | TRY(ext_data_control_manager_v1) 190 | #endif 191 | 192 | #ifdef HAVE_WLR_DATA_CONTROL 193 | if (self->zwlr_data_control_manager_v1 != NULL) { 194 | struct wl_proxy *proxy 195 | = (struct wl_proxy *) self->zwlr_data_control_manager_v1; 196 | if (wl_proxy_get_version(proxy) >= 2) { 197 | device_manager->proxy = proxy; 198 | device_manager_init_zwlr_data_control_manager_v1(device_manager); 199 | return device_manager; 200 | } 201 | } 202 | #endif 203 | 204 | #ifdef HAVE_WP_PRIMARY_SELECTION 205 | TRY(zwp_primary_selection_device_manager_v1) 206 | #endif 207 | 208 | #ifdef HAVE_GTK_PRIMARY_SELECTION 209 | TRY(gtk_primary_selection_device_manager) 210 | #endif 211 | 212 | free(device_manager); 213 | return NULL; 214 | } 215 | 216 | struct seat *registry_find_seat( 217 | struct registry *self, 218 | const char *name 219 | ) { 220 | /* Ensure we get all the seat info */ 221 | wl_display_roundtrip(self->wl_display); 222 | 223 | struct seat **ptr; 224 | wl_array_for_each(ptr, &self->seats) { 225 | struct seat *seat = *ptr; 226 | if (name == NULL || strcmp(seat->name, name) == 0) { 227 | return seat; 228 | } 229 | } 230 | return NULL; 231 | } 232 | -------------------------------------------------------------------------------- /src/types/registry.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_REGISTRY_H 20 | #define TYPES_REGISTRY_H 21 | 22 | #include "includes/shell-protocols.h" 23 | #include "includes/selection-protocols.h" 24 | 25 | #include 26 | 27 | struct shell; 28 | struct device_manager; 29 | struct seat; 30 | 31 | struct registry { 32 | /* This field is initialized by the creator */ 33 | struct wl_display *wl_display; 34 | 35 | /* These fields are initialized by the implementation */ 36 | 37 | struct wl_registry *proxy; 38 | struct wl_array seats; 39 | 40 | struct wl_compositor *wl_compositor; 41 | struct wl_shm *wl_shm; 42 | 43 | #ifdef HAVE_XDG_ACTIVATION 44 | struct xdg_activation_v1 *xdg_activation_v1; 45 | #endif 46 | 47 | /* Shells */ 48 | 49 | struct wl_shell *wl_shell; 50 | #ifdef HAVE_XDG_SHELL 51 | struct xdg_wm_base *xdg_wm_base; 52 | #endif 53 | #ifdef HAVE_GTK_SHELL 54 | struct gtk_shell1 *gtk_shell1; 55 | #endif 56 | 57 | /* Device managers */ 58 | 59 | struct wl_data_device_manager *wl_data_device_manager; 60 | #ifdef HAVE_GTK_PRIMARY_SELECTION 61 | struct gtk_primary_selection_device_manager 62 | *gtk_primary_selection_device_manager; 63 | #endif 64 | #ifdef HAVE_WP_PRIMARY_SELECTION 65 | struct zwp_primary_selection_device_manager_v1 66 | *zwp_primary_selection_device_manager_v1; 67 | #endif 68 | #ifdef HAVE_WLR_DATA_CONTROL 69 | struct zwlr_data_control_manager_v1 70 | *zwlr_data_control_manager_v1; 71 | #endif 72 | #ifdef HAVE_EXT_DATA_CONTROL 73 | struct ext_data_control_manager_v1 74 | *ext_data_control_manager_v1; 75 | #endif 76 | }; 77 | 78 | void registry_init(struct registry *self); 79 | 80 | struct shell *registry_find_shell(struct registry *self); 81 | 82 | struct device_manager *registry_find_device_manager( 83 | struct registry *self, 84 | int primary 85 | ); 86 | 87 | struct seat *registry_find_seat( 88 | struct registry *self, 89 | const char *name 90 | ); 91 | 92 | #endif /* TYPES_REGISTRY_H */ 93 | -------------------------------------------------------------------------------- /src/types/seat.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/seat.h" 20 | #include "types/keyboard.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | static void wl_seat_capabilities_handler( 27 | void *data, 28 | struct wl_seat *seat, 29 | uint32_t capabilities 30 | ) { 31 | struct seat *self = (struct seat *) data; 32 | self->capabilities = capabilities; 33 | } 34 | 35 | static void wl_seat_name_handler( 36 | void *data, 37 | struct wl_seat *seat, 38 | const char *name 39 | ) { 40 | struct seat *self = (struct seat *) data; 41 | self->name = strdup(name); 42 | } 43 | 44 | static const struct wl_seat_listener wl_seat_listener = { 45 | .capabilities = wl_seat_capabilities_handler, 46 | .name = wl_seat_name_handler 47 | }; 48 | 49 | void seat_init(struct seat *self) { 50 | wl_seat_add_listener(self->proxy, &wl_seat_listener, self); 51 | } 52 | 53 | struct keyboard *seat_get_keyboard(struct seat *self) { 54 | if ((self->capabilities & WL_SEAT_CAPABILITY_KEYBOARD) == 0) { 55 | return NULL; 56 | } 57 | struct keyboard *keyboard = calloc(1, sizeof(struct keyboard)); 58 | keyboard->proxy = wl_seat_get_keyboard(self->proxy); 59 | keyboard_init(keyboard); 60 | return keyboard; 61 | } 62 | -------------------------------------------------------------------------------- /src/types/seat.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_SEAT_H 20 | #define TYPES_SEAT_H 21 | 22 | #include 23 | 24 | struct keyboard; 25 | 26 | struct seat { 27 | /* This field is initialized by the creator */ 28 | struct wl_seat *proxy; 29 | 30 | /* These fields are initialized by the implementation */ 31 | char *name; 32 | uint32_t capabilities; 33 | }; 34 | 35 | void seat_init(struct seat *self); 36 | struct keyboard *seat_get_keyboard(struct seat *self); 37 | 38 | 39 | #endif /* TYPES_SEAT_H */ 40 | -------------------------------------------------------------------------------- /src/types/shell-surface.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/shell-surface.h" 20 | #include "includes/shell-protocols.h" 21 | 22 | #include 23 | #include 24 | 25 | void shell_surface_destroy(struct shell_surface *self) { 26 | self->do_destroy(self); 27 | free(self); 28 | } 29 | 30 | 31 | /* Core Wayland implementation */ 32 | 33 | static void wl_shell_surface_ping( 34 | void *data, 35 | struct wl_shell_surface *wl_shell_surface, 36 | uint32_t serial 37 | ) { 38 | wl_shell_surface_pong(wl_shell_surface, serial); 39 | } 40 | 41 | static void wl_shell_surface_configure( 42 | void *data, 43 | struct wl_shell_surface *wl_shell_surface, 44 | uint32_t edges, 45 | int32_t width, 46 | int32_t height 47 | ) {} 48 | 49 | static void wl_shell_surface_popup_done( 50 | void *data, 51 | struct wl_shell_surface *wl_shell_surface 52 | ) {} 53 | 54 | static const struct wl_shell_surface_listener wl_shell_surface_listener = { 55 | .ping = wl_shell_surface_ping, 56 | .configure = wl_shell_surface_configure, 57 | .popup_done = wl_shell_surface_popup_done 58 | }; 59 | 60 | static void destroy_wl_shell_surface(struct shell_surface *self) { 61 | struct wl_shell_surface *proxy = (struct wl_shell_surface *) self->proxy; 62 | wl_shell_surface_destroy(proxy); 63 | } 64 | 65 | void shell_surface_init_wl_shell_surface(struct shell_surface *self) { 66 | struct wl_shell_surface *proxy = (struct wl_shell_surface *) self->proxy; 67 | wl_shell_surface_add_listener(proxy, &wl_shell_surface_listener, self); 68 | wl_shell_surface_set_toplevel(proxy); 69 | wl_shell_surface_set_title(proxy, "wl-clipboard"); 70 | self->do_destroy = destroy_wl_shell_surface; 71 | } 72 | 73 | 74 | /* xdg-shell implementation */ 75 | 76 | #ifdef HAVE_XDG_SHELL 77 | 78 | static void xdg_toplevel_configure_handler( 79 | void *data, 80 | struct xdg_toplevel *xdg_toplevel, 81 | int32_t width, 82 | int32_t height, 83 | struct wl_array *states 84 | ) {} 85 | 86 | static void xdg_toplevel_close_handler( 87 | void *data, 88 | struct xdg_toplevel *xdg_toplevel 89 | ) {} 90 | 91 | static const struct xdg_toplevel_listener xdg_toplevel_listener = { 92 | .configure = xdg_toplevel_configure_handler, 93 | .close = xdg_toplevel_close_handler 94 | }; 95 | 96 | static void xdg_surface_configure_handler( 97 | void *data, 98 | struct xdg_surface *xdg_surface, 99 | uint32_t serial 100 | ) { 101 | xdg_surface_ack_configure(xdg_surface, serial); 102 | } 103 | 104 | static const struct xdg_surface_listener xdg_surface_listener = { 105 | .configure = xdg_surface_configure_handler 106 | }; 107 | 108 | static void destroy_xdg_surface(struct shell_surface *self) { 109 | struct xdg_toplevel *toplevel = (struct xdg_toplevel *) self->proxy2; 110 | struct xdg_surface *proxy = (struct xdg_surface *) self->proxy; 111 | xdg_toplevel_destroy(toplevel); 112 | xdg_surface_destroy(proxy); 113 | } 114 | 115 | void shell_surface_init_xdg_surface(struct shell_surface *self) { 116 | struct xdg_surface *proxy = (struct xdg_surface *) self->proxy; 117 | xdg_surface_add_listener(proxy, &xdg_surface_listener, self); 118 | struct xdg_toplevel *toplevel = xdg_surface_get_toplevel(proxy); 119 | self->proxy2 = (struct wl_proxy *) toplevel; 120 | xdg_toplevel_add_listener(toplevel, &xdg_toplevel_listener, self); 121 | xdg_toplevel_set_title(toplevel, "wl-clipboard"); 122 | self->do_destroy = destroy_xdg_surface; 123 | } 124 | 125 | #endif /* HAVE_XDG_SHELL */ 126 | -------------------------------------------------------------------------------- /src/types/shell-surface.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_SHELL_SURFACE_H 20 | #define TYPES_SHELL_SURFACE_H 21 | 22 | #include "includes/shell-protocols.h" 23 | 24 | struct shell_surface { 25 | /* This field is initialized by the creator */ 26 | struct wl_proxy *proxy; 27 | 28 | /* These fields are initialized by the implementation */ 29 | struct wl_proxy *proxy2; 30 | void (*do_destroy)(struct shell_surface *self); 31 | }; 32 | 33 | void shell_surface_destroy(struct shell_surface *self); 34 | 35 | 36 | /* Initializers */ 37 | 38 | void shell_surface_init_wl_shell_surface(struct shell_surface *self); 39 | 40 | #ifdef HAVE_XDG_SHELL 41 | void shell_surface_init_xdg_surface(struct shell_surface *self); 42 | #endif 43 | 44 | #endif /* TYPES_SHELL_SURFACE_H */ 45 | -------------------------------------------------------------------------------- /src/types/shell.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/shell-surface.h" 20 | #include "types/shell.h" 21 | #include "includes/shell-protocols.h" 22 | 23 | #include 24 | 25 | struct shell_surface *shell_create_shell_surface( 26 | struct shell *self, 27 | struct wl_surface *surface 28 | ) { 29 | return self->do_create_shell_surface(self, surface); 30 | } 31 | 32 | 33 | /* Core Wayland implementation */ 34 | 35 | static struct shell_surface *wl_shell_create_shell_surface( 36 | struct shell *self, 37 | struct wl_surface *surface 38 | ) { 39 | struct wl_shell *shell = (struct wl_shell *) self->proxy; 40 | struct wl_shell_surface *wl_shell_surface = 41 | wl_shell_get_shell_surface(shell, surface); 42 | 43 | struct shell_surface *shell_surface 44 | = calloc(1, sizeof(struct shell_surface)); 45 | shell_surface->proxy = (struct wl_proxy *) wl_shell_surface; 46 | shell_surface_init_wl_shell_surface(shell_surface); 47 | return shell_surface; 48 | } 49 | 50 | void shell_init_wl_shell(struct shell *self) { 51 | self->do_create_shell_surface = wl_shell_create_shell_surface; 52 | } 53 | 54 | 55 | /* xdg-shell implementation */ 56 | 57 | #ifdef HAVE_XDG_SHELL 58 | 59 | static struct shell_surface *xdg_shell_create_shell_surfacce( 60 | struct shell *self, 61 | struct wl_surface *surface 62 | ) { 63 | struct xdg_wm_base *wm_base = (struct xdg_wm_base *) self->proxy; 64 | 65 | struct shell_surface *shell_surface 66 | = calloc(1, sizeof(struct shell_surface)); 67 | 68 | struct xdg_surface *xdg_surface = 69 | xdg_wm_base_get_xdg_surface(wm_base, surface); 70 | shell_surface->proxy = (struct wl_proxy *) xdg_surface; 71 | shell_surface_init_xdg_surface(shell_surface); 72 | return shell_surface; 73 | } 74 | 75 | static void xdg_wm_base_ping_handler( 76 | void *data, 77 | struct xdg_wm_base *wm_base, 78 | uint32_t serial 79 | ) { 80 | xdg_wm_base_pong(wm_base, serial); 81 | } 82 | 83 | static const struct xdg_wm_base_listener xdg_wm_base_listener = { 84 | .ping = xdg_wm_base_ping_handler 85 | }; 86 | 87 | void shell_init_xdg_shell(struct shell *self) { 88 | struct xdg_wm_base *wm_base = (struct xdg_wm_base *) self->proxy; 89 | xdg_wm_base_add_listener(wm_base, &xdg_wm_base_listener, self); 90 | self->do_create_shell_surface = xdg_shell_create_shell_surfacce; 91 | } 92 | 93 | #endif /* HAVE_XDG_SHELL */ 94 | -------------------------------------------------------------------------------- /src/types/shell.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_SHELL_H 20 | #define TYPES_SHELL_H 21 | 22 | #include "includes/shell-protocols.h" 23 | 24 | #include 25 | 26 | struct shell_surface; 27 | 28 | struct shell { 29 | /* This field is initialized by the creator */ 30 | struct wl_proxy *proxy; 31 | 32 | /* This field is initialized by the implementation */ 33 | struct shell_surface *(*do_create_shell_surface)( 34 | struct shell *self, 35 | struct wl_surface *surface 36 | ); 37 | }; 38 | 39 | struct shell_surface *shell_create_shell_surface( 40 | struct shell *self, 41 | struct wl_surface *surface 42 | ); 43 | 44 | 45 | /* Initializers */ 46 | 47 | void shell_init_wl_shell(struct shell *self); 48 | 49 | #ifdef HAVE_XDG_SHELL 50 | void shell_init_xdg_shell(struct shell *self); 51 | #endif 52 | 53 | #endif /* TYPES_SHELL_H */ 54 | -------------------------------------------------------------------------------- /src/types/source.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "config.h" 20 | #include "includes/selection-protocols.h" 21 | #include "types/source.h" 22 | #include "util/string.h" 23 | 24 | #include 25 | 26 | void source_offer(struct source *self, char *mime_type) { 27 | self->do_offer(self->proxy, mime_type); 28 | } 29 | 30 | 31 | /* Macros to reduce implementation boilerplate */ 32 | 33 | #define SEND_HANDLER(type) \ 34 | static void type ## _send_handler( \ 35 | void *data, \ 36 | struct type *proxy, \ 37 | const char *mime_type, \ 38 | int fd \ 39 | ) { \ 40 | struct source *self = data; \ 41 | if (self->send_callback != NULL) { \ 42 | self->send_callback(self, mime_type, fd); \ 43 | } else { \ 44 | close(fd); \ 45 | } \ 46 | } 47 | 48 | #define CANCELLED_HANDLER(type) \ 49 | static void type ## _cancelled_handler( \ 50 | void *data, \ 51 | struct type *proxy \ 52 | ) { \ 53 | struct source *self = data; \ 54 | if (self->cancelled_callback != NULL) { \ 55 | self->cancelled_callback(self); \ 56 | } \ 57 | } 58 | 59 | #define LISTENER(type) \ 60 | static const struct type ## _listener type ## _listener = { \ 61 | .send = type ## _send_handler, \ 62 | .cancelled = type ## _cancelled_handler \ 63 | }; 64 | 65 | #define INIT(type) \ 66 | void source_init_ ## type(struct source *self) { \ 67 | self->do_offer = \ 68 | (void (*)(struct wl_proxy *, const char *)) type ## _offer; \ 69 | struct type *proxy = (struct type *) self->proxy; \ 70 | type ## _add_listener(proxy, &type ## _listener, self); \ 71 | } 72 | 73 | 74 | /* Core Wayland implementation */ 75 | 76 | static void wl_data_source_target_handler 77 | ( 78 | void *data, 79 | struct wl_data_source *wl_data_source, 80 | const char *mime_type 81 | ) {} 82 | 83 | SEND_HANDLER(wl_data_source) 84 | CANCELLED_HANDLER(wl_data_source) 85 | 86 | static const struct wl_data_source_listener wl_data_source_listener = { 87 | .target = wl_data_source_target_handler, 88 | .send = wl_data_source_send_handler, 89 | .cancelled = wl_data_source_cancelled_handler 90 | }; 91 | 92 | INIT(wl_data_source) 93 | 94 | 95 | /* gtk-primary-selection implementation */ 96 | 97 | #ifdef HAVE_GTK_PRIMARY_SELECTION 98 | 99 | SEND_HANDLER(gtk_primary_selection_source) 100 | CANCELLED_HANDLER(gtk_primary_selection_source) 101 | LISTENER(gtk_primary_selection_source) 102 | INIT(gtk_primary_selection_source) 103 | 104 | #endif /* HAVE_GTK_PRIMARY_SELECTION */ 105 | 106 | 107 | /* wp-primary-selection implementation */ 108 | 109 | #ifdef HAVE_WP_PRIMARY_SELECTION 110 | 111 | SEND_HANDLER(zwp_primary_selection_source_v1) 112 | CANCELLED_HANDLER(zwp_primary_selection_source_v1) 113 | LISTENER(zwp_primary_selection_source_v1) 114 | INIT(zwp_primary_selection_source_v1) 115 | 116 | #endif /* HAVE_WP_PRIMARY_SELECTION */ 117 | 118 | 119 | /* wlr-data-control implementation */ 120 | 121 | #ifdef HAVE_WLR_DATA_CONTROL 122 | 123 | SEND_HANDLER(zwlr_data_control_source_v1) 124 | CANCELLED_HANDLER(zwlr_data_control_source_v1) 125 | LISTENER(zwlr_data_control_source_v1) 126 | INIT(zwlr_data_control_source_v1) 127 | 128 | #endif /* HAVE_WLR_DATA_CONTROL */ 129 | 130 | 131 | /* ext-data-control implementation */ 132 | 133 | #ifdef HAVE_EXT_DATA_CONTROL 134 | 135 | SEND_HANDLER(ext_data_control_source_v1) 136 | CANCELLED_HANDLER(ext_data_control_source_v1) 137 | LISTENER(ext_data_control_source_v1) 138 | INIT(ext_data_control_source_v1) 139 | 140 | #endif /* HAVE_EXT_DATA_CONTROL */ 141 | -------------------------------------------------------------------------------- /src/types/source.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef TYPES_SOURCE_H 20 | #define TYPES_SOURCE_H 21 | 22 | #include "includes/selection-protocols.h" 23 | 24 | 25 | struct source { 26 | /* These fields are initialized by the creator */ 27 | void (*send_callback)(struct source *self, const char *mime_type, int fd); 28 | void (*cancelled_callback)(struct source *self); 29 | void *data; 30 | 31 | struct wl_proxy *proxy; 32 | 33 | /* This field is initialized by the implementation */ 34 | void (*do_offer)(struct wl_proxy *proxy, const char *mime_type); 35 | }; 36 | 37 | void source_offer(struct source *self, char *mime_type); 38 | 39 | /* Initializers */ 40 | 41 | void source_init_wl_data_source(struct source *self); 42 | 43 | #ifdef HAVE_GTK_PRIMARY_SELECTION 44 | void source_init_gtk_primary_selection_source(struct source *self); 45 | #endif 46 | 47 | #ifdef HAVE_WP_PRIMARY_SELECTION 48 | void source_init_zwp_primary_selection_source_v1(struct source *self); 49 | #endif 50 | 51 | #ifdef HAVE_WLR_DATA_CONTROL 52 | void source_init_zwlr_data_control_source_v1(struct source *self); 53 | #endif 54 | 55 | #ifdef HAVE_EXT_DATA_CONTROL 56 | void source_init_ext_data_control_source_v1(struct source *self); 57 | #endif 58 | 59 | #endif /* TYPES_SOURCE_H */ 60 | -------------------------------------------------------------------------------- /src/util/files.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "util/files.h" 20 | #include "util/string.h" 21 | #include "util/misc.h" 22 | 23 | #include "config.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include // open 32 | #include // open 33 | #include // exit 34 | #include // basename 35 | #include 36 | #include 37 | #include 38 | 39 | #ifdef HAVE_MEMFD 40 | # include // syscall, SYS_memfd_create 41 | #endif 42 | #ifdef HAVE_SHM_ANON 43 | # include // shm_open, SHM_ANON 44 | #endif 45 | 46 | #include // wl_display_get_fd 47 | 48 | 49 | void complain_about_closed_stdio(struct wl_display *wl_display) { 50 | const char *message = "wl-clipboard has been launched with a closed" 51 | " standard file descriptor. This is a bug in the software that" 52 | " has launched wl-clipboard. Aborting."; 53 | 54 | /* See if we can write to stderr */ 55 | if (wl_display_get_fd(wl_display) < STDERR_FILENO) { 56 | int rc = fcntl(STDERR_FILENO, F_GETFL); 57 | if (rc > 0) { 58 | rc &= O_ACCMODE; 59 | if (rc == O_WRONLY || rc == O_RDWR) { 60 | /* Yes, we can! */ 61 | fprintf(stderr, "%s\n", message); 62 | fflush(stderr); 63 | abort(); 64 | } 65 | } 66 | } 67 | 68 | /* Maybe there is a tty we could write to? */ 69 | FILE *tty = fopen("/dev/tty", "w"); 70 | if (tty != NULL) { 71 | fprintf(tty, "%s\n", message); 72 | fflush(tty); 73 | abort(); 74 | } 75 | 76 | /* As a last resort, try syslog */ 77 | openlog("wl-clipboard", LOG_CONS | LOG_PID, LOG_USER); 78 | syslog(LOG_ERR, "%s", message); 79 | closelog(); 80 | abort(); 81 | } 82 | 83 | int create_anonymous_file() { 84 | int res; 85 | #ifdef HAVE_MEMFD 86 | res = syscall(SYS_memfd_create, "buffer", 0); 87 | if (res >= 0) { 88 | return res; 89 | } 90 | #endif 91 | #ifdef HAVE_SHM_ANON 92 | res = shm_open(SHM_ANON, O_RDWR | O_CREAT, 0600); 93 | if (res >= 0) { 94 | return res; 95 | } 96 | #endif 97 | (void) res; 98 | return fileno(tmpfile()); 99 | } 100 | 101 | void trim_trailing_newline(const char *file_path) { 102 | int fd = open(file_path, O_RDWR); 103 | if (fd < 0) { 104 | perror("open file for trimming"); 105 | return; 106 | } 107 | 108 | int seek_res = lseek(fd, -1, SEEK_END); 109 | if (seek_res < 0 && errno == EINVAL) { 110 | /* It was an empty file */ 111 | goto out; 112 | } else if (seek_res < 0) { 113 | perror("lseek"); 114 | goto out; 115 | } 116 | /* If the seek was successful, seek_res is the 117 | * new file size after trimming the newline. 118 | */ 119 | char last_char; 120 | int read_res = read(fd, &last_char, 1); 121 | if (read_res != 1) { 122 | perror("read"); 123 | goto out; 124 | } 125 | if (last_char != '\n') { 126 | goto out; 127 | } 128 | 129 | int rc = ftruncate(fd, seek_res); 130 | if (rc < 0) { 131 | perror("ftruncate"); 132 | } 133 | out: 134 | close(fd); 135 | } 136 | 137 | char *path_for_fd(int fd) { 138 | char fdpath[64]; 139 | snprintf(fdpath, sizeof(fdpath), "/dev/fd/%d", fd); 140 | return realpath(fdpath, NULL); 141 | } 142 | 143 | char *infer_mime_type_from_contents(const char *file_path) { 144 | /* Spawn xdg-mime query filetype */ 145 | int pipefd[2]; 146 | int rc = pipe(pipefd); 147 | if (rc < 0) { 148 | perror("pipe"); 149 | return NULL; 150 | } 151 | 152 | pid_t pid = fork(); 153 | if (pid < 0) { 154 | perror("fork"); 155 | close(pipefd[0]); 156 | close(pipefd[1]); 157 | return NULL; 158 | } 159 | if (pid == 0) { 160 | dup2(pipefd[1], STDOUT_FILENO); 161 | close(pipefd[0]); 162 | close(pipefd[1]); 163 | int devnull = open("/dev/null", O_RDONLY); 164 | if (devnull >= 0) { 165 | dup2(devnull, STDIN_FILENO); 166 | close(devnull); 167 | } else { 168 | /* If we cannot open /dev/null, just close stdin */ 169 | close(STDIN_FILENO); 170 | } 171 | signal(SIGHUP, SIG_DFL); 172 | signal(SIGPIPE, SIG_DFL); 173 | execlp("xdg-mime", "xdg-mime", "query", "filetype", file_path, NULL); 174 | exit(1); 175 | } 176 | 177 | close(pipefd[1]); 178 | int wstatus; 179 | waitpid(pid, &wstatus, 0); 180 | 181 | /* See if that worked */ 182 | if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { 183 | close(pipefd[0]); 184 | return NULL; 185 | } 186 | 187 | /* Read the result */ 188 | char *res = malloc(256); 189 | size_t len = read(pipefd[0], res, 256); 190 | /* Trim the newline */ 191 | len--; 192 | res[len] = 0; 193 | close(pipefd[0]); 194 | 195 | if (str_has_prefix(res, "inode/")) { 196 | free(res); 197 | return NULL; 198 | } 199 | 200 | return res; 201 | } 202 | 203 | char *infer_mime_type_from_name(const char *file_path) { 204 | const char *actual_ext = get_file_extension(file_path); 205 | if (actual_ext == NULL) { 206 | return NULL; 207 | } 208 | 209 | FILE *f = fopen("/etc/mime.types", "r"); 210 | if (f == NULL) { 211 | f = fopen("/usr/local/etc/mime.types", "r"); 212 | } 213 | if (f == NULL) { 214 | return NULL; 215 | } 216 | 217 | for (char line[200]; fgets(line, sizeof(line), f) != NULL;) { 218 | /* Skip comments and black lines */ 219 | if (line[0] == '#' || line[0] == '\n') { 220 | continue; 221 | } 222 | 223 | /* Each line consists of a mime type and a list of extensions */ 224 | char mime_type[200]; 225 | int consumed; 226 | if (sscanf(line, "%199s%n", mime_type, &consumed) != 1) { 227 | /* A malformed line, perhaps? */ 228 | continue; 229 | } 230 | char *lineptr = line + consumed; 231 | for (char ext[200]; sscanf(lineptr, "%199s%n", ext, &consumed) == 1;) { 232 | if (strcmp(ext, actual_ext) == 0) { 233 | fclose(f); 234 | return strdup(mime_type); 235 | } 236 | lineptr += consumed; 237 | } 238 | } 239 | fclose(f); 240 | return NULL; 241 | } 242 | 243 | /* Returns the name of a new file */ 244 | char *dump_stdin_into_a_temp_file() { 245 | /* Pick a name for the file we'll be 246 | * creating inside that directory. We 247 | * try to preserve the origial name for 248 | * the mime type inference to work. 249 | */ 250 | char *original_path = path_for_fd(STDIN_FILENO); 251 | char *name = original_path != NULL ? basename(original_path) : "stdin"; 252 | 253 | /* Create a temp directory to host out file */ 254 | const char *tmpdir = getenv("TMPDIR"); 255 | if (tmpdir == NULL) { 256 | tmpdir = "/tmp"; 257 | } 258 | size_t tmpdir_len = strlen(tmpdir); 259 | static const char dir_template[] = "wl-copy-buffer-XXXXXX"; 260 | char *path = malloc( 261 | tmpdir_len + 1 + strlen(dir_template) + 1 + strlen(name) + 1 262 | ); 263 | memcpy(path, tmpdir, tmpdir_len); 264 | path[tmpdir_len] = '/'; 265 | memcpy(path + tmpdir_len + 1, dir_template, strlen(dir_template)); 266 | size_t prefix_len = tmpdir_len + 1 + strlen(dir_template); 267 | path[prefix_len] = 0; 268 | 269 | if (mkdtemp(path) != path) { 270 | perror("mkdtemp"); 271 | exit(1); 272 | } 273 | 274 | path[prefix_len] = '/'; 275 | strcpy(path + prefix_len + 1, name); 276 | 277 | /* Spawn cat to perform the copy */ 278 | pid_t pid = fork(); 279 | if (pid < 0) { 280 | perror("fork"); 281 | exit(1); 282 | } 283 | if (pid == 0) { 284 | int fd = creat(path, S_IRUSR | S_IWUSR); 285 | if (fd < 0) { 286 | perror("creat"); 287 | exit(1); 288 | } 289 | dup2(fd, STDOUT_FILENO); 290 | close(fd); 291 | signal(SIGHUP, SIG_DFL); 292 | signal(SIGPIPE, SIG_DFL); 293 | execlp("cat", "cat", NULL); 294 | perror("exec cat"); 295 | exit(1); 296 | } 297 | 298 | int wstatus; 299 | waitpid(pid, &wstatus, 0); 300 | free(original_path); 301 | if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { 302 | bail("Failed to copy the file"); 303 | } 304 | return path; 305 | } 306 | -------------------------------------------------------------------------------- /src/util/files.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UTIL_FILES_H 20 | #define UTIL_FILES_H 21 | 22 | struct wl_display; 23 | void complain_about_closed_stdio(struct wl_display *wl_display); 24 | 25 | int create_anonymous_file(void); 26 | 27 | void trim_trailing_newline(const char *file_path); 28 | 29 | /* These functions return owned strings, so make sure 30 | * to free() their return values when done with them. 31 | */ 32 | 33 | char *path_for_fd(int fd); 34 | char *infer_mime_type_from_contents(const char *file_path); 35 | char *infer_mime_type_from_name(const char *file_path); 36 | 37 | /* Returns the name of a new file */ 38 | char *dump_stdin_into_a_temp_file(void); 39 | 40 | #endif /* UTIL_FILES_H */ 41 | -------------------------------------------------------------------------------- /src/util/misc.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "config.h" 20 | #include "util/misc.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | void print_version_info() { 28 | printf( 29 | "wl-clipboard " PROJECT_VERSION "\n" 30 | "Copyright (C) 2018-2025 Sergey Bugaev\n" 31 | "License GPLv3+: GNU GPL version 3 or later" 32 | " .\n" 33 | "This is free software: you are free to change and redistribute it.\n" 34 | "There is NO WARRANTY, to the extent permitted by law.\n" 35 | ); 36 | } 37 | 38 | void complain_about_selection_support(int primary) { 39 | if (!primary) { 40 | /* We always expect to find at least wl_data_device_manager */ 41 | complain_about_missing_global("wl_data_device_manager"); 42 | } 43 | 44 | #if !defined(HAVE_WP_PRIMARY_SELECTION) && !defined(HAVE_GTK_PRIMARY_SELECTION) 45 | bail("wl-clipboard was built without primary selection support"); 46 | #endif 47 | 48 | bail("The compositor does not seem to support primary selection"); 49 | } 50 | 51 | void complain_about_watch_mode_support() { 52 | #if defined(HAVE_WLR_DATA_CONTROL) || defined(HAVE_EXT_DATA_CONTROL) 53 | bail( 54 | "Watch mode requires a compositor that supports" 55 | " the data-control protocol" 56 | ); 57 | #else 58 | bail( 59 | "wl-clipboard was built without support for" 60 | " the data-control protocol" 61 | ); 62 | #endif 63 | } 64 | 65 | void complain_about_wayland_connection() { 66 | int saved_errno = errno; 67 | fprintf(stderr, "Failed to connect to a Wayland server"); 68 | if (saved_errno != 0) { 69 | fprintf(stderr, ": %s", strerror(saved_errno)); 70 | } 71 | fputc('\n', stderr); 72 | 73 | const char *display = getenv("WAYLAND_DISPLAY"); 74 | const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); 75 | if (display != NULL) { 76 | fprintf(stderr, "Note: WAYLAND_DISPLAY is set to %s\n", display); 77 | } else { 78 | fprintf( 79 | stderr, 80 | "Note: WAYLAND_DISPLAY is unset" 81 | " (falling back to wayland-0)\n" 82 | ); 83 | display = "wayland-0"; 84 | } 85 | if (runtime_dir != NULL) { 86 | fprintf(stderr, "Note: XDG_RUNTIME_DIR is set to %s\n", runtime_dir); 87 | } else { 88 | fprintf(stderr, "Note: XDG_RUNTIME_DIR is unset\n"); 89 | } 90 | if (display[0] != '/' && runtime_dir != NULL) { 91 | fprintf( 92 | stderr, 93 | "Please check whether %s/%s socket exists and is accessible.\n", 94 | runtime_dir, 95 | display 96 | ); 97 | } 98 | exit(1); 99 | } 100 | 101 | void complain_about_missing_seat(const char *seat_name) { 102 | if (seat_name != NULL) { 103 | fprintf(stderr, "No such seat: %s\n", seat_name); 104 | } else { 105 | complain_about_missing_global("seat"); 106 | } 107 | exit(1); 108 | } 109 | 110 | void complain_about_missing_global(const char *global) { 111 | fprintf( 112 | stderr, 113 | "The compositor does not seem to implement %s," 114 | " which is required for wl-clipboard to work\n", 115 | global 116 | ); 117 | exit(1); 118 | } 119 | 120 | void complain_about_missing_shell() { 121 | #ifndef HAVE_XDG_SHELL 122 | fprintf( 123 | stderr, 124 | "wl-clipboard was built without xdg-shell support, and the compositor" 125 | " does not seem to support any other Wayland shell.\nPlease rebuild" 126 | " wl-clipboard with xdg-shell support.\n" 127 | ); 128 | exit(1); 129 | #endif 130 | 131 | 132 | fprintf( 133 | stderr, 134 | "The compositor does not seem to implement a Wayland shell," 135 | " which is required for wl-clipboard to work\n" 136 | ); 137 | 138 | exit(1); 139 | } 140 | -------------------------------------------------------------------------------- /src/util/misc.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UTIL_MISC_H 20 | #define UTIL_MISC_H 21 | 22 | #include 23 | #include 24 | 25 | #define bail(message) do { fprintf(stderr, message "\n"); exit(1); } while (0) 26 | 27 | void print_version_info(void); 28 | 29 | void complain_about_selection_support(int primary); 30 | void complain_about_watch_mode_support(void); 31 | void complain_about_wayland_connection(void); 32 | void complain_about_missing_seat(const char *seat_name); 33 | void complain_about_missing_global(const char *global); 34 | void complain_about_missing_shell(void); 35 | 36 | #endif /* UTIL_MISC_H */ 37 | -------------------------------------------------------------------------------- /src/util/string.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "util/string.h" 20 | 21 | #include 22 | #include 23 | 24 | int mime_type_is_text(const char *mime_type) { 25 | /* A heuristic to detect plain text mime types */ 26 | 27 | /* Types that explicitly declare they're textual */ 28 | int basic 29 | = str_has_prefix(mime_type, "text/") 30 | || strcmp(mime_type, "TEXT") == 0 31 | || strcmp(mime_type, "STRING") == 0 32 | || strcmp(mime_type, "UTF8_STRING") == 0; 33 | 34 | /* Common script and markup types */ 35 | int common 36 | = strstr(mime_type, "json") != NULL 37 | || str_has_suffix(mime_type, "script") 38 | || str_has_suffix(mime_type, "xml") 39 | || str_has_suffix(mime_type, "yaml") 40 | || str_has_suffix(mime_type, "csv") 41 | || str_has_suffix(mime_type, "ini"); 42 | 43 | /* Special-case PGP and SSH keys. 44 | * A public SSH key is typically stored 45 | * in a file that has a name similar to 46 | * id_rsa.pub, which xdg-mime misidentifies 47 | * as being a Publisher file. Note that it 48 | * handles private keys, which do not have 49 | * a .pub extension, correctly. 50 | */ 51 | int special 52 | = strstr(mime_type, "application/vnd.ms-publisher") != NULL 53 | || str_has_suffix(mime_type, "pgp-keys"); 54 | 55 | return basic || common || special; 56 | } 57 | 58 | int str_has_prefix(const char *string, const char *prefix) { 59 | size_t prefix_length = strlen(prefix); 60 | return strncmp(string, prefix, prefix_length) == 0; 61 | } 62 | 63 | int str_has_suffix(const char *string, const char *suffix) { 64 | size_t string_length = strlen(string); 65 | size_t suffix_length = strlen(suffix); 66 | if (string_length < suffix_length) { 67 | return 0; 68 | } 69 | size_t offset = string_length - suffix_length; 70 | return strcmp(string + offset, suffix) == 0; 71 | } 72 | 73 | const char *get_file_extension(const char *file_path) { 74 | const char *name = strrchr(file_path, '/'); 75 | if (name == NULL) { 76 | name = file_path; 77 | } 78 | const char *ext = strrchr(name, '.'); 79 | if (ext == NULL) { 80 | return NULL; 81 | } 82 | return ext + 1; 83 | } 84 | -------------------------------------------------------------------------------- /src/util/string.h: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UTIL_STRING_H 20 | #define UTIL_STRING_H 21 | 22 | #define text_plain "text/plain" 23 | #define text_plain_utf8 "text/plain;charset=utf-8" 24 | #define x_kde_password_manager_hint "x-kde-passwordManagerHint" 25 | 26 | typedef char * const *argv_t; 27 | 28 | int mime_type_is_text(const char *mime_type); 29 | 30 | int str_has_prefix(const char *string, const char *prefix); 31 | int str_has_suffix(const char *string, const char *suffix); 32 | 33 | const char *get_file_extension(const char *file_path); 34 | 35 | #endif /* UTIL_STRING_H */ 36 | -------------------------------------------------------------------------------- /src/wl-copy.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/copy-action.h" 20 | #include "types/source.h" 21 | #include "types/device.h" 22 | #include "types/device-manager.h" 23 | #include "types/registry.h" 24 | #include "types/popup-surface.h" 25 | #include "types/offer.h" 26 | 27 | #include "util/files.h" 28 | #include "util/string.h" 29 | #include "util/misc.h" 30 | 31 | #include 32 | #include 33 | #include 34 | #include // open 35 | #include 36 | #include 37 | #include 38 | 39 | static struct { 40 | int stay_in_foreground; 41 | int clear; 42 | char *mime_type; 43 | int trim_newline; 44 | int paste_once; 45 | int primary; 46 | int sensitive; 47 | const char *seat_name; 48 | } options; 49 | 50 | static void did_set_selection_callback(struct copy_action *copy_action) { 51 | if (options.clear) { 52 | exit(0); 53 | } 54 | 55 | if (!options.stay_in_foreground) { 56 | /* Move to background. 57 | * We fork our process and leave the 58 | * child running in the background, 59 | * while exiting in the parent. We also 60 | * replace stdin/stdout with /dev/null 61 | * so the stdout file descriptor isn't 62 | * kept alive, and chdir to the root, to 63 | * prevent blocking file systems from 64 | * being unmounted. 65 | */ 66 | int devnull = open("/dev/null", O_RDWR); 67 | if (devnull >= 0) { 68 | dup2(devnull, STDOUT_FILENO); 69 | dup2(devnull, STDIN_FILENO); 70 | close(devnull); 71 | } else { 72 | /* If we cannot open /dev/null, 73 | * just close stdin/stdout. 74 | */ 75 | close(STDIN_FILENO); 76 | close(STDOUT_FILENO); 77 | } 78 | if (chdir("/") < 0) { 79 | perror("chdir /"); 80 | } 81 | signal(SIGHUP, SIG_IGN); 82 | pid_t pid = fork(); 83 | if (pid < 0) { 84 | perror("fork"); 85 | /* Proceed without forking */ 86 | } 87 | if (pid > 0) { 88 | exit(0); 89 | } 90 | } 91 | } 92 | 93 | static void cancelled_callback(struct copy_action *copy_action) { 94 | exit(0); 95 | } 96 | 97 | static void pasted_callback(struct copy_action *copy_action) { 98 | if (options.paste_once) { 99 | exit(0); 100 | } 101 | } 102 | 103 | static void selection_callback(struct offer *offer, int primary) { 104 | /* We're not interested */ 105 | if (offer != NULL) { 106 | offer_destroy(offer); 107 | } 108 | } 109 | 110 | static void print_usage(FILE *f, const char *argv0) { 111 | fprintf( 112 | f, 113 | "Usage:\n" 114 | "\t%s [options] text to copy\n" 115 | "\t%s [options] < file-to-copy\n\n" 116 | "Copy content to the Wayland clipboard.\n\n" 117 | "Options:\n" 118 | "\t-o, --paste-once\tOnly serve one paste request and then exit.\n" 119 | "\t-f, --foreground\tStay in the foreground instead of forking.\n" 120 | "\t-c, --clear\t\tInstead of copying, clear the clipboard.\n" 121 | "\t-p, --primary\t\tUse the \"primary\" clipboard.\n" 122 | "\t-n, --trim-newline\tDo not copy the trailing newline character.\n" 123 | "\t-t, --type mime/type\t" 124 | "Override the inferred MIME type for the content.\n" 125 | "\t --sensitive\t\tHint that the content is sensitive.\n" 126 | "\t-s, --seat seat-name\t" 127 | "Pick the seat to work with.\n" 128 | "\t-v, --version\t\tDisplay version info.\n" 129 | "\t-h, --help\t\tDisplay this message.\n" 130 | "Mandatory arguments to long options are mandatory" 131 | " for short options too.\n\n" 132 | "See wl-clipboard(1) for more details.\n", 133 | argv0, 134 | argv0 135 | ); 136 | } 137 | 138 | static void parse_options(int argc, argv_t argv) { 139 | if (argc < 1) { 140 | bail("Empty argv"); 141 | } 142 | 143 | static struct option long_options[] = { 144 | {"version", no_argument, 0, 'v'}, 145 | {"help", no_argument, 0, 'h'}, 146 | {"primary", no_argument, 0, 'p'}, 147 | {"trim-newline", no_argument, 0, 'n'}, 148 | {"paste-once", no_argument, 0, 'o'}, 149 | {"foreground", no_argument, 0, 'f'}, 150 | {"clear", no_argument, 0, 'c'}, 151 | {"type", required_argument, 0, 't'}, 152 | {"sensitive", no_argument, 0, 'S'}, 153 | {"seat", required_argument, 0, 's'}, 154 | {0, 0, 0, 0} 155 | }; 156 | while (1) { 157 | int option_index; 158 | const char *opts = "vhpnofct:s:"; 159 | int c = getopt_long(argc, argv, opts, long_options, &option_index); 160 | if (c == -1) { 161 | break; 162 | } 163 | if (c == 0) { 164 | c = long_options[option_index].val; 165 | } 166 | switch (c) { 167 | case 'v': 168 | print_version_info(); 169 | exit(0); 170 | case 'h': 171 | print_usage(stdout, argv[0]); 172 | exit(0); 173 | case 'p': 174 | options.primary = 1; 175 | break; 176 | case 'n': 177 | options.trim_newline = 1; 178 | break; 179 | case 'o': 180 | options.paste_once = 1; 181 | break; 182 | case 'f': 183 | options.stay_in_foreground = 1; 184 | break; 185 | case 'c': 186 | options.clear = 1; 187 | break; 188 | case 't': 189 | options.mime_type = strdup(optarg); 190 | break; 191 | case 'S': 192 | options.sensitive = 1; 193 | break; 194 | case 's': 195 | options.seat_name = strdup(optarg); 196 | break; 197 | default: 198 | /* getopt has already printed an error message */ 199 | print_usage(stderr, argv[0]); 200 | exit(1); 201 | } 202 | } 203 | } 204 | 205 | int main(int argc, argv_t argv) { 206 | parse_options(argc, argv); 207 | 208 | /* Ignore SIGPIPE. 209 | * We don't really output anything 210 | * to our stdout, yet we don't want 211 | * to get killed when writing clipboard 212 | * contents to a closed pipe. 213 | */ 214 | signal(SIGPIPE, SIG_IGN); 215 | 216 | struct wl_display *wl_display = wl_display_connect(NULL); 217 | if (wl_display == NULL) { 218 | complain_about_wayland_connection(); 219 | } 220 | if (wl_display_get_fd(wl_display) <= STDERR_FILENO) { 221 | complain_about_closed_stdio(wl_display); 222 | } 223 | 224 | struct registry *registry = calloc(1, sizeof(struct registry)); 225 | registry->wl_display = wl_display; 226 | registry_init(registry); 227 | 228 | /* Wait for the initial set of globals to appear */ 229 | wl_display_roundtrip(wl_display); 230 | 231 | struct seat *seat = registry_find_seat(registry, options.seat_name); 232 | if (seat == NULL) { 233 | complain_about_missing_seat(options.seat_name); 234 | } 235 | 236 | /* Create the device */ 237 | struct device_manager *device_manager 238 | = registry_find_device_manager(registry, options.primary); 239 | if (device_manager == NULL) { 240 | complain_about_selection_support(options.primary); 241 | } 242 | 243 | struct device *device = device_manager_get_device(device_manager, seat); 244 | device->selection_callback = selection_callback; 245 | 246 | if (!device_supports_selection(device, options.primary)) { 247 | complain_about_selection_support(options.primary); 248 | } 249 | 250 | /* Create and initialize the copy action */ 251 | struct copy_action *copy_action = calloc(1, sizeof(struct copy_action)); 252 | copy_action->fd_to_copy_from = -1; 253 | copy_action->device = device; 254 | copy_action->primary = options.primary; 255 | copy_action->sensitive = options.sensitive; 256 | 257 | if (!options.clear) { 258 | if (optind < argc) { 259 | /* Copy our command-line arguments */ 260 | copy_action->argv_to_copy = &argv[optind]; 261 | } else { 262 | /* Copy data from our stdin. 263 | * It's important that we only do this 264 | * after going through the initial stages 265 | * that are likely to result in errors, 266 | * so that we don't forget to clean up 267 | * the temp file. 268 | */ 269 | char *temp_file = dump_stdin_into_a_temp_file(); 270 | if (options.trim_newline) { 271 | trim_trailing_newline(temp_file); 272 | } 273 | if (options.mime_type == NULL) { 274 | options.mime_type = infer_mime_type_from_contents(temp_file); 275 | } 276 | copy_action->fd_to_copy_from = open( 277 | temp_file, 278 | O_RDONLY | O_CLOEXEC 279 | ); 280 | if (copy_action->fd_to_copy_from < 0) { 281 | perror("Failed to open temp file"); 282 | return 1; 283 | } 284 | /* Now, remove the temp file and its 285 | * containing directory. We still keep 286 | * access to the file through our open 287 | * file descriptor. 288 | */ 289 | int rc = unlink(temp_file); 290 | if (rc < 0) { 291 | perror("Failed to unlink temp file"); 292 | } 293 | rc = rmdir(dirname(temp_file)); 294 | if (rc < 0) { 295 | perror("Failed to remove temp file directory"); 296 | } 297 | free(temp_file); 298 | } 299 | 300 | /* Create the source */ 301 | copy_action->source = device_manager_create_source(device_manager); 302 | if (options.mime_type != NULL) { 303 | source_offer(copy_action->source, options.mime_type); 304 | } 305 | if (options.mime_type == NULL || mime_type_is_text(options.mime_type)) { 306 | /* Offer a few generic plain text formats */ 307 | source_offer(copy_action->source, text_plain); 308 | source_offer(copy_action->source, text_plain_utf8); 309 | source_offer(copy_action->source, "TEXT"); 310 | source_offer(copy_action->source, "STRING"); 311 | source_offer(copy_action->source, "UTF8_STRING"); 312 | } 313 | free(options.mime_type); 314 | options.mime_type = NULL; 315 | if (options.sensitive) { 316 | source_offer(copy_action->source, x_kde_password_manager_hint); 317 | } 318 | } 319 | 320 | if (device->needs_popup_surface) { 321 | copy_action->popup_surface = calloc(1, sizeof(struct popup_surface)); 322 | copy_action->popup_surface->registry = registry; 323 | copy_action->popup_surface->seat = seat; 324 | } 325 | 326 | copy_action->did_set_selection_callback = did_set_selection_callback; 327 | copy_action->pasted_callback = pasted_callback; 328 | copy_action->cancelled_callback = cancelled_callback; 329 | copy_action_init(copy_action); 330 | 331 | while (wl_display_dispatch(wl_display) >= 0); 332 | 333 | perror("wl_display_dispatch"); 334 | return 1; 335 | } 336 | -------------------------------------------------------------------------------- /src/wl-paste.c: -------------------------------------------------------------------------------- 1 | /* wl-clipboard 2 | * 3 | * Copyright © 2018-2025 Sergey Bugaev 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include "types/offer.h" 20 | #include "types/device.h" 21 | #include "types/device-manager.h" 22 | #include "types/registry.h" 23 | #include "types/popup-surface.h" 24 | 25 | #include "util/files.h" 26 | #include "util/string.h" 27 | #include "util/misc.h" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | static struct { 41 | char *explicit_type; 42 | char *inferred_type; 43 | int no_newline; 44 | int list_types; 45 | int primary; 46 | int watch; 47 | argv_t watch_command; 48 | const char *seat_name; 49 | } options; 50 | 51 | struct types { 52 | int explicit_available; 53 | int inferred_available; 54 | int plain_text_utf8_available; 55 | int plain_text_available; 56 | int has_sensitive_hint; 57 | const char *having_explicit_as_prefix; 58 | const char *any_text; 59 | const char *any; 60 | }; 61 | 62 | static struct wl_display *wl_display = NULL; 63 | static struct popup_surface *popup_surface = NULL; 64 | static int offer_received = 0; 65 | 66 | static struct types classify_offer_types(struct offer *offer) { 67 | struct types types = { 0 }; 68 | offer_for_each_mime_type(offer, mime_type) { 69 | if ( 70 | options.explicit_type != NULL && 71 | strcmp(mime_type, options.explicit_type) == 0 72 | ) { 73 | types.explicit_available = 1; 74 | } 75 | if ( 76 | options.inferred_type != NULL && 77 | strcmp(mime_type, options.inferred_type) == 0 78 | ) { 79 | types.inferred_available = 1; 80 | } 81 | if (strcmp(mime_type, text_plain_utf8) == 0) { 82 | types.plain_text_utf8_available = 1; 83 | } 84 | if (strcmp(mime_type, text_plain) == 0) { 85 | types.plain_text_available = 1; 86 | } 87 | if ( 88 | types.any_text == NULL && 89 | mime_type_is_text(mime_type) 90 | ) { 91 | types.any_text = mime_type; 92 | } 93 | if (types.any == NULL) { 94 | types.any = mime_type; 95 | } 96 | if ( 97 | options.explicit_type != NULL && 98 | types.having_explicit_as_prefix == NULL && 99 | str_has_prefix(mime_type, options.explicit_type) 100 | ) { 101 | types.having_explicit_as_prefix = mime_type; 102 | } 103 | if (strcmp(mime_type, x_kde_password_manager_hint) == 0) { 104 | /* We should be checking if it contains 105 | * the string "secret" as opposed to "public", 106 | * but for now let's just use the presence 107 | * of the type as an indication. 108 | */ 109 | types.has_sensitive_hint = 1; 110 | } 111 | } 112 | return types; 113 | } 114 | 115 | #define try_explicit \ 116 | if (types.explicit_available) \ 117 | return options.explicit_type 118 | 119 | #define try_inferred \ 120 | if (types.inferred_available) \ 121 | return options.inferred_type 122 | 123 | #define try_text_plain_utf8 \ 124 | if (types.plain_text_utf8_available) \ 125 | return text_plain_utf8 126 | 127 | #define try_text_plain \ 128 | if (types.plain_text_available) \ 129 | return text_plain 130 | 131 | #define try_prefixed \ 132 | if (types.having_explicit_as_prefix != NULL) \ 133 | return types.having_explicit_as_prefix 134 | 135 | #define try_any_text \ 136 | if (types.any_text != NULL) \ 137 | return types.any_text 138 | 139 | #define try_any \ 140 | if (types.any != NULL) \ 141 | return types.any 142 | 143 | static const char *mime_type_to_request(struct types types) { 144 | if (options.explicit_type != NULL) { 145 | if (strcmp(options.explicit_type, "text") == 0) { 146 | try_text_plain_utf8; 147 | try_text_plain; 148 | try_any_text; 149 | } else if (strchr(options.explicit_type, '/') != NULL) { 150 | try_explicit; 151 | } else if (isupper(options.explicit_type[0])) { 152 | try_explicit; 153 | } else { 154 | try_explicit; 155 | try_prefixed; 156 | } 157 | } else { 158 | /* No mime type requested explicitly, 159 | * so try to guess. 160 | */ 161 | if (options.inferred_type == NULL) { 162 | try_text_plain_utf8; 163 | try_text_plain; 164 | try_any_text; 165 | try_any; 166 | } else if (mime_type_is_text(options.inferred_type)) { 167 | try_inferred; 168 | try_text_plain_utf8; 169 | try_text_plain; 170 | try_any_text; 171 | } else { 172 | try_inferred; 173 | } 174 | } 175 | return NULL; 176 | } 177 | 178 | #undef try_explicit 179 | #undef try_inferred 180 | #undef try_text_plain_utf8 181 | #undef try_text_plain 182 | #undef try_prefixed 183 | #undef try_any_text 184 | #undef try_any 185 | 186 | static int run_paste_command(int stdin_fd, const char *clipboard_state) { 187 | /* Spawn a cat to perform the copy. 188 | * If watch mode is active, we spawn 189 | * a custom command instead. 190 | */ 191 | pid_t pid = fork(); 192 | if (pid < 0) { 193 | perror("fork"); 194 | close(stdin_fd); 195 | return 0; 196 | } 197 | if (pid == 0) { 198 | dup2(stdin_fd, STDIN_FILENO); 199 | close(stdin_fd); 200 | if (options.watch) { 201 | if (clipboard_state != NULL) { 202 | setenv("CLIPBOARD_STATE", clipboard_state, 1); 203 | } 204 | execvp(options.watch_command[0], options.watch_command); 205 | fprintf( 206 | stderr, 207 | "Failed to spawn %s: %s", 208 | options.watch_command[0], 209 | strerror(errno) 210 | ); 211 | } else { 212 | execlp("cat", "cat", NULL); 213 | perror("exec cat"); 214 | } 215 | exit(1); 216 | } 217 | close(stdin_fd); 218 | waitpid(pid, NULL, 0); 219 | return 1; 220 | } 221 | 222 | static void complain_no_suitable_type(const struct types *types) { 223 | if (types->any == NULL) { 224 | /* Report this the same way as 225 | * there being no offer at all. 226 | */ 227 | bail("Nothing is copied"); 228 | } 229 | fprintf(stderr, "Clipboard content is not available as "); 230 | if (options.explicit_type != NULL) { 231 | fprintf(stderr, "requested type \"%s\"\n", options.explicit_type); 232 | } else { 233 | assert(options.inferred_type); 234 | fprintf( 235 | stderr, 236 | "inferred output type \"%s\"\n", 237 | options.inferred_type 238 | ); 239 | } 240 | fprintf(stderr, "Use \"wl-paste --list-types\" to view available types."); 241 | if (options.explicit_type == NULL) { 242 | fprintf(stderr, " Use \"--type\" to explicitly specify a type."); 243 | } 244 | fputc('\n', stderr); 245 | exit(1); 246 | } 247 | 248 | static void selection_callback(struct offer *offer, int primary) { 249 | /* Ignore all but the first non-NULL offer. 250 | * This could happen due to reentrancy, though 251 | * we try to prevent it in other ways. 252 | */ 253 | if (offer_received && !options.watch) { 254 | return; 255 | } 256 | 257 | /* Ignore events we're not interested in */ 258 | if (primary != options.primary) { 259 | if (offer != NULL) { 260 | offer_destroy(offer); 261 | }; 262 | return; 263 | } 264 | 265 | if (offer == NULL) { 266 | if (!options.watch) { 267 | bail("Nothing is copied"); 268 | } 269 | int devnull = open("/dev/null", O_RDONLY | O_CLOEXEC); 270 | if (devnull < 0) { 271 | perror("open /dev/null"); 272 | return; 273 | } 274 | run_paste_command(devnull, "nil"); 275 | return; 276 | } 277 | 278 | offer_received = 1; 279 | 280 | if (options.list_types) { 281 | offer_for_each_mime_type(offer, mime_type) { 282 | printf("%s\n", mime_type); 283 | } 284 | exit(0); 285 | } 286 | 287 | struct types types = classify_offer_types(offer); 288 | const char *mime_type = mime_type_to_request(types); 289 | 290 | if (mime_type == NULL) { 291 | if (options.watch) { 292 | offer_destroy(offer); 293 | return; 294 | } 295 | complain_no_suitable_type(&types); 296 | } 297 | 298 | /* Never append a newline character to binary content */ 299 | if (!mime_type_is_text(mime_type)) { 300 | options.no_newline = 1; 301 | } 302 | 303 | /* Create a pipe which we'll 304 | * use to receive the data. 305 | */ 306 | int pipefd[2]; 307 | int rc = pipe(pipefd); 308 | if (rc < 0) { 309 | perror("pipe"); 310 | offer_destroy(offer); 311 | if (options.watch) { 312 | return; 313 | } 314 | exit(1); 315 | } 316 | 317 | offer_receive(offer, mime_type, pipefd[1]); 318 | 319 | if (popup_surface != NULL) { 320 | popup_surface_destroy(popup_surface); 321 | popup_surface = NULL; 322 | } 323 | /* Make sure the receive request reaches 324 | * the compositor before we block on reading. 325 | * We call flush() instead of dispatch() to 326 | * prevent reentrancy. 327 | */ 328 | wl_display_flush(wl_display); 329 | 330 | close(pipefd[1]); 331 | const char *clipboard_state = "data"; 332 | if (types.has_sensitive_hint) { 333 | clipboard_state = "sensitive"; 334 | } 335 | rc = run_paste_command(pipefd[0], clipboard_state); 336 | if (!rc) { 337 | if (options.watch) { 338 | /* Try to cope without exiting completely */ 339 | offer_destroy(offer); 340 | return; 341 | } 342 | exit(1); 343 | } 344 | 345 | if (!options.no_newline && !options.watch) { 346 | rc = write(STDOUT_FILENO, "\n", 1); 347 | if (rc != 1) { 348 | perror("write"); 349 | } 350 | } 351 | 352 | offer_destroy(offer); 353 | 354 | if (!options.watch) { 355 | free(options.explicit_type); 356 | free(options.inferred_type); 357 | exit(0); 358 | } 359 | } 360 | 361 | static void print_usage(FILE *f, const char *argv0) { 362 | fprintf( 363 | f, 364 | "Usage:\n" 365 | "\t%s [options]\n" 366 | "Paste content from the Wayland clipboard.\n\n" 367 | "Options:\n" 368 | "\t-n, --no-newline\tDo not append a newline character.\n" 369 | "\t-l, --list-types\tInstead of pasting, list the offered types.\n" 370 | "\t-p, --primary\t\tUse the \"primary\" clipboard.\n" 371 | "\t-w, --watch command\t" 372 | "Run a command each time the selection changes.\n" 373 | "\t-t, --type mime/type\t" 374 | "Override the inferred MIME type for the content.\n" 375 | "\t-s, --seat seat-name\t" 376 | "Pick the seat to work with.\n" 377 | "\t-v, --version\t\tDisplay version info.\n" 378 | "\t-h, --help\t\tDisplay this message.\n" 379 | "Mandatory arguments to long options are mandatory" 380 | " for short options too.\n\n" 381 | "See wl-clipboard(1) for more details.\n", 382 | argv0 383 | ); 384 | } 385 | 386 | static void parse_options(int argc, argv_t argv) { 387 | if (argc < 1) { 388 | bail("Empty argv"); 389 | } 390 | 391 | static struct option long_options[] = { 392 | {"version", no_argument, 0, 'v'}, 393 | {"help", no_argument, 0, 'h'}, 394 | {"primary", no_argument, 0, 'p'}, 395 | {"no-newline", no_argument, 0, 'n'}, 396 | {"list-types", no_argument, 0, 'l'}, 397 | {"watch", required_argument, 0, 'w'}, 398 | {"type", required_argument, 0, 't'}, 399 | {"seat", required_argument, 0, 's'}, 400 | {0, 0, 0, 0} 401 | }; 402 | while (1) { 403 | int option_index; 404 | const char *opts = "vhpnlw:t:s:"; 405 | int c = getopt_long(argc, argv, opts, long_options, &option_index); 406 | if (c == -1) { 407 | break; 408 | } 409 | if (c == 0) { 410 | c = long_options[option_index].val; 411 | } 412 | switch (c) { 413 | case 'v': 414 | print_version_info(); 415 | exit(0); 416 | case 'h': 417 | print_usage(stdout, argv[0]); 418 | exit(0); 419 | case 'p': 420 | options.primary = 1; 421 | break; 422 | case 'n': 423 | options.no_newline = 1; 424 | break; 425 | case 'l': 426 | options.list_types = 1; 427 | break; 428 | case 'w': 429 | options.watch = 1; 430 | /* We tell getopt that --watch requires an argument, 431 | * but it's more nuanced than that. We actually take 432 | * that argument and everything that follows it as a 433 | * subcommand to spawn. When we get here, getopt sets 434 | * the optind variable to point to the *next* option 435 | * to be processed, after both the current option and 436 | * its argument, if any. So if we're invoked like this: 437 | * $ wl-paste --primary --watch foo bar baz 438 | * then optind will point to bar, as it considers foo 439 | * to be the argument that --watch requires. However, 440 | * if we get invoked like this: 441 | * $ wl-paste --watch=foo bar baz 442 | * or like this: 443 | * $ wl-paste -wfoo bar baz 444 | * then getopt will not consider it an error, and optind 445 | * will also point to bar. We do consider that an error, 446 | * so detect this case and print an error message. 447 | */ 448 | options.watch_command = (argv_t) &argv[optind - 1]; 449 | if (options.watch_command[0][0] == '-') { 450 | fprintf( 451 | stderr, 452 | "Expected a subcommand instead of an argument" 453 | " after --watch\n" 454 | ); 455 | print_usage(stderr, argv[0]); 456 | exit(1); 457 | } 458 | /* We're going to forward the rest of our 459 | * arguments to the command we spawn, so stop 460 | * trying to process further options. 461 | */ 462 | return; 463 | case 't': 464 | options.explicit_type = strdup(optarg); 465 | break; 466 | case 's': 467 | options.seat_name = strdup(optarg); 468 | break; 469 | default: 470 | /* getopt has already printed an error message */ 471 | print_usage(stderr, argv[0]); 472 | exit(1); 473 | } 474 | } 475 | 476 | if (optind != argc) { 477 | fprintf(stderr, "Unexpected argument: %s\n", argv[optind]); 478 | print_usage(stderr, argv[0]); 479 | exit(1); 480 | } 481 | } 482 | 483 | int main(int argc, argv_t argv) { 484 | parse_options(argc, argv); 485 | 486 | char *path = path_for_fd(STDOUT_FILENO); 487 | if (path != NULL && options.explicit_type == NULL) { 488 | options.inferred_type = infer_mime_type_from_name(path); 489 | } 490 | free(path); 491 | 492 | wl_display = wl_display_connect(NULL); 493 | if (wl_display == NULL) { 494 | complain_about_wayland_connection(); 495 | } 496 | if (wl_display_get_fd(wl_display) <= STDERR_FILENO) { 497 | complain_about_closed_stdio(wl_display); 498 | } 499 | 500 | struct registry *registry = calloc(1, sizeof(struct registry)); 501 | registry->wl_display = wl_display; 502 | registry_init(registry); 503 | 504 | /* Wait for the initial set of globals to appear */ 505 | wl_display_roundtrip(wl_display); 506 | 507 | struct seat *seat = registry_find_seat(registry, options.seat_name); 508 | if (seat == NULL) { 509 | complain_about_missing_seat(options.seat_name); 510 | } 511 | 512 | /* Create the device */ 513 | struct device_manager *device_manager 514 | = registry_find_device_manager(registry, options.primary); 515 | if (device_manager == NULL) { 516 | complain_about_selection_support(options.primary); 517 | } 518 | 519 | struct device *device = device_manager_get_device(device_manager, seat); 520 | /* Set up the callback before checking whether the device 521 | * actually supports the kind of selection we need, because 522 | * checking for the support might roundtrip. 523 | */ 524 | device->selection_callback = selection_callback; 525 | 526 | if (!device_supports_selection(device, options.primary)) { 527 | complain_about_selection_support(options.primary); 528 | } 529 | 530 | if (device->needs_popup_surface) { 531 | if (options.watch) { 532 | complain_about_watch_mode_support(); 533 | } 534 | /* If we cannot get the selection directly, pop up 535 | * a surface. When it gets focus, we'll immediately 536 | * get the selection events, se we don't need to do 537 | * anything special on the surface getting focus. 538 | */ 539 | popup_surface = calloc(1, sizeof(struct popup_surface)); 540 | popup_surface->registry = registry; 541 | popup_surface->seat = seat; 542 | popup_surface_init(popup_surface); 543 | } 544 | 545 | while (wl_display_dispatch(wl_display) >= 0); 546 | 547 | perror("wl_display_dispatch"); 548 | return 1; 549 | } 550 | -------------------------------------------------------------------------------- /subprojects/expat.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = expat-2.5.0 3 | source_url = https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.xz 4 | source_filename = expat-2.5.0.tar.bz2 5 | source_hash = ef2420f0232c087801abf705e89ae65f6257df6b7931d37846a193ef2e8cdcbe 6 | patch_filename = expat_2.5.0-3_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.5.0-3/get_patch 8 | patch_hash = e30c8c32f79fd4563a86a5c9cfe419568c35b775077c2d1428170081129406b4 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/expat_2.5.0-3/expat-2.5.0.tar.bz2 10 | wrapdb_version = 2.5.0-3 11 | 12 | [provide] 13 | expat = expat_dep 14 | -------------------------------------------------------------------------------- /subprojects/libffi.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = libffi-3.4.4 3 | source_url = https://github.com/libffi/libffi/releases/download/v3.4.4/libffi-3.4.4.tar.gz 4 | source_filename = libffi-3.4.4.tar.gz 5 | source_hash = d66c56ad259a82cf2a9dfc408b32bf5da52371500b84745f7fb8b645712df676 6 | patch_filename = libffi_3.4.4-2_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/libffi_3.4.4-2/get_patch 8 | patch_hash = 77da71839584dfc33d998b6461fff1ebc90a6f9c0a8c2f490335aafe370a115b 9 | wrapdb_version = 3.4.4-2 10 | 11 | [provide] 12 | dependency_names = libffi 13 | -------------------------------------------------------------------------------- /subprojects/wayland-protocols.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = wayland-protocols-1.24 3 | source_url = https://wayland.freedesktop.org/releases/wayland-protocols-1.24.tar.xz 4 | source_filename = wayland-protocols-1.24.tar.xz 5 | source_hash = bff0d8cffeeceb35159d6f4aa6bab18c807b80642c9d50f66cba52ecf7338bc2 6 | 7 | [provide] 8 | wayland-protocols = wayland_protocols 9 | -------------------------------------------------------------------------------- /subprojects/wayland.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = wayland-1.22.0 3 | source_url = https://gitlab.freedesktop.org/wayland/wayland/-/releases/1.22.0/downloads/wayland-1.22.0.tar.xz 4 | source_filename = wayland-1.22.0.tar.xz 5 | source_hash = 1540af1ea698a471c2d8e9d288332c7e0fd360c8f1d12936ebb7e7cbc2425842 6 | 7 | [provide] 8 | dependency_names = wayland-client, wayland-server, wayland-cursor, wayland-egl 9 | program_names = wayland-scanner 10 | --------------------------------------------------------------------------------