432 lines
10 KiB
C++
432 lines
10 KiB
C++
#include <BAN/Math.h>
|
|
#include <BAN/StringView.h>
|
|
#include <BAN/Vector.h>
|
|
#include <kernel/CPUID.h>
|
|
#include <kernel/font.h>
|
|
#include <kernel/Input.h>
|
|
#include <kernel/IO.h>
|
|
#include <kernel/PIT.h>
|
|
#include <kernel/RTC.h>
|
|
#include <kernel/Serial.h>
|
|
#include <kernel/Shell.h>
|
|
#include <kernel/TTY.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#define TTY_PRINT(...) Formatter::print([this](char c) { m_tty->PutChar(c); }, __VA_ARGS__)
|
|
#define TTY_PRINTLN(...) Formatter::println([this](char c) { m_tty->PutChar(c); }, __VA_ARGS__)
|
|
|
|
namespace Kernel
|
|
{
|
|
using namespace BAN;
|
|
|
|
static auto s_default_prompt = "\\[\e[32m\\]user\\[\e[m\\]# "_sv;
|
|
|
|
static uint8_t s_pointer[] {
|
|
________,
|
|
________,
|
|
________,
|
|
________,
|
|
________,
|
|
X_______,
|
|
XX______,
|
|
XXX_____,
|
|
XXXX____,
|
|
XXXXX___,
|
|
XXXXXX__,
|
|
XXXXXXX_,
|
|
XXXXXXXX,
|
|
XXX_____,
|
|
XX______,
|
|
X_______,
|
|
};
|
|
|
|
Shell::Shell(TTY* tty)
|
|
: m_tty(tty)
|
|
{
|
|
Input::register_key_event_callback({ &Shell::KeyEventCallback, this });
|
|
Input::register_mouse_move_event_callback({ &Shell::MouseMoveEventCallback, this });
|
|
SetPrompt(s_default_prompt);
|
|
MUST(m_buffer.PushBack(""_sv));
|
|
}
|
|
|
|
void Shell::SetPrompt(StringView prompt)
|
|
{
|
|
m_prompt_length = 0;
|
|
m_prompt = String();
|
|
|
|
bool skipping = false;
|
|
for (size_t i = 0; i < prompt.Size(); i++)
|
|
{
|
|
if (i < prompt.Size() - 1 && prompt[i] == '\\')
|
|
{
|
|
if (prompt[i + 1] == '[')
|
|
skipping = true;
|
|
if (prompt[i + 1] == ']')
|
|
skipping = false;
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
MUST(m_prompt.PushBack(prompt[i]));
|
|
if (!skipping)
|
|
m_prompt_length++;
|
|
}
|
|
}
|
|
|
|
void Shell::Run()
|
|
{
|
|
TTY_PRINT("{}", m_prompt);
|
|
for (;;)
|
|
{
|
|
asm volatile("hlt");
|
|
Input::update();
|
|
}
|
|
}
|
|
|
|
Vector<String> Shell::ParseArguments(StringView command) const
|
|
{
|
|
Vector<String> result;
|
|
|
|
while (!command.Empty())
|
|
{
|
|
while (!command.Empty() && isspace(command.Front()))
|
|
command = command.Substring(1);
|
|
|
|
if (command.Empty())
|
|
break;
|
|
|
|
MUST(result.PushBack(""_sv));
|
|
|
|
char quoted = '\0';
|
|
bool escape = false;
|
|
while (!command.Empty())
|
|
{
|
|
char ch = command.Front();
|
|
switch (ch)
|
|
{
|
|
case '"':
|
|
case '\'':
|
|
if (!quoted)
|
|
quoted = ch;
|
|
else if (ch == quoted)
|
|
quoted = '\0';
|
|
else
|
|
goto default_case;
|
|
break;
|
|
case '\\':
|
|
if (escape)
|
|
goto default_case;
|
|
escape = true;
|
|
break;
|
|
default:
|
|
default_case:
|
|
if (isspace(ch) && !quoted && !escape)
|
|
goto argument_done;
|
|
if (quoted && escape)
|
|
{
|
|
switch (ch)
|
|
{
|
|
case 'f': MUST(result.Back().PushBack('\f')); break;
|
|
case 'n': MUST(result.Back().PushBack('\n')); break;
|
|
case 'r': MUST(result.Back().PushBack('\r')); break;
|
|
case 't': MUST(result.Back().PushBack('\t')); break;
|
|
case 'v': MUST(result.Back().PushBack('\v')); break;
|
|
case '"': MUST(result.Back().PushBack('"')); break;
|
|
case '\'': MUST(result.Back().PushBack('\'')); break;
|
|
case '\\': MUST(result.Back().PushBack('\\')); break;
|
|
default:
|
|
char buffer[3] { '\\', ch, '\0' };
|
|
MUST(result.Back().Append(buffer));
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MUST(result.Back().PushBack(ch));
|
|
}
|
|
escape = false;
|
|
break;
|
|
}
|
|
command = command.Substring(1);
|
|
}
|
|
argument_done:
|
|
continue;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Shell::ProcessCommand(const Vector<String>& arguments)
|
|
{
|
|
if (arguments.Empty())
|
|
{
|
|
|
|
}
|
|
else if (arguments.Front() == "date")
|
|
{
|
|
if (arguments.Size() != 1)
|
|
{
|
|
TTY_PRINTLN("'date' does not support command line arguments");
|
|
return;
|
|
}
|
|
auto time = RTC::GetCurrentTime();
|
|
TTY_PRINTLN("{}", time);
|
|
}
|
|
else if (arguments.Front() == "echo")
|
|
{
|
|
if (arguments.Size() > 1)
|
|
{
|
|
TTY_PRINT("{}", arguments[1]);
|
|
for (size_t i = 2; i < arguments.Size(); i++)
|
|
TTY_PRINT(" {}", arguments[i]);
|
|
}
|
|
TTY_PRINTLN("");
|
|
}
|
|
else if (arguments.Front() == "clear")
|
|
{
|
|
if (arguments.Size() != 1)
|
|
{
|
|
TTY_PRINTLN("'clear' does not support command line arguments");
|
|
return;
|
|
}
|
|
m_tty->Clear();
|
|
m_tty->SetCursorPosition(0, 0);
|
|
}
|
|
else if (arguments.Front() == "time")
|
|
{
|
|
auto new_args = arguments;
|
|
new_args.Remove(0);
|
|
auto start = PIT::ms_since_boot();
|
|
ProcessCommand(new_args);
|
|
auto duration = PIT::ms_since_boot() - start;
|
|
TTY_PRINTLN("took {} ms", duration);
|
|
}
|
|
else if (arguments.Front() == "memory")
|
|
{
|
|
if (arguments.Size() != 1)
|
|
{
|
|
TTY_PRINTLN("'memory' does not support command line arguments");
|
|
return;
|
|
}
|
|
kmalloc_dump_info();
|
|
}
|
|
else if (arguments.Front() == "cpuinfo")
|
|
{
|
|
if (arguments.Size() != 1)
|
|
{
|
|
TTY_PRINTLN("'cpuinfo' does not support command line arguments");
|
|
return;
|
|
}
|
|
|
|
uint32_t ecx, edx;
|
|
auto vendor = CPUID::GetVendor();
|
|
CPUID::GetFeatures(ecx, edx);
|
|
|
|
TTY_PRINTLN("Vendor: '{}'", vendor);
|
|
TTY_PRINTLN("64-bit: {}", CPUID::Is64Bit());
|
|
bool first = true;
|
|
for (int i = 0; i < 32; i++)
|
|
if (ecx & ((uint32_t)1 << i))
|
|
TTY_PRINT("{}{}", first ? (first = false, "") : ", ", CPUID::FeatStringECX((uint32_t)1 << i));
|
|
for (int i = 0; i < 32; i++)
|
|
if (edx & ((uint32_t)1 << i))
|
|
TTY_PRINT("{}{}", first ? (first = false, "") : ", ", CPUID::FeatStringEDX((uint32_t)1 << i));
|
|
if (!first)
|
|
TTY_PRINTLN("");
|
|
}
|
|
else if (arguments.Front() == "random")
|
|
{
|
|
if (arguments.Size() != 1)
|
|
{
|
|
TTY_PRINTLN("'random' does not support command line arguments");
|
|
return;
|
|
}
|
|
uint32_t ecx, edx;
|
|
CPUID::GetFeatures(ecx, edx);
|
|
if (!(ecx & CPUID::Features::ECX_RDRND))
|
|
{
|
|
TTY_PRINTLN("cpu does not support RDRAND instruction");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
uint32_t random;
|
|
asm volatile("rdrand %0" : "=r"(random));
|
|
TTY_PRINTLN(" 0x{8H}", random);
|
|
}
|
|
}
|
|
else if (arguments.Front() == "reboot")
|
|
{
|
|
if (arguments.Size() != 1)
|
|
{
|
|
TTY_PRINTLN("'reboot' does not support command line arguments");
|
|
return;
|
|
}
|
|
uint8_t good = 0x02;
|
|
while (good & 0x02)
|
|
good = IO::inb(0x64);
|
|
IO::outb(0x64, 0xFE);
|
|
asm volatile("cli; hlt");
|
|
}
|
|
else
|
|
{
|
|
TTY_PRINTLN("unrecognized command '{}'", arguments.Front());
|
|
}
|
|
|
|
}
|
|
|
|
void Shell::ReRenderBuffer() const
|
|
{
|
|
TTY_PRINT("\e[{}G{}\e[K", m_prompt_length + 1, m_buffer[m_cursor_pos.line]);
|
|
}
|
|
|
|
static uint32_t GetLastLength(StringView sv)
|
|
{
|
|
if (sv.Size() >= 2 && ((uint8_t)sv[sv.Size() - 2] >> 5) == 0b110) return 2;
|
|
if (sv.Size() >= 3 && ((uint8_t)sv[sv.Size() - 3] >> 4) == 0b1110) return 3;
|
|
if (sv.Size() >= 4 && ((uint8_t)sv[sv.Size() - 4] >> 3) == 0b11110) return 4;
|
|
return Math::min<uint32_t>(sv.Size(), 1);
|
|
}
|
|
|
|
static uint32_t GetNextLength(StringView sv)
|
|
{
|
|
if (sv.Size() >= 2 && ((uint8_t)sv[0] >> 5) == 0b110) return 2;
|
|
if (sv.Size() >= 3 && ((uint8_t)sv[0] >> 4) == 0b1110) return 3;
|
|
if (sv.Size() >= 4 && ((uint8_t)sv[0] >> 3) == 0b11110) return 4;
|
|
return Math::min<uint32_t>(sv.Size(), 1);
|
|
}
|
|
|
|
static uint32_t GetUnicodeCharacterCount(StringView sv)
|
|
{
|
|
uint32_t len = 0;
|
|
for (uint32_t i = 0; i < sv.Size(); i++)
|
|
{
|
|
uint8_t ch = sv[i];
|
|
if ((ch >> 5) == 0b110) i += 1;
|
|
if ((ch >> 4) == 0b1110) i += 2;
|
|
if ((ch >> 3) == 0b11110) i += 3;
|
|
len++;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
void Shell::KeyEventCallback(Input::KeyEvent event)
|
|
{
|
|
if (!event.pressed)
|
|
return;
|
|
|
|
String& current_buffer = m_buffer[m_cursor_pos.line];
|
|
|
|
switch (event.key)
|
|
{
|
|
case Input::Key::Backspace:
|
|
if (m_cursor_pos.col > 0)
|
|
{
|
|
TTY_PRINT("\e[D{} ", current_buffer.SV().Substring(m_cursor_pos.index));
|
|
|
|
uint32_t len = GetLastLength(current_buffer.SV().Substring(0, m_cursor_pos.index));
|
|
m_cursor_pos.index -= len;
|
|
current_buffer.Erase(m_cursor_pos.index, len);
|
|
m_cursor_pos.col--;
|
|
}
|
|
break;
|
|
|
|
case Input::Key::Enter:
|
|
case Input::Key::NumpadEnter:
|
|
{
|
|
TTY_PRINTLN("");
|
|
auto arguments = ParseArguments(current_buffer.SV());
|
|
if (!arguments.Empty())
|
|
{
|
|
ProcessCommand(arguments);
|
|
MUST(m_old_buffer.PushBack(current_buffer));
|
|
m_buffer = m_old_buffer;
|
|
MUST(m_buffer.PushBack(""_sv));
|
|
MUST(m_buffer.Back().Reserve(128));
|
|
m_cursor_pos.line = m_buffer.Size() - 1;
|
|
}
|
|
m_cursor_pos.col = 0;
|
|
m_cursor_pos.index = 0;
|
|
TTY_PRINT("{}", m_prompt);
|
|
break;
|
|
}
|
|
|
|
case Input::Key::Escape:
|
|
TTY_PRINTLN("time since boot {} ms", PIT::ms_since_boot());
|
|
break;
|
|
|
|
case Input::Key::Tab:
|
|
break;
|
|
|
|
case Input::Key::Left:
|
|
if (m_cursor_pos.index > 0)
|
|
{
|
|
uint32_t len = GetLastLength(current_buffer.SV().Substring(0, m_cursor_pos.index));
|
|
m_cursor_pos.index -= len;
|
|
m_cursor_pos.col--;
|
|
}
|
|
break;
|
|
|
|
case Input::Key::Right:
|
|
if (m_cursor_pos.index < current_buffer.Size())
|
|
{
|
|
uint32_t len = GetNextLength(current_buffer.SV().Substring(m_cursor_pos.index));
|
|
m_cursor_pos.index += len;
|
|
m_cursor_pos.col++;
|
|
}
|
|
break;
|
|
|
|
case Input::Key::Up:
|
|
if (m_cursor_pos.line > 0)
|
|
{
|
|
const auto& new_buffer = m_buffer[m_cursor_pos.line - 1];
|
|
m_cursor_pos.line--;
|
|
m_cursor_pos.index = new_buffer.Size();
|
|
m_cursor_pos.col = GetUnicodeCharacterCount(new_buffer);
|
|
ReRenderBuffer();
|
|
}
|
|
break;
|
|
|
|
case Input::Key::Down:
|
|
if (m_cursor_pos.line < m_buffer.Size() - 1)
|
|
{
|
|
const auto& new_buffer = m_buffer[m_cursor_pos.line + 1];
|
|
m_cursor_pos.line++;
|
|
m_cursor_pos.index = new_buffer.Size();
|
|
m_cursor_pos.col = GetUnicodeCharacterCount(new_buffer);
|
|
ReRenderBuffer();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
const char* utf8 = Input::key_event_to_utf8(event);
|
|
if (utf8)
|
|
{
|
|
TTY_PRINT("{}{}", utf8, current_buffer.SV().Substring(m_cursor_pos.index));
|
|
MUST(current_buffer.Insert(utf8, m_cursor_pos.index));
|
|
m_cursor_pos.index += strlen(utf8);
|
|
m_cursor_pos.col++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
TTY_PRINT("\e[{}G", m_prompt_length + m_cursor_pos.col + 1);
|
|
|
|
if (m_mouse_pos.exists)
|
|
VESA::PutBitmapAt(s_pointer, m_mouse_pos.x, m_mouse_pos.y, VESA::Color::BRIGHT_WHITE);
|
|
}
|
|
|
|
void Shell::MouseMoveEventCallback(Input::MouseMoveEvent event)
|
|
{
|
|
m_mouse_pos.exists = true;
|
|
m_tty->RenderFromBuffer(m_mouse_pos.x, m_mouse_pos.y);
|
|
m_mouse_pos.x = Math::clamp<int32_t>(m_mouse_pos.x + event.dx, 0, m_tty->Width() - 1);
|
|
m_mouse_pos.y = Math::clamp<int32_t>(m_mouse_pos.y - event.dy, 0, m_tty->Height() - 1);
|
|
VESA::PutBitmapAt(s_pointer, m_mouse_pos.x, m_mouse_pos.y, VESA::Color::BRIGHT_WHITE);
|
|
}
|
|
|
|
} |