paste.cpp 3.2 KB

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