Pārlūkot izejas kodu

Userland: Implement paste --watch mode

You can now watch the clipboard for changes and run a command each time
the clipboard contents change like this:

$ paste --watch some command here

The command will be spawned each time the clipboard contents change. It
can read the clipboard contents from its stdin, and CLIPBOARD_STATE
environment variable will be set to "data" if there is data to be read,
or to "nil"/"clear" if the clipboard has been cleared.
Sergey Bugaev 4 gadi atpakaļ
vecāks
revīzija
d8faafef14
1 mainītis faili ar 65 papildinājumiem un 1 dzēšanām
  1. 65 1
      Userland/Utilities/paste.cpp

+ 65 - 1
Userland/Utilities/paste.cpp

@@ -1,29 +1,93 @@
 /*
- * Copyright (c) 2019-2020, Sergey Bugaev <bugaevc@serenityos.org>
+ * Copyright (c) 2019-2021, Sergey Bugaev <bugaevc@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
+#include <AK/Format.h>
 #include <AK/String.h>
 #include <LibCore/ArgsParser.h>
 #include <LibGUI/Application.h>
 #include <LibGUI/Clipboard.h>
+#include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+static void spawn_command(const Vector<const char*>& 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<char**>(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<const char*> 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()) {