Index: apps/moterm/moterm_driver.cc |
diff --git a/apps/moterm/moterm_driver.cc b/apps/moterm/moterm_driver.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..9180d10bb623d740dd49751e9fe1939564ae85b7 |
--- /dev/null |
+++ b/apps/moterm/moterm_driver.cc |
@@ -0,0 +1,374 @@ |
+// 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. |
+ |
+#include "apps/moterm/moterm_driver.h" |
+ |
+#include <algorithm> |
+#include <limits> |
+ |
+#include "base/logging.h" |
+#include "mojo/services/files/public/interfaces/types.mojom.h" |
+ |
+const uint8_t kEOT = 4; |
+const uint8_t kNL = 10; |
+const uint8_t kCR = 13; |
+const uint8_t kDEL = 127; |
+ |
+MotermDriver::PendingRead::PendingRead(uint32_t num_bytes, |
+ const ReadCallback& callback) |
+ : num_bytes(num_bytes), callback(callback) { |
+} |
+ |
+MotermDriver::PendingRead::~PendingRead() { |
+} |
+ |
+// static |
+base::WeakPtr<MotermDriver> MotermDriver::Create( |
+ Client* client, |
+ mojo::InterfaceRequest<mojo::files::File> request) { |
+ return (new MotermDriver(client, request.Pass()))->weak_factory_.GetWeakPtr(); |
+} |
+ |
+void MotermDriver::Detach() { |
+ client_ = nullptr; |
+ delete this; |
+} |
+ |
+void MotermDriver::SendData(const void* bytes, size_t num_bytes) { |
+ DCHECK(client_); |
+ DCHECK(!is_closed_); |
+ |
+ if (icanon_) { |
+ for (size_t i = 0; i < num_bytes; i++) |
+ HandleCanonicalModeInput(static_cast<const uint8_t*>(bytes)[i]); |
+ } else { |
+ // If not in canonical ("cooked") mode, just send data. |
+ const uint8_t* b = static_cast<const uint8_t*>(bytes); |
+ for (size_t i = 0; i < num_bytes; i++) |
+ send_data_queue_.push_back(b[i]); |
+ |
+ CompletePendingReads(); |
+ } |
+} |
+ |
+MotermDriver::MotermDriver(Client* client, |
+ mojo::InterfaceRequest<mojo::files::File> request) |
+ : client_(client), |
+ is_closed_(false), |
+ binding_(this, request.Pass()), |
+ icanon_(true), |
+ icrnl_(true), |
+ veof_(kEOT), |
+ verase_(kDEL), |
+ onlcr_(true), |
+ weak_factory_(this) { |
+ DCHECK(client_); |
+} |
+ |
+MotermDriver::~MotermDriver() { |
+ if (client_) { |
+ // Invalidate weak pointers now, to make sure the client doesn't call us. |
+ weak_factory_.InvalidateWeakPtrs(); |
+ client_->OnDestroyed(); |
+ } |
+} |
+ |
+void MotermDriver::HandleCanonicalModeInput(uint8_t ch) { |
+ DCHECK(icanon_); |
+ |
+ // Translate CR to NL, if appropriate. |
+ if (ch == kCR && icrnl_) |
+ ch = kNL; |
+ |
+ // Newline. |
+ // TODO(vtl): In addition to NL, we're supposed to handle settable EOL and |
+ // EOL2. |
+ if (ch == kNL) { // Newline. |
+ input_line_queue_.push_back(ch); |
+ HandleOutput(&ch, 1); |
+ FlushInputLine(); |
+ return; |
+ } |
+ |
+ // EOF at beginning of line. |
+ if (ch == veof_ && input_line_queue_.empty()) { |
+ // Don't add the character. Just flush and nuke ourselves. |
+ FlushInputLine(); |
+ delete this; |
+ return; |
+ } |
+ |
+ if (ch == verase_) { // Erase (backspace). |
+ if (!input_line_queue_.empty()) { |
+ // TODO(vtl): This is wrong for utf-8 (see Linux's IUTF8). |
+ input_line_queue_.pop_back(); |
+ HandleOutput(reinterpret_cast<const uint8_t*>("\x08 \x08"), 3); |
+ } // Else do nothing. |
+ return; |
+ } |
+ |
+ // Everything else. |
+ // TODO(vtl): Probably want to display control characters as ^X (e.g.). |
+ // TODO(vtl): Handle ^W. |
+ input_line_queue_.push_back(ch); |
+ HandleOutput(&ch, 1); |
+} |
+ |
+void MotermDriver::CompletePendingReads() { |
+ while (send_data_queue_.size() && pending_read_queue_.size()) { |
+ PendingRead pending_read = pending_read_queue_.front(); |
+ pending_read_queue_.pop_front(); |
+ |
+ size_t data_size = std::min(static_cast<size_t>(pending_read.num_bytes), |
+ send_data_queue_.size()); |
+ mojo::Array<uint8_t> data(data_size); |
+ for (size_t i = 0; i < data_size; i++) { |
+ data[i] = send_data_queue_[i]; |
+ // In canonical mode, each read only gets a single line. |
+ if (icanon_ && data[i] == kNL) { |
+ data_size = i + 1; |
+ data.resize(data_size); |
+ break; |
+ } |
+ } |
+ send_data_queue_.erase(send_data_queue_.begin(), |
+ send_data_queue_.begin() + data_size); |
+ |
+ pending_read.callback.Run(mojo::files::ERROR_OK, data.Pass()); |
+ } |
+} |
+ |
+void MotermDriver::FlushInputLine() { |
+ for (size_t i = 0; i < input_line_queue_.size(); i++) |
+ send_data_queue_.push_back(input_line_queue_[i]); |
+ input_line_queue_.clear(); |
+ CompletePendingReads(); |
+} |
+ |
+void MotermDriver::HandleOutput(const uint8_t* bytes, size_t num_bytes) { |
+ // Can we be smarter and not always copy? |
+ std::vector<uint8_t> translated_bytes; |
+ translated_bytes.reserve(num_bytes); |
+ |
+ for (size_t i = 0; i < num_bytes; i++) { |
+ uint8_t ch = bytes[i]; |
+ if (ch == kNL && onlcr_) { |
+ translated_bytes.push_back(kCR); |
+ translated_bytes.push_back(kNL); |
+ } else { |
+ translated_bytes.push_back(ch); |
+ } |
+ } |
+ |
+ // TODO(vtl): It seems extremely unlikely that we'd overlow a |uint32_t| here |
+ // (but it's theoretically possible). But perhaps we should handle it more |
+ // gracefully if it ever comes up. |
+ CHECK_LE(translated_bytes.size(), std::numeric_limits<uint32_t>::max()); |
+ client_->OnDataReceived(translated_bytes.data(), |
+ static_cast<uint32_t>(translated_bytes.size())); |
+} |
+ |
+void MotermDriver::Close(const CloseCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED); |
+ return; |
+ } |
+ |
+ callback.Run(mojo::files::ERROR_OK); |
+ |
+ // TODO(vtl): Call pending read callbacks? |
+ |
+ client_->OnClosed(); |
+} |
+ |
+void MotermDriver::Read(uint32_t num_bytes_to_read, |
+ int64_t offset, |
+ mojo::files::Whence whence, |
+ const ReadCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED, mojo::Array<uint8_t>()); |
+ return; |
+ } |
+ |
+ if (offset != 0 || whence != mojo::files::WHENCE_FROM_CURRENT) { |
+ // TODO(vtl): Is this the "right" behavior? |
+ callback.Run(mojo::files::ERROR_INVALID_ARGUMENT, mojo::Array<uint8_t>()); |
+ return; |
+ } |
+ |
+ if (!num_bytes_to_read) { |
+ callback.Run(mojo::files::ERROR_OK, mojo::Array<uint8_t>()); |
+ return; |
+ } |
+ |
+ pending_read_queue_.push_back(PendingRead(num_bytes_to_read, callback)); |
+ CompletePendingReads(); |
+} |
+ |
+void MotermDriver::Write(mojo::Array<uint8_t> bytes_to_write, |
+ int64_t offset, |
+ mojo::files::Whence whence, |
+ const WriteCallback& callback) { |
+ DCHECK(!bytes_to_write.is_null()); |
+ |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED, 0); |
+ return; |
+ } |
+ |
+ if (offset != 0 || whence != mojo::files::WHENCE_FROM_CURRENT) { |
+ // TODO(vtl): Is this the "right" behavior? |
+ callback.Run(mojo::files::ERROR_INVALID_ARGUMENT, 0); |
+ return; |
+ } |
+ |
+ if (!bytes_to_write.size()) { |
+ callback.Run(mojo::files::ERROR_OK, 0); |
+ return; |
+ } |
+ |
+ HandleOutput(static_cast<const uint8_t*>(&bytes_to_write.front()), |
+ bytes_to_write.size()); |
+ |
+ // TODO(vtl): Is this OK if the client detached (and we're destroyed?). |
+ callback.Run(mojo::files::ERROR_OK, |
+ static_cast<uint32_t>(bytes_to_write.size())); |
+} |
+ |
+void MotermDriver::ReadToStream(mojo::ScopedDataPipeProducerHandle source, |
+ int64_t offset, |
+ mojo::files::Whence whence, |
+ int64_t num_bytes_to_read, |
+ const ReadToStreamCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED); |
+ return; |
+ } |
+ |
+ // TODO(vtl) |
+ NOTIMPLEMENTED(); |
+ callback.Run(mojo::files::ERROR_UNIMPLEMENTED); |
+} |
+ |
+void MotermDriver::WriteFromStream(mojo::ScopedDataPipeConsumerHandle sink, |
+ int64_t offset, |
+ mojo::files::Whence whence, |
+ const WriteFromStreamCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED); |
+ return; |
+ } |
+ |
+ // TODO(vtl) |
+ NOTIMPLEMENTED(); |
+ callback.Run(mojo::files::ERROR_UNIMPLEMENTED); |
+} |
+ |
+void MotermDriver::Tell(const TellCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED, 0); |
+ return; |
+ } |
+ |
+ // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
+ // unsupported/EINVAL is better.) |
+ callback.Run(mojo::files::ERROR_UNAVAILABLE, 0); |
+} |
+ |
+void MotermDriver::Seek(int64_t offset, |
+ mojo::files::Whence whence, |
+ const SeekCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED, 0); |
+ return; |
+ } |
+ |
+ // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
+ // unsupported/EINVAL is better.) |
+ callback.Run(mojo::files::ERROR_UNAVAILABLE, 0); |
+} |
+ |
+void MotermDriver::Stat(const StatCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED, nullptr); |
+ return; |
+ } |
+ |
+ // TODO(vtl) |
+ NOTIMPLEMENTED(); |
+ callback.Run(mojo::files::ERROR_UNIMPLEMENTED, nullptr); |
+} |
+ |
+void MotermDriver::Truncate(int64_t size, const TruncateCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED); |
+ return; |
+ } |
+ |
+ // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
+ // unsupported/EINVAL is better.) |
+ callback.Run(mojo::files::ERROR_UNAVAILABLE); |
+} |
+ |
+void MotermDriver::Touch(mojo::files::TimespecOrNowPtr atime, |
+ mojo::files::TimespecOrNowPtr mtime, |
+ const TouchCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED); |
+ return; |
+ } |
+ |
+ // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
+ // unsupported/EINVAL is better.) |
+ callback.Run(mojo::files::ERROR_UNAVAILABLE); |
+} |
+ |
+void MotermDriver::Dup(mojo::InterfaceRequest<mojo::files::File> file, |
+ const DupCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED); |
+ return; |
+ } |
+ |
+ // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
+ // unsupported/EINVAL is better.) |
+ callback.Run(mojo::files::ERROR_UNAVAILABLE); |
+} |
+ |
+void MotermDriver::Reopen(mojo::InterfaceRequest<mojo::files::File> file, |
+ uint32_t open_flags, |
+ const ReopenCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED); |
+ return; |
+ } |
+ |
+ // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
+ // unsupported/EINVAL is better.) |
+ callback.Run(mojo::files::ERROR_UNAVAILABLE); |
+} |
+ |
+void MotermDriver::AsBuffer(const AsBufferCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED, mojo::ScopedSharedBufferHandle()); |
+ return; |
+ } |
+ |
+ // TODO(vtl): Is this what we want? (Also is "unavailable" right? Maybe |
+ // unsupported/EINVAL is better.) |
+ callback.Run(mojo::files::ERROR_UNAVAILABLE, |
+ mojo::ScopedSharedBufferHandle()); |
+} |
+ |
+void MotermDriver::Ioctl(uint32_t request, |
+ mojo::Array<uint32_t> in_values, |
+ const IoctlCallback& callback) { |
+ if (is_closed_) { |
+ callback.Run(mojo::files::ERROR_CLOSED, mojo::Array<uint32_t>()); |
+ return; |
+ } |
+ |
+ // TODO(vtl) |
+ callback.Run(mojo::files::ERROR_UNIMPLEMENTED, mojo::Array<uint32_t>()); |
+} |