Serenity Operating System
at master 486 lines 16 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2020, Sergey Bugaev <bugaevc@serenityos.org> 4 * Copyright (c) 2021, Liav A. <liavalb@hotmail.co.il> 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include <AK/StdLibExtras.h> 10#include <Kernel/Arch/Delay.h> 11#if ARCH(X86_64) 12# include <Kernel/Arch/x86_64/PCSpeaker.h> 13#endif 14#include <Kernel/CommandLine.h> 15#include <Kernel/Devices/DeviceManagement.h> 16#include <Kernel/Devices/HID/HIDManagement.h> 17#include <Kernel/Graphics/GraphicsManagement.h> 18#include <Kernel/Heap/kmalloc.h> 19#include <Kernel/Sections.h> 20#include <Kernel/StdLib.h> 21#include <Kernel/TTY/ConsoleManagement.h> 22#include <Kernel/TTY/VirtualConsole.h> 23#include <LibVT/Color.h> 24 25namespace Kernel { 26 27ConsoleImpl::ConsoleImpl(VirtualConsole& client) 28 : Terminal(client) 29{ 30} 31 32void ConsoleImpl::invalidate_cursor() 33{ 34} 35void ConsoleImpl::clear() 36{ 37 m_client.clear(); 38} 39void ConsoleImpl::clear_history() 40{ 41} 42 43void ConsoleImpl::set_size(u16 determined_columns, u16 determined_rows) 44{ 45 VERIFY(determined_columns); 46 VERIFY(determined_rows); 47 48 if (determined_columns == columns() && determined_rows == rows()) 49 return; 50 51 m_columns = determined_columns; 52 m_rows = determined_rows; 53 54 m_scroll_region_top = 0; 55 m_scroll_region_bottom = determined_rows - 1; 56 57 m_current_state.cursor.clamp(rows() - 1, columns() - 1); 58 m_normal_saved_state.cursor.clamp(rows() - 1, columns() - 1); 59 m_alternate_saved_state.cursor.clamp(rows() - 1, columns() - 1); 60 m_saved_cursor_position.clamp(rows() - 1, columns() - 1); 61 m_horizontal_tabs.resize(determined_columns); 62 for (unsigned i = 0; i < determined_columns; ++i) 63 m_horizontal_tabs[i] = (i % 8) == 0; 64 // Rightmost column is always last tab on line. 65 m_horizontal_tabs[determined_columns - 1] = 1; 66 m_client.terminal_did_resize(m_columns, m_rows); 67} 68void ConsoleImpl::scroll_up(u16 region_top, u16 region_bottom, size_t count) 69{ 70 // NOTE: We have to invalidate the cursor first. 71 m_client.invalidate_cursor(cursor_row()); 72 m_client.scroll_up(region_top, region_bottom, count); 73} 74 75void ConsoleImpl::scroll_down(u16 region_top, u16 region_bottom, size_t count) 76{ 77 m_client.invalidate_cursor(cursor_row()); 78 m_client.scroll_down(region_top, region_bottom, count); 79} 80 81void ConsoleImpl::put_character_at(unsigned row, unsigned column, u32 ch) 82{ 83 m_client.put_character_at(row, column, ch, m_current_state.attribute); 84 m_last_code_point = ch; 85} 86 87void ConsoleImpl::clear_in_line(u16 row, u16 first_column, u16 last_column) 88{ 89 m_client.clear_in_line(row, first_column, last_column); 90} 91 92void ConsoleImpl::scroll_left(u16 row, u16 column, size_t count) 93{ 94 m_client.scroll_left(row, column, count); 95} 96 97void ConsoleImpl::scroll_right(u16 row, u16 column, size_t count) 98{ 99 m_client.scroll_right(row, column, count); 100} 101 102void VirtualConsole::set_graphical(bool graphical) 103{ 104 m_graphical = graphical; 105} 106 107ErrorOr<NonnullOwnPtr<KString>> VirtualConsole::pseudo_name() const 108{ 109 return KString::formatted("tty:{}", m_index); 110} 111 112UNMAP_AFTER_INIT NonnullLockRefPtr<VirtualConsole> VirtualConsole::create(size_t index) 113{ 114 auto virtual_console_or_error = DeviceManagement::try_create_device<VirtualConsole>(index); 115 // FIXME: Find a way to propagate errors 116 VERIFY(!virtual_console_or_error.is_error()); 117 return virtual_console_or_error.release_value(); 118} 119 120UNMAP_AFTER_INIT NonnullLockRefPtr<VirtualConsole> VirtualConsole::create_with_preset_log(size_t index, CircularQueue<char, 16384> const& log) 121{ 122 auto virtual_console = VirtualConsole::create(index); 123 // HACK: We have to go through the TTY layer for correct newline handling. 124 // It would be nice to not have to make all these calls, but we can't get the underlying data pointer 125 // and head index. If we did that, we could reduce this to at most 2 calls. 126 for (auto ch : log) { 127 virtual_console->emit_char(ch); 128 } 129 return virtual_console; 130} 131 132UNMAP_AFTER_INIT void VirtualConsole::initialize() 133{ 134 VERIFY(GraphicsManagement::the().console()); 135 set_size(GraphicsManagement::the().console()->max_column(), GraphicsManagement::the().console()->max_row()); 136 m_console_impl.set_size(GraphicsManagement::the().console()->max_column(), GraphicsManagement::the().console()->max_row()); 137 138 // Allocate twice of the max row * max column * sizeof(Cell) to ensure we can have some sort of history mechanism... 139 auto size = GraphicsManagement::the().console()->max_column() * GraphicsManagement::the().console()->max_row() * sizeof(Cell) * 2; 140 m_cells = MM.allocate_kernel_region(Memory::page_round_up(size).release_value_but_fixme_should_propagate_errors(), "Virtual Console Cells"sv, Memory::Region::Access::ReadWrite, AllocationStrategy::AllocateNow).release_value(); 141 142 // Add the lines, so we also ensure they will be flushed now 143 for (size_t row = 0; row < rows(); row++) { 144 m_lines.append({ true, 0 }); 145 } 146 VERIFY(m_cells); 147} 148 149void VirtualConsole::refresh_after_resolution_change() 150{ 151 auto old_rows_count = rows(); 152 auto old_columns_count = columns(); 153 set_size(GraphicsManagement::the().console()->max_column(), GraphicsManagement::the().console()->max_row()); 154 m_console_impl.set_size(GraphicsManagement::the().console()->max_column(), GraphicsManagement::the().console()->max_row()); 155 156 // Note: From now on, columns() and rows() are updated with the new settings. 157 158 auto size = GraphicsManagement::the().console()->max_column() * GraphicsManagement::the().console()->max_row() * sizeof(Cell) * 2; 159 auto new_cells = MM.allocate_kernel_region(Memory::page_round_up(size).release_value_but_fixme_should_propagate_errors(), "Virtual Console Cells"sv, Memory::Region::Access::ReadWrite, AllocationStrategy::AllocateNow).release_value(); 160 161 if (rows() < old_rows_count) { 162 m_lines.shrink(rows()); 163 } else { 164 for (size_t row = 0; row < (size_t)(rows() - old_rows_count); row++) { 165 m_lines.append({ true, 0 }); 166 } 167 } 168 169 // Note: A potential loss of displayed data occur when resolution width shrinks. 170 auto common_rows_count = min(old_rows_count, rows()); 171 auto common_columns_count = min(old_columns_count, columns()); 172 for (size_t row = 0; row < common_rows_count; row++) { 173 auto& line = m_lines[row]; 174 memcpy(new_cells->vaddr().offset(row * columns() * sizeof(Cell)).as_ptr(), m_cells->vaddr().offset(row * old_columns_count * sizeof(Cell)).as_ptr(), common_columns_count * sizeof(Cell)); 175 line.dirty = true; 176 } 177 178 // Update the new cells Region 179 m_cells = move(new_cells); 180 m_console_impl.m_need_full_flush = true; 181 flush_dirty_lines(); 182} 183 184UNMAP_AFTER_INIT VirtualConsole::VirtualConsole(unsigned const index) 185 : TTY(4, index) 186 , m_index(index) 187 , m_console_impl(*this) 188{ 189 initialize(); 190} 191 192UNMAP_AFTER_INIT VirtualConsole::~VirtualConsole() 193{ 194 VERIFY_NOT_REACHED(); 195} 196 197static inline Graphics::Console::Color ansi_color_to_standard_vga_color(VT::Color::ANSIColor color) 198{ 199 switch (color) { 200 case VT::Color::ANSIColor::DefaultBackground: 201 case VT::Color::ANSIColor::Black: 202 return Graphics::Console::Color::Black; 203 case VT::Color::ANSIColor::Red: 204 return Graphics::Console::Color::Red; 205 case VT::Color::ANSIColor::Green: 206 return Graphics::Console::Color::Green; 207 case VT::Color::ANSIColor::Yellow: 208 // VGA only has bright yellow, and treats normal yellow as a brownish orange color. 209 return Graphics::Console::Color::Brown; 210 case VT::Color::ANSIColor::Blue: 211 return Graphics::Console::Color::Blue; 212 case VT::Color::ANSIColor::Magenta: 213 return Graphics::Console::Color::Magenta; 214 case VT::Color::ANSIColor::Cyan: 215 return Graphics::Console::Color::Cyan; 216 case VT::Color::ANSIColor::DefaultForeground: 217 case VT::Color::ANSIColor::White: 218 return Graphics::Console::Color::LightGray; 219 case VT::Color::ANSIColor::BrightBlack: 220 return Graphics::Console::Color::DarkGray; 221 case VT::Color::ANSIColor::BrightRed: 222 return Graphics::Console::Color::BrightRed; 223 case VT::Color::ANSIColor::BrightGreen: 224 return Graphics::Console::Color::BrightGreen; 225 case VT::Color::ANSIColor::BrightYellow: 226 return Graphics::Console::Color::Yellow; 227 case VT::Color::ANSIColor::BrightBlue: 228 return Graphics::Console::Color::BrightBlue; 229 case VT::Color::ANSIColor::BrightMagenta: 230 return Graphics::Console::Color::BrightMagenta; 231 case VT::Color::ANSIColor::BrightCyan: 232 return Graphics::Console::Color::BrightCyan; 233 case VT::Color::ANSIColor::BrightWhite: 234 return Graphics::Console::Color::White; 235 } 236 VERIFY_NOT_REACHED(); 237} 238 239static inline Graphics::Console::Color terminal_to_standard_color(VT::Color color) 240{ 241 switch (color.kind()) { 242 case VT::Color::Kind::Named: 243 return ansi_color_to_standard_vga_color(color.as_named()); 244 default: 245 return Graphics::Console::Color::LightGray; 246 } 247} 248 249void VirtualConsole::on_key_pressed(KeyEvent event) 250{ 251 // Ignore keyboard in graphical mode. 252 if (m_graphical) 253 return; 254 255 if (!event.is_press()) 256 return; 257 258 Processor::deferred_call_queue([this, event]() { 259 m_console_impl.handle_key_press(event.key, event.code_point, event.flags); 260 }); 261} 262 263ErrorOr<size_t> VirtualConsole::on_tty_write(UserOrKernelBuffer const& data, size_t size) 264{ 265 SpinlockLocker global_lock(ConsoleManagement::the().tty_write_lock()); 266 auto result = data.read_buffered<512>(size, [&](ReadonlyBytes buffer) { 267 for (const auto& byte : buffer) 268 m_console_impl.on_input(byte); 269 return buffer.size(); 270 }); 271 if (m_active) 272 flush_dirty_lines(); 273 return result; 274} 275 276void VirtualConsole::set_active(bool active) 277{ 278 VERIFY(ConsoleManagement::the().m_lock.is_locked()); 279 VERIFY(m_active != active); 280 m_active = active; 281 282 if (active) { 283 HIDManagement::the().set_client(this); 284 285 m_console_impl.m_need_full_flush = true; 286 flush_dirty_lines(); 287 } else { 288 HIDManagement::the().set_client(nullptr); 289 } 290} 291 292void VirtualConsole::emit_char(char ch) 293{ 294 // Since we are standards-compliant by not moving to column 1 on '\n', we have to add an extra carriage return to 295 // do newlines properly. The `TTY` layer handles adding it. 296 echo_with_processing(static_cast<u8>(ch)); 297} 298 299void VirtualConsole::flush_dirty_lines() 300{ 301 if (!m_active) 302 return; 303 VERIFY(GraphicsManagement::is_initialized()); 304 VERIFY(GraphicsManagement::the().console()); 305 for (u16 visual_row = 0; visual_row < rows(); ++visual_row) { 306 auto& line = m_lines[visual_row]; 307 if (!line.dirty && !m_console_impl.m_need_full_flush) 308 continue; 309 for (size_t column = 0; column < columns(); ++column) { 310 auto& cell = cell_at(column, visual_row); 311 312 auto foreground_color = terminal_to_standard_color(cell.attribute.effective_foreground_color()); 313 if (has_flag(cell.attribute.flags, VT::Attribute::Flags::Bold)) 314 foreground_color = (Graphics::Console::Color)((u8)foreground_color | 0x08); 315 GraphicsManagement::the().console()->write(column, 316 visual_row, 317 ((u8)cell.ch < 128 ? cell.ch : '?'), 318 terminal_to_standard_color(cell.attribute.effective_background_color()), 319 foreground_color); 320 } 321 line.dirty = false; 322 } 323 GraphicsManagement::the().console()->set_cursor(m_console_impl.cursor_column(), m_console_impl.cursor_row()); 324 m_console_impl.m_need_full_flush = false; 325} 326 327void VirtualConsole::beep() 328{ 329 if (!kernel_command_line().is_pc_speaker_enabled()) 330 return; 331#if ARCH(X86_64) 332 PCSpeaker::tone_on(440); 333 microseconds_delay(10000); 334 PCSpeaker::tone_off(); 335#endif 336} 337 338void VirtualConsole::set_window_title(StringView) 339{ 340 // Do nothing. 341} 342 343void VirtualConsole::set_window_progress(int, int) 344{ 345 // Do nothing. 346} 347 348void VirtualConsole::terminal_did_resize(u16 columns, u16 rows) 349{ 350 // FIXME: Allocate more Region(s) or deallocate them if needed... 351 dbgln("VC {}: Resized to {} x {}", index(), columns, rows); 352} 353 354void VirtualConsole::terminal_history_changed(int) 355{ 356 // Do nothing, I guess? 357} 358 359void VirtualConsole::emit(u8 const* data, size_t size) 360{ 361 for (size_t i = 0; i < size; i++) 362 TTY::emit(data[i], true); 363} 364 365void VirtualConsole::set_cursor_shape(VT::CursorShape) 366{ 367 // Do nothing 368} 369 370void VirtualConsole::set_cursor_blinking(bool) 371{ 372 // Do nothing 373} 374 375void VirtualConsole::echo(u8 ch) 376{ 377 m_console_impl.on_input(ch); 378 if (m_active) 379 flush_dirty_lines(); 380} 381 382VirtualConsole::Cell& VirtualConsole::cell_at(size_t x, size_t y) 383{ 384 auto* ptr = (VirtualConsole::Cell*)(m_cells->vaddr().as_ptr()); 385 ptr += (y * columns()) + x; 386 return *ptr; 387} 388 389void VirtualConsole::clear() 390{ 391 auto* cell = (Cell*)m_cells->vaddr().as_ptr(); 392 for (size_t y = 0; y < rows(); y++) { 393 m_lines[y].dirty = true; 394 for (size_t x = 0; x < columns(); x++) { 395 cell[x].clear(); 396 } 397 cell += columns(); 398 } 399 m_console_impl.set_cursor(0, 0); 400} 401 402void VirtualConsole::scroll_up(u16 region_top, u16 region_bottom, size_t count) 403{ 404 VERIFY(region_top <= region_bottom); 405 size_t region_size = region_bottom - region_top + 1; 406 count = min(count, region_size); 407 size_t line_bytes = (columns() * sizeof(Cell)); 408 memmove(m_cells->vaddr().offset(line_bytes * region_top).as_ptr(), m_cells->vaddr().offset(line_bytes * (region_top + count)).as_ptr(), line_bytes * (region_size - count)); 409 for (size_t i = 0; i < count; ++i) 410 clear_line(region_bottom - i); 411 for (u16 row = region_top; row <= region_bottom; ++row) 412 m_lines[row].dirty = true; 413} 414 415void VirtualConsole::scroll_down(u16 region_top, u16 region_bottom, size_t count) 416{ 417 VERIFY(region_top <= region_bottom); 418 size_t region_size = region_bottom - region_top + 1; 419 count = min(count, region_size); 420 size_t line_bytes = (columns() * sizeof(Cell)); 421 memmove(m_cells->vaddr().offset(line_bytes * (region_top + count)).as_ptr(), m_cells->vaddr().offset(line_bytes * region_top).as_ptr(), line_bytes * (region_size - count)); 422 for (size_t i = 0; i < count; ++i) 423 clear_line(region_top + i); 424 for (u16 row = region_top; row <= region_bottom; ++row) 425 m_lines[row].dirty = true; 426} 427 428void VirtualConsole::scroll_left(u16 row, u16 column, size_t count) 429{ 430 VERIFY(row < rows()); 431 VERIFY(column < columns()); 432 count = min<size_t>(count, columns() - column); 433 memmove(&cell_at(column, row), &cell_at(column + count, row), sizeof(Cell) * (columns() - column - count)); 434 for (size_t i = column + count; i < columns(); ++i) 435 cell_at(i, row).clear(); 436 m_lines[row].dirty = true; 437} 438 439void VirtualConsole::scroll_right(u16 row, u16 column, size_t count) 440{ 441 VERIFY(row < rows()); 442 VERIFY(column < columns()); 443 count = min<size_t>(count, columns() - column); 444 memmove(&cell_at(column + count, row), &cell_at(column, row), sizeof(Cell) * (columns() - column - count)); 445 for (size_t i = column; i < column + count; ++i) 446 cell_at(i, row).clear(); 447 m_lines[row].dirty = true; 448} 449 450void VirtualConsole::clear_in_line(u16 row, u16 first_column, u16 last_column) 451{ 452 VERIFY(row < rows()); 453 VERIFY(first_column <= last_column); 454 VERIFY(last_column < columns()); 455 m_lines[row].dirty = true; 456 for (size_t x = first_column; x <= last_column; x++) 457 cell_at(x, row).clear(); 458} 459 460void VirtualConsole::put_character_at(unsigned row, unsigned column, u32 code_point, const VT::Attribute& attribute) 461{ 462 VERIFY(row < rows()); 463 VERIFY(column < columns()); 464 auto& line = m_lines[row]; 465 auto& cell = cell_at(column, row); 466 cell.attribute.foreground_color = attribute.foreground_color; 467 cell.attribute.background_color = attribute.background_color; 468 cell.attribute.flags = attribute.flags; 469 if (code_point > 128) 470 cell.ch = ' '; 471 else 472 cell.ch = code_point; 473 cell.attribute.flags |= VT::Attribute::Flags::Touched; 474 line.dirty = true; 475 // FIXME: Maybe we should consider to change length after printing a special char in a column 476 if (code_point <= 20) 477 return; 478 line.length = max<size_t>(line.length, column); 479} 480 481void VirtualConsole::invalidate_cursor(size_t row) 482{ 483 m_lines[row].dirty = true; 484} 485 486}