kernel+libc+sh: Make the TTY device actually follow termios rules
All checks were successful
continuous-integration/drone/push Build is passing

Like, so much more termios compatibility!
This commit is contained in:
apio 2023-07-13 20:33:20 +02:00
parent efd5bae7a5
commit de6fe7f7c2
Signed by: apio
GPG Key ID: B8A7D06E42258954
10 changed files with 217 additions and 143 deletions

View File

@ -98,7 +98,11 @@ Result<int> luna_main(int argc, char** argv)
auto maybe_cmd = input_file->read_line(); auto maybe_cmd = input_file->read_line();
if (maybe_cmd.has_error()) if (maybe_cmd.has_error())
{ {
if (maybe_cmd.error() == EINTR) continue; if (maybe_cmd.error() == EINTR)
{
os::println("");
continue;
}
return maybe_cmd.release_error(); return maybe_cmd.release_error();
} }
@ -150,6 +154,8 @@ Result<int> luna_main(int argc, char** argv)
{ {
int sig = WTERMSIG(status); int sig = WTERMSIG(status);
if (sig != SIGINT && sig != SIGQUIT) os::println("[sh] Process %d exited: %s", child, strsignal(sig)); if (sig != SIGINT && sig != SIGQUIT) os::println("[sh] Process %d exited: %s", child, strsignal(sig));
else
os::println("");
} }
} }

View File

@ -3,13 +3,14 @@
namespace Keyboard namespace Keyboard
{ {
enum Modifiers struct KeyboardState
{ {
LeftControl = 1, bool ignore_next { false };
LeftAlt = 2, bool left_shift { false };
bool right_shift { false };
bool left_control { false };
bool capslock { false };
}; };
Option<char> decode_scancode(u8 scancode); Option<char> decode_scancode_tty(u8 scancode, KeyboardState& state);
int modifiers();
} }

View File

@ -147,8 +147,7 @@ void io_thread()
u8 scancode; u8 scancode;
while (!scancode_queue.try_pop(scancode)) kernel_wait_for_event(); while (!scancode_queue.try_pop(scancode)) kernel_wait_for_event();
char key; ConsoleDevice::did_press_or_release_key(scancode);
if (Keyboard::decode_scancode(scancode).try_set_value(key)) ConsoleDevice::did_press_key(key);
} }
} }

View File

@ -1,4 +1,5 @@
#include "arch/Keyboard.h" #include "arch/Keyboard.h"
#include <luna/CType.h>
// PS/2 keyboard decoding routine. // PS/2 keyboard decoding routine.
@ -28,11 +29,6 @@ static bool should_ignore_key(u8 scancode)
return (scancode > 0x3A && scancode < 0x47) || scancode == TAB || scancode == F11 || scancode == F12; return (scancode > 0x3A && scancode < 0x47) || scancode == TAB || scancode == F11 || scancode == F12;
} }
static bool g_ignore_next { false };
static bool g_left_shift { false };
static bool g_right_shift { false };
static bool g_capslock { false };
constexpr char key_table[] = { constexpr char key_table[] = {
'\0', '\0',
'\1', // escape '\1', // escape
@ -117,28 +113,26 @@ constexpr char shifted_key_table[] = {
'.', // keypad . '.', // keypad .
}; };
static bool is_shifted() static bool is_shifted(const Keyboard::KeyboardState& state)
{ {
if (g_capslock) return !(g_left_shift || g_right_shift); if (state.capslock) return !(state.left_shift || state.right_shift);
return g_left_shift || g_right_shift; return state.left_shift || state.right_shift;
} }
int g_modifiers = 0;
namespace Keyboard namespace Keyboard
{ {
Option<char> decode_scancode(u8 scancode) Option<char> decode_scancode_tty(u8 scancode, KeyboardState& state)
{ {
if (g_ignore_next) if (state.ignore_next)
{ {
g_ignore_next = false; state.ignore_next = false;
return {}; return {};
} }
// FIXME: Support extended scancodes. // FIXME: Support extended scancodes.
if (scancode == EXTENDED_KEY_CODE) if (scancode == EXTENDED_KEY_CODE)
{ {
g_ignore_next = true; state.ignore_next = true;
return {}; return {};
} }
@ -146,35 +140,27 @@ namespace Keyboard
if (scancode == LEFT_SHIFT) if (scancode == LEFT_SHIFT)
{ {
g_left_shift = !released; state.left_shift = !released;
return {}; return {};
} }
if (scancode == RIGHT_SHIFT) if (scancode == RIGHT_SHIFT)
{ {
g_right_shift = !released; state.right_shift = !released;
return {}; return {};
} }
if (scancode == CAPS_LOCK) if (scancode == CAPS_LOCK)
{ {
if (!released) g_capslock = !g_capslock; if (!released) state.capslock = !state.capslock;
return {};
}
if (scancode == LEFT_ALT)
{
if (released) g_modifiers &= ~LeftAlt;
else
g_modifiers |= LeftAlt;
return {}; return {};
} }
if (scancode == LEFT_CONTROL) if (scancode == LEFT_CONTROL)
{ {
if (released) g_modifiers &= ~LeftControl; if (released) state.left_control = false;
else else
g_modifiers |= LeftControl; state.left_control = true;
return {}; return {};
} }
@ -182,12 +168,20 @@ namespace Keyboard
if (released) return {}; if (released) return {};
if (is_shifted()) return shifted_key_table[scancode]; if (state.left_control)
{
char key;
if (is_shifted(state)) key = shifted_key_table[scancode];
else
key = key_table[scancode];
if (_islower(key)) key = (char)_toupper(key);
if (_isupper(key)) return key - 0x40;
if (key == '@') return key - 0x40;
if (key > 'Z' && key < '`') return key - 0x40;
if (key == '?') return 0x7f;
}
if (is_shifted(state)) return shifted_key_table[scancode];
return key_table[scancode]; return key_table[scancode];
} }
int modifiers()
{
return g_modifiers;
}
} }

View File

@ -1,36 +1,34 @@
#include "fs/devices/ConsoleDevice.h" #include "fs/devices/ConsoleDevice.h"
#include "Log.h" #include "Log.h"
#include "arch/Keyboard.h"
#include "fs/devices/DeviceRegistry.h" #include "fs/devices/DeviceRegistry.h"
#include "memory/MemoryManager.h" #include "memory/MemoryManager.h"
#include "thread/Scheduler.h" #include "thread/Scheduler.h"
#include "video/TextConsole.h" #include "video/TextConsole.h"
#include <bits/ioctl-defs.h> #include <bits/ioctl-defs.h>
#include <bits/termios.h>
#include <luna/Buffer.h>
#include <luna/CString.h> #include <luna/CString.h>
#include <luna/CType.h>
#include <luna/Units.h> #include <luna/Units.h>
#include <luna/Vector.h> #include <luna/Vector.h>
static Buffer g_console_input; Vector<SharedPtr<ConsoleDevice>> ConsoleDevice::m_console_devices;
static bool g_eof { false };
static bool g_echo { true };
static bool g_stop_background_output { false };
static Option<pid_t> g_foreground_process_group;
Result<void> ConsoleDevice::create() Result<void> ConsoleDevice::create()
{ {
auto device = (SharedPtr<Device>)TRY(make_shared<ConsoleDevice>()); auto device = TRY(make_shared<ConsoleDevice>());
g_foreground_process_group = {}; 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); return DeviceRegistry::register_special_device(DeviceRegistry::Console, 0, device);
} }
static Result<void> handle_background_process_group(bool can_succeed, int signo) Result<void> ConsoleDevice::handle_background_process_group(bool can_succeed, int signo) const
{ {
if (!g_foreground_process_group.has_value()) return {}; if (!m_foreground_process_group.has_value()) return {};
auto foreground_pgrp = g_foreground_process_group.value(); auto foreground_pgrp = m_foreground_process_group.value();
auto* current = Scheduler::current(); auto* current = Scheduler::current();
if ((pid_t)current->pgid == foreground_pgrp) return {}; if ((pid_t)current->pgid == foreground_pgrp) return {};
@ -51,22 +49,22 @@ Result<usize> ConsoleDevice::read(u8* buf, usize, usize length) const
{ {
TRY(handle_background_process_group(false, SIGTTIN)); TRY(handle_background_process_group(false, SIGTTIN));
if (length > g_console_input.size()) length = g_console_input.size(); if (length > m_input_buffer.size()) length = m_input_buffer.size();
memcpy(buf, g_console_input.data(), length); memcpy(buf, m_input_buffer.data(), length);
memmove(g_console_input.data(), g_console_input.data() + length, g_console_input.size() - length); memmove(m_input_buffer.data(), m_input_buffer.data() + length, m_input_buffer.size() - length);
g_console_input.try_resize(g_console_input.size() - length).release_value(); m_input_buffer.try_resize(m_input_buffer.size() - length).release_value();
if (!length && g_eof) g_eof = false; if (!length && m_may_read_without_blocking) m_may_read_without_blocking = false;
return length; return length;
} }
Result<usize> ConsoleDevice::write(const u8* buf, usize, usize length) Result<usize> ConsoleDevice::write(const u8* buf, usize, usize length)
{ {
if (g_stop_background_output) TRY(handle_background_process_group(true, SIGTTOU)); if (m_settings.c_lflag & TOSTOP) TRY(handle_background_process_group(true, SIGTTOU));
TextConsole::write((const char*)buf, length); TextConsole::write((const char*)buf, length);
return length; return length;
@ -74,78 +72,124 @@ Result<usize> ConsoleDevice::write(const u8* buf, usize, usize length)
bool ConsoleDevice::blocking() const bool ConsoleDevice::blocking() const
{ {
return g_eof ? false : g_console_input.size() == 0; return m_may_read_without_blocking ? false : m_input_buffer.size() == 0;
} }
static Vector<u8> g_temp_input; void ConsoleDevice::did_press_or_release_key(u8 scancode)
void ConsoleDevice::did_press_key(char key)
{ {
if (key == '\b') for (const auto& device : m_console_devices) { device->process_key_event(scancode); }
}
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 (g_temp_input.try_pop().has_value()) if (key == m_settings.c_cc[VERASE])
{ {
if (g_echo) TextConsole::putwchar(L'\b'); 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;
} }
return; if (key == m_settings.c_cc[VEOF])
}
// Ctrl+D
if (key == 'd' && (Keyboard::modifiers() == Keyboard::LeftControl))
{
if (g_temp_input.size() == 0) g_eof = true;
return;
}
if (key == 'c' && (Keyboard::modifiers() == Keyboard::LeftControl))
{
g_temp_input.clear();
if (g_echo) TextConsole::wprintln(L"^C");
if (g_foreground_process_group.has_value())
{ {
Scheduler::for_each_in_process_group(g_foreground_process_group.value(), [](Thread* thread) { m_input_buffer.append_data(m_line_buffer.data(), m_line_buffer.size());
thread->send_signal(SIGINT); m_line_buffer.clear();
return true;
}); m_may_read_without_blocking = true;
is_special_character = true;
} }
return; 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 (key == 'e' && (Keyboard::modifiers() == (Keyboard::LeftAlt | Keyboard::LeftControl))) if (!is_special_character)
{ {
Scheduler::dump_state(); if (is_canonical())
return; {
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 (key == 'm' && (Keyboard::modifiers() == (Keyboard::LeftAlt | Keyboard::LeftControl))) if (!(m_settings.c_lflag & ECHO)) return;
if (_iscntrl(key))
{ {
kinfoln("Total memory: %s", to_dynamic_unit(MemoryManager::total()).release_value().chars()); if (m_settings.c_lflag & ECHOCTL)
kinfoln("Free memory: %s", to_dynamic_unit(MemoryManager::free()).release_value().chars()); {
kinfoln("Used memory: %s", to_dynamic_unit(MemoryManager::used()).release_value().chars()); bool should_echo = true;
kinfoln("Reserved memory: %s", to_dynamic_unit(MemoryManager::reserved()).release_value().chars()); if (key == '\n' || key == '\0' || key == m_settings.c_cc[VEOF]) should_echo = false;
return;
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
if (key == 'h' && (Keyboard::modifiers() == (Keyboard::LeftAlt | Keyboard::LeftControl))) TextConsole::putchar(key);
{
dump_heap_usage();
return;
}
g_temp_input.try_append((u8)key);
if (key == '\n')
{
g_console_input.append_data(g_temp_input.data(), g_temp_input.size());
g_temp_input.clear();
}
if (!g_echo) return;
TextConsole::putchar(key);
} }
Result<u64> ConsoleDevice::ioctl(int request, void* arg) Result<u64> ConsoleDevice::ioctl(int request, void* arg)
@ -153,27 +197,13 @@ Result<u64> ConsoleDevice::ioctl(int request, void* arg)
switch (request) switch (request)
{ {
case TCGETS: { case TCGETS: {
struct termios tc
{
.c_lflag = 0
};
if (g_echo) tc.c_lflag |= ECHO; return MemoryManager::copy_to_user_typed((struct termios*)arg, &m_settings) ? 0 : err(EFAULT);
if (g_stop_background_output) tc.c_lflag |= TOSTOP;
return MemoryManager::copy_to_user_typed((struct termios*)arg, &tc) ? 0 : err(EFAULT);
} }
case TCSETS: { case TCSETS: {
TRY(handle_background_process_group(true, SIGTTOU)); TRY(handle_background_process_group(true, SIGTTOU));
struct termios tc; if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &m_settings)) return err(EFAULT);
if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &tc)) return err(EFAULT);
if (tc.c_lflag & ECHO) g_echo = true;
else
g_echo = false;
if (tc.c_lflag & TOSTOP) g_stop_background_output = true;
else
g_stop_background_output = false;
return 0; return 0;
} }
@ -190,11 +220,11 @@ Result<u64> ConsoleDevice::ioctl(int request, void* arg)
}); });
if (!pgid_exists) return err(EPERM); if (!pgid_exists) return err(EPERM);
g_foreground_process_group = pgid; m_foreground_process_group = pgid;
return 0; return 0;
} }
case TIOCGPGRP: { case TIOCGPGRP: {
pid_t pgid = g_foreground_process_group.value_or((pid_t)next_thread_id()); 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); if (!MemoryManager::copy_to_user_typed((pid_t*)arg, &pgid)) return err(EFAULT);
return 0; return 0;
} }

View File

@ -1,5 +1,8 @@
#pragma once #pragma once
#include "arch/Keyboard.h"
#include "fs/devices/DeviceRegistry.h" #include "fs/devices/DeviceRegistry.h"
#include <bits/termios.h>
#include <luna/Buffer.h>
class ConsoleDevice : public Device class ConsoleDevice : public Device
{ {
@ -11,7 +14,7 @@ class ConsoleDevice : public Device
Result<usize> write(const u8*, usize, usize) override; Result<usize> write(const u8*, usize, usize) override;
static void did_press_key(char key); static void did_press_or_release_key(u8 scancode);
bool blocking() const override; bool blocking() const override;
@ -28,4 +31,24 @@ class ConsoleDevice : public Device
} }
virtual ~ConsoleDevice() = default; virtual ~ConsoleDevice() = default;
private:
struct termios m_settings;
mutable Buffer m_input_buffer;
Option<pid_t> m_foreground_process_group {};
Vector<u8> m_line_buffer;
mutable Keyboard::KeyboardState m_kb_state;
static Vector<SharedPtr<ConsoleDevice>> m_console_devices;
void process_key_event(u8 scancode);
mutable bool m_may_read_without_blocking { false };
inline bool is_canonical() const
{
return m_settings.c_lflag & ICANON;
}
Result<void> handle_background_process_group(bool can_succeed, int signo) const;
}; };

View File

@ -101,6 +101,10 @@ namespace TextConsole
if (should_scroll()) scroll(); if (should_scroll()) scroll();
break; break;
} }
case L'\t': {
wprint(L" ");
break;
}
case L'\r': g_x_position = 0; break; case L'\r': g_x_position = 0; break;
case L'\b': case L'\b':
if (g_x_position != 0) if (g_x_position != 0)
@ -210,11 +214,11 @@ namespace TextConsole
u16 rows() u16 rows()
{ {
return Framebuffer::height() / FONT_HEIGHT; return (u16)Framebuffer::height() / FONT_HEIGHT;
} }
u16 cols() u16 cols()
{ {
return Framebuffer::width() / FONT_WIDTH; return (u16)Framebuffer::width() / FONT_WIDTH;
} }
} }

View File

@ -3,7 +3,7 @@
#ifndef _BITS_FIXED_SIZE_TYPES_H #ifndef _BITS_FIXED_SIZE_TYPES_H
#define _BITS_FIXED_SIZE_TYPES_H #define _BITS_FIXED_SIZE_TYPES_H
#if !defined(_SYS_TYPES_H) && !defined(_BITS_SETJMP_TYPES_H) && !defined(_BITS_MAKEDEV_H) #if !defined(_SYS_TYPES_H) && !defined(_BITS_SETJMP_TYPES_H) && !defined(_BITS_MAKEDEV_H) && !defined(_BITS_TERMIOS_H)
#error "Never include bits/fixed-size-types.h, use the standard <stdint.h> header instead." #error "Never include bits/fixed-size-types.h, use the standard <stdint.h> header instead."
#endif #endif

View File

@ -3,25 +3,41 @@
#ifndef _BITS_TERMIOS_H #ifndef _BITS_TERMIOS_H
#define _BITS_TERMIOS_H #define _BITS_TERMIOS_H
#include <bits/fixed-size-types.h>
typedef long tcflag_t; typedef long tcflag_t;
typedef char cc_t;
#define NCCS 4
#define VEOF 0
#define VERASE 1
#define VINTR 2
#define VQUIT 3
// VERY incomplete termios structure. // VERY incomplete termios structure.
struct termios struct termios
{ {
tcflag_t c_lflag; tcflag_t c_lflag;
cc_t c_cc[NCCS];
}; };
struct winsize struct winsize
{ {
u16 ws_row; __u16_t ws_row;
u16 ws_col; __u16_t ws_col;
u16 ws_xpixel; __u16_t ws_xpixel;
u16 ws_ypixel; __u16_t ws_ypixel;
}; };
// Values for c_lflag. // Values for c_lflag.
#define ECHO 1 #define ECHO 1
#define TOSTOP 2 #define TOSTOP 2
#define ECHOCTL 4
#define ISIG 8
#define ICANON 16
#define ECHOE 32
#define NOFLSH 64
// termios ioctl() requests. // termios ioctl() requests.
#define TCGETS 0 #define TCGETS 0

View File

@ -18,6 +18,7 @@
#define _POSIX_CHOWN_RESTRICTED 200112L #define _POSIX_CHOWN_RESTRICTED 200112L
#define _POSIX_SHELL 200112L #define _POSIX_SHELL 200112L
#define _POSIX_VDISABLE (-2)
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"