From d8fe4dae2f4243770e1b8221b0e896739f466e09 Mon Sep 17 00:00:00 2001 From: Michael Sorokin Date: Tue, 12 Feb 2019 18:30:42 +0300 Subject: [PATCH 001/139] Add Bash completion --- completions/bash/meson.build | 4 ++ completions/bash/wl-copy | 42 +++++++++++++++++++++ completions/bash/wl-paste | 72 ++++++++++++++++++++++++++++++++++++ completions/meson.build | 1 + meson.build | 1 + 5 files changed, 120 insertions(+) create mode 100644 completions/bash/meson.build create mode 100644 completions/bash/wl-copy create mode 100644 completions/bash/wl-paste create mode 100644 completions/meson.build diff --git a/completions/bash/meson.build b/completions/bash/meson.build new file mode 100644 index 0000000..b618d4e --- /dev/null +++ b/completions/bash/meson.build @@ -0,0 +1,4 @@ +bash_completion_dir = join_paths(get_option('datadir'), 'bash-completion', 'completions') + +install_data('wl-copy', install_dir: bash_completion_dir) +install_data('wl-paste', install_dir: bash_completion_dir) diff --git a/completions/bash/wl-copy b/completions/bash/wl-copy new file mode 100644 index 0000000..7561d44 --- /dev/null +++ b/completions/bash/wl-copy @@ -0,0 +1,42 @@ +# wl-copy(1) completion + +_wl_clipboard_list_seats() { + weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}' +} + +_wl_clipboard_complete_types() { + local cur prev types + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + types="$1" + if [ "${cur:0:1}" = \' -o "${cur:0:1}" = \" ]; then + COMPREPLY=($(compgen -W "$types" -- "$cur")) + else + COMPREPLY=($(compgen -W "$types" -- "$cur" | sed 's/;/\\;/g')) + fi +} + +_wl_copy_completion() { + compopt +o default + local cur prev opts types seats + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="-o --paste-once -f --foreground -c --clear -p --primary -n --trim-newline -t --type -s --seat -v --version -h --help" + if [ "$prev" = "<" ]; then + compopt -o default + COMPREPLY=() + elif [ \( "x${prev:0:1}" = "x-" -a "x${prev:1:2}" != "x-" -a "${prev: -1}" = "t" \) -o "$prev" = "--type" ]; then + types="$(sed 's/^\(\S\+\).*/\1/' /etc/mime.types 2>/dev/null)" + _wl_clipboard_complete_types "$types" + elif [ \( "x${prev:0:1}" = "x-" -a "x${prev:1:2}" != "x-" -a "${prev: -1}" = "s" \) -o "$prev" = "--seat" ]; then + seats="$(_wl_clipboard_list_seats)" + COMPREPLY=($(compgen -W "$seats" -- "$cur")) + elif [ "${cur:0:1}" = "<" ]; then + compopt -o default + COMPREPLY=() + else + COMPREPLY=($(compgen -W "$opts" -- "$cur")) + fi +} + +complete -o default -F _wl_copy_completion wl-copy diff --git a/completions/bash/wl-paste b/completions/bash/wl-paste new file mode 100644 index 0000000..85eb7bf --- /dev/null +++ b/completions/bash/wl-paste @@ -0,0 +1,72 @@ +# wl-paste(1) completion + +_wl_clipboard_list_seats() { + weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}' +} + +_wl_clipboard_complete_types() { + local cur prev types + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + types="$1" + if [ "${cur:0:1}" = \' -o "${cur:0:1}" = \" ]; then + COMPREPLY=($(compgen -W "$types" -- "$cur")) + else + COMPREPLY=($(compgen -W "$types" -- "$cur" | sed 's/;/\\;/g')) + fi +} + +_wl_clipboard_complete_paste_types() { + local i cur primary seat cmd types + for (( i = 0; i < "${#COMP_WORDS[@]}"; ++i )); do + cur="${COMP_WORDS[i]}" + if [ "x${cur:0:1}" = "x-" -a "x${cur:1:2}" != "x-" ]; then + case "$cur" in + *p*) + primary="yes" + ;; + *s) + seat="${COMP_WORDS[i+1]}" + ;; + esac + elif [ "$cur" = "--primary" ]; then + primary="yes" + elif [ "$cur" = "--seat" ]; then + seat="${COMP_WORDS[i+1]}" + fi + done + cmd="${COMP_WORDS[0]}" + if [ -n "$primary" ]; then + cmd="$cmd -p" + fi + if [ -n "$seat" ]; then + cmd="$cmd -s $seat" + fi + cmd="$cmd -l" + types="$($cmd 2>/dev/null)" + _wl_clipboard_complete_types "$types" +} + +_wl_paste_completion() { + compopt +o default + local cur prev opts types seats + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="-n --no-newline -l --list-types -p --primary -t --type -s --seat -v --version -h --help" + if [ "$prev" = ">" ]; then + compopt -o default + COMPREPLY=() + elif [ \( "x${prev:0:1}" = "x-" -a "x${prev:1:2}" != "x-" -a "${prev: -1}" = "t" \) -o "$prev" = "--type" ]; then + _wl_clipboard_complete_paste_types + elif [ \( "x${prev:0:1}" = "x-" -a "x${prev:1:2}" != "x-" -a "${prev: -1}" = "s" \) -o "$prev" = "--seat" ]; then + seats="$(_wl_clipboard_list_seats)" + COMPREPLY=($(compgen -W "$seats" -- "$cur")) + elif [ "${cur:0:1}" = ">" ]; then + compopt -o default + COMPREPLY=() + else + COMPREPLY=($(compgen -W "$opts" -- "$cur")) + fi +} + +complete -o default -F _wl_paste_completion wl-paste diff --git a/completions/meson.build b/completions/meson.build new file mode 100644 index 0000000..3653b21 --- /dev/null +++ b/completions/meson.build @@ -0,0 +1 @@ +subdir('bash') diff --git a/meson.build b/meson.build index 673e9f5..d2f220a 100644 --- a/meson.build +++ b/meson.build @@ -6,3 +6,4 @@ project('wl-clipboard', 'c', subdir('src') subdir('data') +subdir('completions') From a60fba0fad8399071bd36dbd2fb8fe0ef4cf6f11 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Tue, 12 Feb 2019 18:59:29 +0300 Subject: [PATCH 002/139] Mention a few related projects --- README.md | 5 +++++ data/wl-clipboard.1 | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bb01cd..d2f116c 100644 --- a/README.md +++ b/README.md @@ -94,3 +94,8 @@ Optional dependencies for running: wl-clipboard is free software, available under the GNU General Public License version 3 or later. + +# Related projects + +* [wl-clipboard-x11](https://github.com/brunelli/wl-clipboard-x11): A wrapper to use wl-clipboard as a drop-in replacement to X11 clipboard tools. +* [wl-clipboard-rs](https://github.com/YaLTeR/wl-clipboard-rs): A Rust crate (library) for working with the Wayland clipboard which includes a reimplementation of `wl-copy` and `wl-paste`. diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index b080201..7035aa9 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -139,4 +139,6 @@ License GPLv3+: GNU GPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. .SH SEE ALSO -.BR xclip (1) +.BR xclip (1), +.BR xsel (1), +.BR wl-clipboard-x11 (1) From baea12000deb6124f5f2ec00bdc8e226ce064186 Mon Sep 17 00:00:00 2001 From: maxice8 Date: Mon, 4 Mar 2019 13:41:35 -0300 Subject: [PATCH 003/139] meson: require wayland-scanner from build system. --- src/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meson.build b/src/meson.build index fd1d3e0..9f89c23 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,6 +1,6 @@ wayland = dependency('wayland-client') -wayland_scanner = find_program('wayland-scanner', required: false) +wayland_scanner = find_program('wayland-scanner', required: false, native: true) wayland_protocols = dependency('wayland-protocols', version: '>= 1.12', required: false) if wayland_scanner.found() From 0e6e0e2c00e098a05859c1b53ba97328b00bc5ae Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 15 Apr 2019 17:03:49 +0300 Subject: [PATCH 004/139] Drop the mention of gtk-primary-selection Now that there is a standard protocol for primary selection under Wayland, we no longer need to tell people we're using a private GTK protocol. We still of course support the GTK protocol; but that's not a reason to call it out explicitly in the docs. --- README.md | 2 +- data/wl-clipboard.1 | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d2f116c..925cd02 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ For `wl-paste`: For both: -* `-p`, `--primary` Use the "primary" clipboard instead of the regular clipboard. This uses the private GTK+ primary selection protocol. See [the GNOME Wiki page on primary selection under Wayland](https://wiki.gnome.org/Initiatives/Wayland/PrimarySelection) for more details. +* `-p`, `--primary` Use the "primary" clipboard instead of the regular clipboard. * `-t mime/type`, `--type mime/type` Override the inferred MIME type for the content. For `wl-copy` this option controls which type `wl-copy` will offer the content as. For `wl-paste` it controls which of the offered types `wl-paste` will request the content in. In addition to specific MIME types such as _image/png_, `wl-paste` also accepts generic type names such as _text_ and _image_ which make it automatically pick some offered MIME type that matches the given generic name. * `-s seat-name`, `--seat seat-name` Specify which seat `wl-copy` and `wl-paste` should work with. Wayland natively supports multi-seat configurations where each seat gets its own mouse pointer, keyboard focus, and among other things its own separate clipboard. The name of the default seat is likely _default_ or _seat0_, and additional seat names normally come form `udev(7)` property `ENV{WL_SEAT}`. You can view the list of the currently available seats as advertised by the compositor using the `weston-info(1)` tool. If you don't specify the seat name explicitly, `wl-copy` and `wl-paste` will pick a seat arbitrarily. If you are using a single-seat system, there is little reason to use this option. * `-v`, `--version` Display the version of wl-clipboard and some short info about its license. diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index 7035aa9..2172c66 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -35,12 +35,7 @@ with the \fB--type\fR option. .SH OPTIONS .TP \fB-p\fR, \fB--primary -Use the "primary" clipboard instead of the regular clipboard. This uses the -private GTK+ primary selection protocol. See -.UR https://wiki.gnome.org/Initiatives/Wayland/PrimarySelection -the GNOME Wiki page on primary selection under Wayland -.UE -for more details. +Use the "primary" clipboard instead of the regular clipboard. .TP \fB-o\fR, \fB--paste-once Only serve one paste request and then exit. Unless a clipboard manager From 84640e41e950b8d19980006ac614c7d663e7518f Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 15 Apr 2019 17:06:45 +0300 Subject: [PATCH 005/139] Fix a typo Closes https://github.com/bugaevc/wl-clipboard/issues/42 --- data/wl-clipboard.1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index 2172c66..83013e3 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -1,6 +1,6 @@ -.TH WL-CLIPBOARD 1 2019-01-22 wl-clipboard +.TH WL-CLIPBOARD 1 2019-04-15 wl-clipboard .SH NAME -wl-clipboard \- Wayland copy and paste command line utiltites +wl-clipboard \- Wayland copy and paste command line utilites .SH SYNOPSIS .B wl-copy [\fB--primary\fR] From 94466636a31f4125d84a11e4e1899cbdff5b215b Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 15 Apr 2019 17:49:39 +0300 Subject: [PATCH 006/139] Try all available methods for creating a temp file --- src/boilerplate.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/boilerplate.c b/src/boilerplate.c index 87e4b6d..24d82a4 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -459,11 +459,18 @@ void ensure_has_primary_selection() { } int create_anonymous_file() { + int res; #ifdef HAVE_MEMFD - return syscall(SYS_memfd_create, "buffer", 0); + res = syscall(SYS_memfd_create, "buffer", 0); + if (res >= 0) { + return res; + } #endif #ifdef HAVE_SHM_ANON - return shm_open(SHM_ANON, O_RDWR | O_CREAT, 0600); + res = shm_open(SHM_ANON, O_RDWR | O_CREAT, 0600); + if (res >= 0) { + return res; + } #endif return fileno(tmpfile()); } From 5ebad8f43920b6213db1cf560975d3a92c820733 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 15 Apr 2019 17:54:12 +0300 Subject: [PATCH 007/139] Create the temp file as S_IRUSR | S_IWUSR Closes https://github.com/bugaevc/wl-clipboard/issues/34 Note that the temp file wasn't, in fact, world-readable, since the directory it was created inside already was created with S_IRUSR | S_IWUSR | S_IXUSR. --- src/boilerplate.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/boilerplate.c b/src/boilerplate.c index 24d82a4..3076cc7 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -768,13 +768,13 @@ char *dump_stdin_into_a_temp_file() { } if (fork() == 0) { - FILE *res = fopen(res_path, "w"); - if (res == NULL) { - perror("fopen"); + int fd = creat(res_path, S_IRUSR | S_IWUSR); + if (fd < 0) { + perror("creat"); exit(1); } - dup2(fileno(res), STDOUT_FILENO); - fclose(res); + dup2(fd, STDOUT_FILENO); + close(fd); execlp("cat", "cat", NULL); perror("exec cat"); exit(1); From 088b1961483b8db88fb0ab18c84a4bc3a8df5d84 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 15 Apr 2019 18:03:01 +0300 Subject: [PATCH 008/139] Pass project(license: 'GPL3+') to Meson --- meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/meson.build b/meson.build index d2f220a..3d009ba 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,6 @@ project('wl-clipboard', 'c', version: '1.0.0', + license: 'GPL3+', meson_version: '>= 0.44.0', default_options: 'c_std=gnu99' ) From 444998eb810e057a15835af5a2ec1006762b55fd Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 15 Apr 2019 18:48:06 +0300 Subject: [PATCH 009/139] Bump the version to 1.0.0-git In order not to confuse people; the git version is not the 1.0.0 release; it's closer to the upcoming next release feature-wise. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 3d009ba..0a2ed60 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wl-clipboard', 'c', - version: '1.0.0', + version: '1.0.0-git', license: 'GPL3+', meson_version: '>= 0.44.0', default_options: 'c_std=gnu99' From c010972e6b0d2eb3002c49a6a1b5620ff5f7c910 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 15 Apr 2019 18:53:41 +0300 Subject: [PATCH 010/139] Fix another typo See https://github.com/bugaevc/wl-clipboard/issues/42 --- data/wl-clipboard.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index 83013e3..c3fc327 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -1,6 +1,6 @@ .TH WL-CLIPBOARD 1 2019-04-15 wl-clipboard .SH NAME -wl-clipboard \- Wayland copy and paste command line utilites +wl-clipboard \- Wayland copy and paste command line utilities .SH SYNOPSIS .B wl-copy [\fB--primary\fR] From 92bfe29a1e42ce3d1f63b6edaf159529d9d943a8 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 31 Jan 2019 14:18:01 +0300 Subject: [PATCH 011/139] Move various utilities from boilerplate.c to a new util/ directory --- src/boilerplate.c | 221 ---------------------------------------------- src/boilerplate.h | 34 +------ src/meson.build | 11 ++- src/util/files.c | 217 +++++++++++++++++++++++++++++++++++++++++++++ src/util/files.h | 36 ++++++++ src/util/misc.c | 33 +++++++ src/util/misc.h | 29 ++++++ src/util/string.c | 59 +++++++++++++ src/util/string.h | 32 +++++++ 9 files changed, 420 insertions(+), 252 deletions(-) create mode 100644 src/util/files.c create mode 100644 src/util/files.h create mode 100644 src/util/misc.c create mode 100644 src/util/misc.h create mode 100644 src/util/string.c create mode 100644 src/util/string.h diff --git a/src/boilerplate.c b/src/boilerplate.c index 3076cc7..0742bf8 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -458,23 +458,6 @@ void ensure_has_primary_selection() { #endif } -int create_anonymous_file() { - int res; -#ifdef HAVE_MEMFD - res = syscall(SYS_memfd_create, "buffer", 0); - if (res >= 0) { - return res; - } -#endif -#ifdef HAVE_SHM_ANON - res = shm_open(SHM_ANON, O_RDWR | O_CREAT, 0600); - if (res >= 0) { - return res; - } -#endif - return fileno(tmpfile()); -} - void popup_tiny_invisible_surface() { // HACK: // pop up a tiny invisible surface to get the keyboard focus, @@ -618,207 +601,3 @@ uint32_t get_serial() { return global_serial; } - -int mime_type_is_text(const char *mime_type) { - return str_has_prefix(mime_type, "text/") - || strcmp(mime_type, "TEXT") == 0 - || strcmp(mime_type, "STRING") == 0 - || strcmp(mime_type, "UTF8_STRING") == 0 - || str_has_suffix(mime_type, "script") - || str_has_suffix(mime_type, "xml") - || strstr(mime_type, "json") != NULL; -} - -int str_has_prefix(const char *string, const char *prefix) { - size_t prefix_length = strlen(prefix); - return strncmp(string, prefix, prefix_length) == 0; -} - -int str_has_suffix(const char *string, const char *suffix) { - size_t string_length = strlen(string); - size_t suffix_length = strlen(suffix); - if (string_length < suffix_length) { - return 0; - } - size_t offset = string_length - suffix_length; - return strcmp(string + offset, suffix) == 0; -} - -void print_version_info() { - printf( - "wl-clipboard " PROJECT_VERSION "\n" - "Copyright (C) 2019 Sergey Bugaev\n" - "License GPLv3+: GNU GPL version 3 or later" - " .\n" - "This is free software: you are free to change and redistribute it.\n" - "There is NO WARRANTY, to the extent permitted by law.\n" - ); -} - -char *path_for_fd(int fd) { - char fdpath[64]; - snprintf(fdpath, sizeof(fdpath), "/dev/fd/%d", fd); - return realpath(fdpath, NULL); -} - -char *infer_mime_type_from_contents(const char *file_path) { - int pipefd[2]; - pipe(pipefd); - if (fork() == 0) { - dup2(pipefd[1], STDOUT_FILENO); - close(pipefd[0]); - close(pipefd[1]); - int devnull = open("/dev/null", O_RDONLY); - dup2(devnull, STDIN_FILENO); - close(devnull); - execlp("xdg-mime", "xdg-mime", "query", "filetype", file_path, NULL); - exit(1); - } - - close(pipefd[1]); - int wstatus; - wait(&wstatus); - if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0) { - char *res = malloc(PATH_MAX + 1); - size_t len = read(pipefd[0], res, PATH_MAX + 1); - len--; // trim the newline - close(pipefd[0]); - res[len] = 0; - - if (str_has_prefix(res, "inode/")) { - free(res); - return NULL; - } - - return res; - } - close(pipefd[0]); - return NULL; -} - -const char *get_file_extension(const char *file_path) { - const char *name = strrchr(file_path, '/'); - if (name == NULL) { - name = file_path; - } - const char *ext = strrchr(name, '.'); - if (ext == NULL) { - return NULL; - } - return ext + 1; -} - -char *infer_mime_type_from_name(const char *file_path) { - const char *actual_ext = get_file_extension(file_path); - if (actual_ext == NULL) { - return NULL; - } - - FILE *f = fopen("/etc/mime.types", "r"); - if (f == NULL) { - f = fopen("/usr/local/etc/mime.types", "r"); - } - if (f == NULL) { - return NULL; - } - - for (char line[200]; fgets(line, sizeof(line), f) != NULL;) { - // skip comments and blank lines - if (line[0] == '#' || line[0] == '\n') { - continue; - } - - // each line consists of a mime type and a list of extensions - char mime_type[200]; - int consumed; - if (sscanf(line, "%s%n", mime_type, &consumed) != 1) { - // malformed line? - continue; - } - char *lineptr = line + consumed; - for (char ext[200]; sscanf(lineptr, "%s%n", ext, &consumed) == 1;) { - if (strcmp(ext, actual_ext) == 0) { - fclose(f); - return strdup(mime_type); - } - lineptr += consumed; - } - } - fclose(f); - return NULL; -} - -char *dump_stdin_into_a_temp_file() { - char dirpath[] = "/tmp/wl-copy-buffer-XXXXXX"; - if (mkdtemp(dirpath) != dirpath) { - perror("mkdtemp"); - exit(1); - } - char *original_path = path_for_fd(STDIN_FILENO); - - char *res_path = malloc(PATH_MAX + 1); - memcpy(res_path, dirpath, sizeof(dirpath)); - strcat(res_path, "/"); - - if (original_path != NULL) { - char *name = basename(original_path); - strcat(res_path, name); - } else { - strcat(res_path, "stdin"); - } - - if (fork() == 0) { - int fd = creat(res_path, S_IRUSR | S_IWUSR); - if (fd < 0) { - perror("creat"); - exit(1); - } - dup2(fd, STDOUT_FILENO); - close(fd); - execlp("cat", "cat", NULL); - perror("exec cat"); - exit(1); - } - - int wstatus; - wait(&wstatus); - if (original_path != NULL) { - free(original_path); - } - if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0) { - return res_path; - } - bail("Failed to copy the file"); -} - -void trim_trailing_newline(const char *file_path) { - int fd = open(file_path, O_RDWR); - if (fd < 0) { - perror("open file for trimming"); - return; - } - - int seek_res = lseek(fd, -1, SEEK_END); - if (seek_res < 0 && errno == EINVAL) { - // empty file - goto out; - } else if (seek_res < 0) { - perror("lseek"); - goto out; - } - // otherwise, seek_res is the new file size - - char last_char; - int read_res = read(fd, &last_char, 1); - if (read_res != 1) { - perror("read"); - goto out; - } - if (last_char != '\n') { - goto out; - } - - ftruncate(fd, seek_res); -out: - close(fd); -} diff --git a/src/boilerplate.h b/src/boilerplate.h index 9bc5f0a..f6f6712 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -18,6 +18,10 @@ #include "config.h" +#include "util/string.h" +#include "util/files.h" +#include "util/misc.h" + #include #include #include @@ -33,13 +37,6 @@ #include #include // PATH_MAX -#ifdef HAVE_MEMFD -# include // syscall, SYS_memfd_create -#endif -#ifdef HAVE_SHM_ANON -# include // shm_open, SHM_ANON -#endif - #ifdef HAVE_XDG_SHELL # include "xdg-shell.h" @@ -61,11 +58,6 @@ # include "wlr-data-control.h" #endif -#define bail(message) do { fprintf(stderr, message "\n"); exit(1); } while (0) - -#define text_plain "text/plain" -#define text_plain_utf8 "text/plain;charset=utf-8" - struct wl_display *display; struct wl_data_device_manager *data_device_manager; struct wl_seat *seat; @@ -118,21 +110,3 @@ void (*action_on_no_keyboard)(void); void ensure_has_primary_selection(void); uint32_t get_serial(void); - -int mime_type_is_text(const char *mime_type); -int str_has_prefix(const char *string, const char *prefix); -int str_has_suffix(const char *string, const char *suffix); - -void print_version_info(void); - -void trim_trailing_newline(const char *file_path); - -// functions below this line return owned strings, -// free() their return values when done with them - -char *path_for_fd(int fd); -char *infer_mime_type_from_contents(const char *file_path); -char *infer_mime_type_from_name(const char *file_path); - -// returns the name of a new file -char *dump_stdin_into_a_temp_file(void); diff --git a/src/meson.build b/src/meson.build index 9f89c23..3fbc3e7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -95,7 +95,16 @@ endforeach boilerplate = static_library( 'wl-clipboard-boilerplate', - 'boilerplate.c', + [ + 'boilerplate.c', + + 'util/string.h', + 'util/string.c', + 'util/files.h', + 'util/files.c', + 'util/misc.h', + 'util/misc.c', + ], dependencies: wayland, link_with: protocol_deps ) diff --git a/src/util/files.c b/src/util/files.c new file mode 100644 index 0000000..bd1ce8c --- /dev/null +++ b/src/util/files.c @@ -0,0 +1,217 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "util/files.h" +#include "util/string.h" +#include "util/misc.h" + +#include "config.h" + +#include +#include +#include +#include +#include +#include // open +#include // open +#include // open +#include // exit +#include // basename +#include +#include // PATH_MAX + +#ifdef HAVE_MEMFD +# include // syscall, SYS_memfd_create +#endif +#ifdef HAVE_SHM_ANON +# include // shm_open, SHM_ANON +#endif + + +int create_anonymous_file() { + int res; +#ifdef HAVE_MEMFD + res = syscall(SYS_memfd_create, "buffer", 0); + if (res >= 0) { + return res; + } +#endif +#ifdef HAVE_SHM_ANON + res = shm_open(SHM_ANON, O_RDWR | O_CREAT, 0600); + if (res >= 0) { + return res; + } +#endif + return fileno(tmpfile()); +} + +void trim_trailing_newline(const char *file_path) { + int fd = open(file_path, O_RDWR); + if (fd < 0) { + perror("open file for trimming"); + return; + } + + int seek_res = lseek(fd, -1, SEEK_END); + if (seek_res < 0 && errno == EINVAL) { + // empty file + goto out; + } else if (seek_res < 0) { + perror("lseek"); + goto out; + } + // otherwise, seek_res is the new file size + + char last_char; + int read_res = read(fd, &last_char, 1); + if (read_res != 1) { + perror("read"); + goto out; + } + if (last_char != '\n') { + goto out; + } + + ftruncate(fd, seek_res); +out: + close(fd); +} + +char *path_for_fd(int fd) { + char fdpath[64]; + snprintf(fdpath, sizeof(fdpath), "/dev/fd/%d", fd); + return realpath(fdpath, NULL); +} + +char *infer_mime_type_from_contents(const char *file_path) { + int pipefd[2]; + pipe(pipefd); + if (fork() == 0) { + dup2(pipefd[1], STDOUT_FILENO); + close(pipefd[0]); + close(pipefd[1]); + int devnull = open("/dev/null", O_RDONLY); + dup2(devnull, STDIN_FILENO); + close(devnull); + execlp("xdg-mime", "xdg-mime", "query", "filetype", file_path, NULL); + exit(1); + } + + close(pipefd[1]); + int wstatus; + wait(&wstatus); + if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0) { + char *res = malloc(PATH_MAX + 1); + size_t len = read(pipefd[0], res, PATH_MAX + 1); + len--; // trim the newline + close(pipefd[0]); + res[len] = 0; + + if (str_has_prefix(res, "inode/")) { + free(res); + return NULL; + } + + return res; + } + close(pipefd[0]); + return NULL; +} + +char *infer_mime_type_from_name(const char *file_path) { + const char *actual_ext = get_file_extension(file_path); + if (actual_ext == NULL) { + return NULL; + } + + FILE *f = fopen("/etc/mime.types", "r"); + if (f == NULL) { + f = fopen("/usr/local/etc/mime.types", "r"); + } + if (f == NULL) { + return NULL; + } + + for (char line[200]; fgets(line, sizeof(line), f) != NULL;) { + // skip comments and blank lines + if (line[0] == '#' || line[0] == '\n') { + continue; + } + + // each line consists of a mime type and a list of extensions + char mime_type[200]; + int consumed; + if (sscanf(line, "%s%n", mime_type, &consumed) != 1) { + // malformed line? + continue; + } + char *lineptr = line + consumed; + for (char ext[200]; sscanf(lineptr, "%s%n", ext, &consumed) == 1;) { + if (strcmp(ext, actual_ext) == 0) { + fclose(f); + return strdup(mime_type); + } + lineptr += consumed; + } + } + fclose(f); + return NULL; +} + +char *dump_stdin_into_a_temp_file() { + char dirpath[] = "/tmp/wl-copy-buffer-XXXXXX"; + if (mkdtemp(dirpath) != dirpath) { + perror("mkdtemp"); + exit(1); + } + char *original_path = path_for_fd(STDIN_FILENO); + + char *res_path = malloc(PATH_MAX + 1); + memcpy(res_path, dirpath, sizeof(dirpath)); + strcat(res_path, "/"); + + if (original_path != NULL) { + char *name = basename(original_path); + strcat(res_path, name); + } else { + strcat(res_path, "stdin"); + } + + if (fork() == 0) { + int fd = creat(res_path, S_IRUSR | S_IWUSR); + if (fd < 0) { + perror("creat"); + exit(1); + } + dup2(fd, STDOUT_FILENO); + close(fd); + execlp("cat", "cat", NULL); + perror("exec cat"); + exit(1); + } + + int wstatus; + wait(&wstatus); + if (original_path != NULL) { + free(original_path); + } + if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0) { + return res_path; + } + bail("Failed to copy the file"); +} diff --git a/src/util/files.h b/src/util/files.h new file mode 100644 index 0000000..ec8fa3e --- /dev/null +++ b/src/util/files.h @@ -0,0 +1,36 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UTIL_FILES_H +#define UTIL_FILES_H + +int create_anonymous_file(void); + +void trim_trailing_newline(const char *file_path); + +// these functions return owned strings, so make sure +// to free() their return values when done with them + +char *path_for_fd(int fd); +char *infer_mime_type_from_contents(const char *file_path); +char *infer_mime_type_from_name(const char *file_path); + +// returns the name of a new file +char *dump_stdin_into_a_temp_file(void); + +#endif /* UTIL_FILES_H */ diff --git a/src/util/misc.c b/src/util/misc.c new file mode 100644 index 0000000..934792e --- /dev/null +++ b/src/util/misc.c @@ -0,0 +1,33 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" +#include "util/misc.h" + +#include + +void print_version_info() { + printf( + "wl-clipboard " PROJECT_VERSION "\n" + "Copyright (C) 2019 Sergey Bugaev\n" + "License GPLv3+: GNU GPL version 3 or later" + " .\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n" + ); +} diff --git a/src/util/misc.h b/src/util/misc.h new file mode 100644 index 0000000..54890b9 --- /dev/null +++ b/src/util/misc.h @@ -0,0 +1,29 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UTIL_MISC_H +#define UTIL_MISC_H + +#include +#include + +#define bail(message) do { fprintf(stderr, message "\n"); exit(1); } while (0) + +void print_version_info(void); + +#endif /* UTIL_MISC_H */ diff --git a/src/util/string.c b/src/util/string.c new file mode 100644 index 0000000..757b87a --- /dev/null +++ b/src/util/string.c @@ -0,0 +1,59 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "util/string.h" + +#include +#include + +int mime_type_is_text(const char *mime_type) { + return str_has_prefix(mime_type, "text/") + || strcmp(mime_type, "TEXT") == 0 + || strcmp(mime_type, "STRING") == 0 + || strcmp(mime_type, "UTF8_STRING") == 0 + || str_has_suffix(mime_type, "script") + || str_has_suffix(mime_type, "xml") + || strstr(mime_type, "json") != NULL; +} + +int str_has_prefix(const char *string, const char *prefix) { + size_t prefix_length = strlen(prefix); + return strncmp(string, prefix, prefix_length) == 0; +} + +int str_has_suffix(const char *string, const char *suffix) { + size_t string_length = strlen(string); + size_t suffix_length = strlen(suffix); + if (string_length < suffix_length) { + return 0; + } + size_t offset = string_length - suffix_length; + return strcmp(string + offset, suffix) == 0; +} + +const char *get_file_extension(const char *file_path) { + const char *name = strrchr(file_path, '/'); + if (name == NULL) { + name = file_path; + } + const char *ext = strrchr(name, '.'); + if (ext == NULL) { + return NULL; + } + return ext + 1; +} diff --git a/src/util/string.h b/src/util/string.h new file mode 100644 index 0000000..4602652 --- /dev/null +++ b/src/util/string.h @@ -0,0 +1,32 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UTIL_STRING_H +#define UTIL_STRING_H + +#define text_plain "text/plain" +#define text_plain_utf8 "text/plain;charset=utf-8" + +int mime_type_is_text(const char *mime_type); + +int str_has_prefix(const char *string, const char *prefix); +int str_has_suffix(const char *string, const char *suffix); + +const char *get_file_extension(const char *file_path); + +#endif /* UTIL_STRING_H */ From 21724547fe13d7e6027a62994c41677a3cd0cd7d Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 5 Sep 2019 15:51:56 +0300 Subject: [PATCH 012/139] Stop using PATH_MAX Fixes https://github.com/bugaevc/wl-clipboard/issues/45 --- src/util/files.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/util/files.c b/src/util/files.c index bd1ce8c..9384001 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -116,8 +116,8 @@ char *infer_mime_type_from_contents(const char *file_path) { int wstatus; wait(&wstatus); if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0) { - char *res = malloc(PATH_MAX + 1); - size_t len = read(pipefd[0], res, PATH_MAX + 1); + char *res = malloc(256); + size_t len = read(pipefd[0], res, 256); len--; // trim the newline close(pipefd[0]); res[len] = 0; @@ -180,17 +180,12 @@ char *dump_stdin_into_a_temp_file() { exit(1); } char *original_path = path_for_fd(STDIN_FILENO); + char *name = original_path != NULL ? basename(original_path) : "stdin"; - char *res_path = malloc(PATH_MAX + 1); + char *res_path = malloc(strlen(dirpath) + 1 + strlen(name) + 1); memcpy(res_path, dirpath, sizeof(dirpath)); strcat(res_path, "/"); - - if (original_path != NULL) { - char *name = basename(original_path); - strcat(res_path, name); - } else { - strcat(res_path, "stdin"); - } + strcat(res_path, name); if (fork() == 0) { int fd = creat(res_path, S_IRUSR | S_IWUSR); From 5280774e6e44b213257273007b1ebf0150e5b686 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 9 Sep 2019 16:07:04 +0300 Subject: [PATCH 013/139] Refactor some bits in util/files.c --- src/util/files.c | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/util/files.c b/src/util/files.c index 9384001..0bcd0bb 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -57,6 +57,7 @@ int create_anonymous_file() { return res; } #endif + (void) res; return fileno(tmpfile()); } @@ -115,22 +116,24 @@ char *infer_mime_type_from_contents(const char *file_path) { close(pipefd[1]); int wstatus; wait(&wstatus); - if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0) { - char *res = malloc(256); - size_t len = read(pipefd[0], res, 256); - len--; // trim the newline + + if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { close(pipefd[0]); - res[len] = 0; - - if (str_has_prefix(res, "inode/")) { - free(res); - return NULL; - } - - return res; + return NULL; } + + char *res = malloc(256); + size_t len = read(pipefd[0], res, 256); + len--; // trim the newline + res[len] = 0; close(pipefd[0]); - return NULL; + + if (str_has_prefix(res, "inode/")) { + free(res); + return NULL; + } + + return res; } char *infer_mime_type_from_name(const char *file_path) { @@ -184,8 +187,8 @@ char *dump_stdin_into_a_temp_file() { char *res_path = malloc(strlen(dirpath) + 1 + strlen(name) + 1); memcpy(res_path, dirpath, sizeof(dirpath)); - strcat(res_path, "/"); - strcat(res_path, name); + res_path[sizeof(dirpath) - 1] = '/'; + strcpy(res_path + sizeof(dirpath), name); if (fork() == 0) { int fd = creat(res_path, S_IRUSR | S_IWUSR); @@ -205,8 +208,8 @@ char *dump_stdin_into_a_temp_file() { if (original_path != NULL) { free(original_path); } - if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0) { - return res_path; + if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { + bail("Failed to copy the file"); } - bail("Failed to copy the file"); + return res_path; } From a86bb0040f0a07d1f25bf07b6df5835469329bed Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 5 Sep 2019 15:53:06 +0300 Subject: [PATCH 014/139] Treat YAML and PGP keys as text Fixes https://github.com/bugaevc/wl-clipboard/issues/44 and https://github.com/bugaevc/wl-clipboard/issues/46 --- src/util/string.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/string.c b/src/util/string.c index 757b87a..b7b3876 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -28,6 +28,8 @@ int mime_type_is_text(const char *mime_type) { || strcmp(mime_type, "UTF8_STRING") == 0 || str_has_suffix(mime_type, "script") || str_has_suffix(mime_type, "xml") + || str_has_suffix(mime_type, "yaml") + || str_has_suffix(mime_type, "pgp-keys") || strstr(mime_type, "json") != NULL; } From 6b31d8742fc573fe428c33d6bc136c76a19e0eb0 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 9 Sep 2019 16:18:46 +0300 Subject: [PATCH 015/139] Fix a potential buffer overflow --- src/util/files.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/files.c b/src/util/files.c index 0bcd0bb..abb9273 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -159,12 +159,12 @@ char *infer_mime_type_from_name(const char *file_path) { // each line consists of a mime type and a list of extensions char mime_type[200]; int consumed; - if (sscanf(line, "%s%n", mime_type, &consumed) != 1) { + if (sscanf(line, "%199s%n", mime_type, &consumed) != 1) { // malformed line? continue; } char *lineptr = line + consumed; - for (char ext[200]; sscanf(lineptr, "%s%n", ext, &consumed) == 1;) { + for (char ext[200]; sscanf(lineptr, "%199s%n", ext, &consumed) == 1;) { if (strcmp(ext, actual_ext) == 0) { fclose(f); return strdup(mime_type); From 19633ac355fac45f977980df9c8539507365b7eb Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 9 Sep 2019 16:15:01 +0300 Subject: [PATCH 016/139] Tweak comments throughout the codebase --- src/boilerplate.c | 51 +++++++++++++++++++++++++---------------------- src/util/files.c | 29 ++++++++++++++++++++------- src/util/files.h | 7 ++++--- src/wl-copy.c | 26 ++++++++++++++---------- src/wl-paste.c | 9 ++++++--- 5 files changed, 74 insertions(+), 48 deletions(-) diff --git a/src/boilerplate.c b/src/boilerplate.c index 0742bf8..f285d07 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -147,7 +147,7 @@ void keyboard_enter_handler struct wl_array *keys ) { struct wl_seat *this_seat = (struct wl_seat *) data; - // when we get to here, global seat is already initialized + /* When we get to here, global seat is already initialized */ if (this_seat != seat) { return; } @@ -199,7 +199,7 @@ void seat_capabilities_handler struct wl_seat *this_seat, uint32_t capabilities ) { - // stash the capabilities of this seat for later + /* Stash the capabilities of this seat for later */ void *user_data = (void *) (unsigned long) capabilities; wl_seat_set_user_data(this_seat, user_data); @@ -376,7 +376,7 @@ void init_wayland_globals() { struct wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, NULL); - // wait for the "initial" set of globals to appear + /* Wait for the "initial" set of globals to appear */ wl_display_roundtrip(display); if ( @@ -459,24 +459,26 @@ void ensure_has_primary_selection() { } void popup_tiny_invisible_surface() { - // HACK: - // pop up a tiny invisible surface to get the keyboard focus, - // otherwise we won't be notified of the selection + /* HACK: + * Pop up a tiny invisible surface to get the keyboard focus, + * otherwise we won't be notified of the selection. + */ if (!ensure_seat_has_keyboard()) { return; } - // make sure that we get the keyboard - // object before creating the surface, - // so that we get the enter event + /* Make sure that we get the keyboard + * object before creating the surface, + * so that we get the enter event. + */ wl_display_dispatch(display); surface = wl_compositor_create_surface(compositor); #ifdef HAVE_WLR_LAYER_SHELL if (layer_shell != NULL) { - // use wlr-layer-shell + /* Use wlr-layer-shell */ layer_surface = zwlr_layer_shell_v1_get_layer_surface( layer_shell, surface, @@ -490,29 +492,29 @@ void popup_tiny_invisible_surface() { NULL ); zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, 1); - // signal that the surface is ready to be configured + /* Signal that the surface is ready to be configured */ wl_surface_commit(surface); - // wait for the surface to be configured + /* Wait for the surface to be configured */ wl_display_roundtrip(display); } else #endif if (shell != NULL) { - // use wl_shell + /* Use wl_shell */ shell_surface = wl_shell_get_shell_surface(shell, surface); wl_shell_surface_set_toplevel(shell_surface); wl_shell_surface_set_title(shell_surface, "wl-clipboard"); } else { #ifdef HAVE_XDG_SHELL - // use xdg-shell + /* Use xdg-shell */ xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL); xdg_surface = xdg_wm_base_get_xdg_surface(xdg_wm_base, surface); xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL); xdg_toplevel_set_title(xdg_toplevel, "wl-clipboard"); - // signal that the surface is ready to be configured + /* Signal that the surface is ready to be configured */ wl_surface_commit(surface); - // wait for the surface to be configured + /* Wait for the surface to be configured */ wl_display_roundtrip(display); #else bail("Unreachable: HAVE_XDG_SHELL undefined and no wl_shell"); @@ -520,10 +522,11 @@ void popup_tiny_invisible_surface() { } if (surface == NULL) { - // it's possible that we've been given focus without us - // ever commiting a buffer, in which case the handlers - // may have already destroyed the surface; there's no - // way or need for us to commit a buffer in that case + /* It's possible that we've been given focus without us + * ever commiting a buffer, in which case the handlers + * may have already destroyed the surface; there's no + * way or need for us to commit a buffer in that case. + */ return; } @@ -532,17 +535,17 @@ void popup_tiny_invisible_surface() { int stride = width * 4; int size = stride * height; // bytes - // open an anonymous file and write some zero bytes to it + /* Open an anonymous file and write some zero bytes to it */ int fd = create_anonymous_file(); ftruncate(fd, size); - // turn it into a shared memory pool + /* Turn it into a shared memory pool */ struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); - // allocate the buffer in that pool + /* Allocate the buffer in that pool */ struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); - // zeros in ARGB8888 mean fully transparent + /* Zeros in ARGB8888 mean fully transparent */ wl_surface_attach(surface, buffer, 0, 0); wl_surface_damage(surface, 0, 0, width, height); diff --git a/src/util/files.c b/src/util/files.c index abb9273..d7a2564 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -70,14 +70,15 @@ void trim_trailing_newline(const char *file_path) { int seek_res = lseek(fd, -1, SEEK_END); if (seek_res < 0 && errno == EINVAL) { - // empty file + /* It was an empty file */ goto out; } else if (seek_res < 0) { perror("lseek"); goto out; } - // otherwise, seek_res is the new file size - + /* If the seek was successful, seek_res is the + * new file size after trimming the newline. + */ char last_char; int read_res = read(fd, &last_char, 1); if (read_res != 1) { @@ -100,6 +101,7 @@ char *path_for_fd(int fd) { } char *infer_mime_type_from_contents(const char *file_path) { + /* Spawn xdg-mime query filetype */ int pipefd[2]; pipe(pipefd); if (fork() == 0) { @@ -117,14 +119,17 @@ char *infer_mime_type_from_contents(const char *file_path) { int wstatus; wait(&wstatus); + /* See if that worked */ if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { close(pipefd[0]); return NULL; } + /* Read the result */ char *res = malloc(256); size_t len = read(pipefd[0], res, 256); - len--; // trim the newline + /* Trim the newline */ + len--; res[len] = 0; close(pipefd[0]); @@ -151,16 +156,16 @@ char *infer_mime_type_from_name(const char *file_path) { } for (char line[200]; fgets(line, sizeof(line), f) != NULL;) { - // skip comments and blank lines + /* Skip comments and black lines */ if (line[0] == '#' || line[0] == '\n') { continue; } - // each line consists of a mime type and a list of extensions + /* Each line consists of a mime type and a list of extensions */ char mime_type[200]; int consumed; if (sscanf(line, "%199s%n", mime_type, &consumed) != 1) { - // malformed line? + /* A malformed line, perhaps? */ continue; } char *lineptr = line + consumed; @@ -176,20 +181,30 @@ char *infer_mime_type_from_name(const char *file_path) { return NULL; } +/* Returns the name of a new file */ char *dump_stdin_into_a_temp_file() { + /* Create a temp directory to host out file */ char dirpath[] = "/tmp/wl-copy-buffer-XXXXXX"; if (mkdtemp(dirpath) != dirpath) { perror("mkdtemp"); exit(1); } + + /* Pick a name for the file we'll be + * creating inside that directory. We + * try to preserve the origial name for + * the mime type inference to work. + */ char *original_path = path_for_fd(STDIN_FILENO); char *name = original_path != NULL ? basename(original_path) : "stdin"; + /* Construct the path */ char *res_path = malloc(strlen(dirpath) + 1 + strlen(name) + 1); memcpy(res_path, dirpath, sizeof(dirpath)); res_path[sizeof(dirpath) - 1] = '/'; strcpy(res_path + sizeof(dirpath), name); + /* Spawn cat to perform the copy */ if (fork() == 0) { int fd = creat(res_path, S_IRUSR | S_IWUSR); if (fd < 0) { diff --git a/src/util/files.h b/src/util/files.h index ec8fa3e..2a2d4b6 100644 --- a/src/util/files.h +++ b/src/util/files.h @@ -23,14 +23,15 @@ int create_anonymous_file(void); void trim_trailing_newline(const char *file_path); -// these functions return owned strings, so make sure -// to free() their return values when done with them +/* These functions return owned strings, so make sure + * to free() their return values when done with them. + */ char *path_for_fd(int fd); char *infer_mime_type_from_contents(const char *file_path); char *infer_mime_type_from_name(const char *file_path); -// returns the name of a new file +/* Returns the name of a new file */ char *dump_stdin_into_a_temp_file(void); #endif /* UTIL_FILES_H */ diff --git a/src/wl-copy.c b/src/wl-copy.c index c4a3e62..6db93a8 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -23,7 +23,7 @@ char *temp_file_to_copy = NULL; int paste_once = 0; void do_cancel() { - // we're done! + /* We're done! */ if (temp_file_to_copy != NULL) { execlp("rm", "rm", "-r", dirname(temp_file_to_copy), NULL); perror("exec rm"); @@ -34,10 +34,10 @@ void do_cancel() { } void do_send(const char *mime_type, int fd) { - // unset O_NONBLOCK + /* Unset O_NONBLOCK */ fcntl(fd, F_SETFL, 0); if (data_to_copy != NULL) { - // copy the specified data, separated by spaces + /* Copy the specified data, separated by spaces */ FILE *f = fdopen(fd, "w"); if (f == NULL) { perror("fdopen"); @@ -52,8 +52,9 @@ void do_send(const char *mime_type, int fd) { } fclose(f); } else { - // copy from the temp file; for that, we delegate to a - // (hopefully) highly optimized implementation of copying + /* Copy from the temp file; for that, we delegate to a + * (hopefully) highly optimized implementation of copying. + */ if (fork() == 0) { dup2(fd, STDOUT_FILENO); execlp("cat", "cat", temp_file_to_copy, NULL); @@ -235,7 +236,7 @@ void do_offer void (*offer_f)(void *source, const char *type) ) { if (mime_type == NULL || mime_type_is_text(mime_type)) { - // offer a few generic plain text formats + /* Offer a few generic plain text formats */ offer_f(source, text_plain); offer_f(source, text_plain_utf8); offer_f(source, "TEXT"); @@ -435,7 +436,7 @@ int main(int argc, char * const argv[]) { requested_seat_name = strdup(optarg); break; default: - // getopt has already printed an error message + /* getopt has already printed an error message */ print_usage(stderr, argv[0]); exit(1); } @@ -449,10 +450,10 @@ int main(int argc, char * const argv[]) { if (!clear) { if (optind < argc) { - // copy our command-line args + /* Copy our command-line arguments */ data_to_copy = &argv[optind]; } else { - // copy stdin + /* Copy data from our stdin */ temp_file_to_copy = dump_stdin_into_a_temp_file(); if (trim_newline) { trim_trailing_newline(temp_file_to_copy); @@ -465,8 +466,11 @@ int main(int argc, char * const argv[]) { if (!stay_in_foreground && !clear) { if (fork() != 0) { - // exit in the parent, but leave the - // child running in the background + /* Move to background. + * We fork our process and leave the + * child running in the background, + * while exiting in the parent. + */ exit(0); } } diff --git a/src/wl-paste.c b/src/wl-paste.c index d3d4022..4038ed2 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -121,7 +121,9 @@ const char *mime_type_to_request() { try_prefixed; } } else { - // no mime type requested explicitly, try to guess + /* No mime type requested explicitly, + * so try to guess. + */ if (options.inferred_type == NULL) { try_text_plain_utf8; try_text_plain; @@ -169,7 +171,7 @@ void do_paste } const char *mime_type = mime_type_to_request(); - // never append a newline character to binary content + /* Never append a newline character to binary content */ if (!mime_type_is_text(mime_type)) { options.no_newline = 1; } @@ -184,6 +186,7 @@ void do_paste wl_display_roundtrip(display); + /* Spawn a cat to perform the copy */ if (fork() == 0) { dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); @@ -503,7 +506,7 @@ int main(int argc, char * const argv[]) { requested_seat_name = strdup(optarg); break; default: - // getopt has already printed an error message + /* getopt has already printed an error message */ print_usage(stderr, argv[0]); exit(1); } From 993b02deb46bfe6214064b827ee39e4f155c3217 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 9 Sep 2019 16:45:20 +0300 Subject: [PATCH 017/139] Unimplement wlr-layer-shell support This largely reverts commits 3eac5897748e6313540477e528e2c94b6b10e6b2 and cd8cb31e611dbb978a1d42f56e29f5de38b36c7c. Now that wlroots-based compositors implement the wlr-data-control protocol, there's no need to use the popup surface hack when running under them, which means wlr-layer-shell does not actually get used. While it is possible that a compositor supports wlr-layer-shell but not wlr-data-control, it is unlikely enough by this point. Furthermore, wl-clipboard is now used widely enough for niche compositors to consider implmenting a protocol to make wl-clipboard work better rather than the other way around. In other words, if your compositor implements wlr-layer-shell but not wlr-data-control and you would like wl-clipboard to work well under your compositor, please implement wlr-data-control in your compositor so that we can avoid relying on hacks. Closes https://github.com/bugaevc/wl-clipboard/issues/24 --- src/boilerplate.c | 67 ----- src/boilerplate.h | 9 - src/meson.build | 8 - src/protocol/wlr-layer-shell-unstable-v1.xml | 285 ------------------- 4 files changed, 369 deletions(-) delete mode 100644 src/protocol/wlr-layer-shell-unstable-v1.xml diff --git a/src/boilerplate.c b/src/boilerplate.c index f285d07..e16c48e 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -73,16 +73,6 @@ void registry_global_handler ); } #endif -#ifdef HAVE_WLR_LAYER_SHELL - else if (strcmp(interface, "zwlr_layer_shell_v1") == 0) { - layer_shell = wl_registry_bind( - registry, - name, - &zwlr_layer_shell_v1_interface, - 1 - ); - } -#endif #ifdef HAVE_GTK_PRIMARY_SELECTION else if (strcmp(interface, "gtk_primary_selection_device_manager") == 0) { gtk_primary_selection_device_manager = wl_registry_bind( @@ -341,32 +331,6 @@ const struct xdg_wm_base_listener xdg_wm_base_listener = { #endif -#ifdef HAVE_WLR_LAYER_SHELL - -void layer_surface_configure_handler -( - void *data, - struct zwlr_layer_surface_v1 *layer_surface, - uint32_t serial, - uint32_t width, - uint32_t height -) { - zwlr_layer_surface_v1_ack_configure(layer_surface, serial); -} - -void layer_surface_closed_handler -( - void *data, - struct zwlr_layer_surface_v1 *layer_surface -) {} - -const struct zwlr_layer_surface_v1_listener layer_surface_listener = { - .configure = layer_surface_configure_handler, - .closed = layer_surface_closed_handler -}; - -#endif - void init_wayland_globals() { display = wl_display_connect(NULL); if (display == NULL) { @@ -386,9 +350,6 @@ void init_wayland_globals() { (shell == NULL #ifdef HAVE_XDG_SHELL && xdg_wm_base == NULL -#endif -#ifdef HAVE_WLR_LAYER_SHELL - && layer_shell == NULL #endif ) ) { @@ -476,28 +437,6 @@ void popup_tiny_invisible_surface() { surface = wl_compositor_create_surface(compositor); -#ifdef HAVE_WLR_LAYER_SHELL - if (layer_shell != NULL) { - /* Use wlr-layer-shell */ - layer_surface = zwlr_layer_shell_v1_get_layer_surface( - layer_shell, - surface, - NULL, // output - ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, - "wl-clipboard" // namespace - ); - zwlr_layer_surface_v1_add_listener( - layer_surface, - &layer_surface_listener, - NULL - ); - zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, 1); - /* Signal that the surface is ready to be configured */ - wl_surface_commit(surface); - /* Wait for the surface to be configured */ - wl_display_roundtrip(display); - } else -#endif if (shell != NULL) { /* Use wl_shell */ shell_surface = wl_shell_get_shell_surface(shell, surface); @@ -553,12 +492,6 @@ void popup_tiny_invisible_surface() { } void destroy_popup_surface() { -#ifdef HAVE_WLR_LAYER_SHELL - if (layer_surface != NULL) { - zwlr_layer_surface_v1_destroy(layer_surface); - layer_surface = NULL; - } -#endif if (shell_surface != NULL) { wl_shell_surface_destroy(shell_surface); shell_surface = NULL; diff --git a/src/boilerplate.h b/src/boilerplate.h index f6f6712..c553897 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -46,10 +46,6 @@ # include "wp-primary-selection.h" #endif -#ifdef HAVE_WLR_LAYER_SHELL -# include "wlr-layer-shell.h" -#endif - #ifdef HAVE_GTK_PRIMARY_SELECTION # include "gtk-primary-selection.h" #endif @@ -73,11 +69,6 @@ struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; #endif -#ifdef HAVE_WLR_LAYER_SHELL -struct zwlr_layer_shell_v1 *layer_shell; -struct zwlr_layer_surface_v1 *layer_surface; -#endif - struct wl_data_device *data_device; #ifdef HAVE_GTK_PRIMARY_SELECTION diff --git a/src/meson.build b/src/meson.build index 3fbc3e7..fa42d57 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,7 +7,6 @@ if wayland_scanner.found() have_xdg_shell = wayland_protocols.found() have_wp_primary_selection = wayland_protocols.found() and wayland_protocols.version().version_compare('>= 1.17') # these are bundled - have_wlr_layer_shell = have_xdg_shell # depends on xdg_popup have_gtk_primary_selection = true have_wlr_data_control = true @@ -18,7 +17,6 @@ if wayland_scanner.found() endif else have_xdg_shell = false - have_wlr_layer_shell = false have_gtk_primary_selection = false have_wlr_data_control = false endif @@ -32,7 +30,6 @@ conf_data = configuration_data() conf_data.set('PROJECT_VERSION', '"@0@"'.format(meson.project_version())) conf_data.set('HAVE_XDG_SHELL', have_xdg_shell) conf_data.set('HAVE_WP_PRIMARY_SELECTION', have_wp_primary_selection) -conf_data.set('HAVE_WLR_LAYER_SHELL', have_wlr_layer_shell) conf_data.set('HAVE_GTK_PRIMARY_SELECTION', have_gtk_primary_selection) conf_data.set('HAVE_WLR_DATA_CONTROL', have_wlr_data_control) @@ -59,11 +56,6 @@ if have_wp_primary_selection protocols += [['wp-primary-selection', wp_primary_selection_xml]] endif -if have_wlr_layer_shell - wlr_layer_shell_xml = join_paths('protocol', 'wlr-layer-shell-unstable-v1.xml') - protocols += [['wlr-layer-shell', wlr_layer_shell_xml]] -endif - if not have_gtk_primary_selection warning('Building without primary selection support') else diff --git a/src/protocol/wlr-layer-shell-unstable-v1.xml b/src/protocol/wlr-layer-shell-unstable-v1.xml deleted file mode 100644 index 216e0d9..0000000 --- a/src/protocol/wlr-layer-shell-unstable-v1.xml +++ /dev/null @@ -1,285 +0,0 @@ - - - - Copyright © 2017 Drew DeVault - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - Clients can use this interface to assign the surface_layer role to - wl_surfaces. Such surfaces are assigned to a "layer" of the output and - rendered with a defined z-depth respective to each other. They may also be - anchored to the edges and corners of a screen and specify input handling - semantics. This interface should be suitable for the implementation of - many desktop shell components, and a broad number of other applications - that interact with the desktop. - - - - - Create a layer surface for an existing surface. This assigns the role of - layer_surface, or raises a protocol error if another role is already - assigned. - - Creating a layer surface from a wl_surface which has a buffer attached - or committed is a client error, and any attempts by a client to attach - or manipulate a buffer prior to the first layer_surface.configure call - must also be treated as errors. - - You may pass NULL for output to allow the compositor to decide which - output to use. Generally this will be the one that the user most - recently interacted with. - - Clients can specify a namespace that defines the purpose of the layer - surface. - - - - - - - - - - - - - - - - - These values indicate which layers a surface can be rendered in. They - are ordered by z depth, bottom-most first. Traditional shell surfaces - will typically be rendered between the bottom and top layers. - Fullscreen shell surfaces are typically rendered at the top layer. - Multiple surfaces can share a single layer, and ordering within a - single layer is undefined. - - - - - - - - - - - - An interface that may be implemented by a wl_surface, for surfaces that - are designed to be rendered as a layer of a stacked desktop-like - environment. - - Layer surface state (size, anchor, exclusive zone, margin, interactivity) - is double-buffered, and will be applied at the time wl_surface.commit of - the corresponding wl_surface is called. - - - - - Sets the size of the surface in surface-local coordinates. The - compositor will display the surface centered with respect to its - anchors. - - If you pass 0 for either value, the compositor will assign it and - inform you of the assignment in the configure event. You must set your - anchor to opposite edges in the dimensions you omit; not doing so is a - protocol error. Both values are 0 by default. - - Size is double-buffered, see wl_surface.commit. - - - - - - - - Requests that the compositor anchor the surface to the specified edges - and corners. If two orthoginal edges are specified (e.g. 'top' and - 'left'), then the anchor point will be the intersection of the edges - (e.g. the top left corner of the output); otherwise the anchor point - will be centered on that edge, or in the center if none is specified. - - Anchor is double-buffered, see wl_surface.commit. - - - - - - - Requests that the compositor avoids occluding an area of the surface - with other surfaces. The compositor's use of this information is - implementation-dependent - do not assume that this region will not - actually be occluded. - - A positive value is only meaningful if the surface is anchored to an - edge, rather than a corner. The zone is the number of surface-local - coordinates from the edge that are considered exclusive. - - Surfaces that do not wish to have an exclusive zone may instead specify - how they should interact with surfaces that do. If set to zero, the - surface indicates that it would like to be moved to avoid occluding - surfaces with a positive excluzive zone. If set to -1, the surface - indicates that it would not like to be moved to accommodate for other - surfaces, and the compositor should extend it all the way to the edges - it is anchored to. - - For example, a panel might set its exclusive zone to 10, so that - maximized shell surfaces are not shown on top of it. A notification - might set its exclusive zone to 0, so that it is moved to avoid - occluding the panel, but shell surfaces are shown underneath it. A - wallpaper or lock screen might set their exclusive zone to -1, so that - they stretch below or over the panel. - - The default value is 0. - - Exclusive zone is double-buffered, see wl_surface.commit. - - - - - - - Requests that the surface be placed some distance away from the anchor - point on the output, in surface-local coordinates. Setting this value - for edges you are not anchored to has no effect. - - The exclusive zone includes the margin. - - Margin is double-buffered, see wl_surface.commit. - - - - - - - - - - Set to 1 to request that the seat send keyboard events to this layer - surface. For layers below the shell surface layer, the seat will use - normal focus semantics. For layers above the shell surface layers, the - seat will always give exclusive keyboard focus to the top-most layer - which has keyboard interactivity set to true. - - Layer surfaces receive pointer, touch, and tablet events normally. If - you do not want to receive them, set the input region on your surface - to an empty region. - - Events is double-buffered, see wl_surface.commit. - - - - - - - This assigns an xdg_popup's parent to this layer_surface. This popup - should have been created via xdg_surface::get_popup with the parent set - to NULL, and this request must be invoked before committing the popup's - initial state. - - See the documentation of xdg_popup for more details about what an - xdg_popup is and how it is used. - - - - - - - When a configure event is received, if a client commits the - surface in response to the configure event, then the client - must make an ack_configure request sometime before the commit - request, passing along the serial of the configure event. - - If the client receives multiple configure events before it - can respond to one, it only has to ack the last configure event. - - A client is not required to commit immediately after sending - an ack_configure request - it may even ack_configure several times - before its next surface commit. - - A client may send multiple ack_configure requests before committing, but - only the last request sent before a commit indicates which configure - event the client really is responding to. - - - - - - - This request destroys the layer surface. - - - - - - The configure event asks the client to resize its surface. - - Clients should arrange their surface for the new states, and then send - an ack_configure request with the serial sent in this configure event at - some point before committing the new surface. - - The client is free to dismiss all but the last configure event it - received. - - The width and height arguments specify the size of the window in - surface-local coordinates. - - The size is a hint, in the sense that the client is free to ignore it if - it doesn't resize, pick a smaller size (to satisfy aspect ratio or - resize in steps of NxM pixels). If the client picks a smaller size and - is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the - surface will be centered on this axis. - - If the width or height arguments are zero, it means the client should - decide its own window dimension. - - - - - - - - - The closed event is sent by the compositor when the surface will no - longer be shown. The output may have been destroyed or the user may - have asked for it to be removed. Further changes to the surface will be - ignored. The client should destroy the resource after receiving this - event, and create a new surface if they so choose. - - - - - - - - - - - - - - - - - From 8561c790bddd07ea8e5f2824b55b3d36fa8c752d Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Wed, 11 Sep 2019 12:04:55 +0300 Subject: [PATCH 018/139] Workaround id_rsa.pub not being identified as text And extensively docomment why mime_type_is_text() does what it does, including this workaround. --- src/util/string.c | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/util/string.c b/src/util/string.c index b7b3876..f39db31 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -22,15 +22,35 @@ #include int mime_type_is_text(const char *mime_type) { - return str_has_prefix(mime_type, "text/") + /* A heuristic to detect plain text mime types */ + + /* Types that explicitly declare they're textual */ + int basic + = str_has_prefix(mime_type, "text/") || strcmp(mime_type, "TEXT") == 0 || strcmp(mime_type, "STRING") == 0 - || strcmp(mime_type, "UTF8_STRING") == 0 + || strcmp(mime_type, "UTF8_STRING") == 0; + + /* Common script and markup types */ + int common + = strstr(mime_type, "json") != NULL || str_has_suffix(mime_type, "script") || str_has_suffix(mime_type, "xml") - || str_has_suffix(mime_type, "yaml") - || str_has_suffix(mime_type, "pgp-keys") - || strstr(mime_type, "json") != NULL; + || str_has_suffix(mime_type, "yaml"); + + /* Special-case PGP and SSH keys. + * A public SSH key is typically stored + * in a file that has a name similar to + * id_rsa.pub, which xdg-mime misidentifies + * as being a Publisher file. Note that it + * handles private keys, which do not have + * a .pub extension, correctly. + */ + int special + = strstr(mime_type, "application/vnd.ms-publisher") != NULL + || str_has_suffix(mime_type, "pgp-keys"); + + return basic || common || special; } int str_has_prefix(const char *string, const char *prefix) { From 08b80cbdbf4223f3c21090539a68a2b764ffc5cb Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Wed, 11 Sep 2019 12:29:57 +0300 Subject: [PATCH 019/139] Consolidate all options into struct { ... } options This is in preparation for moving option parsing out to explicit separate functions. --- src/wl-copy.c | 51 ++++++++++++++++++++++++++------------------------ src/wl-paste.c | 7 +++---- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/wl-copy.c b/src/wl-copy.c index 6db93a8..64e5397 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -18,9 +18,17 @@ #include "boilerplate.h" +struct { + int stay_in_foreground; + int clear; + char *mime_type; + int trim_newline; + int paste_once; + int primary; +} options; + char * const *data_to_copy = NULL; char *temp_file_to_copy = NULL; -int paste_once = 0; void do_cancel() { /* We're done! */ @@ -65,7 +73,7 @@ void do_send(const char *mime_type, int fd) { wait(NULL); } - if (paste_once) { + if (options.paste_once) { do_cancel(); } } @@ -379,12 +387,6 @@ int main(int argc, char * const argv[]) { bail("Empty argv"); } - int stay_in_foreground = 0; - int clear = 0; - char *mime_type = NULL; - int primary = 0; - int trim_newline = 0; - static struct option long_options[] = { {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, @@ -415,22 +417,22 @@ int main(int argc, char * const argv[]) { print_usage(stdout, argv[0]); exit(0); case 'p': - primary = 1; + options.primary = 1; break; case 'n': - trim_newline = 1; + options.trim_newline = 1; break; case 'o': - paste_once = 1; + options.paste_once = 1; break; case 'f': - stay_in_foreground = 1; + options.stay_in_foreground = 1; break; case 'c': - clear = 1; + options.clear = 1; break; case 't': - mime_type = strdup(optarg); + options.mime_type = strdup(optarg); break; case 's': requested_seat_name = strdup(optarg); @@ -444,27 +446,28 @@ int main(int argc, char * const argv[]) { init_wayland_globals(); - if (primary) { + if (options.primary) { ensure_has_primary_selection(); } - if (!clear) { + if (!options.clear) { if (optind < argc) { /* Copy our command-line arguments */ data_to_copy = &argv[optind]; } else { /* Copy data from our stdin */ temp_file_to_copy = dump_stdin_into_a_temp_file(); - if (trim_newline) { + if (options.trim_newline) { trim_trailing_newline(temp_file_to_copy); } - if (mime_type == NULL) { - mime_type = infer_mime_type_from_contents(temp_file_to_copy); + if (options.mime_type == NULL) { + options.mime_type + = infer_mime_type_from_contents(temp_file_to_copy); } } } - if (!stay_in_foreground && !clear) { + if (!options.stay_in_foreground && !options.clear) { if (fork() != 0) { /* Move to background. * We fork our process and leave the @@ -475,13 +478,13 @@ int main(int argc, char * const argv[]) { } } - if (!primary) { - init_selection(mime_type); + if (!options.primary) { + init_selection(options.mime_type); } else { - init_primary_selection(mime_type); + init_primary_selection(options.mime_type); } - if (clear) { + if (options.clear) { wl_display_roundtrip(display); exit(0); } diff --git a/src/wl-paste.c b/src/wl-paste.c index 4038ed2..1909994 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -23,6 +23,7 @@ struct { char *inferred_type; int no_newline; int list_types; + int primary; } options; struct { @@ -461,8 +462,6 @@ int main(int argc, char * const argv[]) { bail("Empty argv"); } - int primary = 0; - static struct option long_options[] = { {"version", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, @@ -491,7 +490,7 @@ int main(int argc, char * const argv[]) { print_usage(stdout, argv[0]); exit(0); case 'p': - primary = 1; + options.primary = 1; break; case 'n': options.no_newline = 1; @@ -520,7 +519,7 @@ int main(int argc, char * const argv[]) { init_wayland_globals(); - if (!primary) { + if (!options.primary) { init_selection(); } else { init_primary_selection(); From 139171c82482d19a96ed0735fa7abc287882ff18 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Wed, 11 Sep 2019 12:34:42 +0300 Subject: [PATCH 020/139] Separate option parsing into parse_options() Similar to how printing usage is a separate function, print_usage(). --- src/wl-copy.c | 7 +++++-- src/wl-paste.c | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/wl-copy.c b/src/wl-copy.c index 64e5397..38cf9e3 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -381,8 +381,7 @@ void print_usage(FILE *f, const char *argv0) { ); } -int main(int argc, char * const argv[]) { - +void parse_options(int argc, char * const argv[]) { if (argc < 1) { bail("Empty argv"); } @@ -443,6 +442,10 @@ int main(int argc, char * const argv[]) { exit(1); } } +} + +int main(int argc, char * const argv[]) { + parse_options(argc, argv); init_wayland_globals(); diff --git a/src/wl-paste.c b/src/wl-paste.c index 1909994..76767c2 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -456,8 +456,7 @@ void init_primary_selection() { #endif } -int main(int argc, char * const argv[]) { - +void parse_options(int argc, char * const argv[]) { if (argc < 1) { bail("Empty argv"); } @@ -510,6 +509,11 @@ int main(int argc, char * const argv[]) { exit(1); } } +} + +int main(int argc, char * const argv[]) { + parse_options(argc, argv); + char *path = path_for_fd(STDOUT_FILENO); if (path != NULL && options.explicit_type == NULL) { From 7b3f9c004e7cd4f2e3472b788e79b68b3d672dd9 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Wed, 11 Sep 2019 12:59:51 +0300 Subject: [PATCH 021/139] Make most symbols static We used not to care about this and just make everything externally visible. Now we switch to a different convention where everything is static by default unless it needs to be externally visible. --- src/boilerplate.c | 54 +++++++++++++++++++++-------------------- src/boilerplate.h | 1 - src/wl-copy.c | 62 +++++++++++++++++++++++------------------------ src/wl-paste.c | 61 +++++++++++++++++++++++----------------------- 4 files changed, 90 insertions(+), 88 deletions(-) diff --git a/src/boilerplate.c b/src/boilerplate.c index e16c48e..3fd24a3 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -18,7 +18,9 @@ #include "boilerplate.h" -void registry_global_handler +static void process_new_seat(struct wl_seat *new_seat); + +static void registry_global_handler ( void *data, struct wl_registry *registry, @@ -105,19 +107,19 @@ void registry_global_handler #endif } -void registry_global_remove_handler +static void registry_global_remove_handler ( void *data, struct wl_registry *registry, uint32_t name ) {} -const struct wl_registry_listener registry_listener = { +static const struct wl_registry_listener registry_listener = { .global = registry_global_handler, .global_remove = registry_global_remove_handler }; -void keyboard_keymap_handler +static void keyboard_keymap_handler ( void *data, struct wl_keyboard *keyboard, @@ -128,7 +130,7 @@ void keyboard_keymap_handler close(fd); } -void keyboard_enter_handler +static void keyboard_enter_handler ( void *data, struct wl_keyboard *keyboard, @@ -146,7 +148,7 @@ void keyboard_enter_handler } } -void keyboard_leave_handler +static void keyboard_leave_handler ( void *data, struct wl_keyboard *keyboard, @@ -154,7 +156,7 @@ void keyboard_leave_handler struct wl_surface *surface ) {} -void keyboard_key_handler +static void keyboard_key_handler ( void *data, struct wl_keyboard *keyboard, @@ -164,7 +166,7 @@ void keyboard_key_handler uint32_t state ) {} -void keyboard_modifiers_handler +static void keyboard_modifiers_handler ( void *data, struct wl_keyboard *keyboard, @@ -175,7 +177,7 @@ void keyboard_modifiers_handler uint32_t group ) {} -const struct wl_keyboard_listener keayboard_listener = { +static const struct wl_keyboard_listener keayboard_listener = { .keymap = keyboard_keymap_handler, .enter = keyboard_enter_handler, .leave = keyboard_leave_handler, @@ -183,7 +185,7 @@ const struct wl_keyboard_listener keayboard_listener = { .modifiers = keyboard_modifiers_handler, }; -void seat_capabilities_handler +static void seat_capabilities_handler ( void *data, struct wl_seat *this_seat, @@ -199,7 +201,7 @@ void seat_capabilities_handler } } -void seat_name_handler +static void seat_name_handler ( void *data, struct wl_seat *this_seat, @@ -213,14 +215,14 @@ void seat_name_handler } } -const struct wl_seat_listener seat_listener = { +static const struct wl_seat_listener seat_listener = { .capabilities = seat_capabilities_handler, .name = seat_name_handler }; #define UNSET_CAPABILITIES ((void *) (uint32_t) 35) -void process_new_seat(struct wl_seat *new_seat) { +static void process_new_seat(struct wl_seat *new_seat) { if (seat != NULL) { wl_seat_destroy(new_seat); return; @@ -251,7 +253,7 @@ int ensure_seat_has_keyboard() { #undef UNSET_CAPABILITIES -void shell_surface_ping +static void shell_surface_ping ( void *data, struct wl_shell_surface *shell_surface, @@ -260,7 +262,7 @@ void shell_surface_ping wl_shell_surface_pong(shell_surface, serial); } -void shell_surface_configure +static void shell_surface_configure ( void *data, struct wl_shell_surface *shell_surface, @@ -269,13 +271,13 @@ void shell_surface_configure int32_t height ) {} -void shell_surface_popup_done +static void shell_surface_popup_done ( void *data, struct wl_shell_surface *shell_surface ) {} -const struct wl_shell_surface_listener shell_surface_listener = { +static const struct wl_shell_surface_listener shell_surface_listener = { .ping = shell_surface_ping, .configure = shell_surface_configure, .popup_done = shell_surface_popup_done @@ -283,7 +285,7 @@ const struct wl_shell_surface_listener shell_surface_listener = { #ifdef HAVE_XDG_SHELL -void xdg_toplevel_configure_handler +static void xdg_toplevel_configure_handler ( void *data, struct xdg_toplevel *xdg_toplevel, @@ -292,18 +294,18 @@ void xdg_toplevel_configure_handler struct wl_array *states ) {} -void xdg_toplevel_close_handler +static void xdg_toplevel_close_handler ( void *data, struct xdg_toplevel *xdg_toplevel ) {} -const struct xdg_toplevel_listener xdg_toplevel_listener = { +static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = xdg_toplevel_configure_handler, .close = xdg_toplevel_close_handler }; -void xdg_surface_configure_handler +static void xdg_surface_configure_handler ( void *data, struct xdg_surface *xdg_surface, @@ -312,11 +314,11 @@ void xdg_surface_configure_handler xdg_surface_ack_configure(xdg_surface, serial); } -const struct xdg_surface_listener xdg_surface_listener = { +static const struct xdg_surface_listener xdg_surface_listener = { .configure = xdg_surface_configure_handler }; -void xdg_wm_base_ping_handler +static void xdg_wm_base_ping_handler ( void *data, struct xdg_wm_base *xdg_wm_base, @@ -325,7 +327,7 @@ void xdg_wm_base_ping_handler xdg_wm_base_pong(xdg_wm_base, serial); } -const struct xdg_wm_base_listener xdg_wm_base_listener = { +static const struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = xdg_wm_base_ping_handler }; @@ -514,7 +516,7 @@ void destroy_popup_surface() { static uint32_t global_serial; -void callback_done +static void callback_done ( void *data, struct wl_callback *callback, @@ -523,7 +525,7 @@ void callback_done global_serial = serial; } -const struct wl_callback_listener callback_listener = { +static const struct wl_callback_listener callback_listener = { .done = callback_done }; diff --git a/src/boilerplate.h b/src/boilerplate.h index c553897..d4ea531 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -86,7 +86,6 @@ struct zwlr_data_control_manager_v1 *data_control_manager; struct zwlr_data_control_device_v1 *data_control_device; #endif -void process_new_seat(struct wl_seat *new_seat); const char *requested_seat_name; void init_wayland_globals(void); diff --git a/src/wl-copy.c b/src/wl-copy.c index 38cf9e3..5ad5db3 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -18,7 +18,7 @@ #include "boilerplate.h" -struct { +static struct { int stay_in_foreground; int clear; char *mime_type; @@ -27,10 +27,10 @@ struct { int primary; } options; -char * const *data_to_copy = NULL; -char *temp_file_to_copy = NULL; +static char * const *data_to_copy = NULL; +static char *temp_file_to_copy = NULL; -void do_cancel() { +static void do_cancel() { /* We're done! */ if (temp_file_to_copy != NULL) { execlp("rm", "rm", "-r", dirname(temp_file_to_copy), NULL); @@ -41,7 +41,7 @@ void do_cancel() { } } -void do_send(const char *mime_type, int fd) { +static void do_send(const char *mime_type, int fd) { /* Unset O_NONBLOCK */ fcntl(fd, F_SETFL, 0); if (data_to_copy != NULL) { @@ -78,14 +78,14 @@ void do_send(const char *mime_type, int fd) { } } -void data_source_target_handler +static void data_source_target_handler ( void *data, struct wl_data_source *data_source, const char *mime_type ) {} -void data_source_send_handler +static void data_source_send_handler ( void *data, struct wl_data_source *data_source, @@ -95,7 +95,7 @@ void data_source_send_handler do_send(mime_type, fd); } -void data_source_cancelled_handler +static void data_source_cancelled_handler ( void *data, struct wl_data_source *data_source @@ -103,32 +103,32 @@ void data_source_cancelled_handler do_cancel(); } -const struct wl_data_source_listener data_source_listener = { +static const struct wl_data_source_listener data_source_listener = { .target = data_source_target_handler, .send = data_source_send_handler, .cancelled = data_source_cancelled_handler }; -struct wl_data_source *data_source; +static struct wl_data_source *data_source; -void set_data_selection(uint32_t serial) { +static void set_data_selection(uint32_t serial) { wl_data_device_set_selection(data_device, data_source, serial); wl_display_roundtrip(display); destroy_popup_surface(); } -void try_setting_data_selection_directly() { +static void try_setting_data_selection_directly() { set_data_selection(get_serial()); } -void complain_about_missing_keyboard() { +static void complain_about_missing_keyboard() { bail("Setting primary selection is not supported without a keyboard"); } #ifdef HAVE_GTK_PRIMARY_SELECTION -void gtk_primary_selection_source_send_handler +static void gtk_primary_selection_source_send_handler ( void *data, struct gtk_primary_selection_source *gtk_primary_selection_source, @@ -138,7 +138,7 @@ void gtk_primary_selection_source_send_handler do_send(mime_type, fd); } -void gtk_primary_selection_source_cancelled_handler +static void gtk_primary_selection_source_cancelled_handler ( void *data, struct gtk_primary_selection_source *gtk_primary_selection_source @@ -146,15 +146,15 @@ void gtk_primary_selection_source_cancelled_handler do_cancel(); } -const struct gtk_primary_selection_source_listener +static const struct gtk_primary_selection_source_listener gtk_primary_selection_source_listener = { .send = gtk_primary_selection_source_send_handler, .cancelled = gtk_primary_selection_source_cancelled_handler }; -struct gtk_primary_selection_source *gtk_primary_selection_source; +static struct gtk_primary_selection_source *gtk_primary_selection_source; -void set_gtk_primary_selection(uint32_t serial) { +static void set_gtk_primary_selection(uint32_t serial) { gtk_primary_selection_device_set_selection( gtk_primary_selection_device, @@ -170,7 +170,7 @@ void set_gtk_primary_selection(uint32_t serial) { #ifdef HAVE_WP_PRIMARY_SELECTION -void primary_selection_source_send_handler +static void primary_selection_source_send_handler ( void *data, struct zwp_primary_selection_source_v1 *primary_selection_source, @@ -180,7 +180,7 @@ void primary_selection_source_send_handler do_send(mime_type, fd); } -void primary_selection_source_cancelled_handler +static void primary_selection_source_cancelled_handler ( void *data, struct zwp_primary_selection_source_v1 *primary_selection_source @@ -188,15 +188,15 @@ void primary_selection_source_cancelled_handler do_cancel(); } -const struct zwp_primary_selection_source_v1_listener +static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = { .send = primary_selection_source_send_handler, .cancelled = primary_selection_source_cancelled_handler }; -struct zwp_primary_selection_source_v1 *primary_selection_source; +static struct zwp_primary_selection_source_v1 *primary_selection_source; -void set_primary_selection(uint32_t serial) { +static void set_primary_selection(uint32_t serial) { zwp_primary_selection_device_v1_set_selection( primary_selection_device, @@ -212,7 +212,7 @@ void set_primary_selection(uint32_t serial) { #ifdef HAVE_WLR_DATA_CONTROL -void data_control_source_send_handler +static void data_control_source_send_handler ( void *data, struct zwlr_data_control_source_v1 *data_control_source, @@ -222,7 +222,7 @@ void data_control_source_send_handler do_send(mime_type, fd); } -void data_control_source_cancelled_handler +static void data_control_source_cancelled_handler ( void *data, struct zwlr_data_control_source_v1 *data_source @@ -230,14 +230,14 @@ void data_control_source_cancelled_handler do_cancel(); } -const struct zwlr_data_control_source_v1_listener +static const struct zwlr_data_control_source_v1_listener data_control_source_listener = { .send = data_control_source_send_handler, .cancelled = data_control_source_cancelled_handler }; #endif -void do_offer +static void do_offer ( char *mime_type, void *source, @@ -257,7 +257,7 @@ void do_offer free(mime_type); } -void init_selection(char *mime_type) { +static void init_selection(char *mime_type) { if (use_wlr_data_control) { #ifdef HAVE_WLR_DATA_CONTROL struct zwlr_data_control_source_v1 *data_control_source = @@ -299,7 +299,7 @@ void init_selection(char *mime_type) { } } -void init_primary_selection(char *mime_type) { +static void init_primary_selection(char *mime_type) { ensure_has_primary_selection(); #ifdef HAVE_WP_PRIMARY_SELECTION @@ -354,7 +354,7 @@ void init_primary_selection(char *mime_type) { #endif } -void print_usage(FILE *f, const char *argv0) { +static void print_usage(FILE *f, const char *argv0) { fprintf( f, "Usage:\n" @@ -381,7 +381,7 @@ void print_usage(FILE *f, const char *argv0) { ); } -void parse_options(int argc, char * const argv[]) { +static void parse_options(int argc, char * const argv[]) { if (argc < 1) { bail("Empty argv"); } diff --git a/src/wl-paste.c b/src/wl-paste.c index 76767c2..454a666 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -18,7 +18,7 @@ #include "boilerplate.h" -struct { +static struct { char *explicit_type; char *inferred_type; int no_newline; @@ -26,7 +26,7 @@ struct { int primary; } options; -struct { +static struct { int explicit_available; int inferred_available; int plain_text_utf8_available; @@ -36,7 +36,7 @@ struct { char *any; } available_types; -void do_process_offer(const char *offered_type) { +static void do_process_offer(const char *offered_type) { if (options.list_types) { printf("%s\n", offered_type); } else { @@ -107,7 +107,7 @@ if (available_types.any_text != NULL) \ if (available_types.any != NULL) \ return available_types.any -const char *mime_type_to_request() { +static const char *mime_type_to_request() { if (options.explicit_type != NULL) { if (strcmp(options.explicit_type, "text") == 0) { try_text_plain_utf8; @@ -150,7 +150,7 @@ const char *mime_type_to_request() { #undef try_any_text #undef try_any -void free_types() { +static void free_types() { free(available_types.having_explicit_as_prefix); free(available_types.any_text); free(available_types.any); @@ -158,7 +158,7 @@ void free_types() { free(options.inferred_type); } -void do_paste +static void do_paste ( void *offer, void (*receive_f)(void *offer, const char *mime_type, int fd) @@ -205,7 +205,7 @@ void do_paste exit(0); } -void data_offer_offer +static void data_offer_offer ( void *data, struct wl_data_offer *data_offer, @@ -214,11 +214,11 @@ void data_offer_offer do_process_offer(offered_mime_type); } -const struct wl_data_offer_listener data_offer_listener = { +static const struct wl_data_offer_listener data_offer_listener = { .offer = data_offer_offer }; -void data_device_data_offer +static void data_device_data_offer ( void *data, struct wl_data_device *data_device, @@ -227,7 +227,7 @@ void data_device_data_offer wl_data_offer_add_listener(data_offer, &data_offer_listener, NULL); } -void data_device_selection +static void data_device_selection ( void *data, struct wl_data_device *data_device, @@ -239,14 +239,14 @@ void data_device_selection ); } -const struct wl_data_device_listener data_device_listener = { +static const struct wl_data_device_listener data_device_listener = { .data_offer = data_device_data_offer, .selection = data_device_selection }; #ifdef HAVE_GTK_PRIMARY_SELECTION -void gtk_primary_selection_offer_offer +static void gtk_primary_selection_offer_offer ( void *data, struct gtk_primary_selection_offer *gtk_primary_selection_offer, @@ -255,12 +255,12 @@ void gtk_primary_selection_offer_offer do_process_offer(offered_mime_type); } -const struct gtk_primary_selection_offer_listener +static const struct gtk_primary_selection_offer_listener gtk_primary_selection_offer_listener = { .offer = gtk_primary_selection_offer_offer }; -void gtk_primary_selection_device_data_offer +static void gtk_primary_selection_device_data_offer ( void *data, struct gtk_primary_selection_device *gtk_primary_selection_device, @@ -273,7 +273,7 @@ void gtk_primary_selection_device_data_offer ); } -void gtk_primary_selection_device_selection +static void gtk_primary_selection_device_selection ( void *data, struct gtk_primary_selection_device *gtk_primary_selection_device, @@ -286,7 +286,7 @@ void gtk_primary_selection_device_selection ); } -const struct gtk_primary_selection_device_listener +static const struct gtk_primary_selection_device_listener gtk_primary_selection_device_listener = { .data_offer = gtk_primary_selection_device_data_offer, .selection = gtk_primary_selection_device_selection @@ -296,7 +296,7 @@ gtk_primary_selection_device_listener = { #ifdef HAVE_WP_PRIMARY_SELECTION -void primary_selection_offer_offer +static void primary_selection_offer_offer ( void *data, struct zwp_primary_selection_offer_v1 *primary_selection_offer, @@ -305,12 +305,12 @@ void primary_selection_offer_offer do_process_offer(offered_mime_type); } -const struct zwp_primary_selection_offer_v1_listener +static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = { .offer = primary_selection_offer_offer }; -void primary_selection_device_data_offer +static void primary_selection_device_data_offer ( void *data, struct zwp_primary_selection_device_v1 *primary_selection_device, @@ -323,7 +323,7 @@ void primary_selection_device_data_offer ); } -void primary_selection_device_selection +static void primary_selection_device_selection ( void *data, struct zwp_primary_selection_device_v1 *primary_selection_device, @@ -336,7 +336,7 @@ void primary_selection_device_selection ); } -const struct zwp_primary_selection_device_v1_listener +static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = { .data_offer = primary_selection_device_data_offer, .selection = primary_selection_device_selection @@ -345,7 +345,7 @@ primary_selection_device_listener = { #endif #ifdef HAVE_WLR_DATA_CONTROL -void data_control_offer_offer +static void data_control_offer_offer ( void *data, struct zwlr_data_control_offer_v1 *data_offer, @@ -354,11 +354,12 @@ void data_control_offer_offer do_process_offer(offered_mime_type); } -const struct zwlr_data_control_offer_v1_listener data_control_offer_listener = { +static const struct zwlr_data_control_offer_v1_listener +data_control_offer_listener = { .offer = data_control_offer_offer }; -void data_control_device_data_offer +static void data_control_device_data_offer ( void *data, struct zwlr_data_control_device_v1 *data_control_device, @@ -371,7 +372,7 @@ void data_control_device_data_offer ); } -void data_control_device_selection +static void data_control_device_selection ( void *data, struct zwlr_data_control_device_v1 *data_control_device, @@ -383,14 +384,14 @@ void data_control_device_selection ); } -const struct zwlr_data_control_device_v1_listener +static const struct zwlr_data_control_device_v1_listener data_control_device_listener = { .data_offer = data_control_device_data_offer, .selection = data_control_device_selection }; #endif -void print_usage(FILE *f, const char *argv0) { +static void print_usage(FILE *f, const char *argv0) { fprintf( f, "Usage:\n" @@ -413,7 +414,7 @@ void print_usage(FILE *f, const char *argv0) { ); } -void init_selection() { +static void init_selection() { if (use_wlr_data_control) { #ifdef HAVE_WLR_DATA_CONTROL zwlr_data_control_device_v1_add_listener( @@ -428,7 +429,7 @@ void init_selection() { } } -void init_primary_selection() { +static void init_primary_selection() { ensure_has_primary_selection(); #ifdef HAVE_WP_PRIMARY_SELECTION @@ -456,7 +457,7 @@ void init_primary_selection() { #endif } -void parse_options(int argc, char * const argv[]) { +static void parse_options(int argc, char * const argv[]) { if (argc < 1) { bail("Empty argv"); } From 53ac8413faf3870e9356cdd9d572230a61f7d3a6 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Wed, 11 Sep 2019 13:08:04 +0300 Subject: [PATCH 022/139] Add a missing shell surface listener This fell through the cracks. The compilers were not issuing an unused symbol warning because of the symbol being public, so now they do. --- src/boilerplate.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/boilerplate.c b/src/boilerplate.c index 3fd24a3..822d13a 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -442,6 +442,11 @@ void popup_tiny_invisible_surface() { if (shell != NULL) { /* Use wl_shell */ shell_surface = wl_shell_get_shell_surface(shell, surface); + wl_shell_surface_add_listener( + shell_surface, + &shell_surface_listener, + NULL + ); wl_shell_surface_set_toplevel(shell_surface); wl_shell_surface_set_title(shell_surface, "wl-clipboard"); } else { From 99b528386c6c574bcf64890f3bc7fb0e931736d9 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Wed, 11 Sep 2019 13:29:56 +0300 Subject: [PATCH 023/139] Make sure to always handle fork() failure --- src/util/files.c | 17 +++++++++++++++-- src/wl-copy.c | 25 ++++++++++++++++++------- src/wl-paste.c | 7 ++++++- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/util/files.c b/src/util/files.c index d7a2564..9f4668d 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -104,7 +104,15 @@ char *infer_mime_type_from_contents(const char *file_path) { /* Spawn xdg-mime query filetype */ int pipefd[2]; pipe(pipefd); - if (fork() == 0) { + + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + close(pipefd[0]); + close(pipefd[1]); + return NULL; + } + if (pid == 0) { dup2(pipefd[1], STDOUT_FILENO); close(pipefd[0]); close(pipefd[1]); @@ -205,7 +213,12 @@ char *dump_stdin_into_a_temp_file() { strcpy(res_path + sizeof(dirpath), name); /* Spawn cat to perform the copy */ - if (fork() == 0) { + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + if (pid == 0) { int fd = creat(res_path, S_IRUSR | S_IWUSR); if (fd < 0) { perror("creat"); diff --git a/src/wl-copy.c b/src/wl-copy.c index 5ad5db3..ab05bb3 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -63,7 +63,13 @@ static void do_send(const char *mime_type, int fd) { /* Copy from the temp file; for that, we delegate to a * (hopefully) highly optimized implementation of copying. */ - if (fork() == 0) { + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + close(fd); + return; + } + if (pid == 0) { dup2(fd, STDOUT_FILENO); execlp("cat", "cat", temp_file_to_copy, NULL); perror("exec cat"); @@ -471,12 +477,17 @@ int main(int argc, char * const argv[]) { } if (!options.stay_in_foreground && !options.clear) { - if (fork() != 0) { - /* Move to background. - * We fork our process and leave the - * child running in the background, - * while exiting in the parent. - */ + /* Move to background. + * We fork our process and leave the + * child running in the background, + * while exiting in the parent. + */ + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + /* Proceed without forking */ + } + if (pid > 0) { exit(0); } } diff --git a/src/wl-paste.c b/src/wl-paste.c index 454a666..47068c1 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -188,7 +188,12 @@ static void do_paste wl_display_roundtrip(display); /* Spawn a cat to perform the copy */ - if (fork() == 0) { + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + if (pid == 0) { dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); close(pipefd[1]); From a344a7a7762482857207c9c45ea04d8fbc055348 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Wed, 11 Sep 2019 13:33:50 +0300 Subject: [PATCH 024/139] Do not allow any positional arguments to wl-paste They were previously silently ignored. --- src/wl-paste.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/wl-paste.c b/src/wl-paste.c index 47068c1..af46b94 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -515,6 +515,12 @@ static void parse_options(int argc, char * const argv[]) { exit(1); } } + + if (optind != argc) { + fprintf(stderr, "Unexpected argument: %s\n", argv[optind]); + print_usage(stderr, argv[0]); + exit(1); + } } int main(int argc, char * const argv[]) { From f8ab0d1a3cfa7247c8c2c3e2aca0eaa25c77045b Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 12:32:39 +0300 Subject: [PATCH 025/139] Move Wayland protocol includes to their own files --- src/boilerplate.h | 18 ++------------ src/includes/selection-protocols.h | 38 ++++++++++++++++++++++++++++++ src/includes/shell-protocols.h | 30 +++++++++++++++++++++++ src/meson.build | 3 +++ 4 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 src/includes/selection-protocols.h create mode 100644 src/includes/shell-protocols.h diff --git a/src/boilerplate.h b/src/boilerplate.h index d4ea531..b6e0fbb 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -37,22 +37,8 @@ #include #include // PATH_MAX - -#ifdef HAVE_XDG_SHELL -# include "xdg-shell.h" -#endif - -#ifdef HAVE_WP_PRIMARY_SELECTION -# include "wp-primary-selection.h" -#endif - -#ifdef HAVE_GTK_PRIMARY_SELECTION -# include "gtk-primary-selection.h" -#endif - -#ifdef HAVE_WLR_DATA_CONTROL -# include "wlr-data-control.h" -#endif +#include "includes/shell-protocols.h" +#include "includes/selection-protocols.h" struct wl_display *display; struct wl_data_device_manager *data_device_manager; diff --git a/src/includes/selection-protocols.h b/src/includes/selection-protocols.h new file mode 100644 index 0000000..32e74a7 --- /dev/null +++ b/src/includes/selection-protocols.h @@ -0,0 +1,38 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef INCLUDES_SELECTION_PROTOCOLS_H +#define INCLUDES_SELECTION_PROTOCOLS_H + +#include "config.h" + +#include + +#ifdef HAVE_WP_PRIMARY_SELECTION +# include "wp-primary-selection.h" +#endif + +#ifdef HAVE_GTK_PRIMARY_SELECTION +# include "gtk-primary-selection.h" +#endif + +#ifdef HAVE_WLR_DATA_CONTROL +# include "wlr-data-control.h" +#endif + +#endif /* INCLUDES_SELECTION_PROTOCOLS_H */ diff --git a/src/includes/shell-protocols.h b/src/includes/shell-protocols.h new file mode 100644 index 0000000..feb39f5 --- /dev/null +++ b/src/includes/shell-protocols.h @@ -0,0 +1,30 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef INCLUDES_SHELL_PROTOCOLS_H +#define INCLUDES_SHELL_PROTOCOLS_H + +#include "config.h" + +#include + +#ifdef HAVE_XDG_SHELL +# include "xdg-shell.h" +#endif + +#endif /* INCLUDES_SHELL_PROTOCOLS_H */ diff --git a/src/meson.build b/src/meson.build index fa42d57..f517cd7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -96,6 +96,9 @@ boilerplate = static_library( 'util/files.c', 'util/misc.h', 'util/misc.c', + + 'includes/shell-protocols.h', + 'includes/selection-protocols.h', ], dependencies: wayland, link_with: protocol_deps From c60e286d9aed3c7c23718881f6b91912c620cb24 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 12:36:51 +0300 Subject: [PATCH 026/139] Rename libwl-clipboard-boilerplate to just libwl-clipboard It's getting increasingly more structured and less boilerplaity, and this trend is only going to continue. It still remains an internal static library, at least for now. --- src/meson.build | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/meson.build b/src/meson.build index f517cd7..aaf5e68 100644 --- a/src/meson.build +++ b/src/meson.build @@ -85,8 +85,8 @@ foreach protocol : protocols protocol_deps += lib endforeach -boilerplate = static_library( - 'wl-clipboard-boilerplate', +lib = static_library( + 'wl-clipboard', [ 'boilerplate.c', @@ -104,5 +104,5 @@ boilerplate = static_library( link_with: protocol_deps ) -executable('wl-copy', 'wl-copy.c', dependencies: wayland, link_with: boilerplate, install: true) -executable('wl-paste', 'wl-paste.c', dependencies: wayland, link_with: boilerplate, install: true) +executable('wl-copy', 'wl-copy.c', dependencies: wayland, link_with: lib, install: true) +executable('wl-paste', 'wl-paste.c', dependencies: wayland, link_with: lib, install: true) From 6529643bb590a14771b83c03513f049512c4758f Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 13:10:00 +0300 Subject: [PATCH 027/139] Move source handling to types/source This is the first in a series of commits moving parts of logic that was previously implemented in wl-copy/wl-paste into separate files, with separate types (structures) that encapsulate, to a certain degree, the implementation details of a type, somewhat OOP style. --- src/meson.build | 3 + src/types/source.c | 129 +++++++++++++++++++++++++ src/types/source.h | 55 +++++++++++ src/wl-copy.c | 229 +++++++++++---------------------------------- 4 files changed, 241 insertions(+), 175 deletions(-) create mode 100644 src/types/source.c create mode 100644 src/types/source.h diff --git a/src/meson.build b/src/meson.build index aaf5e68..51f8d4b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -99,6 +99,9 @@ lib = static_library( 'includes/shell-protocols.h', 'includes/selection-protocols.h', + + 'types/source.h', + 'types/source.c', ], dependencies: wayland, link_with: protocol_deps diff --git a/src/types/source.c b/src/types/source.c new file mode 100644 index 0000000..a59110d --- /dev/null +++ b/src/types/source.c @@ -0,0 +1,129 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" +#include "includes/selection-protocols.h" +#include "types/source.h" +#include "util/string.h" + +#include + +void source_offer(struct source *self, char *mime_type) { + self->do_offer(self->proxy, mime_type); +} + + +/* Macros to reduce implementation boilerplate */ + +#define SEND_HANDLER(type) \ +static void type ## _send_handler( \ + void *data, \ + struct type *proxy, \ + const char *mime_type, \ + int fd \ +) { \ + struct source *self = data; \ + if (self->send_callback != NULL) { \ + self->send_callback(self, mime_type, fd); \ + } else { \ + close(fd); \ + } \ +} + +#define CANCELLED_HANDLER(type) \ +static void type ## _cancelled_handler( \ + void *data, \ + struct type *proxy \ +) { \ + struct source *self = data; \ + if (self->cancelled_callback != NULL) { \ + self->cancelled_callback(self); \ + } \ +} + +#define LISTENER(type) \ +static const struct type ## _listener type ## _listener = { \ + .send = type ## _send_handler, \ + .cancelled = type ## _cancelled_handler \ +}; + +#define INIT(type) \ +void source_init_ ## type(struct source *self) { \ + self->do_offer = \ + (void (*)(struct wl_proxy *, const char *)) type ## _offer; \ + struct type *proxy = (struct type *) self->proxy; \ + type ## _add_listener(proxy, &type ## _listener, self); \ +} + + +/* Core Wayland implementation */ + +static void wl_data_source_target_handler +( + void *data, + struct wl_data_source *wl_data_source, + const char *mime_type +) {} + +SEND_HANDLER(wl_data_source) +CANCELLED_HANDLER(wl_data_source) + +static const struct wl_data_source_listener wl_data_source_listener = { + .target = wl_data_source_target_handler, + .send = wl_data_source_send_handler, + .cancelled = wl_data_source_cancelled_handler +}; + +INIT(wl_data_source) + + +/* gtk-primary-selection implementation */ + +#ifdef HAVE_GTK_PRIMARY_SELECTION + +SEND_HANDLER(gtk_primary_selection_source) +CANCELLED_HANDLER(gtk_primary_selection_source) +LISTENER(gtk_primary_selection_source) +INIT(gtk_primary_selection_source) + +#endif /* HAVE_GTK_PRIMARY_SELECTION */ + + +/* wp-primary-selection implementation */ + +#ifdef HAVE_WP_PRIMARY_SELECTION + +SEND_HANDLER(zwp_primary_selection_source_v1) +CANCELLED_HANDLER(zwp_primary_selection_source_v1) +LISTENER(zwp_primary_selection_source_v1) +INIT(zwp_primary_selection_source_v1) + +#endif /* HAVE_WP_PRIMARY_SELECTION */ + + +/* wlr-data-control implementation */ + +#ifdef HAVE_WLR_DATA_CONTROL + +SEND_HANDLER(zwlr_data_control_source_v1) +CANCELLED_HANDLER(zwlr_data_control_source_v1) +LISTENER(zwlr_data_control_source_v1) +INIT(zwlr_data_control_source_v1) + +#endif /* HAVE_WLR_DATA_CONTROL */ + diff --git a/src/types/source.h b/src/types/source.h new file mode 100644 index 0000000..b471fe7 --- /dev/null +++ b/src/types/source.h @@ -0,0 +1,55 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_SOURCE_H +#define TYPES_SOURCE_H + +#include "includes/selection-protocols.h" + + +struct source { + /* These fields are initialized by the creator */ + void (*send_callback)(struct source *self, const char *mime_type, int fd); + void (*cancelled_callback)(struct source *self); + void *data; + + struct wl_proxy *proxy; + + /* This field is initialized by the implementation */ + void (*do_offer)(struct wl_proxy *proxy, const char *mime_type); +}; + +void source_offer(struct source *self, char *mime_type); + +/* Initializers */ + +void source_init_wl_data_source(struct source *self); + +#ifdef HAVE_GTK_PRIMARY_SELECTION +void source_init_gtk_primary_selection_source(struct source *self); +#endif + +#ifdef HAVE_WP_PRIMARY_SELECTION +void source_init_zwp_primary_selection_source_v1(struct source *self); +#endif + +#ifdef HAVE_WLR_DATA_CONTROL +void source_init_zwlr_data_control_source_v1(struct source *self); +#endif + +#endif /* TYPES_SOURCE_H */ diff --git a/src/wl-copy.c b/src/wl-copy.c index ab05bb3..0aad31c 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -17,6 +17,7 @@ */ #include "boilerplate.h" +#include "types/source.h" static struct { int stay_in_foreground; @@ -30,7 +31,9 @@ static struct { static char * const *data_to_copy = NULL; static char *temp_file_to_copy = NULL; -static void do_cancel() { +static struct source *source = NULL; + +static void cancelled_callback(struct source *source) { /* We're done! */ if (temp_file_to_copy != NULL) { execlp("rm", "rm", "-r", dirname(temp_file_to_copy), NULL); @@ -41,7 +44,11 @@ static void do_cancel() { } } -static void do_send(const char *mime_type, int fd) { +static void send_callback( + struct source *source, + const char *mime_type, + int fd +) { /* Unset O_NONBLOCK */ fcntl(fd, F_SETFL, 0); if (data_to_copy != NULL) { @@ -80,44 +87,13 @@ static void do_send(const char *mime_type, int fd) { } if (options.paste_once) { - do_cancel(); + cancelled_callback(source); } } -static void data_source_target_handler -( - void *data, - struct wl_data_source *data_source, - const char *mime_type -) {} - -static void data_source_send_handler -( - void *data, - struct wl_data_source *data_source, - const char *mime_type, - int fd -) { - do_send(mime_type, fd); -} - -static void data_source_cancelled_handler -( - void *data, - struct wl_data_source *data_source -) { - do_cancel(); -} - -static const struct wl_data_source_listener data_source_listener = { - .target = data_source_target_handler, - .send = data_source_send_handler, - .cancelled = data_source_cancelled_handler -}; - -static struct wl_data_source *data_source; - static void set_data_selection(uint32_t serial) { + struct wl_data_source *data_source + = (struct wl_data_source *) source->proxy; wl_data_device_set_selection(data_device, data_source, serial); wl_display_roundtrip(display); destroy_popup_surface(); @@ -127,40 +103,15 @@ static void try_setting_data_selection_directly() { set_data_selection(get_serial()); } - static void complain_about_missing_keyboard() { bail("Setting primary selection is not supported without a keyboard"); } #ifdef HAVE_GTK_PRIMARY_SELECTION -static void gtk_primary_selection_source_send_handler -( - void *data, - struct gtk_primary_selection_source *gtk_primary_selection_source, - const char *mime_type, - int fd -) { - do_send(mime_type, fd); -} - -static void gtk_primary_selection_source_cancelled_handler -( - void *data, - struct gtk_primary_selection_source *gtk_primary_selection_source -) { - do_cancel(); -} - -static const struct gtk_primary_selection_source_listener -gtk_primary_selection_source_listener = { - .send = gtk_primary_selection_source_send_handler, - .cancelled = gtk_primary_selection_source_cancelled_handler -}; - -static struct gtk_primary_selection_source *gtk_primary_selection_source; - static void set_gtk_primary_selection(uint32_t serial) { + struct gtk_primary_selection_source *gtk_primary_selection_source + = (struct gtk_primary_selection_source *) source->proxy; gtk_primary_selection_device_set_selection( gtk_primary_selection_device, @@ -176,37 +127,13 @@ static void set_gtk_primary_selection(uint32_t serial) { #ifdef HAVE_WP_PRIMARY_SELECTION -static void primary_selection_source_send_handler -( - void *data, - struct zwp_primary_selection_source_v1 *primary_selection_source, - const char *mime_type, - int fd -) { - do_send(mime_type, fd); -} - -static void primary_selection_source_cancelled_handler -( - void *data, - struct zwp_primary_selection_source_v1 *primary_selection_source -) { - do_cancel(); -} - -static const struct zwp_primary_selection_source_v1_listener -primary_selection_source_listener = { - .send = primary_selection_source_send_handler, - .cancelled = primary_selection_source_cancelled_handler -}; - -static struct zwp_primary_selection_source_v1 *primary_selection_source; - static void set_primary_selection(uint32_t serial) { + struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1 + = (struct zwp_primary_selection_source_v1 *) source->proxy; zwp_primary_selection_device_v1_set_selection( primary_selection_device, - primary_selection_source, + zwp_primary_selection_source_v1, serial ); @@ -216,49 +143,17 @@ static void set_primary_selection(uint32_t serial) { #endif -#ifdef HAVE_WLR_DATA_CONTROL - -static void data_control_source_send_handler -( - void *data, - struct zwlr_data_control_source_v1 *data_control_source, - const char *mime_type, - int fd -) { - do_send(mime_type, fd); -} - -static void data_control_source_cancelled_handler -( - void *data, - struct zwlr_data_control_source_v1 *data_source -) { - do_cancel(); -} - -static const struct zwlr_data_control_source_v1_listener -data_control_source_listener = { - .send = data_control_source_send_handler, - .cancelled = data_control_source_cancelled_handler -}; -#endif - -static void do_offer -( - char *mime_type, - void *source, - void (*offer_f)(void *source, const char *type) -) { +static void do_offer(char *mime_type, struct source *source) { if (mime_type == NULL || mime_type_is_text(mime_type)) { /* Offer a few generic plain text formats */ - offer_f(source, text_plain); - offer_f(source, text_plain_utf8); - offer_f(source, "TEXT"); - offer_f(source, "STRING"); - offer_f(source, "UTF8_STRING"); + source_offer(source, text_plain); + source_offer(source, text_plain_utf8); + source_offer(source, "TEXT"); + source_offer(source, "STRING"); + source_offer(source, "UTF8_STRING"); } if (mime_type != NULL) { - offer_f(source, mime_type); + source_offer(source, mime_type); } free(mime_type); } @@ -266,21 +161,15 @@ static void do_offer static void init_selection(char *mime_type) { if (use_wlr_data_control) { #ifdef HAVE_WLR_DATA_CONTROL - struct zwlr_data_control_source_v1 *data_control_source = - zwlr_data_control_manager_v1_create_data_source( + struct zwlr_data_control_source_v1 *data_control_source + = zwlr_data_control_manager_v1_create_data_source( data_control_manager ); - zwlr_data_control_source_v1_add_listener( - data_control_source, - &data_control_source_listener, - NULL - ); - do_offer( - mime_type, - data_control_source, - (void (*)(void *, const char *)) zwlr_data_control_source_v1_offer - ); + source->proxy = (struct wl_proxy *) data_control_source; + source_init_zwlr_data_control_source_v1(source); + + do_offer(mime_type, source); zwlr_data_control_device_v1_set_selection( data_control_device, @@ -288,16 +177,15 @@ static void init_selection(char *mime_type) { ); #endif } else { - data_source = wl_data_device_manager_create_data_source( - data_device_manager - ); - wl_data_source_add_listener(data_source, &data_source_listener, NULL); + struct wl_data_source *data_source + = wl_data_device_manager_create_data_source( + data_device_manager + ); - do_offer( - mime_type, - data_source, - (void (*)(void *, const char *)) wl_data_source_offer - ); + source->proxy = (struct wl_proxy *) data_source; + source_init_wl_data_source(source); + + do_offer(mime_type, source); action_on_popup_surface_getting_focus = set_data_selection; action_on_no_keyboard = try_setting_data_selection_directly; @@ -310,22 +198,15 @@ static void init_primary_selection(char *mime_type) { #ifdef HAVE_WP_PRIMARY_SELECTION if (primary_selection_device_manager != NULL) { - primary_selection_source = - zwp_primary_selection_device_manager_v1_create_source( + struct zwp_primary_selection_source_v1 *primary_selection_source + = zwp_primary_selection_device_manager_v1_create_source( primary_selection_device_manager ); - zwp_primary_selection_source_v1_add_listener( - primary_selection_source, - &primary_selection_source_listener, - NULL - ); - do_offer( - mime_type, - primary_selection_source, - (void (*)(void *, const char *)) - zwp_primary_selection_source_v1_offer - ); + source->proxy = (struct wl_proxy *) primary_selection_source; + source_init_zwp_primary_selection_source_v1(source); + + do_offer(mime_type, source); action_on_popup_surface_getting_focus = set_primary_selection; action_on_no_keyboard = complain_about_missing_keyboard; @@ -336,21 +217,15 @@ static void init_primary_selection(char *mime_type) { #ifdef HAVE_GTK_PRIMARY_SELECTION if (gtk_primary_selection_device_manager != NULL) { - gtk_primary_selection_source = - gtk_primary_selection_device_manager_create_source( + struct gtk_primary_selection_source* gtk_primary_selection_source + = gtk_primary_selection_device_manager_create_source( gtk_primary_selection_device_manager ); - gtk_primary_selection_source_add_listener( - gtk_primary_selection_source, - >k_primary_selection_source_listener, - NULL - ); - do_offer( - mime_type, - gtk_primary_selection_source, - (void (*)(void *, const char *)) gtk_primary_selection_source_offer - ); + source->proxy = (struct wl_proxy *) gtk_primary_selection_source; + source_init_gtk_primary_selection_source(source); + + do_offer(mime_type, source); action_on_popup_surface_getting_focus = set_gtk_primary_selection; action_on_no_keyboard = complain_about_missing_keyboard; @@ -492,6 +367,10 @@ int main(int argc, char * const argv[]) { } } + source = calloc(1, sizeof(struct source)); + source->send_callback = send_callback; + source->cancelled_callback = cancelled_callback; + if (!options.primary) { init_selection(options.mime_type); } else { From 2f3d6e7f0510fdfc87b85d549126622dce8ee2d1 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 13:59:14 +0300 Subject: [PATCH 028/139] Move offer handling to types/offer --- src/meson.build | 2 + src/types/offer.c | 101 ++++++++++++++++++++++++++++ src/types/offer.h | 55 ++++++++++++++++ src/wl-paste.c | 165 +++++++++++++++------------------------------- 4 files changed, 212 insertions(+), 111 deletions(-) create mode 100644 src/types/offer.c create mode 100644 src/types/offer.h diff --git a/src/meson.build b/src/meson.build index 51f8d4b..eee4058 100644 --- a/src/meson.build +++ b/src/meson.build @@ -102,6 +102,8 @@ lib = static_library( 'types/source.h', 'types/source.c', + 'types/offer.h', + 'types/offer.c', ], dependencies: wayland, link_with: protocol_deps diff --git a/src/types/offer.c b/src/types/offer.c new file mode 100644 index 0000000..737b358 --- /dev/null +++ b/src/types/offer.c @@ -0,0 +1,101 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types/offer.h" +#include "includes/selection-protocols.h" + +#include + +void offer_receive(struct offer *self, const char *mime_type, int fd) { + self->do_receive(self->proxy, mime_type, fd); +} + +void offer_destroy(struct offer *self) { + self->do_destroy(self->proxy); + free(self); +} + + +/* Macros to reduce implementation boilerplate */ + +#define OFFER_HANDLER(type) \ +static void type ## _offer_handler( \ + void *data, \ + struct type *proxy, \ + const char *mime_type \ +) { \ + struct offer *self = data; \ + if (self->offer_callback != NULL) { \ + self->offer_callback(self, mime_type); \ + } \ +} + +#define LISTENER(type) \ +static const struct type ## _listener type ## _listener = { \ + .offer = type ## _offer_handler \ +}; + +#define INIT(type) \ +void offer_init_ ## type(struct offer *self) { \ + self->do_receive = \ + (void (*)(struct wl_proxy *, const char *, int)) type ## _receive; \ + self->do_destroy = (void (*)(struct wl_proxy *)) type ## _destroy; \ + struct type *proxy = (struct type *) self->proxy; \ + type ## _add_listener(proxy, &type ## _listener, self); \ +} + + +/* Core Wayland implementation */ + +OFFER_HANDLER(wl_data_offer) +LISTENER(wl_data_offer) +INIT(wl_data_offer) + + +/* gtk-primary-selection implementation */ + +#ifdef HAVE_GTK_PRIMARY_SELECTION + +OFFER_HANDLER(gtk_primary_selection_offer) +LISTENER(gtk_primary_selection_offer) +INIT(gtk_primary_selection_offer) + +#endif /* HAVE_GTK_PRIMARY_SELECTION */ + + +/* wp-primary-selection implementation */ + +#ifdef HAVE_WP_PRIMARY_SELECTION + +OFFER_HANDLER(zwp_primary_selection_offer_v1) +LISTENER(zwp_primary_selection_offer_v1) +INIT(zwp_primary_selection_offer_v1) + +#endif /* HAVE_WP_PRIMARY_SELECTION */ + + +/* wlr-data-control implementation */ + +#ifdef HAVE_WLR_DATA_CONTROL + +OFFER_HANDLER(zwlr_data_control_offer_v1) +LISTENER(zwlr_data_control_offer_v1) +INIT(zwlr_data_control_offer_v1) + +#endif /* HAVE_WLR_DATA_CONTROL */ + diff --git a/src/types/offer.h b/src/types/offer.h new file mode 100644 index 0000000..59ea053 --- /dev/null +++ b/src/types/offer.h @@ -0,0 +1,55 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_OFFER_H +#define TYPES_OFFER_H + +#include "includes/selection-protocols.h" + +struct offer { + /* These fields are initialized by the creator */ + void (*offer_callback)(struct offer *self, const char *mime_type); + void *data; + + struct wl_proxy *proxy; + + /* These fields are initialized by the implementation */ + void (*do_receive)(struct wl_proxy *proxy, const char *mime_type, int fd); + void (*do_destroy)(struct wl_proxy *proxy); +}; + +void offer_receive(struct offer *self, const char *mime_type, int fd); +void offer_destroy(struct offer *self); + +/* Initializers */ + +void offer_init_wl_data_offer(struct offer *self); + +#ifdef HAVE_GTK_PRIMARY_SELECTION +void offer_init_gtk_primary_selection_offer(struct offer *self); +#endif + +#ifdef HAVE_WP_PRIMARY_SELECTION +void offer_init_zwp_primary_selection_offer_v1(struct offer *self); +#endif + +#ifdef HAVE_WLR_DATA_CONTROL +void offer_init_zwlr_data_control_offer_v1(struct offer *self); +#endif + +#endif /* TYPES_OFFER_H */ diff --git a/src/wl-paste.c b/src/wl-paste.c index af46b94..cf02379 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -17,6 +17,7 @@ */ #include "boilerplate.h" +#include "types/offer.h" static struct { char *explicit_type; @@ -36,7 +37,7 @@ static struct { char *any; } available_types; -static void do_process_offer(const char *offered_type) { +static void offer_callback(struct offer *offer, const char *offered_type) { if (options.list_types) { printf("%s\n", offered_type); } else { @@ -158,11 +159,7 @@ static void free_types() { free(options.inferred_type); } -static void do_paste -( - void *offer, - void (*receive_f)(void *offer, const char *mime_type, int fd) -) { +static void do_paste(struct offer *offer) { if (offer == NULL) { bail("No selection"); } @@ -180,7 +177,7 @@ static void do_paste int pipefd[2]; pipe(pipefd); - receive_f(offer, mime_type, pipefd[1]); + offer_receive(offer, mime_type, pipefd[1]); free_types(); destroy_popup_surface(); @@ -210,38 +207,27 @@ static void do_paste exit(0); } -static void data_offer_offer -( - void *data, - struct wl_data_offer *data_offer, - const char *offered_mime_type -) { - do_process_offer(offered_mime_type); -} - -static const struct wl_data_offer_listener data_offer_listener = { - .offer = data_offer_offer -}; - -static void data_device_data_offer -( +static void data_device_data_offer( void *data, struct wl_data_device *data_device, struct wl_data_offer *data_offer ) { - wl_data_offer_add_listener(data_offer, &data_offer_listener, NULL); + struct offer *offer = calloc(1, sizeof(struct offer)); + offer->proxy = (struct wl_proxy *) data_offer; + offer->offer_callback = offer_callback; + offer_init_wl_data_offer(offer); } -static void data_device_selection -( +static void data_device_selection( void *data, struct wl_data_device *data_device, struct wl_data_offer *data_offer ) { - do_paste( - data_offer, - (void (*)(void *, const char *, int)) wl_data_offer_receive - ); + struct offer *offer = NULL; + if (data_offer != NULL) { + offer = (struct offer *) wl_data_offer_get_user_data(data_offer); + } + do_paste(offer); } static const struct wl_data_device_listener data_device_listener = { @@ -251,44 +237,29 @@ static const struct wl_data_device_listener data_device_listener = { #ifdef HAVE_GTK_PRIMARY_SELECTION -static void gtk_primary_selection_offer_offer -( - void *data, - struct gtk_primary_selection_offer *gtk_primary_selection_offer, - const char *offered_mime_type -) { - do_process_offer(offered_mime_type); -} - -static const struct gtk_primary_selection_offer_listener -gtk_primary_selection_offer_listener = { - .offer = gtk_primary_selection_offer_offer -}; - -static void gtk_primary_selection_device_data_offer -( +static void gtk_primary_selection_device_data_offer( void *data, struct gtk_primary_selection_device *gtk_primary_selection_device, struct gtk_primary_selection_offer *gtk_primary_selection_offer ) { - gtk_primary_selection_offer_add_listener( - gtk_primary_selection_offer, - >k_primary_selection_offer_listener, - NULL - ); + struct offer *offer = calloc(1, sizeof(struct offer)); + offer->proxy = (struct wl_proxy *) gtk_primary_selection_offer; + offer->offer_callback = offer_callback; + offer_init_gtk_primary_selection_offer(offer); } -static void gtk_primary_selection_device_selection -( +static void gtk_primary_selection_device_selection( void *data, struct gtk_primary_selection_device *gtk_primary_selection_device, struct gtk_primary_selection_offer *gtk_primary_selection_offer ) { - do_paste( - gtk_primary_selection_offer, - (void (*)(void *, const char *, int)) - gtk_primary_selection_offer_receive - ); + struct offer *offer = NULL; + if (gtk_primary_selection_offer != NULL) { + offer = (struct offer *) gtk_primary_selection_offer_get_user_data( + gtk_primary_selection_offer + ); + } + do_paste(offer); } static const struct gtk_primary_selection_device_listener @@ -301,44 +272,29 @@ gtk_primary_selection_device_listener = { #ifdef HAVE_WP_PRIMARY_SELECTION -static void primary_selection_offer_offer -( - void *data, - struct zwp_primary_selection_offer_v1 *primary_selection_offer, - const char *offered_mime_type -) { - do_process_offer(offered_mime_type); -} - -static const struct zwp_primary_selection_offer_v1_listener -primary_selection_offer_listener = { - .offer = primary_selection_offer_offer -}; - -static void primary_selection_device_data_offer -( +static void primary_selection_device_data_offer( void *data, struct zwp_primary_selection_device_v1 *primary_selection_device, struct zwp_primary_selection_offer_v1 *primary_selection_offer ) { - zwp_primary_selection_offer_v1_add_listener( - primary_selection_offer, - &primary_selection_offer_listener, - NULL - ); + struct offer *offer = calloc(1, sizeof(struct offer)); + offer->proxy = (struct wl_proxy *) primary_selection_offer; + offer->offer_callback = offer_callback; + offer_init_zwp_primary_selection_offer_v1(offer); } -static void primary_selection_device_selection -( +static void primary_selection_device_selection( void *data, struct zwp_primary_selection_device_v1 *primary_selection_device, struct zwp_primary_selection_offer_v1 *primary_selection_offer ) { - do_paste( - primary_selection_offer, - (void (*)(void *, const char *, int)) - zwp_primary_selection_offer_v1_receive - ); + struct offer *offer = NULL; + if (primary_selection_offer != NULL) { + offer = (struct offer *) zwp_primary_selection_offer_v1_get_user_data( + primary_selection_offer + ); + } + do_paste(offer); } static const struct zwp_primary_selection_device_v1_listener @@ -350,43 +306,30 @@ primary_selection_device_listener = { #endif #ifdef HAVE_WLR_DATA_CONTROL -static void data_control_offer_offer -( - void *data, - struct zwlr_data_control_offer_v1 *data_offer, - const char *offered_mime_type -) { - do_process_offer(offered_mime_type); -} -static const struct zwlr_data_control_offer_v1_listener -data_control_offer_listener = { - .offer = data_control_offer_offer -}; - -static void data_control_device_data_offer -( +static void data_control_device_data_offer( void *data, struct zwlr_data_control_device_v1 *data_control_device, struct zwlr_data_control_offer_v1 *data_control_offer ) { - zwlr_data_control_offer_v1_add_listener( - data_control_offer, - &data_control_offer_listener, - NULL - ); + struct offer *offer = calloc(1, sizeof(struct offer)); + offer->proxy = (struct wl_proxy *) data_control_offer; + offer->offer_callback = offer_callback; + offer_init_zwlr_data_control_offer_v1(offer); } -static void data_control_device_selection -( +static void data_control_device_selection( void *data, struct zwlr_data_control_device_v1 *data_control_device, struct zwlr_data_control_offer_v1 *data_control_offer ) { - do_paste( - data_control_offer, - (void (*)(void *, const char *, int)) zwlr_data_control_offer_v1_receive - ); + struct offer *offer = NULL; + if (data_control_offer != NULL) { + offer = (struct offer *) zwlr_data_control_offer_v1_get_user_data( + data_control_offer + ); + } + do_paste(offer); } static const struct zwlr_data_control_device_v1_listener From e9412d1522d4afd7aa47292a60de3c07ca092c08 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 14:23:09 +0300 Subject: [PATCH 029/139] Move offer mime type handling to struct offer Now, instead of processing offerred mime types as they appear, we will collect them into a single buffer inside struct offer, and then analyze the whole thing when it's ready. This way, in the future it'd be possible to handle the case where we see multiple offers and don't know in adnvance which ones are the ones we need, and which we should ignore. --- src/types/offer.c | 12 ++++-- src/types/offer.h | 18 ++++++-- src/wl-paste.c | 104 ++++++++++++++++++++++------------------------ 3 files changed, 72 insertions(+), 62 deletions(-) diff --git a/src/types/offer.c b/src/types/offer.c index 737b358..c21a562 100644 --- a/src/types/offer.c +++ b/src/types/offer.c @@ -19,7 +19,9 @@ #include "types/offer.h" #include "includes/selection-protocols.h" +#include #include +#include void offer_receive(struct offer *self, const char *mime_type, int fd) { self->do_receive(self->proxy, mime_type, fd); @@ -27,6 +29,7 @@ void offer_receive(struct offer *self, const char *mime_type, int fd) { void offer_destroy(struct offer *self) { self->do_destroy(self->proxy); + wl_array_release(&self->offered_mime_types); free(self); } @@ -40,9 +43,11 @@ static void type ## _offer_handler( \ const char *mime_type \ ) { \ struct offer *self = data; \ - if (self->offer_callback != NULL) { \ - self->offer_callback(self, mime_type); \ - } \ + char *ptr = wl_array_add( \ + &self->offered_mime_types, \ + strlen(mime_type) + 1 \ + ); \ + strcpy(ptr, mime_type); \ } #define LISTENER(type) \ @@ -55,6 +60,7 @@ void offer_init_ ## type(struct offer *self) { \ self->do_receive = \ (void (*)(struct wl_proxy *, const char *, int)) type ## _receive; \ self->do_destroy = (void (*)(struct wl_proxy *)) type ## _destroy; \ + wl_array_init(&self->offered_mime_types); \ struct type *proxy = (struct type *) self->proxy; \ type ## _add_listener(proxy, &type ## _listener, self); \ } diff --git a/src/types/offer.h b/src/types/offer.h index 59ea053..800466b 100644 --- a/src/types/offer.h +++ b/src/types/offer.h @@ -21,18 +21,28 @@ #include "includes/selection-protocols.h" -struct offer { - /* These fields are initialized by the creator */ - void (*offer_callback)(struct offer *self, const char *mime_type); - void *data; +#include +#include +struct offer { + /* This field is initialized by the creator */ struct wl_proxy *proxy; /* These fields are initialized by the implementation */ void (*do_receive)(struct wl_proxy *proxy, const char *mime_type, int fd); void (*do_destroy)(struct wl_proxy *proxy); + struct wl_array offered_mime_types; }; +#define offer_for_each_mime_type(offer, mime_type) \ +for ( \ + const char *mime_type = offer->offered_mime_types.data; \ + mime_type != offer->offered_mime_types.data + \ + offer->offered_mime_types.size; \ + mime_type += strlen(mime_type) + 1 \ +) + + void offer_receive(struct offer *self, const char *mime_type, int fd); void offer_destroy(struct offer *self); diff --git a/src/wl-paste.c b/src/wl-paste.c index cf02379..7936b23 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -27,88 +27,86 @@ static struct { int primary; } options; -static struct { +struct types { int explicit_available; int inferred_available; int plain_text_utf8_available; int plain_text_available; - char *having_explicit_as_prefix; - char *any_text; - char *any; -} available_types; + const char *having_explicit_as_prefix; + const char *any_text; + const char *any; +}; -static void offer_callback(struct offer *offer, const char *offered_type) { - if (options.list_types) { - printf("%s\n", offered_type); - } else { +static struct types classify_offer_types(struct offer *offer) { + struct types types = { 0 }; + offer_for_each_mime_type(offer, mime_type) { if ( options.explicit_type != NULL && - strcmp(offered_type, options.explicit_type) == 0 + strcmp(mime_type, options.explicit_type) == 0 ) { - available_types.explicit_available = 1; + types.explicit_available = 1; } if ( options.inferred_type != NULL && - strcmp(offered_type, options.inferred_type) == 0 + strcmp(mime_type, options.inferred_type) == 0 ) { - available_types.inferred_available = 1; + types.inferred_available = 1; + } + if (strcmp(mime_type, text_plain_utf8) == 0) { + types.plain_text_utf8_available = 1; + } + if (strcmp(mime_type, text_plain) == 0) { + types.plain_text_available = 1; } if ( - strcmp(offered_type, text_plain_utf8) == 0) { - available_types.plain_text_utf8_available = 1; - } - if ( - strcmp(offered_type, text_plain) == 0) { - available_types.plain_text_available = 1; - } - if ( - available_types.any_text == NULL && - mime_type_is_text(offered_type) + types.any_text == NULL && + mime_type_is_text(mime_type) ) { - available_types.any_text = strdup(offered_type); + types.any_text = mime_type; } - if (available_types.any == NULL) { - available_types.any = strdup(offered_type); + if (types.any == NULL) { + types.any = mime_type; } if ( options.explicit_type != NULL && - available_types.having_explicit_as_prefix == NULL && - str_has_prefix(offered_type, options.explicit_type) + types.having_explicit_as_prefix == NULL && + str_has_prefix(mime_type, options.explicit_type) ) { - available_types.having_explicit_as_prefix = strdup(offered_type); + types.having_explicit_as_prefix = mime_type; } } + return types; } #define try_explicit \ -if (available_types.explicit_available) \ +if (types.explicit_available) \ return options.explicit_type #define try_inferred \ -if (available_types.inferred_available) \ +if (types.inferred_available) \ return options.inferred_type #define try_text_plain_utf8 \ -if (available_types.plain_text_utf8_available) \ +if (types.plain_text_utf8_available) \ return text_plain_utf8 #define try_text_plain \ -if (available_types.plain_text_available) \ +if (types.plain_text_available) \ return text_plain #define try_prefixed \ -if (available_types.having_explicit_as_prefix != NULL) \ - return available_types.having_explicit_as_prefix +if (types.having_explicit_as_prefix != NULL) \ + return types.having_explicit_as_prefix #define try_any_text \ -if (available_types.any_text != NULL) \ - return available_types.any_text +if (types.any_text != NULL) \ + return types.any_text #define try_any \ -if (available_types.any != NULL) \ - return available_types.any +if (types.any != NULL) \ + return types.any -static const char *mime_type_to_request() { +static const char *mime_type_to_request(struct types types) { if (options.explicit_type != NULL) { if (strcmp(options.explicit_type, "text") == 0) { try_text_plain_utf8; @@ -151,24 +149,21 @@ static const char *mime_type_to_request() { #undef try_any_text #undef try_any -static void free_types() { - free(available_types.having_explicit_as_prefix); - free(available_types.any_text); - free(available_types.any); - free(options.explicit_type); - free(options.inferred_type); -} - static void do_paste(struct offer *offer) { if (offer == NULL) { bail("No selection"); } if (options.list_types) { + offer_for_each_mime_type(offer, mime_type) { + printf("%s\n", mime_type); + } exit(0); } - const char *mime_type = mime_type_to_request(); + struct types types = classify_offer_types(offer); + const char *mime_type = mime_type_to_request(types); + /* Never append a newline character to binary content */ if (!mime_type_is_text(mime_type)) { options.no_newline = 1; @@ -179,9 +174,7 @@ static void do_paste(struct offer *offer) { offer_receive(offer, mime_type, pipefd[1]); - free_types(); destroy_popup_surface(); - wl_display_roundtrip(display); /* Spawn a cat to perform the copy */ @@ -204,6 +197,11 @@ static void do_paste(struct offer *offer) { if (!options.no_newline) { write(STDOUT_FILENO, "\n", 1); } + + offer_destroy(offer); + + free(options.explicit_type); + free(options.inferred_type); exit(0); } @@ -214,7 +212,6 @@ static void data_device_data_offer( ) { struct offer *offer = calloc(1, sizeof(struct offer)); offer->proxy = (struct wl_proxy *) data_offer; - offer->offer_callback = offer_callback; offer_init_wl_data_offer(offer); } @@ -244,7 +241,6 @@ static void gtk_primary_selection_device_data_offer( ) { struct offer *offer = calloc(1, sizeof(struct offer)); offer->proxy = (struct wl_proxy *) gtk_primary_selection_offer; - offer->offer_callback = offer_callback; offer_init_gtk_primary_selection_offer(offer); } @@ -279,7 +275,6 @@ static void primary_selection_device_data_offer( ) { struct offer *offer = calloc(1, sizeof(struct offer)); offer->proxy = (struct wl_proxy *) primary_selection_offer; - offer->offer_callback = offer_callback; offer_init_zwp_primary_selection_offer_v1(offer); } @@ -314,7 +309,6 @@ static void data_control_device_data_offer( ) { struct offer *offer = calloc(1, sizeof(struct offer)); offer->proxy = (struct wl_proxy *) data_control_offer; - offer->offer_callback = offer_callback; offer_init_zwlr_data_control_offer_v1(offer); } From 5f43e6a1b46d4993baf5cf9368d71b7b6e75292a Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 15:37:53 +0300 Subject: [PATCH 030/139] Move device handling to types/device --- src/boilerplate.c | 33 ------- src/boilerplate.h | 6 -- src/meson.build | 2 + src/types/device.c | 217 +++++++++++++++++++++++++++++++++++++++++++++ src/types/device.h | 73 +++++++++++++++ src/wl-copy.c | 131 ++++++++++++--------------- src/wl-paste.c | 203 +++++++++++------------------------------- 7 files changed, 401 insertions(+), 264 deletions(-) create mode 100644 src/types/device.c create mode 100644 src/types/device.h diff --git a/src/boilerplate.c b/src/boilerplate.c index 822d13a..abbe81a 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -367,39 +367,6 @@ void init_wayland_globals() { } bail("Cannot find the requested seat"); } - - data_device = wl_data_device_manager_get_data_device( - data_device_manager, - seat - ); -#ifdef HAVE_GTK_PRIMARY_SELECTION - if (gtk_primary_selection_device_manager != NULL) { - gtk_primary_selection_device = - gtk_primary_selection_device_manager_get_device( - gtk_primary_selection_device_manager, - seat - ); - } -#endif -#ifdef HAVE_WP_PRIMARY_SELECTION - if (primary_selection_device_manager != NULL) { - primary_selection_device = - zwp_primary_selection_device_manager_v1_get_device( - primary_selection_device_manager, - seat - ); - } -#endif -#ifdef HAVE_WLR_DATA_CONTROL - if (data_control_manager != NULL) { - data_control_device = - zwlr_data_control_manager_v1_get_data_device( - data_control_manager, - seat - ); - use_wlr_data_control = 1; - } -#endif } void ensure_has_primary_selection() { diff --git a/src/boilerplate.h b/src/boilerplate.h index b6e0fbb..fb23c8a 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -55,27 +55,21 @@ struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; #endif -struct wl_data_device *data_device; - #ifdef HAVE_GTK_PRIMARY_SELECTION struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager; -struct gtk_primary_selection_device *gtk_primary_selection_device; #endif #ifdef HAVE_WP_PRIMARY_SELECTION struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager; -struct zwp_primary_selection_device_v1 *primary_selection_device; #endif #ifdef HAVE_WLR_DATA_CONTROL struct zwlr_data_control_manager_v1 *data_control_manager; -struct zwlr_data_control_device_v1 *data_control_device; #endif const char *requested_seat_name; void init_wayland_globals(void); -int use_wlr_data_control; void popup_tiny_invisible_surface(void); void destroy_popup_surface(void); diff --git a/src/meson.build b/src/meson.build index eee4058..8b591cd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -104,6 +104,8 @@ lib = static_library( 'types/source.c', 'types/offer.h', 'types/offer.c', + 'types/device.h', + 'types/device.c', ], dependencies: wayland, link_with: protocol_deps diff --git a/src/types/device.c b/src/types/device.c new file mode 100644 index 0000000..534827f --- /dev/null +++ b/src/types/device.c @@ -0,0 +1,217 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" +#include "types/device.h" +#include "types/offer.h" +#include "types/source.h" +#include "includes/selection-protocols.h" + +#include + +int device_supports_selection(struct device *self, int primary) { + return self->supports_selection(self, primary); +} + +void device_set_selection( + struct device *self, + struct source *source, + uint32_t serial, + int primary +) { + self->do_set_selection(self, source, serial, primary); +} + +/* Macros to reduce implementation boilerplate */ + +#define SUPPORTS_SELECTION(type, expr) \ +static int device_supports_selection_on_ ## type(struct device *self, int primary) { \ + return expr; \ +} + +#define SET_SELECTION_IMPL(type, source_type, impl) \ +static void device_set_selection_on_ ## type( \ + struct device *self, \ + struct source *source_wrapper, \ + uint32_t serial, \ + int primary \ +) { \ + struct type *device = (struct type *) self->proxy; \ + struct source_type *source = NULL; \ + if (source_wrapper != NULL) { \ + source = (struct source_type *) source_wrapper->proxy; \ + } \ + impl \ +} + +#define DATA_OFFER_HANDLER(type, offer_type) \ +static void type ## _data_offer_handler( \ + void *data, \ + struct type *device, \ + struct offer_type *offer_proxy \ +) { \ + struct device *self = data; \ + struct offer *offer = calloc(1, sizeof(struct offer)); \ + offer->proxy = (struct wl_proxy *) offer_proxy; \ + offer_init_ ## offer_type(offer); \ + if (self->new_offer_callback) { \ + self->new_offer_callback(offer); \ + } \ +} + +#define SELECTION_HANDLER(type, offer_type, selection_event, primary) \ +static void type ## _ ## selection_event ## _handler( \ + void *data, \ + struct type *device, \ + struct offer_type *offer_proxy \ +) { \ + struct device *self = data; \ + struct offer *offer = NULL; \ + if (offer_proxy != NULL) { \ + offer = wl_proxy_get_user_data((struct wl_proxy *) offer_proxy); \ + } \ + if (self->selection_callback) { \ + self->selection_callback(offer, primary); \ + } \ +} + +#define INIT(type, needs_surface) \ +void device_init_ ## type(struct device *self) { \ + struct type *device = (struct type *) self->proxy; \ + type ## _add_listener(device, &type ## _listener, self); \ + self->supports_selection = device_supports_selection_on_ ## type; \ + self->needs_popup_surface = needs_surface; \ + self->do_set_selection = device_set_selection_on_ ## type; \ +} + + +/* Core Wayland implementation */ + +SUPPORTS_SELECTION(wl_data_device, !primary) + +SET_SELECTION_IMPL(wl_data_device, wl_data_source, { + wl_data_device_set_selection(device, source, serial); +}) + +DATA_OFFER_HANDLER(wl_data_device, wl_data_offer) + +SELECTION_HANDLER(wl_data_device, wl_data_offer, selection, 0) + +static const struct wl_data_device_listener wl_data_device_listener = { + .data_offer = wl_data_device_data_offer_handler, + .selection = wl_data_device_selection_handler +}; + +INIT(wl_data_device, 1) + + +/* gtk-primary-selection implementation */ + +#ifdef HAVE_GTK_PRIMARY_SELECTION + +SUPPORTS_SELECTION(gtk_primary_selection_device, primary) + +SET_SELECTION_IMPL(gtk_primary_selection_device, gtk_primary_selection_source, { + gtk_primary_selection_device_set_selection(device, source, serial); +}) + +DATA_OFFER_HANDLER(gtk_primary_selection_device, gtk_primary_selection_offer) + +SELECTION_HANDLER( + gtk_primary_selection_device, + gtk_primary_selection_offer, + selection, + 1 +) + +static const struct gtk_primary_selection_device_listener +gtk_primary_selection_device_listener = { + .data_offer = gtk_primary_selection_device_data_offer_handler, + .selection = gtk_primary_selection_device_selection_handler +}; + +INIT(gtk_primary_selection_device, 1) + +#endif /* HAVE_GTK_PRIMARY_SELECTION */ + + +/* wp-primary-selection implementation */ + +#ifdef HAVE_WP_PRIMARY_SELECTION + +SUPPORTS_SELECTION(zwp_primary_selection_device_v1, primary) + +SET_SELECTION_IMPL( + zwp_primary_selection_device_v1, + zwp_primary_selection_source_v1, + { + zwp_primary_selection_device_v1_set_selection(device, source, serial); + } +) + +DATA_OFFER_HANDLER( + zwp_primary_selection_device_v1, + zwp_primary_selection_offer_v1 +) + +SELECTION_HANDLER( + zwp_primary_selection_device_v1, + zwp_primary_selection_offer_v1, + selection, + 1 +) + +static const struct zwp_primary_selection_device_v1_listener +zwp_primary_selection_device_v1_listener = { + .data_offer = zwp_primary_selection_device_v1_data_offer_handler, + .selection = zwp_primary_selection_device_v1_selection_handler +}; + +INIT(zwp_primary_selection_device_v1, 1) + +#endif /* HAVE_WP_PRIMARY_SELECTION */ + + +/* wlr-data-control implementation */ + +#ifdef HAVE_WLR_DATA_CONTROL + +SUPPORTS_SELECTION(zwlr_data_control_device_v1, !primary) + +SET_SELECTION_IMPL(zwlr_data_control_device_v1, zwlr_data_control_source_v1, { + zwlr_data_control_device_v1_set_selection(device, source); +}) + +DATA_OFFER_HANDLER(zwlr_data_control_device_v1, zwlr_data_control_offer_v1) + +SELECTION_HANDLER( + zwlr_data_control_device_v1, + zwlr_data_control_offer_v1, + selection, + 0 +) + +static const struct zwlr_data_control_device_v1_listener +zwlr_data_control_device_v1_listener = { + .data_offer = zwlr_data_control_device_v1_data_offer_handler, + .selection = zwlr_data_control_device_v1_selection_handler, +}; + +INIT(zwlr_data_control_device_v1, 0) + +#endif /* HAVE_WLR_DATA_CONTROL */ diff --git a/src/types/device.h b/src/types/device.h new file mode 100644 index 0000000..0867bb2 --- /dev/null +++ b/src/types/device.h @@ -0,0 +1,73 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_DEVICE_H +#define TYPES_DEVICE_H + +#include "includes/selection-protocols.h" + +#include + +struct offer; +struct source; + +struct device { + /* These fields are initialized by the creator */ + void (*new_offer_callback)(struct offer *offer); + void (*selection_callback)(struct offer *offer, int primary); + void *data; + + struct wl_proxy *proxy; + + /* These fields are initialized by the implementation */ + int (*supports_selection)(struct device *self, int primary); + int needs_popup_surface; + void (*do_set_selection)( + struct device *self, + struct source *source, + uint32_t serial, + int primary + ); +}; + +int device_supports_selection(struct device *self, int primary); + +void device_set_selection( + struct device *self, + struct source *source, + uint32_t serial, + int primary +); + +/* Initializers */ + +void device_init_wl_data_device(struct device *self); + +#ifdef HAVE_GTK_PRIMARY_SELECTION +void device_init_gtk_primary_selection_device(struct device *self); +#endif + +#ifdef HAVE_WP_PRIMARY_SELECTION +void device_init_zwp_primary_selection_device_v1(struct device *self); +#endif + +#ifdef HAVE_WLR_DATA_CONTROL +void device_init_zwlr_data_control_device_v1(struct device *self); +#endif + +#endif /* TYPES_DEVICE_H */ diff --git a/src/wl-copy.c b/src/wl-copy.c index 0aad31c..f3900dc 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -18,6 +18,7 @@ #include "boilerplate.h" #include "types/source.h" +#include "types/device.h" static struct { int stay_in_foreground; @@ -31,6 +32,7 @@ static struct { static char * const *data_to_copy = NULL; static char *temp_file_to_copy = NULL; +static struct device *device = NULL; static struct source *source = NULL; static void cancelled_callback(struct source *source) { @@ -91,58 +93,16 @@ static void send_callback( } } -static void set_data_selection(uint32_t serial) { - struct wl_data_source *data_source - = (struct wl_data_source *) source->proxy; - wl_data_device_set_selection(data_device, data_source, serial); +static void set_selection(uint32_t serial) { + device_set_selection(device, source, serial, options.primary); wl_display_roundtrip(display); destroy_popup_surface(); } -static void try_setting_data_selection_directly() { - set_data_selection(get_serial()); -} - static void complain_about_missing_keyboard() { bail("Setting primary selection is not supported without a keyboard"); } -#ifdef HAVE_GTK_PRIMARY_SELECTION - -static void set_gtk_primary_selection(uint32_t serial) { - struct gtk_primary_selection_source *gtk_primary_selection_source - = (struct gtk_primary_selection_source *) source->proxy; - - gtk_primary_selection_device_set_selection( - gtk_primary_selection_device, - gtk_primary_selection_source, - serial - ); - - wl_display_roundtrip(display); - destroy_popup_surface(); -} - -#endif - -#ifdef HAVE_WP_PRIMARY_SELECTION - -static void set_primary_selection(uint32_t serial) { - struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1 - = (struct zwp_primary_selection_source_v1 *) source->proxy; - - zwp_primary_selection_device_v1_set_selection( - primary_selection_device, - zwp_primary_selection_source_v1, - serial - ); - - wl_display_roundtrip(display); - destroy_popup_surface(); -} - -#endif - static void do_offer(char *mime_type, struct source *source) { if (mime_type == NULL || mime_type_is_text(mime_type)) { /* Offer a few generic plain text formats */ @@ -159,8 +119,8 @@ static void do_offer(char *mime_type, struct source *source) { } static void init_selection(char *mime_type) { - if (use_wlr_data_control) { #ifdef HAVE_WLR_DATA_CONTROL + if (data_control_manager != NULL) { struct zwlr_data_control_source_v1 *data_control_source = zwlr_data_control_manager_v1_create_data_source( data_control_manager @@ -171,26 +131,30 @@ static void init_selection(char *mime_type) { do_offer(mime_type, source); - zwlr_data_control_device_v1_set_selection( - data_control_device, - data_control_source - ); -#endif - } else { - struct wl_data_source *data_source - = wl_data_device_manager_create_data_source( - data_device_manager + struct zwlr_data_control_device_v1 *data_control_device + = zwlr_data_control_manager_v1_get_data_device( + data_control_manager, + seat ); - - source->proxy = (struct wl_proxy *) data_source; - source_init_wl_data_source(source); - - do_offer(mime_type, source); - - action_on_popup_surface_getting_focus = set_data_selection; - action_on_no_keyboard = try_setting_data_selection_directly; - popup_tiny_invisible_surface(); + device->proxy = (struct wl_proxy *) data_control_device; + device_init_zwlr_data_control_device_v1(device); } +#endif + + struct wl_data_source *data_source + = wl_data_device_manager_create_data_source( + data_device_manager + ); + + source->proxy = (struct wl_proxy *) data_source; + source_init_wl_data_source(source); + + do_offer(mime_type, source); + + struct wl_data_device *data_device + = wl_data_device_manager_get_data_device(data_device_manager, seat); + device->proxy = (struct wl_proxy *) data_device; + device_init_wl_data_device(device); } static void init_primary_selection(char *mime_type) { @@ -208,16 +172,19 @@ static void init_primary_selection(char *mime_type) { do_offer(mime_type, source); - action_on_popup_surface_getting_focus = set_primary_selection; - action_on_no_keyboard = complain_about_missing_keyboard; - popup_tiny_invisible_surface(); - return; + struct zwp_primary_selection_device_v1 *primary_selection_device + = zwp_primary_selection_device_manager_v1_get_device( + primary_selection_device_manager, + seat + ); + device->proxy = (struct wl_proxy *) primary_selection_device; + device_init_zwp_primary_selection_device_v1(device); } #endif #ifdef HAVE_GTK_PRIMARY_SELECTION if (gtk_primary_selection_device_manager != NULL) { - struct gtk_primary_selection_source* gtk_primary_selection_source + struct gtk_primary_selection_source *gtk_primary_selection_source = gtk_primary_selection_device_manager_create_source( gtk_primary_selection_device_manager ); @@ -227,10 +194,13 @@ static void init_primary_selection(char *mime_type) { do_offer(mime_type, source); - action_on_popup_surface_getting_focus = set_gtk_primary_selection; - action_on_no_keyboard = complain_about_missing_keyboard; - popup_tiny_invisible_surface(); - return; + struct gtk_primary_selection_device *gtk_primary_selection_device + = gtk_primary_selection_device_manager_get_device( + gtk_primary_selection_device_manager, + seat + ); + device->proxy = (struct wl_proxy *) gtk_primary_selection_device; + device_init_gtk_primary_selection_device(device); } #endif } @@ -367,6 +337,8 @@ int main(int argc, char * const argv[]) { } } + device = calloc(1, sizeof(struct device)); + source = calloc(1, sizeof(struct source)); source->send_callback = send_callback; source->cancelled_callback = cancelled_callback; @@ -377,6 +349,21 @@ int main(int argc, char * const argv[]) { init_primary_selection(options.mime_type); } + /* See if we can just set the selection directly */ + if (!device->needs_popup_surface) { + /* If we can, it doesn't actually require + * a serial, so passing zero will do. + */ + device_set_selection(device, source, 0, options.primary); + } else { + /* If we cannot, schedule to do it later, + * when our popup surface gains keyboard focus. + */ + action_on_popup_surface_getting_focus = set_selection; + action_on_no_keyboard = complain_about_missing_keyboard; + popup_tiny_invisible_surface(); + } + if (options.clear) { wl_display_roundtrip(display); exit(0); diff --git a/src/wl-paste.c b/src/wl-paste.c index 7936b23..e79628f 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -18,6 +18,7 @@ #include "boilerplate.h" #include "types/offer.h" +#include "types/device.h" static struct { char *explicit_type; @@ -37,6 +38,8 @@ struct types { const char *any; }; +static struct device *device = NULL; + static struct types classify_offer_types(struct offer *offer) { struct types types = { 0 }; offer_for_each_mime_type(offer, mime_type) { @@ -149,7 +152,15 @@ static const char *mime_type_to_request(struct types types) { #undef try_any_text #undef try_any -static void do_paste(struct offer *offer) { +static void selection_callback(struct offer *offer, int primary) { + /* Ignore events we're not interested in */ + if (primary != options.primary) { + if (offer != NULL) { + offer_destroy(offer); + }; + return; + } + if (offer == NULL) { bail("No selection"); } @@ -205,134 +216,6 @@ static void do_paste(struct offer *offer) { exit(0); } -static void data_device_data_offer( - void *data, - struct wl_data_device *data_device, - struct wl_data_offer *data_offer -) { - struct offer *offer = calloc(1, sizeof(struct offer)); - offer->proxy = (struct wl_proxy *) data_offer; - offer_init_wl_data_offer(offer); -} - -static void data_device_selection( - void *data, - struct wl_data_device *data_device, - struct wl_data_offer *data_offer -) { - struct offer *offer = NULL; - if (data_offer != NULL) { - offer = (struct offer *) wl_data_offer_get_user_data(data_offer); - } - do_paste(offer); -} - -static const struct wl_data_device_listener data_device_listener = { - .data_offer = data_device_data_offer, - .selection = data_device_selection -}; - -#ifdef HAVE_GTK_PRIMARY_SELECTION - -static void gtk_primary_selection_device_data_offer( - void *data, - struct gtk_primary_selection_device *gtk_primary_selection_device, - struct gtk_primary_selection_offer *gtk_primary_selection_offer -) { - struct offer *offer = calloc(1, sizeof(struct offer)); - offer->proxy = (struct wl_proxy *) gtk_primary_selection_offer; - offer_init_gtk_primary_selection_offer(offer); -} - -static void gtk_primary_selection_device_selection( - void *data, - struct gtk_primary_selection_device *gtk_primary_selection_device, - struct gtk_primary_selection_offer *gtk_primary_selection_offer -) { - struct offer *offer = NULL; - if (gtk_primary_selection_offer != NULL) { - offer = (struct offer *) gtk_primary_selection_offer_get_user_data( - gtk_primary_selection_offer - ); - } - do_paste(offer); -} - -static const struct gtk_primary_selection_device_listener -gtk_primary_selection_device_listener = { - .data_offer = gtk_primary_selection_device_data_offer, - .selection = gtk_primary_selection_device_selection -}; - -#endif - -#ifdef HAVE_WP_PRIMARY_SELECTION - -static void primary_selection_device_data_offer( - void *data, - struct zwp_primary_selection_device_v1 *primary_selection_device, - struct zwp_primary_selection_offer_v1 *primary_selection_offer -) { - struct offer *offer = calloc(1, sizeof(struct offer)); - offer->proxy = (struct wl_proxy *) primary_selection_offer; - offer_init_zwp_primary_selection_offer_v1(offer); -} - -static void primary_selection_device_selection( - void *data, - struct zwp_primary_selection_device_v1 *primary_selection_device, - struct zwp_primary_selection_offer_v1 *primary_selection_offer -) { - struct offer *offer = NULL; - if (primary_selection_offer != NULL) { - offer = (struct offer *) zwp_primary_selection_offer_v1_get_user_data( - primary_selection_offer - ); - } - do_paste(offer); -} - -static const struct zwp_primary_selection_device_v1_listener -primary_selection_device_listener = { - .data_offer = primary_selection_device_data_offer, - .selection = primary_selection_device_selection -}; - -#endif - -#ifdef HAVE_WLR_DATA_CONTROL - -static void data_control_device_data_offer( - void *data, - struct zwlr_data_control_device_v1 *data_control_device, - struct zwlr_data_control_offer_v1 *data_control_offer -) { - struct offer *offer = calloc(1, sizeof(struct offer)); - offer->proxy = (struct wl_proxy *) data_control_offer; - offer_init_zwlr_data_control_offer_v1(offer); -} - -static void data_control_device_selection( - void *data, - struct zwlr_data_control_device_v1 *data_control_device, - struct zwlr_data_control_offer_v1 *data_control_offer -) { - struct offer *offer = NULL; - if (data_control_offer != NULL) { - offer = (struct offer *) zwlr_data_control_offer_v1_get_user_data( - data_control_offer - ); - } - do_paste(offer); -} - -static const struct zwlr_data_control_device_v1_listener -data_control_device_listener = { - .data_offer = data_control_device_data_offer, - .selection = data_control_device_selection -}; -#endif - static void print_usage(FILE *f, const char *argv0) { fprintf( f, @@ -357,43 +240,50 @@ static void print_usage(FILE *f, const char *argv0) { } static void init_selection() { - if (use_wlr_data_control) { #ifdef HAVE_WLR_DATA_CONTROL - zwlr_data_control_device_v1_add_listener( - data_control_device, - &data_control_device_listener, - NULL - ); -#endif - } else { - wl_data_device_add_listener(data_device, &data_device_listener, NULL); - popup_tiny_invisible_surface(); + if (data_control_manager != NULL) { + struct zwlr_data_control_device_v1 *data_control_device + = zwlr_data_control_manager_v1_get_data_device( + data_control_manager, + seat + ); + device->proxy = (struct wl_proxy *) data_control_device; + device_init_zwlr_data_control_device_v1(device); + return; } +#endif + + struct wl_data_device *data_device + = wl_data_device_manager_get_data_device(data_device_manager, seat); + device->proxy = (struct wl_proxy *) data_device; + device_init_wl_data_device(device); } static void init_primary_selection() { ensure_has_primary_selection(); #ifdef HAVE_WP_PRIMARY_SELECTION - if (primary_selection_device != NULL) { - zwp_primary_selection_device_v1_add_listener( - primary_selection_device, - &primary_selection_device_listener, - NULL - ); - popup_tiny_invisible_surface(); + if (primary_selection_device_manager != NULL) { + struct zwp_primary_selection_device_v1 *primary_selection_device + = zwp_primary_selection_device_manager_v1_get_device( + primary_selection_device_manager, + seat + ); + device->proxy = (struct wl_proxy *) primary_selection_device; + device_init_zwp_primary_selection_device_v1(device); return; } #endif #ifdef HAVE_GTK_PRIMARY_SELECTION - if (gtk_primary_selection_device != NULL) { - gtk_primary_selection_device_add_listener( - gtk_primary_selection_device, - >k_primary_selection_device_listener, - NULL - ); - popup_tiny_invisible_surface(); + if (gtk_primary_selection_device_manager != NULL) { + struct gtk_primary_selection_device *gtk_primary_selection_device + = gtk_primary_selection_device_manager_get_device( + gtk_primary_selection_device_manager, + seat + ); + device->proxy = (struct wl_proxy *) gtk_primary_selection_device; + device_init_gtk_primary_selection_device(device); return; } #endif @@ -472,12 +362,19 @@ int main(int argc, char * const argv[]) { init_wayland_globals(); + device = calloc(1, sizeof(struct device)); + device->selection_callback = selection_callback; + if (!options.primary) { init_selection(); } else { init_primary_selection(); } + if (device->needs_popup_surface) { + popup_tiny_invisible_surface(); + } + while (wl_display_dispatch(display) >= 0); perror("wl_display_dispatch"); From dc6d39cce472d37b92cabd6042241957f5402111 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 16:54:18 +0300 Subject: [PATCH 031/139] Move device manager handling to types/device-manager --- src/meson.build | 2 + src/types/device-manager.c | 137 +++++++++++++++++++++++++++++++++++++ src/types/device-manager.h | 70 +++++++++++++++++++ src/wl-copy.c | 105 +++++++++------------------- src/wl-paste.c | 57 +++++++-------- 5 files changed, 264 insertions(+), 107 deletions(-) create mode 100644 src/types/device-manager.c create mode 100644 src/types/device-manager.h diff --git a/src/meson.build b/src/meson.build index 8b591cd..2911726 100644 --- a/src/meson.build +++ b/src/meson.build @@ -106,6 +106,8 @@ lib = static_library( 'types/offer.c', 'types/device.h', 'types/device.c', + 'types/device-manager.h', + 'types/device-manager.c', ], dependencies: wayland, link_with: protocol_deps diff --git a/src/types/device-manager.c b/src/types/device-manager.c new file mode 100644 index 0000000..dff3cb4 --- /dev/null +++ b/src/types/device-manager.c @@ -0,0 +1,137 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types/device-manager.h" +#include "types/device.h" +#include "types/source.h" +#include "includes/selection-protocols.h" + +#include + + +struct source *device_manager_create_source(struct device_manager *self) { + return self->do_create_source(self); +} + +struct device *device_manager_get_device( + struct device_manager *self, + struct wl_seat *seat +) { + return self->do_get_device(self, seat); +} + +/* Macros to reduce implementation boilerplate */ + +#define CREATE_SOURCE(type, source_type, method_name) \ +static struct source *device_manager_ ## type ## _do_create_source( \ + struct device_manager *self \ +) { \ + struct type *proxy = (struct type *) self->proxy; \ + struct source *source = calloc(1, sizeof(struct source)); \ + source->proxy = (struct wl_proxy *) type ## _ ## method_name(proxy); \ + source_init_ ## source_type(source); \ + return source; \ +} + +#define GET_DEVICE(type, device_type, method_name) \ +static struct device *device_manager_ ## type ## _do_get_device( \ + struct device_manager *self, \ + struct wl_seat *seat \ +) { \ + struct type *proxy = (struct type *) self->proxy; \ + struct device *device = calloc(1, sizeof(struct device)); \ + device->proxy = (struct wl_proxy *) type ## _ ## method_name(proxy, seat); \ + device_init_ ## device_type(device); \ + return device; \ +} + +#define INIT(type) \ +void device_manager_init_ ## type(struct device_manager *self) { \ + self->do_create_source = device_manager_ ## type ## _do_create_source; \ + self->do_get_device = device_manager_ ## type ## _do_get_device; \ +} + + +/* Core Wayland implementation */ + +CREATE_SOURCE(wl_data_device_manager, wl_data_source, create_data_source) +GET_DEVICE(wl_data_device_manager, wl_data_device, get_data_device) +INIT(wl_data_device_manager) + + +/* gtk-primary-selection implementation */ + +#ifdef HAVE_GTK_PRIMARY_SELECTION + +CREATE_SOURCE( + gtk_primary_selection_device_manager, + gtk_primary_selection_source, + create_source +) + +GET_DEVICE( + gtk_primary_selection_device_manager, + gtk_primary_selection_device, + get_device +) + +INIT(gtk_primary_selection_device_manager) + +#endif /* HAVE_GTK_PRIMARY_SELECTION */ + + +/* wp-primary-selection implementation */ + +#ifdef HAVE_WP_PRIMARY_SELECTION + +CREATE_SOURCE( + zwp_primary_selection_device_manager_v1, + zwp_primary_selection_source_v1, + create_source +) + +GET_DEVICE( + zwp_primary_selection_device_manager_v1, + zwp_primary_selection_device_v1, + get_device +) + +INIT(zwp_primary_selection_device_manager_v1) + +#endif /* HAVE_WP_PRIMARY_SELECTION */ + + +/* wlr-data-control implementation */ + +#ifdef HAVE_WLR_DATA_CONTROL + +CREATE_SOURCE( + zwlr_data_control_manager_v1, + zwlr_data_control_source_v1, + create_data_source +) + +GET_DEVICE( + zwlr_data_control_manager_v1, + zwlr_data_control_device_v1, + get_data_device +) + +INIT(zwlr_data_control_manager_v1) + +#endif /* HAVE_WLR_DATA_CONTROL */ diff --git a/src/types/device-manager.h b/src/types/device-manager.h new file mode 100644 index 0000000..57e1270 --- /dev/null +++ b/src/types/device-manager.h @@ -0,0 +1,70 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_DEVICE_MANAGER_H +#define TYPES_DEVICE_MANAGER_H + +#include "includes/selection-protocols.h" + +#include + +struct device; +struct source; + +struct device_manager { + /* This field is initialized by the creator */ + struct wl_proxy *proxy; + + /* These fields are initialized by the implementation */ + struct source *(*do_create_source)(struct device_manager *self); + struct device *(*do_get_device)( + struct device_manager *self, + struct wl_seat *seat + ); +}; + +struct source *device_manager_create_source(struct device_manager *self); + +struct device *device_manager_get_device( + struct device_manager *self, + struct wl_seat *seat +); + +/* Initializers */ + +void device_manager_init_wl_data_device_manager(struct device_manager *self); + +#ifdef HAVE_GTK_PRIMARY_SELECTION +void device_manager_init_gtk_primary_selection_device_manager( + struct device_manager *self +); +#endif + +#ifdef HAVE_WP_PRIMARY_SELECTION +void device_manager_init_zwp_primary_selection_device_manager_v1( + struct device_manager *self +); +#endif + +#ifdef HAVE_WLR_DATA_CONTROL +void device_manager_init_zwlr_data_control_manager_v1( + struct device_manager *self +); +#endif + +#endif /* TYPES_DEVICE_MANAGER_H */ diff --git a/src/wl-copy.c b/src/wl-copy.c index f3900dc..c8801c7 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -19,6 +19,7 @@ #include "boilerplate.h" #include "types/source.h" #include "types/device.h" +#include "types/device-manager.h" static struct { int stay_in_foreground; @@ -32,6 +33,7 @@ static struct { static char * const *data_to_copy = NULL; static char *temp_file_to_copy = NULL; +static struct device_manager *device_manager = NULL; static struct device *device = NULL; static struct source *source = NULL; @@ -118,89 +120,41 @@ static void do_offer(char *mime_type, struct source *source) { free(mime_type); } -static void init_selection(char *mime_type) { +static void init_device_manager() { #ifdef HAVE_WLR_DATA_CONTROL if (data_control_manager != NULL) { - struct zwlr_data_control_source_v1 *data_control_source - = zwlr_data_control_manager_v1_create_data_source( - data_control_manager - ); - - source->proxy = (struct wl_proxy *) data_control_source; - source_init_zwlr_data_control_source_v1(source); - - do_offer(mime_type, source); - - struct zwlr_data_control_device_v1 *data_control_device - = zwlr_data_control_manager_v1_get_data_device( - data_control_manager, - seat - ); - device->proxy = (struct wl_proxy *) data_control_device; - device_init_zwlr_data_control_device_v1(device); + device_manager->proxy = (struct wl_proxy *) data_control_manager; + device_manager_init_zwlr_data_control_manager_v1(device_manager); + return; } #endif - struct wl_data_source *data_source - = wl_data_device_manager_create_data_source( - data_device_manager - ); - - source->proxy = (struct wl_proxy *) data_source; - source_init_wl_data_source(source); - - do_offer(mime_type, source); - - struct wl_data_device *data_device - = wl_data_device_manager_get_data_device(data_device_manager, seat); - device->proxy = (struct wl_proxy *) data_device; - device_init_wl_data_device(device); + device_manager->proxy = (struct wl_proxy *) data_device_manager; + device_manager_init_wl_data_device_manager(device_manager); } -static void init_primary_selection(char *mime_type) { +static void init_primary_device_manager() { ensure_has_primary_selection(); #ifdef HAVE_WP_PRIMARY_SELECTION if (primary_selection_device_manager != NULL) { - struct zwp_primary_selection_source_v1 *primary_selection_source - = zwp_primary_selection_device_manager_v1_create_source( - primary_selection_device_manager - ); - - source->proxy = (struct wl_proxy *) primary_selection_source; - source_init_zwp_primary_selection_source_v1(source); - - do_offer(mime_type, source); - - struct zwp_primary_selection_device_v1 *primary_selection_device - = zwp_primary_selection_device_manager_v1_get_device( - primary_selection_device_manager, - seat - ); - device->proxy = (struct wl_proxy *) primary_selection_device; - device_init_zwp_primary_selection_device_v1(device); + device_manager->proxy + = (struct wl_proxy *) primary_selection_device_manager; + device_manager_init_zwp_primary_selection_device_manager_v1( + device_manager + ); + return; } #endif #ifdef HAVE_GTK_PRIMARY_SELECTION if (gtk_primary_selection_device_manager != NULL) { - struct gtk_primary_selection_source *gtk_primary_selection_source - = gtk_primary_selection_device_manager_create_source( - gtk_primary_selection_device_manager - ); - - source->proxy = (struct wl_proxy *) gtk_primary_selection_source; - source_init_gtk_primary_selection_source(source); - - do_offer(mime_type, source); - - struct gtk_primary_selection_device *gtk_primary_selection_device - = gtk_primary_selection_device_manager_get_device( - gtk_primary_selection_device_manager, - seat - ); - device->proxy = (struct wl_proxy *) gtk_primary_selection_device; - device_init_gtk_primary_selection_device(device); + device_manager->proxy + = (struct wl_proxy *) gtk_primary_selection_device_manager; + device_manager_init_gtk_primary_selection_device_manager( + device_manager + ); + return; } #endif } @@ -337,17 +291,20 @@ int main(int argc, char * const argv[]) { } } - device = calloc(1, sizeof(struct device)); + device_manager = calloc(1, sizeof(struct device_manager)); + if (!options.primary) { + init_device_manager(); + } else { + init_primary_device_manager(); + } - source = calloc(1, sizeof(struct source)); + device = device_manager_get_device(device_manager, seat); + + source = device_manager_create_source(device_manager); source->send_callback = send_callback; source->cancelled_callback = cancelled_callback; - if (!options.primary) { - init_selection(options.mime_type); - } else { - init_primary_selection(options.mime_type); - } + do_offer(options.mime_type, source); /* See if we can just set the selection directly */ if (!device->needs_popup_surface) { diff --git a/src/wl-paste.c b/src/wl-paste.c index e79628f..f8a89c0 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -19,6 +19,7 @@ #include "boilerplate.h" #include "types/offer.h" #include "types/device.h" +#include "types/device-manager.h" static struct { char *explicit_type; @@ -38,7 +39,7 @@ struct types { const char *any; }; -static struct device *device = NULL; +static struct device_manager *device_manager = NULL; static struct types classify_offer_types(struct offer *offer) { struct types types = { 0 }; @@ -239,51 +240,40 @@ static void print_usage(FILE *f, const char *argv0) { ); } -static void init_selection() { +static void init_device_manager() { #ifdef HAVE_WLR_DATA_CONTROL if (data_control_manager != NULL) { - struct zwlr_data_control_device_v1 *data_control_device - = zwlr_data_control_manager_v1_get_data_device( - data_control_manager, - seat - ); - device->proxy = (struct wl_proxy *) data_control_device; - device_init_zwlr_data_control_device_v1(device); + device_manager->proxy = (struct wl_proxy *) data_control_manager; + device_manager_init_zwlr_data_control_manager_v1(device_manager); return; } #endif - struct wl_data_device *data_device - = wl_data_device_manager_get_data_device(data_device_manager, seat); - device->proxy = (struct wl_proxy *) data_device; - device_init_wl_data_device(device); + device_manager->proxy = (struct wl_proxy *) data_device_manager; + device_manager_init_wl_data_device_manager(device_manager); } -static void init_primary_selection() { +static void init_primary_device_manager() { ensure_has_primary_selection(); #ifdef HAVE_WP_PRIMARY_SELECTION if (primary_selection_device_manager != NULL) { - struct zwp_primary_selection_device_v1 *primary_selection_device - = zwp_primary_selection_device_manager_v1_get_device( - primary_selection_device_manager, - seat - ); - device->proxy = (struct wl_proxy *) primary_selection_device; - device_init_zwp_primary_selection_device_v1(device); + device_manager->proxy + = (struct wl_proxy *) primary_selection_device_manager; + device_manager_init_zwp_primary_selection_device_manager_v1( + device_manager + ); return; } #endif #ifdef HAVE_GTK_PRIMARY_SELECTION if (gtk_primary_selection_device_manager != NULL) { - struct gtk_primary_selection_device *gtk_primary_selection_device - = gtk_primary_selection_device_manager_get_device( - gtk_primary_selection_device_manager, - seat - ); - device->proxy = (struct wl_proxy *) gtk_primary_selection_device; - device_init_gtk_primary_selection_device(device); + device_manager->proxy + = (struct wl_proxy *) gtk_primary_selection_device_manager; + device_manager_init_gtk_primary_selection_device_manager( + device_manager + ); return; } #endif @@ -362,15 +352,16 @@ int main(int argc, char * const argv[]) { init_wayland_globals(); - device = calloc(1, sizeof(struct device)); - device->selection_callback = selection_callback; - + device_manager = calloc(1, sizeof(struct device_manager)); if (!options.primary) { - init_selection(); + init_device_manager(); } else { - init_primary_selection(); + init_primary_device_manager(); } + struct device *device = device_manager_get_device(device_manager, seat); + device->selection_callback = selection_callback; + if (device->needs_popup_surface) { popup_tiny_invisible_surface(); } From a2fd1910fb7bce08e1544fed728eb407ef13b336 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 17:15:47 +0300 Subject: [PATCH 032/139] Move keyboard handling to types/keyboard --- src/boilerplate.c | 67 +++++------------------------------- src/meson.build | 2 ++ src/types/keyboard.c | 82 ++++++++++++++++++++++++++++++++++++++++++++ src/types/keyboard.h | 34 ++++++++++++++++++ 4 files changed, 126 insertions(+), 59 deletions(-) create mode 100644 src/types/keyboard.c create mode 100644 src/types/keyboard.h diff --git a/src/boilerplate.c b/src/boilerplate.c index abbe81a..33bbf48 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -17,6 +17,7 @@ */ #include "boilerplate.h" +#include "types/keyboard.h" static void process_new_seat(struct wl_seat *new_seat); @@ -119,26 +120,8 @@ static const struct wl_registry_listener registry_listener = { .global_remove = registry_global_remove_handler }; -static void keyboard_keymap_handler -( - void *data, - struct wl_keyboard *keyboard, - uint32_t format, - int fd, - uint32_t size -) { - close(fd); -} - -static void keyboard_enter_handler -( - void *data, - struct wl_keyboard *keyboard, - uint32_t serial, - struct wl_surface *surface, - struct wl_array *keys -) { - struct wl_seat *this_seat = (struct wl_seat *) data; +static void forward_on_focus(struct keyboard *keyboard, uint32_t serial) { + struct wl_seat *this_seat = (struct wl_seat *) keyboard->data; /* When we get to here, global seat is already initialized */ if (this_seat != seat) { return; @@ -148,43 +131,6 @@ static void keyboard_enter_handler } } -static void keyboard_leave_handler -( - void *data, - struct wl_keyboard *keyboard, - uint32_t serial, - struct wl_surface *surface -) {} - -static void keyboard_key_handler -( - void *data, - struct wl_keyboard *keyboard, - uint32_t serial, - uint32_t time, - uint32_t key, - uint32_t state -) {} - -static void keyboard_modifiers_handler -( - void *data, - struct wl_keyboard *keyboard, - uint32_t serial, - uint32_t mods_depressed, - uint32_t mods_latched, - uint32_t mods_locked, - uint32_t group -) {} - -static const struct wl_keyboard_listener keayboard_listener = { - .keymap = keyboard_keymap_handler, - .enter = keyboard_enter_handler, - .leave = keyboard_leave_handler, - .key = keyboard_key_handler, - .modifiers = keyboard_modifiers_handler, -}; - static void seat_capabilities_handler ( void *data, @@ -196,8 +142,11 @@ static void seat_capabilities_handler wl_seat_set_user_data(this_seat, user_data); if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { - struct wl_keyboard *keyboard = wl_seat_get_keyboard(this_seat); - wl_keyboard_add_listener(keyboard, &keayboard_listener, this_seat); + struct keyboard *keyboard = calloc(1, sizeof(struct keyboard)); + keyboard->proxy = wl_seat_get_keyboard(this_seat); + keyboard->data = (void *) this_seat; + keyboard->on_focus = forward_on_focus; + keyboard_init(keyboard); } } diff --git a/src/meson.build b/src/meson.build index 2911726..04f5ec2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -108,6 +108,8 @@ lib = static_library( 'types/device.c', 'types/device-manager.h', 'types/device-manager.c', + 'types/keyboard.h', + 'types/keyboard.c', ], dependencies: wayland, link_with: protocol_deps diff --git a/src/types/keyboard.c b/src/types/keyboard.c new file mode 100644 index 0000000..99ca793 --- /dev/null +++ b/src/types/keyboard.c @@ -0,0 +1,82 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types/keyboard.h" + +#include + +static void wl_keyboard_keymap_handler( + void *data, + struct wl_keyboard *keyboard, + uint32_t format, + int fd, + uint32_t size +) { + close(fd); +} + +static void wl_keyboard_enter_handler( + void *data, + struct wl_keyboard *keyboard, + uint32_t serial, + struct wl_surface *surface, + struct wl_array *keys +) { + struct keyboard *self = (struct keyboard *) data; + if (self->on_focus != NULL) { + self->on_focus(self, serial); + } +} + +static void wl_keyboard_leave_handler( + void *data, + struct wl_keyboard *keyboard, + uint32_t serial, + struct wl_surface *surface +) {} + +static void wl_keyboard_key_handler( + void *data, + struct wl_keyboard *keyboard, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state +) {} + +static void wl_keyboard_modifiers_handler( + void *data, + struct wl_keyboard *keyboard, + uint32_t serial, + uint32_t mods_depressed, + uint32_t mods_latched, + uint32_t mods_locked, + uint32_t group +) {} + +static const struct wl_keyboard_listener wl_keyboard_listener = { + .keymap = wl_keyboard_keymap_handler, + .enter = wl_keyboard_enter_handler, + .leave = wl_keyboard_leave_handler, + .key = wl_keyboard_key_handler, + .modifiers = wl_keyboard_modifiers_handler, +}; + +void keyboard_init(struct keyboard *self) { + wl_keyboard_add_listener(self->proxy, &wl_keyboard_listener, self); +} diff --git a/src/types/keyboard.h b/src/types/keyboard.h new file mode 100644 index 0000000..e5f02d0 --- /dev/null +++ b/src/types/keyboard.h @@ -0,0 +1,34 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_KEYBOARD_H +#define TYPES_KEYBOARD_H + +#include +#include + +struct keyboard { + /* These fields are initialized by the creator */ + struct wl_keyboard *proxy; + void (*on_focus)(struct keyboard *self, uint32_t serial); + void *data; +}; + +void keyboard_init(struct keyboard *self); + +#endif /* TYPES_KEYBOARD_H */ From 6df1f37bf03925f39375b94e2010b3c393a60d89 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 17:39:02 +0300 Subject: [PATCH 033/139] Move shell surface handling to types/shell-surface --- src/boilerplate.c | 110 ++++++--------------------------- src/boilerplate.h | 3 - src/meson.build | 2 + src/types/shell-surface.c | 125 ++++++++++++++++++++++++++++++++++++++ src/types/shell-surface.h | 44 ++++++++++++++ 5 files changed, 190 insertions(+), 94 deletions(-) create mode 100644 src/types/shell-surface.c create mode 100644 src/types/shell-surface.h diff --git a/src/boilerplate.c b/src/boilerplate.c index 33bbf48..06f1249 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -18,6 +18,9 @@ #include "boilerplate.h" #include "types/keyboard.h" +#include "types/shell-surface.h" + +static struct shell_surface *shell_surface = NULL; static void process_new_seat(struct wl_seat *new_seat); @@ -202,71 +205,8 @@ int ensure_seat_has_keyboard() { #undef UNSET_CAPABILITIES -static void shell_surface_ping -( - void *data, - struct wl_shell_surface *shell_surface, - uint32_t serial -) { - wl_shell_surface_pong(shell_surface, serial); -} - -static void shell_surface_configure -( - void *data, - struct wl_shell_surface *shell_surface, - uint32_t edges, - int32_t width, - int32_t height -) {} - -static void shell_surface_popup_done -( - void *data, - struct wl_shell_surface *shell_surface -) {} - -static const struct wl_shell_surface_listener shell_surface_listener = { - .ping = shell_surface_ping, - .configure = shell_surface_configure, - .popup_done = shell_surface_popup_done -}; - #ifdef HAVE_XDG_SHELL -static void xdg_toplevel_configure_handler -( - void *data, - struct xdg_toplevel *xdg_toplevel, - int32_t width, - int32_t height, - struct wl_array *states -) {} - -static void xdg_toplevel_close_handler -( - void *data, - struct xdg_toplevel *xdg_toplevel -) {} - -static const struct xdg_toplevel_listener xdg_toplevel_listener = { - .configure = xdg_toplevel_configure_handler, - .close = xdg_toplevel_close_handler -}; - -static void xdg_surface_configure_handler -( - void *data, - struct xdg_surface *xdg_surface, - uint32_t serial -) { - xdg_surface_ack_configure(xdg_surface, serial); -} - -static const struct xdg_surface_listener xdg_surface_listener = { - .configure = xdg_surface_configure_handler -}; - static void xdg_wm_base_ping_handler ( void *data, @@ -353,35 +293,33 @@ void popup_tiny_invisible_surface() { */ wl_display_dispatch(display); + shell_surface = calloc(1, sizeof(struct shell_surface)); + surface = wl_compositor_create_surface(compositor); if (shell != NULL) { /* Use wl_shell */ - shell_surface = wl_shell_get_shell_surface(shell, surface); - wl_shell_surface_add_listener( - shell_surface, - &shell_surface_listener, - NULL - ); - wl_shell_surface_set_toplevel(shell_surface); - wl_shell_surface_set_title(shell_surface, "wl-clipboard"); + shell_surface->proxy + = (struct wl_proxy *) wl_shell_get_shell_surface(shell, surface); + shell_surface_init_wl_shell_surface(shell_surface); } else { #ifdef HAVE_XDG_SHELL /* Use xdg-shell */ xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL); - xdg_surface = xdg_wm_base_get_xdg_surface(xdg_wm_base, surface); - xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL); - xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); - xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL); - xdg_toplevel_set_title(xdg_toplevel, "wl-clipboard"); - /* Signal that the surface is ready to be configured */ - wl_surface_commit(surface); - /* Wait for the surface to be configured */ - wl_display_roundtrip(display); + shell_surface->proxy + = (struct wl_proxy *) xdg_wm_base_get_xdg_surface( + xdg_wm_base, + surface + ); + shell_surface_init_xdg_surface(shell_surface); #else bail("Unreachable: HAVE_XDG_SHELL undefined and no wl_shell"); #endif } + /* Signal that the surface is ready to be configured */ + wl_surface_commit(surface); + /* Wait for the surface to be configured */ + wl_display_roundtrip(display); if (surface == NULL) { /* It's possible that we've been given focus without us @@ -416,19 +354,9 @@ void popup_tiny_invisible_surface() { void destroy_popup_surface() { if (shell_surface != NULL) { - wl_shell_surface_destroy(shell_surface); + shell_surface_destroy(shell_surface); shell_surface = NULL; } -#ifdef HAVE_XDG_SHELL - if (xdg_toplevel != NULL) { - xdg_toplevel_destroy(xdg_toplevel); - xdg_toplevel = NULL; - } - if (xdg_surface != NULL) { - xdg_surface_destroy(xdg_surface); - xdg_surface = NULL; - } -#endif if (surface != NULL) { wl_surface_destroy(surface); surface = NULL; diff --git a/src/boilerplate.h b/src/boilerplate.h index fb23c8a..2a3e9e8 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -47,12 +47,9 @@ struct wl_compositor *compositor; struct wl_shm *shm; struct wl_shell *shell; struct wl_surface *surface; -struct wl_shell_surface *shell_surface; #ifdef HAVE_XDG_SHELL struct xdg_wm_base *xdg_wm_base; -struct xdg_surface *xdg_surface; -struct xdg_toplevel *xdg_toplevel; #endif #ifdef HAVE_GTK_PRIMARY_SELECTION diff --git a/src/meson.build b/src/meson.build index 04f5ec2..fe842be 100644 --- a/src/meson.build +++ b/src/meson.build @@ -110,6 +110,8 @@ lib = static_library( 'types/device-manager.c', 'types/keyboard.h', 'types/keyboard.c', + 'types/shell-surface.h', + 'types/shell-surface.c', ], dependencies: wayland, link_with: protocol_deps diff --git a/src/types/shell-surface.c b/src/types/shell-surface.c new file mode 100644 index 0000000..ba7e61b --- /dev/null +++ b/src/types/shell-surface.c @@ -0,0 +1,125 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types/shell-surface.h" +#include "includes/shell-protocols.h" + +#include +#include + +void shell_surface_destroy(struct shell_surface *self) { + self->do_destroy(self); + free(self); +} + + +/* Core Wayland implementation */ + +static void wl_shell_surface_ping( + void *data, + struct wl_shell_surface *wl_shell_surface, + uint32_t serial +) { + wl_shell_surface_pong(wl_shell_surface, serial); +} + +static void wl_shell_surface_configure( + void *data, + struct wl_shell_surface *wl_shell_surface, + uint32_t edges, + int32_t width, + int32_t height +) {} + +static void wl_shell_surface_popup_done( + void *data, + struct wl_shell_surface *wl_shell_surface +) {} + +static const struct wl_shell_surface_listener wl_shell_surface_listener = { + .ping = wl_shell_surface_ping, + .configure = wl_shell_surface_configure, + .popup_done = wl_shell_surface_popup_done +}; + +static void destroy_wl_shell_surface(struct shell_surface *self) { + struct wl_shell_surface *proxy = (struct wl_shell_surface *) self->proxy; + wl_shell_surface_destroy(proxy); +} + +void shell_surface_init_wl_shell_surface(struct shell_surface *self) { + struct wl_shell_surface *proxy = (struct wl_shell_surface *) self->proxy; + wl_shell_surface_add_listener(proxy, &wl_shell_surface_listener, self); + wl_shell_surface_set_toplevel(proxy); + wl_shell_surface_set_title(proxy, "wl-clipboard"); + self->do_destroy = destroy_wl_shell_surface; +} + + +/* xdg-shell implementation */ + +#ifdef HAVE_XDG_SHELL + +static void xdg_toplevel_configure_handler( + void *data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array *states +) {} + +static void xdg_toplevel_close_handler( + void *data, + struct xdg_toplevel *xdg_toplevel +) {} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure_handler, + .close = xdg_toplevel_close_handler +}; + +static void xdg_surface_configure_handler( + void *data, + struct xdg_surface *xdg_surface, + uint32_t serial +) { + xdg_surface_ack_configure(xdg_surface, serial); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure_handler +}; + +static void destroy_xdg_surface(struct shell_surface *self) { + struct xdg_toplevel *toplevel = (struct xdg_toplevel *) self->proxy2; + struct xdg_surface *proxy = (struct xdg_surface *) self->proxy; + xdg_toplevel_destroy(toplevel); + xdg_surface_destroy(proxy); +} + +void shell_surface_init_xdg_surface(struct shell_surface *self) { + struct xdg_surface *proxy = (struct xdg_surface *) self->proxy; + xdg_surface_add_listener(proxy, &xdg_surface_listener, self); + struct xdg_toplevel *toplevel = xdg_surface_get_toplevel(proxy); + self->proxy2 = (struct wl_proxy *) toplevel; + xdg_toplevel_add_listener(toplevel, &xdg_toplevel_listener, self); + xdg_toplevel_set_title(toplevel, "wl-clipboard"); + self->do_destroy = destroy_xdg_surface; +} + +#endif /* HAVE_XDG_SHELL */ diff --git a/src/types/shell-surface.h b/src/types/shell-surface.h new file mode 100644 index 0000000..d888553 --- /dev/null +++ b/src/types/shell-surface.h @@ -0,0 +1,44 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_SHELL_SURFACE_H +#define TYPES_SHELL_SURFACE_H + +#include "includes/shell-protocols.h" + +struct shell_surface { + /* This field is initialized by the creator */ + struct wl_proxy *proxy; + + /* These fields are initialized by the implementation */ + struct wl_proxy *proxy2; + void (*do_destroy)(struct shell_surface *self); +}; + +void shell_surface_destroy(struct shell_surface *self); + + +/* Initializers */ + +void shell_surface_init_wl_shell_surface(struct shell_surface *self); + +#ifdef HAVE_XDG_SHELL +void shell_surface_init_xdg_surface(struct shell_surface *self); +#endif + +#endif /* TYPES_SHELL_SURFACE_H */ From efc78a88909604334bf7f8ffeda6cdfcd830f183 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 17:53:29 +0300 Subject: [PATCH 034/139] Move shell handling to types/shell --- src/boilerplate.c | 59 ++++++++++-------------------- src/boilerplate.h | 2 +- src/meson.build | 2 + src/types/shell.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++ src/types/shell.h | 53 +++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 41 deletions(-) create mode 100644 src/types/shell.c create mode 100644 src/types/shell.h diff --git a/src/boilerplate.c b/src/boilerplate.c index 06f1249..57b0d43 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -19,8 +19,10 @@ #include "boilerplate.h" #include "types/keyboard.h" #include "types/shell-surface.h" +#include "types/shell.h" static struct shell_surface *shell_surface = NULL; +static struct shell *shell = NULL; static void process_new_seat(struct wl_seat *new_seat); @@ -62,7 +64,7 @@ static void registry_global_handler 1 ); } else if (strcmp(interface, "wl_shell") == 0) { - shell = wl_registry_bind( + wl_shell = wl_registry_bind( registry, name, &wl_shell_interface, @@ -205,23 +207,6 @@ int ensure_seat_has_keyboard() { #undef UNSET_CAPABILITIES -#ifdef HAVE_XDG_SHELL - -static void xdg_wm_base_ping_handler -( - void *data, - struct xdg_wm_base *xdg_wm_base, - uint32_t serial -) { - xdg_wm_base_pong(xdg_wm_base, serial); -} - -static const struct xdg_wm_base_listener xdg_wm_base_listener = { - .ping = xdg_wm_base_ping_handler -}; - -#endif - void init_wayland_globals() { display = wl_display_connect(NULL); if (display == NULL) { @@ -247,6 +232,21 @@ void init_wayland_globals() { bail("Missing a required global object"); } + shell = calloc(1, sizeof(struct shell)); + if (wl_shell != NULL) { + /* Use wl_shell */ + shell->proxy = (struct wl_proxy *) wl_shell; + shell_init_wl_shell(shell); + } else { +#ifdef HAVE_XDG_SHELL + /* Use xdg-shell */ + shell->proxy = (struct wl_proxy *) xdg_wm_base; + shell_init_xdg_shell(shell); +#else + bail("Unreachable: HAVE_XDG_SHELL undefined and no wl_shell"); +#endif + } + if (seat == NULL && requested_seat_name != NULL) { wl_display_roundtrip(display); } @@ -293,29 +293,8 @@ void popup_tiny_invisible_surface() { */ wl_display_dispatch(display); - shell_surface = calloc(1, sizeof(struct shell_surface)); - surface = wl_compositor_create_surface(compositor); - - if (shell != NULL) { - /* Use wl_shell */ - shell_surface->proxy - = (struct wl_proxy *) wl_shell_get_shell_surface(shell, surface); - shell_surface_init_wl_shell_surface(shell_surface); - } else { -#ifdef HAVE_XDG_SHELL - /* Use xdg-shell */ - xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL); - shell_surface->proxy - = (struct wl_proxy *) xdg_wm_base_get_xdg_surface( - xdg_wm_base, - surface - ); - shell_surface_init_xdg_surface(shell_surface); -#else - bail("Unreachable: HAVE_XDG_SHELL undefined and no wl_shell"); -#endif - } + shell_surface = shell_create_shell_surface(shell, surface); /* Signal that the surface is ready to be configured */ wl_surface_commit(surface); /* Wait for the surface to be configured */ diff --git a/src/boilerplate.h b/src/boilerplate.h index 2a3e9e8..27651f4 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -45,7 +45,7 @@ struct wl_data_device_manager *data_device_manager; struct wl_seat *seat; struct wl_compositor *compositor; struct wl_shm *shm; -struct wl_shell *shell; +struct wl_shell *wl_shell; struct wl_surface *surface; #ifdef HAVE_XDG_SHELL diff --git a/src/meson.build b/src/meson.build index fe842be..8554d0a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -110,6 +110,8 @@ lib = static_library( 'types/device-manager.c', 'types/keyboard.h', 'types/keyboard.c', + 'types/shell.h', + 'types/shell.c', 'types/shell-surface.h', 'types/shell-surface.c', ], diff --git a/src/types/shell.c b/src/types/shell.c new file mode 100644 index 0000000..36d192a --- /dev/null +++ b/src/types/shell.c @@ -0,0 +1,93 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types/shell-surface.h" +#include "types/shell.h" +#include "includes/shell-protocols.h" + +#include + +struct shell_surface *shell_create_shell_surface( + struct shell *self, + struct wl_surface *surface +) { + return self->do_create_shell_surface(self, surface); +} + + +/* Core Wayland implementation */ + +static struct shell_surface *wl_shell_create_shell_surface( + struct shell *self, + struct wl_surface *surface +) { + struct wl_shell *shell = (struct wl_shell *) self->proxy; + struct wl_shell_surface *wl_shell_surface = + wl_shell_get_shell_surface(shell, surface); + + struct shell_surface *shell_surface + = calloc(1, sizeof(struct shell_surface)); + shell_surface->proxy = (struct wl_proxy *) wl_shell_surface; + shell_surface_init_wl_shell_surface(shell_surface); + return shell_surface; +} + +void shell_init_wl_shell(struct shell *self) { + self->do_create_shell_surface = wl_shell_create_shell_surface; +} + + +/* xdg-shell implementation */ + +#ifdef HAVE_XDG_SHELL + +static struct shell_surface *xdg_shell_create_shell_surfacce( + struct shell *self, + struct wl_surface *surface +) { + struct xdg_wm_base *wm_base = (struct xdg_wm_base *) self->proxy; + + struct shell_surface *shell_surface + = calloc(1, sizeof(struct shell_surface)); + + struct xdg_surface *xdg_surface = + xdg_wm_base_get_xdg_surface(wm_base, surface); + shell_surface->proxy = (struct wl_proxy *) xdg_surface; + shell_surface_init_xdg_surface(shell_surface); + return shell_surface; +} + +static void xdg_wm_base_ping_handler( + void *data, + struct xdg_wm_base *wm_base, + uint32_t serial +) { + xdg_wm_base_pong(wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping_handler +}; + +void shell_init_xdg_shell(struct shell *self) { + struct xdg_wm_base *wm_base = (struct xdg_wm_base *) self->proxy; + xdg_wm_base_add_listener(wm_base, &xdg_wm_base_listener, self); + self->do_create_shell_surface = xdg_shell_create_shell_surfacce; +} + +#endif /* HAVE_XDG_SHELL */ diff --git a/src/types/shell.h b/src/types/shell.h new file mode 100644 index 0000000..9b866eb --- /dev/null +++ b/src/types/shell.h @@ -0,0 +1,53 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_SHELL_H +#define TYPES_SHELL_H + +#include "includes/shell-protocols.h" + +#include + +struct shell_surface; + +struct shell { + /* This field is initialized by the creator */ + struct wl_proxy *proxy; + + /* This field is initialized by the implementation */ + struct shell_surface *(*do_create_shell_surface)( + struct shell *self, + struct wl_surface *surface + ); +}; + +struct shell_surface *shell_create_shell_surface( + struct shell *self, + struct wl_surface *surface +); + + +/* Initializers */ + +void shell_init_wl_shell(struct shell *self); + +#ifdef HAVE_XDG_SHELL +void shell_init_xdg_shell(struct shell *self); +#endif + +#endif /* TYPES_SHELL_H */ From d66fb03313128544b36419e74578cc3fb8663aef Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 18:33:15 +0300 Subject: [PATCH 035/139] Move popup surface handling to types/popup-surface --- src/boilerplate.c | 54 +++++----------------- src/boilerplate.h | 1 - src/meson.build | 2 + src/types/popup-surface.c | 95 +++++++++++++++++++++++++++++++++++++++ src/types/popup-surface.h | 43 ++++++++++++++++++ 5 files changed, 151 insertions(+), 44 deletions(-) create mode 100644 src/types/popup-surface.c create mode 100644 src/types/popup-surface.h diff --git a/src/boilerplate.c b/src/boilerplate.c index 57b0d43..7bf77f9 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -20,9 +20,10 @@ #include "types/keyboard.h" #include "types/shell-surface.h" #include "types/shell.h" +#include "types/popup-surface.h" -static struct shell_surface *shell_surface = NULL; static struct shell *shell = NULL; +static struct popup_surface *popup_surface = NULL; static void process_new_seat(struct wl_seat *new_seat); @@ -293,52 +294,19 @@ void popup_tiny_invisible_surface() { */ wl_display_dispatch(display); - surface = wl_compositor_create_surface(compositor); - shell_surface = shell_create_shell_surface(shell, surface); - /* Signal that the surface is ready to be configured */ - wl_surface_commit(surface); - /* Wait for the surface to be configured */ - wl_display_roundtrip(display); + popup_surface = calloc(1, sizeof(struct popup_surface)); + popup_surface->wl_display = display; + popup_surface->wl_compositor = compositor; + popup_surface->wl_shm = shm; + popup_surface->shell = shell; - if (surface == NULL) { - /* It's possible that we've been given focus without us - * ever commiting a buffer, in which case the handlers - * may have already destroyed the surface; there's no - * way or need for us to commit a buffer in that case. - */ - return; - } - - int width = 1; - int height = 1; - int stride = width * 4; - int size = stride * height; // bytes - - /* Open an anonymous file and write some zero bytes to it */ - int fd = create_anonymous_file(); - ftruncate(fd, size); - - /* Turn it into a shared memory pool */ - struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); - - /* Allocate the buffer in that pool */ - struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, - 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); - /* Zeros in ARGB8888 mean fully transparent */ - - wl_surface_attach(surface, buffer, 0, 0); - wl_surface_damage(surface, 0, 0, width, height); - wl_surface_commit(surface); + popup_surface_init(popup_surface); } void destroy_popup_surface() { - if (shell_surface != NULL) { - shell_surface_destroy(shell_surface); - shell_surface = NULL; - } - if (surface != NULL) { - wl_surface_destroy(surface); - surface = NULL; + if (popup_surface != NULL) { + popup_surface_destroy(popup_surface); + popup_surface = NULL; } } diff --git a/src/boilerplate.h b/src/boilerplate.h index 27651f4..c36afa6 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -46,7 +46,6 @@ struct wl_seat *seat; struct wl_compositor *compositor; struct wl_shm *shm; struct wl_shell *wl_shell; -struct wl_surface *surface; #ifdef HAVE_XDG_SHELL struct xdg_wm_base *xdg_wm_base; diff --git a/src/meson.build b/src/meson.build index 8554d0a..55bc901 100644 --- a/src/meson.build +++ b/src/meson.build @@ -114,6 +114,8 @@ lib = static_library( 'types/shell.c', 'types/shell-surface.h', 'types/shell-surface.c', + 'types/popup-surface.h', + 'types/popup-surface.c', ], dependencies: wayland, link_with: protocol_deps diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c new file mode 100644 index 0000000..d599077 --- /dev/null +++ b/src/types/popup-surface.c @@ -0,0 +1,95 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types/popup-surface.h" +#include "types/shell.h" +#include "types/shell-surface.h" +#include "util/files.h" + +#include +#include +#include + +void popup_surface_init(struct popup_surface *self) { + self->wl_surface = wl_compositor_create_surface(self->wl_compositor); + self->shell_surface = shell_create_shell_surface( + self->shell, + self->wl_surface + ); + + /* Signal that the surface is ready to be configured */ + wl_surface_commit(self->wl_surface); + wl_display_roundtrip(self->wl_display); + + if (self->wl_surface == NULL) { + /* It's possible that we were given focus + * (without ever commiting a buffer) during + * the above roundtrip, in which case the + * handlers may have already destroyed the + * surface. No need to do anything further in + * that case. + */ + free(self); + return; + } + + /* Remember that after this point, we should + * free() self when it gets destroyed. + */ + self->should_free_self = 1; + + int width = 1; + int height = 1; + int stride = width * 4; + int size = stride * height; + + /* Open an anonymous file and write some zero bytes to it */ + int fd = create_anonymous_file(); + ftruncate(fd, size); + + /* Create a shared memory pool */ + struct wl_shm *wl_shm = self->wl_shm; + struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(wl_shm, fd, size); + + /* Allocate the buffer in that pool */ + struct wl_buffer *wl_buffer = wl_shm_pool_create_buffer( + wl_shm_pool, + 0, + width, + height, + stride, + WL_SHM_FORMAT_ARGB8888 + ); + /* We're using ARGB, so zero bytes mean + * a fully transparent pixel, which happens + * to be exactly what we want. + */ + + wl_surface_attach(self->wl_surface, wl_buffer, 0, 0); + wl_surface_damage(self->wl_surface, 0, 0, width, height); + wl_surface_commit(self->wl_surface); +} + +void popup_surface_destroy(struct popup_surface *self) { + shell_surface_destroy(self->shell_surface); + wl_surface_destroy(self->wl_surface); + + if (self->should_free_self) { + free(self); + } +} diff --git a/src/types/popup-surface.h b/src/types/popup-surface.h new file mode 100644 index 0000000..9193d73 --- /dev/null +++ b/src/types/popup-surface.h @@ -0,0 +1,43 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_POPUP_SURFACE_H +#define TYPES_POPUP_SURFACE_H + +#include + +struct shell; +struct shell_surface; + +struct popup_surface { + /* These fields are initialized by the creator */ + struct wl_display *wl_display; + struct wl_compositor *wl_compositor; + struct wl_shm *wl_shm; + struct shell *shell; + + /* These fields are initialized by the implementation */ + struct shell_surface *shell_surface; + struct wl_surface *wl_surface; + int should_free_self; +}; + +void popup_surface_init(struct popup_surface *self); +void popup_surface_destroy(struct popup_surface *self); + +#endif /* TYPES_POPUP_SURFACE_H */ From 12f775aae6c86fc05eca2ce01ac93b8a746ea808 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 13:01:12 +0300 Subject: [PATCH 036/139] Move registry and globals handling to types/registry --- src/boilerplate.c | 162 ++----------------------------------- src/boilerplate.h | 27 +------ src/meson.build | 2 + src/types/popup-surface.c | 21 ++++- src/types/popup-surface.h | 9 +-- src/types/registry.c | 163 ++++++++++++++++++++++++++++++++++++++ src/types/registry.h | 73 +++++++++++++++++ src/util/misc.c | 13 +++ src/util/misc.h | 2 + src/wl-copy.c | 60 +++----------- src/wl-paste.c | 57 +++---------- 11 files changed, 306 insertions(+), 283 deletions(-) create mode 100644 src/types/registry.c create mode 100644 src/types/registry.h diff --git a/src/boilerplate.c b/src/boilerplate.c index 7bf77f9..ece50be 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -21,111 +21,10 @@ #include "types/shell-surface.h" #include "types/shell.h" #include "types/popup-surface.h" +#include "types/registry.h" -static struct shell *shell = NULL; static struct popup_surface *popup_surface = NULL; -static void process_new_seat(struct wl_seat *new_seat); - -static void registry_global_handler -( - void *data, - struct wl_registry *registry, - uint32_t name, - const char *interface, - uint32_t version -) { - if (strcmp(interface, "wl_data_device_manager") == 0) { - data_device_manager = wl_registry_bind( - registry, - name, - &wl_data_device_manager_interface, - 1 - ); - } else if (strcmp(interface, "wl_seat") == 0) { - struct wl_seat *new_seat = wl_registry_bind( - registry, - name, - &wl_seat_interface, - 2 - ); - process_new_seat(new_seat); - } else if (strcmp(interface, "wl_compositor") == 0) { - compositor = wl_registry_bind( - registry, - name, - &wl_compositor_interface, - 3 - ); - } else if (strcmp(interface, "wl_shm") == 0) { - shm = wl_registry_bind( - registry, - name, - &wl_shm_interface, - 1 - ); - } else if (strcmp(interface, "wl_shell") == 0) { - wl_shell = wl_registry_bind( - registry, - name, - &wl_shell_interface, - 1 - ); - } -#ifdef HAVE_XDG_SHELL - else if (strcmp(interface, "xdg_wm_base") == 0) { - xdg_wm_base = wl_registry_bind( - registry, - name, - &xdg_wm_base_interface, - 1 - ); - } -#endif -#ifdef HAVE_GTK_PRIMARY_SELECTION - else if (strcmp(interface, "gtk_primary_selection_device_manager") == 0) { - gtk_primary_selection_device_manager = wl_registry_bind( - registry, - name, - >k_primary_selection_device_manager_interface, - 1 - ); - } -#endif -#ifdef HAVE_WP_PRIMARY_SELECTION - else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0) { - primary_selection_device_manager = wl_registry_bind( - registry, - name, - &zwp_primary_selection_device_manager_v1_interface, - 1 - ); - } -#endif -#ifdef HAVE_WLR_DATA_CONTROL - else if (strcmp(interface, "zwlr_data_control_manager_v1") == 0) { - data_control_manager = wl_registry_bind( - registry, - name, - &zwlr_data_control_manager_v1_interface, - 1 - ); - } -#endif -} - -static void registry_global_remove_handler -( - void *data, - struct wl_registry *registry, - uint32_t name -) {} - -static const struct wl_registry_listener registry_listener = { - .global = registry_global_handler, - .global_remove = registry_global_remove_handler -}; - static void forward_on_focus(struct keyboard *keyboard, uint32_t serial) { struct wl_seat *this_seat = (struct wl_seat *) keyboard->data; /* When we get to here, global seat is already initialized */ @@ -177,7 +76,7 @@ static const struct wl_seat_listener seat_listener = { #define UNSET_CAPABILITIES ((void *) (uint32_t) 35) -static void process_new_seat(struct wl_seat *new_seat) { +void process_new_seat(struct wl_seat *new_seat) { if (seat != NULL) { wl_seat_destroy(new_seat); return; @@ -214,40 +113,13 @@ void init_wayland_globals() { bail("Failed to connect to a Wayland server"); } - struct wl_registry *registry = wl_display_get_registry(display); - wl_registry_add_listener(registry, ®istry_listener, NULL); + registry = calloc(1, sizeof(struct registry)); + registry->wl_display = display; + registry_init(registry); /* Wait for the "initial" set of globals to appear */ wl_display_roundtrip(display); - if ( - data_device_manager == NULL || - compositor == NULL || - shm == NULL || - (shell == NULL -#ifdef HAVE_XDG_SHELL - && xdg_wm_base == NULL -#endif - ) - ) { - bail("Missing a required global object"); - } - - shell = calloc(1, sizeof(struct shell)); - if (wl_shell != NULL) { - /* Use wl_shell */ - shell->proxy = (struct wl_proxy *) wl_shell; - shell_init_wl_shell(shell); - } else { -#ifdef HAVE_XDG_SHELL - /* Use xdg-shell */ - shell->proxy = (struct wl_proxy *) xdg_wm_base; - shell_init_xdg_shell(shell); -#else - bail("Unreachable: HAVE_XDG_SHELL undefined and no wl_shell"); -#endif - } - if (seat == NULL && requested_seat_name != NULL) { wl_display_roundtrip(display); } @@ -259,25 +131,6 @@ void init_wayland_globals() { } } -void ensure_has_primary_selection() { -#ifdef HAVE_GTK_PRIMARY_SELECTION - if (gtk_primary_selection_device_manager != NULL) { - return; - } -#endif -#ifdef HAVE_WP_PRIMARY_SELECTION - if (primary_selection_device_manager != NULL) { - return; - } -#endif - -#if defined(HAVE_GTK_PRIMARY_SELECTION) || defined(HAVE_WP_PRIMARY_SELECTION) - bail("Primary selection is not supported on this compositor"); -#else - bail("wl-clipboard was built without primary selection support"); -#endif -} - void popup_tiny_invisible_surface() { /* HACK: * Pop up a tiny invisible surface to get the keyboard focus, @@ -295,10 +148,7 @@ void popup_tiny_invisible_surface() { wl_display_dispatch(display); popup_surface = calloc(1, sizeof(struct popup_surface)); - popup_surface->wl_display = display; - popup_surface->wl_compositor = compositor; - popup_surface->wl_shm = shm; - popup_surface->shell = shell; + popup_surface->registry = registry; popup_surface_init(popup_surface); } diff --git a/src/boilerplate.h b/src/boilerplate.h index c36afa6..a7928cc 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -40,29 +40,12 @@ #include "includes/shell-protocols.h" #include "includes/selection-protocols.h" +#include "types/registry.h" + struct wl_display *display; -struct wl_data_device_manager *data_device_manager; +struct registry *registry; + struct wl_seat *seat; -struct wl_compositor *compositor; -struct wl_shm *shm; -struct wl_shell *wl_shell; - -#ifdef HAVE_XDG_SHELL -struct xdg_wm_base *xdg_wm_base; -#endif - -#ifdef HAVE_GTK_PRIMARY_SELECTION -struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager; -#endif - -#ifdef HAVE_WP_PRIMARY_SELECTION -struct zwp_primary_selection_device_manager_v1 *primary_selection_device_manager; -#endif - -#ifdef HAVE_WLR_DATA_CONTROL -struct zwlr_data_control_manager_v1 *data_control_manager; -#endif - const char *requested_seat_name; void init_wayland_globals(void); @@ -73,6 +56,4 @@ void destroy_popup_surface(void); void (*action_on_popup_surface_getting_focus)(uint32_t serial); void (*action_on_no_keyboard)(void); -void ensure_has_primary_selection(void); - uint32_t get_serial(void); diff --git a/src/meson.build b/src/meson.build index 55bc901..ce3d2b4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -116,6 +116,8 @@ lib = static_library( 'types/shell-surface.c', 'types/popup-surface.h', 'types/popup-surface.c', + 'types/registry.h', + 'types/registry.c', ], dependencies: wayland, link_with: protocol_deps diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index d599077..fe2a59f 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -17,16 +17,27 @@ */ #include "types/popup-surface.h" +#include "types/registry.h" #include "types/shell.h" #include "types/shell-surface.h" #include "util/files.h" +#include "util/misc.h" #include #include #include void popup_surface_init(struct popup_surface *self) { - self->wl_surface = wl_compositor_create_surface(self->wl_compositor); + self->shell = registry_find_shell(self->registry); + if (self->shell == NULL) { + bail("Missing a shell"); + } + + struct wl_compositor *wl_compositor = self->registry->wl_compositor; + if (wl_compositor == NULL) { + bail("Missing the compositor"); + } + self->wl_surface = wl_compositor_create_surface(wl_compositor); self->shell_surface = shell_create_shell_surface( self->shell, self->wl_surface @@ -34,7 +45,7 @@ void popup_surface_init(struct popup_surface *self) { /* Signal that the surface is ready to be configured */ wl_surface_commit(self->wl_surface); - wl_display_roundtrip(self->wl_display); + wl_display_roundtrip(self->registry->wl_display); if (self->wl_surface == NULL) { /* It's possible that we were given focus @@ -63,7 +74,10 @@ void popup_surface_init(struct popup_surface *self) { ftruncate(fd, size); /* Create a shared memory pool */ - struct wl_shm *wl_shm = self->wl_shm; + struct wl_shm *wl_shm = self->registry->wl_shm; + if (wl_shm == NULL) { + bail("Missing the shm"); + } struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(wl_shm, fd, size); /* Allocate the buffer in that pool */ @@ -88,6 +102,7 @@ void popup_surface_init(struct popup_surface *self) { void popup_surface_destroy(struct popup_surface *self) { shell_surface_destroy(self->shell_surface); wl_surface_destroy(self->wl_surface); + free(self->shell); if (self->should_free_self) { free(self); diff --git a/src/types/popup-surface.h b/src/types/popup-surface.h index 9193d73..bb54f21 100644 --- a/src/types/popup-surface.h +++ b/src/types/popup-surface.h @@ -21,17 +21,16 @@ #include +struct registry; struct shell; struct shell_surface; struct popup_surface { - /* These fields are initialized by the creator */ - struct wl_display *wl_display; - struct wl_compositor *wl_compositor; - struct wl_shm *wl_shm; - struct shell *shell; + /* This field is initialized by the creator */ + struct registry *registry; /* These fields are initialized by the implementation */ + struct shell *shell; struct shell_surface *shell_surface; struct wl_surface *wl_surface; int should_free_self; diff --git a/src/types/registry.c b/src/types/registry.c new file mode 100644 index 0000000..101dbad --- /dev/null +++ b/src/types/registry.c @@ -0,0 +1,163 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types/registry.h" +#include "types/shell.h" +#include "types/device-manager.h" +#include "includes/shell-protocols.h" +#include "includes/selection-protocols.h" +#include "util/misc.h" + +#include +#include + +void process_new_seat(struct wl_seat *new_seat); + +#define BIND(interface_name, known_version) \ +if (strcmp(interface, #interface_name) == 0) { \ + self->interface_name = wl_registry_bind( \ + wl_registry, \ + name, \ + &interface_name ## _interface, \ + known_version \ + ); \ +} + +static void wl_registry_global_handler( + void *data, + struct wl_registry *wl_registry, + uint32_t name, + const char *interface, + uint32_t version +) { + struct registry *self = (struct registry *) data; + + BIND(wl_compositor, 2) + BIND(wl_shm, 1) + + /* Shells */ + + BIND(wl_shell, 1) + +#ifdef HAVE_XDG_SHELL + BIND(xdg_wm_base, 1) +#endif + + /* Device managers */ + + BIND(wl_data_device_manager, 1) + +#ifdef HAVE_GTK_PRIMARY_SELECTION + BIND(gtk_primary_selection_device_manager, 1) +#endif + +#ifdef HAVE_WP_PRIMARY_SELECTION + BIND(zwp_primary_selection_device_manager_v1, 1) +#endif + +#ifdef HAVE_WLR_DATA_CONTROL + BIND(zwlr_data_control_manager_v1, 1) +#endif + + if (strcmp(interface, "wl_seat") == 0) { + struct wl_seat *seat = wl_registry_bind( + wl_registry, + name, + &wl_seat_interface, + 2 + ); + process_new_seat(seat); + } +} + +static void wl_registry_global_remove_handler( + void *data, + struct wl_registry *wl_registry, + uint32_t name +) {} + +static const struct wl_registry_listener wl_registry_listener = { + .global = wl_registry_global_handler, + .global_remove = wl_registry_global_remove_handler +}; + +void registry_init(struct registry *self) { + self->proxy = wl_display_get_registry(self->wl_display); + wl_registry_add_listener(self->proxy, &wl_registry_listener, self); +} + +struct shell *registry_find_shell(struct registry *self) { + struct shell *shell = calloc(1, sizeof(struct shell)); + + if (self->wl_shell != NULL) { + shell->proxy = (struct wl_proxy *) self->wl_shell; + shell_init_wl_shell(shell); + return shell; + } + +#ifdef HAVE_XDG_SHELL + if (self->xdg_wm_base != NULL) { + shell->proxy = (struct wl_proxy *) self->xdg_wm_base; + shell_init_xdg_shell(shell); + return shell; + } +#endif + + free(shell); + return NULL; +} + +#define TRY(type) \ +if (self->type != NULL) { \ + device_manager->proxy = (struct wl_proxy *) self->type; \ + device_manager_init_ ## type(device_manager); \ + return device_manager; \ +} + +struct device_manager *registry_find_device_manager( + struct registry *self, + int primary +) { + struct device_manager *device_manager + = calloc(1, sizeof(struct device_manager)); + + /* We prefer wlr-data-control, as it doesn't require + * us to use the popup surface hack. + */ + + if (!primary) { +#ifdef HAVE_WLR_DATA_CONTROL + TRY(zwlr_data_control_manager_v1) +#endif + TRY(wl_data_device_manager) + + free(device_manager); + return NULL; + } + +#ifdef HAVE_WP_PRIMARY_SELECTION + TRY(zwp_primary_selection_device_manager_v1) +#endif + +#ifdef HAVE_GTK_PRIMARY_SELECTION + TRY(gtk_primary_selection_device_manager) +#endif + + free(device_manager); + return NULL; +} diff --git a/src/types/registry.h b/src/types/registry.h new file mode 100644 index 0000000..a131d14 --- /dev/null +++ b/src/types/registry.h @@ -0,0 +1,73 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_REGISTRY_H +#define TYPES_REGISTRY_H + +#include "includes/shell-protocols.h" +#include "includes/selection-protocols.h" + +#include + +struct shell; +struct device_manager; + +struct registry { + /* This field is initialized by the creator */ + struct wl_display *wl_display; + + /* These fields are initialized by the implementation */ + + struct wl_registry *proxy; + struct wl_compositor *wl_compositor; + struct wl_shm *wl_shm; + + /* Shells */ + + struct wl_shell *wl_shell; +#ifdef HAVE_XDG_SHELL + struct xdg_wm_base *xdg_wm_base; +#endif + + /* Device managers */ + + struct wl_data_device_manager *wl_data_device_manager; +#ifdef HAVE_GTK_PRIMARY_SELECTION + struct gtk_primary_selection_device_manager + *gtk_primary_selection_device_manager; +#endif +#ifdef HAVE_WP_PRIMARY_SELECTION + struct zwp_primary_selection_device_manager_v1 + *zwp_primary_selection_device_manager_v1; +#endif +#ifdef HAVE_WLR_DATA_CONTROL + struct zwlr_data_control_manager_v1 + *zwlr_data_control_manager_v1; +#endif +}; + +void registry_init(struct registry *self); + +struct shell *registry_find_shell(struct registry *self); + +struct device_manager *registry_find_device_manager( + struct registry *self, + int primary +); + +#endif /* TYPES_REGISTRY_H */ diff --git a/src/util/misc.c b/src/util/misc.c index 934792e..f252a62 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -31,3 +31,16 @@ void print_version_info() { "There is NO WARRANTY, to the extent permitted by law.\n" ); } + +void complain_about_selection_support(int primary) { + if (!primary) { + /* We always expect to find at least wl_data_device_manager */ + bail("Missing a required global object"); + } + +#if !defined(HAVE_WP_PRIMARY_SELECTION) && !defined(HAVE_GTK_PRIMARY_SELECTION) + bail("wl-clipboard was built without primary selection support"); +#endif + + bail("The compositor does not seem to support primary selection"); +} diff --git a/src/util/misc.h b/src/util/misc.h index 54890b9..be5298f 100644 --- a/src/util/misc.h +++ b/src/util/misc.h @@ -26,4 +26,6 @@ void print_version_info(void); +void complain_about_selection_support(int primary); + #endif /* UTIL_MISC_H */ diff --git a/src/wl-copy.c b/src/wl-copy.c index c8801c7..22f5abc 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -20,6 +20,7 @@ #include "types/source.h" #include "types/device.h" #include "types/device-manager.h" +#include "types/registry.h" static struct { int stay_in_foreground; @@ -33,7 +34,6 @@ static struct { static char * const *data_to_copy = NULL; static char *temp_file_to_copy = NULL; -static struct device_manager *device_manager = NULL; static struct device *device = NULL; static struct source *source = NULL; @@ -120,45 +120,6 @@ static void do_offer(char *mime_type, struct source *source) { free(mime_type); } -static void init_device_manager() { -#ifdef HAVE_WLR_DATA_CONTROL - if (data_control_manager != NULL) { - device_manager->proxy = (struct wl_proxy *) data_control_manager; - device_manager_init_zwlr_data_control_manager_v1(device_manager); - return; - } -#endif - - device_manager->proxy = (struct wl_proxy *) data_device_manager; - device_manager_init_wl_data_device_manager(device_manager); -} - -static void init_primary_device_manager() { - ensure_has_primary_selection(); - -#ifdef HAVE_WP_PRIMARY_SELECTION - if (primary_selection_device_manager != NULL) { - device_manager->proxy - = (struct wl_proxy *) primary_selection_device_manager; - device_manager_init_zwp_primary_selection_device_manager_v1( - device_manager - ); - return; - } -#endif - -#ifdef HAVE_GTK_PRIMARY_SELECTION - if (gtk_primary_selection_device_manager != NULL) { - device_manager->proxy - = (struct wl_proxy *) gtk_primary_selection_device_manager; - device_manager_init_gtk_primary_selection_device_manager( - device_manager - ); - return; - } -#endif -} - static void print_usage(FILE *f, const char *argv0) { fprintf( f, @@ -254,10 +215,6 @@ int main(int argc, char * const argv[]) { init_wayland_globals(); - if (options.primary) { - ensure_has_primary_selection(); - } - if (!options.clear) { if (optind < argc) { /* Copy our command-line arguments */ @@ -291,15 +248,20 @@ int main(int argc, char * const argv[]) { } } - device_manager = calloc(1, sizeof(struct device_manager)); - if (!options.primary) { - init_device_manager(); - } else { - init_primary_device_manager(); + /* Create the device */ + struct device_manager *device_manager + = registry_find_device_manager(registry, options.primary); + if (device_manager == NULL) { + complain_about_selection_support(options.primary); } device = device_manager_get_device(device_manager, seat); + if (!device_supports_selection(device, options.primary)) { + complain_about_selection_support(options.primary); + } + + /* Create and initialize the source */ source = device_manager_create_source(device_manager); source->send_callback = send_callback; source->cancelled_callback = cancelled_callback; diff --git a/src/wl-paste.c b/src/wl-paste.c index f8a89c0..c4c8f57 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -20,6 +20,7 @@ #include "types/offer.h" #include "types/device.h" #include "types/device-manager.h" +#include "types/registry.h" static struct { char *explicit_type; @@ -39,8 +40,6 @@ struct types { const char *any; }; -static struct device_manager *device_manager = NULL; - static struct types classify_offer_types(struct offer *offer) { struct types types = { 0 }; offer_for_each_mime_type(offer, mime_type) { @@ -240,45 +239,6 @@ static void print_usage(FILE *f, const char *argv0) { ); } -static void init_device_manager() { -#ifdef HAVE_WLR_DATA_CONTROL - if (data_control_manager != NULL) { - device_manager->proxy = (struct wl_proxy *) data_control_manager; - device_manager_init_zwlr_data_control_manager_v1(device_manager); - return; - } -#endif - - device_manager->proxy = (struct wl_proxy *) data_device_manager; - device_manager_init_wl_data_device_manager(device_manager); -} - -static void init_primary_device_manager() { - ensure_has_primary_selection(); - -#ifdef HAVE_WP_PRIMARY_SELECTION - if (primary_selection_device_manager != NULL) { - device_manager->proxy - = (struct wl_proxy *) primary_selection_device_manager; - device_manager_init_zwp_primary_selection_device_manager_v1( - device_manager - ); - return; - } -#endif - -#ifdef HAVE_GTK_PRIMARY_SELECTION - if (gtk_primary_selection_device_manager != NULL) { - device_manager->proxy - = (struct wl_proxy *) gtk_primary_selection_device_manager; - device_manager_init_gtk_primary_selection_device_manager( - device_manager - ); - return; - } -#endif -} - static void parse_options(int argc, char * const argv[]) { if (argc < 1) { bail("Empty argv"); @@ -343,7 +303,6 @@ static void parse_options(int argc, char * const argv[]) { int main(int argc, char * const argv[]) { parse_options(argc, argv); - char *path = path_for_fd(STDOUT_FILENO); if (path != NULL && options.explicit_type == NULL) { options.inferred_type = infer_mime_type_from_name(path); @@ -352,14 +311,18 @@ int main(int argc, char * const argv[]) { init_wayland_globals(); - device_manager = calloc(1, sizeof(struct device_manager)); - if (!options.primary) { - init_device_manager(); - } else { - init_primary_device_manager(); + /* Create the device */ + struct device_manager *device_manager + = registry_find_device_manager(registry, options.primary); + if (device_manager == NULL) { + complain_about_selection_support(options.primary); } struct device *device = device_manager_get_device(device_manager, seat); + + if (!device_supports_selection(device, options.primary)) { + complain_about_selection_support(options.primary); + } device->selection_callback = selection_callback; if (device->needs_popup_surface) { From d342c85bfce0cc345cf20414e85769aa35e52999 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 13:32:57 +0300 Subject: [PATCH 037/139] Move seat handling to types/seat This type is now responsible for keeping track of the seat's name and capabilities. An array of available seats is stored by the registry, which allows you to find a desired seat by its name. --- src/boilerplate.c | 94 ++++---------------------------------- src/boilerplate.h | 3 +- src/meson.build | 2 + src/types/device-manager.c | 6 ++- src/types/device-manager.h | 5 +- src/types/registry.c | 27 +++++++++-- src/types/registry.h | 8 ++++ src/types/seat.c | 61 +++++++++++++++++++++++++ src/types/seat.h | 39 ++++++++++++++++ 9 files changed, 151 insertions(+), 94 deletions(-) create mode 100644 src/types/seat.c create mode 100644 src/types/seat.h diff --git a/src/boilerplate.c b/src/boilerplate.c index ece50be..58c24db 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -26,87 +26,11 @@ static struct popup_surface *popup_surface = NULL; static void forward_on_focus(struct keyboard *keyboard, uint32_t serial) { - struct wl_seat *this_seat = (struct wl_seat *) keyboard->data; - /* When we get to here, global seat is already initialized */ - if (this_seat != seat) { - return; - } if (action_on_popup_surface_getting_focus != NULL) { action_on_popup_surface_getting_focus(serial); } } -static void seat_capabilities_handler -( - void *data, - struct wl_seat *this_seat, - uint32_t capabilities -) { - /* Stash the capabilities of this seat for later */ - void *user_data = (void *) (unsigned long) capabilities; - wl_seat_set_user_data(this_seat, user_data); - - if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { - struct keyboard *keyboard = calloc(1, sizeof(struct keyboard)); - keyboard->proxy = wl_seat_get_keyboard(this_seat); - keyboard->data = (void *) this_seat; - keyboard->on_focus = forward_on_focus; - keyboard_init(keyboard); - } -} - -static void seat_name_handler -( - void *data, - struct wl_seat *this_seat, - const char *name -) { - if (requested_seat_name == NULL) { - return; - } - if (strcmp(name, requested_seat_name) == 0) { - seat = this_seat; - } -} - -static const struct wl_seat_listener seat_listener = { - .capabilities = seat_capabilities_handler, - .name = seat_name_handler -}; - -#define UNSET_CAPABILITIES ((void *) (uint32_t) 35) - -void process_new_seat(struct wl_seat *new_seat) { - if (seat != NULL) { - wl_seat_destroy(new_seat); - return; - } - if (requested_seat_name == NULL) { - seat = new_seat; - } - wl_seat_add_listener(new_seat, &seat_listener, UNSET_CAPABILITIES); -} - -int ensure_seat_has_keyboard() { - void *user_data = wl_seat_get_user_data(seat); - while (user_data == UNSET_CAPABILITIES) { - wl_display_roundtrip(display); - user_data = wl_seat_get_user_data(seat); - } - - uint32_t capabilities = (uint32_t) (unsigned long) user_data; - if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { - return 1; - } - - if (action_on_no_keyboard != NULL) { - action_on_no_keyboard(); - } - return 0; -} - -#undef UNSET_CAPABILITIES - void init_wayland_globals() { display = wl_display_connect(NULL); if (display == NULL) { @@ -120,14 +44,13 @@ void init_wayland_globals() { /* Wait for the "initial" set of globals to appear */ wl_display_roundtrip(display); - if (seat == NULL && requested_seat_name != NULL) { - wl_display_roundtrip(display); - } + seat = registry_find_seat(registry, requested_seat_name); if (seat == NULL) { - if (requested_seat_name == NULL) { - bail("No seat available"); + if (requested_seat_name != NULL) { + bail("No such seat"); + } else { + bail("Missing a seat"); } - bail("Cannot find the requested seat"); } } @@ -137,10 +60,11 @@ void popup_tiny_invisible_surface() { * otherwise we won't be notified of the selection. */ - if (!ensure_seat_has_keyboard()) { - return; + struct keyboard *keyboard = seat_get_keyboard(seat); + if (keyboard == NULL) { + bail("This seat has no keyboard"); } - + keyboard->on_focus = forward_on_focus; /* Make sure that we get the keyboard * object before creating the surface, * so that we get the enter event. diff --git a/src/boilerplate.h b/src/boilerplate.h index a7928cc..5ef4431 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -41,11 +41,12 @@ #include "includes/selection-protocols.h" #include "types/registry.h" +#include "types/seat.h" struct wl_display *display; struct registry *registry; -struct wl_seat *seat; +struct seat *seat; const char *requested_seat_name; void init_wayland_globals(void); diff --git a/src/meson.build b/src/meson.build index ce3d2b4..895415f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -110,6 +110,8 @@ lib = static_library( 'types/device-manager.c', 'types/keyboard.h', 'types/keyboard.c', + 'types/seat.h', + 'types/seat.c', 'types/shell.h', 'types/shell.c', 'types/shell-surface.h', diff --git a/src/types/device-manager.c b/src/types/device-manager.c index dff3cb4..54e37ce 100644 --- a/src/types/device-manager.c +++ b/src/types/device-manager.c @@ -19,6 +19,7 @@ #include "types/device-manager.h" #include "types/device.h" #include "types/source.h" +#include "types/seat.h" #include "includes/selection-protocols.h" #include @@ -30,7 +31,7 @@ struct source *device_manager_create_source(struct device_manager *self) { struct device *device_manager_get_device( struct device_manager *self, - struct wl_seat *seat + struct seat *seat ) { return self->do_get_device(self, seat); } @@ -51,9 +52,10 @@ static struct source *device_manager_ ## type ## _do_create_source( \ #define GET_DEVICE(type, device_type, method_name) \ static struct device *device_manager_ ## type ## _do_get_device( \ struct device_manager *self, \ - struct wl_seat *seat \ + struct seat *seat_wrapper \ ) { \ struct type *proxy = (struct type *) self->proxy; \ + struct wl_seat *seat = (struct wl_seat *) seat_wrapper->proxy; \ struct device *device = calloc(1, sizeof(struct device)); \ device->proxy = (struct wl_proxy *) type ## _ ## method_name(proxy, seat); \ device_init_ ## device_type(device); \ diff --git a/src/types/device-manager.h b/src/types/device-manager.h index 57e1270..433546f 100644 --- a/src/types/device-manager.h +++ b/src/types/device-manager.h @@ -23,6 +23,7 @@ #include +struct seat; struct device; struct source; @@ -34,7 +35,7 @@ struct device_manager { struct source *(*do_create_source)(struct device_manager *self); struct device *(*do_get_device)( struct device_manager *self, - struct wl_seat *seat + struct seat *seat ); }; @@ -42,7 +43,7 @@ struct source *device_manager_create_source(struct device_manager *self); struct device *device_manager_get_device( struct device_manager *self, - struct wl_seat *seat + struct seat *seat ); /* Initializers */ diff --git a/src/types/registry.c b/src/types/registry.c index 101dbad..9826541 100644 --- a/src/types/registry.c +++ b/src/types/registry.c @@ -17,6 +17,7 @@ */ #include "types/registry.h" +#include "types/seat.h" #include "types/shell.h" #include "types/device-manager.h" #include "includes/shell-protocols.h" @@ -26,8 +27,6 @@ #include #include -void process_new_seat(struct wl_seat *new_seat); - #define BIND(interface_name, known_version) \ if (strcmp(interface, #interface_name) == 0) { \ self->interface_name = wl_registry_bind( \ @@ -75,13 +74,16 @@ static void wl_registry_global_handler( #endif if (strcmp(interface, "wl_seat") == 0) { - struct wl_seat *seat = wl_registry_bind( + struct seat *seat = calloc(1, sizeof(struct seat)); + seat->proxy = wl_registry_bind( wl_registry, name, &wl_seat_interface, 2 ); - process_new_seat(seat); + seat_init(seat); + struct seat **ptr = wl_array_add(&self->seats, sizeof(struct seat *)); + *ptr = seat; } } @@ -161,3 +163,20 @@ struct device_manager *registry_find_device_manager( free(device_manager); return NULL; } + +struct seat *registry_find_seat( + struct registry *self, + const char *name +) { + /* Ensure we get all the seat info */ + wl_display_roundtrip(self->wl_display); + + struct seat **ptr; + wl_array_for_each(ptr, &self->seats) { + struct seat *seat = *ptr; + if (name == NULL || strcmp(seat->name, name) == 0) { + return seat; + } + } + return NULL; +} diff --git a/src/types/registry.h b/src/types/registry.h index a131d14..e52e427 100644 --- a/src/types/registry.h +++ b/src/types/registry.h @@ -26,6 +26,7 @@ struct shell; struct device_manager; +struct seat; struct registry { /* This field is initialized by the creator */ @@ -34,6 +35,8 @@ struct registry { /* These fields are initialized by the implementation */ struct wl_registry *proxy; + struct wl_array seats; + struct wl_compositor *wl_compositor; struct wl_shm *wl_shm; @@ -70,4 +73,9 @@ struct device_manager *registry_find_device_manager( int primary ); +struct seat *registry_find_seat( + struct registry *self, + const char *name +); + #endif /* TYPES_REGISTRY_H */ diff --git a/src/types/seat.c b/src/types/seat.c new file mode 100644 index 0000000..6e48450 --- /dev/null +++ b/src/types/seat.c @@ -0,0 +1,61 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types/seat.h" +#include "types/keyboard.h" + +#include +#include +#include + +static void wl_seat_capabilities_handler( + void *data, + struct wl_seat *seat, + uint32_t capabilities +) { + struct seat *self = (struct seat *) data; + self->capabilities = capabilities; +} + +static void wl_seat_name_handler( + void *data, + struct wl_seat *seat, + const char *name +) { + struct seat *self = (struct seat *) data; + self->name = strdup(name); +} + +static const struct wl_seat_listener wl_seat_listener = { + .capabilities = wl_seat_capabilities_handler, + .name = wl_seat_name_handler +}; + +void seat_init(struct seat *self) { + wl_seat_add_listener(self->proxy, &wl_seat_listener, self); +} + +struct keyboard *seat_get_keyboard(struct seat *self) { + if ((self->capabilities & WL_SEAT_CAPABILITY_KEYBOARD) == 0) { + return NULL; + } + struct keyboard *keyboard = calloc(1, sizeof(struct keyboard)); + keyboard->proxy = wl_seat_get_keyboard(self->proxy); + keyboard_init(keyboard); + return keyboard; +} diff --git a/src/types/seat.h b/src/types/seat.h new file mode 100644 index 0000000..cd92073 --- /dev/null +++ b/src/types/seat.h @@ -0,0 +1,39 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_SEAT_H +#define TYPES_SEAT_H + +#include + +struct keyboard; + +struct seat { + /* This field is initialized by the creator */ + struct wl_seat *proxy; + + /* These fields are initialized by the implementation */ + char *name; + uint32_t capabilities; +}; + +void seat_init(struct seat *self); +struct keyboard *seat_get_keyboard(struct seat *self); + + +#endif /* TYPES_SEAT_H */ From 9d2d90184fa79ee1e62b0d38ab40706a23185cd0 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 13:53:39 +0300 Subject: [PATCH 038/139] Make popup surface manage the keyboard and forward its callback --- src/boilerplate.c | 16 +++------------- src/types/popup-surface.c | 34 ++++++++++++++++++++++++++++++---- src/types/popup-surface.h | 8 +++++++- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/boilerplate.c b/src/boilerplate.c index 58c24db..d2a6621 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -17,7 +17,6 @@ */ #include "boilerplate.h" -#include "types/keyboard.h" #include "types/shell-surface.h" #include "types/shell.h" #include "types/popup-surface.h" @@ -25,7 +24,7 @@ static struct popup_surface *popup_surface = NULL; -static void forward_on_focus(struct keyboard *keyboard, uint32_t serial) { +static void forward_on_focus(struct popup_surface *popup_surface, uint32_t serial) { if (action_on_popup_surface_getting_focus != NULL) { action_on_popup_surface_getting_focus(serial); } @@ -60,19 +59,10 @@ void popup_tiny_invisible_surface() { * otherwise we won't be notified of the selection. */ - struct keyboard *keyboard = seat_get_keyboard(seat); - if (keyboard == NULL) { - bail("This seat has no keyboard"); - } - keyboard->on_focus = forward_on_focus; - /* Make sure that we get the keyboard - * object before creating the surface, - * so that we get the enter event. - */ - wl_display_dispatch(display); - popup_surface = calloc(1, sizeof(struct popup_surface)); popup_surface->registry = registry; + popup_surface->seat = seat; + popup_surface->on_focus = forward_on_focus; popup_surface_init(popup_surface); } diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index fe2a59f..b629a77 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -18,6 +18,8 @@ #include "types/popup-surface.h" #include "types/registry.h" +#include "types/seat.h" +#include "types/keyboard.h" #include "types/shell.h" #include "types/shell-surface.h" #include "util/files.h" @@ -27,12 +29,36 @@ #include #include +static void forward_on_focus( + struct keyboard *keyboard, + uint32_t serial +) { + struct popup_surface *self = (struct popup_surface *) keyboard->data; + if (self->on_focus != NULL) { + self->on_focus(self, serial); + } +} + void popup_surface_init(struct popup_surface *self) { self->shell = registry_find_shell(self->registry); if (self->shell == NULL) { bail("Missing a shell"); } + self->keyboard = seat_get_keyboard(self->seat); + if (self->keyboard == NULL) { + bail("This seat has no keyboard"); + } + self->keyboard->on_focus = forward_on_focus; + self->keyboard->data = self; + + /* Make sure that we get the keyboard + * object before we create the surface, + * so that we get the enter event. + */ + wl_display_dispatch(self->registry->wl_display); + + struct wl_compositor *wl_compositor = self->registry->wl_compositor; if (wl_compositor == NULL) { bail("Missing the compositor"); @@ -50,10 +76,10 @@ void popup_surface_init(struct popup_surface *self) { if (self->wl_surface == NULL) { /* It's possible that we were given focus * (without ever commiting a buffer) during - * the above roundtrip, in which case the - * handlers may have already destroyed the - * surface. No need to do anything further in - * that case. + * the above roundtrip, in which case we have + * already fired the callback and have likely + * already destroyed the surface. No need to + * do anything further in that case. */ free(self); return; diff --git a/src/types/popup-surface.h b/src/types/popup-surface.h index bb54f21..8847519 100644 --- a/src/types/popup-surface.h +++ b/src/types/popup-surface.h @@ -24,15 +24,21 @@ struct registry; struct shell; struct shell_surface; +struct seat; +struct keyboard; struct popup_surface { - /* This field is initialized by the creator */ + /* These fields are initialized by the creator */ struct registry *registry; + struct seat *seat; + void (*on_focus)(struct popup_surface *self, uint32_t serial); + void *data; /* These fields are initialized by the implementation */ struct shell *shell; struct shell_surface *shell_surface; struct wl_surface *wl_surface; + struct keyboard *keyboard; int should_free_self; }; From b43487e83842ca81a79196c0fb94d79e7695aa77 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 14:06:16 +0300 Subject: [PATCH 039/139] Add a typedef for argv_t --- src/util/string.h | 2 ++ src/wl-copy.c | 6 +++--- src/wl-paste.c | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/util/string.h b/src/util/string.h index 4602652..239cf58 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -22,6 +22,8 @@ #define text_plain "text/plain" #define text_plain_utf8 "text/plain;charset=utf-8" +typedef char * const *argv_t; + int mime_type_is_text(const char *mime_type); int str_has_prefix(const char *string, const char *prefix); diff --git a/src/wl-copy.c b/src/wl-copy.c index 22f5abc..bd8a87e 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -31,7 +31,7 @@ static struct { int primary; } options; -static char * const *data_to_copy = NULL; +static argv_t data_to_copy = NULL; static char *temp_file_to_copy = NULL; static struct device *device = NULL; @@ -147,7 +147,7 @@ static void print_usage(FILE *f, const char *argv0) { ); } -static void parse_options(int argc, char * const argv[]) { +static void parse_options(int argc, argv_t argv) { if (argc < 1) { bail("Empty argv"); } @@ -210,7 +210,7 @@ static void parse_options(int argc, char * const argv[]) { } } -int main(int argc, char * const argv[]) { +int main(int argc, argv_t argv) { parse_options(argc, argv); init_wayland_globals(); diff --git a/src/wl-paste.c b/src/wl-paste.c index c4c8f57..ded5179 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -239,7 +239,7 @@ static void print_usage(FILE *f, const char *argv0) { ); } -static void parse_options(int argc, char * const argv[]) { +static void parse_options(int argc, argv_t argv) { if (argc < 1) { bail("Empty argv"); } @@ -300,7 +300,7 @@ static void parse_options(int argc, char * const argv[]) { } } -int main(int argc, char * const argv[]) { +int main(int argc, argv_t argv) { parse_options(argc, argv); char *path = path_for_fd(STDOUT_FILENO); From 6b72066be07a3c280a62df792bf6df1a33d1b0b4 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 14:21:26 +0300 Subject: [PATCH 040/139] Make wl-copy and wl-paste use popup surface directly This lets us finally completely get rid of the infrastructure that boilerplate.h and boilerplate.c used to have for managing the popup surface and its callbacks. --- src/boilerplate.c | 55 ----------------------------------------------- src/boilerplate.h | 8 ------- src/wl-copy.c | 24 +++++++++++++-------- src/wl-paste.c | 18 ++++++++++++++-- 4 files changed, 31 insertions(+), 74 deletions(-) diff --git a/src/boilerplate.c b/src/boilerplate.c index d2a6621..a5ed4ae 100644 --- a/src/boilerplate.c +++ b/src/boilerplate.c @@ -22,14 +22,6 @@ #include "types/popup-surface.h" #include "types/registry.h" -static struct popup_surface *popup_surface = NULL; - -static void forward_on_focus(struct popup_surface *popup_surface, uint32_t serial) { - if (action_on_popup_surface_getting_focus != NULL) { - action_on_popup_surface_getting_focus(serial); - } -} - void init_wayland_globals() { display = wl_display_connect(NULL); if (display == NULL) { @@ -52,50 +44,3 @@ void init_wayland_globals() { } } } - -void popup_tiny_invisible_surface() { - /* HACK: - * Pop up a tiny invisible surface to get the keyboard focus, - * otherwise we won't be notified of the selection. - */ - - popup_surface = calloc(1, sizeof(struct popup_surface)); - popup_surface->registry = registry; - popup_surface->seat = seat; - popup_surface->on_focus = forward_on_focus; - - popup_surface_init(popup_surface); -} - -void destroy_popup_surface() { - if (popup_surface != NULL) { - popup_surface_destroy(popup_surface); - popup_surface = NULL; - } -} - -static uint32_t global_serial; - -static void callback_done -( - void *data, - struct wl_callback *callback, - uint32_t serial -) { - global_serial = serial; -} - -static const struct wl_callback_listener callback_listener = { - .done = callback_done -}; - -uint32_t get_serial() { - struct wl_callback *callback = wl_display_sync(display); - wl_callback_add_listener(callback, &callback_listener, NULL); - - while (global_serial == 0) { - wl_display_dispatch(display); - } - - return global_serial; -} diff --git a/src/boilerplate.h b/src/boilerplate.h index 5ef4431..9f99d6b 100644 --- a/src/boilerplate.h +++ b/src/boilerplate.h @@ -50,11 +50,3 @@ struct seat *seat; const char *requested_seat_name; void init_wayland_globals(void); - -void popup_tiny_invisible_surface(void); -void destroy_popup_surface(void); - -void (*action_on_popup_surface_getting_focus)(uint32_t serial); -void (*action_on_no_keyboard)(void); - -uint32_t get_serial(void); diff --git a/src/wl-copy.c b/src/wl-copy.c index bd8a87e..97a1ccd 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -21,6 +21,7 @@ #include "types/device.h" #include "types/device-manager.h" #include "types/registry.h" +#include "types/popup-surface.h" static struct { int stay_in_foreground; @@ -36,6 +37,7 @@ static char *temp_file_to_copy = NULL; static struct device *device = NULL; static struct source *source = NULL; +static struct popup_surface *popup_surface = NULL; static void cancelled_callback(struct source *source) { /* We're done! */ @@ -95,14 +97,16 @@ static void send_callback( } } -static void set_selection(uint32_t serial) { +static void set_selection( + struct popup_surface *popup_surface, + uint32_t serial +) { device_set_selection(device, source, serial, options.primary); wl_display_roundtrip(display); - destroy_popup_surface(); -} - -static void complain_about_missing_keyboard() { - bail("Setting primary selection is not supported without a keyboard"); + if (popup_surface != NULL) { + popup_surface_destroy(popup_surface); + popup_surface = NULL; + } } static void do_offer(char *mime_type, struct source *source) { @@ -278,9 +282,11 @@ int main(int argc, argv_t argv) { /* If we cannot, schedule to do it later, * when our popup surface gains keyboard focus. */ - action_on_popup_surface_getting_focus = set_selection; - action_on_no_keyboard = complain_about_missing_keyboard; - popup_tiny_invisible_surface(); + popup_surface = calloc(1, sizeof(struct popup_surface)); + popup_surface->registry = registry; + popup_surface->seat = seat; + popup_surface->on_focus = set_selection; + popup_surface_init(popup_surface); } if (options.clear) { diff --git a/src/wl-paste.c b/src/wl-paste.c index ded5179..70c41d1 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -21,6 +21,7 @@ #include "types/device.h" #include "types/device-manager.h" #include "types/registry.h" +#include "types/popup-surface.h" static struct { char *explicit_type; @@ -40,6 +41,8 @@ struct types { const char *any; }; +static struct popup_surface *popup_surface = NULL; + static struct types classify_offer_types(struct offer *offer) { struct types types = { 0 }; offer_for_each_mime_type(offer, mime_type) { @@ -185,7 +188,10 @@ static void selection_callback(struct offer *offer, int primary) { offer_receive(offer, mime_type, pipefd[1]); - destroy_popup_surface(); + if (popup_surface != NULL) { + popup_surface_destroy(popup_surface); + popup_surface = NULL; + } wl_display_roundtrip(display); /* Spawn a cat to perform the copy */ @@ -326,7 +332,15 @@ int main(int argc, argv_t argv) { device->selection_callback = selection_callback; if (device->needs_popup_surface) { - popup_tiny_invisible_surface(); + /* If we cannot get the selection directly, pop up + * a surface. When it gets focus, we'll immediately + * get the selection events, se we don't need to do + * anything special on the surface getting focus. + */ + popup_surface = calloc(1, sizeof(struct popup_surface)); + popup_surface->registry = registry; + popup_surface->seat = seat; + popup_surface_init(popup_surface); } while (wl_display_dispatch(display) >= 0); From cc866e690cf1469821fdc08fa0ea81b4effbab47 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 14:51:56 +0300 Subject: [PATCH 041/139] Move Wayland initialization into wl-copy and wl-paste This was the last thing left in boilerplate.c, so now boilerplate.c and boilerplate.h are finally completely gone. --- src/boilerplate.c | 46 ----------------------------------------- src/boilerplate.h | 52 ----------------------------------------------- src/meson.build | 2 -- src/wl-copy.c | 44 +++++++++++++++++++++++++++++++++------ src/wl-paste.c | 43 ++++++++++++++++++++++++++++++++++----- 5 files changed, 76 insertions(+), 111 deletions(-) delete mode 100644 src/boilerplate.c delete mode 100644 src/boilerplate.h diff --git a/src/boilerplate.c b/src/boilerplate.c deleted file mode 100644 index a5ed4ae..0000000 --- a/src/boilerplate.c +++ /dev/null @@ -1,46 +0,0 @@ -/* wl-clipboard - * - * Copyright © 2019 Sergey Bugaev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "boilerplate.h" -#include "types/shell-surface.h" -#include "types/shell.h" -#include "types/popup-surface.h" -#include "types/registry.h" - -void init_wayland_globals() { - display = wl_display_connect(NULL); - if (display == NULL) { - bail("Failed to connect to a Wayland server"); - } - - registry = calloc(1, sizeof(struct registry)); - registry->wl_display = display; - registry_init(registry); - - /* Wait for the "initial" set of globals to appear */ - wl_display_roundtrip(display); - - seat = registry_find_seat(registry, requested_seat_name); - if (seat == NULL) { - if (requested_seat_name != NULL) { - bail("No such seat"); - } else { - bail("Missing a seat"); - } - } -} diff --git a/src/boilerplate.h b/src/boilerplate.h deleted file mode 100644 index 9f99d6b..0000000 --- a/src/boilerplate.h +++ /dev/null @@ -1,52 +0,0 @@ -/* wl-clipboard - * - * Copyright © 2019 Sergey Bugaev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "config.h" - -#include "util/string.h" -#include "util/files.h" -#include "util/misc.h" - -#include -#include -#include -#include -#include -#include -#include // isupper -#include // open -#include // open -#include // open -#include // exit -#include // basename -#include -#include // PATH_MAX - -#include "includes/shell-protocols.h" -#include "includes/selection-protocols.h" - -#include "types/registry.h" -#include "types/seat.h" - -struct wl_display *display; -struct registry *registry; - -struct seat *seat; -const char *requested_seat_name; - -void init_wayland_globals(void); diff --git a/src/meson.build b/src/meson.build index 895415f..ab85a56 100644 --- a/src/meson.build +++ b/src/meson.build @@ -88,8 +88,6 @@ endforeach lib = static_library( 'wl-clipboard', [ - 'boilerplate.c', - 'util/string.h', 'util/string.c', 'util/files.h', diff --git a/src/wl-copy.c b/src/wl-copy.c index 97a1ccd..7e5fa48 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -16,13 +16,24 @@ * along with this program. If not, see . */ -#include "boilerplate.h" #include "types/source.h" #include "types/device.h" #include "types/device-manager.h" #include "types/registry.h" #include "types/popup-surface.h" +#include "util/files.h" +#include "util/string.h" +#include "util/misc.h" + +#include +#include +#include +#include +#include +#include +#include + static struct { int stay_in_foreground; int clear; @@ -30,11 +41,13 @@ static struct { int trim_newline; int paste_once; int primary; + const char *seat_name; } options; static argv_t data_to_copy = NULL; static char *temp_file_to_copy = NULL; +static struct wl_display *wl_display = NULL; static struct device *device = NULL; static struct source *source = NULL; static struct popup_surface *popup_surface = NULL; @@ -102,7 +115,7 @@ static void set_selection( uint32_t serial ) { device_set_selection(device, source, serial, options.primary); - wl_display_roundtrip(display); + wl_display_roundtrip(wl_display); if (popup_surface != NULL) { popup_surface_destroy(popup_surface); popup_surface = NULL; @@ -204,7 +217,7 @@ static void parse_options(int argc, argv_t argv) { options.mime_type = strdup(optarg); break; case 's': - requested_seat_name = strdup(optarg); + options.seat_name = strdup(optarg); break; default: /* getopt has already printed an error message */ @@ -217,7 +230,26 @@ static void parse_options(int argc, argv_t argv) { int main(int argc, argv_t argv) { parse_options(argc, argv); - init_wayland_globals(); + wl_display = wl_display_connect(NULL); + if (wl_display == NULL) { + bail("Failed to connect to a Wayland server"); + } + + struct registry *registry = calloc(1, sizeof(struct registry)); + registry->wl_display = wl_display; + registry_init(registry); + + /* Wait for the initial set of globals to appear */ + wl_display_roundtrip(wl_display); + + struct seat *seat = registry_find_seat(registry, options.seat_name); + if (seat == NULL) { + if (options.seat_name != NULL) { + bail("No such seat"); + } else { + bail("Missing a seat"); + } + } if (!options.clear) { if (optind < argc) { @@ -290,11 +322,11 @@ int main(int argc, argv_t argv) { } if (options.clear) { - wl_display_roundtrip(display); + wl_display_roundtrip(wl_display); exit(0); } - while (wl_display_dispatch(display) >= 0); + while (wl_display_dispatch(wl_display) >= 0); perror("wl_display_dispatch"); return 1; diff --git a/src/wl-paste.c b/src/wl-paste.c index 70c41d1..5d1241b 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -16,19 +16,32 @@ * along with this program. If not, see . */ -#include "boilerplate.h" #include "types/offer.h" #include "types/device.h" #include "types/device-manager.h" #include "types/registry.h" #include "types/popup-surface.h" +#include "util/files.h" +#include "util/string.h" +#include "util/misc.h" + +#include +#include +#include +#include +#include +#include +#include +#include + static struct { char *explicit_type; char *inferred_type; int no_newline; int list_types; int primary; + const char *seat_name; } options; struct types { @@ -41,6 +54,7 @@ struct types { const char *any; }; +static struct wl_display *wl_display = NULL; static struct popup_surface *popup_surface = NULL; static struct types classify_offer_types(struct offer *offer) { @@ -192,7 +206,7 @@ static void selection_callback(struct offer *offer, int primary) { popup_surface_destroy(popup_surface); popup_surface = NULL; } - wl_display_roundtrip(display); + wl_display_roundtrip(wl_display); /* Spawn a cat to perform the copy */ pid_t pid = fork(); @@ -290,7 +304,7 @@ static void parse_options(int argc, argv_t argv) { options.explicit_type = strdup(optarg); break; case 's': - requested_seat_name = strdup(optarg); + options.seat_name = strdup(optarg); break; default: /* getopt has already printed an error message */ @@ -315,7 +329,26 @@ int main(int argc, argv_t argv) { } free(path); - init_wayland_globals(); + wl_display = wl_display_connect(NULL); + if (wl_display == NULL) { + bail("Failed to connect to a Wayland server"); + } + + struct registry *registry = calloc(1, sizeof(struct registry)); + registry->wl_display = wl_display; + registry_init(registry); + + /* Wait for the initial set of globals to appear */ + wl_display_roundtrip(wl_display); + + struct seat *seat = registry_find_seat(registry, options.seat_name); + if (seat == NULL) { + if (options.seat_name != NULL) { + bail("No such seat"); + } else { + bail("Missing a seat"); + } + } /* Create the device */ struct device_manager *device_manager @@ -343,7 +376,7 @@ int main(int argc, argv_t argv) { popup_surface_init(popup_surface); } - while (wl_display_dispatch(display) >= 0); + while (wl_display_dispatch(wl_display) >= 0); perror("wl_display_dispatch"); return 1; From 612401d36889f9a0c51f80f83d0213f9c783ebb2 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 17:10:54 +0300 Subject: [PATCH 042/139] Reimplement clearing selection We now properly set a nil/NULL source instead of setting a dummy source and exiting. It's actually more straigtforawrd this way, and the Mutter bug that caused it to crash on nil sources [1] has been fixed. Fixes https://github.com/bugaevc/wl-clipboard/issues/47 [1]: https://gitlab.gnome.org/GNOME/mutter/issues/335 --- src/wl-copy.c | 98 ++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 52 deletions(-) diff --git a/src/wl-copy.c b/src/wl-copy.c index 7e5fa48..ff469ae 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -120,21 +120,9 @@ static void set_selection( popup_surface_destroy(popup_surface); popup_surface = NULL; } -} - -static void do_offer(char *mime_type, struct source *source) { - if (mime_type == NULL || mime_type_is_text(mime_type)) { - /* Offer a few generic plain text formats */ - source_offer(source, text_plain); - source_offer(source, text_plain_utf8); - source_offer(source, "TEXT"); - source_offer(source, "STRING"); - source_offer(source, "UTF8_STRING"); + if (options.clear) { + exit(0); } - if (mime_type != NULL) { - source_offer(source, mime_type); - } - free(mime_type); } static void print_usage(FILE *f, const char *argv0) { @@ -251,6 +239,19 @@ int main(int argc, argv_t argv) { } } + /* Create the device */ + struct device_manager *device_manager + = registry_find_device_manager(registry, options.primary); + if (device_manager == NULL) { + complain_about_selection_support(options.primary); + } + + device = device_manager_get_device(device_manager, seat); + + if (!device_supports_selection(device, options.primary)) { + complain_about_selection_support(options.primary); + } + if (!options.clear) { if (optind < argc) { /* Copy our command-line arguments */ @@ -266,44 +267,42 @@ int main(int argc, argv_t argv) { = infer_mime_type_from_contents(temp_file_to_copy); } } - } - if (!options.stay_in_foreground && !options.clear) { - /* Move to background. - * We fork our process and leave the - * child running in the background, - * while exiting in the parent. - */ - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - /* Proceed without forking */ + if (!options.stay_in_foreground) { + /* Move to background. + * We fork our process and leave the + * child running in the background, + * while exiting in the parent. + */ + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + /* Proceed without forking */ + } + if (pid > 0) { + exit(0); + } } - if (pid > 0) { - exit(0); + + /* Create the source */ + source = device_manager_create_source(device_manager); + source->send_callback = send_callback; + source->cancelled_callback = cancelled_callback; + if (options.mime_type != NULL) { + source_offer(source, options.mime_type); } + if (options.mime_type == NULL || mime_type_is_text(options.mime_type)) { + /* Offer a few generic plain text formats */ + source_offer(source, text_plain); + source_offer(source, text_plain_utf8); + source_offer(source, "TEXT"); + source_offer(source, "STRING"); + source_offer(source, "UTF8_STRING"); + } + free(options.mime_type); + options.mime_type = NULL; } - /* Create the device */ - struct device_manager *device_manager - = registry_find_device_manager(registry, options.primary); - if (device_manager == NULL) { - complain_about_selection_support(options.primary); - } - - device = device_manager_get_device(device_manager, seat); - - if (!device_supports_selection(device, options.primary)) { - complain_about_selection_support(options.primary); - } - - /* Create and initialize the source */ - source = device_manager_create_source(device_manager); - source->send_callback = send_callback; - source->cancelled_callback = cancelled_callback; - - do_offer(options.mime_type, source); - /* See if we can just set the selection directly */ if (!device->needs_popup_surface) { /* If we can, it doesn't actually require @@ -321,11 +320,6 @@ int main(int argc, argv_t argv) { popup_surface_init(popup_surface); } - if (options.clear) { - wl_display_roundtrip(wl_display); - exit(0); - } - while (wl_display_dispatch(wl_display) >= 0); perror("wl_display_dispatch"); From dbf6647faed9d518d0a0bec2cd8ac410fdf6202e Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 12 Sep 2019 14:49:52 +0300 Subject: [PATCH 043/139] Update the bundled version of wlr-data-control-unstable-v1 --- src/protocol/wlr-data-control-unstable-v1.xml | 80 ++++++++++++++++--- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/src/protocol/wlr-data-control-unstable-v1.xml b/src/protocol/wlr-data-control-unstable-v1.xml index a588755..75e8671 100644 --- a/src/protocol/wlr-data-control-unstable-v1.xml +++ b/src/protocol/wlr-data-control-unstable-v1.xml @@ -2,6 +2,7 @@ Copyright © 2018 Simon Ser + Copyright © 2019 Ivan Molodetskikh Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted @@ -40,7 +41,7 @@ interface version number is reset. - + This interface is a manager that allows creating per-seat data device controls. @@ -70,7 +71,7 @@ - + This interface allows a client to manage a seat's selection. @@ -79,8 +80,14 @@ - All objects created by the device will still remain valid, until their - appropriate destroy request has been called. + This request asks the compositor to set the selection to the data from + the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the selection, set the source to NULL. @@ -95,20 +102,19 @@ The data_offer event introduces a new wlr_data_control_offer object, - which will subsequently be used in the wlr_data_control_device.selection - event. Immediately following the wlr_data_control_device.data_offer - event, the new data_offer object will send out - wlr_data_control_offer.offer events to describe the MIME types it - offers. - - This event replaces the previous data offer, which should be destroyed - by the client. + which will subsequently be used in either the + wlr_data_control_device.selection event (for the regular clipboard + selections) or the wlr_data_control_device.primary_selection event (for + the primary clipboard selections). Immediately following the + wlr_data_control_device.data_offer event, the new data_offer object + will send out wlr_data_control_offer.offer events to describe the MIME + types it offers. - + The selection event is sent out to notify the client of a new wlr_data_control_offer for the selection for this device. The wlr_data_control_device.data_offer and the wlr_data_control_offer.offer @@ -118,6 +124,9 @@ wlr_data_control_offer or NULL is received. The client must destroy the previous selection wlr_data_control_offer, if any, upon receiving this event. + + The first selection event is sent upon binding the + wlr_data_control_device object. @@ -129,6 +138,51 @@ the client. + + + + + + The primary_selection event is sent out to notify the client of a new + wlr_data_control_offer for the primary selection for this device. The + wlr_data_control_device.data_offer and the wlr_data_control_offer.offer + events are sent out immediately before this event to introduce the data + offer object. The primary_selection event is sent to a client when a + new primary selection is set. The wlr_data_control_offer is valid until + a new wlr_data_control_offer or NULL is received. The client must + destroy the previous primary selection wlr_data_control_offer, if any, + upon receiving this event. + + If the compositor supports primary selection, the first + primary_selection event is sent upon binding the + wlr_data_control_device object. + + + + + + + This request asks the compositor to set the primary selection to the + data from the source on behalf of the client. + + The given source may not be used in any further set_selection or + set_primary_selection requests. Attempting to use a previously used + source is a protocol error. + + To unset the primary selection, set the source to NULL. + + The compositor will ignore this request if it does not support primary + selection. + + + + + + + From c49bc0ece6e80147a150b05bdf02cfbb3c15164c Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 17:25:43 +0300 Subject: [PATCH 044/139] Support acessing primary selection using wlr-data-control See https://github.com/swaywm/wlr-protocols/pull/34 --- src/types/device-manager.c | 1 + src/types/device-manager.h | 3 +- src/types/device.c | 58 ++++++++++++++++++++++++++++++++++++-- src/types/device.h | 1 + src/types/registry.c | 28 ++++++++++++++++-- src/wl-paste.c | 6 +++- 6 files changed, 91 insertions(+), 6 deletions(-) diff --git a/src/types/device-manager.c b/src/types/device-manager.c index 54e37ce..bd6e04e 100644 --- a/src/types/device-manager.c +++ b/src/types/device-manager.c @@ -58,6 +58,7 @@ static struct device *device_manager_ ## type ## _do_get_device( \ struct wl_seat *seat = (struct wl_seat *) seat_wrapper->proxy; \ struct device *device = calloc(1, sizeof(struct device)); \ device->proxy = (struct wl_proxy *) type ## _ ## method_name(proxy, seat); \ + device->wl_display = self->wl_display; \ device_init_ ## device_type(device); \ return device; \ } diff --git a/src/types/device-manager.h b/src/types/device-manager.h index 433546f..bb7e88d 100644 --- a/src/types/device-manager.h +++ b/src/types/device-manager.h @@ -28,8 +28,9 @@ struct device; struct source; struct device_manager { - /* This field is initialized by the creator */ + /* These fields are initialized by the creator */ struct wl_proxy *proxy; + struct wl_display *wl_display; /* These fields are initialized by the implementation */ struct source *(*do_create_source)(struct device_manager *self); diff --git a/src/types/device.c b/src/types/device.c index 534827f..16525a6 100644 --- a/src/types/device.c +++ b/src/types/device.c @@ -22,6 +22,7 @@ #include "types/source.h" #include "includes/selection-protocols.h" +#include #include int device_supports_selection(struct device *self, int primary) { @@ -191,10 +192,46 @@ INIT(zwp_primary_selection_device_v1, 1) #ifdef HAVE_WLR_DATA_CONTROL -SUPPORTS_SELECTION(zwlr_data_control_device_v1, !primary) +enum TriState { + Unknown, + Yes, + No +}; + +/* Whether wlr-data-control supports primary selection */ +static enum TriState device_wlr_supports_primary_selection = Unknown; +static int device_get_wlr_supports_selection(struct device *self, int primary) { + if (!primary) { + return 1; + } + + if (device_wlr_supports_primary_selection == Yes) { + return 1; + } else if (device_wlr_supports_primary_selection == No) { + return 0; + } + + wl_display_roundtrip(self->wl_display); + + if (device_wlr_supports_primary_selection == Yes) { + return 1; + } else { + device_wlr_supports_primary_selection = No; + return 0; + } +} + +SUPPORTS_SELECTION( + zwlr_data_control_device_v1, + device_get_wlr_supports_selection(self, primary) +) SET_SELECTION_IMPL(zwlr_data_control_device_v1, zwlr_data_control_source_v1, { - zwlr_data_control_device_v1_set_selection(device, source); + if (!primary) { + zwlr_data_control_device_v1_set_selection(device, source); + } else { + zwlr_data_control_device_v1_set_primary_selection(device, source); + } }) DATA_OFFER_HANDLER(zwlr_data_control_device_v1, zwlr_data_control_offer_v1) @@ -206,10 +243,27 @@ SELECTION_HANDLER( 0 ) +static void zwlr_data_control_device_v1_primary_selection_handler( + void *data, + struct zwlr_data_control_device_v1 *device, + struct zwlr_data_control_offer_v1 *offer_proxy +) { + device_wlr_supports_primary_selection = Yes; + struct device *self = data; + struct offer *offer = NULL; + if (offer_proxy != NULL) { + offer = wl_proxy_get_user_data((struct wl_proxy *) offer_proxy); + } + if (self->selection_callback != NULL) { + self->selection_callback(offer, 1); + } +} + static const struct zwlr_data_control_device_v1_listener zwlr_data_control_device_v1_listener = { .data_offer = zwlr_data_control_device_v1_data_offer_handler, .selection = zwlr_data_control_device_v1_selection_handler, + .primary_selection = zwlr_data_control_device_v1_primary_selection_handler }; INIT(zwlr_data_control_device_v1, 0) diff --git a/src/types/device.h b/src/types/device.h index 0867bb2..5f5e3fe 100644 --- a/src/types/device.h +++ b/src/types/device.h @@ -33,6 +33,7 @@ struct device { void *data; struct wl_proxy *proxy; + struct wl_display *wl_display; /* These fields are initialized by the implementation */ int (*supports_selection)(struct device *self, int primary); diff --git a/src/types/registry.c b/src/types/registry.c index 9826541..67c7fde 100644 --- a/src/types/registry.c +++ b/src/types/registry.c @@ -70,7 +70,7 @@ static void wl_registry_global_handler( #endif #ifdef HAVE_WLR_DATA_CONTROL - BIND(zwlr_data_control_manager_v1, 1) + BIND(zwlr_data_control_manager_v1, version > 2 ? 2 : version) #endif if (strcmp(interface, "wl_seat") == 0) { @@ -137,8 +137,10 @@ struct device_manager *registry_find_device_manager( ) { struct device_manager *device_manager = calloc(1, sizeof(struct device_manager)); + device_manager->wl_display = self->wl_display; - /* We prefer wlr-data-control, as it doesn't require + /* For regular selection, we just look at the two supported + * protocols. We prefer wlr-data-control, as it doesn't require * us to use the popup surface hack. */ @@ -152,6 +154,28 @@ struct device_manager *registry_find_device_manager( return NULL; } + /* For primary selection, it's a bit more complicated. We also + * prefer wlr-data-control, but we don't know in advance whether + * the compositor supports primary selection, as unlike with + * other protocols here, the mere presence of wlr-data-control + * does not imply primary selection support. However, we assume + * that if a compositor supports primary selection at all, then + * if it supports wlr-data-control v2 it also supports primary + * selection over wlr-data-control; which is only reasonable. + */ + +#ifdef HAVE_WLR_DATA_CONTROL + if (self->zwlr_data_control_manager_v1 != NULL) { + struct wl_proxy *proxy + = (struct wl_proxy *) self->zwlr_data_control_manager_v1; + if (wl_proxy_get_version(proxy) >= 2) { + device_manager->proxy = proxy; + device_manager_init_zwlr_data_control_manager_v1(device_manager); + return device_manager; + } + } +#endif + #ifdef HAVE_WP_PRIMARY_SELECTION TRY(zwp_primary_selection_device_manager_v1) #endif diff --git a/src/wl-paste.c b/src/wl-paste.c index 5d1241b..b255610 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -358,11 +358,15 @@ int main(int argc, argv_t argv) { } struct device *device = device_manager_get_device(device_manager, seat); + /* Set up the callback before checking whether the device + * actually supports the kind of selection we need, because + * checking for the support might roundtrip. + */ + device->selection_callback = selection_callback; if (!device_supports_selection(device, options.primary)) { complain_about_selection_support(options.primary); } - device->selection_callback = selection_callback; if (device->needs_popup_surface) { /* If we cannot get the selection directly, pop up From d2542ddfd7303f8cffcf59afeebfef78eb53e846 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 17:45:38 +0300 Subject: [PATCH 045/139] Move copy action logic into types/copy-action --- src/meson.build | 2 + src/types/copy-action.c | 158 ++++++++++++++++++++++++++++++++++++++++ src/types/copy-action.h | 54 ++++++++++++++ src/wl-copy.c | 148 ++++++++++++------------------------- 4 files changed, 261 insertions(+), 101 deletions(-) create mode 100644 src/types/copy-action.c create mode 100644 src/types/copy-action.h diff --git a/src/meson.build b/src/meson.build index ab85a56..9837e9a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -118,6 +118,8 @@ lib = static_library( 'types/popup-surface.c', 'types/registry.h', 'types/registry.c', + 'types/copy-action.h', + 'types/copy-action.c', ], dependencies: wayland, link_with: protocol_deps diff --git a/src/types/copy-action.c b/src/types/copy-action.c new file mode 100644 index 0000000..5ef9ea1 --- /dev/null +++ b/src/types/copy-action.c @@ -0,0 +1,158 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "types/copy-action.h" +#include "types/device.h" +#include "types/source.h" +#include "types/popup-surface.h" + +#include "util/misc.h" + +#include +#include +#include +#include +#include +#include +#include + +static void do_set_selection(struct copy_action *self, uint32_t serial) { + /* Set the selection and make sure it reaches + * the display before we do anything else, + * such as destroying the surface or exiting. + */ + device_set_selection(self->device, self->source, serial, self->primary); + wl_display_roundtrip(self->device->wl_display); + + /* Now, if we have used a popup surface, destroy it */ + if (self->device->needs_popup_surface) { + popup_surface_destroy(self->popup_surface); + self->popup_surface = NULL; + } + + /* And invoke the callback */ + if (self->did_set_selection_callback != NULL) { + self->did_set_selection_callback(self); + } +} + +static void on_focus( + struct popup_surface *popup_surface, + uint32_t serial +) { + struct copy_action *self = (struct copy_action *) popup_surface->data; + do_set_selection(self, serial); +} + +static void do_send(struct source *source, const char *mime_type, int fd) { + struct copy_action *self = source->data; + + /* Unset O_NONBLOCK */ + fcntl(fd, F_SETFL, 0); + + if (self->file_to_copy != NULL) { + /* Copy the file to the given file descriptor + * by spawning an appropriate cat process. + */ + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + close(fd); + return; + } + if (pid == 0) { + dup2(fd, STDOUT_FILENO); + close(fd); + execlp("cat", "cat", self->file_to_copy, NULL); + perror("exec cat"); + exit(1); + } + close(fd); + /* Wait for the cat process to exit. This effectively + * means waiting for the other side to read the whole + * file. In theory, a malicious client could perform a + * denial-of-serivice attack against us. Perhaps we + * should switch to an asynchronous child waiting scheme + * instead. + */ + wait(NULL); + } else { + /* We'll perform the copy ourselves */ + FILE *f = fdopen(fd, "w"); + if (f == NULL) { + perror("fdopen"); + close(fd); + return; + } + + if (self->data_to_copy.ptr != NULL) { + /* Just copy the given chunk of data */ + fwrite(self->data_to_copy.ptr, 1, self->data_to_copy.len, f); + } else if (self->argv_to_copy != NULL) { + /* Copy an argv-style string array, + * inserting spaces between items. + */ + int is_first = 1; + for (argv_t word = self->argv_to_copy; *word != NULL; word++) { + if (!is_first) { + fwrite(" ", 1, 1, f); + } + is_first = 0; + fwrite(*word, 1, strlen(*word), f); + } + } else { + bail("Unreachable: nothing to copy"); + } + + fclose(f); + } + + + if (self->pasted_callback != NULL) { + self->pasted_callback(self); + } +} + +static void forward_cancel(struct source *source) { + struct copy_action *self = source->data; + if (self->cancelled_callback != NULL) { + self->cancelled_callback(self); + } +} + +void copy_action_init(struct copy_action *self) { + if (self->source != NULL) { + self->source->send_callback = do_send; + self->source->cancelled_callback = forward_cancel; + self->source->data = self; + } + /* See if we can just set the selection directly */ + if (!self->device->needs_popup_surface) { + /* If we can, it doesn't actually require + * a serial, so passing zero will do. + */ + do_set_selection(self, 0); + } else { + /* If we cannot, schedule to do it later, + * when our popup surface gains keyboard focus. + */ + self->popup_surface->on_focus = on_focus; + self->popup_surface->data = self; + popup_surface_init(self->popup_surface); + } +} diff --git a/src/types/copy-action.h b/src/types/copy-action.h new file mode 100644 index 0000000..c0a1290 --- /dev/null +++ b/src/types/copy-action.h @@ -0,0 +1,54 @@ +/* wl-clipboard + * + * Copyright © 2019 Sergey Bugaev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef TYPES_COPY_ACTION_H +#define TYPES_COPY_ACTION_H + +#include "util/string.h" + +#include + +struct device; +struct source; +struct popup_surface; + +struct copy_action { + /* These fields are initialized by the creator */ + struct device *device; + struct source *source; + struct popup_surface *popup_surface; + int primary; + + void (*did_set_selection_callback)(struct copy_action *self); + void (*pasted_callback)(struct copy_action *self); + void (*cancelled_callback)(struct copy_action *self); + + /* Exactly one of these fields must be non-null if the source + * is non-null, otherwise all these fields must be null. + */ + const char *file_to_copy; + argv_t argv_to_copy; + struct { + const char *ptr; + size_t len; + } data_to_copy; +}; + +void copy_action_init(struct copy_action *self); + +#endif /* TYPES_COPY_ACTION_H */ diff --git a/src/wl-copy.c b/src/wl-copy.c index ff469ae..f21fef6 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +#include "types/copy-action.h" #include "types/source.h" #include "types/device.h" #include "types/device-manager.h" @@ -28,11 +29,9 @@ #include #include -#include #include #include #include -#include static struct { int stay_in_foreground; @@ -44,18 +43,20 @@ static struct { const char *seat_name; } options; -static argv_t data_to_copy = NULL; -static char *temp_file_to_copy = NULL; +static void did_set_selection_callback(struct copy_action *copy_action) { + if (options.clear) { + exit(0); + } +} -static struct wl_display *wl_display = NULL; -static struct device *device = NULL; -static struct source *source = NULL; -static struct popup_surface *popup_surface = NULL; - -static void cancelled_callback(struct source *source) { - /* We're done! */ - if (temp_file_to_copy != NULL) { - execlp("rm", "rm", "-r", dirname(temp_file_to_copy), NULL); +static void cleanup_and_exit(struct copy_action *copy_action) { + /* We're done copying! + * All that's left to do now is to + * clean up after ourselves and exit.*/ + char *temp_file = (char *) copy_action->file_to_copy; + if (temp_file != NULL) { + /* Clean up our temporary file */ + execlp("rm", "rm", "-r", dirname(temp_file), NULL); perror("exec rm"); exit(1); } else { @@ -63,65 +64,13 @@ static void cancelled_callback(struct source *source) { } } -static void send_callback( - struct source *source, - const char *mime_type, - int fd -) { - /* Unset O_NONBLOCK */ - fcntl(fd, F_SETFL, 0); - if (data_to_copy != NULL) { - /* Copy the specified data, separated by spaces */ - FILE *f = fdopen(fd, "w"); - if (f == NULL) { - perror("fdopen"); - exit(1); - } - char * const *dataptr = data_to_copy; - for (int is_first = 1; *dataptr != NULL; dataptr++, is_first = 0) { - if (!is_first) { - fwrite(" ", 1, 1, f); - } - fwrite(*dataptr, 1, strlen(*dataptr), f); - } - fclose(f); - } else { - /* Copy from the temp file; for that, we delegate to a - * (hopefully) highly optimized implementation of copying. - */ - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - close(fd); - return; - } - if (pid == 0) { - dup2(fd, STDOUT_FILENO); - execlp("cat", "cat", temp_file_to_copy, NULL); - perror("exec cat"); - exit(1); - } - close(fd); - wait(NULL); - } - - if (options.paste_once) { - cancelled_callback(source); - } +static void cancelled_callback(struct copy_action *copy_action) { + cleanup_and_exit(copy_action); } -static void set_selection( - struct popup_surface *popup_surface, - uint32_t serial -) { - device_set_selection(device, source, serial, options.primary); - wl_display_roundtrip(wl_display); - if (popup_surface != NULL) { - popup_surface_destroy(popup_surface); - popup_surface = NULL; - } - if (options.clear) { - exit(0); +static void pasted_callback(struct copy_action *copy_action) { + if (options.paste_once) { + cleanup_and_exit(copy_action); } } @@ -218,7 +167,7 @@ static void parse_options(int argc, argv_t argv) { int main(int argc, argv_t argv) { parse_options(argc, argv); - wl_display = wl_display_connect(NULL); + struct wl_display *wl_display = wl_display_connect(NULL); if (wl_display == NULL) { bail("Failed to connect to a Wayland server"); } @@ -246,26 +195,31 @@ int main(int argc, argv_t argv) { complain_about_selection_support(options.primary); } - device = device_manager_get_device(device_manager, seat); + struct device *device = device_manager_get_device(device_manager, seat); if (!device_supports_selection(device, options.primary)) { complain_about_selection_support(options.primary); } + /* Create and initialize the copy action */ + struct copy_action *copy_action = calloc(1, sizeof(struct copy_action)); + copy_action->device = device; + copy_action->primary = options.primary; + if (!options.clear) { if (optind < argc) { /* Copy our command-line arguments */ - data_to_copy = &argv[optind]; + copy_action->argv_to_copy = &argv[optind]; } else { /* Copy data from our stdin */ - temp_file_to_copy = dump_stdin_into_a_temp_file(); + char *temp_file = dump_stdin_into_a_temp_file(); if (options.trim_newline) { - trim_trailing_newline(temp_file_to_copy); + trim_trailing_newline(temp_file); } if (options.mime_type == NULL) { - options.mime_type - = infer_mime_type_from_contents(temp_file_to_copy); + options.mime_type = infer_mime_type_from_contents(temp_file); } + copy_action->file_to_copy = temp_file; } if (!options.stay_in_foreground) { @@ -285,41 +239,33 @@ int main(int argc, argv_t argv) { } /* Create the source */ - source = device_manager_create_source(device_manager); - source->send_callback = send_callback; - source->cancelled_callback = cancelled_callback; + copy_action->source = device_manager_create_source(device_manager); if (options.mime_type != NULL) { - source_offer(source, options.mime_type); + source_offer(copy_action->source, options.mime_type); } if (options.mime_type == NULL || mime_type_is_text(options.mime_type)) { /* Offer a few generic plain text formats */ - source_offer(source, text_plain); - source_offer(source, text_plain_utf8); - source_offer(source, "TEXT"); - source_offer(source, "STRING"); - source_offer(source, "UTF8_STRING"); + source_offer(copy_action->source, text_plain); + source_offer(copy_action->source, text_plain_utf8); + source_offer(copy_action->source, "TEXT"); + source_offer(copy_action->source, "STRING"); + source_offer(copy_action->source, "UTF8_STRING"); } free(options.mime_type); options.mime_type = NULL; } - /* See if we can just set the selection directly */ - if (!device->needs_popup_surface) { - /* If we can, it doesn't actually require - * a serial, so passing zero will do. - */ - device_set_selection(device, source, 0, options.primary); - } else { - /* If we cannot, schedule to do it later, - * when our popup surface gains keyboard focus. - */ - popup_surface = calloc(1, sizeof(struct popup_surface)); - popup_surface->registry = registry; - popup_surface->seat = seat; - popup_surface->on_focus = set_selection; - popup_surface_init(popup_surface); + if (device->needs_popup_surface) { + copy_action->popup_surface = calloc(1, sizeof(struct popup_surface)); + copy_action->popup_surface->registry = registry; + copy_action->popup_surface->seat = seat; } + copy_action->did_set_selection_callback = did_set_selection_callback; + copy_action->pasted_callback = pasted_callback; + copy_action->cancelled_callback = cancelled_callback; + copy_action_init(copy_action); + while (wl_display_dispatch(wl_display) >= 0); perror("wl_display_dispatch"); From d3d9510c8bb1468bc468928c1a3141dd378609a2 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 17:47:36 +0300 Subject: [PATCH 046/139] Move forking to background to happen after setting selection This way, we'll actually return our error code to the parent process if anything goes wrong during the initial stages, instead of successfully exiting in the parent. --- src/wl-copy.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/wl-copy.c b/src/wl-copy.c index f21fef6..612e895 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -47,6 +47,22 @@ static void did_set_selection_callback(struct copy_action *copy_action) { if (options.clear) { exit(0); } + + if (!options.stay_in_foreground) { + /* Move to background. + * We fork our process and leave the + * child running in the background, + * while exiting in the parent. + */ + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + /* Proceed without forking */ + } + if (pid > 0) { + exit(0); + } + } } static void cleanup_and_exit(struct copy_action *copy_action) { @@ -222,22 +238,6 @@ int main(int argc, argv_t argv) { copy_action->file_to_copy = temp_file; } - if (!options.stay_in_foreground) { - /* Move to background. - * We fork our process and leave the - * child running in the background, - * while exiting in the parent. - */ - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - /* Proceed without forking */ - } - if (pid > 0) { - exit(0); - } - } - /* Create the source */ copy_action->source = device_manager_create_source(device_manager); if (options.mime_type != NULL) { From 211e9c51996682c025de5982eab8118f92fc7dc5 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 15 Sep 2019 19:21:54 +0300 Subject: [PATCH 047/139] Implement wl-paste --watch Closes https://github.com/bugaevc/wl-clipboard/issues/39 --- README.md | 1 + completions/bash/wl-paste | 13 ++++++-- data/wl-clipboard.1 | 9 ++++- src/wl-paste.c | 70 ++++++++++++++++++++++++++++++++++----- 4 files changed, 81 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 925cd02..66231f9 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ For `wl-paste`: * `-n`, `--no-newline` Do not append a newline character after the pasted clipboard content. This option is automatically enabled for non-text content types. * `-l`, `--list-types` Instead of pasting the selection, output the list of MIME types it is offered in. +* `-w command...`, `--watch command...` Instead of pasting once and exiting, continuously watch the clipboard for changes, and run the specified command each time a new selection appears. The spawned process can read the clipboard contents from its standard input. This mode requires a compositor that supports the [wlroots data-control protocol](https://github.com/swaywm/wlr-protocols/blob/master/unstable/wlr-data-control-unstable-v1.xml). For both: diff --git a/completions/bash/wl-paste b/completions/bash/wl-paste index 85eb7bf..91003fe 100644 --- a/completions/bash/wl-paste +++ b/completions/bash/wl-paste @@ -49,10 +49,19 @@ _wl_clipboard_complete_paste_types() { _wl_paste_completion() { compopt +o default - local cur prev opts types seats + local cur prev opts types seats offset + + for (( offset=1; offset < COMP_CWORD; offset++ )); do + cur="${COMP_WORDS[offset]}" + if [ \( "x${cur:0:1}" = "x-" -a "x${cur:1:2}" != "x-" -a "${cur: -1}" = "w" \) -o "$cur" = "--watch" ]; then + _command_offset $(($offset+1)) + return + fi + done + cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="-n --no-newline -l --list-types -p --primary -t --type -s --seat -v --version -h --help" + opts="-n --no-newline -l --list-types -w --watch -p --primary -t --type -s --seat -v --version -h --help" if [ "$prev" = ">" ]; then compopt -o default COMPREPLY=() diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index c3fc327..c2d2762 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -1,4 +1,4 @@ -.TH WL-CLIPBOARD 1 2019-04-15 wl-clipboard +.TH WL-CLIPBOARD 1 2019-09-16 wl-clipboard .SH NAME wl-clipboard \- Wayland copy and paste command line utilities .SH SYNOPSIS @@ -16,6 +16,7 @@ wl-clipboard \- Wayland copy and paste command line utilities [\fB--primary\fR] [\fB--no-newline\fR] [\fB--list-types\fR] +[\fB--watch \fIcommand\fR...\] [\fB--type \fImime/type\fR] [\fB--seat \fIseat-name\fR] .SH DESCRIPTION @@ -85,6 +86,12 @@ system, there is little reason to use this option. Instead of pasting the selection, output the list of MIME types it is offered in. .TP +\fB-w\fI command\fR..., \fB--watch \fIcommand\fR... +Instead of pasting once and exiting, continuously watch the clipboard for +changes, and run the specified \fIcommand\fR each time a new selection appears. +The spawned process can read the clipboard contents from its standard input. +This mode requires a compositor that supports the wlroots data-control protocol. +.TP \fB-v\fR, \fB--version Display the version of wl-clipboard and some short info about its license. .TP diff --git a/src/wl-paste.c b/src/wl-paste.c index b255610..36a1142 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -41,6 +41,8 @@ static struct { int no_newline; int list_types; int primary; + int watch; + argv_t watch_command; const char *seat_name; } options; @@ -158,7 +160,7 @@ static const char *mime_type_to_request(struct types types) { try_inferred; } } - bail("No suitable type of content copied"); + return NULL; } #undef try_explicit @@ -179,6 +181,9 @@ static void selection_callback(struct offer *offer, int primary) { } if (offer == NULL) { + if (options.watch) { + return; + } bail("No selection"); } @@ -192,6 +197,14 @@ static void selection_callback(struct offer *offer, int primary) { struct types types = classify_offer_types(offer); const char *mime_type = mime_type_to_request(types); + if (mime_type == NULL) { + if (options.watch) { + offer_destroy(offer); + return; + } + bail("No suitable type of content copied"); + } + /* Never append a newline character to binary content */ if (!mime_type_is_text(mime_type)) { options.no_newline = 1; @@ -208,32 +221,54 @@ static void selection_callback(struct offer *offer, int primary) { } wl_display_roundtrip(wl_display); - /* Spawn a cat to perform the copy */ + /* Spawn a cat to perform the copy. + * If watch mode is active, we spawn + * a custom command instead. + */ pid_t pid = fork(); if (pid < 0) { perror("fork"); + if (options.watch) { + /* Try to cope without exiting completely */ + close(pipefd[0]); + close(pipefd[1]); + offer_destroy(offer); + return; + } exit(1); } if (pid == 0) { dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); close(pipefd[1]); - execlp("cat", "cat", NULL); - perror("exec cat"); + if (options.watch) { + execvp(options.watch_command[0], options.watch_command); + fprintf( + stderr, + "Failed to spawn %s: %s", + options.watch_command[0], + strerror(errno) + ); + } else { + execlp("cat", "cat", NULL); + perror("exec cat"); + } exit(1); } close(pipefd[0]); close(pipefd[1]); wait(NULL); - if (!options.no_newline) { + if (!options.no_newline && !options.watch) { write(STDOUT_FILENO, "\n", 1); } offer_destroy(offer); - free(options.explicit_type); - free(options.inferred_type); - exit(0); + if (!options.watch) { + free(options.explicit_type); + free(options.inferred_type); + exit(0); + } } static void print_usage(FILE *f, const char *argv0) { @@ -246,6 +281,8 @@ static void print_usage(FILE *f, const char *argv0) { "\t-n, --no-newline\tDo not append a newline character.\n" "\t-l, --list-types\tInstead of pasting, list the offered types.\n" "\t-p, --primary\t\tUse the \"primary\" clipboard.\n" + "\t-w, --watch command\t" + "Run a command each time the selection changes.\n" "\t-t, --type mime/type\t" "Override the inferred MIME type for the content.\n" "\t-s, --seat seat-name\t" @@ -270,13 +307,14 @@ static void parse_options(int argc, argv_t argv) { {"primary", no_argument, 0, 'p'}, {"no-newline", no_argument, 0, 'n'}, {"list-types", no_argument, 0, 'l'}, + {"watch", required_argument, 0, 'w'}, {"type", required_argument, 0, 't'}, {"seat", required_argument, 0, 's'}, {0, 0, 0, 0} }; while (1) { int option_index; - const char *opts = "vhpnlt:s:"; + const char *opts = "vhpnlw:t:s:"; int c = getopt_long(argc, argv, opts, long_options, &option_index); if (c == -1) { break; @@ -300,6 +338,14 @@ static void parse_options(int argc, argv_t argv) { case 'l': options.list_types = 1; break; + case 'w': + options.watch = 1; + options.watch_command = (argv_t) &argv[optind - 1]; + /* We're going to forward the rest of our + * arguments to the command we spawn, so stop + * trying to process further options. + */ + return; case 't': options.explicit_type = strdup(optarg); break; @@ -369,6 +415,12 @@ int main(int argc, argv_t argv) { } if (device->needs_popup_surface) { + if (options.watch) { + bail( + "Watch mode requires a compositor that supports " + "wlroots data-control protocol" + ); + } /* If we cannot get the selection directly, pop up * a surface. When it gets focus, we'll immediately * get the selection events, se we don't need to do From 2c14ebe15dbd81d59567b5a78717fcbd3c2a7b7f Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 00:28:09 +0300 Subject: [PATCH 048/139] Bump the version to 2.0.0-beta --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 0a2ed60..6414a6f 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wl-clipboard', 'c', - version: '1.0.0-git', + version: '2.0.0-beta', license: 'GPL3+', meson_version: '>= 0.44.0', default_options: 'c_std=gnu99' From ce879500d71fa4ccdf5c29c42f6b286325ca28ae Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Mon, 16 Sep 2019 00:18:33 -0700 Subject: [PATCH 049/139] Add zsh completions --- completions/meson.build | 1 + completions/zsh/_wl-copy | 42 +++++++++++++++++++++++++++++++++++++ completions/zsh/_wl-paste | 42 +++++++++++++++++++++++++++++++++++++ completions/zsh/meson.build | 4 ++++ 4 files changed, 89 insertions(+) create mode 100644 completions/zsh/_wl-copy create mode 100644 completions/zsh/_wl-paste create mode 100644 completions/zsh/meson.build diff --git a/completions/meson.build b/completions/meson.build index 3653b21..3cce95b 100644 --- a/completions/meson.build +++ b/completions/meson.build @@ -1 +1,2 @@ subdir('bash') +subdir('zsh') diff --git a/completions/zsh/_wl-copy b/completions/zsh/_wl-copy new file mode 100644 index 0000000..5fd9bd7 --- /dev/null +++ b/completions/zsh/_wl-copy @@ -0,0 +1,42 @@ +#compdef wl-copy + +__xdg_mimetypes() { + local expl SEARCH=${XDG_DATA_HOME:-$HOME/.local/share} + SEARCH+=:${XDG_DATA_DIRS:-/usr/share:/usr/local/share} + + local -a _all_mimetypes + for dir in ${(s.:.)SEARCH}; do + [[ -f "${dir}/mime/types" ]] && + _all_mimetypes+=(${(f)"$(<"${dir}/mime/types")"}) + done + + _wanted mimetypes expl 'mimetypes' _multi_parts / _all_mimetypes +} + +__all_seats() { + local -a seats + if (( $+commands[weston-info] && $+commands[sed] )); then + seats=( ${(@f)"$(weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}')"} ) + elif (( $+commands[loginctl] )); then + seats=( ${(@f)"$(loginctl --no-legend --no-pager list-seats 2>/dev/null)"} ) + fi + + if [[ -z $seats ]]; then + # seat0 is always a vaild seat and covers most cases, so its a good fallback. + compadd "$@" - seat0 + else + compadd "$@" -a seats + fi +} + +_arguments -S -s \ + {-o,--paste-once}'[Only serve one paste request and then exit]' \ + {-f,--foreground}'[Stay in the foreground instead of forking]' \ + {-c,--clear}'[Instead of copying anything, clear the clipboard]' \ + {-p,--primary}'[Use the "primary" clipboard]' \ + {-n,--trim-newline}'[Do not copy the trailing newline character]' \ + {-t+,--type=}'[Override the inferred MIME type for the content]:mimetype:__xdg_mimetypes' \ + {-s+,--seat=}'[Pick the seat to work with]:seat:__all_seats' \ + {-v,--version}'[Display version info]' \ + {-h,--help}'[Display a help message]' \ + '*::text' diff --git a/completions/zsh/_wl-paste b/completions/zsh/_wl-paste new file mode 100644 index 0000000..a402d30 --- /dev/null +++ b/completions/zsh/_wl-paste @@ -0,0 +1,42 @@ +#compdef wl-paste + +_wl-paste_types() { + local -a types args + args=( + ${(k)opt_args[-p]} + ${(k)opt_args[--primary]} + ${(k)opt_args[-s]} ${(v)opt_args[-s]} + ${(k)opt_args[--seat]} ${(v)opt_args[--seat]} + ) + types=( ${(@f)"$(wl-paste $args -l 2>/dev/null)"} ) + + local expl + _wanted types expl 'type' compadd "$@" -a types +} + +__all_seats() { + local -a seats + if (( $+commands[weston-info] && $+commands[sed] )); then + seats=( ${(@f)"$(weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}')"} ) + elif (( $+commands[loginctl] )); then + seats=( ${(@f)"$(loginctl --no-legend --no-pager list-seats 2>/dev/null)"} ) + fi + + if [[ -z $seats ]]; then + # seat0 is always a vaild seat and covers most cases, so its a good fallback. + compadd "$@" - seat0 + else + compadd "$@" -a seats + fi +} + +typeset -A opt_args +_arguments -S -s \ + {-n,--no-newline}'[Do not append a newline character]' \ + {-l,--list-types}'[Instead of pasting, list the offered types]' \ + {-p,--primary}'[Use the "primary" clipboard]' \ + {-w+,--watch=}'[Run a command wach time the selection changes]:*::command:_normal' \ + {-t+,--type=}'[Override the inferred MIME type for the content]:mimetype:_wl-paste_types' \ + {-s+,--seat=}'[Pick the seat to work with]:seat:__all_seats' \ + {-v,--version}'[Display version info]' \ + {-h,--help}'[Display a help message]' diff --git a/completions/zsh/meson.build b/completions/zsh/meson.build new file mode 100644 index 0000000..0ae6a08 --- /dev/null +++ b/completions/zsh/meson.build @@ -0,0 +1,4 @@ +zsh_completion_dir = join_paths(get_option('datadir'), 'zsh', 'site-functions') + +install_data('_wl-copy', install_dir: zsh_completion_dir) +install_data('_wl-paste', install_dir: zsh_completion_dir) From 5b595cf9af5b6c0e89d5f5376fc250dde015fc2a Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 11:55:17 +0300 Subject: [PATCH 050/139] Add generated protocol headers to sources list This ensures the protocol headers are generated before we start to compile the code files. Previously it would only be considered a linking dependency. Fixes https://github.com/bugaevc/wl-clipboard/issues/58 --- src/meson.build | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/meson.build b/src/meson.build index 9837e9a..600d067 100644 --- a/src/meson.build +++ b/src/meson.build @@ -69,6 +69,7 @@ if have_wlr_data_control endif protocol_deps = [] +protocol_headers = [] foreach protocol : protocols name = protocol[0] @@ -81,6 +82,7 @@ foreach protocol : protocols input: xml, output: name + '.c', command: [wayland_scanner, scanner_code_command, '@INPUT@', '@OUTPUT@'] ) + protocol_headers += header lib = static_library(name, impl, header, dependencies: wayland) protocol_deps += lib endforeach @@ -88,6 +90,11 @@ endforeach lib = static_library( 'wl-clipboard', [ + protocol_headers, + + 'includes/shell-protocols.h', + 'includes/selection-protocols.h', + 'util/string.h', 'util/string.c', 'util/files.h', @@ -95,9 +102,6 @@ lib = static_library( 'util/misc.h', 'util/misc.c', - 'includes/shell-protocols.h', - 'includes/selection-protocols.h', - 'types/source.h', 'types/source.c', 'types/offer.h', From 9980a1b5c2ff6cf059c333430c73880c9c5a655f Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 19:17:32 +0300 Subject: [PATCH 051/139] Ensure wl-paste --watch gets a full subcommand, not an argument Fixes https://github.com/bugaevc/wl-clipboard/issues/59 --- src/wl-paste.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/wl-paste.c b/src/wl-paste.c index 36a1142..899c7c4 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -340,7 +340,34 @@ static void parse_options(int argc, argv_t argv) { break; case 'w': options.watch = 1; + /* We tell getopt that --watch requires an argument, + * but it's more nuanced than that. We actually take + * that argument and everything that follows it as a + * subcommand to spawn. When we get here, getopt sets + * the optind variable to point to the *next* option + * to be processed, after both the current option and + * its argument, if any. So if we're invoked like this: + * $ wl-paste --primary --watch foo bar baz + * then optind will point to bar, as it considers foo + * to be the argument that --watch requires. However, + * if we get invoked like this: + * $ wl-paste --watch=foo bar baz + * or like this: + * $ wl-paste -wfoo bar baz + * then getopt will not consider it an error, and optind + * will also point to bar. We do consider that an error, + * so detect this case and print an error message. + */ options.watch_command = (argv_t) &argv[optind - 1]; + if (options.watch_command[0][0] == '-') { + fprintf( + stderr, + "Expected a subcommand instead of an argument" + " after --watch\n" + ); + print_usage(stderr, argv[0]); + exit(1); + } /* We're going to forward the rest of our * arguments to the command we spawn, so stop * trying to process further options. From 48cf78f91b917122e6030787d237c77349609afc Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 19:25:23 +0300 Subject: [PATCH 052/139] Document that --watch implies --no-newline --- README.md | 2 +- data/wl-clipboard.1 | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 66231f9..8ce4efa 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ For `wl-copy`: For `wl-paste`: -* `-n`, `--no-newline` Do not append a newline character after the pasted clipboard content. This option is automatically enabled for non-text content types. +* `-n`, `--no-newline` Do not append a newline character after the pasted clipboard content. This option is automatically enabled for non-text content types and when using the `--watch` mode. * `-l`, `--list-types` Instead of pasting the selection, output the list of MIME types it is offered in. * `-w command...`, `--watch command...` Instead of pasting once and exiting, continuously watch the clipboard for changes, and run the specified command each time a new selection appears. The spawned process can read the clipboard contents from its standard input. This mode requires a compositor that supports the [wlroots data-control protocol](https://github.com/swaywm/wlr-protocols/blob/master/unstable/wlr-data-control-unstable-v1.xml). diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index c2d2762..f41968b 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -58,7 +58,8 @@ Do not copy the trailing newline character if it is present in the input file. .TP \fB-n\fR, \fB--no-newline Do not append a newline character after the pasted clipboard content. This -option is automatically enabled for non-text content types. +option is automatically enabled for non-text content types and when using the +\fB--watch\fR mode. .TP \fB-t\fI mime/type\fR, \fB--type\fI mime/type Override the automatically selected MIME type. For \fBwl-copy\fR this option From d9d2d93d736d9caea66832e5da5713bad876a5f7 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 19:26:10 +0300 Subject: [PATCH 053/139] Remove a leftover mention of PATH_MAX See https://github.com/bugaevc/wl-clipboard/issues/45 --- src/util/files.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/util/files.c b/src/util/files.c index 9f4668d..b3a4eda 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -33,7 +33,6 @@ #include // exit #include // basename #include -#include // PATH_MAX #ifdef HAVE_MEMFD # include // syscall, SYS_memfd_create From 235f10060f58f012af6d5fb079a52e09c30f49da Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 19:33:14 +0300 Subject: [PATCH 054/139] Update build instructions Mention that wl-clipboard now works on Mac OS X and GNU Hurd, as well as rewrite the dependencies section a bit now that there are more protocols wl-clipboard can make use of. --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8ce4efa..915db62 100644 --- a/README.md +++ b/README.md @@ -79,13 +79,17 @@ $ ninja $ sudo ninja install ``` -wl-clipboard supports Linux and BSD systems. The only mandatory dependency is -the `wayland-client` library (try package named `wayland-devel` or -`libwayland-dev`). +wl-clipboard supports Linux and BSD systems, and is also known to work on +Mac OS X and GNU Hurd. The only mandatory dependency is the `wayland-client` +library (try package named `wayland-devel` or `libwayland-dev`). Optional dependencies for building: -* `wayland-scanner` for primary selection support using the bundled [gtk-primary-selection protocol](src/protocol/gtk-primary-selection.xml) -* `wayland-protocols` (version 1.12 or later) for xdg-shell support (otherwise it won't run under compositors lacking `wl_shell` support, see [the issue #2](https://github.com/bugaevc/wl-clipboard/issues/2)) +* `wayland-scanner` +* `wayland-protocols` (version 1.12 or later) + +If these are found during configuration, wl-clipboard gets built with +additional protocols support, which enables features such as primary selection +support and `--watch` mode. Optional dependencies for running: * `xdg-mime` for content type inference in `wl-copy` (try package named `xdg-utils`) From 7c771d259d40a79f974af9e445b45b16da0b9172 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 19:43:18 +0300 Subject: [PATCH 055/139] Improve an error message Namely, print a better the error message when trying to use the --watch mode if wl-clipboard was built without wlr-data-control support. --- src/util/misc.c | 13 +++++++++++++ src/util/misc.h | 1 + src/wl-paste.c | 5 +---- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/util/misc.c b/src/util/misc.c index f252a62..e9f7671 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -44,3 +44,16 @@ void complain_about_selection_support(int primary) { bail("The compositor does not seem to support primary selection"); } + +void complain_about_watch_mode_support() { +#ifdef HAVE_WLR_DATA_CONTROL + bail( + "Watch mode requires a compositor that supports " + "wlroots data-control protocol" + ); +#else + bail( + "wl-clipboard was built without wlroots data-control protocol support" + ); +#endif +} diff --git a/src/util/misc.h b/src/util/misc.h index be5298f..02c9984 100644 --- a/src/util/misc.h +++ b/src/util/misc.h @@ -27,5 +27,6 @@ void print_version_info(void); void complain_about_selection_support(int primary); +void complain_about_watch_mode_support(void); #endif /* UTIL_MISC_H */ diff --git a/src/wl-paste.c b/src/wl-paste.c index 899c7c4..a6572b6 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -443,10 +443,7 @@ int main(int argc, argv_t argv) { if (device->needs_popup_surface) { if (options.watch) { - bail( - "Watch mode requires a compositor that supports " - "wlroots data-control protocol" - ); + complain_about_watch_mode_support(); } /* If we cannot get the selection directly, pop up * a surface. When it gets focus, we'll immediately From 176351050a4b5daa66bb1a71c3916cb5fb2d2ca3 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 19:49:40 +0300 Subject: [PATCH 056/139] Expand a comment --- src/wl-copy.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/wl-copy.c b/src/wl-copy.c index 612e895..e474e94 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -227,7 +227,13 @@ int main(int argc, argv_t argv) { /* Copy our command-line arguments */ copy_action->argv_to_copy = &argv[optind]; } else { - /* Copy data from our stdin */ + /* Copy data from our stdin. + * It's important that we only do this + * after going through the initial stages + * that are likely to result in errors, + * so that we don't forget to clean up + * the temp file. + */ char *temp_file = dump_stdin_into_a_temp_file(); if (options.trim_newline) { trim_trailing_newline(temp_file); From 09a76e0a93b4bef4bc81b5602dcf9dd66d466358 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 20:55:15 +0300 Subject: [PATCH 057/139] Fix wl-copy/wl-paste executables not depending on protocol_headers This is a follow-up to https://github.com/bugaevc/wl-clipboard/commit/5b595cf9af5b6c0e89d5f5376fc250dde015fc2a Fixes https://github.com/bugaevc/wl-clipboard/issues/58 --- src/meson.build | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/meson.build b/src/meson.build index 600d067..45c2f67 100644 --- a/src/meson.build +++ b/src/meson.build @@ -129,5 +129,17 @@ lib = static_library( link_with: protocol_deps ) -executable('wl-copy', 'wl-copy.c', dependencies: wayland, link_with: lib, install: true) -executable('wl-paste', 'wl-paste.c', dependencies: wayland, link_with: lib, install: true) +executable( + 'wl-copy', + ['wl-copy.c', protocol_headers], + dependencies: wayland, + link_with: lib, + install: true +) +executable( + 'wl-paste', + ['wl-paste.c', protocol_headers], + dependencies: wayland, + link_with: lib, + install: true +) From bd49478c60a4be5516931de6307f872c1d48973c Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 21:37:36 +0300 Subject: [PATCH 058/139] Fix a few unused return code warnings --- src/util/files.c | 11 +++++++++-- src/wl-paste.c | 18 ++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/util/files.c b/src/util/files.c index b3a4eda..14a3091 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -88,7 +88,10 @@ void trim_trailing_newline(const char *file_path) { goto out; } - ftruncate(fd, seek_res); + int rc = ftruncate(fd, seek_res); + if (rc < 0) { + perror("ftruncate"); + } out: close(fd); } @@ -102,7 +105,11 @@ char *path_for_fd(int fd) { char *infer_mime_type_from_contents(const char *file_path) { /* Spawn xdg-mime query filetype */ int pipefd[2]; - pipe(pipefd); + int rc = pipe(pipefd); + if (rc < 0) { + perror("pipe"); + return NULL; + } pid_t pid = fork(); if (pid < 0) { diff --git a/src/wl-paste.c b/src/wl-paste.c index a6572b6..1c61974 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -210,8 +210,19 @@ static void selection_callback(struct offer *offer, int primary) { options.no_newline = 1; } + /* Create a pipe which we'll + * use to receive the data. + */ int pipefd[2]; - pipe(pipefd); + int rc = pipe(pipefd); + if (rc < 0) { + perror("pipe"); + offer_destroy(offer); + if (options.watch) { + return; + } + exit(1); + } offer_receive(offer, mime_type, pipefd[1]); @@ -259,7 +270,10 @@ static void selection_callback(struct offer *offer, int primary) { close(pipefd[1]); wait(NULL); if (!options.no_newline && !options.watch) { - write(STDOUT_FILENO, "\n", 1); + rc = write(STDOUT_FILENO, "\n", 1); + if (rc != 1) { + perror("write"); + } } offer_destroy(offer); From 716a0a7899431d22533fff8a9f3057cc1573cae2 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 16 Sep 2019 23:33:16 +0300 Subject: [PATCH 059/139] Fix use-after-free If we would get an enter event after the popup surface was destroyed, we would try to use the freed popup surface data to handle the callback. Clear the callback to prevent that. --- src/types/popup-surface.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index b629a77..1aad115 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -126,6 +126,14 @@ void popup_surface_init(struct popup_surface *self) { } void popup_surface_destroy(struct popup_surface *self) { + /* We cannot destroy the keyboard + * because it acts as a listener, + * and there's no way to reset that. + * So just unreference ourselves. + */ + self->keyboard->on_focus = NULL; + self->keyboard->data = NULL; + shell_surface_destroy(self->shell_surface); wl_surface_destroy(self->wl_surface); free(self->shell); From 6c7ef0dd96f548fc49e7e6afd250f5f824d4e042 Mon Sep 17 00:00:00 2001 From: louib Date: Tue, 17 Sep 2019 16:35:05 -0400 Subject: [PATCH 060/139] Fix some typos in the manpage --- data/wl-clipboard.1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index f41968b..0aea5f2 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -74,7 +74,7 @@ Specify which seat \fBwl-copy\fR and \fBwl-paste\fR should work with. Wayland natively supports multi-seat configurations where each seat gets its own mouse pointer, keyboard focus, and among other things its own separate clipboard. The name of the default seat is likely \fIdefault\fR or \fIseat0\fR, and additional -seat names normally come form +seat names normally come from the .BR udev (7) property \fBENV{WL_SEAT}\fR. You can view the list of the currently available seats as advertised by the compositor using the @@ -103,7 +103,7 @@ Display a short help message listing the available options. WAYLAND_DISPLAY Specifies what Wayland server \fBwl-copy\fR and \fBwl-paste\fR should connect to. This is the same environment variable that you pass to other Wayland -clients, such as graphical aplications, that connect to this Wayland server. It +clients, such as graphical applications, that connect to this Wayland server. It is normally set up automatically by the graphical session and the Wayland compositor. See .BR wl_display_connect (3) From a60a0a468cddeb2de8cb83264e1874ab3f46fbd5 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Tue, 17 Sep 2019 23:48:18 +0300 Subject: [PATCH 061/139] Move --watch to the end of the options list in the man page We stop processing options as soon as we see --watch, and pass the rest of the arguments to the spawned command unmodified. Placing --watch before other options in the usage example could be a little bit confusing, as it appeared to suggest that we actually do accept other options after it. --- data/wl-clipboard.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index 0aea5f2..93feed9 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -16,9 +16,9 @@ wl-clipboard \- Wayland copy and paste command line utilities [\fB--primary\fR] [\fB--no-newline\fR] [\fB--list-types\fR] -[\fB--watch \fIcommand\fR...\] [\fB--type \fImime/type\fR] [\fB--seat \fIseat-name\fR] +[\fB--watch \fIcommand\fR...\] .SH DESCRIPTION \fBwl-copy\fR copies the given \fItext\fR to the Wayland clipboard. If no \fItext\fR is given, \fBwl-copy\fR copies data from its standard input. From 921f8ad52a4867fa78048e993760d890f4243e36 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Sun, 22 Sep 2019 17:34:51 -0700 Subject: [PATCH 062/139] Add zshcompletiondir option for build --- completions/zsh/meson.build | 11 ++++++++--- meson_options.txt | 5 +++++ 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 meson_options.txt diff --git a/completions/zsh/meson.build b/completions/zsh/meson.build index 0ae6a08..dc2820d 100644 --- a/completions/zsh/meson.build +++ b/completions/zsh/meson.build @@ -1,4 +1,9 @@ -zsh_completion_dir = join_paths(get_option('datadir'), 'zsh', 'site-functions') +zsh_completion_dir = get_option('zshcompletiondir') +if zsh_completion_dir == '' + zsh_completion_dir = join_paths(get_option('datadir'), 'zsh', 'site-functions') +endif -install_data('_wl-copy', install_dir: zsh_completion_dir) -install_data('_wl-paste', install_dir: zsh_completion_dir) +if zsh_completion_dir != 'no' + install_data('_wl-copy', install_dir: zsh_completion_dir) + install_data('_wl-paste', install_dir: zsh_completion_dir) +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..af4afae --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,5 @@ +option('zshcompletiondir', + type: 'string', + value: '', + description: 'Directory for zsh completions. Set "no" to disable.' +) From df48e2117d88878e0cb946fd6e73f99c34eebefe Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Sun, 22 Sep 2019 17:35:25 -0700 Subject: [PATCH 063/139] Clean up wl-paste(1) zsh completions --- completions/zsh/_wl-paste | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/completions/zsh/_wl-paste b/completions/zsh/_wl-paste index a402d30..852d630 100644 --- a/completions/zsh/_wl-paste +++ b/completions/zsh/_wl-paste @@ -1,12 +1,13 @@ #compdef wl-paste +local expl +typeset -A opt_args + _wl-paste_types() { local -a types args args=( - ${(k)opt_args[-p]} - ${(k)opt_args[--primary]} - ${(k)opt_args[-s]} ${(v)opt_args[-s]} - ${(k)opt_args[--seat]} ${(v)opt_args[--seat]} + ${(kv)opt_args[(I)-p|--primary]} + ${(kv)opt_args[(I)-s|--seat]} ) types=( ${(@f)"$(wl-paste $args -l 2>/dev/null)"} ) @@ -23,19 +24,18 @@ __all_seats() { fi if [[ -z $seats ]]; then - # seat0 is always a vaild seat and covers most cases, so its a good fallback. + # seat0 covers most cases, so its a good fallback. compadd "$@" - seat0 else compadd "$@" -a seats fi } -typeset -A opt_args _arguments -S -s \ {-n,--no-newline}'[Do not append a newline character]' \ {-l,--list-types}'[Instead of pasting, list the offered types]' \ {-p,--primary}'[Use the "primary" clipboard]' \ - {-w+,--watch=}'[Run a command wach time the selection changes]:*::command:_normal' \ + {-w,--watch}'[Run a command wach time the selection changes]:*::command:_normal' \ {-t+,--type=}'[Override the inferred MIME type for the content]:mimetype:_wl-paste_types' \ {-s+,--seat=}'[Pick the seat to work with]:seat:__all_seats' \ {-v,--version}'[Display version info]' \ From cef29eac68d00e2c5e3d35c4f23804de24e15642 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 19 Sep 2019 16:34:07 +0300 Subject: [PATCH 064/139] Refactor bash completion scripts This fixes https://github.com/bugaevc/wl-clipboard/issues/62 The problem was that a long option such as --seat would be considered to be a combination of short options ending with the letter t, such as -pnt, and completed as if it needed a type to go after that t. This also fixes comments and empty lines from /etc/mime.types being retained in wl-copy --type completions. --- completions/bash/wl-copy | 52 ++++++++++++++++++++--------- completions/bash/wl-paste | 69 ++++++++++++++++++++++++--------------- 2 files changed, 79 insertions(+), 42 deletions(-) diff --git a/completions/bash/wl-copy b/completions/bash/wl-copy index 7561d44..83ec084 100644 --- a/completions/bash/wl-copy +++ b/completions/bash/wl-copy @@ -19,24 +19,44 @@ _wl_clipboard_complete_types() { _wl_copy_completion() { compopt +o default local cur prev opts types seats + cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="-o --paste-once -f --foreground -c --clear -p --primary -n --trim-newline -t --type -s --seat -v --version -h --help" - if [ "$prev" = "<" ]; then - compopt -o default - COMPREPLY=() - elif [ \( "x${prev:0:1}" = "x-" -a "x${prev:1:2}" != "x-" -a "${prev: -1}" = "t" \) -o "$prev" = "--type" ]; then - types="$(sed 's/^\(\S\+\).*/\1/' /etc/mime.types 2>/dev/null)" - _wl_clipboard_complete_types "$types" - elif [ \( "x${prev:0:1}" = "x-" -a "x${prev:1:2}" != "x-" -a "${prev: -1}" = "s" \) -o "$prev" = "--seat" ]; then - seats="$(_wl_clipboard_list_seats)" - COMPREPLY=($(compgen -W "$seats" -- "$cur")) - elif [ "${cur:0:1}" = "<" ]; then - compopt -o default - COMPREPLY=() - else - COMPREPLY=($(compgen -W "$opts" -- "$cur")) - fi + + case "$cur" in + "<"*) + compopt -o default + COMPREPLY=() + return + ;; + esac + + case "$prev" in + "<"*) + compopt -o default + COMPREPLY=() + ;; + -t | -[a-z]*t | --type) + types="$(sed -e 's/#.*$//' -e '/^$/d' -e 's/^\(\S\+\).*/\1/' /etc/mime.types 2>/dev/null)" + _wl_clipboard_complete_types "$types" + ;; + -s | -[a-z]*s | --seat) + seats="$(_wl_clipboard_list_seats)" + COMPREPLY=($(compgen -W "$seats" -- "$cur")) + ;; + *) + opts="-o --paste-once " + opts+="-f --foreground " + opts+="-c --clear " + opts+="-p --primary " + opts+="-n --trim-newline " + opts+="-t --type " + opts+="-s --seat " + opts+="-v --version " + opts+="-h --help " + COMPREPLY=($(compgen -W "$opts" -- "$cur")) + ;; + esac } complete -o default -F _wl_copy_completion wl-copy diff --git a/completions/bash/wl-paste b/completions/bash/wl-paste index 91003fe..44d9708 100644 --- a/completions/bash/wl-paste +++ b/completions/bash/wl-paste @@ -20,20 +20,19 @@ _wl_clipboard_complete_paste_types() { local i cur primary seat cmd types for (( i = 0; i < "${#COMP_WORDS[@]}"; ++i )); do cur="${COMP_WORDS[i]}" - if [ "x${cur:0:1}" = "x-" -a "x${cur:1:2}" != "x-" ]; then - case "$cur" in - *p*) + case "$cur" in + -p* | -[a-z]*p* | --primary) primary="yes" ;; - *s) + esac + case "$cur" in + -s | -[a-z]*s | --seat) seat="${COMP_WORDS[i+1]}" ;; - esac - elif [ "$cur" = "--primary" ]; then - primary="yes" - elif [ "$cur" = "--seat" ]; then - seat="${COMP_WORDS[i+1]}" - fi + -s* | -[a-z]s*) + seat=$(echo "$cur" | sed 's/-[^s]*s//') + ;; + esac done cmd="${COMP_WORDS[0]}" if [ -n "$primary" ]; then @@ -53,7 +52,7 @@ _wl_paste_completion() { for (( offset=1; offset < COMP_CWORD; offset++ )); do cur="${COMP_WORDS[offset]}" - if [ \( "x${cur:0:1}" = "x-" -a "x${cur:1:2}" != "x-" -a "${cur: -1}" = "w" \) -o "$cur" = "--watch" ]; then + if [ "$cur" = "-w" -o "$cur" = "--watch" ]; then _command_offset $(($offset+1)) return fi @@ -61,21 +60,39 @@ _wl_paste_completion() { cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="-n --no-newline -l --list-types -w --watch -p --primary -t --type -s --seat -v --version -h --help" - if [ "$prev" = ">" ]; then - compopt -o default - COMPREPLY=() - elif [ \( "x${prev:0:1}" = "x-" -a "x${prev:1:2}" != "x-" -a "${prev: -1}" = "t" \) -o "$prev" = "--type" ]; then - _wl_clipboard_complete_paste_types - elif [ \( "x${prev:0:1}" = "x-" -a "x${prev:1:2}" != "x-" -a "${prev: -1}" = "s" \) -o "$prev" = "--seat" ]; then - seats="$(_wl_clipboard_list_seats)" - COMPREPLY=($(compgen -W "$seats" -- "$cur")) - elif [ "${cur:0:1}" = ">" ]; then - compopt -o default - COMPREPLY=() - else - COMPREPLY=($(compgen -W "$opts" -- "$cur")) - fi + + case "$cur" in + ">"*) + compopt -o default + COMPREPLY=() + return + ;; + esac + + case "$prev" in + ">"*) + compopt -o default + COMPREPLY=() + ;; + -t | -[a-z]*t | --type) + _wl_clipboard_complete_paste_types + ;; + -s | -[a-z]*s | --seat) + seats="$(_wl_clipboard_list_seats)" + COMPREPLY=($(compgen -W "$seats" -- "$cur")) + ;; + *) + opts="-n --no-newline " + opts+="-l --list-types " + opts+="-p --primary " + opts+="-w --watch " + opts+="-t --type " + opts+="-s --seat " + opts+="-v --version " + opts+="-h --help " + COMPREPLY=($(compgen -W "$opts" -- "$cur")) + ;; + esac } complete -o default -F _wl_paste_completion wl-paste From c983120cc99a14e4e093445970af7a9c41a83468 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 3 Oct 2019 12:23:39 +0300 Subject: [PATCH 065/139] Make sure to clean up the temp file on server communicaiton errors --- src/wl-copy.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/wl-copy.c b/src/wl-copy.c index e474e94..5f1766a 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -65,7 +65,7 @@ static void did_set_selection_callback(struct copy_action *copy_action) { } } -static void cleanup_and_exit(struct copy_action *copy_action) { +static void cleanup_and_exit(struct copy_action *copy_action, int code) { /* We're done copying! * All that's left to do now is to * clean up after ourselves and exit.*/ @@ -76,17 +76,17 @@ static void cleanup_and_exit(struct copy_action *copy_action) { perror("exec rm"); exit(1); } else { - exit(0); + exit(code); } } static void cancelled_callback(struct copy_action *copy_action) { - cleanup_and_exit(copy_action); + cleanup_and_exit(copy_action, 0); } static void pasted_callback(struct copy_action *copy_action) { if (options.paste_once) { - cleanup_and_exit(copy_action); + cleanup_and_exit(copy_action, 0); } } @@ -275,5 +275,6 @@ int main(int argc, argv_t argv) { while (wl_display_dispatch(wl_display) >= 0); perror("wl_display_dispatch"); + cleanup_and_exit(copy_action, 1); return 1; } From f3a45f69f7d14e7f7050bca4cbf6fea6697d1455 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 3 Oct 2019 15:16:09 +0300 Subject: [PATCH 066/139] Release wl-clipboard 2.0 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 6414a6f..ca762a3 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wl-clipboard', 'c', - version: '2.0.0-beta', + version: '2.0.0', license: 'GPL3+', meson_version: '>= 0.44.0', default_options: 'c_std=gnu99' From 3ce6d652c6910c6867a0afa09db45c22f732de23 Mon Sep 17 00:00:00 2001 From: Haden Collins Date: Mon, 20 Jan 2020 15:57:37 -0600 Subject: [PATCH 067/139] Add fish completions --- completions/fish/meson.build | 18 +++++++++++++ completions/fish/wl-copy.fish | 21 ++++++++++++++++ completions/fish/wl-paste.fish | 46 ++++++++++++++++++++++++++++++++++ completions/meson.build | 1 + meson_options.txt | 5 ++++ 5 files changed, 91 insertions(+) create mode 100644 completions/fish/meson.build create mode 100644 completions/fish/wl-copy.fish create mode 100644 completions/fish/wl-paste.fish diff --git a/completions/fish/meson.build b/completions/fish/meson.build new file mode 100644 index 0000000..b0bc411 --- /dev/null +++ b/completions/fish/meson.build @@ -0,0 +1,18 @@ +fish_completion_dir = get_option('fishcompletiondir') +fish = dependency('fish', required : false) + +if fish_completion_dir == '' and fish.found() + fish_completion_dir = fish.get_pkgconfig_variable('completionsdir') +elif fish_completion_dir == '' + # Fish does not look in /usr/local/, which is the default prefix, + # so we cannot use get_option("datadir"). Instead, they recommend + # custom completions to be installed into /usr/share/fish/vendor_completions.d, + # so we use that. Note that this is a fallback in case you neither + # have fish installed, nor have specified a path explicitly + fish_completion_dir = '/usr/share/fish/vendor_completions.d' +endif + +if fish_completion_dir != 'no' + install_data('wl-copy.fish', install_dir: fish_completion_dir) + install_data('wl-paste.fish', install_dir: fish_completion_dir) +endif diff --git a/completions/fish/wl-copy.fish b/completions/fish/wl-copy.fish new file mode 100644 index 0000000..dafd32a --- /dev/null +++ b/completions/fish/wl-copy.fish @@ -0,0 +1,21 @@ +function __wayland_seats --description 'Print all wayland seats' + if type -q weston-info + weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}' + else if type -q loginctl + loginctl --no-legend --no-pager list-seats 2>/dev/null + else + # Fallback seat + echo "seat0" + end + +end + +complete -c wl-copy -x +complete -c wl-copy -s h -l help -d 'Display a help message' +complete -c wl-copy -s v -l version -d 'Display version info' +complete -c wl-copy -s o -l paste-once -d 'Only serve one paste request and then exit' +complete -c wl-copy -s c -l clear -d 'Instead of copying anything, clear the clipboard' +complete -c wl-copy -s p -l primary -d 'Use the "primary" clipboard' +complete -c wl-copy -s n -l trim-newline -d 'Do not copy the trailing newline character' +complete -c wl-copy -s t -l type -x -d 'Override the inferred MIME type for the content' -a "(__fish_print_xdg_mimetypes)" +complete -c wl-copy -s s -l seat -x -d 'Pick the seat to work with' -a "(__wayland_seats)" diff --git a/completions/fish/wl-paste.fish b/completions/fish/wl-paste.fish new file mode 100644 index 0000000..2842019 --- /dev/null +++ b/completions/fish/wl-paste.fish @@ -0,0 +1,46 @@ +function __wayland_seats --description 'Print all wayland seats' + if type -q weston-info + weston-info 2>/dev/null | sed -n '/wl_seat/{n;s/\s*name: //;p}' + else if type -q loginctl + loginctl --no-legend --no-pager list-seats 2>/dev/null + else + # Fallback seat + echo "seat0" + end +end + +function __wl_paste_types --description 'Print types with context' + set -l type (commandline -o) + + for i in (seq (count $type)) + if [ "$type[$i]" = '--primary' ]; or [ "$type[$i]" = '-p' ] + set clip "primary" + else if [ "$type[$i]" = '--seat' ]; or [ "$type[$i]" = '-s' ] + set seat $type[(math $i + 1)] + end + end + + # Note fish does not handle passing unset variables + # to commands well, thus setting clip to "--primary" and passing + # that to wl-paste wont work, so if statements are used instead + if test -n "$seat"; and test -n "$clip" + wl-paste 2>/dev/null --seat "$seat" -p -l + else if test -n "$seat" + wl-paste 2>/dev/null --seat "$seat" -l + else if test -n "$clip" + wl-paste 2>/dev/null -p -l + else + wl-paste 2>/dev/null -l + end +end + +complete -c wl-paste -f +complete -c wl-paste -f -n '__fish_contains_opt -s w watch' -a "(__fish_complete_subcommand -- -s --seat -t --type)" +complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s h -l help -d 'Display a help message' +complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s v -l version -d 'Display version info' +complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s n -l no-newline -d 'Do not append a newline character' +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' +complete -c wl-paste -n '__fish_not_contain_opt -s w watch' -s p -l primary -d 'Use the "primary" clipboard' +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' +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)" +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)" diff --git a/completions/meson.build b/completions/meson.build index 3cce95b..96542c5 100644 --- a/completions/meson.build +++ b/completions/meson.build @@ -1,2 +1,3 @@ subdir('bash') subdir('zsh') +subdir('fish') diff --git a/meson_options.txt b/meson_options.txt index af4afae..32b24ed 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -3,3 +3,8 @@ option('zshcompletiondir', value: '', description: 'Directory for zsh completions. Set "no" to disable.' ) +option('fishcompletiondir', + type: 'string', + value: '', + description: 'Directory for fish completions. Set "no" to disable.' +) From 2c3cee1c8173d75b1411997b2c0f3e486194ac0c Mon Sep 17 00:00:00 2001 From: Lorenzo Date: Thu, 13 Feb 2020 13:42:13 +0100 Subject: [PATCH 068/139] Fix a warning We should not be using void * in pointer arithmetics. Fixes https://github.com/bugaevc/wl-clipboard/issues/76 --- src/types/offer.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/types/offer.h b/src/types/offer.h index 800466b..21dd567 100644 --- a/src/types/offer.h +++ b/src/types/offer.h @@ -37,12 +37,11 @@ struct offer { #define offer_for_each_mime_type(offer, mime_type) \ for ( \ const char *mime_type = offer->offered_mime_types.data; \ - mime_type != offer->offered_mime_types.data + \ - offer->offered_mime_types.size; \ + mime_type != (const char *) offer->offered_mime_types.data + \ + offer->offered_mime_types.size; \ mime_type += strlen(mime_type) + 1 \ ) - void offer_receive(struct offer *self, const char *mime_type, int fd); void offer_destroy(struct offer *self); From aa4633b894c3c5ae6053026529ec9288566060a6 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Fri, 6 Nov 2020 10:01:53 -0800 Subject: [PATCH 069/139] Close stdin/stdout file descriptors properly wl-copy shouldn't hold up a pipeline by forking with the stdout file descriptor left open. Replacing stdin/stdout with /dev/null should also handle errors when opening /dev/null. Closes https://github.com/bugaevc/wl-clipboard/issues/100 --- src/util/files.c | 9 +++++++-- src/wl-copy.c | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/util/files.c b/src/util/files.c index 14a3091..750aa0b 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -123,8 +123,13 @@ char *infer_mime_type_from_contents(const char *file_path) { close(pipefd[0]); close(pipefd[1]); int devnull = open("/dev/null", O_RDONLY); - dup2(devnull, STDIN_FILENO); - close(devnull); + if (devnull >= 0) { + dup2(devnull, STDIN_FILENO); + close(devnull); + } else { + /* If we cannot open /dev/null, just close stdin */ + close(STDIN_FILENO); + } execlp("xdg-mime", "xdg-mime", "query", "filetype", file_path, NULL); exit(1); } diff --git a/src/wl-copy.c b/src/wl-copy.c index 5f1766a..e70ff4f 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -30,6 +30,7 @@ #include #include #include +#include // open #include #include @@ -53,7 +54,20 @@ static void did_set_selection_callback(struct copy_action *copy_action) { * We fork our process and leave the * child running in the background, * while exiting in the parent. + * Also replace stdin/stdout with + * /dev/null so the stdout file + * descriptor isn't kept alive. */ + int devnull = open("/dev/null", O_RDWR); + if (devnull >= 0) { + dup2(devnull, STDOUT_FILENO); + dup2(devnull, STDIN_FILENO); + close(devnull); + } else { + /* If we cannot open /dev/null, just close stdin/stdout */ + close(STDIN_FILENO); + close(STDOUT_FILENO); + } pid_t pid = fork(); if (pid < 0) { perror("fork"); From d2c32f2b4d469f53070e8c4052dea6f412de3916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Fri, 21 May 2021 18:46:42 +0200 Subject: [PATCH 070/139] Ignore SIGHUP in wl-copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Running e.g “foot wl-copy foobar” or “alacritty -e wl-copy foobar” results in the forked copy of wl-copy being terminated and “foobar” no longer being paste:able. The happens because it (the forked copy) receives a SIGHUP when the terminal exits, which terminates wl-copy. This patch fixes this by ignoring SIGHUP. Technically, this is only needed in the forked wl-copy. But, to handle the examples above, the handler needs to be installed *before* the parent wl-copy exits. To avoid a race condition, ignore SIGHUP before the fork. This should be ok since the parent wl-copy exits almost immediately after the fork. --- src/wl-copy.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wl-copy.c b/src/wl-copy.c index e70ff4f..f08cf17 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -33,6 +33,7 @@ #include // open #include #include +#include static struct { int stay_in_foreground; @@ -68,6 +69,7 @@ static void did_set_selection_callback(struct copy_action *copy_action) { close(STDIN_FILENO); close(STDOUT_FILENO); } + signal(SIGHUP, SIG_IGN); pid_t pid = fork(); if (pid < 0) { perror("fork"); From 297ab2a6fdd34c15c5eb3eb427be8daf753bcfd7 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Tue, 20 Jul 2021 21:04:27 +0300 Subject: [PATCH 071/139] Fix a crash During the wl_display_roundtrip() call, the popup surface could gain focus a second time, which would cause do_set_selection() to be invoked again. It would destroy the popup surface, which would cause the outer do_set_selection() call to crash. Fix this in two ways: * Unset the callback before we perform roundtrip, to prevent any reentrancy. * Check whether we (still) have a popup_surface instead of whether we need it; this way even if we end up without a popup surface for some other mysterious reason, we still won't segfault here. Fixes https://github.com/bugaevc/wl-clipboard/issues/117 --- src/types/copy-action.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/types/copy-action.c b/src/types/copy-action.c index 5ef9ea1..f661fc3 100644 --- a/src/types/copy-action.c +++ b/src/types/copy-action.c @@ -35,12 +35,18 @@ static void do_set_selection(struct copy_action *self, uint32_t serial) { /* Set the selection and make sure it reaches * the display before we do anything else, * such as destroying the surface or exiting. + * Make sure to unset the callback to prevent + * reentrancy issues. */ + if (self->popup_surface != NULL) { + self->popup_surface->on_focus = NULL; + self->popup_surface->data = NULL; + } device_set_selection(self->device, self->source, serial, self->primary); wl_display_roundtrip(self->device->wl_display); /* Now, if we have used a popup surface, destroy it */ - if (self->device->needs_popup_surface) { + if (self->popup_surface != NULL) { popup_surface_destroy(self->popup_surface); self->popup_surface = NULL; } From c206d79821bce27dda15ef102536b60ea3182427 Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Sat, 19 Mar 2022 15:12:00 +0300 Subject: [PATCH 072/139] Release wl-clipboard 2.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index ca762a3..81ad51f 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wl-clipboard', 'c', - version: '2.0.0', + version: '2.1.0', license: 'GPL3+', meson_version: '>= 0.44.0', default_options: 'c_std=gnu99' From 89cd9f9e430a6cd8b82fa65a9c05e530e2430ffd Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Mon, 2 May 2022 18:53:22 +0200 Subject: [PATCH 073/139] Ignore nested selection_callback calls in wl-paste In `selection_callback`, if wl-paste uses a popup window, `wl_display_roundtrip` is called. This can cause `selection_callback` to be called again with a NULL offer since wl-paste has lost the keyboard focus. Before this change this would cause wl-paste to abort the ongoing paste by calling `exit(1)`. Instead ignore all but the first selection_callback call except in watch mode. --- src/wl-paste.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/wl-paste.c b/src/wl-paste.c index 1c61974..5071782 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -58,6 +58,7 @@ struct types { static struct wl_display *wl_display = NULL; static struct popup_surface *popup_surface = NULL; +static int offer_received = 0; static struct types classify_offer_types(struct offer *offer) { struct types types = { 0 }; @@ -172,6 +173,11 @@ static const char *mime_type_to_request(struct types types) { #undef try_any static void selection_callback(struct offer *offer, int primary) { + /* Ignore all but the first non-NULL offer */ + if (offer_received && !options.watch) { + return; + } + /* Ignore events we're not interested in */ if (primary != options.primary) { if (offer != NULL) { @@ -187,6 +193,8 @@ static void selection_callback(struct offer *offer, int primary) { bail("No selection"); } + offer_received = 1; + if (options.list_types) { offer_for_each_mime_type(offer, mime_type) { printf("%s\n", mime_type); From d83a629822d83b73ffa0f2a6e1c82b1caab30b8d Mon Sep 17 00:00:00 2001 From: Nathan Ringo Date: Sun, 8 Jan 2023 18:36:26 -0600 Subject: [PATCH 074/139] Change wait() calls to waitpid() This is a more correct thing to do, considering that there could potentially be other child processes that wl-copy itself does not know about, due to the current process spawning them before exec'ing the wl-copy executable. In particular, it makes some sense to spawn a process that produces clipboard contents (piped to stdin of wl-copy) as a child of the process that becomes wl-copy. Fixes #156 --- src/types/copy-action.c | 2 +- src/util/files.c | 4 ++-- src/wl-paste.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/copy-action.c b/src/types/copy-action.c index f661fc3..8117fca 100644 --- a/src/types/copy-action.c +++ b/src/types/copy-action.c @@ -96,7 +96,7 @@ static void do_send(struct source *source, const char *mime_type, int fd) { * should switch to an asynchronous child waiting scheme * instead. */ - wait(NULL); + waitpid(pid, NULL, 0); } else { /* We'll perform the copy ourselves */ FILE *f = fdopen(fd, "w"); diff --git a/src/util/files.c b/src/util/files.c index 750aa0b..69812fd 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -136,7 +136,7 @@ char *infer_mime_type_from_contents(const char *file_path) { close(pipefd[1]); int wstatus; - wait(&wstatus); + waitpid(pid, &wstatus, 0); /* See if that worked */ if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { @@ -243,7 +243,7 @@ char *dump_stdin_into_a_temp_file() { } int wstatus; - wait(&wstatus); + waitpid(pid, &wstatus, 0); if (original_path != NULL) { free(original_path); } diff --git a/src/wl-paste.c b/src/wl-paste.c index 5071782..e384216 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -276,7 +276,7 @@ static void selection_callback(struct offer *offer, int primary) { } close(pipefd[0]); close(pipefd[1]); - wait(NULL); + waitpid(pid, NULL, 0); if (!options.no_newline && !options.watch) { rc = write(STDOUT_FILENO, "\n", 1); if (rc != 1) { From 6c77782829906db4fde35a68ef5c1539d6cfd59f Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 15:46:13 +0300 Subject: [PATCH 075/139] Fix comment formatting --- src/wl-copy.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/wl-copy.c b/src/wl-copy.c index f08cf17..6b1296a 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -84,7 +84,8 @@ static void did_set_selection_callback(struct copy_action *copy_action) { static void cleanup_and_exit(struct copy_action *copy_action, int code) { /* We're done copying! * All that's left to do now is to - * clean up after ourselves and exit.*/ + * clean up after ourselves and exit. + */ char *temp_file = (char *) copy_action->file_to_copy; if (temp_file != NULL) { /* Clean up our temporary file */ From 3b8c9a091a43840e6aa1a844c6859c823e137e26 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 15:46:29 +0300 Subject: [PATCH 076/139] Ignore SIGPIPE in wl-copy Fixes wl-copy dying under KDE. --- src/wl-copy.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/wl-copy.c b/src/wl-copy.c index 6b1296a..b2ba104 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -200,6 +200,14 @@ static void parse_options(int argc, argv_t argv) { int main(int argc, argv_t argv) { parse_options(argc, argv); + /* Ignore SIGPIPE. + * We don't really output anything + * to our stdout, yet we don't want + * to get killed when writing clipboard + * contents to a closed pipe. + */ + signal(SIGPIPE, SIG_IGN); + struct wl_display *wl_display = wl_display_connect(NULL); if (wl_display == NULL) { bail("Failed to connect to a Wayland server"); From 86eacb7cd74e88a839296b3b83bad2d954bea840 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 15:54:10 +0300 Subject: [PATCH 077/139] Prevent reentrancy in wl-paste --watch It could happen that new offer comes in while we're in the middle of trying to receive the previous one. This leads to issues and hangs, since the logic is not prepared to handle reentrancy at all. Commit 89cd9f9e430a6cd8b82fa65a9c05e530e2430ffd has prevented this from happening by immediately rejecting all but the very first offer, unless the --watch mode is used. This makes sense, because unless the --watch mode is used, we are indeed only interested in pasting once. But this issue still manifested itself in the --watch mode. Fix that one too, by not using wl_display_roundtrip() and thus not dispatching any events in the middle of processing an offer. Related to https://github.com/bugaevc/wl-clipboard/pull/133 Fixes https://github.com/bugaevc/wl-clipboard/issues/137 --- src/wl-paste.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/wl-paste.c b/src/wl-paste.c index e384216..d1c84eb 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -173,7 +173,10 @@ static const char *mime_type_to_request(struct types types) { #undef try_any static void selection_callback(struct offer *offer, int primary) { - /* Ignore all but the first non-NULL offer */ + /* Ignore all but the first non-NULL offer. + * This could happen due to reentrancy, though + * we try to prevent it in other ways. + */ if (offer_received && !options.watch) { return; } @@ -238,7 +241,12 @@ static void selection_callback(struct offer *offer, int primary) { popup_surface_destroy(popup_surface); popup_surface = NULL; } - wl_display_roundtrip(wl_display); + /* Make sure the receive request reaches + * the compositor before we block on reading. + * We call flush() instead of dispatch() to + * prevent reentrancy. + */ + wl_display_flush(wl_display); /* Spawn a cat to perform the copy. * If watch mode is active, we spawn From 065d6da7d22a51c6c66e7280daaec60d9647c322 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 16:05:26 +0300 Subject: [PATCH 078/139] Update copyright years Let's use a range of years instead of a single year. --- data/wl-clipboard.1 | 4 ++-- src/includes/selection-protocols.h | 2 +- src/includes/shell-protocols.h | 2 +- src/types/copy-action.c | 2 +- src/types/copy-action.h | 2 +- src/types/device-manager.c | 2 +- src/types/device-manager.h | 2 +- src/types/device.c | 2 +- src/types/device.h | 2 +- src/types/keyboard.c | 2 +- src/types/keyboard.h | 2 +- src/types/offer.c | 2 +- src/types/offer.h | 2 +- src/types/popup-surface.c | 2 +- src/types/popup-surface.h | 2 +- src/types/registry.c | 2 +- src/types/registry.h | 2 +- src/types/seat.c | 2 +- src/types/seat.h | 2 +- src/types/shell-surface.c | 2 +- src/types/shell-surface.h | 2 +- src/types/shell.c | 2 +- src/types/shell.h | 2 +- src/types/source.c | 2 +- src/types/source.h | 2 +- src/util/files.c | 2 +- src/util/files.h | 2 +- src/util/misc.c | 4 ++-- src/util/misc.h | 2 +- src/util/string.c | 2 +- src/util/string.h | 2 +- src/wl-copy.c | 2 +- src/wl-paste.c | 2 +- 33 files changed, 35 insertions(+), 35 deletions(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index 93feed9..19e2e11 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -1,4 +1,4 @@ -.TH WL-CLIPBOARD 1 2019-09-16 wl-clipboard +.TH WL-CLIPBOARD 1 2023-03-04 wl-clipboard .SH NAME wl-clipboard \- Wayland copy and paste command line utilities .SH SYNOPSIS @@ -136,7 +136,7 @@ Written by Sergey Bugaev. .SH REPORTING BUGS Report wl-clipboard bugs to .SH COPYRIGHT -Copyright \(co 2019 Sergey Bugaev. +Copyright \(co 2018-2023 Sergey Bugaev. License GPLv3+: GNU GPL version 3 or later . .br This is free software: you are free to change and redistribute it. diff --git a/src/includes/selection-protocols.h b/src/includes/selection-protocols.h index 32e74a7..768cdf1 100644 --- a/src/includes/selection-protocols.h +++ b/src/includes/selection-protocols.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/includes/shell-protocols.h b/src/includes/shell-protocols.h index feb39f5..98ad3e7 100644 --- a/src/includes/shell-protocols.h +++ b/src/includes/shell-protocols.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/copy-action.c b/src/types/copy-action.c index 8117fca..4b6a760 100644 --- a/src/types/copy-action.c +++ b/src/types/copy-action.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/copy-action.h b/src/types/copy-action.h index c0a1290..d7d3422 100644 --- a/src/types/copy-action.h +++ b/src/types/copy-action.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device-manager.c b/src/types/device-manager.c index bd6e04e..5e88189 100644 --- a/src/types/device-manager.c +++ b/src/types/device-manager.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device-manager.h b/src/types/device-manager.h index bb7e88d..f6de074 100644 --- a/src/types/device-manager.h +++ b/src/types/device-manager.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device.c b/src/types/device.c index 16525a6..c205b60 100644 --- a/src/types/device.c +++ b/src/types/device.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device.h b/src/types/device.h index 5f5e3fe..041a9e8 100644 --- a/src/types/device.h +++ b/src/types/device.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/keyboard.c b/src/types/keyboard.c index 99ca793..6090b5f 100644 --- a/src/types/keyboard.c +++ b/src/types/keyboard.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/keyboard.h b/src/types/keyboard.h index e5f02d0..fe0b27f 100644 --- a/src/types/keyboard.h +++ b/src/types/keyboard.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/offer.c b/src/types/offer.c index c21a562..f5be19b 100644 --- a/src/types/offer.c +++ b/src/types/offer.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/offer.h b/src/types/offer.h index 21dd567..a12d786 100644 --- a/src/types/offer.h +++ b/src/types/offer.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index 1aad115..4ea8347 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/popup-surface.h b/src/types/popup-surface.h index 8847519..7683483 100644 --- a/src/types/popup-surface.h +++ b/src/types/popup-surface.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/registry.c b/src/types/registry.c index 67c7fde..5b547b2 100644 --- a/src/types/registry.c +++ b/src/types/registry.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/registry.h b/src/types/registry.h index e52e427..0564307 100644 --- a/src/types/registry.h +++ b/src/types/registry.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/seat.c b/src/types/seat.c index 6e48450..59e7df8 100644 --- a/src/types/seat.c +++ b/src/types/seat.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/seat.h b/src/types/seat.h index cd92073..8ffade7 100644 --- a/src/types/seat.h +++ b/src/types/seat.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell-surface.c b/src/types/shell-surface.c index ba7e61b..1940d4d 100644 --- a/src/types/shell-surface.c +++ b/src/types/shell-surface.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell-surface.h b/src/types/shell-surface.h index d888553..f8cef96 100644 --- a/src/types/shell-surface.h +++ b/src/types/shell-surface.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell.c b/src/types/shell.c index 36d192a..12ef997 100644 --- a/src/types/shell.c +++ b/src/types/shell.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell.h b/src/types/shell.h index 9b866eb..99369ce 100644 --- a/src/types/shell.h +++ b/src/types/shell.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/source.c b/src/types/source.c index a59110d..45b0f14 100644 --- a/src/types/source.c +++ b/src/types/source.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/source.h b/src/types/source.h index b471fe7..a0cd28b 100644 --- a/src/types/source.h +++ b/src/types/source.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/files.c b/src/util/files.c index 69812fd..bc2b06b 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/files.h b/src/util/files.h index 2a2d4b6..b34f852 100644 --- a/src/util/files.h +++ b/src/util/files.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/misc.c b/src/util/misc.c index e9f7671..132474d 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ void print_version_info() { printf( "wl-clipboard " PROJECT_VERSION "\n" - "Copyright (C) 2019 Sergey Bugaev\n" + "Copyright (C) 2018-2023 Sergey Bugaev\n" "License GPLv3+: GNU GPL version 3 or later" " .\n" "This is free software: you are free to change and redistribute it.\n" diff --git a/src/util/misc.h b/src/util/misc.h index 02c9984..f491e6b 100644 --- a/src/util/misc.h +++ b/src/util/misc.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/string.c b/src/util/string.c index f39db31..b4c6411 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/string.h b/src/util/string.h index 239cf58..1477944 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/wl-copy.c b/src/wl-copy.c index b2ba104..70ac45c 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/wl-paste.c b/src/wl-paste.c index d1c84eb..2b6d0af 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2019 Sergey Bugaev + * Copyright © 2018-2023 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From dbaa6848539061c01db47d8f5665457cfcff5e64 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 20:18:51 +0300 Subject: [PATCH 079/139] Unlink the temp file sooner We only need the temp file to be present on the file system to run xdg-mime on it. Once we have done that, we can unlink it and simply use an fd to refer to it, seeking to the beginning each time. This makes us more robust against wl-copy crashing (or getting killed), in which case the file would be kept on the file system; this should no longer be the case with this patch. Related to https://github.com/bugaevc/wl-clipboard/pull/155 --- src/types/copy-action.c | 11 ++++++++-- src/types/copy-action.h | 3 ++- src/wl-copy.c | 45 +++++++++++++++++++++++------------------ 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/types/copy-action.c b/src/types/copy-action.c index 4b6a760..b3f8829 100644 --- a/src/types/copy-action.c +++ b/src/types/copy-action.c @@ -71,7 +71,7 @@ static void do_send(struct source *source, const char *mime_type, int fd) { /* Unset O_NONBLOCK */ fcntl(fd, F_SETFL, 0); - if (self->file_to_copy != NULL) { + if (self->fd_to_copy_from != -1) { /* Copy the file to the given file descriptor * by spawning an appropriate cat process. */ @@ -82,9 +82,11 @@ static void do_send(struct source *source, const char *mime_type, int fd) { return; } if (pid == 0) { + dup2(self->fd_to_copy_from, STDIN_FILENO); + close(self->fd_to_copy_from); dup2(fd, STDOUT_FILENO); close(fd); - execlp("cat", "cat", self->file_to_copy, NULL); + execlp("cat", "cat", NULL); perror("exec cat"); exit(1); } @@ -97,6 +99,11 @@ static void do_send(struct source *source, const char *mime_type, int fd) { * instead. */ waitpid(pid, NULL, 0); + /* Seek back to the beginning of the file */ + off_t rc = lseek(self->fd_to_copy_from, 0, SEEK_SET); + if (rc < 0) { + perror("lseek"); + } } else { /* We'll perform the copy ourselves */ FILE *f = fdopen(fd, "w"); diff --git a/src/types/copy-action.h b/src/types/copy-action.h index d7d3422..babe179 100644 --- a/src/types/copy-action.h +++ b/src/types/copy-action.h @@ -40,8 +40,9 @@ struct copy_action { /* Exactly one of these fields must be non-null if the source * is non-null, otherwise all these fields must be null. + * The null value for fd_to_copy_from is -1. */ - const char *file_to_copy; + int fd_to_copy_from; argv_t argv_to_copy; struct { const char *ptr; diff --git a/src/wl-copy.c b/src/wl-copy.c index 70ac45c..a9813da 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -81,29 +81,13 @@ static void did_set_selection_callback(struct copy_action *copy_action) { } } -static void cleanup_and_exit(struct copy_action *copy_action, int code) { - /* We're done copying! - * All that's left to do now is to - * clean up after ourselves and exit. - */ - char *temp_file = (char *) copy_action->file_to_copy; - if (temp_file != NULL) { - /* Clean up our temporary file */ - execlp("rm", "rm", "-r", dirname(temp_file), NULL); - perror("exec rm"); - exit(1); - } else { - exit(code); - } -} - static void cancelled_callback(struct copy_action *copy_action) { - cleanup_and_exit(copy_action, 0); + exit(0); } static void pasted_callback(struct copy_action *copy_action) { if (options.paste_once) { - cleanup_and_exit(copy_action, 0); + exit(0); } } @@ -244,6 +228,7 @@ int main(int argc, argv_t argv) { /* Create and initialize the copy action */ struct copy_action *copy_action = calloc(1, sizeof(struct copy_action)); + copy_action->fd_to_copy_from = -1; copy_action->device = device; copy_action->primary = options.primary; @@ -266,7 +251,28 @@ int main(int argc, argv_t argv) { if (options.mime_type == NULL) { options.mime_type = infer_mime_type_from_contents(temp_file); } - copy_action->file_to_copy = temp_file; + copy_action->fd_to_copy_from = open( + temp_file, + O_RDONLY | O_CLOEXEC + ); + if (copy_action->fd_to_copy_from < 0) { + perror("Failed to open temp file"); + return 1; + } + /* Now, remove the temp file and its + * containing directory. We still keep + * access to the file through our open + * file descriptor. + */ + int rc = unlink(temp_file); + if (rc < 0) { + perror("Failed to unlink temp file"); + } + rc = rmdir(dirname(temp_file)); + if (rc < 0) { + perror("Failed to remove temp file directory"); + } + free(temp_file); } /* Create the source */ @@ -300,6 +306,5 @@ int main(int argc, argv_t argv) { while (wl_display_dispatch(wl_display) >= 0); perror("wl_display_dispatch"); - cleanup_and_exit(copy_action, 1); return 1; } From 7463d266cc85fe502d959d6de3674e50ec9e3d92 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 23:12:56 +0300 Subject: [PATCH 080/139] Hardcode a couple more textual formats While this approach obviously doesn't scale and we should probably switch to a better one, adding these two lines is easy and it scratches the particular itch. Closes https://github.com/bugaevc/wl-clipboard/issues/73 --- src/util/string.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/util/string.c b/src/util/string.c index b4c6411..bfea1de 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -36,7 +36,9 @@ int mime_type_is_text(const char *mime_type) { = strstr(mime_type, "json") != NULL || str_has_suffix(mime_type, "script") || str_has_suffix(mime_type, "xml") - || str_has_suffix(mime_type, "yaml"); + || str_has_suffix(mime_type, "yaml") + || str_has_suffix(mime_type, "csv") + || str_has_suffix(mime_type, "ini"); /* Special-case PGP and SSH keys. * A public SSH key is typically stored From 4e678efa05b2fb13576c86f3d9e4d9592394edb6 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 22:11:54 +0300 Subject: [PATCH 081/139] Extract running paste command into a function This makes things a tiny bit cleaer, and will be useful for additionally running the paste command when the offer is NULL. --- src/wl-paste.c | 67 ++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/src/wl-paste.c b/src/wl-paste.c index 2b6d0af..b26822c 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -172,6 +172,39 @@ static const char *mime_type_to_request(struct types types) { #undef try_any_text #undef try_any +static int run_paste_command(int stdin_fd) { + /* Spawn a cat to perform the copy. + * If watch mode is active, we spawn + * a custom command instead. + */ + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + close(stdin_fd); + return 0; + } + if (pid == 0) { + dup2(stdin_fd, STDIN_FILENO); + close(stdin_fd); + if (options.watch) { + execvp(options.watch_command[0], options.watch_command); + fprintf( + stderr, + "Failed to spawn %s: %s", + options.watch_command[0], + strerror(errno) + ); + } else { + execlp("cat", "cat", NULL); + perror("exec cat"); + } + exit(1); + } + close(stdin_fd); + waitpid(pid, NULL, 0); + return 1; +} + static void selection_callback(struct offer *offer, int primary) { /* Ignore all but the first non-NULL offer. * This could happen due to reentrancy, though @@ -248,43 +281,17 @@ static void selection_callback(struct offer *offer, int primary) { */ wl_display_flush(wl_display); - /* Spawn a cat to perform the copy. - * If watch mode is active, we spawn - * a custom command instead. - */ - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); + close(pipefd[1]); + rc = run_paste_command(pipefd[0]); + if (!rc) { if (options.watch) { /* Try to cope without exiting completely */ - close(pipefd[0]); - close(pipefd[1]); offer_destroy(offer); return; } exit(1); } - if (pid == 0) { - dup2(pipefd[0], STDIN_FILENO); - close(pipefd[0]); - close(pipefd[1]); - if (options.watch) { - execvp(options.watch_command[0], options.watch_command); - fprintf( - stderr, - "Failed to spawn %s: %s", - options.watch_command[0], - strerror(errno) - ); - } else { - execlp("cat", "cat", NULL); - perror("exec cat"); - } - exit(1); - } - close(pipefd[0]); - close(pipefd[1]); - waitpid(pid, NULL, 0); + if (!options.no_newline && !options.watch) { rc = write(STDOUT_FILENO, "\n", 1); if (rc != 1) { From 56db369a3f418a3ff28b3e6fe1fd545a0f89673e Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 22:20:13 +0300 Subject: [PATCH 082/139] Set CLIPBOARD_STATE=data for the watch command This begins implementing the CLIPBOARD_STATE clipboard protocol outlined in https://github.com/bugaevc/wl-clipboard/pull/93, and already implemented by the SerenityOS clipboard tools. --- src/wl-paste.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/wl-paste.c b/src/wl-paste.c index b26822c..37dfb51 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -172,7 +172,7 @@ static const char *mime_type_to_request(struct types types) { #undef try_any_text #undef try_any -static int run_paste_command(int stdin_fd) { +static int run_paste_command(int stdin_fd, const char *clipboard_state) { /* Spawn a cat to perform the copy. * If watch mode is active, we spawn * a custom command instead. @@ -187,6 +187,9 @@ static int run_paste_command(int stdin_fd) { dup2(stdin_fd, STDIN_FILENO); close(stdin_fd); if (options.watch) { + if (clipboard_state != NULL) { + setenv("CLIPBOARD_STATE", clipboard_state, 1); + } execvp(options.watch_command[0], options.watch_command); fprintf( stderr, @@ -282,7 +285,7 @@ static void selection_callback(struct offer *offer, int primary) { wl_display_flush(wl_display); close(pipefd[1]); - rc = run_paste_command(pipefd[0]); + rc = run_paste_command(pipefd[0], "data"); if (!rc) { if (options.watch) { /* Try to cope without exiting completely */ From d13ee70029966e9460949c74af07b713ae4737a7 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 22:28:16 +0300 Subject: [PATCH 083/139] Run watch command on empty clipboard too Connect its stdin to /dev/null, and set CLIPBOARD_STATE=nil. Closes https://github.com/bugaevc/wl-clipboard/pull/93 Closes https://github.com/bugaevc/wl-clipboard/issues/109 --- src/wl-paste.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/wl-paste.c b/src/wl-paste.c index 37dfb51..7517d8a 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -226,10 +227,16 @@ static void selection_callback(struct offer *offer, int primary) { } if (offer == NULL) { - if (options.watch) { + if (!options.watch) { + bail("No selection"); + } + int devnull = open("/dev/null", O_RDONLY | O_CLOEXEC); + if (devnull < 0) { + perror("open /dev/null"); return; } - bail("No selection"); + run_paste_command(devnull, "nil"); + return; } offer_received = 1; From d1278cd2ff9410c2b7e1065c7d279c7abf8a0cc8 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 23:38:24 +0300 Subject: [PATCH 084/139] Split an overly long line --- src/types/device.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/types/device.c b/src/types/device.c index c205b60..4d81b94 100644 --- a/src/types/device.c +++ b/src/types/device.c @@ -41,7 +41,10 @@ void device_set_selection( /* Macros to reduce implementation boilerplate */ #define SUPPORTS_SELECTION(type, expr) \ -static int device_supports_selection_on_ ## type(struct device *self, int primary) { \ +static int device_supports_selection_on_ ## type( \ + struct device *self, \ + int primary \ +) { \ return expr; \ } From 1e9f24f11c59fc827ecc577e3d3c8a0826050c28 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 4 Mar 2023 23:38:45 +0300 Subject: [PATCH 085/139] Destroy offers in wl-copy ...so that we don't leak them. Fixes https://github.com/bugaevc/wl-clipboard/issues/69 --- src/wl-copy.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/wl-copy.c b/src/wl-copy.c index a9813da..c556aeb 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -22,6 +22,7 @@ #include "types/device-manager.h" #include "types/registry.h" #include "types/popup-surface.h" +#include "types/offer.h" #include "util/files.h" #include "util/string.h" @@ -91,6 +92,13 @@ static void pasted_callback(struct copy_action *copy_action) { } } +static void selection_callback(struct offer *offer, int primary) { + /* We're not interested */ + if (offer != NULL) { + offer_destroy(offer); + } +} + static void print_usage(FILE *f, const char *argv0) { fprintf( f, @@ -221,6 +229,7 @@ int main(int argc, argv_t argv) { } struct device *device = device_manager_get_device(device_manager, seat); + device->selection_callback = selection_callback; if (!device_supports_selection(device, options.primary)) { complain_about_selection_support(options.primary); From d8e2daf8e243e5549c8073acc3878c7521b64033 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 9 Apr 2023 16:24:46 +0300 Subject: [PATCH 086/139] Diagnose Wayland connection issues When the Wayland connection fails, let's print some useful info that may help the user diagnose why it is failing. For instance, in case of https://github.com/bugaevc/wl-clipboard/issues/114, this would print: Failed to connect to a Wayland server: No such file or directory Note: WAYLAND_DISPLAY is unset (using wayland-0) Note: XDG_RUNTIME_DIR is set to /tmp/ Please check whether /tmp//wayland-0 socket exists and is accessible. --- src/util/misc.c | 35 +++++++++++++++++++++++++++++++++++ src/util/misc.h | 1 + src/wl-copy.c | 2 +- src/wl-paste.c | 2 +- 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/util/misc.c b/src/util/misc.c index 132474d..a26d18b 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -20,6 +20,9 @@ #include "util/misc.h" #include +#include +#include +#include void print_version_info() { printf( @@ -57,3 +60,35 @@ void complain_about_watch_mode_support() { ); #endif } + +void complain_about_wayland_connection() { + int saved_errno = errno; + fprintf(stderr, "Failed to connect to a Wayland server"); + if (saved_errno != 0) { + fprintf(stderr, ": %s", strerror(saved_errno)); + } + fputc('\n', stderr); + + const char *display = getenv("WAYLAND_DISPLAY"); + const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); + if (display != NULL) { + fprintf(stderr, "Note: WAYLAND_DISPLAY is set to %s\n", display); + } else { + fprintf(stderr, "Note: WAYLAND_DISPLAY is unset (using wayland-0)\n"); + display = "wayland-0"; + } + if (runtime_dir != NULL) { + fprintf(stderr, "Note: XDG_RUNTIME_DIR is set to %s\n", runtime_dir); + } else { + fprintf(stderr, "Note: XDG_RUNTIME_DIR is unset\n"); + } + if (display[0] != '/' && runtime_dir != NULL) { + fprintf( + stderr, + "Please check whether %s/%s socket exists and is accessible.\n", + runtime_dir, + display + ); + } + exit(1); +} diff --git a/src/util/misc.h b/src/util/misc.h index f491e6b..e79dcde 100644 --- a/src/util/misc.h +++ b/src/util/misc.h @@ -28,5 +28,6 @@ void print_version_info(void); void complain_about_selection_support(int primary); void complain_about_watch_mode_support(void); +void complain_about_wayland_connection(void); #endif /* UTIL_MISC_H */ diff --git a/src/wl-copy.c b/src/wl-copy.c index c556aeb..2c16df3 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -202,7 +202,7 @@ int main(int argc, argv_t argv) { struct wl_display *wl_display = wl_display_connect(NULL); if (wl_display == NULL) { - bail("Failed to connect to a Wayland server"); + complain_about_wayland_connection(); } struct registry *registry = calloc(1, sizeof(struct registry)); diff --git a/src/wl-paste.c b/src/wl-paste.c index 7517d8a..d66327f 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -451,7 +451,7 @@ int main(int argc, argv_t argv) { wl_display = wl_display_connect(NULL); if (wl_display == NULL) { - bail("Failed to connect to a Wayland server"); + complain_about_wayland_connection(); } struct registry *registry = calloc(1, sizeof(struct registry)); From 4b2c4b241f9378c64102642d97eb17115ece42be Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 9 Apr 2023 16:52:11 +0300 Subject: [PATCH 087/139] Expand the "No suitable type" message Let's list the inferred type (if any), and hint the user to use '--type' if that's not the type they wanted. Resolves https://github.com/bugaevc/wl-clipboard/issues/166 --- src/wl-paste.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/wl-paste.c b/src/wl-paste.c index d66327f..9b2cacb 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -209,6 +210,26 @@ static int run_paste_command(int stdin_fd, const char *clipboard_state) { return 1; } +static void complain_no_suitable_type() { + fprintf(stderr, "Clipboard content is not available as "); + if (options.explicit_type != NULL) { + fprintf(stderr, "requested type \"%s\"\n", options.explicit_type); + } else { + assert(options.inferred_type); + fprintf( + stderr, + "inferred output type \"%s\"\n", + options.inferred_type + ); + } + fprintf(stderr, "Use \"wl-paste --list-types\" to view available types."); + if (options.explicit_type == NULL) { + fprintf(stderr, " Use \"--type\" to explicitly specify a type."); + } + fputc('\n', stderr); + exit(1); +} + static void selection_callback(struct offer *offer, int primary) { /* Ignore all but the first non-NULL offer. * This could happen due to reentrancy, though @@ -256,7 +277,7 @@ static void selection_callback(struct offer *offer, int primary) { offer_destroy(offer); return; } - bail("No suitable type of content copied"); + complain_no_suitable_type(); } /* Never append a newline character to binary content */ From a3a55ec70ac114c36c64410b4c444ecba8329509 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 9 Apr 2023 17:11:35 +0300 Subject: [PATCH 088/139] Improve various other error messages --- src/types/popup-surface.c | 6 +++--- src/util/misc.c | 21 ++++++++++++++++++++- src/util/misc.h | 2 ++ src/wl-copy.c | 6 +----- src/wl-paste.c | 8 ++------ 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index 4ea8347..bbfca6e 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -42,7 +42,7 @@ static void forward_on_focus( void popup_surface_init(struct popup_surface *self) { self->shell = registry_find_shell(self->registry); if (self->shell == NULL) { - bail("Missing a shell"); + complain_about_missing_global("a Wayland shell"); } self->keyboard = seat_get_keyboard(self->seat); @@ -61,7 +61,7 @@ void popup_surface_init(struct popup_surface *self) { struct wl_compositor *wl_compositor = self->registry->wl_compositor; if (wl_compositor == NULL) { - bail("Missing the compositor"); + complain_about_missing_global("wl_compositor"); } self->wl_surface = wl_compositor_create_surface(wl_compositor); self->shell_surface = shell_create_shell_surface( @@ -102,7 +102,7 @@ void popup_surface_init(struct popup_surface *self) { /* Create a shared memory pool */ struct wl_shm *wl_shm = self->registry->wl_shm; if (wl_shm == NULL) { - bail("Missing the shm"); + complain_about_missing_global("wl_shm"); } struct wl_shm_pool *wl_shm_pool = wl_shm_create_pool(wl_shm, fd, size); diff --git a/src/util/misc.c b/src/util/misc.c index a26d18b..7a1a5a7 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -38,7 +38,7 @@ void print_version_info() { void complain_about_selection_support(int primary) { if (!primary) { /* We always expect to find at least wl_data_device_manager */ - bail("Missing a required global object"); + complain_about_missing_global("wl_data_device_manager"); } #if !defined(HAVE_WP_PRIMARY_SELECTION) && !defined(HAVE_GTK_PRIMARY_SELECTION) @@ -92,3 +92,22 @@ void complain_about_wayland_connection() { } exit(1); } + +void complain_about_missing_seat(const char *seat_name) { + if (seat_name != NULL) { + fprintf(stderr, "No such seat: %s\n", seat_name); + } else { + complain_about_missing_global("seat"); + } + exit(1); +} + +void complain_about_missing_global(const char *global) { + fprintf( + stderr, + "The compositor does not seem to implement %s," + " which is required for wl-clipboard to work\n", + global + ); + exit(1); +} diff --git a/src/util/misc.h b/src/util/misc.h index e79dcde..f743bf6 100644 --- a/src/util/misc.h +++ b/src/util/misc.h @@ -29,5 +29,7 @@ void print_version_info(void); void complain_about_selection_support(int primary); void complain_about_watch_mode_support(void); void complain_about_wayland_connection(void); +void complain_about_missing_seat(const char *seat_name); +void complain_about_missing_global(const char *global); #endif /* UTIL_MISC_H */ diff --git a/src/wl-copy.c b/src/wl-copy.c index 2c16df3..d911875 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -214,11 +214,7 @@ int main(int argc, argv_t argv) { struct seat *seat = registry_find_seat(registry, options.seat_name); if (seat == NULL) { - if (options.seat_name != NULL) { - bail("No such seat"); - } else { - bail("Missing a seat"); - } + complain_about_missing_seat(options.seat_name); } /* Create the device */ diff --git a/src/wl-paste.c b/src/wl-paste.c index 9b2cacb..b608ea9 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -249,7 +249,7 @@ static void selection_callback(struct offer *offer, int primary) { if (offer == NULL) { if (!options.watch) { - bail("No selection"); + bail("Nothing is copied"); } int devnull = open("/dev/null", O_RDONLY | O_CLOEXEC); if (devnull < 0) { @@ -484,11 +484,7 @@ int main(int argc, argv_t argv) { struct seat *seat = registry_find_seat(registry, options.seat_name); if (seat == NULL) { - if (options.seat_name != NULL) { - bail("No such seat"); - } else { - bail("Missing a seat"); - } + complain_about_missing_seat(options.seat_name); } /* Create the device */ From 06731b63bfc72be5a017c113d2c62d1c86e19f39 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 9 Apr 2023 17:30:54 +0300 Subject: [PATCH 089/139] Factor out protocols-specific part of meson.build --- src/meson.build | 78 ++-------------------------------------- src/protocol/meson.build | 75 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 76 deletions(-) create mode 100644 src/protocol/meson.build diff --git a/src/meson.build b/src/meson.build index 45c2f67..37a638c 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,92 +1,18 @@ wayland = dependency('wayland-client') -wayland_scanner = find_program('wayland-scanner', required: false, native: true) -wayland_protocols = dependency('wayland-protocols', version: '>= 1.12', required: false) - -if wayland_scanner.found() - have_xdg_shell = wayland_protocols.found() - have_wp_primary_selection = wayland_protocols.found() and wayland_protocols.version().version_compare('>= 1.17') - # these are bundled - have_gtk_primary_selection = true - have_wlr_data_control = true - - if wayland.version().version_compare('>= 1.15') - scanner_code_command = 'private-code' - else - scanner_code_command = 'code' - endif -else - have_xdg_shell = false - have_gtk_primary_selection = false - have_wlr_data_control = false -endif - cc = meson.get_compiler('c') have_memfd = cc.has_header_symbol('sys/syscall.h', 'SYS_memfd_create') have_shm_anon = cc.has_header_symbol('sys/mman.h', 'SHM_ANON') conf_data = configuration_data() -conf_data.set('PROJECT_VERSION', '"@0@"'.format(meson.project_version())) -conf_data.set('HAVE_XDG_SHELL', have_xdg_shell) -conf_data.set('HAVE_WP_PRIMARY_SELECTION', have_wp_primary_selection) -conf_data.set('HAVE_GTK_PRIMARY_SELECTION', have_gtk_primary_selection) -conf_data.set('HAVE_WLR_DATA_CONTROL', have_wlr_data_control) - conf_data.set('HAVE_MEMFD', have_memfd) conf_data.set('HAVE_SHM_ANON', have_shm_anon) +subdir('protocol') + configure_file(output: 'config.h', configuration: conf_data) -if wayland_protocols.found() - protocols_path = wayland_protocols.get_pkgconfig_variable('pkgdatadir') -endif - -protocols = [] - -if not have_xdg_shell - warning('Building without xdg-shell support') -else - xdg_shell_xml = join_paths(protocols_path, 'stable', 'xdg-shell', 'xdg-shell.xml') - protocols += [['xdg-shell', xdg_shell_xml]] -endif - -if have_wp_primary_selection - wp_primary_selection_xml = join_paths(protocols_path, 'unstable', 'primary-selection', 'primary-selection-unstable-v1.xml') - protocols += [['wp-primary-selection', wp_primary_selection_xml]] -endif - -if not have_gtk_primary_selection - warning('Building without primary selection support') -else - gtk_primary_selection_xml = join_paths('protocol', 'gtk-primary-selection.xml') - protocols += [['gtk-primary-selection', gtk_primary_selection_xml]] -endif - -if have_wlr_data_control - wlr_data_control_xml = join_paths('protocol', 'wlr-data-control-unstable-v1.xml') - protocols += [['wlr-data-control', wlr_data_control_xml]] -endif - -protocol_deps = [] -protocol_headers = [] - -foreach protocol : protocols - name = protocol[0] - xml = protocol[1] - header = custom_target(name + ' client header', - input: xml, output: name + '.h', - command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'] - ) - impl = custom_target(name + ' code', - input: xml, output: name + '.c', - command: [wayland_scanner, scanner_code_command, '@INPUT@', '@OUTPUT@'] - ) - protocol_headers += header - lib = static_library(name, impl, header, dependencies: wayland) - protocol_deps += lib -endforeach - lib = static_library( 'wl-clipboard', [ diff --git a/src/protocol/meson.build b/src/protocol/meson.build new file mode 100644 index 0000000..4e9c2c3 --- /dev/null +++ b/src/protocol/meson.build @@ -0,0 +1,75 @@ +wayland_scanner = find_program('wayland-scanner', required: false, native: true) +wayland_protocols = dependency('wayland-protocols', version: '>= 1.12', required: false) + +if wayland_scanner.found() + have_xdg_shell = wayland_protocols.found() + have_wp_primary_selection = wayland_protocols.found() and wayland_protocols.version().version_compare('>= 1.17') + # these are bundled + have_gtk_primary_selection = true + have_wlr_data_control = true + + if wayland.version().version_compare('>= 1.15') + scanner_code_command = 'private-code' + else + scanner_code_command = 'code' + endif +else + have_xdg_shell = false + have_gtk_primary_selection = false + have_wlr_data_control = false +endif + +conf_data.set('PROJECT_VERSION', '"@0@"'.format(meson.project_version())) +conf_data.set('HAVE_XDG_SHELL', have_xdg_shell) +conf_data.set('HAVE_WP_PRIMARY_SELECTION', have_wp_primary_selection) +conf_data.set('HAVE_GTK_PRIMARY_SELECTION', have_gtk_primary_selection) +conf_data.set('HAVE_WLR_DATA_CONTROL', have_wlr_data_control) + +if wayland_protocols.found() + protocols_path = wayland_protocols.get_pkgconfig_variable('pkgdatadir') +endif + +protocols = [] + +if not have_xdg_shell + warning('Building without xdg-shell support') +else + xdg_shell_xml = join_paths(protocols_path, 'stable', 'xdg-shell', 'xdg-shell.xml') + protocols += [['xdg-shell', xdg_shell_xml]] +endif + +if have_wp_primary_selection + wp_primary_selection_xml = join_paths(protocols_path, 'unstable', 'primary-selection', 'primary-selection-unstable-v1.xml') + protocols += [['wp-primary-selection', wp_primary_selection_xml]] +endif + +if not have_gtk_primary_selection + warning('Building without primary selection support') +else + gtk_primary_selection_xml = 'gtk-primary-selection.xml' + protocols += [['gtk-primary-selection', gtk_primary_selection_xml]] +endif + +if have_wlr_data_control + wlr_data_control_xml = 'wlr-data-control-unstable-v1.xml' + protocols += [['wlr-data-control', wlr_data_control_xml]] +endif + +protocol_deps = [] +protocol_headers = [] + +foreach protocol : protocols + name = protocol[0] + xml = protocol[1] + header = custom_target(name + ' client header', + input: xml, output: name + '.h', + command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'] + ) + impl = custom_target(name + ' code', + input: xml, output: name + '.c', + command: [wayland_scanner, scanner_code_command, '@INPUT@', '@OUTPUT@'] + ) + protocol_headers += header + lib = static_library(name, impl, header, dependencies: wayland) + protocol_deps += lib +endforeach From 01ad5cd5244d47d8f7d5983f24a2b06bd564768c Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 9 Apr 2023 17:38:23 +0300 Subject: [PATCH 090/139] Fix missing have_wp_primary_selection --- src/protocol/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/src/protocol/meson.build b/src/protocol/meson.build index 4e9c2c3..f291176 100644 --- a/src/protocol/meson.build +++ b/src/protocol/meson.build @@ -15,6 +15,7 @@ if wayland_scanner.found() endif else have_xdg_shell = false + have_wp_primary_selection = false have_gtk_primary_selection = false have_wlr_data_control = false endif From 7c0802dd25ff6e91300c312fe1e5f6660880dcdd Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 9 Apr 2023 17:50:17 +0300 Subject: [PATCH 091/139] Silence a Meson deprecation warning --- completions/fish/meson.build | 6 +++++- src/protocol/meson.build | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/completions/fish/meson.build b/completions/fish/meson.build index b0bc411..cb1280c 100644 --- a/completions/fish/meson.build +++ b/completions/fish/meson.build @@ -2,7 +2,11 @@ fish_completion_dir = get_option('fishcompletiondir') fish = dependency('fish', required : false) if fish_completion_dir == '' and fish.found() - fish_completion_dir = fish.get_pkgconfig_variable('completionsdir') + if meson.version().version_compare('>= 0.58') + fish_completion_dir = fish.get_variable('completionsdir') + else + fish_completion_dir = fish.get_pkgconfig_variable('completionsdir') + endif elif fish_completion_dir == '' # Fish does not look in /usr/local/, which is the default prefix, # so we cannot use get_option("datadir"). Instead, they recommend diff --git a/src/protocol/meson.build b/src/protocol/meson.build index f291176..01480ec 100644 --- a/src/protocol/meson.build +++ b/src/protocol/meson.build @@ -27,7 +27,11 @@ conf_data.set('HAVE_GTK_PRIMARY_SELECTION', have_gtk_primary_selection) conf_data.set('HAVE_WLR_DATA_CONTROL', have_wlr_data_control) if wayland_protocols.found() - protocols_path = wayland_protocols.get_pkgconfig_variable('pkgdatadir') + if meson.version().version_compare('>= 0.58') + protocols_path = wayland_protocols.get_variable('pkgdatadir') + else + protocols_path = wayland_protocols.get_pkgconfig_variable('pkgdatadir') + endif endif protocols = [] From 2de3fbf3d530a99d97d33f787489e28b8e5c1590 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 9 Apr 2023 18:27:05 +0300 Subject: [PATCH 092/139] Implement support for gtk-shell When the compositor supports it, wl-clipboard will use gtk-shell, the semi-private protocol used by GTK, Mutter, and phoc, to ask the compositor to raise the popup surface. https://github.com/bugaevc/wl-clipboard/issues/169 --- src/includes/shell-protocols.h | 4 ++ src/protocol/gtk-shell.xml | 107 +++++++++++++++++++++++++++++++++ src/protocol/meson.build | 9 +++ src/types/popup-surface.c | 29 +++++++++ src/types/popup-surface.h | 6 ++ src/types/registry.c | 4 ++ src/types/registry.h | 3 + 7 files changed, 162 insertions(+) create mode 100644 src/protocol/gtk-shell.xml diff --git a/src/includes/shell-protocols.h b/src/includes/shell-protocols.h index 98ad3e7..5f15a39 100644 --- a/src/includes/shell-protocols.h +++ b/src/includes/shell-protocols.h @@ -27,4 +27,8 @@ # include "xdg-shell.h" #endif +#ifdef HAVE_GTK_SHELL +# include "gtk-shell.h" +#endif + #endif /* INCLUDES_SHELL_PROTOCOLS_H */ diff --git a/src/protocol/gtk-shell.xml b/src/protocol/gtk-shell.xml new file mode 100644 index 0000000..a8d51c6 --- /dev/null +++ b/src/protocol/gtk-shell.xml @@ -0,0 +1,107 @@ + + + + + gtk_shell is a protocol extension providing additional features for + clients implementing it. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/protocol/meson.build b/src/protocol/meson.build index 01480ec..8782fde 100644 --- a/src/protocol/meson.build +++ b/src/protocol/meson.build @@ -7,6 +7,7 @@ if wayland_scanner.found() # these are bundled have_gtk_primary_selection = true have_wlr_data_control = true + have_gtk_shell = true if wayland.version().version_compare('>= 1.15') scanner_code_command = 'private-code' @@ -18,6 +19,7 @@ else have_wp_primary_selection = false have_gtk_primary_selection = false have_wlr_data_control = false + have_gtk_shell = false endif conf_data.set('PROJECT_VERSION', '"@0@"'.format(meson.project_version())) @@ -25,6 +27,7 @@ conf_data.set('HAVE_XDG_SHELL', have_xdg_shell) conf_data.set('HAVE_WP_PRIMARY_SELECTION', have_wp_primary_selection) conf_data.set('HAVE_GTK_PRIMARY_SELECTION', have_gtk_primary_selection) conf_data.set('HAVE_WLR_DATA_CONTROL', have_wlr_data_control) +conf_data.set('HAVE_GTK_SHELL', have_gtk_shell) if wayland_protocols.found() if meson.version().version_compare('>= 0.58') @@ -60,6 +63,12 @@ if have_wlr_data_control protocols += [['wlr-data-control', wlr_data_control_xml]] endif +if have_gtk_shell + gtk_shell_xml = 'gtk-shell.xml' + protocols += [['gtk-shell', gtk_shell_xml]] +endif + + protocol_deps = [] protocol_headers = [] diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index bbfca6e..effd869 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -69,6 +69,15 @@ void popup_surface_init(struct popup_surface *self) { self->wl_surface ); +#ifdef HAVE_GTK_SHELL + if (self->registry->gtk_shell1 != NULL) { + self->gtk_surface = gtk_shell1_get_gtk_surface( + self->registry->gtk_shell1, + self->wl_surface + ); + } +#endif + /* Signal that the surface is ready to be configured */ wl_surface_commit(self->wl_surface); wl_display_roundtrip(self->registry->wl_display); @@ -122,6 +131,14 @@ void popup_surface_init(struct popup_surface *self) { wl_surface_attach(self->wl_surface, wl_buffer, 0, 0); wl_surface_damage(self->wl_surface, 0, 0, width, height); + + /* Ask the compositor nicely to give us focus */ +#ifdef HAVE_GTK_SHELL + if (self->gtk_surface != NULL) { + gtk_surface1_present(self->gtk_surface, 0); + } +#endif + wl_surface_commit(self->wl_surface); } @@ -135,6 +152,18 @@ void popup_surface_destroy(struct popup_surface *self) { self->keyboard->data = NULL; shell_surface_destroy(self->shell_surface); +#ifdef HAVE_GTK_SHELL + if (self->gtk_surface) { + if ( + gtk_surface1_get_version(self->gtk_surface) >= + GTK_SURFACE1_RELEASE_SINCE_VERSION + ) { + gtk_surface1_release(self->gtk_surface); + } else { + gtk_surface1_destroy(self->gtk_surface); + } + } +#endif wl_surface_destroy(self->wl_surface); free(self->shell); diff --git a/src/types/popup-surface.h b/src/types/popup-surface.h index 7683483..d81fe37 100644 --- a/src/types/popup-surface.h +++ b/src/types/popup-surface.h @@ -19,6 +19,8 @@ #ifndef TYPES_POPUP_SURFACE_H #define TYPES_POPUP_SURFACE_H +#include "includes/shell-protocols.h" + #include struct registry; @@ -40,6 +42,10 @@ struct popup_surface { struct wl_surface *wl_surface; struct keyboard *keyboard; int should_free_self; + +#ifdef HAVE_GTK_SHELL + struct gtk_surface1 *gtk_surface; +#endif }; void popup_surface_init(struct popup_surface *self); diff --git a/src/types/registry.c b/src/types/registry.c index 5b547b2..8f8d55d 100644 --- a/src/types/registry.c +++ b/src/types/registry.c @@ -57,6 +57,10 @@ static void wl_registry_global_handler( BIND(xdg_wm_base, 1) #endif +#ifdef HAVE_GTK_SHELL + BIND(gtk_shell1, 4) +#endif + /* Device managers */ BIND(wl_data_device_manager, 1) diff --git a/src/types/registry.h b/src/types/registry.h index 0564307..8f18640 100644 --- a/src/types/registry.h +++ b/src/types/registry.h @@ -46,6 +46,9 @@ struct registry { #ifdef HAVE_XDG_SHELL struct xdg_wm_base *xdg_wm_base; #endif +#ifdef HAVE_GTK_SHELL + struct gtk_shell1 *gtk_shell1; +#endif /* Device managers */ From e541d9a2d04a74f31a69abe53778f9c710fc4cc4 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 9 Apr 2023 19:10:09 +0300 Subject: [PATCH 093/139] Implement support for xdg-activation If someone happens to leave an activation token for wl-clipboard, it will now use it to request its window to be activated. Resolves https://github.com/bugaevc/wl-clipboard/issues/169 This should hopefully help with https://github.com/bugaevc/wl-clipboard/issues/168 --- src/includes/shell-protocols.h | 5 +++++ src/protocol/meson.build | 8 ++++++++ src/types/popup-surface.c | 20 +++++++++++++++++++- src/types/registry.c | 4 ++++ src/types/registry.h | 4 ++++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/includes/shell-protocols.h b/src/includes/shell-protocols.h index 5f15a39..a71bddc 100644 --- a/src/includes/shell-protocols.h +++ b/src/includes/shell-protocols.h @@ -31,4 +31,9 @@ # include "gtk-shell.h" #endif +/* Not strictly speaking a shell */ +#ifdef HAVE_XDG_ACTIVATION +# include "xdg-activation.h" +#endif + #endif /* INCLUDES_SHELL_PROTOCOLS_H */ diff --git a/src/protocol/meson.build b/src/protocol/meson.build index 8782fde..0d1a47e 100644 --- a/src/protocol/meson.build +++ b/src/protocol/meson.build @@ -4,6 +4,7 @@ wayland_protocols = dependency('wayland-protocols', version: '>= 1.12', required if wayland_scanner.found() have_xdg_shell = wayland_protocols.found() have_wp_primary_selection = wayland_protocols.found() and wayland_protocols.version().version_compare('>= 1.17') + have_xdg_activation = wayland_protocols.found() and wayland_protocols.version().version_compare('>= 1.21') # these are bundled have_gtk_primary_selection = true have_wlr_data_control = true @@ -17,6 +18,7 @@ if wayland_scanner.found() else have_xdg_shell = false have_wp_primary_selection = false + have_xdg_activation = false have_gtk_primary_selection = false have_wlr_data_control = false have_gtk_shell = false @@ -27,6 +29,7 @@ conf_data.set('HAVE_XDG_SHELL', have_xdg_shell) conf_data.set('HAVE_WP_PRIMARY_SELECTION', have_wp_primary_selection) conf_data.set('HAVE_GTK_PRIMARY_SELECTION', have_gtk_primary_selection) conf_data.set('HAVE_WLR_DATA_CONTROL', have_wlr_data_control) +conf_data.set('HAVE_XDG_ACTIVATION', have_xdg_activation) conf_data.set('HAVE_GTK_SHELL', have_gtk_shell) if wayland_protocols.found() @@ -63,6 +66,11 @@ if have_wlr_data_control protocols += [['wlr-data-control', wlr_data_control_xml]] endif +if have_xdg_activation + xdg_activation_xml = join_paths(protocols_path, 'staging', 'xdg-activation', 'xdg-activation-v1.xml') + protocols += [['xdg-activation', xdg_activation_xml]] +endif + if have_gtk_shell gtk_shell_xml = 'gtk-shell.xml' protocols += [['gtk-shell', gtk_shell_xml]] diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index effd869..3470be5 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -138,6 +138,24 @@ void popup_surface_init(struct popup_surface *self) { gtk_surface1_present(self->gtk_surface, 0); } #endif +#ifdef HAVE_XDG_ACTIVATION + if (self->registry->xdg_activation_v1 != NULL) { + /* See if someone was kind enough to leave + * some tokens for us in the environment. + */ + const char *token = getenv("XDG_ACTIVATION_TOKEN"); + if (token == NULL) { + token = getenv("DESKTOP_STARTUP_ID"); + } + if (token != NULL) { + xdg_activation_v1_activate( + self->registry->xdg_activation_v1, + token, + self->wl_surface + ); + } + } +#endif wl_surface_commit(self->wl_surface); } @@ -153,7 +171,7 @@ void popup_surface_destroy(struct popup_surface *self) { shell_surface_destroy(self->shell_surface); #ifdef HAVE_GTK_SHELL - if (self->gtk_surface) { + if (self->gtk_surface != NULL) { if ( gtk_surface1_get_version(self->gtk_surface) >= GTK_SURFACE1_RELEASE_SINCE_VERSION diff --git a/src/types/registry.c b/src/types/registry.c index 8f8d55d..b33c4d1 100644 --- a/src/types/registry.c +++ b/src/types/registry.c @@ -49,6 +49,10 @@ static void wl_registry_global_handler( BIND(wl_compositor, 2) BIND(wl_shm, 1) +#ifdef HAVE_XDG_ACTIVATION + BIND(xdg_activation_v1, 1) +#endif + /* Shells */ BIND(wl_shell, 1) diff --git a/src/types/registry.h b/src/types/registry.h index 8f18640..d6c64ce 100644 --- a/src/types/registry.h +++ b/src/types/registry.h @@ -40,6 +40,10 @@ struct registry { struct wl_compositor *wl_compositor; struct wl_shm *wl_shm; +#ifdef HAVE_XDG_ACTIVATION + struct xdg_activation_v1 *xdg_activation_v1; +#endif + /* Shells */ struct wl_shell *wl_shell; From 95df4ef92b08f26116a243d6aca1dc1aaf6843b2 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 9 Apr 2023 19:32:39 +0300 Subject: [PATCH 094/139] Tweak error messages further --- src/types/popup-surface.c | 2 +- src/util/misc.c | 23 ++++++++++++++++++++++- src/util/misc.h | 1 + 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index 3470be5..b145d7f 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -42,7 +42,7 @@ static void forward_on_focus( void popup_surface_init(struct popup_surface *self) { self->shell = registry_find_shell(self->registry); if (self->shell == NULL) { - complain_about_missing_global("a Wayland shell"); + complain_about_missing_shell(); } self->keyboard = seat_get_keyboard(self->seat); diff --git a/src/util/misc.c b/src/util/misc.c index 7a1a5a7..797d2b6 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -74,7 +74,11 @@ void complain_about_wayland_connection() { if (display != NULL) { fprintf(stderr, "Note: WAYLAND_DISPLAY is set to %s\n", display); } else { - fprintf(stderr, "Note: WAYLAND_DISPLAY is unset (using wayland-0)\n"); + fprintf( + stderr, + "Note: WAYLAND_DISPLAY is unset" + " (falling back to wayland-0)\n" + ); display = "wayland-0"; } if (runtime_dir != NULL) { @@ -111,3 +115,20 @@ void complain_about_missing_global(const char *global) { ); exit(1); } + +void complain_about_missing_shell() { + fprintf( + stderr, + "The compositor does not seem to implement a Wayland shell," + " which is required for wl-clipboard to work\n" + ); + +#ifndef HAVE_XDG_SHELL + fprintf( + stderr, + "Note: wl-clipboard was built without xdg-shell support\n" + ); +#endif + + exit(1); +} diff --git a/src/util/misc.h b/src/util/misc.h index f743bf6..1bf81a8 100644 --- a/src/util/misc.h +++ b/src/util/misc.h @@ -31,5 +31,6 @@ void complain_about_watch_mode_support(void); void complain_about_wayland_connection(void); void complain_about_missing_seat(const char *seat_name); void complain_about_missing_global(const char *global); +void complain_about_missing_shell(void); #endif /* UTIL_MISC_H */ From 1270e06366fbf53d18de3ae22402418574931978 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 22 Apr 2023 12:41:25 +0300 Subject: [PATCH 095/139] Fix a reentrancy-related regression It is possible that the compositor gives our popup surface focus before we ever commit a buffer. In that case, we run the rest of our logic immediately, and popup_surface_init() knows not to proceed with creating a buffer. However, that has regressed in commit d66fb03313128544b36419e74578cc3fb8663aef, where we started setting popup_surface to NULL, but still checking for wl_surface being NULL to detect that case. Fix this by also resetting self->wl_surface to NULL. Fixes https://github.com/bugaevc/wl-clipboard/issues/174 --- src/types/popup-surface.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index b145d7f..8af3755 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -183,6 +183,8 @@ void popup_surface_destroy(struct popup_surface *self) { } #endif wl_surface_destroy(self->wl_surface); + /* Let popup_surface_init() know we're already done */ + self->wl_surface = NULL; free(self->shell); if (self->should_free_self) { From 12cb0e5947416690e5209e250807d656ffc9508f Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 22 Apr 2023 21:30:56 +0300 Subject: [PATCH 096/139] Improve the wl-clipboard(1) man page --- data/wl-clipboard.1 | 173 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 133 insertions(+), 40 deletions(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index 19e2e11..b6f7de5 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -1,24 +1,17 @@ -.TH WL-CLIPBOARD 1 2023-03-04 wl-clipboard +.TH WL-CLIPBOARD 1 2023-04-22 wl-clipboard .SH NAME wl-clipboard \- Wayland copy and paste command line utilities .SH SYNOPSIS .B wl-copy -[\fB--primary\fR] -[\fB--trim-newline\fR] -[\fB--paste-once\fR] -[\fB--foreground\fR] -[\fB--clear\fR] -[\fB--type \fImime/type\fR] -[\fB--seat \fIseat-name\fR] +[\fB\-\-primary\fR] +[\fB\-\-type \fImime/type\fR] [\fItext\fR...] .PP .B wl-paste -[\fB--primary\fR] -[\fB--no-newline\fR] -[\fB--list-types\fR] -[\fB--type \fImime/type\fR] -[\fB--seat \fIseat-name\fR] -[\fB--watch \fIcommand\fR...\] +[\fB\-\-primary\fR] +[\fB\-\-type \fImime/type\fR] +.PP +Only the most useful options are listed here; see below for the full list. .SH DESCRIPTION \fBwl-copy\fR copies the given \fItext\fR to the Wayland clipboard. If no \fItext\fR is given, \fBwl-copy\fR copies data from its standard input. @@ -32,36 +25,36 @@ running \fBxdg-mime\fR(1) on it. \fBwl-paste\fR tries its best to pick a type to paste based on the list of offered MIME types and the extension of the file it's pasting into. If you're not satisfied with the type they pick or don't want to rely on this implicit type inference, you can explicitly specify the type to use -with the \fB--type\fR option. +with the \fB\-\-type\fR option. .SH OPTIONS .TP -\fB-p\fR, \fB--primary +\fB\-p\fR, \fB\-\-primary Use the "primary" clipboard instead of the regular clipboard. .TP -\fB-o\fR, \fB--paste-once +\fB\-o\fR, \fB\-\-paste-once\fR (for \fBwl-copy\fR) Only serve one paste request and then exit. Unless a clipboard manager specifically designed to prevent this is in use, this has the effect of clearing the clipboard after the first paste, which is useful for copying sensitive data -such as passwords. Note that this may break pasting into some clients, in -particular pasting into XWayland windows is known to break when this option is -used. +such as passwords. Note that this may break pasting into some clients that +expect to be able to paste multiple times, in particular pasting into XWayland +windows is known to break when this option is used. .TP -\fB-f\fR, \fB--foreground +\fB\-f\fR, \fB\-\-foreground\fR (for \fBwl-copy\fR) By default, \fBwl-copy\fR forks and serves data requests in the background; this option overrides that behavior, causing \fBwl-copy\fR to run in the foreground. .TP -\fB-c\fR, \fB--clear +\fB\-c\fR, \fB\-\-clear\fR (for \fBwl-copy\fR) Instead of copying anything, clear the clipboard so that nothing is copied. .TP -\fB-n\fR, \fB--trim-newline +\fB\-n\fR, \fB\-\-trim-newline\fR (for \fBwl-copy\fR) Do not copy the trailing newline character if it is present in the input file. .TP -\fB-n\fR, \fB--no-newline +\fB\-n\fR, \fB\-\-no-newline\fR (for \fBwl-paste\fR) Do not append a newline character after the pasted clipboard content. This option is automatically enabled for non-text content types and when using the -\fB--watch\fR mode. +\fB\-\-watch\fR mode. .TP -\fB-t\fI mime/type\fR, \fB--type\fI mime/type +\fB\-t\fI mime/type\fR, \fB\-\-type\fI mime/type Override the automatically selected MIME type. For \fBwl-copy\fR this option controls which type \fBwl-copy\fR will offer the content as. For \fBwl-paste\fR it controls which of the offered types \fBwl-paste\fR will request the content @@ -69,7 +62,7 @@ in. In addition to specific MIME types such as \fIimage/png\fR, \fBwl-paste\fR also accepts generic type names such as \fItext\fR and \fIimage\fR which make it automatically pick some offered MIME type that matches the given generic name. .TP -\fB-s\fI seat-name\fR, \fB--seat\fI seat-name +\fB\-s\fI seat-name\fR, \fB\-\-seat\fI seat-name Specify which seat \fBwl-copy\fR and \fBwl-paste\fR should work with. Wayland natively supports multi-seat configurations where each seat gets its own mouse pointer, keyboard focus, and among other things its own separate clipboard. The @@ -83,24 +76,27 @@ tool. If you don't specify the seat name explicitly, \fBwl-copy\fR and \fBwl-paste\fR will pick a seat arbitrarily. If you are using a single-seat system, there is little reason to use this option. .TP -\fB-l\fR, \fB--list-types +\fB\-l\fR, \fB\-\-list-types\fR (for \fBwl-paste\fR) Instead of pasting the selection, output the list of MIME types it is offered in. .TP -\fB-w\fI command\fR..., \fB--watch \fIcommand\fR... +\fB\-w\fI command\fR..., \fB\-\-watch \fIcommand\fR... (for \fBwl-paste\fR) Instead of pasting once and exiting, continuously watch the clipboard for changes, and run the specified \fIcommand\fR each time a new selection appears. The spawned process can read the clipboard contents from its standard input. +\fBwl-paste\fR also sets the \fBCLIPBOARD_STATE\fR variable in the environment +of the spawned processes (see below). +.IP This mode requires a compositor that supports the wlroots data-control protocol. .TP -\fB-v\fR, \fB--version +\fB\-v\fR, \fB\-\-version Display the version of wl-clipboard and some short info about its license. .TP -\fB-h\fR, \fB--help +\fB\-h\fR, \fB\-\-help Display a short help message listing the available options. .SH ENVIRONMENT .TP -WAYLAND_DISPLAY +.B WAYLAND_DISPLAY Specifies what Wayland server \fBwl-copy\fR and \fBwl-paste\fR should connect to. This is the same environment variable that you pass to other Wayland clients, such as graphical applications, that connect to this Wayland server. It @@ -109,32 +105,129 @@ compositor. See .BR wl_display_connect (3) for more details. .TP -WAYLAND_DEBUG +.B WAYLAND_DEBUG When set to \fB1\fR, causes the \fBwayland-client\fR(7) library to log every interaction \fBwl-copy\fR and \fBwl-paste\fR make with the Wayland compositor to stderr. +.TP +.B CLIPBOARD_STATE +Set by \fBwl-paste\fR for the spawned command in \fB\-\-watch\fR mode. Currently +the following possible values are \fIdefined\fR: +.RS +.TP +CLIPBOARD_STATE=\fBdata +Indicates that the clipboard contains data that the spawned command can read +from its standard input. This is the most common case. +.TP +CLIPBOARD_STATE=\fBnil +Indicates that the clipboard is empty. In this case the spawned command's +standard input will be attached to \fI/dev/null\fR. Note that this is subtly +different from the clipboard containing zero-sized data (which can be achieved, +for instance, by running \fBwl-copy < /dev/null\fR). +.TP +CLIPBOARD_STATE=\fBclear +Indicates that the clipboard is empty because of an explicit clear request, such +as after running \fBwl-copy --clear\fR. As for \fBnil\fR, the command's standard +input will be attached to \fI/dev/null\fR. +.TP +CLIPBOARD_STATE=\fBsensitive +Indicates that the clipboard contains sensitive data such as a password or a +key. It is probably best to avoid visibly displaying or persistently saving +clipboard contents. +.RE +.IP +Any client programs implementing the \fBCLIPBOARD_STATE\fR protocol are +encouraged to implement proper support for all the values listed above, as well +as to fall back to some sensible behavior if \fBCLIPBOARD_STATE\fR is unset or +set to some unrecognized value (this is to leave the design space open for +future extensions). However, the currently existing Wayland clipboard protocols +don't let wl-clipboard identify the cases where \fBclear\fR and \fBsensitive\fR +values should be set, so currently wl-clipboard only ever sets +\fBCLIPBOARD_STATE\fR to \fBdata\fR or \fBnil\fR. +.IP +The \fBCLIPBOARD_STATE\fR protocol was intentionally designed to not be specific +to either wl-clipboard or Wayland; in fact, other clipboard tools are encouraged +to implement the same protocol. Currently, the SerenityOS +.BR paste (1) +utility is known to implement the same \fBCLIPBOARD_STATE\fR protocol. +.SH FILES +.TP +.I /etc/mime.types +If present, read by \fBwl-paste\fR to infer the MIME type to paste in based on +the file name extension of its standard output. +.SH BUGS +Unless the Wayland compositor implements the wlroots data-control protocol, +wl-clipboard has to resort to using a hack to access the clipboard: it will +briefly pop up a tiny transparent surface (window). On some desktop +environments (in particular when using tiling window managers), this can cause +visual issues such as brief flashing. In some cases the Wayland compositor +doesn't give focus to the popup surface, which prevents wl-clipboard from +accessing the clipboard and manifests as a hang. +.PP +There is currently no way to copy data in multiple MIME types, such as multiple +image formats, at the same time. +.br +See +.nh +. +.hy +.PP +wl-clipboard is not always able to detect that a MIME type is textual, which may +break pasting into clients that expect textual formats, not +\fIapplication/something\fR. The workaround, same as for all format inference +issues, is to specify the desired MIME type explicitly, such as +\fBwl-copy \-\-type\fI text/plain\fR. +.PP +\fBwl-copy \-\-clear\fR and \fBwl-copy \-\-paste-once\fR don't always interact +well with clipboard managers that are overeager to preserve clipboard contents. +.PP +Applications written using the GTK 3 toolkit copy text with \(dq\er\en\(dq (also +known as CR LF, 13 10 in ASCII) line endings, which takes most other software by +surprise. wl-cipboard does nothing to rectify this. The recommended workaround +is piping \fBwl-paste\fR output through +.BR dos2unix (1) +when pasting from a GTK 3 application. +.br +See +.nh +. +.hy .SH EXAMPLES +.TP +Copy a simple text message: $ .BI wl-copy " Hello world!" -.PP +.TP +Copy the list of files in \fI~/Downloads\fR: $ .IB "ls ~/Downloads" " | wl-copy" -.PP +.TP +Copy an image: $ .BI "wl-copy < " ~/Pictures/photo.png -.PP +.TP +Copy the previous command: $ .B wl-copy \(dq!!\(dq -.PP +.TP +Paste to a file, without appending a newline: $ -.BI "wl-paste -n > " clipboard.txt -.PP +.BI "wl-paste \-n > " clipboard.txt +.TP +Sort clipboard contents: $ -.B wl-paste --list-types | wl-copy +.B wl-paste | sort | wl-copy +.TP +Upload clipboard contents to a pastebin on each change: +$ +.BI "wl-paste --watch nc " "paste.example.org 5555 .SH AUTHOR Written by Sergey Bugaev. .SH REPORTING BUGS Report wl-clipboard bugs to +.br +Please make sure to mention which Wayland compositor you are using, and attach +\fBWAYLAND_DEBUG=1\fR debugging logs of wl-clipboard. .SH COPYRIGHT Copyright \(co 2018-2023 Sergey Bugaev. License GPLv3+: GNU GPL version 3 or later . From 985b9f4f23ae015df1498759f139a807cb931a16 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 22 Apr 2023 23:05:51 +0300 Subject: [PATCH 097/139] Split and rewrite Markdown docs --- BUILDING.md | 42 ++++++++++++++++++++++ README.md | 100 +++++++++++----------------------------------------- 2 files changed, 63 insertions(+), 79 deletions(-) create mode 100644 BUILDING.md diff --git a/BUILDING.md b/BUILDING.md new file mode 100644 index 0000000..82af29e --- /dev/null +++ b/BUILDING.md @@ -0,0 +1,42 @@ +# Building + +wl-clipboard is a simple Meson project, so building it is just: + +```bash +# Clone: +$ git clone https://github.com/bugaevc/wl-clipboard.git +$ cd wl-clipboard + +# Build: +$ meson setup build +$ cd build +$ ninja + +# Install +$ sudo meson install +``` + +wl-clipboard supports Linux and BSD systems, and is also known to work on +Mac OS X and GNU Hurd. The only mandatory dependency is the `wayland-client` +library (try package named `wayland-devel` or `libwayland-dev`). + +Optional (but highly recommended) dependencies for building: +* `wayland-scanner` +* `wayland-protocols` (version 1.12 or later) + +If these are found during configuration, wl-clipboard gets built with +additional protocols support, which enables features such as primary selection +support and `--watch` mode. Note that many compositors have dropped support for +the `wl_shell` interface, which means wl-clipboard will not work under them +unless built with both `wayland-scanner` and `wayland-protocols`. + +Optional dependencies for running: +* `xdg-mime` for content type inference in `wl-copy` (try package named + `xdg-utils`) +* `/etc/mime.types` file for type inference in `wl-paste` (try package named + `mime-support` or `mailcap`) + +If you're packaging wl-clipboard for a distribution, please consider making +packages providing `xdg-mime` and `/etc/mime.types` *weak* dependencies of the +package providing wl-clipboard, meaning ones that get installed along with +wl-clipboard by default, but are not strictly required by it. diff --git a/README.md b/README.md index 915db62..b0ebd16 100644 --- a/README.md +++ b/README.md @@ -4,96 +4,35 @@ This project implements two command-line Wayland clipboard utilities, `wl-copy` and `wl-paste`, that let you easily copy data between the clipboard and Unix pipes, sockets, files and so on. -Usage is as simple as: - ```bash -# copy a simple text message +# Copy a simple text message: $ wl-copy Hello world! -# copy the list of files in Downloads +# Copy the list of files in ~/Downloads: $ ls ~/Downloads | wl-copy -# copy an image file +# Copy an image: $ wl-copy < ~/Pictures/photo.png -# paste to a file -$ wl-paste > clipboard.txt - -# grep each pasted word in file source.c -$ for word in $(wl-paste); do grep $word source.c; done - -# copy the previous command +# Copy the previous command: $ wl-copy "!!" -# replace the current selection with the list of types it's offered in -$ wl-paste --list-types | wl-copy +# Paste to a file: +$ wl-paste > clipboard.txt + +# Sort clipboard contents: +$ wl-paste | sort | wl-copy + +# Upload clipboard contents to a pastebin on each change: +$ wl-paste --watch nc paste.example.org 5555 ``` -Although `wl-copy` and `wl-paste` are particularly optimized for plain text and -other textual content formats, they fully support content of arbitrary MIME -types. `wl-copy` automatically infers the type of the copied content by running -`xdg-mime(1)` on it. `wl-paste` tries its best to pick a type to paste based on -the list of offered MIME types and the extension of the file it's pasting into. -If you're not satisfied with the type they pick or don't want to rely on this -implicit type inference, you can explicitly specify the type to use with the -`--type` option. +Please see the wl-clipboard(1) man page for more details. -# Options +# Installing -For `wl-copy`: - -* `-n`, `--trim-newline` Do not copy the trailing newline character if it is present in the input file. -* `-o`, `--paste-once` Only serve one paste request and then exit. Unless a clipboard manager specifically designed to prevent this is in use, this has the effect of clearing the clipboard after the first paste, which is useful for copying sensitive data such as passwords. Note that this may break pasting into some clients, in particular pasting into XWayland windows is known to break when this option is used. -* `-f`, `--foreground` By default, `wl-copy` forks and serves data requests in the background; this option overrides that behavior, causing `wl-copy` to run in the foreground. -* `-c`, `--clear` Instead of copying anything, clear the clipboard so that nothing is copied. - -For `wl-paste`: - -* `-n`, `--no-newline` Do not append a newline character after the pasted clipboard content. This option is automatically enabled for non-text content types and when using the `--watch` mode. -* `-l`, `--list-types` Instead of pasting the selection, output the list of MIME types it is offered in. -* `-w command...`, `--watch command...` Instead of pasting once and exiting, continuously watch the clipboard for changes, and run the specified command each time a new selection appears. The spawned process can read the clipboard contents from its standard input. This mode requires a compositor that supports the [wlroots data-control protocol](https://github.com/swaywm/wlr-protocols/blob/master/unstable/wlr-data-control-unstable-v1.xml). - -For both: - -* `-p`, `--primary` Use the "primary" clipboard instead of the regular clipboard. -* `-t mime/type`, `--type mime/type` Override the inferred MIME type for the content. For `wl-copy` this option controls which type `wl-copy` will offer the content as. For `wl-paste` it controls which of the offered types `wl-paste` will request the content in. In addition to specific MIME types such as _image/png_, `wl-paste` also accepts generic type names such as _text_ and _image_ which make it automatically pick some offered MIME type that matches the given generic name. -* `-s seat-name`, `--seat seat-name` Specify which seat `wl-copy` and `wl-paste` should work with. Wayland natively supports multi-seat configurations where each seat gets its own mouse pointer, keyboard focus, and among other things its own separate clipboard. The name of the default seat is likely _default_ or _seat0_, and additional seat names normally come form `udev(7)` property `ENV{WL_SEAT}`. You can view the list of the currently available seats as advertised by the compositor using the `weston-info(1)` tool. If you don't specify the seat name explicitly, `wl-copy` and `wl-paste` will pick a seat arbitrarily. If you are using a single-seat system, there is little reason to use this option. -* `-v`, `--version` Display the version of wl-clipboard and some short info about its license. -* `-h`, `--help` Display a short help message listing the available options. - -# Building - -wl-clipboard is a simple Meson project, so building it is just: - -```bash -# clone -$ git clone https://github.com/bugaevc/wl-clipboard.git -$ cd wl-clipboard - -# build -$ meson build -$ cd build -$ ninja - -# install -$ sudo ninja install -``` - -wl-clipboard supports Linux and BSD systems, and is also known to work on -Mac OS X and GNU Hurd. The only mandatory dependency is the `wayland-client` -library (try package named `wayland-devel` or `libwayland-dev`). - -Optional dependencies for building: -* `wayland-scanner` -* `wayland-protocols` (version 1.12 or later) - -If these are found during configuration, wl-clipboard gets built with -additional protocols support, which enables features such as primary selection -support and `--watch` mode. - -Optional dependencies for running: -* `xdg-mime` for content type inference in `wl-copy` (try package named `xdg-utils`) -* `/etc/mime.types` file for type inference in `wl-paste` (try package named `mime-support` or `mailcap`) +wl-clipboard is likely available in your favorite Linux or BSD distro. For +building from source, see [BUILDING.md](BUILDING.md). # License @@ -102,5 +41,8 @@ version 3 or later. # Related projects -* [wl-clipboard-x11](https://github.com/brunelli/wl-clipboard-x11): A wrapper to use wl-clipboard as a drop-in replacement to X11 clipboard tools. -* [wl-clipboard-rs](https://github.com/YaLTeR/wl-clipboard-rs): A Rust crate (library) for working with the Wayland clipboard which includes a reimplementation of `wl-copy` and `wl-paste`. +* [wl-clipboard-x11](https://github.com/brunelli/wl-clipboard-x11): A wrapper to + use wl-clipboard as a drop-in replacement to X11 clipboard tools. +* [wl-clipboard-rs](https://github.com/YaLTeR/wl-clipboard-rs): A Rust crate + (library) for working with the Wayland clipboard which includes a + reimplementation of `wl-copy` and `wl-paste`. From ea5c14e1915731fa5a4fa2cae60fd17684636ef5 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 23 Apr 2023 13:18:53 +0300 Subject: [PATCH 098/139] Bump the version to 2.2.0-rc1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 81ad51f..1879fba 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wl-clipboard', 'c', - version: '2.1.0', + version: '2.2.0-rc1', license: 'GPL3+', meson_version: '>= 0.44.0', default_options: 'c_std=gnu99' From 61d5fa34b4722a1fb3ce7a83e2dbc413ffef863f Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 4 May 2023 00:38:29 +0300 Subject: [PATCH 099/139] Silence a warning --- src/types/popup-surface.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index 8af3755..9c42c08 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -106,7 +106,10 @@ void popup_surface_init(struct popup_surface *self) { /* Open an anonymous file and write some zero bytes to it */ int fd = create_anonymous_file(); - ftruncate(fd, size); + int rc = ftruncate(fd, size); + if (rc < 0) { + perror("ftruncate"); + } /* Create a shared memory pool */ struct wl_shm *wl_shm = self->registry->wl_shm; From 84f16d447fee126a5f19a46d7c300941081832b6 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 4 May 2023 00:39:45 +0300 Subject: [PATCH 100/139] Complain when launched with closed stdio fds This will help us diagnose such cases, which otherwise manifest as weird hangs and other bugs. Closes https://github.com/bugaevc/wl-clipboard/issues/153 --- src/util/files.c | 39 ++++++++++++++++++++++++++++++++++++++- src/util/files.h | 3 +++ src/wl-copy.c | 3 +++ src/wl-paste.c | 3 +++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/util/files.c b/src/util/files.c index bc2b06b..b213010 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -27,12 +27,13 @@ #include #include #include -#include // open +#include #include // open #include // open #include // exit #include // basename #include +#include #ifdef HAVE_MEMFD # include // syscall, SYS_memfd_create @@ -41,6 +42,42 @@ # include // shm_open, SHM_ANON #endif +#include // wl_display_get_fd + + +void complain_about_closed_stdio(struct wl_display *wl_display) { + const char *message = "wl-clipboard has been launched with a closed" + " standard file descriptor. This is a bug in the software that" + " has launched wl-clipboard. Aborting."; + + /* See if we can write to stderr */ + if (wl_display_get_fd(wl_display) < STDERR_FILENO) { + int rc = fcntl(STDERR_FILENO, F_GETFL); + if (rc > 0) { + rc &= O_ACCMODE; + if (rc == O_WRONLY || rc == O_RDWR) { + /* Yes, we can! */ + fprintf(stderr, "%s\n", message); + fflush(stderr); + abort(); + } + } + } + + /* Maybe there is a tty we could write to? */ + FILE *tty = fopen("/dev/tty", "w"); + if (tty != NULL) { + fprintf(tty, "%s\n", message); + fflush(stderr); + abort(); + } + + /* As a last resort, try syslog */ + openlog("wl-clipboard", LOG_CONS | LOG_PID, LOG_USER); + syslog(LOG_ERR, message); + closelog(); + abort(); +} int create_anonymous_file() { int res; diff --git a/src/util/files.h b/src/util/files.h index b34f852..b1e8a62 100644 --- a/src/util/files.h +++ b/src/util/files.h @@ -19,6 +19,9 @@ #ifndef UTIL_FILES_H #define UTIL_FILES_H +struct wl_display; +void complain_about_closed_stdio(struct wl_display *wl_display); + int create_anonymous_file(void); void trim_trailing_newline(const char *file_path); diff --git a/src/wl-copy.c b/src/wl-copy.c index d911875..1a033bc 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -204,6 +204,9 @@ int main(int argc, argv_t argv) { if (wl_display == NULL) { complain_about_wayland_connection(); } + if (wl_display_get_fd(wl_display) <= STDERR_FILENO) { + complain_about_closed_stdio(wl_display); + } struct registry *registry = calloc(1, sizeof(struct registry)); registry->wl_display = wl_display; diff --git a/src/wl-paste.c b/src/wl-paste.c index b608ea9..d9a405a 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -474,6 +474,9 @@ int main(int argc, argv_t argv) { if (wl_display == NULL) { complain_about_wayland_connection(); } + if (wl_display_get_fd(wl_display) <= STDERR_FILENO) { + complain_about_closed_stdio(wl_display); + } struct registry *registry = calloc(1, sizeof(struct registry)); registry->wl_display = wl_display; From c82266610eb8a868b5948dcb4e26b6cf1c851500 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 2 Jul 2023 15:26:44 +0300 Subject: [PATCH 101/139] Strongly require protocol dependencies, with opt-out --- BUILDING.md | 10 +++++++--- meson.build | 2 +- meson_options.txt | 3 +++ src/protocol/meson.build | 7 ++++--- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index 82af29e..6f82c33 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -26,9 +26,13 @@ Optional (but highly recommended) dependencies for building: If these are found during configuration, wl-clipboard gets built with additional protocols support, which enables features such as primary selection -support and `--watch` mode. Note that many compositors have dropped support for -the `wl_shell` interface, which means wl-clipboard will not work under them -unless built with both `wayland-scanner` and `wayland-protocols`. +support and `--watch` mode. + +Note that many compositors have dropped support for the `wl_shell` interface, +which means wl-clipboard will not work under them unless built with both +`wayland-scanner` and `wayland-protocols`. For this reason, you have to +explicitly opt into allowing building without these dependencies by specifying +`-D protocols=auto` (or `-D protocols=disabled`) when configuring with Meson. Optional dependencies for running: * `xdg-mime` for content type inference in `wl-copy` (try package named diff --git a/meson.build b/meson.build index 1879fba..4b6b127 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('wl-clipboard', 'c', version: '2.2.0-rc1', license: 'GPL3+', - meson_version: '>= 0.44.0', + meson_version: '>= 0.47.0', default_options: 'c_std=gnu99' ) diff --git a/meson_options.txt b/meson_options.txt index 32b24ed..b6e621c 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,6 @@ +option('protocols', + type: 'feature', + value: 'enabled') option('zshcompletiondir', type: 'string', value: '', diff --git a/src/protocol/meson.build b/src/protocol/meson.build index 0d1a47e..655be26 100644 --- a/src/protocol/meson.build +++ b/src/protocol/meson.build @@ -1,5 +1,6 @@ -wayland_scanner = find_program('wayland-scanner', required: false, native: true) -wayland_protocols = dependency('wayland-protocols', version: '>= 1.12', required: false) +protocols_opt = get_option('protocols') +wayland_scanner = find_program('wayland-scanner', required: protocols_opt, native: true) +wayland_protocols = dependency('wayland-protocols', version: '>= 1.12', required: protocols_opt) if wayland_scanner.found() have_xdg_shell = wayland_protocols.found() @@ -43,7 +44,7 @@ endif protocols = [] if not have_xdg_shell - warning('Building without xdg-shell support') + warning('Building without xdg-shell support, wl-clipboard will be unable to work on some compositors') else xdg_shell_xml = join_paths(protocols_path, 'stable', 'xdg-shell', 'xdg-shell.xml') protocols += [['xdg-shell', xdg_shell_xml]] From a9c58833f0f7e800041dd8e06ca9e32eecb43e19 Mon Sep 17 00:00:00 2001 From: oxalica Date: Sat, 24 Jun 2023 20:31:59 +0800 Subject: [PATCH 102/139] Set CWD to root for background process This releases the occupation of CWD, so that the mounting point of removable devices containing it can be umounted. --- src/wl-copy.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/wl-copy.c b/src/wl-copy.c index 1a033bc..9edb575 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -55,10 +55,12 @@ static void did_set_selection_callback(struct copy_action *copy_action) { /* Move to background. * We fork our process and leave the * child running in the background, - * while exiting in the parent. - * Also replace stdin/stdout with - * /dev/null so the stdout file - * descriptor isn't kept alive. + * while exiting in the parent. We also + * replace stdin/stdout with /dev/null + * so the stdout file descriptor isn't + * kept alive, and chdir to the root, to + * prevent blocking file systems from + * being unmounted. */ int devnull = open("/dev/null", O_RDWR); if (devnull >= 0) { @@ -66,10 +68,15 @@ static void did_set_selection_callback(struct copy_action *copy_action) { dup2(devnull, STDIN_FILENO); close(devnull); } else { - /* If we cannot open /dev/null, just close stdin/stdout */ + /* If we cannot open /dev/null, + * just close stdin/stdout. + */ close(STDIN_FILENO); close(STDOUT_FILENO); } + if (chdir("/") < 0) { + perror("chdir /"); + } signal(SIGHUP, SIG_IGN); pid_t pid = fork(); if (pid < 0) { From 4a5d9017b18a4859779a2c420d561b3f0d4eedc7 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 2 Jul 2023 18:06:02 +0300 Subject: [PATCH 103/139] Tweak error message about missing shell The most probable reason for this is wl-clipboard being built without xdg-shell support, which is something we're trying to discourage, and something that typically happens with self-built wl-clipboard binaries, as opposed to distro-provided ones. Instead of just adding a note (which would only be helpful if you're actively aware of the decision to build wl-clipboard without xdg-shell), print a specialized message explicitly pointing at missing xdg-shell at build time as the root issue, and ask the user to rebuild wl-clipboard. --- src/util/misc.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/util/misc.c b/src/util/misc.c index 797d2b6..adfb882 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -117,18 +117,22 @@ void complain_about_missing_global(const char *global) { } void complain_about_missing_shell() { +#ifndef HAVE_XDG_SHELL + fprintf( + stderr, + "wl-clipboard was built without xdg-shell support, and the compositor" + " does not seem to support any other Wayland shell.\nPlease rebuild" + " wl-clipboard with xdg-shell support.\n" + ); + exit(1); +#endif + + fprintf( stderr, "The compositor does not seem to implement a Wayland shell," " which is required for wl-clipboard to work\n" ); -#ifndef HAVE_XDG_SHELL - fprintf( - stderr, - "Note: wl-clipboard was built without xdg-shell support\n" - ); -#endif - exit(1); } From 8b68195ddcd8c843adf731ca645062e217ffeaf6 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 2 Jul 2023 18:15:14 +0300 Subject: [PATCH 104/139] Add wayland-protocols as a Meson wrap subproject Part of https://github.com/bugaevc/wl-clipboard/issues/182 --- BUILDING.md | 3 ++- subprojects/wayland-protocols.wrap | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 subprojects/wayland-protocols.wrap diff --git a/BUILDING.md b/BUILDING.md index 6f82c33..0fb96d7 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -26,7 +26,8 @@ Optional (but highly recommended) dependencies for building: If these are found during configuration, wl-clipboard gets built with additional protocols support, which enables features such as primary selection -support and `--watch` mode. +support and `--watch` mode. If `wayland-protocols` is not found, Meson can +optionally build it on the spot as a subproject. Note that many compositors have dropped support for the `wl_shell` interface, which means wl-clipboard will not work under them unless built with both diff --git a/subprojects/wayland-protocols.wrap b/subprojects/wayland-protocols.wrap new file mode 100644 index 0000000..5a3bafa --- /dev/null +++ b/subprojects/wayland-protocols.wrap @@ -0,0 +1,4 @@ +[wrap-git] +url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git +revision = 1.31 +depth = 1 From 8e2b84aa83d192d6235e01dd1d82d5c57d77e367 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 2 Jul 2023 18:27:10 +0300 Subject: [PATCH 105/139] Tweak the man page * Remove the mention of ASCII codes * Add another BUG entry --- data/wl-clipboard.1 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index b6f7de5..e7e35d3 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -182,9 +182,9 @@ issues, is to specify the desired MIME type explicitly, such as well with clipboard managers that are overeager to preserve clipboard contents. .PP Applications written using the GTK 3 toolkit copy text with \(dq\er\en\(dq (also -known as CR LF, 13 10 in ASCII) line endings, which takes most other software by -surprise. wl-cipboard does nothing to rectify this. The recommended workaround -is piping \fBwl-paste\fR output through +known as CR LF) line endings, which takes most other software by surprise. +wl-cipboard does nothing to rectify this. The recommended workaround is piping +\fBwl-paste\fR output through .BR dos2unix (1) when pasting from a GTK 3 application. .br @@ -192,6 +192,10 @@ See .nh . .hy +.PP +When trying to paste content copied with \fBwl-copy\fR, \fBwl-copy\fR does not +check whether the requested MIME type is among those it has offered, and always +provides the same data in response. .SH EXAMPLES .TP Copy a simple text message: From 5c4457da4ce86b869219a1b256f121535ed5f3ef Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 2 Jul 2023 18:47:55 +0300 Subject: [PATCH 106/139] Mention getopt(3) features in the man page Closes https://github.com/bugaevc/wl-clipboard/issues/131 --- data/wl-clipboard.1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index e7e35d3..6846977 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -27,6 +27,13 @@ pasting into. If you're not satisfied with the type they pick or don't want to rely on this implicit type inference, you can explicitly specify the type to use with the \fB\-\-type\fR option. .SH OPTIONS +To parse options, wl-clipboard uses the +.BR getopt (3) +library routines, whose features depend on the C library in use. In particular, +it may be possible to specify \fB\-\-\fR as an argument on its own to prevent +any further arguments from getting parsed as options (which lets you copy text +containing words that start with the \fB-\fR sign), and to shorten long options +to their unambiguous prefixes. .TP \fB\-p\fR, \fB\-\-primary Use the "primary" clipboard instead of the regular clipboard. @@ -202,6 +209,9 @@ Copy a simple text message: $ .BI wl-copy " Hello world!" .TP +Copy a message starting with dashes: +.BI "wl-copy -- " --long +.TP Copy the list of files in \fI~/Downloads\fR: $ .IB "ls ~/Downloads" " | wl-copy" From ac7363c51c74a2d29cd435e93b2922243f92b996 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 2 Jul 2023 18:52:25 +0300 Subject: [PATCH 107/139] Tweak --help message To make sure it fits on 80 columns. --- src/wl-copy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wl-copy.c b/src/wl-copy.c index 9edb575..120464e 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -116,7 +116,7 @@ static void print_usage(FILE *f, const char *argv0) { "Options:\n" "\t-o, --paste-once\tOnly serve one paste request and then exit.\n" "\t-f, --foreground\tStay in the foreground instead of forking.\n" - "\t-c, --clear\t\tInstead of copying anything, clear the clipboard.\n" + "\t-c, --clear\t\tInstead of copying, clear the clipboard.\n" "\t-p, --primary\t\tUse the \"primary\" clipboard.\n" "\t-n, --trim-newline\tDo not copy the trailing newline character.\n" "\t-t, --type mime/type\t" From 752820fb5943f0aa4915ff742e3b0f8e0b867e03 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 2 Jul 2023 19:07:29 +0300 Subject: [PATCH 108/139] Bump the version to 2.2.0-rc2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 4b6b127..6e26deb 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wl-clipboard', 'c', - version: '2.2.0-rc1', + version: '2.2.0-rc2', license: 'GPL3+', meson_version: '>= 0.47.0', default_options: 'c_std=gnu99' From 6250cde91bfd2efcefdccc41619241f7ddc10750 Mon Sep 17 00:00:00 2001 From: Lukas Lihotzki Date: Thu, 13 Jul 2023 00:36:33 +0200 Subject: [PATCH 109/139] Fix indentation in wl-copy.c --- src/wl-copy.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/wl-copy.c b/src/wl-copy.c index 120464e..b040cb6 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -279,15 +279,15 @@ int main(int argc, argv_t argv) { * access to the file through our open * file descriptor. */ - int rc = unlink(temp_file); - if (rc < 0) { - perror("Failed to unlink temp file"); - } - rc = rmdir(dirname(temp_file)); - if (rc < 0) { - perror("Failed to remove temp file directory"); - } - free(temp_file); + int rc = unlink(temp_file); + if (rc < 0) { + perror("Failed to unlink temp file"); + } + rc = rmdir(dirname(temp_file)); + if (rc < 0) { + perror("Failed to remove temp file directory"); + } + free(temp_file); } /* Create the source */ From 80d97713814508ae8e55d078897df2044d26fa66 Mon Sep 17 00:00:00 2001 From: Artturin Date: Mon, 3 Jul 2023 20:56:18 +0300 Subject: [PATCH 110/139] Fix a warning "format not a string literal and no format arguments [-Wformat-security]" https://fedoraproject.org/wiki/Format-Security-FAQ --- src/util/files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/files.c b/src/util/files.c index b213010..b55c77f 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -74,7 +74,7 @@ void complain_about_closed_stdio(struct wl_display *wl_display) { /* As a last resort, try syslog */ openlog("wl-clipboard", LOG_CONS | LOG_PID, LOG_USER); - syslog(LOG_ERR, message); + syslog(LOG_ERR, "%s", message); closelog(); abort(); } From a4a3e8054b3a2866efecafa045396fcbb1869e63 Mon Sep 17 00:00:00 2001 From: Tristan Partin Date: Fri, 7 Jul 2023 16:21:57 -0500 Subject: [PATCH 111/139] Use SPDX license string This is what Meson expects you to use. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 6e26deb..7fb0e90 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('wl-clipboard', 'c', version: '2.2.0-rc2', - license: 'GPL3+', + license: 'GPL-3.0-or-later', meson_version: '>= 0.47.0', default_options: 'c_std=gnu99' ) From cb82e5f1dacfbe25d2ef184cd5bd526e0758b1cd Mon Sep 17 00:00:00 2001 From: Tristan Partin Date: Fri, 7 Jul 2023 16:22:54 -0500 Subject: [PATCH 112/139] Don't configure wayland-protocols tests --- src/protocol/meson.build | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/protocol/meson.build b/src/protocol/meson.build index 655be26..743b299 100644 --- a/src/protocol/meson.build +++ b/src/protocol/meson.build @@ -1,6 +1,11 @@ protocols_opt = get_option('protocols') wayland_scanner = find_program('wayland-scanner', required: protocols_opt, native: true) -wayland_protocols = dependency('wayland-protocols', version: '>= 1.12', required: protocols_opt) +wayland_protocols = dependency( + 'wayland-protocols', + version: '>= 1.12', + required: protocols_opt, + default_options: ['tests=false'] +) if wayland_scanner.found() have_xdg_shell = wayland_protocols.found() From 518025b9e90cccd980022ea58313fbb8913e90f3 Mon Sep 17 00:00:00 2001 From: Tristan Partin Date: Fri, 7 Jul 2023 16:23:28 -0500 Subject: [PATCH 113/139] Remove unnecessary Meson arrays --- src/meson.build | 70 ++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/meson.build b/src/meson.build index 37a638c..6643a11 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,56 +15,56 @@ configure_file(output: 'config.h', configuration: conf_data) lib = static_library( 'wl-clipboard', - [ - protocol_headers, + protocol_headers, - 'includes/shell-protocols.h', - 'includes/selection-protocols.h', + 'includes/shell-protocols.h', + 'includes/selection-protocols.h', - 'util/string.h', - 'util/string.c', - 'util/files.h', - 'util/files.c', - 'util/misc.h', - 'util/misc.c', + 'util/string.h', + 'util/string.c', + 'util/files.h', + 'util/files.c', + 'util/misc.h', + 'util/misc.c', - 'types/source.h', - 'types/source.c', - 'types/offer.h', - 'types/offer.c', - 'types/device.h', - 'types/device.c', - 'types/device-manager.h', - 'types/device-manager.c', - 'types/keyboard.h', - 'types/keyboard.c', - 'types/seat.h', - 'types/seat.c', - 'types/shell.h', - 'types/shell.c', - 'types/shell-surface.h', - 'types/shell-surface.c', - 'types/popup-surface.h', - 'types/popup-surface.c', - 'types/registry.h', - 'types/registry.c', - 'types/copy-action.h', - 'types/copy-action.c', - ], + 'types/source.h', + 'types/source.c', + 'types/offer.h', + 'types/offer.c', + 'types/device.h', + 'types/device.c', + 'types/device-manager.h', + 'types/device-manager.c', + 'types/keyboard.h', + 'types/keyboard.c', + 'types/seat.h', + 'types/seat.c', + 'types/shell.h', + 'types/shell.c', + 'types/shell-surface.h', + 'types/shell-surface.c', + 'types/popup-surface.h', + 'types/popup-surface.c', + 'types/registry.h', + 'types/registry.c', + 'types/copy-action.h', + 'types/copy-action.c', dependencies: wayland, link_with: protocol_deps ) executable( 'wl-copy', - ['wl-copy.c', protocol_headers], + 'wl-copy.c', + protocol_headers, dependencies: wayland, link_with: lib, install: true ) executable( 'wl-paste', - ['wl-paste.c', protocol_headers], + 'wl-paste.c', + protocol_headers, dependencies: wayland, link_with: lib, install: true From 321836d559c80c2babe6924585d2ebbe40c1a3da Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 15 Jul 2023 16:27:11 +0300 Subject: [PATCH 114/139] Add Wayland as a Meson wrap subproject Good things about this: 1. It should now be possible to build wl-clipboard without any system dependencies (other than Meson and the compiler toolchain), with everything coming from subprojects. Yet, this does not hurt the normal case of using system-provided dependencies for everything. 2. New enough Wayland (1.22.0, as opposed to 1.20.0 in the Meson WrapDB at the time of writing) now ships with a call to override_find_program('wayland-scanner'), so finding wayland-scanner works transparently. Less than ideal things about this: 1. This requires us to specify default options (to disable Wayland tests, docs, and importantly DTD validation, which depends on libxml2) in our buildscript, not in the wrap file. 2. Wayland still doesn't ship expat and libffi as its own subprojects, so we have to ship the wraps in wl-clipboard alongside with Wayland itself. 3. If libwayland-client is found but wayland-scanner is not, the subproject gets built to get wayland-scanner, but our default options specified at dependency('wayland-client') call time are not respected and this throws an error about missing libxml2. This would be fixed if Meson allowed specifying default options in the wrap file. Closes https://github.com/bugaevc/wl-clipboard/issues/182 --- BUILDING.md | 4 ++-- src/meson.build | 5 ++++- subprojects/expat.wrap | 13 +++++++++++++ subprojects/libffi.wrap | 12 ++++++++++++ subprojects/wayland.wrap | 9 +++++++++ 5 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 subprojects/expat.wrap create mode 100644 subprojects/libffi.wrap create mode 100644 subprojects/wayland.wrap diff --git a/BUILDING.md b/BUILDING.md index 0fb96d7..6ec5612 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -26,8 +26,8 @@ Optional (but highly recommended) dependencies for building: If these are found during configuration, wl-clipboard gets built with additional protocols support, which enables features such as primary selection -support and `--watch` mode. If `wayland-protocols` is not found, Meson can -optionally build it on the spot as a subproject. +support and `--watch` mode. If they are not found, Meson can optionally build +them on the spot as subprojects. Note that many compositors have dropped support for the `wl_shell` interface, which means wl-clipboard will not work under them unless built with both diff --git a/src/meson.build b/src/meson.build index 6643a11..e3b9bcf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,4 +1,7 @@ -wayland = dependency('wayland-client') +wayland = dependency( + 'wayland-client', + default_options: ['tests=false', 'documentation=false', 'dtd_validation=false'] +) cc = meson.get_compiler('c') have_memfd = cc.has_header_symbol('sys/syscall.h', 'SYS_memfd_create') diff --git a/subprojects/expat.wrap b/subprojects/expat.wrap new file mode 100644 index 0000000..bda9011 --- /dev/null +++ b/subprojects/expat.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = expat-2.5.0 +source_url = https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.xz +source_filename = expat-2.5.0.tar.bz2 +source_hash = ef2420f0232c087801abf705e89ae65f6257df6b7931d37846a193ef2e8cdcbe +patch_filename = expat_2.5.0-3_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/expat_2.5.0-3/get_patch +patch_hash = e30c8c32f79fd4563a86a5c9cfe419568c35b775077c2d1428170081129406b4 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/expat_2.5.0-3/expat-2.5.0.tar.bz2 +wrapdb_version = 2.5.0-3 + +[provide] +expat = expat_dep diff --git a/subprojects/libffi.wrap b/subprojects/libffi.wrap new file mode 100644 index 0000000..5bdb9ea --- /dev/null +++ b/subprojects/libffi.wrap @@ -0,0 +1,12 @@ +[wrap-file] +directory = libffi-3.4.4 +source_url = https://github.com/libffi/libffi/releases/download/v3.4.4/libffi-3.4.4.tar.gz +source_filename = libffi-3.4.4.tar.gz +source_hash = d66c56ad259a82cf2a9dfc408b32bf5da52371500b84745f7fb8b645712df676 +patch_filename = libffi_3.4.4-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/libffi_3.4.4-2/get_patch +patch_hash = 77da71839584dfc33d998b6461fff1ebc90a6f9c0a8c2f490335aafe370a115b +wrapdb_version = 3.4.4-2 + +[provide] +dependency_names = libffi diff --git a/subprojects/wayland.wrap b/subprojects/wayland.wrap new file mode 100644 index 0000000..e0991a2 --- /dev/null +++ b/subprojects/wayland.wrap @@ -0,0 +1,9 @@ +[wrap-file] +directory = wayland-1.22.0 +source_url = https://gitlab.freedesktop.org/wayland/wayland/-/releases/1.22.0/downloads/wayland-1.22.0.tar.xz +source_filename = wayland-1.22.0.tar.xz +source_hash = 1540af1ea698a471c2d8e9d288332c7e0fd360c8f1d12936ebb7e7cbc2425842 + +[provide] +dependency_names = wayland-client, wayland-server, wayland-cursor, wayland-egl +program_names = wayland-scanner From 491a67ed69e08ee9ee8021cde7742d36c877b879 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 16 Jul 2023 18:42:50 +0300 Subject: [PATCH 115/139] Use wayland-protocols.wrap from WrapDB This refers to a release rather than Git version and doesn't require Git to be installed. Related to https://github.com/bugaevc/wl-clipboard/issues/182 --- subprojects/wayland-protocols.wrap | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/subprojects/wayland-protocols.wrap b/subprojects/wayland-protocols.wrap index 5a3bafa..71650c7 100644 --- a/subprojects/wayland-protocols.wrap +++ b/subprojects/wayland-protocols.wrap @@ -1,4 +1,8 @@ -[wrap-git] -url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git -revision = 1.31 -depth = 1 +[wrap-file] +directory = wayland-protocols-1.24 +source_url = https://wayland.freedesktop.org/releases/wayland-protocols-1.24.tar.xz +source_filename = wayland-protocols-1.24.tar.xz +source_hash = bff0d8cffeeceb35159d6f4aa6bab18c807b80642c9d50f66cba52ecf7338bc2 + +[provide] +wayland-protocols = wayland_protocols From 8c91a6344ba13419070816a4566b60c53da37f27 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 22 Jul 2023 14:19:39 +0300 Subject: [PATCH 116/139] Tweak a couple of error messages --- src/util/misc.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/util/misc.c b/src/util/misc.c index adfb882..1a0e628 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -51,12 +51,13 @@ void complain_about_selection_support(int primary) { void complain_about_watch_mode_support() { #ifdef HAVE_WLR_DATA_CONTROL bail( - "Watch mode requires a compositor that supports " - "wlroots data-control protocol" + "Watch mode requires a compositor that supports" + " the wlroots data-control protocol" ); #else bail( - "wl-clipboard was built without wlroots data-control protocol support" + "wl-clipboard was built without support for" + " the wlroots data-control protocol" ); #endif } From c9964a555ee4470ec63cbc522f96eb24cd3b632f Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sat, 22 Jul 2023 14:29:58 +0300 Subject: [PATCH 117/139] Release wl-clipboard 2.2 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 7fb0e90..4c33618 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wl-clipboard', 'c', - version: '2.2.0-rc2', + version: '2.2.0', license: 'GPL-3.0-or-later', meson_version: '>= 0.47.0', default_options: 'c_std=gnu99' From fcde402ed711dc24883e207f5665fbb22eba6e4d Mon Sep 17 00:00:00 2001 From: anteater <65555601+nt8r@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:05:06 +0000 Subject: [PATCH 118/139] Do not abort when interfaces are older than expected Instead, let consumers abort if the missing interface is a fatal problem. --- src/types/registry.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/types/registry.c b/src/types/registry.c index b33c4d1..e1ea2c0 100644 --- a/src/types/registry.c +++ b/src/types/registry.c @@ -29,12 +29,14 @@ #define BIND(interface_name, known_version) \ if (strcmp(interface, #interface_name) == 0) { \ - self->interface_name = wl_registry_bind( \ - wl_registry, \ - name, \ - &interface_name ## _interface, \ - known_version \ - ); \ + if (version >= known_version) { \ + self->interface_name = wl_registry_bind( \ + wl_registry, \ + name, \ + &interface_name ## _interface, \ + known_version \ + ); \ + } \ } static void wl_registry_global_handler( From 8dbdefb77a7ec9343aa5fd0700dd9a430164ad0a Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 20 Aug 2023 13:13:13 +0300 Subject: [PATCH 119/139] Tweak binding interfaces further - Also check for wl_seat version - Fix parenthesizing in a macro --- src/types/registry.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/registry.c b/src/types/registry.c index e1ea2c0..1c7ad99 100644 --- a/src/types/registry.c +++ b/src/types/registry.c @@ -29,7 +29,7 @@ #define BIND(interface_name, known_version) \ if (strcmp(interface, #interface_name) == 0) { \ - if (version >= known_version) { \ + if (version >= (known_version)) { \ self->interface_name = wl_registry_bind( \ wl_registry, \ name, \ @@ -83,7 +83,7 @@ static void wl_registry_global_handler( BIND(zwlr_data_control_manager_v1, version > 2 ? 2 : version) #endif - if (strcmp(interface, "wl_seat") == 0) { + if (strcmp(interface, "wl_seat") == 0 && version >= 2) { struct seat *seat = calloc(1, sizeof(struct seat)); seat->proxy = wl_registry_bind( wl_registry, From 6256adf6d936ec182e1daf6b7fd833a709a1ac5b Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 20 Aug 2023 13:41:24 +0300 Subject: [PATCH 120/139] Restore signal handlers in wl-copy before spawning children We ignore SIGHUP and SIGPIPE in wl-copy itself, yet we want our child processes, notably cat, to still get terminated if it receives SIGPIPE. Signal handlers set to SIG_IGN unfortunately get inherited over exec, so reset them before execing. Fixes https://github.com/bugaevc/wl-clipboard/issues/190 --- src/types/copy-action.c | 3 +++ src/util/files.c | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/types/copy-action.c b/src/types/copy-action.c index b3f8829..19b7599 100644 --- a/src/types/copy-action.c +++ b/src/types/copy-action.c @@ -30,6 +30,7 @@ #include #include #include +#include static void do_set_selection(struct copy_action *self, uint32_t serial) { /* Set the selection and make sure it reaches @@ -86,6 +87,8 @@ static void do_send(struct source *source, const char *mime_type, int fd) { close(self->fd_to_copy_from); dup2(fd, STDOUT_FILENO); close(fd); + signal(SIGHUP, SIG_DFL); + signal(SIGPIPE, SIG_DFL); execlp("cat", "cat", NULL); perror("exec cat"); exit(1); diff --git a/src/util/files.c b/src/util/files.c index b55c77f..eade589 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -33,6 +33,7 @@ #include // exit #include // basename #include +#include #include #ifdef HAVE_MEMFD @@ -167,6 +168,8 @@ char *infer_mime_type_from_contents(const char *file_path) { /* If we cannot open /dev/null, just close stdin */ close(STDIN_FILENO); } + signal(SIGHUP, SIG_DFL); + signal(SIGPIPE, SIG_DFL); execlp("xdg-mime", "xdg-mime", "query", "filetype", file_path, NULL); exit(1); } @@ -274,6 +277,8 @@ char *dump_stdin_into_a_temp_file() { } dup2(fd, STDOUT_FILENO); close(fd); + signal(SIGHUP, SIG_DFL); + signal(SIGPIPE, SIG_DFL); execlp("cat", "cat", NULL); perror("exec cat"); exit(1); From 3eb912c274042cd5deed6b478b39908a12f37498 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 27 Aug 2023 21:55:20 +0300 Subject: [PATCH 121/139] Release wl-clipboard 2.2.1 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 4c33618..b1148bd 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wl-clipboard', 'c', - version: '2.2.0', + version: '2.2.1', license: 'GPL-3.0-or-later', meson_version: '>= 0.47.0', default_options: 'c_std=gnu99' From abfd513ecceef5f33632e0f63daea92f9bbe08e2 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Fri, 29 Sep 2023 14:48:22 +0300 Subject: [PATCH 122/139] Fix assertion failure If we try to paste when there is an offer that has no MIME types offered, we'd fail when trying to report it to the user. Make sure to handle this case explicitly, and report it the same way as there no being any offer at all. --- src/wl-paste.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/wl-paste.c b/src/wl-paste.c index d9a405a..dee2fad 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -210,7 +210,13 @@ static int run_paste_command(int stdin_fd, const char *clipboard_state) { return 1; } -static void complain_no_suitable_type() { +static void complain_no_suitable_type(const struct types *types) { + if (types->any == NULL) { + /* Report this the same way as + * there being no offer at all. + */ + bail("Nothing is copied"); + } fprintf(stderr, "Clipboard content is not available as "); if (options.explicit_type != NULL) { fprintf(stderr, "requested type \"%s\"\n", options.explicit_type); @@ -277,7 +283,7 @@ static void selection_callback(struct offer *offer, int primary) { offer_destroy(offer); return; } - complain_no_suitable_type(); + complain_no_suitable_type(&types); } /* Never append a newline character to binary content */ From 4f20741f66c4e9bcf45481e941e344f8fe5e998d Mon Sep 17 00:00:00 2001 From: Florian Grabmeier Date: Wed, 27 Dec 2023 19:37:48 +0100 Subject: [PATCH 123/139] Fix wrong flushing issue Signed-off-by: Florian Grabmeier --- src/util/files.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/files.c b/src/util/files.c index eade589..d2f1c4d 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -69,7 +69,7 @@ void complain_about_closed_stdio(struct wl_display *wl_display) { FILE *tty = fopen("/dev/tty", "w"); if (tty != NULL) { fprintf(tty, "%s\n", message); - fflush(stderr); + fflush(tty); abort(); } From e3c1b21ddd0885fba909602cf21e9f4f35862dea Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 24 Mar 2025 21:22:02 +0300 Subject: [PATCH 124/139] Add --sensitive flag to wl-copy This currently sets the x-kde-passwordManagerHint, which is also set by "some password managers", notably KWallet, and respected by Klipper. Should a proper way to indicate a data source as sensitive appear, this same option should be setting that, too. See https://github.com/bugaevc/wl-clipboard/pull/214 Closes https://github.com/bugaevc/wl-clipboard/issues/177 Co-authored-by: Puck Meerburg --- data/wl-clipboard.1 | 6 ++++++ src/types/copy-action.c | 16 +++++++++++++++- src/types/copy-action.h | 1 + src/util/string.h | 1 + src/wl-copy.c | 10 ++++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index 6846977..2236a82 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -96,6 +96,12 @@ of the spawned processes (see below). .IP This mode requires a compositor that supports the wlroots data-control protocol. .TP +\fB\-\-sensitive\fR (for \fBwl-copy\fR) +Hint that the data being copied contains passwords, keys, or other sensitive +content. Some clipboard managers may react by not persisting the copied data in +the clipboard history. This corresponds to \fBCLIPBOARD_STATE=sensitive\fR (see +below). +.TP \fB\-v\fR, \fB\-\-version Display the version of wl-clipboard and some short info about its license. .TP diff --git a/src/types/copy-action.c b/src/types/copy-action.c index 19b7599..9975c35 100644 --- a/src/types/copy-action.c +++ b/src/types/copy-action.c @@ -69,9 +69,23 @@ static void on_focus( static void do_send(struct source *source, const char *mime_type, int fd) { struct copy_action *self = source->data; - /* Unset O_NONBLOCK */ + /* Unset O_NONBLOCK */ fcntl(fd, F_SETFL, 0); + if (!strcmp(mime_type, x_kde_password_manager_hint)) { + /* We always respond to x-kde-passwordManagerHint, + * even though we only offer it when --sensitive + * is set. + */ + const char *data = self->sensitive ? "secret" : "public"; + write(fd, data, strlen(data)); + close(fd); + /* This does not count as pasting, so don't invoke + * our pasted_callback, just return. + */ + return; + } + if (self->fd_to_copy_from != -1) { /* Copy the file to the given file descriptor * by spawning an appropriate cat process. diff --git a/src/types/copy-action.h b/src/types/copy-action.h index babe179..426e929 100644 --- a/src/types/copy-action.h +++ b/src/types/copy-action.h @@ -33,6 +33,7 @@ struct copy_action { struct source *source; struct popup_surface *popup_surface; int primary; + int sensitive; void (*did_set_selection_callback)(struct copy_action *self); void (*pasted_callback)(struct copy_action *self); diff --git a/src/util/string.h b/src/util/string.h index 1477944..8275d7a 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -21,6 +21,7 @@ #define text_plain "text/plain" #define text_plain_utf8 "text/plain;charset=utf-8" +#define x_kde_password_manager_hint "x-kde-passwordManagerHint" typedef char * const *argv_t; diff --git a/src/wl-copy.c b/src/wl-copy.c index b040cb6..bee20ff 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -43,6 +43,7 @@ static struct { int trim_newline; int paste_once; int primary; + int sensitive; const char *seat_name; } options; @@ -121,6 +122,7 @@ static void print_usage(FILE *f, const char *argv0) { "\t-n, --trim-newline\tDo not copy the trailing newline character.\n" "\t-t, --type mime/type\t" "Override the inferred MIME type for the content.\n" + "\t --sensitive\t\tHint that the content is sensitive.\n" "\t-s, --seat seat-name\t" "Pick the seat to work with.\n" "\t-v, --version\t\tDisplay version info.\n" @@ -147,6 +149,7 @@ static void parse_options(int argc, argv_t argv) { {"foreground", no_argument, 0, 'f'}, {"clear", no_argument, 0, 'c'}, {"type", required_argument, 0, 't'}, + {"sensitive", no_argument, 0, 'S'}, {"seat", required_argument, 0, 's'}, {0, 0, 0, 0} }; @@ -185,6 +188,9 @@ static void parse_options(int argc, argv_t argv) { case 't': options.mime_type = strdup(optarg); break; + case 'S': + options.sensitive = 1; + break; case 's': options.seat_name = strdup(optarg); break; @@ -246,6 +252,7 @@ int main(int argc, argv_t argv) { copy_action->fd_to_copy_from = -1; copy_action->device = device; copy_action->primary = options.primary; + copy_action->sensitive = options.sensitive; if (!options.clear) { if (optind < argc) { @@ -305,6 +312,9 @@ int main(int argc, argv_t argv) { } free(options.mime_type); options.mime_type = NULL; + if (options.sensitive) { + source_offer(copy_action->source, x_kde_password_manager_hint); + } } if (device->needs_popup_surface) { From 1e50b65d5ef94d2e595cfaf30a81f933ba80b1f9 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 24 Mar 2025 21:43:14 +0300 Subject: [PATCH 125/139] Set CLIPBOARD_STATE=sensitive when x-kde-passwordManagerHint is set See https://github.com/bugaevc/wl-clipboard/pull/204 --- data/wl-clipboard.1 | 5 +++-- src/wl-paste.c | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index 2236a82..f7f3a90 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -155,8 +155,9 @@ as to fall back to some sensible behavior if \fBCLIPBOARD_STATE\fR is unset or set to some unrecognized value (this is to leave the design space open for future extensions). However, the currently existing Wayland clipboard protocols don't let wl-clipboard identify the cases where \fBclear\fR and \fBsensitive\fR -values should be set, so currently wl-clipboard only ever sets -\fBCLIPBOARD_STATE\fR to \fBdata\fR or \fBnil\fR. +values should be set. For this reason, currently, wl-clipboard never actually +sets \fBCLIPBOARD_STATE\fR to \fBclear\fR, and only sets it to \fBsensitive\fR +when it enounters \fBx-kde-passwordManagerHint\fR among the MIME types. .IP The \fBCLIPBOARD_STATE\fR protocol was intentionally designed to not be specific to either wl-clipboard or Wayland; in fact, other clipboard tools are encouraged diff --git a/src/wl-paste.c b/src/wl-paste.c index dee2fad..63fe526 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -53,6 +53,7 @@ struct types { int inferred_available; int plain_text_utf8_available; int plain_text_available; + int has_sensitive_hint; const char *having_explicit_as_prefix; const char *any_text; const char *any; @@ -99,6 +100,14 @@ static struct types classify_offer_types(struct offer *offer) { ) { types.having_explicit_as_prefix = mime_type; } + if (strcmp(mime_type, x_kde_password_manager_hint) == 0) { + /* We should be checking if it contains + * the string "secret" as opposed to "public", + * but for now let's just use the presence + * of the type as an indication. + */ + types.has_sensitive_hint = 1; + } } return types; } @@ -319,7 +328,11 @@ static void selection_callback(struct offer *offer, int primary) { wl_display_flush(wl_display); close(pipefd[1]); - rc = run_paste_command(pipefd[0], "data"); + const char *clipboard_state = "data"; + if (types.has_sensitive_hint) { + clipboard_state = "sensitive"; + } + rc = run_paste_command(pipefd[0], clipboard_state); if (!rc) { if (options.watch) { /* Try to cope without exiting completely */ From 424517085c45849edfeff72a4e3cc0724f54404a Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Mon, 24 Mar 2025 21:54:09 +0300 Subject: [PATCH 126/139] Bump copyright year to 2025 As well as update the man page date. --- data/wl-clipboard.1 | 4 ++-- src/includes/selection-protocols.h | 2 +- src/includes/shell-protocols.h | 2 +- src/types/copy-action.c | 2 +- src/types/copy-action.h | 2 +- src/types/device-manager.c | 2 +- src/types/device-manager.h | 2 +- src/types/device.c | 2 +- src/types/device.h | 2 +- src/types/keyboard.c | 2 +- src/types/keyboard.h | 2 +- src/types/offer.c | 2 +- src/types/offer.h | 2 +- src/types/popup-surface.c | 2 +- src/types/popup-surface.h | 2 +- src/types/registry.c | 2 +- src/types/registry.h | 2 +- src/types/seat.c | 2 +- src/types/seat.h | 2 +- src/types/shell-surface.c | 2 +- src/types/shell-surface.h | 2 +- src/types/shell.c | 2 +- src/types/shell.h | 2 +- src/types/source.c | 2 +- src/types/source.h | 2 +- src/util/files.c | 2 +- src/util/files.h | 2 +- src/util/misc.c | 4 ++-- src/util/misc.h | 2 +- src/util/string.c | 2 +- src/util/string.h | 2 +- src/wl-copy.c | 2 +- src/wl-paste.c | 2 +- 33 files changed, 35 insertions(+), 35 deletions(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index f7f3a90..f106ecb 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -1,4 +1,4 @@ -.TH WL-CLIPBOARD 1 2023-04-22 wl-clipboard +.TH WL-CLIPBOARD 1 2025-03-24 wl-clipboard .SH NAME wl-clipboard \- Wayland copy and paste command line utilities .SH SYNOPSIS @@ -250,7 +250,7 @@ Report wl-clipboard bugs to Please make sure to mention which Wayland compositor you are using, and attach \fBWAYLAND_DEBUG=1\fR debugging logs of wl-clipboard. .SH COPYRIGHT -Copyright \(co 2018-2023 Sergey Bugaev. +Copyright \(co 2018-2025 Sergey Bugaev. License GPLv3+: GNU GPL version 3 or later . .br This is free software: you are free to change and redistribute it. diff --git a/src/includes/selection-protocols.h b/src/includes/selection-protocols.h index 768cdf1..3f59f3d 100644 --- a/src/includes/selection-protocols.h +++ b/src/includes/selection-protocols.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/includes/shell-protocols.h b/src/includes/shell-protocols.h index a71bddc..5118bc3 100644 --- a/src/includes/shell-protocols.h +++ b/src/includes/shell-protocols.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/copy-action.c b/src/types/copy-action.c index 9975c35..9471e87 100644 --- a/src/types/copy-action.c +++ b/src/types/copy-action.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/copy-action.h b/src/types/copy-action.h index 426e929..04f1b7a 100644 --- a/src/types/copy-action.h +++ b/src/types/copy-action.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device-manager.c b/src/types/device-manager.c index 5e88189..6571be3 100644 --- a/src/types/device-manager.c +++ b/src/types/device-manager.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device-manager.h b/src/types/device-manager.h index f6de074..2414cf6 100644 --- a/src/types/device-manager.h +++ b/src/types/device-manager.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device.c b/src/types/device.c index 4d81b94..2c586e9 100644 --- a/src/types/device.c +++ b/src/types/device.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device.h b/src/types/device.h index 041a9e8..6ffa32a 100644 --- a/src/types/device.h +++ b/src/types/device.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/keyboard.c b/src/types/keyboard.c index 6090b5f..bc2c168 100644 --- a/src/types/keyboard.c +++ b/src/types/keyboard.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/keyboard.h b/src/types/keyboard.h index fe0b27f..9a32ac7 100644 --- a/src/types/keyboard.h +++ b/src/types/keyboard.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/offer.c b/src/types/offer.c index f5be19b..369682d 100644 --- a/src/types/offer.c +++ b/src/types/offer.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/offer.h b/src/types/offer.h index a12d786..1c4d120 100644 --- a/src/types/offer.h +++ b/src/types/offer.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index 9c42c08..29ce693 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/popup-surface.h b/src/types/popup-surface.h index d81fe37..f74a0ed 100644 --- a/src/types/popup-surface.h +++ b/src/types/popup-surface.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/registry.c b/src/types/registry.c index 1c7ad99..9574ead 100644 --- a/src/types/registry.c +++ b/src/types/registry.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/registry.h b/src/types/registry.h index d6c64ce..af66b35 100644 --- a/src/types/registry.h +++ b/src/types/registry.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/seat.c b/src/types/seat.c index 59e7df8..d345f34 100644 --- a/src/types/seat.c +++ b/src/types/seat.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/seat.h b/src/types/seat.h index 8ffade7..3f362fa 100644 --- a/src/types/seat.h +++ b/src/types/seat.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell-surface.c b/src/types/shell-surface.c index 1940d4d..935d9b9 100644 --- a/src/types/shell-surface.c +++ b/src/types/shell-surface.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell-surface.h b/src/types/shell-surface.h index f8cef96..4be8612 100644 --- a/src/types/shell-surface.h +++ b/src/types/shell-surface.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell.c b/src/types/shell.c index 12ef997..6b93ff2 100644 --- a/src/types/shell.c +++ b/src/types/shell.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell.h b/src/types/shell.h index 99369ce..79218e7 100644 --- a/src/types/shell.h +++ b/src/types/shell.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/source.c b/src/types/source.c index 45b0f14..f9e5839 100644 --- a/src/types/source.c +++ b/src/types/source.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/source.h b/src/types/source.h index a0cd28b..cfbf8db 100644 --- a/src/types/source.h +++ b/src/types/source.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/files.c b/src/util/files.c index d2f1c4d..8f9d481 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/files.h b/src/util/files.h index b1e8a62..05ba140 100644 --- a/src/util/files.h +++ b/src/util/files.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/misc.c b/src/util/misc.c index 1a0e628..fffd561 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,7 +27,7 @@ void print_version_info() { printf( "wl-clipboard " PROJECT_VERSION "\n" - "Copyright (C) 2018-2023 Sergey Bugaev\n" + "Copyright (C) 2018-2025 Sergey Bugaev\n" "License GPLv3+: GNU GPL version 3 or later" " .\n" "This is free software: you are free to change and redistribute it.\n" diff --git a/src/util/misc.h b/src/util/misc.h index 1bf81a8..ac7599c 100644 --- a/src/util/misc.h +++ b/src/util/misc.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/string.c b/src/util/string.c index bfea1de..330d748 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/string.h b/src/util/string.h index 8275d7a..808d4e7 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/wl-copy.c b/src/wl-copy.c index bee20ff..72a35f7 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/wl-paste.c b/src/wl-paste.c index 63fe526..d07bdd7 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2023 Sergey Bugaev + * Copyright © 2018-2025 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 091d6028b5c9db75ad36f9fceb0db3ee718045fa Mon Sep 17 00:00:00 2001 From: Vlad Zahorodnii Date: Sat, 12 Apr 2025 16:47:22 +0300 Subject: [PATCH 127/139] Add support for ext-data-control-v1 The ext-data-control protocol is identical to the wlr-data-control protocol, except that it is governed by the wayland-protocols now. This change was tested in Plasma Wayland with and without the ext_data_control_manager_v1 global. Closes #242 Signed-off-by: Vlad Zahorodnii --- src/includes/selection-protocols.h | 4 ++ src/protocol/meson.build | 8 +++ src/types/device-manager.c | 20 +++++++ src/types/device-manager.h | 6 ++ src/types/device.c | 89 ++++++++++++++++++++++++++++-- src/types/device.h | 4 ++ src/types/offer.c | 10 ++++ src/types/offer.h | 4 ++ src/types/registry.c | 21 ++++++- src/types/registry.h | 4 ++ src/types/source.c | 11 ++++ src/types/source.h | 4 ++ src/util/misc.c | 6 +- 13 files changed, 179 insertions(+), 12 deletions(-) diff --git a/src/includes/selection-protocols.h b/src/includes/selection-protocols.h index 3f59f3d..e8e6424 100644 --- a/src/includes/selection-protocols.h +++ b/src/includes/selection-protocols.h @@ -35,4 +35,8 @@ # include "wlr-data-control.h" #endif +#ifdef HAVE_EXT_DATA_CONTROL +# include "ext-data-control.h" +#endif + #endif /* INCLUDES_SELECTION_PROTOCOLS_H */ diff --git a/src/protocol/meson.build b/src/protocol/meson.build index 743b299..b63218b 100644 --- a/src/protocol/meson.build +++ b/src/protocol/meson.build @@ -14,6 +14,7 @@ if wayland_scanner.found() # these are bundled have_gtk_primary_selection = true have_wlr_data_control = true + have_ext_data_control = wayland_protocols.found() and wayland_protocols.version().version_compare('>= 1.39') have_gtk_shell = true if wayland.version().version_compare('>= 1.15') @@ -27,6 +28,7 @@ else have_xdg_activation = false have_gtk_primary_selection = false have_wlr_data_control = false + have_ext_data_control = false have_gtk_shell = false endif @@ -35,6 +37,7 @@ conf_data.set('HAVE_XDG_SHELL', have_xdg_shell) conf_data.set('HAVE_WP_PRIMARY_SELECTION', have_wp_primary_selection) conf_data.set('HAVE_GTK_PRIMARY_SELECTION', have_gtk_primary_selection) conf_data.set('HAVE_WLR_DATA_CONTROL', have_wlr_data_control) +conf_data.set('HAVE_EXT_DATA_CONTROL', have_ext_data_control) conf_data.set('HAVE_XDG_ACTIVATION', have_xdg_activation) conf_data.set('HAVE_GTK_SHELL', have_gtk_shell) @@ -72,6 +75,11 @@ if have_wlr_data_control protocols += [['wlr-data-control', wlr_data_control_xml]] endif +if have_ext_data_control + ext_data_control_xml = join_paths(protocols_path, 'staging', 'ext-data-control', 'ext-data-control-v1.xml') + protocols += [['ext-data-control', ext_data_control_xml]] +endif + if have_xdg_activation xdg_activation_xml = join_paths(protocols_path, 'staging', 'xdg-activation', 'xdg-activation-v1.xml') protocols += [['xdg-activation', xdg_activation_xml]] diff --git a/src/types/device-manager.c b/src/types/device-manager.c index 6571be3..5ed49a3 100644 --- a/src/types/device-manager.c +++ b/src/types/device-manager.c @@ -138,3 +138,23 @@ GET_DEVICE( INIT(zwlr_data_control_manager_v1) #endif /* HAVE_WLR_DATA_CONTROL */ + +/* ext-data-control implementation */ + +#ifdef HAVE_EXT_DATA_CONTROL + +CREATE_SOURCE( + ext_data_control_manager_v1, + ext_data_control_source_v1, + create_data_source +) + +GET_DEVICE( + ext_data_control_manager_v1, + ext_data_control_device_v1, + get_data_device +) + +INIT(ext_data_control_manager_v1) + +#endif /* HAVE_EXT_DATA_CONTROL */ diff --git a/src/types/device-manager.h b/src/types/device-manager.h index 2414cf6..458c261 100644 --- a/src/types/device-manager.h +++ b/src/types/device-manager.h @@ -69,4 +69,10 @@ void device_manager_init_zwlr_data_control_manager_v1( ); #endif +#ifdef HAVE_EXT_DATA_CONTROL +void device_manager_init_ext_data_control_manager_v1( + struct device_manager *self +); +#endif + #endif /* TYPES_DEVICE_MANAGER_H */ diff --git a/src/types/device.c b/src/types/device.c index 2c586e9..a9e1031 100644 --- a/src/types/device.c +++ b/src/types/device.c @@ -25,6 +25,12 @@ #include #include +enum TriState { + Unknown, + Yes, + No +}; + int device_supports_selection(struct device *self, int primary) { return self->supports_selection(self, primary); } @@ -195,12 +201,6 @@ INIT(zwp_primary_selection_device_v1, 1) #ifdef HAVE_WLR_DATA_CONTROL -enum TriState { - Unknown, - Yes, - No -}; - /* Whether wlr-data-control supports primary selection */ static enum TriState device_wlr_supports_primary_selection = Unknown; static int device_get_wlr_supports_selection(struct device *self, int primary) { @@ -272,3 +272,80 @@ zwlr_data_control_device_v1_listener = { INIT(zwlr_data_control_device_v1, 0) #endif /* HAVE_WLR_DATA_CONTROL */ + + +/* ext-data-control implementation */ + +#ifdef HAVE_EXT_DATA_CONTROL + +/* Whether ext-data-control supports primary selection */ +static enum TriState device_ext_supports_primary_selection = Unknown; +static int device_get_ext_supports_selection(struct device *self, int primary) { + if (!primary) { + return 1; + } + + if (device_ext_supports_primary_selection == Yes) { + return 1; + } else if (device_ext_supports_primary_selection == No) { + return 0; + } + + wl_display_roundtrip(self->wl_display); + + if (device_ext_supports_primary_selection == Yes) { + return 1; + } else { + device_ext_supports_primary_selection = No; + return 0; + } +} + +SUPPORTS_SELECTION( + ext_data_control_device_v1, + device_get_ext_supports_selection(self, primary) +) + +SET_SELECTION_IMPL(ext_data_control_device_v1, ext_data_control_source_v1, { + if (!primary) { + ext_data_control_device_v1_set_selection(device, source); + } else { + ext_data_control_device_v1_set_primary_selection(device, source); + } +}) + +DATA_OFFER_HANDLER(ext_data_control_device_v1, ext_data_control_offer_v1) + +SELECTION_HANDLER( + ext_data_control_device_v1, + ext_data_control_offer_v1, + selection, + 0 +) + +static void ext_data_control_device_v1_primary_selection_handler( + void *data, + struct ext_data_control_device_v1 *device, + struct ext_data_control_offer_v1 *offer_proxy +) { + device_ext_supports_primary_selection = Yes; + struct device *self = data; + struct offer *offer = NULL; + if (offer_proxy != NULL) { + offer = wl_proxy_get_user_data((struct wl_proxy *) offer_proxy); + } + if (self->selection_callback != NULL) { + self->selection_callback(offer, 1); + } +} + +static const struct ext_data_control_device_v1_listener +ext_data_control_device_v1_listener = { + .data_offer = ext_data_control_device_v1_data_offer_handler, + .selection = ext_data_control_device_v1_selection_handler, + .primary_selection = ext_data_control_device_v1_primary_selection_handler +}; + +INIT(ext_data_control_device_v1, 0) + +#endif /* HAVE_EXT_DATA_CONTROL */ diff --git a/src/types/device.h b/src/types/device.h index 6ffa32a..f91aa19 100644 --- a/src/types/device.h +++ b/src/types/device.h @@ -71,4 +71,8 @@ void device_init_zwp_primary_selection_device_v1(struct device *self); void device_init_zwlr_data_control_device_v1(struct device *self); #endif +#ifdef HAVE_EXT_DATA_CONTROL +void device_init_ext_data_control_device_v1(struct device *self); +#endif + #endif /* TYPES_DEVICE_H */ diff --git a/src/types/offer.c b/src/types/offer.c index 369682d..4d1eb41 100644 --- a/src/types/offer.c +++ b/src/types/offer.c @@ -105,3 +105,13 @@ INIT(zwlr_data_control_offer_v1) #endif /* HAVE_WLR_DATA_CONTROL */ + +/* ext-data-control implementation */ + +#ifdef HAVE_EXT_DATA_CONTROL + +OFFER_HANDLER(ext_data_control_offer_v1) +LISTENER(ext_data_control_offer_v1) +INIT(ext_data_control_offer_v1) + +#endif /* HAVE_EXT_DATA_CONTROL */ diff --git a/src/types/offer.h b/src/types/offer.h index 1c4d120..c5cc5e8 100644 --- a/src/types/offer.h +++ b/src/types/offer.h @@ -61,4 +61,8 @@ void offer_init_zwp_primary_selection_offer_v1(struct offer *self); void offer_init_zwlr_data_control_offer_v1(struct offer *self); #endif +#ifdef HAVE_EXT_DATA_CONTROL +void offer_init_ext_data_control_offer_v1(struct offer *self); +#endif + #endif /* TYPES_OFFER_H */ diff --git a/src/types/registry.c b/src/types/registry.c index 9574ead..1bea0e4 100644 --- a/src/types/registry.c +++ b/src/types/registry.c @@ -83,6 +83,10 @@ static void wl_registry_global_handler( BIND(zwlr_data_control_manager_v1, version > 2 ? 2 : version) #endif +#ifdef HAVE_EXT_DATA_CONTROL + BIND(ext_data_control_manager_v1, 1) +#endif + if (strcmp(interface, "wl_seat") == 0 && version >= 2) { struct seat *seat = calloc(1, sizeof(struct seat)); seat->proxy = wl_registry_bind( @@ -149,12 +153,15 @@ struct device_manager *registry_find_device_manager( = calloc(1, sizeof(struct device_manager)); device_manager->wl_display = self->wl_display; - /* For regular selection, we just look at the two supported - * protocols. We prefer wlr-data-control, as it doesn't require - * us to use the popup surface hack. + /* For regular selection, we just look at the three supported + * protocols. We prefer ext-data-control or wlr-data-control, + * as they don't require us to use the popup surface hack. */ if (!primary) { +#ifdef HAVE_EXT_DATA_CONTROL + TRY(ext_data_control_manager_v1) +#endif #ifdef HAVE_WLR_DATA_CONTROL TRY(zwlr_data_control_manager_v1) #endif @@ -172,8 +179,16 @@ struct device_manager *registry_find_device_manager( * that if a compositor supports primary selection at all, then * if it supports wlr-data-control v2 it also supports primary * selection over wlr-data-control; which is only reasonable. + * + * The same goes for the newer ext-data-control, which has + * potential support for primary selection since v1, so no need + * for the version check there. */ +#ifdef HAVE_EXT_DATA_CONTROL + TRY(ext_data_control_manager_v1) +#endif + #ifdef HAVE_WLR_DATA_CONTROL if (self->zwlr_data_control_manager_v1 != NULL) { struct wl_proxy *proxy diff --git a/src/types/registry.h b/src/types/registry.h index af66b35..dca9ded 100644 --- a/src/types/registry.h +++ b/src/types/registry.h @@ -69,6 +69,10 @@ struct registry { struct zwlr_data_control_manager_v1 *zwlr_data_control_manager_v1; #endif +#ifdef HAVE_EXT_DATA_CONTROL + struct ext_data_control_manager_v1 + *ext_data_control_manager_v1; +#endif }; void registry_init(struct registry *self); diff --git a/src/types/source.c b/src/types/source.c index f9e5839..0d62380 100644 --- a/src/types/source.c +++ b/src/types/source.c @@ -127,3 +127,14 @@ INIT(zwlr_data_control_source_v1) #endif /* HAVE_WLR_DATA_CONTROL */ + +/* ext-data-control implementation */ + +#ifdef HAVE_EXT_DATA_CONTROL + +SEND_HANDLER(ext_data_control_source_v1) +CANCELLED_HANDLER(ext_data_control_source_v1) +LISTENER(ext_data_control_source_v1) +INIT(ext_data_control_source_v1) + +#endif /* HAVE_EXT_DATA_CONTROL */ diff --git a/src/types/source.h b/src/types/source.h index cfbf8db..1bd7f3e 100644 --- a/src/types/source.h +++ b/src/types/source.h @@ -52,4 +52,8 @@ void source_init_zwp_primary_selection_source_v1(struct source *self); void source_init_zwlr_data_control_source_v1(struct source *self); #endif +#ifdef HAVE_EXT_DATA_CONTROL +void source_init_ext_data_control_source_v1(struct source *self); +#endif + #endif /* TYPES_SOURCE_H */ diff --git a/src/util/misc.c b/src/util/misc.c index fffd561..24e290f 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -49,15 +49,15 @@ void complain_about_selection_support(int primary) { } void complain_about_watch_mode_support() { -#ifdef HAVE_WLR_DATA_CONTROL +#if defined(HAVE_WLR_DATA_CONTROL) || defined(HAVE_EXT_DATA_CONTROL) bail( "Watch mode requires a compositor that supports" - " the wlroots data-control protocol" + " the data-control protocol" ); #else bail( "wl-clipboard was built without support for" - " the wlroots data-control protocol" + " the data-control protocol" ); #endif } From aaa927ee7f7d91bcc25a3b68f60d01005d3b0f7f Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 24 Apr 2025 16:49:07 +0300 Subject: [PATCH 128/139] Respect $TMPDIR Fixes https://github.com/bugaevc/wl-clipboard/issues/257 --- src/util/files.c | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/util/files.c b/src/util/files.c index 8f9d481..13a09cd 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -242,13 +242,6 @@ char *infer_mime_type_from_name(const char *file_path) { /* Returns the name of a new file */ char *dump_stdin_into_a_temp_file() { - /* Create a temp directory to host out file */ - char dirpath[] = "/tmp/wl-copy-buffer-XXXXXX"; - if (mkdtemp(dirpath) != dirpath) { - perror("mkdtemp"); - exit(1); - } - /* Pick a name for the file we'll be * creating inside that directory. We * try to preserve the origial name for @@ -257,11 +250,29 @@ char *dump_stdin_into_a_temp_file() { char *original_path = path_for_fd(STDIN_FILENO); char *name = original_path != NULL ? basename(original_path) : "stdin"; - /* Construct the path */ - char *res_path = malloc(strlen(dirpath) + 1 + strlen(name) + 1); - memcpy(res_path, dirpath, sizeof(dirpath)); - res_path[sizeof(dirpath) - 1] = '/'; - strcpy(res_path + sizeof(dirpath), name); + /* Create a temp directory to host out file */ + const char *tmpdir = getenv("TMPDIR"); + if (tmpdir == NULL) { + tmpdir = "/tmp"; + } + size_t tmpdir_len = strlen(tmpdir); + static const char dir_template[] = "wl-copy-buffer-XXXXXX"; + char *path = malloc( + tmpdir_len + 1 + strlen(dir_template) + 1 + strlen(name) + 1 + ); + memcpy(path, tmpdir, tmpdir_len); + path[tmpdir_len] = '/'; + memcpy(path + tmpdir_len + 1, dir_template, strlen(dir_template)); + size_t prefix_len = tmpdir_len + 1 + strlen(dir_template); + path[prefix_len] = 0; + + if (mkdtemp(path) != path) { + perror("mkdtemp"); + exit(1); + } + + path[prefix_len] = '/'; + strcpy(path + prefix_len + 1, name); /* Spawn cat to perform the copy */ pid_t pid = fork(); @@ -270,7 +281,7 @@ char *dump_stdin_into_a_temp_file() { exit(1); } if (pid == 0) { - int fd = creat(res_path, S_IRUSR | S_IWUSR); + int fd = creat(path, S_IRUSR | S_IWUSR); if (fd < 0) { perror("creat"); exit(1); @@ -286,11 +297,9 @@ char *dump_stdin_into_a_temp_file() { int wstatus; waitpid(pid, &wstatus, 0); - if (original_path != NULL) { - free(original_path); - } + free(original_path); if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { bail("Failed to copy the file"); } - return res_path; + return path; } From 927515b0862da048ad966ccb12ac1f947827b3a1 Mon Sep 17 00:00:00 2001 From: Aleksandr Mezin Date: Sat, 9 Dec 2023 02:09:55 +0200 Subject: [PATCH 129/139] Set app id/class for the popup surface This should allow other apps to detect wl-clipboard and implement the necessary workarounds. --- src/types/shell-surface.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/shell-surface.c b/src/types/shell-surface.c index 935d9b9..592ed2b 100644 --- a/src/types/shell-surface.c +++ b/src/types/shell-surface.c @@ -67,6 +67,7 @@ void shell_surface_init_wl_shell_surface(struct shell_surface *self) { wl_shell_surface_add_listener(proxy, &wl_shell_surface_listener, self); wl_shell_surface_set_toplevel(proxy); wl_shell_surface_set_title(proxy, "wl-clipboard"); + wl_shell_surface_set_class(proxy, "io.github.bugaevc.wl-clipboard"); self->do_destroy = destroy_wl_shell_surface; } @@ -119,6 +120,7 @@ void shell_surface_init_xdg_surface(struct shell_surface *self) { self->proxy2 = (struct wl_proxy *) toplevel; xdg_toplevel_add_listener(toplevel, &xdg_toplevel_listener, self); xdg_toplevel_set_title(toplevel, "wl-clipboard"); + xdg_toplevel_set_app_id(toplevel, "io.github.bugaevc.wl-clipboard"); self->do_destroy = destroy_xdg_surface; } From e8082035dafe0241739d7f7d16f7ecfd2ce06172 Mon Sep 17 00:00:00 2001 From: Ron Nazarov Date: Mon, 18 Nov 2024 14:22:54 +0000 Subject: [PATCH 130/139] Fix buffer overflow in infer_mime_type_from_contents Return early if len == 0 and don't attempt to trim a nonexistent newline. Fixes https://github.com/bugaevc/wl-clipboard/issues/243 --- src/util/files.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/util/files.c b/src/util/files.c index 13a09cd..738f3ef 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -186,11 +186,15 @@ char *infer_mime_type_from_contents(const char *file_path) { /* Read the result */ char *res = malloc(256); - size_t len = read(pipefd[0], res, 256); + ssize_t len = read(pipefd[0], res, 256); + close(pipefd[0]); + if (len <= 0) { + free(res); + return NULL; + } /* Trim the newline */ len--; res[len] = 0; - close(pipefd[0]); if (str_has_prefix(res, "inode/")) { free(res); From ffa52935e13cf1f813e2dc748e59d154ce4abceb Mon Sep 17 00:00:00 2001 From: anteater <65555601+nt8r@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:01:50 +0000 Subject: [PATCH 131/139] files: read shared-mime-info globs2 file for mime extensions This makes wl-paste mime detection work on systems with shared-mime-info, which is used by most desktop programs over mailcap's /etc/mime.types --- BUILDING.md | 9 +++++--- src/util/files.c | 60 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index 6ec5612..08fc58e 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -40,8 +40,11 @@ Optional dependencies for running: `xdg-utils`) * `/etc/mime.types` file for type inference in `wl-paste` (try package named `mime-support` or `mailcap`) +* `/usr/share/mime/globs2` file for for type inference in `wl-paste` (try + packaged named `shared-mime-info`) If you're packaging wl-clipboard for a distribution, please consider making -packages providing `xdg-mime` and `/etc/mime.types` *weak* dependencies of the -package providing wl-clipboard, meaning ones that get installed along with -wl-clipboard by default, but are not strictly required by it. +packages providing `xdg-mime`, `/etc/mime.types`, and `/usr/share/mime/globs2` +*weak* dependencies of the package providing wl-clipboard, meaning ones that +get installed along with wl-clipboard by default, but are not strictly required +by it. diff --git a/src/util/files.c b/src/util/files.c index 738f3ef..dcb7b8c 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -28,6 +28,7 @@ #include #include #include +#include #include // open #include // open #include // exit @@ -204,9 +205,8 @@ char *infer_mime_type_from_contents(const char *file_path) { return res; } -char *infer_mime_type_from_name(const char *file_path) { - const char *actual_ext = get_file_extension(file_path); - if (actual_ext == NULL) { +static char *search_mime_dot_types_for_ext(const char *ext) { + if (ext == NULL) { return NULL; } @@ -219,7 +219,7 @@ char *infer_mime_type_from_name(const char *file_path) { } for (char line[200]; fgets(line, sizeof(line), f) != NULL;) { - /* Skip comments and black lines */ + /* Skip comments and blank lines */ if (line[0] == '#' || line[0] == '\n') { continue; } @@ -229,11 +229,12 @@ char *infer_mime_type_from_name(const char *file_path) { int consumed; if (sscanf(line, "%199s%n", mime_type, &consumed) != 1) { /* A malformed line, perhaps? */ + fprintf(stderr, "malformed mime.types line: %s\n", line); continue; } char *lineptr = line + consumed; - for (char ext[200]; sscanf(lineptr, "%199s%n", ext, &consumed) == 1;) { - if (strcmp(ext, actual_ext) == 0) { + for (char ext_pattern[200]; sscanf(lineptr, "%199s%n", ext_pattern, &consumed) == 1;) { + if (strcmp(ext_pattern, ext) == 0) { fclose(f); return strdup(mime_type); } @@ -244,6 +245,53 @@ char *infer_mime_type_from_name(const char *file_path) { return NULL; } +static char *search_shared_mime_info_globs_for_filename(const char *filename) { + FILE *f = fopen("/usr/share/mime/globs2", "r"); + if (f == NULL) { + f = fopen("/usr/local/share/mime/globs2", "r"); + } + if (f == NULL) { + return NULL; + } + + for (char line[200]; fgets(line, sizeof(line), f) != NULL;) { + /* Skip comments and blank lines */ + if (line[0] == '#' || line[0] == '\n') { + continue; + } + + /* Each line consists of colon-separated + * weight, mime type, and glob pattern. + * We ignore the weight. + */ + char mime_type[200]; + char filename_glob[200]; + if (sscanf(line, "%*d:%199[^:]:%199s\n", mime_type, filename_glob) != 2) { + /* A malformed line, perhaps? */ + fprintf(stderr, "malformed globs2 line: %s\n", line); + continue; + } + if (fnmatch(filename_glob, filename, 0) == 0) { + fclose(f); + return strdup(mime_type); + } + } + fclose(f); + return NULL; +} + +char *infer_mime_type_from_name(const char *file_path) { + const char *ext = get_file_extension(file_path); + char *file_path_dup = strdup(file_path); + const char *filename = basename(file_path_dup); + char *mime_type = search_shared_mime_info_globs_for_filename(filename); + if (!mime_type) { + mime_type = search_mime_dot_types_for_ext(ext); + } + free(file_path_dup); + return mime_type; +} + /* Returns the name of a new file */ char *dump_stdin_into_a_temp_file() { /* Pick a name for the file we'll be From c20a5fb1d5370cca77a6b285cefe2147f9655320 Mon Sep 17 00:00:00 2001 From: rezky_nightky Date: Tue, 2 Dec 2025 01:50:09 +0700 Subject: [PATCH 132/139] fix: correct spelling issues across docs and helpers - fix typos in CLIPBOARD_STATE man page entry (@data/wl-clipboard.1#152-160) - clean up wording in fish and zsh completions (@completions/fish/wl-paste.fish#23-33, @completions/zsh/_wl-copy#16-29) - correct comments in popup surface and file helpers (@src/types/popup-surface.c#81-95, @src/util/files.c#247-256) Author: rezky_nightky Timestamp: 2025-12-01T18:50:09Z Repository: wl-clipboard Branch: master Signing: GPG (989AF9F0) Performance: 5 files, +5/-5 lines --- completions/fish/wl-paste.fish | 2 +- completions/zsh/_wl-copy | 2 +- data/wl-clipboard.1 | 2 +- src/types/popup-surface.c | 2 +- src/util/files.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/completions/fish/wl-paste.fish b/completions/fish/wl-paste.fish index 2842019..04df3a4 100644 --- a/completions/fish/wl-paste.fish +++ b/completions/fish/wl-paste.fish @@ -22,7 +22,7 @@ function __wl_paste_types --description 'Print types with context' # Note fish does not handle passing unset variables # to commands well, thus setting clip to "--primary" and passing - # that to wl-paste wont work, so if statements are used instead + # that to wl-paste won't work, so if statements are used instead if test -n "$seat"; and test -n "$clip" wl-paste 2>/dev/null --seat "$seat" -p -l else if test -n "$seat" diff --git a/completions/zsh/_wl-copy b/completions/zsh/_wl-copy index 5fd9bd7..197a5b0 100644 --- a/completions/zsh/_wl-copy +++ b/completions/zsh/_wl-copy @@ -22,7 +22,7 @@ __all_seats() { fi if [[ -z $seats ]]; then - # seat0 is always a vaild seat and covers most cases, so its a good fallback. + # seat0 is always a valid seat and covers most cases, so it's a good fallback. compadd "$@" - seat0 else compadd "$@" -a seats diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index f106ecb..faba95d 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -157,7 +157,7 @@ future extensions). However, the currently existing Wayland clipboard protocols don't let wl-clipboard identify the cases where \fBclear\fR and \fBsensitive\fR values should be set. For this reason, currently, wl-clipboard never actually sets \fBCLIPBOARD_STATE\fR to \fBclear\fR, and only sets it to \fBsensitive\fR -when it enounters \fBx-kde-passwordManagerHint\fR among the MIME types. +when it encounters \fBx-kde-passwordManagerHint\fR among the MIME types. .IP The \fBCLIPBOARD_STATE\fR protocol was intentionally designed to not be specific to either wl-clipboard or Wayland; in fact, other clipboard tools are encouraged diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index 29ce693..d748fdd 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -84,7 +84,7 @@ void popup_surface_init(struct popup_surface *self) { if (self->wl_surface == NULL) { /* It's possible that we were given focus - * (without ever commiting a buffer) during + * (without ever committing a buffer) during * the above roundtrip, in which case we have * already fired the callback and have likely * already destroyed the surface. No need to diff --git a/src/util/files.c b/src/util/files.c index dcb7b8c..f88f1a6 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -296,7 +296,7 @@ char *infer_mime_type_from_name(const char *file_path) { char *dump_stdin_into_a_temp_file() { /* Pick a name for the file we'll be * creating inside that directory. We - * try to preserve the origial name for + * try to preserve the original name for * the mime type inference to work. */ char *original_path = path_for_fd(STDIN_FILENO); From 8da63985db50fe83962e396ca063e67f6fb4b157 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 22 Mar 2026 13:50:45 +0300 Subject: [PATCH 133/139] Add a package spec example Using some fictional syntax. Hopefully, this will help people see those recommended optional dependencies listed more clearly. --- BUILDING.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/BUILDING.md b/BUILDING.md index 08fc58e..0222fae 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -48,3 +48,16 @@ packages providing `xdg-mime`, `/etc/mime.types`, and `/usr/share/mime/globs2` *weak* dependencies of the package providing wl-clipboard, meaning ones that get installed along with wl-clipboard by default, but are not strictly required by it. + +A package definition for wl-clipboard might therefore contain something along +the following lines: + +``` +Package: wl-clipboard +Build-Depends: + gcc, meson, ninja, + wayland-devel, wayland-scanner, + wayland-protocols >= 1.12 +Recommends: + xdg-utils, mime-support, shared-mime-info +``` From 123f52fed124a1b35a5df71c91c1ec0c6ca5736a Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 22 Mar 2026 14:09:02 +0300 Subject: [PATCH 134/139] Bump copyright year --- data/wl-clipboard.1 | 4 ++-- src/includes/selection-protocols.h | 2 +- src/includes/shell-protocols.h | 2 +- src/types/copy-action.c | 2 +- src/types/copy-action.h | 2 +- src/types/device-manager.c | 2 +- src/types/device-manager.h | 2 +- src/types/device.c | 2 +- src/types/device.h | 2 +- src/types/keyboard.c | 2 +- src/types/keyboard.h | 2 +- src/types/offer.c | 2 +- src/types/offer.h | 2 +- src/types/popup-surface.c | 2 +- src/types/popup-surface.h | 2 +- src/types/registry.c | 2 +- src/types/registry.h | 2 +- src/types/seat.c | 2 +- src/types/seat.h | 2 +- src/types/shell-surface.c | 2 +- src/types/shell-surface.h | 2 +- src/types/shell.c | 2 +- src/types/shell.h | 2 +- src/types/source.c | 2 +- src/types/source.h | 2 +- src/util/files.c | 2 +- src/util/files.h | 2 +- src/util/misc.c | 4 ++-- src/util/misc.h | 2 +- src/util/string.c | 2 +- src/util/string.h | 2 +- src/wl-copy.c | 2 +- src/wl-paste.c | 2 +- 33 files changed, 35 insertions(+), 35 deletions(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index faba95d..421c6be 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -1,4 +1,4 @@ -.TH WL-CLIPBOARD 1 2025-03-24 wl-clipboard +.TH WL-CLIPBOARD 1 2026-03-22 wl-clipboard .SH NAME wl-clipboard \- Wayland copy and paste command line utilities .SH SYNOPSIS @@ -250,7 +250,7 @@ Report wl-clipboard bugs to Please make sure to mention which Wayland compositor you are using, and attach \fBWAYLAND_DEBUG=1\fR debugging logs of wl-clipboard. .SH COPYRIGHT -Copyright \(co 2018-2025 Sergey Bugaev. +Copyright \(co 2018-2026 Sergey Bugaev. License GPLv3+: GNU GPL version 3 or later . .br This is free software: you are free to change and redistribute it. diff --git a/src/includes/selection-protocols.h b/src/includes/selection-protocols.h index e8e6424..f01070b 100644 --- a/src/includes/selection-protocols.h +++ b/src/includes/selection-protocols.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/includes/shell-protocols.h b/src/includes/shell-protocols.h index 5118bc3..5d700d1 100644 --- a/src/includes/shell-protocols.h +++ b/src/includes/shell-protocols.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/copy-action.c b/src/types/copy-action.c index 9471e87..79b0b0a 100644 --- a/src/types/copy-action.c +++ b/src/types/copy-action.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/copy-action.h b/src/types/copy-action.h index 04f1b7a..6c92e9a 100644 --- a/src/types/copy-action.h +++ b/src/types/copy-action.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device-manager.c b/src/types/device-manager.c index 5ed49a3..55e7aa7 100644 --- a/src/types/device-manager.c +++ b/src/types/device-manager.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device-manager.h b/src/types/device-manager.h index 458c261..8629a57 100644 --- a/src/types/device-manager.h +++ b/src/types/device-manager.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device.c b/src/types/device.c index a9e1031..6282c1f 100644 --- a/src/types/device.c +++ b/src/types/device.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/device.h b/src/types/device.h index f91aa19..ad780e0 100644 --- a/src/types/device.h +++ b/src/types/device.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/keyboard.c b/src/types/keyboard.c index bc2c168..12eb82f 100644 --- a/src/types/keyboard.c +++ b/src/types/keyboard.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/keyboard.h b/src/types/keyboard.h index 9a32ac7..4e95d85 100644 --- a/src/types/keyboard.h +++ b/src/types/keyboard.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/offer.c b/src/types/offer.c index 4d1eb41..cf3c7ff 100644 --- a/src/types/offer.c +++ b/src/types/offer.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/offer.h b/src/types/offer.h index c5cc5e8..c8eaed1 100644 --- a/src/types/offer.h +++ b/src/types/offer.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/popup-surface.c b/src/types/popup-surface.c index d748fdd..b8eee53 100644 --- a/src/types/popup-surface.c +++ b/src/types/popup-surface.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/popup-surface.h b/src/types/popup-surface.h index f74a0ed..e6148a7 100644 --- a/src/types/popup-surface.h +++ b/src/types/popup-surface.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/registry.c b/src/types/registry.c index 1bea0e4..7eeaec4 100644 --- a/src/types/registry.c +++ b/src/types/registry.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/registry.h b/src/types/registry.h index dca9ded..30a62b5 100644 --- a/src/types/registry.h +++ b/src/types/registry.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/seat.c b/src/types/seat.c index d345f34..3d7e604 100644 --- a/src/types/seat.c +++ b/src/types/seat.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/seat.h b/src/types/seat.h index 3f362fa..edcbcd6 100644 --- a/src/types/seat.h +++ b/src/types/seat.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell-surface.c b/src/types/shell-surface.c index 592ed2b..f2b73ec 100644 --- a/src/types/shell-surface.c +++ b/src/types/shell-surface.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell-surface.h b/src/types/shell-surface.h index 4be8612..fe70baf 100644 --- a/src/types/shell-surface.h +++ b/src/types/shell-surface.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell.c b/src/types/shell.c index 6b93ff2..2a57f9f 100644 --- a/src/types/shell.c +++ b/src/types/shell.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/shell.h b/src/types/shell.h index 79218e7..5c8c2dd 100644 --- a/src/types/shell.h +++ b/src/types/shell.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/source.c b/src/types/source.c index 0d62380..78f00ae 100644 --- a/src/types/source.c +++ b/src/types/source.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/types/source.h b/src/types/source.h index 1bd7f3e..a29d816 100644 --- a/src/types/source.h +++ b/src/types/source.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/files.c b/src/util/files.c index f88f1a6..078c051 100644 --- a/src/util/files.c +++ b/src/util/files.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/files.h b/src/util/files.h index 05ba140..e39ca4f 100644 --- a/src/util/files.h +++ b/src/util/files.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/misc.c b/src/util/misc.c index 24e290f..4edd822 100644 --- a/src/util/misc.c +++ b/src/util/misc.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,7 +27,7 @@ void print_version_info() { printf( "wl-clipboard " PROJECT_VERSION "\n" - "Copyright (C) 2018-2025 Sergey Bugaev\n" + "Copyright (C) 2018-2026 Sergey Bugaev\n" "License GPLv3+: GNU GPL version 3 or later" " .\n" "This is free software: you are free to change and redistribute it.\n" diff --git a/src/util/misc.h b/src/util/misc.h index ac7599c..299a0a6 100644 --- a/src/util/misc.h +++ b/src/util/misc.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/string.c b/src/util/string.c index 330d748..9482bf9 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/util/string.h b/src/util/string.h index 808d4e7..e007f2e 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/wl-copy.c b/src/wl-copy.c index 72a35f7..660fb80 100644 --- a/src/wl-copy.c +++ b/src/wl-copy.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/wl-paste.c b/src/wl-paste.c index d07bdd7..e359ad9 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -1,6 +1,6 @@ /* wl-clipboard * - * Copyright © 2018-2025 Sergey Bugaev + * Copyright © 2018-2026 Sergey Bugaev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From a783546b9453d02f9acbd2cb34d96d2f90b12bd2 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 22 Mar 2026 14:29:00 +0300 Subject: [PATCH 135/139] Update libffi.wrap from WrapDB Fixes building on new-ish GCC, see https://github.com/libffi/libffi/issues/760 Closes https://github.com/bugaevc/wl-clipboard/issues/278 --- subprojects/libffi.wrap | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/subprojects/libffi.wrap b/subprojects/libffi.wrap index 5bdb9ea..18baccc 100644 --- a/subprojects/libffi.wrap +++ b/subprojects/libffi.wrap @@ -1,12 +1,13 @@ [wrap-file] -directory = libffi-3.4.4 -source_url = https://github.com/libffi/libffi/releases/download/v3.4.4/libffi-3.4.4.tar.gz -source_filename = libffi-3.4.4.tar.gz -source_hash = d66c56ad259a82cf2a9dfc408b32bf5da52371500b84745f7fb8b645712df676 -patch_filename = libffi_3.4.4-2_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/libffi_3.4.4-2/get_patch -patch_hash = 77da71839584dfc33d998b6461fff1ebc90a6f9c0a8c2f490335aafe370a115b -wrapdb_version = 3.4.4-2 +directory = libffi-3.5.2 +source_url = https://github.com/libffi/libffi/releases/download/v3.5.2/libffi-3.5.2.tar.gz +source_filename = libffi-3.5.2.tar.gz +source_hash = f3a3082a23b37c293a4fcd1053147b371f2ff91fa7ea1b2a52e335676bac82dc +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/libffi_3.5.2-2/libffi-3.5.2.tar.gz +patch_filename = libffi_3.5.2-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/libffi_3.5.2-2/get_patch +patch_hash = 74ed624f74cd860be3bdf6d473b70ab88707bdf2f940191fbcb577e2a49a9710 +wrapdb_version = 3.5.2-2 [provide] dependency_names = libffi From 67a7b937895bceec1ae5ccebb10216f63f70ca1b Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Sun, 22 Mar 2026 21:46:43 +0300 Subject: [PATCH 136/139] Release wl-clipboard 2.3 --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index b1148bd..3109a50 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('wl-clipboard', 'c', - version: '2.2.1', + version: '2.3.0', license: 'GPL-3.0-or-later', meson_version: '>= 0.47.0', default_options: 'c_std=gnu99' From ea2af55ffd16c90203879ec1cc5f7af8f6fe7891 Mon Sep 17 00:00:00 2001 From: Ariadna Vigo Date: Sat, 1 Aug 2026 23:12:28 +0200 Subject: [PATCH 137/139] data/wl-clipboard.1: Fixed typo 'wl-cipboard' -> 'wl-clipboard' (line 200) --- data/wl-clipboard.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index 421c6be..60c6d0b 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -197,7 +197,7 @@ well with clipboard managers that are overeager to preserve clipboard contents. .PP Applications written using the GTK 3 toolkit copy text with \(dq\er\en\(dq (also known as CR LF) line endings, which takes most other software by surprise. -wl-cipboard does nothing to rectify this. The recommended workaround is piping +wl-clipboard does nothing to rectify this. The recommended workaround is piping \fBwl-paste\fR output through .BR dos2unix (1) when pasting from a GTK 3 application. From 7495661ad900f50381735f714b9a2c6c8d30bde4 Mon Sep 17 00:00:00 2001 From: sentriz Date: Wed, 5 Aug 2026 14:39:35 +0100 Subject: [PATCH 138/139] wl-paste: set CLIPBOARD_TYPE for the watch command in --watch mode, expose the MIME type that wl-paste resolved for the paste to the spawned command via the CLIPBOARD_TYPE env var $ ./build/src/wl-paste --watch sh -c 'echo "state=$CLIPBOARD_STATE type=$CLIPBOARD_TYPE bytes=$(wc -c)"' state=data type=text/plain;charset=utf-8 bytes=226 state=data type=image/png bytes=671 closes #296 --- data/wl-clipboard.1 | 9 +++++++-- src/wl-paste.c | 13 ++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index 60c6d0b..a271b2d 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -91,8 +91,8 @@ in. Instead of pasting once and exiting, continuously watch the clipboard for changes, and run the specified \fIcommand\fR each time a new selection appears. The spawned process can read the clipboard contents from its standard input. -\fBwl-paste\fR also sets the \fBCLIPBOARD_STATE\fR variable in the environment -of the spawned processes (see below). +\fBwl-paste\fR also sets the \fBCLIPBOARD_STATE\fR and \fBCLIPBOARD_TYPE\fR +variables in the environment of the spawned processes (see below). .IP This mode requires a compositor that supports the wlroots data-control protocol. .TP @@ -164,6 +164,11 @@ to either wl-clipboard or Wayland; in fact, other clipboard tools are encouraged to implement the same protocol. Currently, the SerenityOS .BR paste (1) utility is known to implement the same \fBCLIPBOARD_STATE\fR protocol. +.TP +.B CLIPBOARD_TYPE +Set by \fBwl-paste\fR for the spawned command in \fB\-\-watch\fR mode to the +MIME type of the data on its standard input, such as \fBimage/png\fR. Left +unset when there is no data to read, such as when the clipboard is empty. .SH FILES .TP .I /etc/mime.types diff --git a/src/wl-paste.c b/src/wl-paste.c index e359ad9..39b49fe 100644 --- a/src/wl-paste.c +++ b/src/wl-paste.c @@ -183,7 +183,11 @@ static const char *mime_type_to_request(struct types types) { #undef try_any_text #undef try_any -static int run_paste_command(int stdin_fd, const char *clipboard_state) { +static int run_paste_command( + int stdin_fd, + const char *clipboard_state, + const char *mime_type +) { /* Spawn a cat to perform the copy. * If watch mode is active, we spawn * a custom command instead. @@ -201,6 +205,9 @@ static int run_paste_command(int stdin_fd, const char *clipboard_state) { if (clipboard_state != NULL) { setenv("CLIPBOARD_STATE", clipboard_state, 1); } + if (mime_type != NULL) { + setenv("CLIPBOARD_TYPE", mime_type, 1); + } execvp(options.watch_command[0], options.watch_command); fprintf( stderr, @@ -271,7 +278,7 @@ static void selection_callback(struct offer *offer, int primary) { perror("open /dev/null"); return; } - run_paste_command(devnull, "nil"); + run_paste_command(devnull, "nil", NULL); return; } @@ -332,7 +339,7 @@ static void selection_callback(struct offer *offer, int primary) { if (types.has_sensitive_hint) { clipboard_state = "sensitive"; } - rc = run_paste_command(pipefd[0], clipboard_state); + rc = run_paste_command(pipefd[0], clipboard_state, mime_type); if (!rc) { if (options.watch) { /* Try to cope without exiting completely */ From 16cf9d3fcccff525574364bf6ddc676352f49e92 Mon Sep 17 00:00:00 2001 From: Sergey Bugaev Date: Thu, 6 Aug 2026 11:55:44 +0300 Subject: [PATCH 139/139] Update the man page Mention the new file being read, and bump the date. --- data/wl-clipboard.1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/wl-clipboard.1 b/data/wl-clipboard.1 index a271b2d..4243a91 100644 --- a/data/wl-clipboard.1 +++ b/data/wl-clipboard.1 @@ -1,4 +1,4 @@ -.TH WL-CLIPBOARD 1 2026-03-22 wl-clipboard +.TH WL-CLIPBOARD 1 2026-08-06 wl-clipboard .SH NAME wl-clipboard \- Wayland copy and paste command line utilities .SH SYNOPSIS @@ -171,7 +171,7 @@ MIME type of the data on its standard input, such as \fBimage/png\fR. Left unset when there is no data to read, such as when the clipboard is empty. .SH FILES .TP -.I /etc/mime.types +\fI/etc/mime.types\fR, \fI/usr/share/mime/globs2\fR If present, read by \fBwl-paste\fR to infer the MIME type to paste in based on the file name extension of its standard output. .SH BUGS