From 6d8043767ed3b7441ff7f2db4ea7ae47937cdad6 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 15 Jan 2019 04:30:55 +0100 Subject: [PATCH] Start working on a graphical Terminal program. --- Kernel/init.cpp | 1 + Kernel/sync.sh | 1 + Terminal/Makefile | 33 +++ Terminal/Terminal.cpp | 446 +++++++++++++++++++++++++++++++++++++ Terminal/Terminal.h | 70 ++++++ Terminal/main.cpp | 48 ++++ Userland/guitest.cpp | 3 +- Widgets/GraphicsBitmap.cpp | 3 - 8 files changed, 601 insertions(+), 4 deletions(-) create mode 100644 Terminal/Makefile create mode 100644 Terminal/Terminal.cpp create mode 100644 Terminal/Terminal.h create mode 100644 Terminal/main.cpp diff --git a/Kernel/init.cpp b/Kernel/init.cpp index 50763fd5a79..4d2e83a79bf 100644 --- a/Kernel/init.cpp +++ b/Kernel/init.cpp @@ -98,6 +98,7 @@ static void init_stage2() int error; //Process::create_user_process("/bin/sh", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0); + Process::create_user_process("/bin/Terminal", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0); #ifdef SPAWN_GUI_TEST_APP Process::create_user_process("/bin/guitest", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0); #endif diff --git a/Kernel/sync.sh b/Kernel/sync.sh index 5051382e055..d72dca30c6b 100755 --- a/Kernel/sync.sh +++ b/Kernel/sync.sh @@ -33,6 +33,7 @@ cp -v ../Userland/touch mnt/bin/touch cp -v ../Userland/sync mnt/bin/sync cp -v ../Userland/more mnt/bin/more cp -v ../Userland/guitest mnt/bin/guitest +cp -v ../Terminal/Terminal mnt/bin/Terminal sh sync-local.sh cp -v kernel.map mnt/ ln -s dir_a mnt/dir_cur diff --git a/Terminal/Makefile b/Terminal/Makefile new file mode 100644 index 00000000000..40e66f47fec --- /dev/null +++ b/Terminal/Makefile @@ -0,0 +1,33 @@ +OBJS = \ + Terminal.o \ + main.o + +APP = Terminal + +ARCH_FLAGS = +STANDARD_FLAGS = -std=c++17 -nostdinc++ -nostdlib -nostdinc +USERLAND_FLAGS = -ffreestanding -fno-stack-protector -fno-ident +WARNING_FLAGS = -Wextra -Wall -Wundef -Wcast-qual -Wwrite-strings +FLAVOR_FLAGS = -march=i386 -mregparm=3 -m32 -fno-exceptions -fno-rtti -fmerge-all-constants -fno-unroll-loops -fno-pie -fno-pic +OPTIMIZATION_FLAGS = -Oz -fno-asynchronous-unwind-tables +INCLUDE_FLAGS = -I.. -I. -I../LibC + +DEFINES = -DSERENITY -DSANITIZE_PTRS -DUSERLAND + +CXXFLAGS = $(WARNING_FLAGS) $(OPTIMIZATION_FLAGS) $(USERLAND_FLAGS) $(FLAVOR_FLAGS) $(ARCH_FLAGS) $(STANDARD_FLAGS) $(INCLUDE_FLAGS) $(DEFINES) +CXX = clang +LD = ld +AR = ar +LDFLAGS = -static --strip-debug -melf_i386 --build-id=none -z norelro -z now -e _start --gc-sections + +all: $(APP) + +$(APP): $(OBJS) + $(LD) -o $(APP) $(LDFLAGS) $(OBJS) ../LibC/LibC.a + +.cpp.o: + @echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $< + +clean: + @echo "CLEAN"; rm -f $(APPS) $(OBJS) + diff --git a/Terminal/Terminal.cpp b/Terminal/Terminal.cpp new file mode 100644 index 00000000000..2aebda19f7b --- /dev/null +++ b/Terminal/Terminal.cpp @@ -0,0 +1,446 @@ +#include "Terminal.h" +#include +#include +#include +#include +#include +#include + +void Terminal::create_window() +{ + auto& font = Font::defaultFont(); + + m_pixel_width = m_columns * font.glyphWidth(); + m_pixel_height = m_rows * font.glyphHeight(); + + GUI_CreateWindowParameters params; + params.rect = { { 300, 300 }, { m_pixel_width, m_pixel_height } }; + params.background_color = 0x000000; + strcpy(params.title, "Terminal"); + m_window_id = gui_create_window(¶ms); + ASSERT(m_window_id > 0); + if (m_window_id < 0) { + perror("gui_create_window"); + exit(1); + } + + GUI_WindowBackingStoreInfo info; + int rc = gui_get_window_backing_store(m_window_id, &info); + if (rc < 0) { + perror("gui_get_window_backing_store"); + exit(1); + } + + m_backing = GraphicsBitmap::create_wrapper(info.size, info.pixels); + dbgprintf("(Terminal:%d) window backing %ux%u @ %p\n", getpid(), info.size.width, info.size.height, info.pixels); + +} + +Terminal::Terminal() +{ + set_size(80, 25); + m_horizontal_tabs = static_cast(malloc(columns())); + for (unsigned i = 0; i < columns(); ++i) + m_horizontal_tabs[i] = (i % 8) == 0; + // Rightmost column is always last tab on line. + m_horizontal_tabs[columns() - 1] = 1; + + m_buffer = (byte*)malloc(rows() * columns() * 2); + word* line_mem = reinterpret_cast(m_buffer); + for (word i = 0; i < rows() * columns(); ++i) + line_mem[i] = 0x0720; + + inject_string_at(2, 2, "I am text inside the Terminal buffer."); +} + +void Terminal::inject_string_at(word row, word column, const String& string) +{ + for (size_t i = 0; i < string.length(); ++i) { + put_character_at(row, column + i, string[i]); + } +} + +Terminal::~Terminal() +{ + kfree(m_horizontal_tabs); + m_horizontal_tabs = nullptr; +} + +void Terminal::clear() +{ + word* linemem = (word*)m_buffer; + for (word i = 0; i < rows() * columns(); ++i) + linemem[i] = 0x0720; + set_cursor(0, 0); +} + +inline bool is_valid_parameter_character(byte ch) +{ + return ch >= 0x30 && ch <= 0x3f; +} + +inline bool is_valid_intermediate_character(byte ch) +{ + return ch >= 0x20 && ch <= 0x2f; +} + +inline bool is_valid_final_character(byte ch) +{ + return ch >= 0x40 && ch <= 0x7e; +} + +unsigned parseUInt(const String& str, bool& ok) +{ + unsigned value = 0; + for (size_t i = 0; i < str.length(); ++i) { + if (str[i] < '0' || str[i] > '9') { + ok = false; + return 0; + } + value = value * 10; + value += str[i] - '0'; + } + ok = true; + return value; +} + +enum class VGAColor : byte { + Black = 0, + Blue, + Green, + Cyan, + Red, + Magenta, + Brown, + LightGray, + DarkGray, + BrightBlue, + BrightGreen, + BrightCyan, + BrightRed, + BrightMagenta, + Yellow, + White, +}; + +enum class ANSIColor : byte { + Black = 0, + Red, + Green, + Brown, + Blue, + Magenta, + Cyan, + LightGray, + DarkGray, + BrightRed, + BrightGreen, + Yellow, + BrightBlue, + BrightMagenta, + BrightCyan, + White, +}; + +static inline VGAColor ansi_color_to_vga(ANSIColor color) +{ + switch (color) { + case ANSIColor::Black: return VGAColor::Black; + case ANSIColor::Red: return VGAColor::Red; + case ANSIColor::Brown: return VGAColor::Brown; + case ANSIColor::Blue: return VGAColor::Blue; + case ANSIColor::Magenta: return VGAColor::Magenta; + case ANSIColor::Green: return VGAColor::Green; + case ANSIColor::Cyan: return VGAColor::Cyan; + case ANSIColor::LightGray: return VGAColor::LightGray; + case ANSIColor::DarkGray: return VGAColor::DarkGray; + case ANSIColor::BrightRed: return VGAColor::BrightRed; + case ANSIColor::BrightGreen: return VGAColor::BrightGreen; + case ANSIColor::Yellow: return VGAColor::Yellow; + case ANSIColor::BrightBlue: return VGAColor::BrightBlue; + case ANSIColor::BrightMagenta: return VGAColor::BrightMagenta; + case ANSIColor::BrightCyan: return VGAColor::BrightCyan; + case ANSIColor::White: return VGAColor::White; + } + ASSERT_NOT_REACHED(); + return VGAColor::LightGray; +} + +static inline byte ansi_color_to_vga(byte color) +{ + return (byte)ansi_color_to_vga((ANSIColor)color); +} + +void Terminal::escape$m(const Vector& params) +{ + for (auto param : params) { + switch (param) { + case 0: + // Reset + m_current_attribute = 0x07; + break; + case 1: + // Bold + m_current_attribute |= 8; + break; + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: + // Foreground color + m_current_attribute &= ~0x7; + m_current_attribute |= ansi_color_to_vga(param - 30); + break; + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: + // Background color + m_current_attribute &= ~0x70; + m_current_attribute |= ansi_color_to_vga(param - 30) << 8; + break; + } + } +} + +void Terminal::escape$s(const Vector&) +{ + m_saved_cursor_row = m_cursor_row; + m_saved_cursor_column = m_cursor_column; +} + +void Terminal::escape$u(const Vector&) +{ + set_cursor(m_saved_cursor_row, m_saved_cursor_column); +} + +void Terminal::escape$H(const Vector& params) +{ + unsigned row = 1; + unsigned col = 1; + if (params.size() >= 1) + row = params[0]; + if (params.size() >= 2) + col = params[1]; + set_cursor(row - 1, col - 1); +} + +void Terminal::escape$A(const Vector& params) +{ + int num = 1; + if (params.size() >= 1) + num = params[0]; + int new_row = (int)m_cursor_row - num; + if (new_row < 0) + new_row = 0; + set_cursor(new_row, m_cursor_column); +} + +void Terminal::escape$D(const Vector& params) +{ + int num = 1; + if (params.size() >= 1) + num = params[0]; + int new_column = (int)m_cursor_column - num; + if (new_column < 0) + new_column = 0; + set_cursor(m_cursor_row, new_column); +} + +void Terminal::escape$J(const Vector& params) +{ + int mode = 0; + if (params.size() >= 1) + mode = params[0]; + switch (mode) { + case 0: + // FIXME: Clear from cursor to end of screen. + notImplemented(); + break; + case 1: + // FIXME: Clear from cursor to beginning of screen. + notImplemented(); + break; + case 2: + clear(); + break; + case 3: + // FIXME: [3J should also clear the scrollback buffer. + clear(); + break; + } +} + +void Terminal::execute_escape_sequence(byte final) +{ + auto paramparts = String((const char*)m_parameters.data(), m_parameters.size()).split(';'); + Vector params; + for (auto& parampart : paramparts) { + bool ok; + unsigned value = parseUInt(parampart, ok); + if (!ok) { + // FIXME: Should we do something else? + return; + } + params.append(value); + } + switch (final) { + case 'A': escape$A(params); break; + case 'D': escape$D(params); break; + case 'H': escape$H(params); break; + case 'J': escape$J(params); break; + case 'm': escape$m(params); break; + case 's': escape$s(params); break; + case 'u': escape$u(params); break; + default: break; + } + + m_parameters.clear(); + m_intermediates.clear(); +} + +void Terminal::scroll_up() +{ + if (m_cursor_row == (rows() - 1)) { + memcpy(m_buffer, m_buffer + 160, 160 * 24); + word* linemem = (word*)&m_buffer[24 * 160]; + for (word i = 0; i < columns(); ++i) + linemem[i] = 0x0720; + } else { + ++m_cursor_row; + } + m_cursor_column = 0; +} + +void Terminal::set_cursor(unsigned row, unsigned column) +{ + ASSERT(row < rows()); + ASSERT(column < columns()); + m_cursor_row = row; + m_cursor_column = column; +} + +void Terminal::put_character_at(unsigned row, unsigned column, byte ch) +{ + ASSERT(row < rows()); + ASSERT(column < columns()); + word cur = (row * 160) + (column * 2); + m_buffer[cur] = ch; + m_buffer[cur + 1] = m_current_attribute; +} + +void Terminal::on_char(byte ch) +{ + switch (m_escape_state) { + case ExpectBracket: + if (ch == '[') + m_escape_state = ExpectParameter; + else + m_escape_state = Normal; + return; + case ExpectParameter: + if (is_valid_parameter_character(ch)) { + m_parameters.append(ch); + return; + } + m_escape_state = ExpectIntermediate; + // fall through + case ExpectIntermediate: + if (is_valid_intermediate_character(ch)) { + m_intermediates.append(ch); + return; + } + m_escape_state = ExpectFinal; + // fall through + case ExpectFinal: + if (is_valid_final_character(ch)) { + m_escape_state = Normal; + execute_escape_sequence(ch); + return; + } + m_escape_state = Normal; + return; + case Normal: + break; + } + + switch (ch) { + case '\0': + return; + case '\033': + m_escape_state = ExpectBracket; + return; + case 8: // Backspace + if (m_cursor_column) { + set_cursor(m_cursor_row, m_cursor_column - 1); + put_character_at(m_cursor_row, m_cursor_column, ' '); + return; + } + break; + case '\a': + // FIXME: Bell! + return; + case '\t': { + for (unsigned i = m_cursor_column; i < columns(); ++i) { + if (m_horizontal_tabs[i]) { + set_cursor(m_cursor_row, i); + return; + } + } + return; + } + case '\n': + scroll_up(); + set_cursor(m_cursor_row, m_cursor_column); + return; + } + + put_character_at(m_cursor_row, m_cursor_column, ch); + + ++m_cursor_column; + if (m_cursor_column >= columns()) + scroll_up(); + set_cursor(m_cursor_row, m_cursor_column); +} + +void Terminal::set_size(word columns, word rows) +{ + m_columns = columns; + m_rows = rows; +} + +void Terminal::paint() +{ + Rect rect { 0, 0, m_pixel_width, m_pixel_height }; + Font& font = Font::defaultFont(); + Painter painter(*m_backing); + painter.fill_rect(rect, Color::Black); + + char buf[2] = { 0, 0 }; + for (unsigned row = 0; row < m_rows; ++row) { + int y = row * font.glyphHeight(); + for (unsigned column = 0; column < m_columns; ++column) { + int x = column * font.glyphWidth(); + //buf[0] = at(row, column).character; + buf[0] = m_buffer[(row * 160) + (column * 2)]; + painter.draw_text({ x + 2, y + 2, font.glyphWidth(), font.glyphHeight() }, buf, Painter::TextAlignment::TopLeft, Color(0xa0, 0xa0, 0xa0)); + } + } + + if (m_belling) + painter.draw_rect(rect, Color::Red); + + int rc = gui_invalidate_window(m_window_id); + if (rc < 0) { + perror("gui_invalidate_window"); + exit(1); + } +} diff --git a/Terminal/Terminal.h b/Terminal/Terminal.h new file mode 100644 index 00000000000..8f4ef4aa4b8 --- /dev/null +++ b/Terminal/Terminal.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include + + +class Terminal { +public: + Terminal(); + ~Terminal(); + + void create_window(); + void paint(); + void on_char(byte); + +private: + void scroll_up(); + void set_cursor(unsigned row, unsigned column); + void put_character_at(unsigned row, unsigned column, byte ch); + + void escape$A(const Vector&); + void escape$D(const Vector&); + void escape$H(const Vector&); + void escape$J(const Vector&); + void escape$m(const Vector&); + void escape$s(const Vector&); + void escape$u(const Vector&); + + void clear(); + + void set_size(word columns, word rows); + word columns() const { return m_columns; } + word rows() const { return m_rows; } + + void inject_string_at(word row, word column, const String&); + + byte* m_buffer { nullptr }; + + word m_columns { 0 }; + word m_rows { 0 }; + + byte m_cursor_row { 0 }; + byte m_cursor_column { 0 }; + byte m_saved_cursor_row { 0 }; + byte m_saved_cursor_column { 0 }; + byte m_current_attribute { 0x07 }; + + void execute_escape_sequence(byte final); + + enum EscapeState { + Normal, + ExpectBracket, + ExpectParameter, + ExpectIntermediate, + ExpectFinal, + }; + EscapeState m_escape_state { Normal }; + Vector m_parameters; + Vector m_intermediates; + byte* m_horizontal_tabs { nullptr }; + bool m_belling { false }; + + int m_window_id { 0 }; + RetainPtr m_backing; + + int m_pixel_width { 0 }; + int m_pixel_height { 0 }; +}; diff --git a/Terminal/main.cpp b/Terminal/main.cpp new file mode 100644 index 00000000000..ed769205ba5 --- /dev/null +++ b/Terminal/main.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Terminal.h" + +static void paint(GraphicsBitmap& bitmap, int width, int height); + +int main(int argc, char** argv) +{ + int fd = open("/dev/gui_events", O_RDONLY); + if (fd < 0) { + perror("open"); + return 1; + } + + Terminal terminal; + terminal.create_window(); + terminal.paint(); + + for (;;) { + GUI_Event event; + ssize_t nread = read(fd, &event, sizeof(event)); + if (nread < 0) { + perror("read"); + return 1; + } + assert(nread == sizeof(event)); + dbgprintf("(Terminal:%d) ", getpid()); + switch (event.type) { + case GUI_Event::Type::Paint: dbgprintf("WID=%x Paint [%d,%d %dx%d]\n", event.window_id, event.paint.rect.location.x, event.paint.rect.location.y, event.paint.rect.size.width, event.paint.rect.size.height); break; + case GUI_Event::Type::MouseDown: dbgprintf("WID=%x MouseDown %d,%d\n", event.window_id, event.mouse.position.x, event.mouse.position.y); break; + case GUI_Event::Type::MouseUp: dbgprintf("WID=%x MouseUp %d,%d\n", event.window_id, event.mouse.position.x, event.mouse.position.y); break; + case GUI_Event::Type::MouseMove: dbgprintf("WID=%x MouseMove %d,%d\n", event.window_id, event.mouse.position.x, event.mouse.position.y); break; + } + + if (event.type == GUI_Event::Type::MouseDown) + terminal.paint(); + } + return 0; +} diff --git a/Userland/guitest.cpp b/Userland/guitest.cpp index 7b8a8e22376..07d5de91888 100644 --- a/Userland/guitest.cpp +++ b/Userland/guitest.cpp @@ -15,7 +15,7 @@ static void paint(GraphicsBitmap& bitmap, int width, int height); int main(int argc, char** argv) { GUI_CreateWindowParameters wparams; - wparams.rect = { { 200, 200 }, { 300, 200 } }; + wparams.rect = { { 100, 100 }, { 120, 120 } }; wparams.background_color = 0xffc0c0; strcpy(wparams.title, "GUI test app"); int window_id = gui_create_window(&wparams); @@ -56,6 +56,7 @@ int main(int argc, char** argv) perror("read"); return 1; } + dbgprintf("(%d) ", getpid()); assert(nread == sizeof(event)); switch (event.type) { case GUI_Event::Type::Paint: dbgprintf("WID=%x Paint [%d,%d %dx%d]\n", event.window_id, event.paint.rect.location.x, event.paint.rect.location.y, event.paint.rect.size.width, event.paint.rect.size.height); break; diff --git a/Widgets/GraphicsBitmap.cpp b/Widgets/GraphicsBitmap.cpp index 75bbff0f568..6fc98447f99 100644 --- a/Widgets/GraphicsBitmap.cpp +++ b/Widgets/GraphicsBitmap.cpp @@ -27,9 +27,6 @@ GraphicsBitmap::GraphicsBitmap(Process& process, const Size& size) auto& server = EventLoop::main().server_process(); ProcessInspectionHandle composer_handle(server); m_server_region = server.allocate_region_with_vmo(LinearAddress(), size_in_bytes, move(vmo), 0, "GraphicsBitmap (shared)", true, true); - - process.dumpRegions(); - server.dumpRegions(); } m_data = (RGBA32*)m_server_region->linearAddress.asPtr(); }