paste.cpp 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  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. #include <AK/ByteString.h>
  8. #include <AK/Format.h>
  9. #include <LibCore/ArgsParser.h>
  10. #include <LibCore/Environment.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. static void spawn_command(Span<StringView> command, ByteBuffer const& data, char const* state)
  20. {
  21. auto pipefd = MUST(Core::System::pipe2(0));
  22. pid_t pid = MUST(Core::System::fork());
  23. if (pid == 0) {
  24. // We're the child.
  25. MUST(Core::System::dup2(pipefd[0], 0));
  26. MUST(Core::System::close(pipefd[0]));
  27. MUST(Core::System::close(pipefd[1]));
  28. MUST(Core::Environment::set("CLIPBOARD_STATE"sv, { state, strlen(state) }, Core::Environment::Overwrite::Yes));
  29. MUST(Core::System::exec(command[0], command, Core::System::SearchInPath::Yes));
  30. perror("exec");
  31. exit(1);
  32. }
  33. // We're the parent.
  34. MUST(Core::System::close(pipefd[0]));
  35. FILE* f = fdopen(pipefd[1], "w");
  36. fwrite(data.data(), data.size(), 1, f);
  37. if (ferror(f))
  38. warnln("failed to write data to the pipe: {}", strerror(ferror(f)));
  39. fclose(f);
  40. if (wait(nullptr) < 0)
  41. perror("wait");
  42. }
  43. ErrorOr<int> serenity_main(Main::Arguments arguments)
  44. {
  45. bool print_type = false;
  46. bool no_newline = false;
  47. bool watch = false;
  48. Vector<StringView> watch_command;
  49. Core::ArgsParser args_parser;
  50. args_parser.set_general_help("Paste from the clipboard to stdout.");
  51. args_parser.add_option(print_type, "Display the copied type", "print-type");
  52. args_parser.add_option(no_newline, "Do not append a newline", "no-newline", 'n');
  53. args_parser.add_option(watch, "Run a command when clipboard data changes", "watch", 'w');
  54. args_parser.add_positional_argument(watch_command, "Command to run in watch mode", "command", Core::ArgsParser::Required::No);
  55. args_parser.parse(arguments);
  56. auto app = TRY(GUI::Application::create(arguments));
  57. auto& clipboard = GUI::Clipboard::the();
  58. if (watch) {
  59. watch_command.append({});
  60. clipboard.on_change = [&](ByteString const&) {
  61. // Technically there's a race here...
  62. auto data_and_type = clipboard.fetch_data_and_type();
  63. if (data_and_type.mime_type.is_empty()) {
  64. spawn_command(watch_command, {}, "clear");
  65. } else {
  66. spawn_command(watch_command, data_and_type.data, "data");
  67. }
  68. };
  69. // Trigger it the first time immediately.
  70. clipboard.on_change({});
  71. return app->exec();
  72. }
  73. auto data_and_type = clipboard.fetch_data_and_type();
  74. if (data_and_type.mime_type.is_empty()) {
  75. warnln("Nothing copied");
  76. return 1;
  77. }
  78. if (!print_type) {
  79. out("{}", StringView(data_and_type.data));
  80. // Append a newline to text contents, unless the caller says otherwise.
  81. if (data_and_type.mime_type.starts_with("text/"sv) && !no_newline)
  82. outln();
  83. } else {
  84. outln("{}", data_and_type.mime_type);
  85. }
  86. return 0;
  87. }