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();
if (maybe_cmd.has_error())
{
if (maybe_cmd.error() == EINTR) continue;
if (maybe_cmd.error() == EINTR)
{
os::println("");
continue;
}
return maybe_cmd.release_error();
}
@ -150,6 +154,8 @@ Result<int> luna_main(int argc, char** argv)
{
int sig = WTERMSIG(status);
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
{
enum Modifiers
struct KeyboardState
{
LeftControl = 1,
LeftAlt = 2,
bool ignore_next { false };
bool left_shift { false };
bool right_shift { false };
bool left_control { false };
bool capslock { false };
};
Option<char> decode_scancode(u8 scancode);
int modifiers();
Option<char> decode_scancode_tty(u8 scancode, KeyboardState& state);
}

View File

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

View File

@ -1,4 +1,5 @@
#include "arch/Keyboard.h"
#include <luna/CType.h>
// 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;
}
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[] = {
'\0',
'\1', // escape
@ -117,28 +113,26 @@ constexpr char shifted_key_table[] = {
'.', // keypad .
};
static bool is_shifted()
static bool is_shifted(const Keyboard::KeyboardState& state)
{
if (g_capslock) return !(g_left_shift || g_right_shift);
return g_left_shift || g_right_shift;
if (state.capslock) return !(state.left_shift || state.right_shift);
return state.left_shift || state.right_shift;
}
int g_modifiers = 0;
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 {};
}
// FIXME: Support extended scancodes.
if (scancode == EXTENDED_KEY_CODE)
{
g_ignore_next = true;
state.ignore_next = true;
return {};
}
@ -146,35 +140,27 @@ namespace Keyboard
if (scancode == LEFT_SHIFT)
{
g_left_shift = !released;
state.left_shift = !released;
return {};
}
if (scancode == RIGHT_SHIFT)
{
g_right_shift = !released;
state.right_shift = !released;
return {};
}
if (scancode == CAPS_LOCK)
{
if (!released) g_capslock = !g_capslock;
return {};
}
if (scancode == LEFT_ALT)
{
if (released) g_modifiers &= ~LeftAlt;
else
g_modifiers |= LeftAlt;
if (!released) state.capslock = !state.capslock;
return {};
}
if (scancode == LEFT_CONTROL)
{
if (released) g_modifiers &= ~LeftControl;
if (released) state.left_control = false;
else
g_modifiers |= LeftControl;
state.left_control = true;
return {};
}
@ -182,12 +168,20 @@ namespace Keyboard
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];
}
int modifiers()
{
return g_modifiers;
}
}

View File

@ -1,36 +1,34 @@
#include "fs/devices/ConsoleDevice.h"
#include "Log.h"
#include "arch/Keyboard.h"
#include "fs/devices/DeviceRegistry.h"
#include "memory/MemoryManager.h"
#include "thread/Scheduler.h"
#include "video/TextConsole.h"
#include <bits/ioctl-defs.h>
#include <bits/termios.h>
#include <luna/Buffer.h>
#include <luna/CString.h>
#include <luna/CType.h>
#include <luna/Units.h>
#include <luna/Vector.h>
static Buffer g_console_input;
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;
Vector<SharedPtr<ConsoleDevice>> ConsoleDevice::m_console_devices;
Result<void> ConsoleDevice::create()
{
auto device = (SharedPtr<Device>)TRY(make_shared<ConsoleDevice>());
g_foreground_process_group = {};
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);
}
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();
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));
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;
}
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);
return length;
@ -74,78 +72,124 @@ Result<usize> ConsoleDevice::write(const u8* buf, usize, usize length)
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_key(char key)
void ConsoleDevice::did_press_or_release_key(u8 scancode)
{
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;
}
// 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())
if (key == m_settings.c_cc[VEOF])
{
Scheduler::for_each_in_process_group(g_foreground_process_group.value(), [](Thread* thread) {
thread->send_signal(SIGINT);
return true;
});
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;
}
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();
return;
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 (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());
kinfoln("Free memory: %s", to_dynamic_unit(MemoryManager::free()).release_value().chars());
kinfoln("Used memory: %s", to_dynamic_unit(MemoryManager::used()).release_value().chars());
kinfoln("Reserved memory: %s", to_dynamic_unit(MemoryManager::reserved()).release_value().chars());
return;
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);
}
}
if (key == 'h' && (Keyboard::modifiers() == (Keyboard::LeftAlt | Keyboard::LeftControl)))
{
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);
else
TextConsole::putchar(key);
}
Result<u64> ConsoleDevice::ioctl(int request, void* arg)
@ -153,27 +197,13 @@ Result<u64> ConsoleDevice::ioctl(int request, void* arg)
switch (request)
{
case TCGETS: {
struct termios tc
{
.c_lflag = 0
};
if (g_echo) tc.c_lflag |= ECHO;
if (g_stop_background_output) tc.c_lflag |= TOSTOP;
return MemoryManager::copy_to_user_typed((struct termios*)arg, &tc) ? 0 : err(EFAULT);
return MemoryManager::copy_to_user_typed((struct termios*)arg, &m_settings) ? 0 : err(EFAULT);
}
case TCSETS: {
TRY(handle_background_process_group(true, SIGTTOU));
struct termios tc;
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;
if (!MemoryManager::copy_from_user_typed((const struct termios*)arg, &m_settings)) return err(EFAULT);
return 0;
}
@ -190,11 +220,11 @@ Result<u64> ConsoleDevice::ioctl(int request, void* arg)
});
if (!pgid_exists) return err(EPERM);
g_foreground_process_group = pgid;
m_foreground_process_group = pgid;
return 0;
}
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);
return 0;
}

View File

@ -1,5 +1,8 @@
#pragma once
#include "arch/Keyboard.h"
#include "fs/devices/DeviceRegistry.h"
#include <bits/termios.h>
#include <luna/Buffer.h>
class ConsoleDevice : public Device
{
@ -11,7 +14,7 @@ class ConsoleDevice : public Device
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;
@ -28,4 +31,24 @@ class ConsoleDevice : public Device
}
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();
break;
}
case L'\t': {
wprint(L" ");
break;
}
case L'\r': g_x_position = 0; break;
case L'\b':
if (g_x_position != 0)
@ -210,11 +214,11 @@ namespace TextConsole
u16 rows()
{
return Framebuffer::height() / FONT_HEIGHT;
return (u16)Framebuffer::height() / FONT_HEIGHT;
}
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
#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."
#endif

View File

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

View File

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