254 lines
7.5 KiB
C++
254 lines
7.5 KiB
C++
#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 <bits/ioctl-defs.h>
|
|
#include <luna/CString.h>
|
|
#include <luna/CType.h>
|
|
#include <luna/Units.h>
|
|
#include <luna/Vector.h>
|
|
|
|
Vector<SharedPtr<ConsoleDevice>> ConsoleDevice::m_console_devices;
|
|
bool ConsoleDevice::s_is_in_graphical_mode { false };
|
|
|
|
Result<void> ConsoleDevice::create()
|
|
{
|
|
auto device = TRY(make_shared<ConsoleDevice>());
|
|
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<void> 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<usize> 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<usize> 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<u64> 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);
|
|
}
|
|
}
|