OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "apps/moterm/moterm_model.h" |
| 6 |
| 7 #include <string.h> |
| 8 |
| 9 #include <algorithm> |
| 10 #include <limits> |
| 11 |
| 12 #include "base/logging.h" |
| 13 |
| 14 namespace { |
| 15 |
| 16 // Moterm -> teken conversions: |
| 17 |
| 18 teken_pos_t MotermToTekenSize(const MotermModel::Size& size) { |
| 19 DCHECK_LE(size.rows, std::numeric_limits<teken_unit_t>::max()); |
| 20 DCHECK_LE(size.columns, std::numeric_limits<teken_unit_t>::max()); |
| 21 teken_pos_t rv = {static_cast<teken_unit_t>(size.rows), |
| 22 static_cast<teken_unit_t>(size.columns)}; |
| 23 return rv; |
| 24 } |
| 25 |
| 26 // Teken -> moterm conversions: |
| 27 |
| 28 MotermModel::Position TekenToMotermPosition(const teken_pos_t& position) { |
| 29 return MotermModel::Position(static_cast<int>(position.tp_row), |
| 30 static_cast<int>(position.tp_col)); |
| 31 } |
| 32 |
| 33 MotermModel::Size TekenToMotermSize(const teken_pos_t& size) { |
| 34 return MotermModel::Size(size.tp_row, size.tp_col); |
| 35 } |
| 36 |
| 37 MotermModel::Rectangle TekenToMotermRectangle(const teken_rect_t& rectangle) { |
| 38 return MotermModel::Rectangle( |
| 39 static_cast<int>(rectangle.tr_begin.tp_row), |
| 40 static_cast<int>(rectangle.tr_begin.tp_col), |
| 41 rectangle.tr_end.tp_row - rectangle.tr_begin.tp_row, |
| 42 rectangle.tr_end.tp_col - rectangle.tr_begin.tp_col); |
| 43 } |
| 44 |
| 45 MotermModel::Color TekenToMotermColor(teken_color_t color, bool bold) { |
| 46 static const uint8_t rgb[TC_NCOLORS][3] = { |
| 47 {0x00, 0x00, 0x00}, // Black. |
| 48 {0x80, 0x00, 0x00}, // Red. |
| 49 {0x00, 0x80, 0x00}, // Green. |
| 50 {0x80, 0x80, 0x00}, // Yellow (even if teken thinks it's brown). |
| 51 {0x00, 0x00, 0x80}, // Blue. |
| 52 {0x80, 0x00, 0x80}, // Magenta. |
| 53 {0x00, 0x80, 0x80}, // Cyan. |
| 54 {0xc0, 0xc0, 0xc0} // White. |
| 55 }; |
| 56 static const uint8_t bold_rgb[TC_NCOLORS][3] = { |
| 57 {0x80, 0x80, 0x80}, // Black. |
| 58 {0xff, 0x00, 0x00}, // Red. |
| 59 {0x00, 0xff, 0x00}, // Green. |
| 60 {0xff, 0xff, 0x00}, // Yellow (even if teken thinks it's brown). |
| 61 {0x00, 0x00, 0xff}, // Blue. |
| 62 {0xff, 0x00, 0xff}, // Magenta. |
| 63 {0x00, 0xff, 0xff}, // Cyan. |
| 64 {0xff, 0xff, 0xff} // White. |
| 65 }; |
| 66 DCHECK_LT(color, static_cast<unsigned>(TC_NCOLORS)); |
| 67 return bold ? MotermModel::Color(bold_rgb[color][0], bold_rgb[color][1], |
| 68 bold_rgb[color][2]) |
| 69 : MotermModel::Color(rgb[color][0], rgb[color][1], rgb[color][2]); |
| 70 } |
| 71 |
| 72 // Utility functions: |
| 73 |
| 74 MotermModel::Rectangle EnclosingRectangle(const MotermModel::Rectangle& rect1, |
| 75 const MotermModel::Rectangle& rect2) { |
| 76 if (rect1.IsEmpty()) |
| 77 return rect2; |
| 78 if (rect2.IsEmpty()) |
| 79 return rect1; |
| 80 |
| 81 int start_row = std::min(rect1.position.row, rect2.position.row); |
| 82 int start_col = std::min(rect1.position.column, rect2.position.column); |
| 83 // TODO(vtl): Some theoretical overflows here. |
| 84 int end_row = |
| 85 std::max(rect1.position.row + static_cast<int>(rect1.size.rows), |
| 86 rect2.position.row + static_cast<int>(rect2.size.rows)); |
| 87 int end_col = |
| 88 std::max(rect1.position.column + static_cast<int>(rect1.size.columns), |
| 89 rect2.position.column + static_cast<int>(rect2.size.columns)); |
| 90 DCHECK_LE(start_row, end_row); |
| 91 DCHECK_LE(start_col, end_col); |
| 92 return MotermModel::Rectangle(start_row, start_col, |
| 93 static_cast<unsigned>(end_row - start_row), |
| 94 static_cast<unsigned>(end_col - start_col)); |
| 95 } |
| 96 |
| 97 } // namespace |
| 98 |
| 99 const MotermModel::Attributes MotermModel::kAttributesBold; |
| 100 const MotermModel::Attributes MotermModel::kAttributesUnderline; |
| 101 const MotermModel::Attributes MotermModel::kAttributesBlink; |
| 102 |
| 103 const unsigned MotermModel::kMaxRows; |
| 104 const unsigned MotermModel::kMaxColumns; |
| 105 |
| 106 MotermModel::MotermModel(const Size& max_size, const Size& size) |
| 107 : max_size_(max_size), terminal_(), current_state_changes_() { |
| 108 DCHECK_GT(max_size_.rows, 0u); |
| 109 DCHECK_LE(max_size_.rows, kMaxRows); |
| 110 DCHECK_GT(max_size_.columns, 0u); |
| 111 DCHECK_LE(max_size_.columns, kMaxColumns); |
| 112 |
| 113 DCHECK_GT(size.rows, 0u); |
| 114 DCHECK_LE(size.rows, max_size_.rows); |
| 115 DCHECK_GT(size.columns, 0u); |
| 116 DCHECK_LE(size.columns, max_size_.columns); |
| 117 |
| 118 size_t num_chars = max_size_.rows * max_size_.columns; |
| 119 characters_.reset(new teken_char_t[num_chars]); |
| 120 memset(characters_.get(), 0, num_chars * sizeof(characters_[0])); |
| 121 attributes_.reset(new teken_attr_t[num_chars]); |
| 122 memset(attributes_.get(), 0, num_chars * sizeof(attributes_[0])); |
| 123 |
| 124 static const teken_funcs_t callbacks = {&MotermModel::OnBellThunk, |
| 125 &MotermModel::OnCursorThunk, |
| 126 &MotermModel::OnPutcharThunk, |
| 127 &MotermModel::OnFillThunk, |
| 128 &MotermModel::OnCopyThunk, |
| 129 &MotermModel::OnParamThunk, |
| 130 &MotermModel::OnRespondThunk}; |
| 131 teken_init(&terminal_, &callbacks, this); |
| 132 |
| 133 teken_pos_t s = MotermToTekenSize(size); |
| 134 teken_set_winsize(&terminal_, &s); |
| 135 } |
| 136 |
| 137 MotermModel::~MotermModel() { |
| 138 } |
| 139 |
| 140 void MotermModel::ProcessInput(const void* input_bytes, |
| 141 size_t num_input_bytes, |
| 142 StateChanges* state_changes) { |
| 143 DCHECK(state_changes); |
| 144 DCHECK(!current_state_changes_); |
| 145 current_state_changes_ = state_changes; |
| 146 |
| 147 // Get the initial cursor position, so we'll be able to tell if it moved. |
| 148 teken_pos_t initial_cursor_pos = *teken_get_cursor(&terminal_); |
| 149 |
| 150 // Note: This may call some of our callbacks. |
| 151 teken_input(&terminal_, input_bytes, num_input_bytes); |
| 152 |
| 153 teken_pos_t final_cursor_pos = *teken_get_cursor(&terminal_); |
| 154 if (initial_cursor_pos.tp_row != final_cursor_pos.tp_row || |
| 155 initial_cursor_pos.tp_col != final_cursor_pos.tp_col) { |
| 156 state_changes->cursor_moved = true; |
| 157 // Update dirty rect to include old and new cursor positions. |
| 158 current_state_changes_->dirty_rect = EnclosingRectangle( |
| 159 current_state_changes_->dirty_rect, |
| 160 Rectangle(initial_cursor_pos.tp_row, initial_cursor_pos.tp_col, 1, 1)); |
| 161 current_state_changes_->dirty_rect = EnclosingRectangle( |
| 162 current_state_changes_->dirty_rect, |
| 163 Rectangle(final_cursor_pos.tp_row, final_cursor_pos.tp_col, 1, 1)); |
| 164 } |
| 165 |
| 166 current_state_changes_ = nullptr; |
| 167 } |
| 168 |
| 169 MotermModel::Size MotermModel::GetSize() const { |
| 170 // Teken isn't const-correct, sadly. |
| 171 return TekenToMotermSize( |
| 172 *teken_get_winsize(const_cast<teken_t*>(&terminal_))); |
| 173 } |
| 174 |
| 175 MotermModel::Position MotermModel::GetCursorPosition() const { |
| 176 // Teken isn't const-correct, sadly. |
| 177 return TekenToMotermPosition( |
| 178 *teken_get_cursor(const_cast<teken_t*>(&terminal_))); |
| 179 } |
| 180 |
| 181 MotermModel::CharacterInfo MotermModel::GetCharacterInfoAt( |
| 182 const Position& position) const { |
| 183 DCHECK_GE(position.row, 0); |
| 184 DCHECK_LT(position.row, static_cast<int>(GetSize().rows)); |
| 185 DCHECK_GE(position.column, 0); |
| 186 DCHECK_LT(position.column, static_cast<int>(GetSize().columns)); |
| 187 |
| 188 uint32_t ch = characters_[position.row * max_size_.columns + position.column]; |
| 189 const teken_attr_t& teken_attr = |
| 190 attributes_[position.row * max_size_.columns + position.column]; |
| 191 Color fg = TekenToMotermColor(teken_attr.ta_fgcolor, |
| 192 (teken_attr.ta_format & TF_BOLD)); |
| 193 Color bg = TekenToMotermColor(teken_attr.ta_bgcolor, false); |
| 194 Attributes attr = 0; |
| 195 if ((teken_attr.ta_format & TF_BOLD)) |
| 196 attr |= kAttributesBold; |
| 197 if ((teken_attr.ta_format & TF_UNDERLINE)) |
| 198 attr |= kAttributesUnderline; |
| 199 if ((teken_attr.ta_format & TF_BLINK)) |
| 200 attr |= kAttributesBlink; |
| 201 if ((teken_attr.ta_format & TF_REVERSE)) |
| 202 std::swap(fg, bg); |
| 203 return CharacterInfo(ch, attr, fg, bg); |
| 204 } |
| 205 |
| 206 void MotermModel::SetSize(const Size& size, bool reset) { |
| 207 DCHECK_GT(size.rows, 1u); |
| 208 DCHECK_LE(size.rows, max_size_.rows); |
| 209 DCHECK_GT(size.columns, 1u); |
| 210 DCHECK_LE(size.columns, max_size_.columns); |
| 211 teken_pos_t teken_size = {static_cast<teken_unit_t>(size.rows), |
| 212 static_cast<teken_unit_t>(size.columns)}; |
| 213 if (reset) { |
| 214 teken_set_winsize_noreset(&terminal_, &teken_size); |
| 215 } else { |
| 216 // We'll try a bit harder to keep a sensible cursor position. |
| 217 teken_pos_t cursor_pos = |
| 218 *teken_get_cursor(const_cast<teken_t*>(&terminal_)); |
| 219 teken_set_winsize(&terminal_, &teken_size); |
| 220 if (cursor_pos.tp_row >= teken_size.tp_row) |
| 221 cursor_pos.tp_row = teken_size.tp_row - 1; |
| 222 if (cursor_pos.tp_col >= teken_size.tp_col) |
| 223 cursor_pos.tp_col = teken_size.tp_col - 1; |
| 224 teken_set_cursor(&terminal_, &cursor_pos); |
| 225 } |
| 226 } |
| 227 |
| 228 void MotermModel::OnBell() { |
| 229 DCHECK(current_state_changes_); |
| 230 current_state_changes_->bell_count++; |
| 231 } |
| 232 |
| 233 void MotermModel::OnCursor(const teken_pos_t* pos) { |
| 234 DCHECK(current_state_changes_); |
| 235 // Don't do anything. We'll just compare initial and final cursor positions. |
| 236 } |
| 237 |
| 238 void MotermModel::OnPutchar(const teken_pos_t* pos, |
| 239 teken_char_t ch, |
| 240 const teken_attr_t* attr) { |
| 241 character_at(pos->tp_row, pos->tp_col) = ch; |
| 242 attribute_at(pos->tp_row, pos->tp_col) = *attr; |
| 243 |
| 244 // Update dirty rect. |
| 245 DCHECK(current_state_changes_); |
| 246 current_state_changes_->dirty_rect = |
| 247 EnclosingRectangle(current_state_changes_->dirty_rect, |
| 248 Rectangle(pos->tp_row, pos->tp_col, 1, 1)); |
| 249 } |
| 250 |
| 251 void MotermModel::OnFill(const teken_rect_t* rect, |
| 252 teken_char_t ch, |
| 253 const teken_attr_t* attr) { |
| 254 for (size_t row = rect->tr_begin.tp_row; row < rect->tr_end.tp_row; row++) { |
| 255 for (size_t col = rect->tr_begin.tp_col; col < rect->tr_end.tp_col; col++) { |
| 256 character_at(row, col) = ch; |
| 257 attribute_at(row, col) = *attr; |
| 258 } |
| 259 } |
| 260 |
| 261 // Update dirty rect. |
| 262 DCHECK(current_state_changes_); |
| 263 current_state_changes_->dirty_rect = EnclosingRectangle( |
| 264 current_state_changes_->dirty_rect, TekenToMotermRectangle(*rect)); |
| 265 } |
| 266 |
| 267 void MotermModel::OnCopy(const teken_rect_t* rect, const teken_pos_t* pos) { |
| 268 unsigned height = rect->tr_end.tp_row - rect->tr_begin.tp_row; |
| 269 unsigned width = rect->tr_end.tp_col - rect->tr_begin.tp_col; |
| 270 |
| 271 // This is really a "move" (like |memmove()|) -- overlaps are likely. Process |
| 272 // the rows depending on which way (vertically) we're moving. |
| 273 if (pos->tp_row <= rect->tr_begin.tp_row) { |
| 274 // Start from the top row. |
| 275 for (unsigned row = 0; row < height; row++) { |
| 276 // Use |memmove()| here, to in case we're not moving vertically. |
| 277 memmove(&character_at(pos->tp_row + row, pos->tp_col), |
| 278 &character_at(rect->tr_begin.tp_row + row, pos->tp_col), |
| 279 width * sizeof(characters_[0])); |
| 280 memmove(&attribute_at(pos->tp_row + row, pos->tp_col), |
| 281 &attribute_at(rect->tr_begin.tp_row + row, pos->tp_col), |
| 282 width * sizeof(attributes_[0])); |
| 283 } |
| 284 } else { |
| 285 // Start from the bottom row. |
| 286 for (unsigned row = height; row > 0;) { |
| 287 row--; |
| 288 // We can use |memcpy()| here. |
| 289 memcpy(&character_at(pos->tp_row + row, pos->tp_col), |
| 290 &character_at(rect->tr_begin.tp_row + row, pos->tp_col), |
| 291 width * sizeof(characters_[0])); |
| 292 memcpy(&attribute_at(pos->tp_row + row, pos->tp_col), |
| 293 &attribute_at(rect->tr_begin.tp_row + row, pos->tp_col), |
| 294 width * sizeof(attributes_[0])); |
| 295 } |
| 296 } |
| 297 |
| 298 // Update dirty rect. |
| 299 DCHECK(current_state_changes_); |
| 300 current_state_changes_->dirty_rect = EnclosingRectangle( |
| 301 current_state_changes_->dirty_rect, |
| 302 Rectangle(static_cast<int>(pos->tp_row), static_cast<int>(pos->tp_col), |
| 303 width, height)); |
| 304 } |
| 305 |
| 306 void MotermModel::OnParam(int cmd, unsigned val) { |
| 307 // TODO(vtl) |
| 308 NOTIMPLEMENTED(); |
| 309 } |
| 310 |
| 311 void MotermModel::OnRespond(const void* buf, size_t size) { |
| 312 // TODO(vtl) |
| 313 NOTIMPLEMENTED(); |
| 314 } |
| 315 |
| 316 // static |
| 317 void MotermModel::OnBellThunk(void* ctx) { |
| 318 DCHECK(ctx); |
| 319 return static_cast<MotermModel*>(ctx)->OnBell(); |
| 320 } |
| 321 |
| 322 // static |
| 323 void MotermModel::OnCursorThunk(void* ctx, const teken_pos_t* pos) { |
| 324 DCHECK(ctx); |
| 325 return static_cast<MotermModel*>(ctx)->OnCursor(pos); |
| 326 } |
| 327 |
| 328 // static |
| 329 void MotermModel::OnPutcharThunk(void* ctx, |
| 330 const teken_pos_t* pos, |
| 331 teken_char_t ch, |
| 332 const teken_attr_t* attr) { |
| 333 DCHECK(ctx); |
| 334 return static_cast<MotermModel*>(ctx)->OnPutchar(pos, ch, attr); |
| 335 } |
| 336 |
| 337 // static |
| 338 void MotermModel::OnFillThunk(void* ctx, |
| 339 const teken_rect_t* rect, |
| 340 teken_char_t ch, |
| 341 const teken_attr_t* attr) { |
| 342 DCHECK(ctx); |
| 343 return static_cast<MotermModel*>(ctx)->OnFill(rect, ch, attr); |
| 344 } |
| 345 |
| 346 // static |
| 347 void MotermModel::OnCopyThunk(void* ctx, |
| 348 const teken_rect_t* rect, |
| 349 const teken_pos_t* pos) { |
| 350 DCHECK(ctx); |
| 351 return static_cast<MotermModel*>(ctx)->OnCopy(rect, pos); |
| 352 } |
| 353 |
| 354 // static |
| 355 void MotermModel::OnParamThunk(void* ctx, int cmd, unsigned val) { |
| 356 DCHECK(ctx); |
| 357 return static_cast<MotermModel*>(ctx)->OnParam(cmd, val); |
| 358 } |
| 359 |
| 360 // static |
| 361 void MotermModel::OnRespondThunk(void* ctx, const void* buf, size_t size) { |
| 362 DCHECK(ctx); |
| 363 return static_cast<MotermModel*>(ctx)->OnRespond(buf, size); |
| 364 } |
OLD | NEW |