|  | // 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_view.h" | 
|  |  | 
|  | #ifndef GL_GLEXT_PROTOTYPES | 
|  | #define GL_GLEXT_PROTOTYPES | 
|  | #endif | 
|  |  | 
|  | #include <GLES2/gl2.h> | 
|  | #include <GLES2/gl2ext.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <string> | 
|  |  | 
|  | #include "apps/moterm/key_util.h" | 
|  | #include "base/logging.h" | 
|  | #include "mojo/public/cpp/bindings/interface_request.h" | 
|  | #include "mojo/services/files/interfaces/file.mojom.h" | 
|  | #include "mojo/services/files/interfaces/types.mojom.h" | 
|  | #include "mojo/services/input_events/interfaces/input_event_constants.mojom.h" | 
|  | #include "mojo/services/input_events/interfaces/input_events.mojom.h" | 
|  | #include "mojo/services/terminal/interfaces/terminal_client.mojom.h" | 
|  | #include "skia/ext/refptr.h" | 
|  | #include "third_party/dejavu-fonts-ttf-2.34/kDejaVuSansMonoRegular.h" | 
|  | #include "third_party/skia/include/core/SkBitmap.h" | 
|  | #include "third_party/skia/include/core/SkCanvas.h" | 
|  | #include "third_party/skia/include/core/SkColor.h" | 
|  | #include "third_party/skia/include/core/SkImageInfo.h" | 
|  | #include "third_party/skia/include/core/SkPaint.h" | 
|  | #include "third_party/skia/include/core/SkRect.h" | 
|  | #include "third_party/skia/include/core/SkStream.h" | 
|  | #include "third_party/skia/include/core/SkXfermode.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const GLint kTextureFormat = | 
|  | (kN32_SkColorType == kRGBA_8888_SkColorType) ? GL_RGBA : GL_BGRA_EXT; | 
|  |  | 
|  | mojo::Size RectToSize(const mojo::Rect& rect) { | 
|  | mojo::Size size; | 
|  | size.width = rect.width; | 
|  | size.height = rect.height; | 
|  | return size; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | MotermView::MotermView( | 
|  | mojo::Shell* shell, | 
|  | mojo::View* view, | 
|  | mojo::InterfaceRequest<mojo::ServiceProvider> service_provider_request) | 
|  | : view_(view), | 
|  | gl_helper_(this, | 
|  | shell, | 
|  | kTextureFormat, | 
|  | false, | 
|  | RectToSize(view->bounds())), | 
|  | model_(MotermModel::Size(240, 160), MotermModel::Size(24, 80), this), | 
|  | frame_pending_(false), | 
|  | force_next_draw_(false), | 
|  | ascent_(0), | 
|  | line_height_(0), | 
|  | advance_width_(0), | 
|  | keypad_application_mode_(false) { | 
|  | // TODO(vtl): |service_provider_impl_|'s ctor doesn't like an invalid request, | 
|  | // so we have to conditionally, explicitly bind. | 
|  | if (service_provider_request.is_pending()) { | 
|  | service_provider_impl_.Bind(service_provider_request.Pass()); | 
|  | service_provider_impl_.AddService<mojo::terminal::Terminal>(this); | 
|  | } | 
|  |  | 
|  | regular_typeface_ = skia::AdoptRef(SkTypeface::CreateFromStream( | 
|  | new SkMemoryStream(font_data::kDejaVuSansMonoRegular.data, | 
|  | font_data::kDejaVuSansMonoRegular.size))); | 
|  |  | 
|  | // TODO(vtl): This duplicates some code. | 
|  | SkPaint fg_paint; | 
|  | fg_paint.setTypeface(regular_typeface_.get()); | 
|  | fg_paint.setTextSize(16); | 
|  | // Figure out appropriate metrics. | 
|  | SkPaint::FontMetrics fm = {}; | 
|  | fg_paint.getFontMetrics(&fm); | 
|  | ascent_ = static_cast<int>(ceilf(-fm.fAscent)); | 
|  | line_height_ = ascent_ + static_cast<int>(ceilf(fm.fDescent + fm.fLeading)); | 
|  | DCHECK_GT(line_height_, 0); | 
|  | // To figure out the advance width, measure an X. Better hope the font is | 
|  | // monospace. | 
|  | advance_width_ = static_cast<int>(ceilf(fg_paint.measureText("X", 1))); | 
|  | DCHECK_GT(advance_width_, 0); | 
|  |  | 
|  | view_->AddObserver(this); | 
|  |  | 
|  | // Force an initial draw. | 
|  | Draw(true); | 
|  | } | 
|  |  | 
|  | MotermView::~MotermView() { | 
|  | if (driver_) | 
|  | driver_->Detach(); | 
|  | } | 
|  |  | 
|  | void MotermView::OnViewDestroyed(mojo::View* view) { | 
|  | DCHECK_EQ(view, view_); | 
|  | view_->RemoveObserver(this); | 
|  | delete this; | 
|  | } | 
|  |  | 
|  | void MotermView::OnViewBoundsChanged(mojo::View* view, | 
|  | const mojo::Rect& old_bounds, | 
|  | const mojo::Rect& new_bounds) { | 
|  | DCHECK_EQ(view, view_); | 
|  | gl_helper_.SetSurfaceSize(RectToSize(view_->bounds())); | 
|  | bitmap_device_.clear(); | 
|  | Draw(true); | 
|  | } | 
|  |  | 
|  | void MotermView::OnViewInputEvent(mojo::View* view, | 
|  | const mojo::EventPtr& event) { | 
|  | if (event->action == mojo::EventType::KEY_PRESSED) | 
|  | OnKeyPressed(event); | 
|  | } | 
|  |  | 
|  | void MotermView::OnSurfaceIdChanged(mojo::SurfaceIdPtr surface_id) { | 
|  | view_->SetSurfaceId(surface_id.Pass()); | 
|  | } | 
|  |  | 
|  | void MotermView::OnContextLost() { | 
|  | // TODO(vtl): We'll need to force a draw when we regain a context. | 
|  | } | 
|  |  | 
|  | void MotermView::OnFrameDisplayed(uint32_t frame_id) { | 
|  | DCHECK(frame_pending_); | 
|  | frame_pending_ = false; | 
|  | Draw(false); | 
|  | } | 
|  |  | 
|  | void MotermView::OnResponse(const void* buf, size_t size) { | 
|  | if (driver_) | 
|  | driver_->SendData(buf, size); | 
|  | } | 
|  |  | 
|  | void MotermView::OnSetKeypadMode(bool application_mode) { | 
|  | keypad_application_mode_ = application_mode; | 
|  | } | 
|  |  | 
|  | void MotermView::OnDataReceived(const void* bytes, size_t num_bytes) { | 
|  | model_.ProcessInput(bytes, num_bytes, &model_state_changes_); | 
|  | Draw(false); | 
|  | } | 
|  |  | 
|  | void MotermView::OnClosed() { | 
|  | DCHECK(driver_); | 
|  | driver_->Detach(); | 
|  | driver_.reset(); | 
|  |  | 
|  | OnDestroyed(); | 
|  | } | 
|  |  | 
|  | void MotermView::OnDestroyed() { | 
|  | DCHECK(!driver_); | 
|  | if (!on_closed_callback_.is_null()) { | 
|  | mojo::Closure callback; | 
|  | std::swap(callback, on_closed_callback_); | 
|  | callback.Run(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void MotermView::Create( | 
|  | mojo::ApplicationConnection* connection, | 
|  | mojo::InterfaceRequest<mojo::terminal::Terminal> request) { | 
|  | terminal_bindings_.AddBinding(this, request.Pass()); | 
|  | } | 
|  |  | 
|  | void MotermView::Connect( | 
|  | mojo::InterfaceRequest<mojo::files::File> terminal_file, | 
|  | bool force, | 
|  | const ConnectCallback& callback) { | 
|  | if (driver_) { | 
|  | // We already have a connection. | 
|  | if (force) { | 
|  | OnClosed(); | 
|  | } else { | 
|  | // TODO(vtl): Is this error code right? | 
|  | callback.Run(mojo::files::Error::UNAVAILABLE); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | driver_ = MotermDriver::Create(this, terminal_file.Pass()); | 
|  | DCHECK(on_closed_callback_.is_null()); | 
|  | on_closed_callback_ = [callback] { callback.Run(mojo::files::Error::OK); }; | 
|  | } | 
|  |  | 
|  | void MotermView::ConnectToClient( | 
|  | mojo::terminal::TerminalClientPtr terminal_client, | 
|  | bool force, | 
|  | const ConnectToClientCallback& callback) { | 
|  | if (driver_) { | 
|  | // We already have a connection. | 
|  | if (force) { | 
|  | OnClosed(); | 
|  | } else { | 
|  | // TODO(vtl): Is this error code right? | 
|  | callback.Run(mojo::files::Error::UNAVAILABLE); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | mojo::files::FilePtr file; | 
|  | driver_ = MotermDriver::Create(this, GetProxy(&file)); | 
|  | terminal_client->ConnectToTerminal(file.Pass()); | 
|  | DCHECK(on_closed_callback_.is_null()); | 
|  | on_closed_callback_ = [callback] { callback.Run(mojo::files::Error::OK); }; | 
|  | } | 
|  |  | 
|  | void MotermView::GetSize(const GetSizeCallback& callback) { | 
|  | MotermModel::Size size = model_.GetSize(); | 
|  | callback.Run(mojo::files::Error::OK, size.rows, size.columns); | 
|  | } | 
|  |  | 
|  | void MotermView::SetSize(uint32_t rows, | 
|  | uint32_t columns, | 
|  | bool reset, | 
|  | const SetSizeCallback& callback) { | 
|  | if (!rows) { | 
|  | rows = std::max(1u, std::min(MotermModel::kMaxRows, | 
|  | static_cast<uint32_t>(view_->bounds().height) / | 
|  | line_height_)); | 
|  | } | 
|  | if (!columns) { | 
|  | columns = | 
|  | std::max(1u, std::min(MotermModel::kMaxColumns, | 
|  | static_cast<uint32_t>(view_->bounds().width) / | 
|  | advance_width_)); | 
|  | } | 
|  |  | 
|  | model_.SetSize(MotermModel::Size(rows, columns), reset); | 
|  | callback.Run(mojo::files::Error::OK, rows, columns); | 
|  |  | 
|  | Draw(false); | 
|  | } | 
|  |  | 
|  | void MotermView::Draw(bool force) { | 
|  | // TODO(vtl): See TODO above |frame_pending_| in the class declaration. | 
|  | if (frame_pending_) { | 
|  | force_next_draw_ |= force; | 
|  | return; | 
|  | } | 
|  |  | 
|  | force |= force_next_draw_; | 
|  | force_next_draw_ = false; | 
|  |  | 
|  | if (!force && !model_state_changes_.IsDirty()) | 
|  | return; | 
|  |  | 
|  | // TODO(vtl): If |!force|, draw only the dirty region(s)? | 
|  | model_state_changes_.Reset(); | 
|  |  | 
|  | int32_t width = view_->bounds().width; | 
|  | int32_t height = view_->bounds().height; | 
|  | DCHECK_GT(width, 0); | 
|  | DCHECK_GT(height, 0); | 
|  |  | 
|  | if (!bitmap_device_) { | 
|  | bitmap_device_ = skia::AdoptRef(SkBitmapDevice::Create(SkImageInfo::Make( | 
|  | width, height, kN32_SkColorType, kOpaque_SkAlphaType))); | 
|  | } | 
|  |  | 
|  | SkCanvas canvas(bitmap_device_.get()); | 
|  | canvas.clear(SK_ColorBLACK); | 
|  |  | 
|  | SkPaint bg_paint; | 
|  | bg_paint.setStyle(SkPaint::kFill_Style); | 
|  |  | 
|  | SkPaint fg_paint; | 
|  | fg_paint.setTypeface(regular_typeface_.get()); | 
|  | fg_paint.setTextSize(16); | 
|  | fg_paint.setTextEncoding(SkPaint::kUTF32_TextEncoding); | 
|  |  | 
|  | MotermModel::Size size = model_.GetSize(); | 
|  | int y = 0; | 
|  | for (unsigned i = 0; i < size.rows; i++, y += line_height_) { | 
|  | int x = 0; | 
|  | for (unsigned j = 0; j < size.columns; j++, x += advance_width_) { | 
|  | MotermModel::CharacterInfo ch = | 
|  | model_.GetCharacterInfoAt(MotermModel::Position(i, j)); | 
|  |  | 
|  | // Paint the background. | 
|  | bg_paint.setColor(SkColorSetRGB(ch.background_color.red, | 
|  | ch.background_color.green, | 
|  | ch.background_color.blue)); | 
|  | canvas.drawRect(SkRect::MakeXYWH(x, y, advance_width_, line_height_), | 
|  | bg_paint); | 
|  |  | 
|  | // Paint the foreground. | 
|  | if (ch.code_point) { | 
|  | uint32_t flags = SkPaint::kAntiAlias_Flag; | 
|  | // TODO(vtl): Use real bold font? | 
|  | if ((ch.attributes & MotermModel::kAttributesBold)) | 
|  | flags |= SkPaint::kFakeBoldText_Flag; | 
|  | if ((ch.attributes & MotermModel::kAttributesUnderline)) | 
|  | flags |= SkPaint::kUnderlineText_Flag; | 
|  | // TODO(vtl): Handle blink, because that's awesome. | 
|  | fg_paint.setFlags(flags); | 
|  | fg_paint.setColor(SkColorSetRGB(ch.foreground_color.red, | 
|  | ch.foreground_color.green, | 
|  | ch.foreground_color.blue)); | 
|  |  | 
|  | canvas.drawText(&ch.code_point, sizeof(ch.code_point), x, y + ascent_, | 
|  | fg_paint); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (model_.GetCursorVisibility()) { | 
|  | // Draw the cursor. | 
|  | MotermModel::Position cursor_pos = model_.GetCursorPosition(); | 
|  | // Reuse the background paint, but don't just paint over. | 
|  | // TODO(vtl): Consider doing other things. Maybe make it blink, to be extra | 
|  | // annoying. | 
|  | // TODO(vtl): Maybe vary how we draw the cursor, depending on if we're | 
|  | // focused and/or active. | 
|  | bg_paint.setColor(SK_ColorWHITE); | 
|  | bg_paint.setXfermodeMode(SkXfermode::kDifference_Mode); | 
|  | canvas.drawRect(SkRect::MakeXYWH(cursor_pos.column * advance_width_, | 
|  | cursor_pos.row * line_height_, | 
|  | advance_width_, line_height_), | 
|  | bg_paint); | 
|  | } | 
|  |  | 
|  | canvas.flush(); | 
|  |  | 
|  | const SkBitmap& bitmap(bitmap_device_->accessBitmap(false)); | 
|  | // TODO(vtl): Do we need really need to lock/unlock pixels? | 
|  | SkAutoLockPixels pixel_locker(bitmap); | 
|  |  | 
|  | gl_helper_.StartFrame(); | 
|  | // (The texture is already bound.) | 
|  | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, kTextureFormat, | 
|  | GL_UNSIGNED_BYTE, bitmap.getPixels()); | 
|  | gl_helper_.EndFrame(); | 
|  | frame_pending_ = true; | 
|  | } | 
|  |  | 
|  | void MotermView::OnKeyPressed(const mojo::EventPtr& key_event) { | 
|  | std::string input_sequence = | 
|  | GetInputSequenceForKeyPressedEvent(*key_event, keypad_application_mode_); | 
|  | if (input_sequence.empty()) | 
|  | return; | 
|  |  | 
|  | if (driver_) | 
|  | driver_->SendData(input_sequence.data(), input_sequence.size()); | 
|  | } |