Serenity Operating System
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}