Index: apps/moterm/moterm_model.cc |
diff --git a/apps/moterm/moterm_model.cc b/apps/moterm/moterm_model.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3b321585b0ab52c10b02cff8cd3330168190b514 |
--- /dev/null |
+++ b/apps/moterm/moterm_model.cc |
@@ -0,0 +1,364 @@ |
+// 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_model.h" |
+ |
+#include <string.h> |
+ |
+#include <algorithm> |
+#include <limits> |
+ |
+#include "base/logging.h" |
+ |
+namespace { |
+ |
+// Moterm -> teken conversions: |
+ |
+teken_pos_t MotermToTekenSize(const MotermModel::Size& size) { |
+ DCHECK_LE(size.rows, std::numeric_limits<teken_unit_t>::max()); |
+ DCHECK_LE(size.columns, std::numeric_limits<teken_unit_t>::max()); |
+ teken_pos_t rv = {static_cast<teken_unit_t>(size.rows), |
+ static_cast<teken_unit_t>(size.columns)}; |
+ return rv; |
+} |
+ |
+// Teken -> moterm conversions: |
+ |
+MotermModel::Position TekenToMotermPosition(const teken_pos_t& position) { |
+ return MotermModel::Position(static_cast<int>(position.tp_row), |
+ static_cast<int>(position.tp_col)); |
+} |
+ |
+MotermModel::Size TekenToMotermSize(const teken_pos_t& size) { |
+ return MotermModel::Size(size.tp_row, size.tp_col); |
+} |
+ |
+MotermModel::Rectangle TekenToMotermRectangle(const teken_rect_t& rectangle) { |
+ return MotermModel::Rectangle( |
+ static_cast<int>(rectangle.tr_begin.tp_row), |
+ static_cast<int>(rectangle.tr_begin.tp_col), |
+ rectangle.tr_end.tp_row - rectangle.tr_begin.tp_row, |
+ rectangle.tr_end.tp_col - rectangle.tr_begin.tp_col); |
+} |
+ |
+MotermModel::Color TekenToMotermColor(teken_color_t color, bool bold) { |
+ static const uint8_t rgb[TC_NCOLORS][3] = { |
+ {0x00, 0x00, 0x00}, // Black. |
+ {0x80, 0x00, 0x00}, // Red. |
+ {0x00, 0x80, 0x00}, // Green. |
+ {0x80, 0x80, 0x00}, // Yellow (even if teken thinks it's brown). |
+ {0x00, 0x00, 0x80}, // Blue. |
+ {0x80, 0x00, 0x80}, // Magenta. |
+ {0x00, 0x80, 0x80}, // Cyan. |
+ {0xc0, 0xc0, 0xc0} // White. |
+ }; |
+ static const uint8_t bold_rgb[TC_NCOLORS][3] = { |
+ {0x80, 0x80, 0x80}, // Black. |
+ {0xff, 0x00, 0x00}, // Red. |
+ {0x00, 0xff, 0x00}, // Green. |
+ {0xff, 0xff, 0x00}, // Yellow (even if teken thinks it's brown). |
+ {0x00, 0x00, 0xff}, // Blue. |
+ {0xff, 0x00, 0xff}, // Magenta. |
+ {0x00, 0xff, 0xff}, // Cyan. |
+ {0xff, 0xff, 0xff} // White. |
+ }; |
+ DCHECK_LT(color, static_cast<unsigned>(TC_NCOLORS)); |
+ return bold ? MotermModel::Color(bold_rgb[color][0], bold_rgb[color][1], |
+ bold_rgb[color][2]) |
+ : MotermModel::Color(rgb[color][0], rgb[color][1], rgb[color][2]); |
+} |
+ |
+// Utility functions: |
+ |
+MotermModel::Rectangle EnclosingRectangle(const MotermModel::Rectangle& rect1, |
+ const MotermModel::Rectangle& rect2) { |
+ if (rect1.IsEmpty()) |
+ return rect2; |
+ if (rect2.IsEmpty()) |
+ return rect1; |
+ |
+ int start_row = std::min(rect1.position.row, rect2.position.row); |
+ int start_col = std::min(rect1.position.column, rect2.position.column); |
+ // TODO(vtl): Some theoretical overflows here. |
+ int end_row = |
+ std::max(rect1.position.row + static_cast<int>(rect1.size.rows), |
+ rect2.position.row + static_cast<int>(rect2.size.rows)); |
+ int end_col = |
+ std::max(rect1.position.column + static_cast<int>(rect1.size.columns), |
+ rect2.position.column + static_cast<int>(rect2.size.columns)); |
+ DCHECK_LE(start_row, end_row); |
+ DCHECK_LE(start_col, end_col); |
+ return MotermModel::Rectangle(start_row, start_col, |
+ static_cast<unsigned>(end_row - start_row), |
+ static_cast<unsigned>(end_col - start_col)); |
+} |
+ |
+} // namespace |
+ |
+const MotermModel::Attributes MotermModel::kAttributesBold; |
+const MotermModel::Attributes MotermModel::kAttributesUnderline; |
+const MotermModel::Attributes MotermModel::kAttributesBlink; |
+ |
+const unsigned MotermModel::kMaxRows; |
+const unsigned MotermModel::kMaxColumns; |
+ |
+MotermModel::MotermModel(const Size& max_size, const Size& size) |
+ : max_size_(max_size), terminal_(), current_state_changes_() { |
+ DCHECK_GT(max_size_.rows, 0u); |
+ DCHECK_LE(max_size_.rows, kMaxRows); |
+ DCHECK_GT(max_size_.columns, 0u); |
+ DCHECK_LE(max_size_.columns, kMaxColumns); |
+ |
+ DCHECK_GT(size.rows, 0u); |
+ DCHECK_LE(size.rows, max_size_.rows); |
+ DCHECK_GT(size.columns, 0u); |
+ DCHECK_LE(size.columns, max_size_.columns); |
+ |
+ size_t num_chars = max_size_.rows * max_size_.columns; |
+ characters_.reset(new teken_char_t[num_chars]); |
+ memset(characters_.get(), 0, num_chars * sizeof(characters_[0])); |
+ attributes_.reset(new teken_attr_t[num_chars]); |
+ memset(attributes_.get(), 0, num_chars * sizeof(attributes_[0])); |
+ |
+ static const teken_funcs_t callbacks = {&MotermModel::OnBellThunk, |
+ &MotermModel::OnCursorThunk, |
+ &MotermModel::OnPutcharThunk, |
+ &MotermModel::OnFillThunk, |
+ &MotermModel::OnCopyThunk, |
+ &MotermModel::OnParamThunk, |
+ &MotermModel::OnRespondThunk}; |
+ teken_init(&terminal_, &callbacks, this); |
+ |
+ teken_pos_t s = MotermToTekenSize(size); |
+ teken_set_winsize(&terminal_, &s); |
+} |
+ |
+MotermModel::~MotermModel() { |
+} |
+ |
+void MotermModel::ProcessInput(const void* input_bytes, |
+ size_t num_input_bytes, |
+ StateChanges* state_changes) { |
+ DCHECK(state_changes); |
+ DCHECK(!current_state_changes_); |
+ current_state_changes_ = state_changes; |
+ |
+ // Get the initial cursor position, so we'll be able to tell if it moved. |
+ teken_pos_t initial_cursor_pos = *teken_get_cursor(&terminal_); |
+ |
+ // Note: This may call some of our callbacks. |
+ teken_input(&terminal_, input_bytes, num_input_bytes); |
+ |
+ teken_pos_t final_cursor_pos = *teken_get_cursor(&terminal_); |
+ if (initial_cursor_pos.tp_row != final_cursor_pos.tp_row || |
+ initial_cursor_pos.tp_col != final_cursor_pos.tp_col) { |
+ state_changes->cursor_moved = true; |
+ // Update dirty rect to include old and new cursor positions. |
+ current_state_changes_->dirty_rect = EnclosingRectangle( |
+ current_state_changes_->dirty_rect, |
+ Rectangle(initial_cursor_pos.tp_row, initial_cursor_pos.tp_col, 1, 1)); |
+ current_state_changes_->dirty_rect = EnclosingRectangle( |
+ current_state_changes_->dirty_rect, |
+ Rectangle(final_cursor_pos.tp_row, final_cursor_pos.tp_col, 1, 1)); |
+ } |
+ |
+ current_state_changes_ = nullptr; |
+} |
+ |
+MotermModel::Size MotermModel::GetSize() const { |
+ // Teken isn't const-correct, sadly. |
+ return TekenToMotermSize( |
+ *teken_get_winsize(const_cast<teken_t*>(&terminal_))); |
+} |
+ |
+MotermModel::Position MotermModel::GetCursorPosition() const { |
+ // Teken isn't const-correct, sadly. |
+ return TekenToMotermPosition( |
+ *teken_get_cursor(const_cast<teken_t*>(&terminal_))); |
+} |
+ |
+MotermModel::CharacterInfo MotermModel::GetCharacterInfoAt( |
+ const Position& position) const { |
+ DCHECK_GE(position.row, 0); |
+ DCHECK_LT(position.row, static_cast<int>(GetSize().rows)); |
+ DCHECK_GE(position.column, 0); |
+ DCHECK_LT(position.column, static_cast<int>(GetSize().columns)); |
+ |
+ uint32_t ch = characters_[position.row * max_size_.columns + position.column]; |
+ const teken_attr_t& teken_attr = |
+ attributes_[position.row * max_size_.columns + position.column]; |
+ Color fg = TekenToMotermColor(teken_attr.ta_fgcolor, |
+ (teken_attr.ta_format & TF_BOLD)); |
+ Color bg = TekenToMotermColor(teken_attr.ta_bgcolor, false); |
+ Attributes attr = 0; |
+ if ((teken_attr.ta_format & TF_BOLD)) |
+ attr |= kAttributesBold; |
+ if ((teken_attr.ta_format & TF_UNDERLINE)) |
+ attr |= kAttributesUnderline; |
+ if ((teken_attr.ta_format & TF_BLINK)) |
+ attr |= kAttributesBlink; |
+ if ((teken_attr.ta_format & TF_REVERSE)) |
+ std::swap(fg, bg); |
+ return CharacterInfo(ch, attr, fg, bg); |
+} |
+ |
+void MotermModel::SetSize(const Size& size, bool reset) { |
+ DCHECK_GT(size.rows, 1u); |
+ DCHECK_LE(size.rows, max_size_.rows); |
+ DCHECK_GT(size.columns, 1u); |
+ DCHECK_LE(size.columns, max_size_.columns); |
+ teken_pos_t teken_size = {static_cast<teken_unit_t>(size.rows), |
+ static_cast<teken_unit_t>(size.columns)}; |
+ if (reset) { |
+ teken_set_winsize_noreset(&terminal_, &teken_size); |
+ } else { |
+ // We'll try a bit harder to keep a sensible cursor position. |
+ teken_pos_t cursor_pos = |
+ *teken_get_cursor(const_cast<teken_t*>(&terminal_)); |
+ teken_set_winsize(&terminal_, &teken_size); |
+ if (cursor_pos.tp_row >= teken_size.tp_row) |
+ cursor_pos.tp_row = teken_size.tp_row - 1; |
+ if (cursor_pos.tp_col >= teken_size.tp_col) |
+ cursor_pos.tp_col = teken_size.tp_col - 1; |
+ teken_set_cursor(&terminal_, &cursor_pos); |
+ } |
+} |
+ |
+void MotermModel::OnBell() { |
+ DCHECK(current_state_changes_); |
+ current_state_changes_->bell_count++; |
+} |
+ |
+void MotermModel::OnCursor(const teken_pos_t* pos) { |
+ DCHECK(current_state_changes_); |
+ // Don't do anything. We'll just compare initial and final cursor positions. |
+} |
+ |
+void MotermModel::OnPutchar(const teken_pos_t* pos, |
+ teken_char_t ch, |
+ const teken_attr_t* attr) { |
+ character_at(pos->tp_row, pos->tp_col) = ch; |
+ attribute_at(pos->tp_row, pos->tp_col) = *attr; |
+ |
+ // Update dirty rect. |
+ DCHECK(current_state_changes_); |
+ current_state_changes_->dirty_rect = |
+ EnclosingRectangle(current_state_changes_->dirty_rect, |
+ Rectangle(pos->tp_row, pos->tp_col, 1, 1)); |
+} |
+ |
+void MotermModel::OnFill(const teken_rect_t* rect, |
+ teken_char_t ch, |
+ const teken_attr_t* attr) { |
+ for (size_t row = rect->tr_begin.tp_row; row < rect->tr_end.tp_row; row++) { |
+ for (size_t col = rect->tr_begin.tp_col; col < rect->tr_end.tp_col; col++) { |
+ character_at(row, col) = ch; |
+ attribute_at(row, col) = *attr; |
+ } |
+ } |
+ |
+ // Update dirty rect. |
+ DCHECK(current_state_changes_); |
+ current_state_changes_->dirty_rect = EnclosingRectangle( |
+ current_state_changes_->dirty_rect, TekenToMotermRectangle(*rect)); |
+} |
+ |
+void MotermModel::OnCopy(const teken_rect_t* rect, const teken_pos_t* pos) { |
+ unsigned height = rect->tr_end.tp_row - rect->tr_begin.tp_row; |
+ unsigned width = rect->tr_end.tp_col - rect->tr_begin.tp_col; |
+ |
+ // This is really a "move" (like |memmove()|) -- overlaps are likely. Process |
+ // the rows depending on which way (vertically) we're moving. |
+ if (pos->tp_row <= rect->tr_begin.tp_row) { |
+ // Start from the top row. |
+ for (unsigned row = 0; row < height; row++) { |
+ // Use |memmove()| here, to in case we're not moving vertically. |
+ memmove(&character_at(pos->tp_row + row, pos->tp_col), |
+ &character_at(rect->tr_begin.tp_row + row, pos->tp_col), |
+ width * sizeof(characters_[0])); |
+ memmove(&attribute_at(pos->tp_row + row, pos->tp_col), |
+ &attribute_at(rect->tr_begin.tp_row + row, pos->tp_col), |
+ width * sizeof(attributes_[0])); |
+ } |
+ } else { |
+ // Start from the bottom row. |
+ for (unsigned row = height; row > 0;) { |
+ row--; |
+ // We can use |memcpy()| here. |
+ memcpy(&character_at(pos->tp_row + row, pos->tp_col), |
+ &character_at(rect->tr_begin.tp_row + row, pos->tp_col), |
+ width * sizeof(characters_[0])); |
+ memcpy(&attribute_at(pos->tp_row + row, pos->tp_col), |
+ &attribute_at(rect->tr_begin.tp_row + row, pos->tp_col), |
+ width * sizeof(attributes_[0])); |
+ } |
+ } |
+ |
+ // Update dirty rect. |
+ DCHECK(current_state_changes_); |
+ current_state_changes_->dirty_rect = EnclosingRectangle( |
+ current_state_changes_->dirty_rect, |
+ Rectangle(static_cast<int>(pos->tp_row), static_cast<int>(pos->tp_col), |
+ width, height)); |
+} |
+ |
+void MotermModel::OnParam(int cmd, unsigned val) { |
+ // TODO(vtl) |
+ NOTIMPLEMENTED(); |
+} |
+ |
+void MotermModel::OnRespond(const void* buf, size_t size) { |
+ // TODO(vtl) |
+ NOTIMPLEMENTED(); |
+} |
+ |
+// static |
+void MotermModel::OnBellThunk(void* ctx) { |
+ DCHECK(ctx); |
+ return static_cast<MotermModel*>(ctx)->OnBell(); |
+} |
+ |
+// static |
+void MotermModel::OnCursorThunk(void* ctx, const teken_pos_t* pos) { |
+ DCHECK(ctx); |
+ return static_cast<MotermModel*>(ctx)->OnCursor(pos); |
+} |
+ |
+// static |
+void MotermModel::OnPutcharThunk(void* ctx, |
+ const teken_pos_t* pos, |
+ teken_char_t ch, |
+ const teken_attr_t* attr) { |
+ DCHECK(ctx); |
+ return static_cast<MotermModel*>(ctx)->OnPutchar(pos, ch, attr); |
+} |
+ |
+// static |
+void MotermModel::OnFillThunk(void* ctx, |
+ const teken_rect_t* rect, |
+ teken_char_t ch, |
+ const teken_attr_t* attr) { |
+ DCHECK(ctx); |
+ return static_cast<MotermModel*>(ctx)->OnFill(rect, ch, attr); |
+} |
+ |
+// static |
+void MotermModel::OnCopyThunk(void* ctx, |
+ const teken_rect_t* rect, |
+ const teken_pos_t* pos) { |
+ DCHECK(ctx); |
+ return static_cast<MotermModel*>(ctx)->OnCopy(rect, pos); |
+} |
+ |
+// static |
+void MotermModel::OnParamThunk(void* ctx, int cmd, unsigned val) { |
+ DCHECK(ctx); |
+ return static_cast<MotermModel*>(ctx)->OnParam(cmd, val); |
+} |
+ |
+// static |
+void MotermModel::OnRespondThunk(void* ctx, const void* buf, size_t size) { |
+ DCHECK(ctx); |
+ return static_cast<MotermModel*>(ctx)->OnRespond(buf, size); |
+} |