| 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);
|
| +}
|
|
|