Serenity Operating System
at master 105 lines 3.3 kB view raw
1/* 2 * Copyright (c) 2019-2021, Sergey Bugaev <bugaevc@serenityos.org> 3 * Copyright (c) 2022, Zachary Penn <zack@sysdevs.org> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/DeprecatedString.h> 9#include <AK/Format.h> 10#include <LibCore/ArgsParser.h> 11#include <LibCore/System.h> 12#include <LibGUI/Application.h> 13#include <LibGUI/Clipboard.h> 14#include <LibMain/Main.h> 15#include <stdio.h> 16#include <stdlib.h> 17#include <string.h> 18#include <sys/wait.h> 19 20static void spawn_command(Span<StringView> command, ByteBuffer const& data, char const* state) 21{ 22 auto pipefd = MUST(Core::System::pipe2(0)); 23 pid_t pid = MUST(Core::System::fork()); 24 25 if (pid == 0) { 26 // We're the child. 27 MUST(Core::System::dup2(pipefd[0], 0)); 28 MUST(Core::System::close(pipefd[0])); 29 MUST(Core::System::close(pipefd[1])); 30 MUST(Core::System::setenv("CLIPBOARD_STATE"sv, { state, strlen(state) }, true)); 31 MUST(Core::System::exec(command[0], command, Core::System::SearchInPath::Yes)); 32 perror("exec"); 33 exit(1); 34 } 35 36 // We're the parent. 37 MUST(Core::System::close(pipefd[0])); 38 FILE* f = fdopen(pipefd[1], "w"); 39 fwrite(data.data(), data.size(), 1, f); 40 41 if (ferror(f)) 42 warnln("failed to write data to the pipe: {}", strerror(ferror(f))); 43 44 fclose(f); 45 46 if (wait(nullptr) < 0) 47 perror("wait"); 48} 49 50ErrorOr<int> serenity_main(Main::Arguments arguments) 51{ 52 bool print_type = false; 53 bool no_newline = false; 54 bool watch = false; 55 Vector<StringView> watch_command; 56 57 Core::ArgsParser args_parser; 58 args_parser.set_general_help("Paste from the clipboard to stdout."); 59 args_parser.add_option(print_type, "Display the copied type", "print-type", 0); 60 args_parser.add_option(no_newline, "Do not append a newline", "no-newline", 'n'); 61 args_parser.add_option(watch, "Run a command when clipboard data changes", "watch", 'w'); 62 args_parser.add_positional_argument(watch_command, "Command to run in watch mode", "command", Core::ArgsParser::Required::No); 63 args_parser.parse(arguments); 64 65 auto app = TRY(GUI::Application::try_create(arguments)); 66 67 auto& clipboard = GUI::Clipboard::the(); 68 69 if (watch) { 70 watch_command.append({}); 71 72 clipboard.on_change = [&](DeprecatedString const&) { 73 // Technically there's a race here... 74 auto data_and_type = clipboard.fetch_data_and_type(); 75 if (data_and_type.mime_type.is_null()) { 76 spawn_command(watch_command, {}, "clear"); 77 } else { 78 spawn_command(watch_command, data_and_type.data, "data"); 79 } 80 }; 81 82 // Trigger it the first time immediately. 83 clipboard.on_change({}); 84 85 return app->exec(); 86 } 87 88 auto data_and_type = clipboard.fetch_data_and_type(); 89 90 if (data_and_type.mime_type.is_null()) { 91 warnln("Nothing copied"); 92 return 1; 93 } 94 95 if (!print_type) { 96 out("{}", StringView(data_and_type.data)); 97 // Append a newline to text contents, unless the caller says otherwise. 98 if (data_and_type.mime_type.starts_with("text/"sv) && !no_newline) 99 outln(); 100 } else { 101 outln("{}", data_and_type.mime_type); 102 } 103 104 return 0; 105}