#include "fs/devices/ConsoleDevice.h" #include "Log.h" #include "Pledge.h" #include "fs/devices/DeviceRegistry.h" #include "fs/devices/KeyboardDevice.h" #include "memory/MemoryManager.h" #include "thread/Scheduler.h" #include "video/TextConsole.h" #include #include #include #include #include Vector> ConsoleDevice::m_console_devices; bool ConsoleDevice::s_is_in_graphical_mode { false }; Result ConsoleDevice::create() { auto device = TRY(make_shared()); device->m_settings.c_lflag = ECHO | ECHOE | ECHOCTL | ISIG | ICANON; device->m_settings.c_cc[VEOF] = '\4'; device->m_settings.c_cc[VERASE] = '\b'; device->m_settings.c_cc[VINTR] = '\3'; device->m_settings.c_cc[VQUIT] = '\x1c'; TRY(m_console_devices.try_append(device)); return DeviceRegistry::register_special_device(DeviceRegistry::Console, 0, device); } Result ConsoleDevice::handle_background_process_group(bool can_succeed, int signo) const { if (!m_foreground_process_group.has_value()) return {}; auto foreground_pgrp = m_foreground_process_group.value(); auto* current = Scheduler::current(); if (current->pgid == foreground_pgrp) return {}; if ((current->signal_mask & (1 << (signo - 1))) || (current->signal_handlers[signo - 1].sa_handler == SIG_IGN)) { if (can_succeed) return {}; return err(EIO); } current->send_signal(signo); if (can_succeed) return err(EINTR); return err(EIO); } Result ConsoleDevice::read(u8* buf, usize, usize length) const { TRY(handle_background_process_group(false, SIGTTIN)); length = m_input_buffer.dequeue_data(buf, length); if (!length && m_may_read_without_blocking) m_may_read_without_blocking = false; return length; } Result ConsoleDevice::write(const u8* buf, usize, usize length) { if (m_settings.c_lflag & TOSTOP) TRY(handle_background_process_group(true, SIGTTOU)); // if (s_is_in_graphical_mode) return length; TextConsole::write((const char*)buf, length); return length; } bool ConsoleDevice::will_block_if_read() const { return m_may_read_without_blocking ? false : m_input_buffer.size() == 0; } void ConsoleDevice::did_press_or_release_key(u8 scancode) { if (!s_is_in_graphical_mode) for (const auto& device : m_console_devices) { device->process_key_event(scancode); } else { static Keyboard::KeyboardState state = {}; auto packet = Keyboard::decode_scancode(scancode, state); if (packet.has_value()) KeyboardDevice::add_keyboard_event(packet.release_value()); } } void ConsoleDevice::process_key_event(u8 scancode) { auto rc = Keyboard::decode_scancode_tty(scancode, m_kb_state); if (!rc.has_value()) return; char key = rc.value(); check(key >= 0); bool is_special_character { false }; if (is_canonical()) { if (key == m_settings.c_cc[VERASE]) { auto maybe_char = m_line_buffer.try_pop(); if (maybe_char.has_value() && maybe_char.value()) { if ((m_settings.c_lflag & ECHO) && (m_settings.c_lflag & ECHOE)) { TextConsole::putwchar(L'\b'); if (_iscntrl(maybe_char.value())) TextConsole::putwchar(L'\b'); } return; } if ((m_settings.c_lflag & ECHOE)) return; else is_special_character = true; } if (key == m_settings.c_cc[VEOF]) { m_input_buffer.append_data(m_line_buffer.data(), m_line_buffer.size()); m_line_buffer.clear(); m_may_read_without_blocking = true; is_special_character = true; } if (m_settings.c_lflag & ISIG) { if (key == m_settings.c_cc[VINTR]) { if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear(); if (m_foreground_process_group.has_value()) { Scheduler::for_each_in_process_group(m_foreground_process_group.value(), [](Thread* thread) { thread->send_signal(SIGINT); return true; }); } is_special_character = true; } if (key == m_settings.c_cc[VQUIT]) { if (!(m_settings.c_lflag & NOFLSH)) m_line_buffer.clear(); if (m_foreground_process_group.has_value()) { Scheduler::for_each_in_process_group(m_foreground_process_group.value(), [](Thread* thread) { thread->send_signal(SIGQUIT); return true; }); } is_special_character = true; } } } if (!is_special_character) { if (is_canonical()) { m_line_buffer.try_append((u8)key); if (key == '\n') { m_input_buffer.append_data(m_line_buffer.data(), m_line_buffer.size()); m_line_buffer.clear(); } } else m_input_buffer.append_data((u8*)&key, 1); } if (!(m_settings.c_lflag & ECHO)) return; if (_iscntrl(key)) { if (m_settings.c_lflag & ECHOCTL) { bool should_echo = true; if (key == '\n' || key == '\0' || key == m_settings.c_cc[VEOF]) should_echo = false; if (should_echo) { char caret_notation[3] = { '^', '\0', '\0' }; if (key == 0x7f) caret_notation[1] = '?'; else caret_notation[1] = key + 0x40; TextConsole::print(caret_notation); } else TextConsole::putchar(key); } } else TextConsole::putchar(key); } Result ConsoleDevice::ioctl(int request, void* arg) { auto* current = Scheduler::current(); TRY(check_pledge(current, Promise::p_tty)); switch (request) { case TCGETS: { return MemoryManager::copy_to_user_typed((struct termios*)arg, &m_settings) ? 0 : err(EFAULT); } case TCSETS: { TRY(handle_background_process_group(true, SIGTTOU)); if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &m_settings)) return err(EFAULT); return 0; } case TIOCSPGRP: { TRY(handle_background_process_group(true, SIGTTOU)); pid_t pgid; if (!MemoryManager::copy_from_user_typed((const pid_t*)arg, &pgid)) return err(EFAULT); bool pgid_exists = false; Scheduler::for_each_in_process_group(pgid, [&pgid_exists](Thread*) { pgid_exists = true; return false; }); if (!pgid_exists) return err(EPERM); m_foreground_process_group = pgid; return 0; } case TIOCGPGRP: { pid_t pgid = m_foreground_process_group.value_or((pid_t)next_thread_id()); if (!MemoryManager::copy_to_user_typed((pid_t*)arg, &pgid)) return err(EFAULT); return 0; } case TIOCGWINSZ: { struct winsize window; window.ws_col = TextConsole::cols(); window.ws_row = TextConsole::rows(); if (!MemoryManager::copy_to_user_typed((struct winsize*)arg, &window)) return err(EFAULT); return 0; } case TTYSETGFX: { s_is_in_graphical_mode = (bool)arg; return 0; } default: return err(EINVAL); } }