diff --git a/Userland/Utilities/paste.cpp b/Userland/Utilities/paste.cpp index 24575d2b016..061d8c8fc70 100644 --- a/Userland/Utilities/paste.cpp +++ b/Userland/Utilities/paste.cpp @@ -1,29 +1,93 @@ /* - * Copyright (c) 2019-2020, Sergey Bugaev + * Copyright (c) 2019-2021, Sergey Bugaev * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include #include +#include #include +#include +#include +#include + +static void spawn_command(const Vector& command, const ByteBuffer& data, const char* state) +{ + int pipefd[2]; + if (pipe(pipefd) < 0) { + perror("pipe"); + return; + } + + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + return; + } else if (pid == 0) { + // We're the child. + dup2(pipefd[0], 0); + close(pipefd[0]); + close(pipefd[1]); + setenv("CLIPBOARD_STATE", state, true); + execvp(command[0], const_cast(command.data())); + perror("exec"); + exit(1); + } + + // We're the parent. + close(pipefd[0]); + FILE* f = fdopen(pipefd[1], "w"); + fwrite(data.data(), data.size(), 1, f); + if (ferror(f)) + warnln("failed to write data to the pipe: {}", strerror(ferror(f))); + fclose(f); + + if (wait(nullptr) < 0) + perror("wait"); +} int main(int argc, char* argv[]) { bool print_type = false; bool no_newline = false; + bool watch = false; + Vector watch_command; Core::ArgsParser args_parser; args_parser.set_general_help("Paste from the clipboard to stdout."); args_parser.add_option(print_type, "Display the copied type", "print-type", 0); args_parser.add_option(no_newline, "Do not append a newline", "no-newline", 'n'); + args_parser.add_option(watch, "Run a command when clipboard data changes", "watch", 'w'); + args_parser.add_positional_argument(watch_command, "Command to run in watch mode", "command", Core::ArgsParser::Required::No); args_parser.parse(argc, argv); auto app = GUI::Application::construct(argc, argv); auto& clipboard = GUI::Clipboard::the(); + + if (watch) { + watch_command.append(nullptr); + + clipboard.on_change = [&](const String&) { + // Technically there's a race here... + auto data_and_type = clipboard.data_and_type(); + if (data_and_type.mime_type.is_null()) { + spawn_command(watch_command, {}, "clear"); + } else { + spawn_command(watch_command, data_and_type.data, "data"); + } + }; + + // Trigger it the first time immediately. + clipboard.on_change({}); + + return app->exec(); + } + auto data_and_type = clipboard.data_and_type(); if (data_and_type.mime_type.is_null()) {