#include #include #include #include #include #include #include #include #include #include #include #include #include namespace Kernel { static BAN::RefPtr s_tty; BAN::RefPtr TTY::current() { ASSERT(s_tty); return s_tty; } void TTY::set_as_current() { s_tty = this; clear(); auto inode_or_error = DevFileSystem::get().root_inode()->find_inode("tty"sv); if (inode_or_error.is_error()) { if (inode_or_error.error().get_error_code() == ENOENT) DevFileSystem::get().add_inode("tty"sv, MUST(TmpSymlinkInode::create_new(DevFileSystem::get(), 0666, 0, 0, s_tty->name()))); else dwarnln("{}", inode_or_error.error()); return; } auto inode = inode_or_error.release_value(); if (inode->mode().iflnk()) MUST(reinterpret_cast(inode.ptr())->set_link_target(name())); } BAN::ErrorOr TTY::tty_ctrl(int command, int flags) { if (flags & ~(TTY_FLAG_ENABLE_INPUT | TTY_FLAG_ENABLE_OUTPUT)) return BAN::Error::from_errno(EINVAL); switch (command) { case TTY_CMD_SET: if ((flags & TTY_FLAG_ENABLE_INPUT) && !m_tty_ctrl.receive_input) { m_tty_ctrl.receive_input = true; m_tty_ctrl.semaphore.unblock(); } if (flags & TTY_FLAG_ENABLE_OUTPUT) m_tty_ctrl.draw_graphics = true; break; case TTY_CMD_UNSET: if ((flags & TTY_FLAG_ENABLE_INPUT) && m_tty_ctrl.receive_input) m_tty_ctrl.receive_input = false; if (flags & TTY_FLAG_ENABLE_OUTPUT) m_tty_ctrl.draw_graphics = false; break; default: return BAN::Error::from_errno(EINVAL); } return {}; } void TTY::initialize_devices() { static bool initialized = false; ASSERT(!initialized); Process::create_kernel( [](void*) { auto file_or_error = VirtualFileSystem::get().file_from_absolute_path({ 0, 0, 0, 0 }, "/dev/input0"sv, O_RDONLY); if (file_or_error.is_error()) { dprintln("no input device found"); return; } auto inode = file_or_error.value().inode; while (true) { while (!TTY::current()->m_tty_ctrl.receive_input) TTY::current()->m_tty_ctrl.semaphore.block(); Input::KeyEvent event; size_t read = MUST(inode->read(0, BAN::ByteSpan::from(event))); ASSERT(read == sizeof(event)); TTY::current()->on_key_event(event); } }, nullptr ); initialized = true; } void TTY::on_key_event(Input::KeyEvent event) { LockGuard _(m_lock); if (event.released()) return; const char* ansi_c_str = Input::key_event_to_utf8(event); if (event.ctrl()) { ansi_c_str = nullptr; switch (event.key) { case Input::Key::A: ansi_c_str = "\x01"; break; case Input::Key::B: ansi_c_str = "\x02"; break; case Input::Key::C: ansi_c_str = "\x03"; break; case Input::Key::D: ansi_c_str = "\x04"; break; case Input::Key::E: ansi_c_str = "\x05"; break; case Input::Key::F: ansi_c_str = "\x06"; break; case Input::Key::G: ansi_c_str = "\x07"; break; case Input::Key::H: ansi_c_str = "\x08"; break; case Input::Key::I: ansi_c_str = "\x09"; break; case Input::Key::J: ansi_c_str = "\x0A"; break; case Input::Key::K: ansi_c_str = "\x0B"; break; case Input::Key::L: ansi_c_str = "\x0C"; break; case Input::Key::M: ansi_c_str = "\x0D"; break; case Input::Key::N: ansi_c_str = "\x0E"; break; case Input::Key::O: ansi_c_str = "\x0F"; break; case Input::Key::P: ansi_c_str = "\x10"; break; case Input::Key::Q: ansi_c_str = "\x11"; break; case Input::Key::R: ansi_c_str = "\x12"; break; case Input::Key::S: ansi_c_str = "\x13"; break; case Input::Key::T: ansi_c_str = "\x14"; break; case Input::Key::U: ansi_c_str = "\x15"; break; case Input::Key::V: ansi_c_str = "\x16"; break; case Input::Key::W: ansi_c_str = "\x17"; break; case Input::Key::X: ansi_c_str = "\x18"; break; case Input::Key::Y: ansi_c_str = "\x19"; break; case Input::Key::Z: ansi_c_str = "\x1A"; break; default: break; } } else { switch (event.key) { case Input::Key::Enter: case Input::Key::NumpadEnter: ansi_c_str = "\n"; break; case Input::Key::Backspace: ansi_c_str = "\b"; break; case Input::Key::ArrowUp: ansi_c_str = "\e[A"; break; case Input::Key::ArrowDown: ansi_c_str = "\e[B"; break; case Input::Key::ArrowRight: ansi_c_str = "\e[C"; break; case Input::Key::ArrowLeft: ansi_c_str = "\e[D"; break; default: break; } } if (ansi_c_str) { auto* ptr = (const uint8_t*)ansi_c_str; while (*ptr) handle_input_byte(*ptr++); } } void TTY::handle_input_byte(uint8_t ch) { if (ch == 0) return; LockGuard _(m_lock); // ^C if (ch == '\x03') { if (auto ret = Process::sys_kill(-m_foreground_pgrp, SIGINT); ret.is_error()) dwarnln("TTY: {}", ret.error()); return; } // ^D + canonical if (ch == '\x04' && m_termios.canonical) { m_output.flush = true; m_output.semaphore.unblock(); return; } // backspace + canonical if (ch == '\b' && m_termios.canonical) { do_backspace(); return; } m_output.buffer[m_output.bytes++] = ch; if (m_termios.echo) { if ((ch <= 31 || ch == 127) && ch != '\n') { putchar('^'); if (ch <= 26 && ch != 10) putchar('A' + ch - 1); else if (ch == 27) putchar('['); else if (ch == 28) putchar('\\'); else if (ch == 29) putchar(']'); else if (ch == 30) putchar('^'); else if (ch == 31) putchar('_'); else if (ch == 127) putchar('?'); } else { putchar(ch); } } if (ch == '\n' || !m_termios.canonical) { m_output.flush = true; m_output.semaphore.unblock(); } } void TTY::do_backspace() { auto print_backspace = [this] { putchar('\b'); putchar(' '); putchar('\b'); }; if (m_output.bytes > 0) { uint8_t last = m_output.buffer[m_output.bytes - 1]; // Multibyte UTF8 if ((last & 0xC0) == 0x80) { // NOTE: this should be valid UTF8 since keyboard input already 'validates' it while ((m_output.buffer[m_output.bytes - 1] & 0xC0) == 0x80) { ASSERT(m_output.bytes > 0); m_output.bytes--; } ASSERT(m_output.bytes > 0); m_output.bytes--; print_backspace(); } // Caret notation else if (last < 32 || last == 127) { m_output.bytes--; print_backspace(); print_backspace(); } // Ascii else { m_output.bytes--; print_backspace(); } } } void TTY::putchar(uint8_t ch) { if (m_tty_ctrl.draw_graphics) putchar_impl(ch); } BAN::ErrorOr TTY::read_impl(off_t, BAN::ByteSpan buffer) { LockGuard _(m_lock); while (!m_output.flush) { m_lock.unlock(); m_output.semaphore.block(); m_lock.lock(); } if (m_output.bytes == 0) { m_output.flush = false; return 0; } size_t to_copy = BAN::Math::min(buffer.size(), m_output.bytes); memcpy(buffer.data(), m_output.buffer.data(), to_copy); memmove(m_output.buffer.data(), m_output.buffer.data() + to_copy, m_output.bytes - to_copy); m_output.bytes -= to_copy; if (m_output.bytes == 0) m_output.flush = false; m_output.semaphore.unblock(); return to_copy; } BAN::ErrorOr TTY::write_impl(off_t, BAN::ConstByteSpan buffer) { LockGuard _(m_lock); for (size_t i = 0; i < buffer.size(); i++) putchar(buffer[i]); return buffer.size(); } bool TTY::has_data_impl() const { LockGuard _(m_lock); return m_output.flush; } void TTY::putchar_current(uint8_t ch) { ASSERT(s_tty); LockGuard _(s_tty->m_lock); s_tty->putchar(ch); } bool TTY::is_initialized() { return s_tty; } }