1 /* 2 * Copyright 2012-2015, Adrien Destugues, pulkomandy@gmail.com 3 * Distributed under the terms of the MIT licence. 4 */ 5 6 7 #include "TermView.h" 8 9 #include <stdio.h> 10 11 #include <Entry.h> 12 #include <File.h> 13 #include <Font.h> 14 #include <Layout.h> 15 #include <ScrollBar.h> 16 17 #include "SerialApp.h" 18 19 20 struct ScrollBufferItem { 21 int cols; 22 VTermScreenCell cells[]; 23 }; 24 25 26 TermView::TermView() 27 : 28 BView("TermView", B_WILL_DRAW | B_FRAME_EVENTS) 29 { 30 _Init(); 31 } 32 33 34 TermView::TermView(BRect r) 35 : 36 BView(r, "TermView", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_FRAME_EVENTS) 37 { 38 _Init(); 39 } 40 41 42 TermView::~TermView() 43 { 44 vterm_free(fTerm); 45 } 46 47 48 void 49 TermView::AttachedToWindow() 50 { 51 BView::AttachedToWindow(); 52 MakeFocus(); 53 } 54 55 56 void 57 TermView::Draw(BRect updateRect) 58 { 59 VTermRect updatedChars = _PixelsToGlyphs(updateRect); 60 61 VTermPos pos; 62 font_height height; 63 GetFontHeight(&height); 64 65 VTermPos cursorPos; 66 vterm_state_get_cursorpos(vterm_obtain_state(fTerm), &cursorPos); 67 68 for (pos.row = updatedChars.start_row; pos.row <= updatedChars.end_row; 69 pos.row++) { 70 int x = updatedChars.start_col * fFontWidth + kBorderSpacing; 71 int y = pos.row * fFontHeight + (int)ceil(height.ascent) 72 + kBorderSpacing; 73 MovePenTo(x, y); 74 75 for (pos.col = updatedChars.start_col; 76 pos.col <= updatedChars.end_col;) { 77 VTermScreenCell cell; 78 79 _GetCell(pos, cell); 80 81 rgb_color foreground, background; 82 foreground.red = cell.fg.red; 83 foreground.green = cell.fg.green; 84 foreground.blue = cell.fg.blue; 85 foreground.alpha = 255; 86 background.red = cell.bg.red; 87 background.green = cell.bg.green; 88 background.blue = cell.bg.blue; 89 background.alpha = 255; 90 91 // Draw the cursor by swapping foreground and background colors 92 if ((pos.col == cursorPos.col && pos.row == cursorPos.row)) { 93 SetLowColor(foreground); 94 SetViewColor(foreground); 95 SetHighColor(background); 96 } else { 97 SetLowColor(background); 98 SetViewColor(background); 99 SetHighColor(foreground); 100 } 101 102 FillRect(BRect(x, y - ceil(height.ascent) + 1, 103 x + cell.width * fFontWidth - 1, 104 y + ceil(height.descent) + ceil(height.leading)), 105 B_SOLID_LOW); 106 107 BFont font = be_fixed_font; 108 if (cell.attrs.bold) 109 font.SetFace(B_BOLD_FACE); 110 if (cell.attrs.underline) 111 font.SetFace(B_UNDERSCORE_FACE); 112 if (cell.attrs.italic) 113 font.SetFace(B_ITALIC_FACE); 114 if (cell.attrs.blink) // FIXME make it actually blink 115 font.SetFace(B_OUTLINED_FACE); 116 if (cell.attrs.reverse) 117 font.SetFace(B_NEGATIVE_FACE); 118 if (cell.attrs.strike) 119 font.SetFace(B_STRIKEOUT_FACE); 120 121 // TODO handle "font" (alternate fonts), dwl and dhl (double size) 122 123 SetFont(&font); 124 125 if (cell.chars[0] == 0) { 126 DrawString(" "); 127 pos.col ++; 128 x += fFontWidth; 129 } else { 130 char buffer[VTERM_MAX_CHARS_PER_CELL]; 131 wcstombs(buffer, (wchar_t*)cell.chars, 132 VTERM_MAX_CHARS_PER_CELL); 133 134 DrawString(buffer); 135 x += (int)ceil(StringWidth(buffer)); 136 pos.col += cell.width; 137 } 138 } 139 } 140 } 141 142 143 void 144 TermView::FrameResized(float width, float height) 145 { 146 VTermRect newSize = _PixelsToGlyphs(BRect(0, 0, width - 2 * kBorderSpacing, 147 height - 2 * kBorderSpacing)); 148 vterm_set_size(fTerm, newSize.end_row, newSize.end_col); 149 } 150 151 152 void 153 TermView::GetPreferredSize(float* width, float* height) 154 { 155 if (width != NULL) 156 *width = kDefaultWidth * fFontWidth + 2 * kBorderSpacing - 1; 157 if (height != NULL) 158 *height = kDefaultHeight * fFontHeight + 2 * kBorderSpacing - 1; 159 } 160 161 162 void 163 TermView::KeyDown(const char* bytes, int32 numBytes) 164 { 165 // Translate some keys to more usual VT100 escape codes 166 switch (bytes[0]) { 167 case B_UP_ARROW: 168 numBytes = 3; 169 bytes = "\x1B[A"; 170 break; 171 case B_DOWN_ARROW: 172 numBytes = 3; 173 bytes = "\x1B[B"; 174 break; 175 case B_RIGHT_ARROW: 176 numBytes = 3; 177 bytes = "\x1B[C"; 178 break; 179 case B_LEFT_ARROW: 180 numBytes = 3; 181 bytes = "\x1B[D"; 182 break; 183 case B_BACKSPACE: 184 numBytes = 1; 185 bytes = "\x7F"; 186 break; 187 case '\n': 188 numBytes = fLineTerminator.Length(); 189 bytes = fLineTerminator.String(); 190 break; 191 } 192 193 // Send the bytes to the serial port 194 BMessage* keyEvent = new BMessage(kMsgDataWrite); 195 keyEvent->AddData("data", B_RAW_TYPE, bytes, numBytes); 196 be_app_messenger.SendMessage(keyEvent); 197 } 198 199 200 void 201 TermView::MessageReceived(BMessage* message) 202 { 203 switch(message->what) 204 { 205 case 'DATA': 206 { 207 entry_ref ref; 208 if (message->FindRef("refs", &ref) == B_OK) 209 { 210 // The user just dropped a file on us 211 // TODO send it by XMODEM or so 212 } 213 break; 214 } 215 default: 216 BView::MessageReceived(message); 217 } 218 } 219 220 221 void 222 TermView::SetLineTerminator(BString terminator) 223 { 224 fLineTerminator = terminator; 225 } 226 227 228 void 229 TermView::PushBytes(const char* bytes, size_t length) 230 { 231 vterm_push_bytes(fTerm, bytes, length); 232 } 233 234 235 // #pragma mark - 236 237 238 void 239 TermView::_Init() 240 { 241 SetFont(be_fixed_font); 242 243 font_height height; 244 GetFontHeight(&height); 245 fFontHeight = (int)(ceilf(height.ascent) + ceilf(height.descent) 246 + ceilf(height.leading)); 247 fFontWidth = (int)be_fixed_font->StringWidth("X"); 248 fTerm = vterm_new(kDefaultHeight, kDefaultWidth); 249 250 fTermScreen = vterm_obtain_screen(fTerm); 251 vterm_screen_set_callbacks(fTermScreen, &sScreenCallbacks, this); 252 vterm_screen_reset(fTermScreen, 1); 253 254 vterm_parser_set_utf8(fTerm, 1); 255 256 VTermScreenCell cell; 257 VTermPos firstPos; 258 firstPos.row = 0; 259 firstPos.col = 0; 260 _GetCell(firstPos, cell); 261 262 rgb_color background; 263 background.red = cell.bg.red; 264 background.green = cell.bg.green; 265 background.blue = cell.bg.blue; 266 background.alpha = 255; 267 268 SetViewColor(background); 269 SetLineTerminator("\n"); 270 } 271 272 273 VTermRect 274 TermView::_PixelsToGlyphs(BRect pixels) const 275 { 276 pixels.OffsetBy(-kBorderSpacing, -kBorderSpacing); 277 278 VTermRect rect; 279 rect.start_col = (int)floor(pixels.left / fFontWidth); 280 rect.end_col = (int)ceil(pixels.right / fFontWidth); 281 rect.start_row = (int)floor(pixels.top / fFontHeight); 282 rect.end_row = (int)ceil(pixels.bottom / fFontHeight); 283 #if 0 284 printf( 285 "TOP %d ch < %f px\n" 286 "BTM %d ch < %f px\n" 287 "LFT %d ch < %f px\n" 288 "RGH %d ch < %f px\n", 289 rect.start_row, pixels.top, 290 rect.end_row, pixels.bottom, 291 rect.start_col, pixels.left, 292 rect.end_col, pixels.right 293 ); 294 #endif 295 return rect; 296 } 297 298 299 BRect TermView::_GlyphsToPixels(const VTermRect& glyphs) const 300 { 301 BRect rect; 302 rect.top = glyphs.start_row * fFontHeight; 303 rect.bottom = glyphs.end_row * fFontHeight; 304 rect.left = glyphs.start_col * fFontWidth; 305 rect.right = glyphs.end_col * fFontWidth; 306 307 rect.OffsetBy(kBorderSpacing, kBorderSpacing); 308 #if 0 309 printf( 310 "TOP %d ch > %f px (%f)\n" 311 "BTM %d ch > %f px\n" 312 "LFT %d ch > %f px (%f)\n" 313 "RGH %d ch > %f px\n", 314 glyphs.start_row, rect.top, fFontHeight, 315 glyphs.end_row, rect.bottom, 316 glyphs.start_col, rect.left, fFontWidth, 317 glyphs.end_col, rect.right 318 ); 319 #endif 320 return rect; 321 } 322 323 324 BRect 325 TermView::_GlyphsToPixels(const int width, const int height) const 326 { 327 VTermRect rect; 328 rect.start_row = 0; 329 rect.start_col = 0; 330 rect.end_row = height; 331 rect.end_col = width; 332 return _GlyphsToPixels(rect); 333 } 334 335 336 void 337 TermView::_GetCell(VTermPos pos, VTermScreenCell& cell) 338 { 339 // First handle cells from the normal screen 340 if (vterm_screen_get_cell(fTermScreen, pos, &cell) != 0) 341 return; 342 343 // Try the scroll-back buffer 344 if (pos.row < 0 && pos.col >= 0) { 345 int offset = - pos.row - 1; 346 ScrollBufferItem* line 347 = (ScrollBufferItem*)fScrollBuffer.ItemAt(offset); 348 if (line != NULL && pos.col < line->cols) { 349 cell = line->cells[pos.col]; 350 return; 351 } 352 } 353 354 // All cells outside the used terminal area are drawn with the same 355 // background color as the top-left one. 356 // TODO should they use the attributes of the closest neighbor instead? 357 VTermPos firstPos; 358 firstPos.row = 0; 359 firstPos.col = 0; 360 vterm_screen_get_cell(fTermScreen, firstPos, &cell); 361 cell.chars[0] = 0; 362 cell.width = 1; 363 } 364 365 366 void 367 TermView::_Damage(VTermRect rect) 368 { 369 Invalidate(_GlyphsToPixels(rect)); 370 } 371 372 373 void 374 TermView::_MoveCursor(VTermPos pos, VTermPos oldPos, int visible) 375 { 376 VTermRect r; 377 r.start_row = pos.row; 378 r.start_col = pos.col; 379 r.end_col = pos.col + 1; 380 r.end_row = pos.row + 1; 381 Invalidate(_GlyphsToPixels(r)); 382 383 r.start_row = oldPos.row; 384 r.start_col = oldPos.col; 385 r.end_col = oldPos.col + 1; 386 r.end_row = oldPos.row + 1; 387 Invalidate(_GlyphsToPixels(r)); 388 } 389 390 391 void 392 TermView::_PushLine(int cols, const VTermScreenCell* cells) 393 { 394 ScrollBufferItem* item = (ScrollBufferItem*)malloc(sizeof(int) 395 + cols * sizeof(VTermScreenCell)); 396 item->cols = cols; 397 memcpy(item->cells, cells, cols * sizeof(VTermScreenCell)); 398 399 fScrollBuffer.AddItem(item, 0); 400 401 free(fScrollBuffer.RemoveItem(kScrollBackSize)); 402 403 int availableRows, availableCols; 404 vterm_get_size(fTerm, &availableRows, &availableCols); 405 406 VTermRect dirty; 407 dirty.start_col = 0; 408 dirty.end_col = availableCols; 409 dirty.end_row = 0; 410 dirty.start_row = -fScrollBuffer.CountItems(); 411 // FIXME we should rather use CopyRect if possible, and only invalidate the 412 // newly exposed area here. 413 Invalidate(_GlyphsToPixels(dirty)); 414 415 BScrollBar* scrollBar = ScrollBar(B_VERTICAL); 416 if (scrollBar != NULL) { 417 float range = (fScrollBuffer.CountItems() + availableRows) 418 * fFontHeight; 419 scrollBar->SetRange(availableRows * fFontHeight - range, 0.0f); 420 // TODO we need to adjust this in FrameResized, as availableRows can 421 // change 422 scrollBar->SetProportion(availableRows * fFontHeight / range); 423 scrollBar->SetSteps(fFontHeight, fFontHeight * 3); 424 } 425 } 426 427 428 /* static */ int 429 TermView::_Damage(VTermRect rect, void* user) 430 { 431 TermView* view = (TermView*)user; 432 view->_Damage(rect); 433 434 return 0; 435 } 436 437 438 /* static */ int 439 TermView::_MoveCursor(VTermPos pos, VTermPos oldPos, int visible, void* user) 440 { 441 TermView* view = (TermView*)user; 442 view->_MoveCursor(pos, oldPos, visible); 443 444 return 0; 445 } 446 447 448 /* static */ int 449 TermView::_PushLine(int cols, const VTermScreenCell* cells, void* user) 450 { 451 TermView* view = (TermView*)user; 452 view->_PushLine(cols, cells); 453 454 return 0; 455 } 456 457 458 const 459 VTermScreenCallbacks TermView::sScreenCallbacks = { 460 &TermView::_Damage, 461 /*.moverect =*/ NULL, 462 &TermView::_MoveCursor, 463 /*.settermprop =*/ NULL, 464 /*.setmousefunc =*/ NULL, 465 /*.bell =*/ NULL, 466 /*.resize =*/ NULL, 467 &TermView::_PushLine, 468 /*.sb_popline =*/ NULL, 469 }; 470