Index: examples/native_run_app/native_run_app.cc |
diff --git a/examples/native_run_app/native_run_app.cc b/examples/native_run_app/native_run_app.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..933876be0dfa9ce8af5ab5846419f226e6444575 |
--- /dev/null |
+++ b/examples/native_run_app/native_run_app.cc |
@@ -0,0 +1,251 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+// This is a terminal client (i.e., a "raw" |mojo.terminal.Terminal| -- e.g., |
+// moterm -- can be asked to talk to this) that prompts the user for a native |
+// (Linux) binary to run and then does so (via mojo:native_support). |
+// |
+// E.g., first run mojo:moterm_example_app (embedded by a window manager). Then, |
+// at the prompt, enter "mojo:native_run_app". At the next prompt, enter "bash" |
+// (or "echo hello mojo"). |
+// |
+// TODO(vtl): Maybe it should optionally be able to extract the binary path (and |
+// args) from the connection URL? |
+ |
+#include <string.h> |
+ |
+#include <string> |
+#include <vector> |
+ |
+#include "base/logging.h" |
+#include "base/macros.h" |
+#include "base/strings/string_split.h" |
+#include "mojo/application/application_runner_chromium.h" |
+#include "mojo/public/c/system/main.h" |
+#include "mojo/public/cpp/application/application_connection.h" |
+#include "mojo/public/cpp/application/application_delegate.h" |
+#include "mojo/public/cpp/application/application_impl.h" |
+#include "mojo/public/cpp/application/interface_factory.h" |
+#include "mojo/public/cpp/bindings/interface_request.h" |
+#include "mojo/public/cpp/bindings/strong_binding.h" |
+#include "mojo/services/files/public/interfaces/files.mojom.h" |
+#include "mojo/services/files/public/interfaces/ioctl.mojom.h" |
+#include "mojo/services/files/public/interfaces/ioctl_terminal.mojom.h" |
+#include "mojo/services/files/public/interfaces/types.mojom.h" |
+#include "mojo/services/native_support/public/interfaces/process.mojom.h" |
+#include "mojo/services/terminal/public/interfaces/terminal_client.mojom.h" |
+ |
+using mojo::terminal::TerminalClient; |
+ |
+class TerminalConnection { |
+ public: |
+ explicit TerminalConnection(mojo::files::FilePtr terminal, |
+ native_support::Process* native_support_process) |
+ : terminal_(terminal.Pass()), |
+ native_support_process_(native_support_process) { |
+ terminal_.set_connection_error_handler([this]() { delete this; }); |
+ Start(); |
+ } |
+ ~TerminalConnection() {} |
+ |
+ private: |
+ void Write(const char* s, mojo::files::File::WriteCallback callback) { |
+ size_t length = strlen(s); |
+ mojo::Array<uint8_t> a(length); |
+ memcpy(&a[0], s, length); |
+ terminal_->Write(a.Pass(), 0, mojo::files::WHENCE_FROM_CURRENT, callback); |
+ } |
+ |
+ void Start() { |
+ // TODO(vtl): Check canonical mode (via ioctl) first (or before |Read()|). |
+ |
+ const char kPrompt[] = "\x1b[0mNative program to run?\n>>> "; |
+ Write(kPrompt, [this](mojo::files::Error error, uint32_t) { |
+ this->DidWritePrompt(error); |
+ }); |
+ } |
+ void DidWritePrompt(mojo::files::Error error) { |
+ if (error != mojo::files::ERROR_OK) { |
+ LOG(ERROR) << "Write() error: " << error; |
+ delete this; |
+ return; |
+ } |
+ |
+ terminal_->Read( |
+ 1000, 0, mojo::files::WHENCE_FROM_CURRENT, |
+ [this](mojo::files::Error error, mojo::Array<uint8_t> bytes_read) { |
+ this->DidReadFromPrompt(error, bytes_read.Pass()); |
+ }); |
+ } |
+ void DidReadFromPrompt(mojo::files::Error error, |
+ mojo::Array<uint8_t> bytes_read) { |
+ if (error != mojo::files::ERROR_OK || !bytes_read.size()) { |
+ LOG(ERROR) << "Read() error: " << error; |
+ delete this; |
+ return; |
+ } |
+ |
+ std::string input(reinterpret_cast<const char*>(&bytes_read[0]), |
+ bytes_read.size()); |
+ command_line_.clear(); |
+ base::SplitStringAlongWhitespace(input, &command_line_); |
+ |
+ if (command_line_.empty()) { |
+ Start(); |
+ return; |
+ } |
+ |
+ // Set the terminal to noncanonical mode. Do so by getting the settings, |
+ // flipping the flag, and setting them. |
+ // TODO(vtl): Should it do other things? |
+ // TODO(vtl): I wonder if these ioctls shouldn't be done by the |
+ // |SpawnWithTerminal()| implementation instead. Hmmm. |
+ mojo::Array<uint32_t> in_values = mojo::Array<uint32_t>::New(1); |
+ in_values[0] = mojo::files::kIoctlTerminalGetSettings; |
+ terminal_->Ioctl( |
+ mojo::files::kIoctlTerminal, in_values.Pass(), |
+ [this](mojo::files::Error error, mojo::Array<uint32_t> out_values) { |
+ this->DidGetTerminalSettings(error, out_values.Pass()); |
+ }); |
+ } |
+ void DidGetTerminalSettings(mojo::files::Error error, |
+ mojo::Array<uint32_t> out_values) { |
+ if (error != mojo::files::ERROR_OK || out_values.size() < 6) { |
+ LOG(ERROR) << "Ioctl() (terminal get settings) error: " << error; |
+ delete this; |
+ return; |
+ } |
+ |
+ const size_t kBaseFieldCount = |
+ mojo::files::kIoctlTerminalTermiosBaseFieldCount; |
+ const uint32_t kLFlagIdx = mojo::files::kIoctlTerminalTermiosLFlagIndex; |
+ const uint32_t kLFlagICANON = mojo::files::kIoctlTerminalTermiosLFlagICANON; |
+ |
+ auto in_values = mojo::Array<uint32_t>::New(1 + kBaseFieldCount); |
+ in_values[0] = mojo::files::kIoctlTerminalSetSettings; |
+ for (size_t i = 0; i < kBaseFieldCount; i++) |
+ in_values[1 + i] = out_values[i]; |
+ // Just turn off ICANON, which is in "lflag". |
+ in_values[1 + kLFlagIdx] &= ~kLFlagICANON; |
+ terminal_->Ioctl( |
+ mojo::files::kIoctlTerminal, in_values.Pass(), |
+ [this](mojo::files::Error error, mojo::Array<uint32_t> out_values) { |
+ this->DidSetTerminalSettings(error, out_values.Pass()); |
+ }); |
+ } |
+ void DidSetTerminalSettings(mojo::files::Error error, |
+ mojo::Array<uint32_t> out_values) { |
+ if (error != mojo::files::ERROR_OK) { |
+ LOG(ERROR) << "Ioctl() (terminal set settings) error: " << error; |
+ delete this; |
+ return; |
+ } |
+ |
+ // Now, we can spawn. |
+ mojo::String path(command_line_[0]); |
+ mojo::Array<mojo::String> argv; |
+ for (const auto& arg : command_line_) |
+ argv.push_back(arg); |
+ |
+ // TODO(vtl): If the |InterfacePtr| underlying |native_support_process_| |
+ // encounters an error, then we're sort of dead in the water. |
+ native_support_process_->SpawnWithTerminal( |
+ path, argv.Pass(), mojo::Array<mojo::String>(), terminal_.Pass(), |
+ GetProxy(&process_controller_), [this](mojo::files::Error error) { |
+ this->DidSpawnWithTerminal(error); |
+ }); |
+ process_controller_.set_connection_error_handler([this]() { delete this; }); |
+ } |
+ void DidSpawnWithTerminal(mojo::files::Error error) { |
+ if (error != mojo::files::ERROR_OK) { |
+ LOG(ERROR) << "SpawnWithTerminal() error: " << error; |
+ delete this; |
+ return; |
+ } |
+ process_controller_->Wait( |
+ [this](mojo::files::Error error, int32_t exit_status) { |
+ this->DidWait(error, exit_status); |
+ }); |
+ } |
+ void DidWait(mojo::files::Error error, int32_t exit_status) { |
+ if (error != mojo::files::ERROR_OK) |
+ LOG(ERROR) << "Wait() error: " << error; |
+ else if (exit_status != 0) // |exit_status| only valid if OK. |
+ LOG(ERROR) << "Process exit status: " << exit_status; |
+ |
+ // We're done, regardless. |
+ delete this; |
+ } |
+ |
+ mojo::files::FilePtr terminal_; |
+ native_support::Process* native_support_process_; |
+ native_support::ProcessControllerPtr process_controller_; |
+ |
+ std::vector<std::string> command_line_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TerminalConnection); |
+}; |
+ |
+class TerminalClientImpl : public TerminalClient { |
+ public: |
+ TerminalClientImpl(mojo::InterfaceRequest<TerminalClient> request, |
+ native_support::Process* native_support_process) |
+ : binding_(this, request.Pass()), |
+ native_support_process_(native_support_process) {} |
+ ~TerminalClientImpl() override {} |
+ |
+ // |TerminalClient| implementation: |
+ void ConnectToTerminal(mojo::files::FilePtr terminal) override { |
+ if (terminal) { |
+ // Owns itself. |
+ new TerminalConnection(terminal.Pass(), native_support_process_); |
+ } else { |
+ LOG(ERROR) << "No terminal"; |
+ } |
+ } |
+ |
+ private: |
+ mojo::StrongBinding<TerminalClient> binding_; |
+ native_support::Process* native_support_process_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TerminalClientImpl); |
+}; |
+ |
+class NativeRunApp : public mojo::ApplicationDelegate, |
+ public mojo::InterfaceFactory<TerminalClient> { |
+ public: |
+ NativeRunApp() : application_impl_(nullptr) {} |
+ ~NativeRunApp() override {} |
+ |
+ private: |
+ // |mojo::ApplicationDelegate|: |
+ void Initialize(mojo::ApplicationImpl* application_impl) override { |
+ DCHECK(!application_impl_); |
+ application_impl_ = application_impl; |
+ application_impl_->ConnectToService("mojo:native_support", |
+ &native_support_process_); |
+ } |
+ |
+ bool ConfigureIncomingConnection( |
+ mojo::ApplicationConnection* connection) override { |
+ connection->AddService<TerminalClient>(this); |
+ return true; |
+ } |
+ |
+ // |InterfaceFactory<TerminalClient>| implementation: |
+ void Create(mojo::ApplicationConnection* /*connection*/, |
+ mojo::InterfaceRequest<TerminalClient> request) override { |
+ new TerminalClientImpl(request.Pass(), native_support_process_.get()); |
+ } |
+ |
+ mojo::ApplicationImpl* application_impl_; |
+ native_support::ProcessPtr native_support_process_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(NativeRunApp); |
+}; |
+ |
+MojoResult MojoMain(MojoHandle application_request) { |
+ mojo::ApplicationRunnerChromium runner(new NativeRunApp()); |
+ return runner.Run(application_request); |
+} |