Moterm part 6: Add moterm_example_app.

This is an example app that is itself embeddable, and in turn embeds a
single moterm. It uses that moterm to prompt the user about what to
connect to (e.g.: mojo:echo_terminal,
mojo:netcat?host=localhost&port=80), and then connects the moterm to
whatever the user enters. After that connection is done (typically
terminated by ^D on a line by itself), it prompts the user again, etc.

R=erg@chromium.org

Review URL: https://codereview.chromium.org/1137033005
diff --git a/apps/moterm/moterm_example_app.cc b/apps/moterm/moterm_example_app.cc
new file mode 100644
index 0000000..abe3842
--- /dev/null
+++ b/apps/moterm/moterm_example_app.cc
@@ -0,0 +1,208 @@
+// 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 simple example application (with an embeddable view), which embeds
+// the Moterm view, uses it to prompt the user, etc.
+
+#include <string.h>
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "examples/window_manager/window_manager.mojom.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/connect.h"
+#include "mojo/public/cpp/bindings/array.h"
+#include "mojo/public/interfaces/application/service_provider.mojom.h"
+#include "mojo/public/interfaces/application/shell.mojom.h"
+#include "mojo/services/files/public/interfaces/file.mojom.h"
+#include "mojo/services/files/public/interfaces/types.mojom.h"
+#include "mojo/services/terminal/public/interfaces/terminal.mojom.h"
+#include "mojo/services/terminal/public/interfaces/terminal_client.mojom.h"
+#include "mojo/services/view_manager/public/cpp/view.h"
+#include "mojo/services/view_manager/public/cpp/view_manager.h"
+#include "mojo/services/view_manager/public/cpp/view_manager_client_factory.h"
+#include "mojo/services/view_manager/public/cpp/view_manager_delegate.h"
+#include "mojo/services/view_manager/public/cpp/view_observer.h"
+
+// Kind of like |fputs()| (doesn't wait for result).
+void Fputs(mojo::files::File* file, const char* s) {
+  size_t length = strlen(s);
+  mojo::Array<uint8_t> a(length);
+  memcpy(&a[0], s, length);
+
+  file->Write(a.Pass(), 0, mojo::files::WHENCE_FROM_CURRENT,
+              mojo::files::File::WriteCallback());
+}
+
+class MotermExampleAppView : public mojo::ViewObserver {
+ public:
+  explicit MotermExampleAppView(mojo::Shell* shell, mojo::View* view)
+      : shell_(shell), view_(view), moterm_view_(), weak_factory_(this) {
+    view_->AddObserver(this);
+
+    moterm_view_ = view_->view_manager()->CreateView();
+    view_->AddChild(moterm_view_);
+    moterm_view_->SetBounds(view_->bounds());
+    moterm_view_->SetVisible(true);
+    mojo::ServiceProviderPtr moterm_sp;
+    moterm_view_->Embed("mojo:moterm", GetProxy(&moterm_sp), nullptr);
+    moterm_sp->ConnectToService(mojo::terminal::Terminal::Name_,
+                                GetProxy(&moterm_terminal_).PassMessagePipe());
+    Resize();
+    StartPrompt(true);
+  }
+
+  ~MotermExampleAppView() override {}
+
+ private:
+  void Resize() {
+    moterm_terminal_->SetSize(0, 0, false, [](mojo::files::Error error,
+                                              uint32_t rows, uint32_t columns) {
+      DCHECK_EQ(error, mojo::files::ERROR_OK);
+      DVLOG(1) << "New size: " << rows << "x" << columns;
+    });
+  }
+
+  void StartPrompt(bool first_time) {
+    if (!moterm_file_) {
+      moterm_terminal_->Connect(GetProxy(&moterm_file_), false,
+                                [](mojo::files::Error error) {
+                                  DCHECK_EQ(error, mojo::files::ERROR_OK);
+                                });
+    }
+
+    if (first_time) {
+      // Display some welcome text.
+      Fputs(moterm_file_.get(),
+            "Welcome to "
+            "\x1b[1m\x1b[34mM\x1b[31mo\x1b[33mt\x1b[34me\x1b[32mr\x1b[31mm\n"
+            "\n");
+    }
+
+    Fputs(moterm_file_.get(), "\x1b[0m\nWhere do you want to go today?\n:) ");
+    moterm_file_->Read(1000, 0, mojo::files::WHENCE_FROM_CURRENT,
+                       base::Bind(&MotermExampleAppView::OnInputFromPrompt,
+                                  weak_factory_.GetWeakPtr()));
+  }
+  void OnInputFromPrompt(mojo::files::Error error,
+                         mojo::Array<uint8_t> bytes_read) {
+    if (error != mojo::files::ERROR_OK || !bytes_read) {
+      // TODO(vtl): Handle errors?
+      NOTIMPLEMENTED();
+      return;
+    }
+
+    std::string dest_url;
+    if (bytes_read.size() >= 1) {
+      base::TrimWhitespaceASCII(
+          std::string(reinterpret_cast<const char*>(&bytes_read[0]),
+                      bytes_read.size()),
+          base::TRIM_ALL, &dest_url);
+    }
+
+    if (dest_url.empty()) {
+      Fputs(moterm_file_.get(), "\nError: no destination URL given\n");
+      StartPrompt(false);
+      return;
+    }
+
+    Fputs(moterm_file_.get(),
+          base::StringPrintf("\nGoing to %s ...\n", dest_url.c_str()).c_str());
+    moterm_file_.reset();
+
+    mojo::ServiceProviderPtr dest_sp;
+    shell_->ConnectToApplication(dest_url, GetProxy(&dest_sp), nullptr);
+    mojo::terminal::TerminalClientPtr dest_terminal_client;
+    mojo::ConnectToService(dest_sp.get(), &dest_terminal_client);
+    moterm_terminal_->ConnectToClient(
+        dest_terminal_client.Pass(), true,
+        base::Bind(&MotermExampleAppView::OnDestinationDone,
+                   weak_factory_.GetWeakPtr()));
+  }
+  void OnDestinationDone(mojo::files::Error error) {
+    // We should always succeed. (It'll only fail on synchronous failures, which
+    // only occur when it's busy.)
+    DCHECK_EQ(error, mojo::files::ERROR_OK);
+    StartPrompt(false);
+  }
+
+  // |ViewObserver|:
+  void OnViewDestroyed(mojo::View* view) override {
+    DCHECK(view == view_);
+    delete this;
+  }
+
+  void OnViewBoundsChanged(mojo::View* view,
+                           const mojo::Rect& old_bounds,
+                           const mojo::Rect& new_bounds) override {
+    DCHECK_EQ(view, view_);
+    moterm_view_->SetBounds(view_->bounds());
+    Resize();
+  }
+
+  mojo::Shell* const shell_;
+  mojo::View* const view_;
+
+  mojo::View* moterm_view_;
+  mojo::terminal::TerminalPtr moterm_terminal_;
+  // Valid while prompting.
+  mojo::files::FilePtr moterm_file_;
+
+  base::WeakPtrFactory<MotermExampleAppView> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(MotermExampleAppView);
+};
+
+class MotermExampleApp : public mojo::ApplicationDelegate,
+                         public mojo::ViewManagerDelegate {
+ public:
+  MotermExampleApp() : application_impl_() {}
+  ~MotermExampleApp() override {}
+
+ private:
+  // |mojo::ApplicationDelegate|:
+  void Initialize(mojo::ApplicationImpl* application_impl) override {
+    DCHECK(!application_impl_);
+    application_impl_ = application_impl;
+    view_manager_client_factory_.reset(
+        new mojo::ViewManagerClientFactory(application_impl_->shell(), this));
+  }
+
+  bool ConfigureIncomingConnection(
+      mojo::ApplicationConnection* connection) override {
+    connection->AddService(view_manager_client_factory_.get());
+    return true;
+  }
+
+  // |mojo::ViewManagerDelegate|:
+  void OnEmbed(mojo::View* root,
+               mojo::InterfaceRequest<mojo::ServiceProvider> services,
+               mojo::ServiceProviderPtr exposed_services) override {
+    new MotermExampleAppView(application_impl_->shell(), root);
+  }
+
+  void OnViewManagerDisconnected(mojo::ViewManager* view_manager) override {
+    base::MessageLoop::current()->Quit();
+  }
+
+  mojo::ApplicationImpl* application_impl_;
+  scoped_ptr<mojo::ViewManagerClientFactory> view_manager_client_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(MotermExampleApp);
+};
+
+MojoResult MojoMain(MojoHandle application_request) {
+  mojo::ApplicationRunnerChromium runner(new MotermExampleApp());
+  return runner.Run(application_request);
+}