Changes in uspace/app/terminal/terminal.c [77ffa01:e273e9e] in mainline
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
uspace/app/terminal/terminal.c
r77ffa01 re273e9e 1 1 /* 2 * Copyright (c) 202 1Jiri Svoboda2 * Copyright (c) 2024 Jiri Svoboda 3 3 * Copyright (c) 2012 Petr Koupy 4 4 * All rights reserved. … … 40 40 #include <errno.h> 41 41 #include <fbfont/font-8x16.h> 42 #include < io/chargrid.h>42 #include <fibril.h> 43 43 #include <gfx/bitmap.h> 44 44 #include <gfx/context.h> … … 48 48 #include <io/console.h> 49 49 #include <io/pixelmap.h> 50 #include < task.h>50 #include <macros.h> 51 51 #include <stdarg.h> 52 52 #include <stdio.h> 53 53 #include <stdlib.h> 54 #include <str_error.h> 54 55 #include <str.h> 56 #include <task.h> 55 57 #include <ui/resource.h> 56 58 #include <ui/ui.h> … … 68 70 69 71 #define TERM_CAPS \ 70 (CONSOLE_CAP_STYLE | CONSOLE_CAP_INDEXED | CONSOLE_CAP_RGB) 72 (CONSOLE_CAP_CURSORCTL | CONSOLE_CAP_STYLE | CONSOLE_CAP_INDEXED | \ 73 CONSOLE_CAP_RGB) 74 75 #define SCROLLBACK_MAX_LINES 1000 76 #define MIN_WINDOW_COLS 8 77 #define MIN_WINDOW_ROWS 4 71 78 72 79 static LIST_INITIALIZE(terms); 80 81 #define COLOR_BRIGHT 8 82 83 static const pixel_t _basic_colors[16] = { 84 [COLOR_BLACK] = PIXEL(255, 0, 0, 0), 85 [COLOR_RED] = PIXEL(255, 170, 0, 0), 86 [COLOR_GREEN] = PIXEL(255, 0, 170, 0), 87 [COLOR_YELLOW] = PIXEL(255, 170, 85, 0), 88 [COLOR_BLUE] = PIXEL(255, 0, 0, 170), 89 [COLOR_MAGENTA] = PIXEL(255, 170, 0, 170), 90 [COLOR_CYAN] = PIXEL(255, 0, 170, 170), 91 [COLOR_WHITE] = PIXEL(255, 170, 170, 170), 92 93 [COLOR_BLACK | COLOR_BRIGHT] = PIXEL(255, 85, 85, 85), 94 [COLOR_RED | COLOR_BRIGHT] = PIXEL(255, 255, 85, 85), 95 [COLOR_GREEN | COLOR_BRIGHT] = PIXEL(255, 85, 255, 85), 96 [COLOR_YELLOW | COLOR_BRIGHT] = PIXEL(255, 255, 255, 85), 97 [COLOR_BLUE | COLOR_BRIGHT] = PIXEL(255, 85, 85, 255), 98 [COLOR_MAGENTA | COLOR_BRIGHT] = PIXEL(255, 255, 85, 255), 99 [COLOR_CYAN | COLOR_BRIGHT] = PIXEL(255, 85, 255, 255), 100 [COLOR_WHITE | COLOR_BRIGHT] = PIXEL(255, 255, 255, 255), 101 }; 73 102 74 103 static errno_t term_open(con_srvs_t *, con_srv_t *); … … 87 116 static void term_set_rgb_color(con_srv_t *, pixel_t, pixel_t); 88 117 static void term_set_cursor_visibility(con_srv_t *, bool); 118 static errno_t term_set_caption(con_srv_t *, const char *); 89 119 static errno_t term_get_event(con_srv_t *, cons_event_t *); 90 120 static errno_t term_map(con_srv_t *, sysarg_t, sysarg_t, charfield_t **); … … 108 138 .set_rgb_color = term_set_rgb_color, 109 139 .set_cursor_visibility = term_set_cursor_visibility, 140 .set_caption = term_set_caption, 110 141 .get_event = term_get_event, 111 142 .map = term_map, … … 115 146 116 147 static void terminal_close_event(ui_window_t *, void *); 117 static void terminal_focus_event(ui_window_t *, void *); 148 static void terminal_focus_event(ui_window_t *, void *, unsigned); 149 static void terminal_resize_event(ui_window_t *, void *); 118 150 static void terminal_kbd_event(ui_window_t *, void *, kbd_event_t *); 119 151 static void terminal_pos_event(ui_window_t *, void *, pos_event_t *); 120 static void terminal_unfocus_event(ui_window_t *, void *); 152 static void terminal_unfocus_event(ui_window_t *, void *, unsigned); 153 static void terminal_maximize_event(ui_window_t *, void *); 154 static void terminal_unmaximize_event(ui_window_t *, void *); 121 155 122 156 static ui_window_cb_t terminal_window_cb = { 123 157 .close = terminal_close_event, 124 158 .focus = terminal_focus_event, 159 .resize = terminal_resize_event, 125 160 .kbd = terminal_kbd_event, 126 161 .pos = terminal_pos_event, 127 .unfocus = terminal_unfocus_event 162 .unfocus = terminal_unfocus_event, 163 .maximize = terminal_maximize_event, 164 .unmaximize = terminal_unmaximize_event, 128 165 }; 129 166 167 static errno_t terminal_wait_fibril(void *); 168 130 169 static terminal_t *srv_to_terminal(con_srv_t *srv) 131 170 { … … 133 172 } 134 173 135 static void getterm(const char *svc, const char *app)136 { 137 task_spawnl(NULL, NULL, APP_GETTERM, APP_GETTERM, svc,174 static errno_t getterm(task_wait_t *wait, const char *svc, const char *app) 175 { 176 return task_spawnl(NULL, wait, APP_GETTERM, APP_GETTERM, svc, 138 177 LOCFS_MOUNT_POINT, "--msg", "--wait", "--", app, NULL); 139 178 } 140 179 141 static pixel_t color_table[16] = { 142 [COLOR_BLACK] = PIXEL(255, 0, 0, 0), 143 [COLOR_BLUE] = PIXEL(255, 0, 0, 240), 144 [COLOR_GREEN] = PIXEL(255, 0, 240, 0), 145 [COLOR_CYAN] = PIXEL(255, 0, 240, 240), 146 [COLOR_RED] = PIXEL(255, 240, 0, 0), 147 [COLOR_MAGENTA] = PIXEL(255, 240, 0, 240), 148 [COLOR_YELLOW] = PIXEL(255, 240, 240, 0), 149 [COLOR_WHITE] = PIXEL(255, 240, 240, 240), 150 151 [COLOR_BLACK + 8] = PIXEL(255, 0, 0, 0), 152 [COLOR_BLUE + 8] = PIXEL(255, 0, 0, 255), 153 [COLOR_GREEN + 8] = PIXEL(255, 0, 255, 0), 154 [COLOR_CYAN + 8] = PIXEL(255, 0, 255, 255), 155 [COLOR_RED + 8] = PIXEL(255, 255, 0, 0), 156 [COLOR_MAGENTA + 8] = PIXEL(255, 255, 0, 255), 157 [COLOR_YELLOW + 8] = PIXEL(255, 255, 255, 0), 158 [COLOR_WHITE + 8] = PIXEL(255, 255, 255, 255), 159 }; 160 161 static inline void attrs_rgb(char_attrs_t attrs, pixel_t *bgcolor, pixel_t *fgcolor) 162 { 163 switch (attrs.type) { 180 static pixel_t termui_color_to_pixel(termui_color_t c) 181 { 182 uint8_t r, g, b; 183 termui_color_to_rgb(c, &r, &g, &b); 184 return PIXEL(255, r, g, b); 185 } 186 187 static termui_color_t termui_color_from_pixel(pixel_t pixel) 188 { 189 return termui_color_from_rgb(RED(pixel), GREEN(pixel), BLUE(pixel)); 190 } 191 192 static termui_cell_t charfield_to_termui_cell(terminal_t *term, const charfield_t *cf) 193 { 194 termui_cell_t cell = { }; 195 196 cell.glyph_idx = fb_font_glyph(cf->ch, NULL); 197 198 switch (cf->attrs.type) { 164 199 case CHAR_ATTR_STYLE: 165 switch ( attrs.val.style) {200 switch (cf->attrs.val.style) { 166 201 case STYLE_NORMAL: 167 *bgcolor = color_table[COLOR_WHITE];168 *fgcolor = color_table[COLOR_BLACK];202 cell.bgcolor = term->default_bgcolor; 203 cell.fgcolor = term->default_fgcolor; 169 204 break; 170 205 case STYLE_EMPHASIS: 171 *bgcolor = color_table[COLOR_WHITE];172 *fgcolor = color_table[COLOR_RED];206 cell.bgcolor = term->emphasis_bgcolor; 207 cell.fgcolor = term->emphasis_fgcolor; 173 208 break; 174 209 case STYLE_INVERTED: 175 *bgcolor = color_table[COLOR_BLACK]; 176 *fgcolor = color_table[COLOR_WHITE]; 210 cell.bgcolor = term->default_bgcolor; 211 cell.fgcolor = term->default_fgcolor; 212 cell.inverted = 1; 177 213 break; 178 214 case STYLE_SELECTED: 179 *bgcolor = color_table[COLOR_RED];180 *fgcolor = color_table[COLOR_WHITE];215 cell.bgcolor = term->selection_bgcolor; 216 cell.fgcolor = term->selection_fgcolor; 181 217 break; 182 218 } 183 219 break; 220 184 221 case CHAR_ATTR_INDEX: 185 *bgcolor = color_table[(attrs.val.index.bgcolor & 7) | 186 ((attrs.val.index.attr & CATTR_BRIGHT) ? 8 : 0)]; 187 *fgcolor = color_table[(attrs.val.index.fgcolor & 7) | 188 ((attrs.val.index.attr & CATTR_BRIGHT) ? 8 : 0)]; 222 char_attr_index_t index = cf->attrs.val.index; 223 224 int bright = (index.attr & CATTR_BRIGHT) ? COLOR_BRIGHT : 0; 225 pixel_t bgcolor = _basic_colors[index.bgcolor]; 226 pixel_t fgcolor = _basic_colors[index.fgcolor | bright]; 227 cell.bgcolor = termui_color_from_pixel(bgcolor); 228 cell.fgcolor = termui_color_from_pixel(fgcolor); 229 230 if (index.attr & CATTR_BLINK) 231 cell.blink = 1; 232 189 233 break; 234 190 235 case CHAR_ATTR_RGB: 191 *bgcolor = 0xff000000 | attrs.val.rgb.bgcolor;192 *fgcolor = 0xff000000 | attrs.val.rgb.fgcolor;236 cell.bgcolor = termui_color_from_pixel(cf->attrs.val.rgb.bgcolor); 237 cell.fgcolor = termui_color_from_pixel(cf->attrs.val.rgb.fgcolor); 193 238 break; 194 239 } 240 241 return cell; 195 242 } 196 243 … … 210 257 } 211 258 212 static void term_update_char(terminal_t *term, pixelmap_t *pixelmap, 213 sysarg_t sx, sysarg_t sy, sysarg_t col, sysarg_t row) 214 { 215 charfield_t *field = 216 chargrid_charfield_at(term->backbuf, col, row); 217 218 bool inverted = chargrid_cursor_at(term->backbuf, col, row); 219 220 sysarg_t bx = sx + (col * FONT_WIDTH); 221 sysarg_t by = sy + (row * FONT_SCANLINES); 222 223 pixel_t bgcolor = 0; 224 pixel_t fgcolor = 0; 225 226 if (inverted) 227 attrs_rgb(field->attrs, &fgcolor, &bgcolor); 228 else 229 attrs_rgb(field->attrs, &bgcolor, &fgcolor); 230 231 // FIXME: Glyph type should be actually uint32_t 232 // for full UTF-32 coverage. 233 234 uint16_t glyph = fb_font_glyph(field->ch, NULL); 259 static void term_draw_cell(terminal_t *term, pixelmap_t *pixelmap, int col, int row, const termui_cell_t *cell) 260 { 261 termui_color_t bg = cell->bgcolor; 262 if (bg == TERMUI_COLOR_DEFAULT) 263 bg = term->default_bgcolor; 264 265 termui_color_t fg = cell->fgcolor; 266 if (fg == TERMUI_COLOR_DEFAULT) 267 fg = term->default_fgcolor; 268 269 pixel_t bgcolor = termui_color_to_pixel(bg); 270 pixel_t fgcolor = termui_color_to_pixel(fg); 271 272 int bx = col * FONT_WIDTH; 273 int by = row * FONT_SCANLINES; 274 275 // TODO: support bold/italic/underline/strike/blink styling 276 277 if (cell->inverted ^ cell->cursor) { 278 pixel_t tmp = bgcolor; 279 bgcolor = fgcolor; 280 fgcolor = tmp; 281 } 282 283 uint32_t glyph = cell->glyph_idx; 284 assert(glyph < FONT_GLYPHS); 285 286 if (glyph == 0) 287 glyph = fb_font_glyph(U' ', NULL); 235 288 236 289 for (unsigned int y = 0; y < FONT_SCANLINES; y++) { … … 244 297 } 245 298 } 299 246 300 term_update_region(term, bx, by, FONT_WIDTH, FONT_SCANLINES); 247 301 } 248 302 249 static bool term_update_scroll(terminal_t *term, pixelmap_t *pixelmap, 250 sysarg_t sx, sysarg_t sy) 251 { 252 sysarg_t top_row = chargrid_get_top_row(term->frontbuf); 253 254 if (term->top_row == top_row) { 255 return false; 256 } 257 258 term->top_row = top_row; 259 260 for (sysarg_t row = 0; row < term->rows; row++) { 261 for (sysarg_t col = 0; col < term->cols; col++) { 262 charfield_t *front_field = 263 chargrid_charfield_at(term->frontbuf, col, row); 264 charfield_t *back_field = 265 chargrid_charfield_at(term->backbuf, col, row); 266 bool update = false; 267 268 if (front_field->ch != back_field->ch) { 269 back_field->ch = front_field->ch; 270 update = true; 271 } 272 273 if (!attrs_same(front_field->attrs, back_field->attrs)) { 274 back_field->attrs = front_field->attrs; 275 update = true; 276 } 277 278 front_field->flags &= ~CHAR_FLAG_DIRTY; 279 280 if (update) { 281 term_update_char(term, pixelmap, sx, sy, col, row); 282 } 283 } 284 } 285 286 return true; 287 } 288 289 static bool term_update_cursor(terminal_t *term, pixelmap_t *pixelmap, 290 sysarg_t sx, sysarg_t sy) 291 { 292 bool update = false; 293 294 sysarg_t front_col; 295 sysarg_t front_row; 296 chargrid_get_cursor(term->frontbuf, &front_col, &front_row); 297 298 sysarg_t back_col; 299 sysarg_t back_row; 300 chargrid_get_cursor(term->backbuf, &back_col, &back_row); 301 302 bool front_visibility = 303 chargrid_get_cursor_visibility(term->frontbuf) && 304 term->is_focused; 305 bool back_visibility = 306 chargrid_get_cursor_visibility(term->backbuf); 307 308 if (front_visibility != back_visibility) { 309 chargrid_set_cursor_visibility(term->backbuf, 310 front_visibility); 311 term_update_char(term, pixelmap, sx, sy, back_col, back_row); 312 update = true; 313 } 314 315 if ((front_col != back_col) || (front_row != back_row)) { 316 chargrid_set_cursor(term->backbuf, front_col, front_row); 317 term_update_char(term, pixelmap, sx, sy, back_col, back_row); 318 term_update_char(term, pixelmap, sx, sy, front_col, front_row); 319 update = true; 320 } 321 322 return update; 323 } 324 325 static void term_update(terminal_t *term) 326 { 327 pixelmap_t pixelmap; 303 static void term_render(terminal_t *term) 304 { 305 (void) gfx_bitmap_render(term->bmp, &term->update, &term->off); 306 307 term->update.p0.x = 0; 308 term->update.p0.y = 0; 309 term->update.p1.x = 0; 310 term->update.p1.y = 0; 311 } 312 313 static void termui_refresh_cb(void *userdata) 314 { 315 terminal_t *term = userdata; 316 317 termui_force_viewport_update(term->termui, 0, termui_get_rows(term->termui)); 318 } 319 320 static void termui_scroll_cb(void *userdata, int delta) 321 { 322 (void) delta; 323 324 // Until we have support for hardware accelerated scrolling, just redraw everything. 325 termui_refresh_cb(userdata); 326 } 327 328 static pixelmap_t term_get_pixelmap(terminal_t *term) 329 { 330 pixelmap_t pixelmap = { }; 328 331 gfx_bitmap_alloc_t alloc; 329 gfx_coord2_t pos; 330 errno_t rc; 331 332 rc = gfx_bitmap_get_alloc(term->bmp, &alloc); 333 if (rc != EOK) { 334 return; 335 } 336 337 fibril_mutex_lock(&term->mtx); 332 333 errno_t rc = gfx_bitmap_get_alloc(term->bmp, &alloc); 334 if (rc != EOK) 335 return pixelmap; 336 338 337 pixelmap.width = term->w; 339 338 pixelmap.height = term->h; 340 339 pixelmap.data = alloc.pixels; 341 342 bool update = false; 343 sysarg_t sx = 0; 344 sysarg_t sy = 0; 345 346 if (term_update_scroll(term, &pixelmap, sx, sy)) { 347 update = true; 348 } else { 349 for (sysarg_t y = 0; y < term->rows; y++) { 350 for (sysarg_t x = 0; x < term->cols; x++) { 351 charfield_t *front_field = 352 chargrid_charfield_at(term->frontbuf, x, y); 353 charfield_t *back_field = 354 chargrid_charfield_at(term->backbuf, x, y); 355 bool cupdate = false; 356 357 if ((front_field->flags & CHAR_FLAG_DIRTY) == 358 CHAR_FLAG_DIRTY) { 359 if (front_field->ch != back_field->ch) { 360 back_field->ch = front_field->ch; 361 cupdate = true; 362 } 363 364 if (!attrs_same(front_field->attrs, 365 back_field->attrs)) { 366 back_field->attrs = front_field->attrs; 367 cupdate = true; 368 } 369 370 front_field->flags &= ~CHAR_FLAG_DIRTY; 371 } 372 373 if (cupdate) { 374 term_update_char(term, &pixelmap, sx, sy, x, y); 375 update = true; 376 } 377 } 378 } 379 } 380 381 if (term_update_cursor(term, &pixelmap, sx, sy)) 382 update = true; 383 384 if (update) { 385 pos.x = 4; 386 pos.y = 26; 387 (void) gfx_bitmap_render(term->bmp, &term->update, &pos); 388 389 term->update.p0.x = 0; 390 term->update.p0.y = 0; 391 term->update.p1.x = 0; 392 term->update.p1.y = 0; 393 } 394 395 fibril_mutex_unlock(&term->mtx); 396 } 397 398 static void term_repaint(terminal_t *term) 399 { 400 pixelmap_t pixelmap; 401 gfx_bitmap_alloc_t alloc; 402 errno_t rc; 403 404 rc = gfx_bitmap_get_alloc(term->bmp, &alloc); 405 if (rc != EOK) { 406 printf("Error getting bitmap allocation info.\n"); 340 return pixelmap; 341 } 342 343 static void term_clear_bitmap(terminal_t *term, pixel_t color) 344 { 345 pixelmap_t pixelmap = term_get_pixelmap(term); 346 if (pixelmap.data == NULL) 407 347 return; 408 } 409 410 fibril_mutex_lock(&term->mtx); 411 412 pixelmap.width = term->w; 413 pixelmap.height = term->h; 414 pixelmap.data = alloc.pixels; 415 416 sysarg_t sx = 0; 417 sysarg_t sy = 0; 418 419 if (!term_update_scroll(term, &pixelmap, sx, sy)) { 420 for (sysarg_t y = 0; y < term->rows; y++) { 421 for (sysarg_t x = 0; x < term->cols; x++) { 422 charfield_t *front_field = 423 chargrid_charfield_at(term->frontbuf, x, y); 424 charfield_t *back_field = 425 chargrid_charfield_at(term->backbuf, x, y); 426 427 back_field->ch = front_field->ch; 428 back_field->attrs = front_field->attrs; 429 front_field->flags &= ~CHAR_FLAG_DIRTY; 430 431 term_update_char(term, &pixelmap, sx, sy, x, y); 432 } 433 } 434 } 435 436 term_update_cursor(term, &pixelmap, sx, sy); 437 438 fibril_mutex_unlock(&term->mtx); 348 349 sysarg_t pixels = pixelmap.height * pixelmap.width; 350 for (sysarg_t i = 0; i < pixels; i++) 351 pixelmap.data[i] = color; 352 353 term_update_region(term, 0, 0, pixelmap.width, pixelmap.height); 354 } 355 356 static void termui_update_cb(void *userdata, int col, int row, const termui_cell_t *cell, int len) 357 { 358 terminal_t *term = userdata; 359 360 pixelmap_t pixelmap = term_get_pixelmap(term); 361 if (pixelmap.data == NULL) 362 return; 363 364 for (int i = 0; i < len; i++) 365 term_draw_cell(term, &pixelmap, col + i, row, &cell[i]); 439 366 } 440 367 … … 476 403 if (pos < size) { 477 404 link_t *link = prodcons_consume(&term->input_pc); 478 cons_event_t *event = list_get_instance(link, cons_event_t, link); 405 terminal_event_t *qevent = list_get_instance(link, 406 terminal_event_t, link); 407 cons_event_t *event = &qevent->ev; 479 408 480 409 /* Accept key presses of printable chars only. */ … … 490 419 } 491 420 492 free( event);421 free(qevent); 493 422 } 494 423 } … … 500 429 static void term_write_char(terminal_t *term, wchar_t ch) 501 430 { 502 sysarg_t updated = 0;503 504 fibril_mutex_lock(&term->mtx);505 506 431 switch (ch) { 507 case '\n':508 updated = chargrid_newline(term->frontbuf);432 case L'\n': 433 termui_put_crlf(term->termui); 509 434 break; 510 case '\r': 435 case L'\r': 436 termui_put_cr(term->termui); 511 437 break; 512 case '\t':513 updated = chargrid_tabstop(term->frontbuf, 8);438 case L'\t': 439 termui_put_tab(term->termui); 514 440 break; 515 case '\b':516 updated = chargrid_backspace(term->frontbuf);441 case L'\b': 442 termui_put_backspace(term->termui); 517 443 break; 518 444 default: 519 updated = chargrid_putuchar(term->frontbuf, ch, true); 520 } 521 522 fibril_mutex_unlock(&term->mtx); 523 524 if (updated > 1) 525 term_update(term); 445 // TODO: For some languages, we might need support for combining 446 // characters. Currently, we assume every unicode code point is 447 // an individual printed character, which is not always the case. 448 termui_put_glyph(term->termui, fb_font_glyph(ch, NULL), 1); 449 break; 450 } 526 451 } 527 452 … … 529 454 { 530 455 terminal_t *term = srv_to_terminal(srv); 456 457 fibril_mutex_lock(&term->mtx); 531 458 532 459 size_t off = 0; … … 534 461 term_write_char(term, str_decode(data, &off, size)); 535 462 463 fibril_mutex_unlock(&term->mtx); 464 465 term_render(term); 536 466 gfx_update(term->gc); 537 467 *nwritten = size; 468 538 469 return EOK; 539 470 } … … 543 474 terminal_t *term = srv_to_terminal(srv); 544 475 545 term_ update(term);476 term_render(term); 546 477 gfx_update(term->gc); 547 478 } … … 552 483 553 484 fibril_mutex_lock(&term->mtx); 554 chargrid_clear(term->frontbuf);555 fibril_mutex_unlock(&term->mtx); 556 557 term_ update(term);485 termui_clear_screen(term->termui); 486 fibril_mutex_unlock(&term->mtx); 487 488 term_render(term); 558 489 gfx_update(term->gc); 559 490 } … … 564 495 565 496 fibril_mutex_lock(&term->mtx); 566 chargrid_set_cursor(term->frontbuf, col, row);567 fibril_mutex_unlock(&term->mtx); 568 569 term_ update(term);497 termui_set_pos(term->termui, col, row); 498 fibril_mutex_unlock(&term->mtx); 499 500 term_render(term); 570 501 gfx_update(term->gc); 571 502 } … … 576 507 577 508 fibril_mutex_lock(&term->mtx); 578 chargrid_get_cursor(term->frontbuf, col, row); 579 fibril_mutex_unlock(&term->mtx); 509 int irow, icol; 510 termui_get_pos(term->termui, &icol, &irow); 511 fibril_mutex_unlock(&term->mtx); 512 513 *col = icol; 514 *row = irow; 580 515 581 516 return EOK; … … 587 522 588 523 fibril_mutex_lock(&term->mtx); 589 *cols = term ->cols;590 *rows = term ->rows;524 *cols = termui_get_cols(term->termui); 525 *rows = termui_get_rows(term->termui); 591 526 fibril_mutex_unlock(&term->mtx); 592 527 … … 606 541 terminal_t *term = srv_to_terminal(srv); 607 542 608 fibril_mutex_lock(&term->mtx); 609 chargrid_set_style(term->frontbuf, style); 543 termui_cell_t cellstyle = { }; 544 545 switch (style) { 546 case STYLE_NORMAL: 547 cellstyle.bgcolor = term->default_bgcolor; 548 cellstyle.fgcolor = term->default_fgcolor; 549 break; 550 case STYLE_EMPHASIS: 551 cellstyle.bgcolor = term->emphasis_bgcolor; 552 cellstyle.fgcolor = term->emphasis_fgcolor; 553 break; 554 case STYLE_INVERTED: 555 cellstyle.bgcolor = term->default_bgcolor; 556 cellstyle.fgcolor = term->default_fgcolor; 557 cellstyle.inverted = 1; 558 break; 559 case STYLE_SELECTED: 560 cellstyle.bgcolor = term->selection_bgcolor; 561 cellstyle.fgcolor = term->selection_fgcolor; 562 break; 563 } 564 565 fibril_mutex_lock(&term->mtx); 566 termui_set_style(term->termui, cellstyle); 610 567 fibril_mutex_unlock(&term->mtx); 611 568 } … … 616 573 terminal_t *term = srv_to_terminal(srv); 617 574 618 fibril_mutex_lock(&term->mtx); 619 chargrid_set_color(term->frontbuf, bgcolor, fgcolor, attr); 575 int bright = (attr & CATTR_BRIGHT) ? COLOR_BRIGHT : 0; 576 577 termui_cell_t cellstyle = { }; 578 cellstyle.bgcolor = termui_color_from_pixel(_basic_colors[bgcolor]); 579 cellstyle.fgcolor = termui_color_from_pixel(_basic_colors[fgcolor | bright]); 580 581 if (attr & CATTR_BLINK) 582 cellstyle.blink = 1; 583 584 fibril_mutex_lock(&term->mtx); 585 termui_set_style(term->termui, cellstyle); 620 586 fibril_mutex_unlock(&term->mtx); 621 587 } … … 625 591 { 626 592 terminal_t *term = srv_to_terminal(srv); 627 628 fibril_mutex_lock(&term->mtx); 629 chargrid_set_rgb_color(term->frontbuf, bgcolor, fgcolor); 593 termui_cell_t cellstyle = { 594 .bgcolor = termui_color_from_pixel(bgcolor), 595 .fgcolor = termui_color_from_pixel(fgcolor), 596 }; 597 598 fibril_mutex_lock(&term->mtx); 599 termui_set_style(term->termui, cellstyle); 630 600 fibril_mutex_unlock(&term->mtx); 631 601 } … … 636 606 637 607 fibril_mutex_lock(&term->mtx); 638 chargrid_set_cursor_visibility(term->frontbuf, visible);639 fibril_mutex_unlock(&term->mtx); 640 641 term_ update(term);608 termui_set_cursor_visibility(term->termui, visible); 609 fibril_mutex_unlock(&term->mtx); 610 611 term_render(term); 642 612 gfx_update(term->gc); 643 613 } 644 614 615 static errno_t term_set_caption(con_srv_t *srv, const char *caption) 616 { 617 terminal_t *term = srv_to_terminal(srv); 618 const char *cap; 619 620 fibril_mutex_lock(&term->mtx); 621 622 if (str_size(caption) > 0) 623 cap = caption; 624 else 625 cap = "Terminal"; 626 627 ui_window_set_caption(term->window, cap); 628 fibril_mutex_unlock(&term->mtx); 629 630 term_render(term); 631 gfx_update(term->gc); 632 return EOK; 633 } 634 645 635 static errno_t term_get_event(con_srv_t *srv, cons_event_t *event) 646 636 { 647 637 terminal_t *term = srv_to_terminal(srv); 648 638 link_t *link = prodcons_consume(&term->input_pc); 649 cons_event_t *ev = list_get_instance(link, cons_event_t, link);650 651 *event = *ev;639 terminal_event_t *ev = list_get_instance(link, terminal_event_t, link); 640 641 *event = ev->ev; 652 642 free(ev); 653 643 return EOK; … … 686 676 term->urows = rows; 687 677 term->ubuf = buf; 678 679 /* Scroll back to active screen. */ 680 termui_history_scroll(term->termui, INT_MAX); 681 688 682 fibril_mutex_unlock(&term->mtx); 689 683 … … 706 700 term->ubuf = NULL; 707 701 702 termui_wipe_screen(term->termui, 0); 703 704 fibril_mutex_unlock(&term->mtx); 705 706 /* Update terminal */ 707 term_render(term); 708 gfx_update(term->gc); 709 708 710 if (buf != NULL) 709 711 as_area_destroy(buf); 710 711 fibril_mutex_unlock(&term->mtx);712 712 } 713 713 … … 724 724 { 725 725 terminal_t *term = srv_to_terminal(srv); 726 charfield_t *ch;727 sysarg_t col, row;728 726 729 727 fibril_mutex_lock(&term->mtx); … … 735 733 736 734 /* Make sure we have meaningful coordinates, within bounds */ 737 738 if (c1 > term->ucols)739 c1 = term->ucols;740 if (c1 > term->cols)741 c1 = term->cols; 742 if (c0 >= c1 ) {735 c1 = min(c1, term->ucols); 736 c1 = min(c1, (sysarg_t) termui_get_cols(term->termui)); 737 r1 = min(r1, term->urows); 738 r1 = min(r1, (sysarg_t) termui_get_rows(term->termui)); 739 740 if (c0 >= c1 || r0 >= r1) { 743 741 fibril_mutex_unlock(&term->mtx); 744 742 return; 745 743 } 746 if (r1 > term->urows)747 r1 = term->urows;748 if (r1 > term->rows)749 r1 = term->rows;750 if (r0 >= r1) {751 fibril_mutex_unlock(&term->mtx);752 return;753 }754 744 755 745 /* Update front buffer from user buffer */ 756 746 757 for (row = r0; row < r1; row++) { 758 for (col = c0; col < c1; col++) { 759 ch = chargrid_charfield_at(term->frontbuf, col, row); 760 *ch = term->ubuf[row * term->ucols + col]; 747 for (sysarg_t row = r0; row < r1; row++) { 748 termui_cell_t *cells = termui_get_active_row(term->termui, row); 749 750 for (sysarg_t col = c0; col < c1; col++) { 751 cells[col] = charfield_to_termui_cell(term, &term->ubuf[row * term->ucols + col]); 761 752 } 753 754 termui_update_cb(term, c0, row, &cells[c0], c1 - c0); 762 755 } 763 756 … … 765 758 766 759 /* Update terminal */ 767 term_ update(term);760 term_render(term); 768 761 gfx_update(term->gc); 769 762 } 770 763 771 static void deinit_terminal(terminal_t *term) 764 static errno_t terminal_window_resize(terminal_t *term) 765 { 766 gfx_rect_t rect; 767 ui_window_get_app_rect(term->window, &rect); 768 769 int width = rect.p1.x - rect.p0.x; 770 int height = rect.p1.y - rect.p0.y; 771 772 if (!term->gc) 773 term->gc = ui_window_get_gc(term->window); 774 else 775 assert(term->gc == ui_window_get_gc(term->window)); 776 777 if (!term->ui_res) 778 term->ui_res = ui_window_get_res(term->window); 779 else 780 assert(term->ui_res == ui_window_get_res(term->window)); 781 782 gfx_bitmap_t *new_bmp; 783 gfx_bitmap_params_t params; 784 gfx_bitmap_params_init(¶ms); 785 params.rect.p0.x = 0; 786 params.rect.p0.y = 0; 787 params.rect.p1.x = width; 788 params.rect.p1.y = height; 789 790 errno_t rc = gfx_bitmap_create(term->gc, ¶ms, NULL, &new_bmp); 791 if (rc != EOK) { 792 fprintf(stderr, "Error allocating new screen bitmap: %s\n", str_error(rc)); 793 return rc; 794 } 795 796 if (term->bmp) { 797 rc = gfx_bitmap_destroy(term->bmp); 798 if (rc != EOK) 799 fprintf(stderr, "Error deallocating old screen bitmap: %s\n", str_error(rc)); 800 } 801 802 term->bmp = new_bmp; 803 term->w = width; 804 term->h = height; 805 806 term_clear_bitmap(term, termui_color_to_pixel(term->default_bgcolor)); 807 808 return EOK; 809 } 810 811 void terminal_destroy(terminal_t *term) 772 812 { 773 813 list_remove(&term->link); 774 814 775 if (term->frontbuf) 776 chargrid_destroy(term->frontbuf); 777 778 if (term->backbuf) 779 chargrid_destroy(term->backbuf); 780 } 781 782 void terminal_destroy(terminal_t *term) 783 { 784 deinit_terminal(term); 815 termui_destroy(term->termui); 816 817 if (term->ubuf) 818 as_area_destroy(term->ubuf); 819 820 ui_destroy(term->ui); 785 821 free(term); 786 822 } … … 789 825 { 790 826 /* Got key press/release event */ 791 cons_event_t *event =792 ( cons_event_t *) malloc(sizeof(cons_event_t));827 terminal_event_t *event = 828 (terminal_event_t *) malloc(sizeof(terminal_event_t)); 793 829 if (event == NULL) 794 830 return; 795 831 796 *event= *ev;832 event->ev = *ev; 797 833 link_initialize(&event->link); 798 834 … … 805 841 terminal_t *term = (terminal_t *) arg; 806 842 807 (void) term; 808 809 // XXX This is not really a clean way of terminating 810 exit(0); 843 ui_quit(term->ui); 811 844 } 812 845 813 846 /** Handle window focus event. */ 814 static void terminal_focus_event(ui_window_t *window, void *arg) 847 static void terminal_focus_event(ui_window_t *window, void *arg, 848 unsigned nfocus) 815 849 { 816 850 terminal_t *term = (terminal_t *) arg; 817 851 852 (void)nfocus; 818 853 term->is_focused = true; 819 term_ update(term);854 term_render(term); 820 855 gfx_update(term->gc); 856 } 857 858 static void terminal_resize_handler(ui_window_t *window, void *arg) 859 { 860 terminal_t *term = (terminal_t *) arg; 861 862 fibril_mutex_lock(&term->mtx); 863 864 errno_t rc = terminal_window_resize(term); 865 if (rc == EOK) { 866 (void) termui_resize(term->termui, term->w / FONT_WIDTH, term->h / FONT_SCANLINES, SCROLLBACK_MAX_LINES); 867 termui_refresh_cb(term); 868 term_render(term); 869 gfx_update(term->gc); 870 871 cons_event_t event = { .type = CEV_RESIZE }; 872 terminal_queue_cons_event(term, &event); 873 } 874 875 fibril_mutex_unlock(&term->mtx); 876 } 877 878 static void terminal_resize_event(ui_window_t *window, void *arg) 879 { 880 ui_window_def_resize(window); 881 terminal_resize_handler(window, arg); 882 } 883 884 static void terminal_maximize_event(ui_window_t *window, void *arg) 885 { 886 ui_window_def_maximize(window); 887 terminal_resize_handler(window, arg); 888 } 889 890 static void terminal_unmaximize_event(ui_window_t *window, void *arg) 891 { 892 ui_window_def_unmaximize(window); 893 terminal_resize_handler(window, arg); 821 894 } 822 895 … … 831 904 event.ev.key = *kbd_event; 832 905 833 terminal_queue_cons_event(term, &event); 906 const int PAGE_ROWS = (termui_get_rows(term->termui) * 2) / 3; 907 908 fibril_mutex_lock(&term->mtx); 909 910 if (!term->ubuf && kbd_event->type == KEY_PRESS && 911 (kbd_event->key == KC_PAGE_UP || kbd_event->key == KC_PAGE_DOWN)) { 912 913 termui_history_scroll(term->termui, 914 (kbd_event->key == KC_PAGE_UP) ? -PAGE_ROWS : PAGE_ROWS); 915 916 term_render(term); 917 gfx_update(term->gc); 918 } else { 919 terminal_queue_cons_event(term, &event); 920 } 921 922 fibril_mutex_unlock(&term->mtx); 834 923 } 835 924 … … 840 929 terminal_t *term = (terminal_t *) arg; 841 930 842 sysarg_t sx = -term->off.x; 843 sysarg_t sy = -term->off.y; 844 845 if (event->type == POS_PRESS || event->type == POS_RELEASE) { 846 cevent.type = CEV_POS; 847 cevent.ev.pos.type = event->type; 848 cevent.ev.pos.pos_id = event->pos_id; 849 cevent.ev.pos.btn_num = event->btn_num; 850 851 cevent.ev.pos.hpos = (event->hpos - sx) / FONT_WIDTH; 852 cevent.ev.pos.vpos = (event->vpos - sy) / FONT_SCANLINES; 931 switch (event->type) { 932 case POS_UPDATE: 933 return; 934 935 case POS_PRESS: 936 case POS_RELEASE: 937 case POS_DCLICK: 938 } 939 940 /* Ignore mouse events when we're in scrollback mode. */ 941 if (termui_scrollback_is_active(term->termui)) 942 return; 943 944 sysarg_t sx = term->off.x; 945 sysarg_t sy = term->off.y; 946 947 if (event->hpos < sx || event->vpos < sy) 948 return; 949 950 cevent.type = CEV_POS; 951 cevent.ev.pos.type = event->type; 952 cevent.ev.pos.pos_id = event->pos_id; 953 cevent.ev.pos.btn_num = event->btn_num; 954 955 cevent.ev.pos.hpos = (event->hpos - sx) / FONT_WIDTH; 956 cevent.ev.pos.vpos = (event->vpos - sy) / FONT_SCANLINES; 957 958 /* Filter out events outside the terminal area. */ 959 int cols = termui_get_cols(term->termui); 960 int rows = termui_get_rows(term->termui); 961 962 if (cevent.ev.pos.hpos < (sysarg_t) cols && cevent.ev.pos.vpos < (sysarg_t) rows) 853 963 terminal_queue_cons_event(term, &cevent); 854 }855 964 } 856 965 857 966 /** Handle window unfocus event. */ 858 static void terminal_unfocus_event(ui_window_t *window, void *arg) 967 static void terminal_unfocus_event(ui_window_t *window, void *arg, 968 unsigned nfocus) 859 969 { 860 970 terminal_t *term = (terminal_t *) arg; 861 971 862 term->is_focused = false; 863 term_update(term); 864 gfx_update(term->gc); 972 if (nfocus == 0) { 973 term->is_focused = false; 974 term_render(term); 975 gfx_update(term->gc); 976 } 865 977 } 866 978 … … 882 994 883 995 if (!atomic_flag_test_and_set(&term->refcnt)) 884 chargrid_set_cursor_visibility(term->frontbuf, true);996 termui_set_cursor_visibility(term->termui, true); 885 997 886 998 con_conn(icall, &term->srvs); 887 999 } 888 1000 1001 static errno_t term_init_window(terminal_t *term, const char *display_spec, 1002 gfx_coord_t width, gfx_coord_t height, 1003 gfx_coord_t min_width, gfx_coord_t min_height, 1004 terminal_flags_t flags) 1005 { 1006 gfx_rect_t min_rect = { { 0, 0 }, { min_width, min_height } }; 1007 gfx_rect_t wmin_rect; 1008 gfx_rect_t wrect; 1009 1010 errno_t rc = ui_create(display_spec, &term->ui); 1011 if (rc != EOK) { 1012 printf("Error creating UI on %s.\n", display_spec); 1013 return rc; 1014 } 1015 1016 ui_wnd_params_t wparams; 1017 ui_wnd_params_init(&wparams); 1018 wparams.caption = "Terminal"; 1019 wparams.style |= ui_wds_maximize_btn | ui_wds_resizable; 1020 1021 if ((flags & tf_topleft) != 0) 1022 wparams.placement = ui_wnd_place_top_left; 1023 1024 if (ui_is_fullscreen(term->ui)) { 1025 wparams.placement = ui_wnd_place_full_screen; 1026 wparams.style &= ~ui_wds_decorated; 1027 } 1028 1029 /* Compute wrect such that application area corresponds to rect. */ 1030 ui_wdecor_rect_from_app(term->ui, wparams.style, &min_rect, &wrect); 1031 gfx_rect_rtranslate(&wrect.p0, &wrect, &wmin_rect); 1032 wparams.min_size = wmin_rect.p1; 1033 1034 gfx_rect_t rect = { { 0, 0 }, { width, height } }; 1035 ui_wdecor_rect_from_app(term->ui, wparams.style, &rect, &rect); 1036 term->off.x = -rect.p0.x; 1037 term->off.y = -rect.p0.y; 1038 printf("off=%d,%d\n", term->off.x, term->off.y); 1039 gfx_rect_translate(&term->off, &rect, &wparams.rect); 1040 printf("wparams.rect=%d,%d,%d,%d\n", 1041 wparams.rect.p0.x, 1042 wparams.rect.p1.x, 1043 wparams.rect.p0.y, 1044 wparams.rect.p1.y); 1045 1046 rc = ui_window_create(term->ui, &wparams, &term->window); 1047 if (rc != EOK) 1048 return rc; 1049 1050 ui_window_set_cb(term->window, &terminal_window_cb, (void *) term); 1051 return terminal_window_resize(term); 1052 } 1053 889 1054 errno_t terminal_create(const char *display_spec, sysarg_t width, 890 sysarg_t height, terminal_flags_t flags, terminal_t **rterm) 891 { 892 terminal_t *term; 893 gfx_bitmap_params_t params; 894 ui_wnd_params_t wparams; 895 gfx_rect_t rect; 896 gfx_coord2_t off; 897 gfx_rect_t wrect; 1055 sysarg_t height, terminal_flags_t flags, const char *command, 1056 terminal_t **rterm) 1057 { 898 1058 errno_t rc; 899 1059 900 term = calloc(1, sizeof(terminal_t));1060 terminal_t *term = calloc(1, sizeof(terminal_t)); 901 1061 if (term == NULL) { 902 1062 printf("Out of memory.\n"); … … 911 1071 term->char_remains_len = 0; 912 1072 913 term->w = width; 914 term->h = height; 915 916 term->cols = width / FONT_WIDTH; 917 term->rows = height / FONT_SCANLINES; 918 919 term->frontbuf = NULL; 920 term->backbuf = NULL; 921 922 term->frontbuf = chargrid_create(term->cols, term->rows, 923 CHARGRID_FLAG_NONE); 924 if (!term->frontbuf) { 925 printf("Error creating front buffer.\n"); 1073 term->default_bgcolor = termui_color_from_pixel(_basic_colors[COLOR_WHITE | COLOR_BRIGHT]); 1074 term->default_fgcolor = termui_color_from_pixel(_basic_colors[COLOR_BLACK]); 1075 1076 term->emphasis_bgcolor = termui_color_from_pixel(_basic_colors[COLOR_WHITE | COLOR_BRIGHT]); 1077 term->emphasis_fgcolor = termui_color_from_pixel(_basic_colors[COLOR_RED | COLOR_BRIGHT]); 1078 1079 term->selection_bgcolor = termui_color_from_pixel(_basic_colors[COLOR_RED | COLOR_BRIGHT]); 1080 term->selection_fgcolor = termui_color_from_pixel(_basic_colors[COLOR_WHITE | COLOR_BRIGHT]); 1081 1082 rc = term_init_window(term, display_spec, width, height, 1083 MIN_WINDOW_COLS * FONT_WIDTH, MIN_WINDOW_ROWS * FONT_SCANLINES, flags); 1084 if (rc != EOK) { 1085 printf("Error creating window (%s).\n", str_error(rc)); 1086 goto error; 1087 } 1088 1089 term->termui = termui_create(term->w / FONT_WIDTH, 1090 term->h / FONT_SCANLINES, SCROLLBACK_MAX_LINES); 1091 if (!term->termui) { 1092 printf("Error creating terminal UI.\n"); 926 1093 rc = ENOMEM; 927 1094 goto error; 928 1095 } 929 1096 930 term->backbuf = chargrid_create(term->cols, term->rows, 931 CHARGRID_FLAG_NONE); 932 if (!term->backbuf) { 933 printf("Error creating back buffer.\n"); 934 rc = ENOMEM; 935 goto error; 936 } 937 938 rect.p0.x = 0; 939 rect.p0.y = 0; 940 rect.p1.x = width; 941 rect.p1.y = height; 942 943 ui_wnd_params_init(&wparams); 944 wparams.caption = "Terminal"; 945 if ((flags & tf_topleft) != 0) 946 wparams.placement = ui_wnd_place_top_left; 947 948 /* 949 * Compute window rectangle such that application area corresponds 950 * to rect 951 */ 952 ui_wdecor_rect_from_app(wparams.style, &rect, &wrect); 953 off = wrect.p0; 954 gfx_rect_rtranslate(&off, &wrect, &wparams.rect); 955 956 term->off = off; 957 958 rc = ui_create(display_spec, &term->ui); 959 if (rc != EOK) { 960 printf("Error creating UI on %s.\n", display_spec); 961 goto error; 962 } 963 964 rc = ui_window_create(term->ui, &wparams, &term->window); 965 if (rc != EOK) { 966 printf("Error creating window.\n"); 967 goto error; 968 } 969 970 term->gc = ui_window_get_gc(term->window); 971 term->ui_res = ui_window_get_res(term->window); 972 973 ui_window_set_cb(term->window, &terminal_window_cb, (void *) term); 974 975 gfx_bitmap_params_init(¶ms); 976 params.rect.p0.x = 0; 977 params.rect.p0.y = 0; 978 params.rect.p1.x = width; 979 params.rect.p1.y = height; 980 981 rc = gfx_bitmap_create(term->gc, ¶ms, NULL, &term->bmp); 982 if (rc != EOK) { 983 printf("Error allocating screen bitmap.\n"); 984 goto error; 985 } 986 987 chargrid_clear(term->frontbuf); 988 chargrid_clear(term->backbuf); 989 term->top_row = 0; 1097 termui_set_refresh_cb(term->termui, termui_refresh_cb, term); 1098 termui_set_scroll_cb(term->termui, termui_scroll_cb, term); 1099 termui_set_update_cb(term->termui, termui_update_cb, term); 990 1100 991 1101 async_set_fallback_port_handler(term_connection, NULL); … … 994 1104 term->srvs.sarg = term; 995 1105 996 rc = loc_server_register(NAME );1106 rc = loc_server_register(NAME, &term->srv); 997 1107 if (rc != EOK) { 998 1108 printf("Error registering server.\n"); … … 1005 1115 task_get_id()); 1006 1116 1007 rc = loc_service_register( vc, &term->dsid);1117 rc = loc_service_register(term->srv, vc, &term->dsid); 1008 1118 if (rc != EOK) { 1009 1119 printf("Error registering service.\n"); … … 1013 1123 1014 1124 list_append(&term->link, &terms); 1015 getterm(vc, "/app/bdsh"); 1125 rc = getterm(&term->wait, vc, command); 1126 if (rc != EOK) 1127 goto error; 1128 1129 term->wfid = fibril_create(terminal_wait_fibril, term); 1130 if (term->wfid == 0) 1131 goto error; 1132 1133 fibril_add_ready(term->wfid); 1016 1134 1017 1135 term->is_focused = true; 1018 1136 1019 term->update.p0.x = 0; 1020 term->update.p0.y = 0; 1021 term->update.p1.x = 0; 1022 term->update.p1.y = 0; 1023 1024 term_repaint(term); 1137 termui_refresh_cb(term); 1025 1138 1026 1139 *rterm = term; 1027 1140 return EOK; 1028 1141 error: 1142 if (term->dsid != 0) 1143 loc_service_unregister(term->srv, term->dsid); 1144 if (term->srv != NULL) 1145 loc_server_unregister(term->srv); 1029 1146 if (term->window != NULL) 1030 1147 ui_window_destroy(term->window); 1031 1148 if (term->ui != NULL) 1032 1149 ui_destroy(term->ui); 1033 if (term->frontbuf != NULL) 1034 chargrid_destroy(term->frontbuf); 1035 if (term->backbuf != NULL) 1036 chargrid_destroy(term->backbuf); 1150 if (term->termui != NULL) 1151 termui_destroy(term->termui); 1037 1152 free(term); 1038 1153 return rc; 1039 1154 } 1040 1155 1156 static errno_t terminal_wait_fibril(void *arg) 1157 { 1158 terminal_t *term = (terminal_t *)arg; 1159 task_exit_t texit; 1160 int retval; 1161 1162 /* 1163 * XXX There is no way to break the sleep if the task does not 1164 * exit. 1165 */ 1166 (void) task_wait(&term->wait, &texit, &retval); 1167 ui_quit(term->ui); 1168 return EOK; 1169 } 1170 1041 1171 /** @} 1042 1172 */
Note:
See TracChangeset
for help on using the changeset viewer.