mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 02:47:34 +00:00
Move apps into a top-level Applications/ directory.
This commit is contained in:
parent
29f2a22d34
commit
2cf1dd5b6f
27 changed files with 30 additions and 26 deletions
3
Applications/Clock/.gitignore
vendored
Normal file
3
Applications/Clock/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.o
|
||||
*.d
|
||||
Clock
|
37
Applications/Clock/ClockWidget.cpp
Normal file
37
Applications/Clock/ClockWidget.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <SharedGraphics/Painter.h>
|
||||
#include "ClockWidget.h"
|
||||
|
||||
ClockWidget::ClockWidget(GWidget* parent)
|
||||
: GWidget(parent)
|
||||
{
|
||||
set_relative_rect({ 0, 0, 100, 40 });
|
||||
start_timer(300);
|
||||
}
|
||||
|
||||
ClockWidget::~ClockWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void ClockWidget::paint_event(GPaintEvent&)
|
||||
{
|
||||
auto now = time(nullptr);
|
||||
auto& tm = *localtime(&now);
|
||||
|
||||
char timeBuf[128];
|
||||
sprintf(timeBuf, "%02u:%02u:%02u", tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
|
||||
Painter painter(*this);
|
||||
painter.fill_rect(rect(), Color::LightGray);
|
||||
painter.draw_text(rect(), timeBuf, Painter::TextAlignment::Center, Color::Black);
|
||||
}
|
||||
|
||||
void ClockWidget::timer_event(GTimerEvent&)
|
||||
{
|
||||
auto now = time(nullptr);
|
||||
if (now == m_last_time)
|
||||
return;
|
||||
m_last_time = now;
|
||||
update();
|
||||
}
|
16
Applications/Clock/ClockWidget.h
Normal file
16
Applications/Clock/ClockWidget.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibGUI/GWidget.h>
|
||||
|
||||
class ClockWidget final : public GWidget {
|
||||
public:
|
||||
explicit ClockWidget(GWidget* parent = nullptr);
|
||||
virtual ~ClockWidget() override;
|
||||
|
||||
private:
|
||||
virtual void paint_event(GPaintEvent&) override;
|
||||
virtual void timer_event(GTimerEvent&) override;
|
||||
|
||||
time_t m_last_time { 0 };
|
||||
};
|
||||
|
35
Applications/Clock/Makefile
Normal file
35
Applications/Clock/Makefile
Normal file
|
@ -0,0 +1,35 @@
|
|||
OBJS = \
|
||||
ClockWidget.o \
|
||||
main.o
|
||||
|
||||
APP = Clock
|
||||
|
||||
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 -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 = -MMD -MP $(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 -e _start --gc-sections
|
||||
|
||||
all: $(APP)
|
||||
|
||||
$(APP): $(OBJS)
|
||||
$(LD) -o $(APP) $(LDFLAGS) $(OBJS) ../LibGUI/LibGUI.a ../LibC/LibC.a
|
||||
|
||||
.cpp.o:
|
||||
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
|
||||
-include $(OBJS:%.o=%.d)
|
||||
|
||||
clean:
|
||||
@echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d
|
||||
|
22
Applications/Clock/main.cpp
Normal file
22
Applications/Clock/main.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include <LibGUI/GEventLoop.h>
|
||||
#include <LibGUI/GWindow.h>
|
||||
#include "ClockWidget.h"
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
GEventLoop loop;
|
||||
|
||||
auto* window = new GWindow;
|
||||
window->set_title("Clock");
|
||||
window->set_rect({ 600, 100, 100, 40 });
|
||||
window->set_should_exit_app_on_close(true);
|
||||
|
||||
auto* clock_widget = new ClockWidget;
|
||||
clock_widget->set_relative_rect({ 0, 0, 100, 40 });
|
||||
window->set_main_widget(clock_widget);
|
||||
|
||||
window->show();
|
||||
return loop.exec();
|
||||
}
|
||||
|
||||
|
3
Applications/FileManager/.gitignore
vendored
Normal file
3
Applications/FileManager/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.o
|
||||
*.d
|
||||
FileManager
|
145
Applications/FileManager/DirectoryView.cpp
Normal file
145
Applications/FileManager/DirectoryView.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <SharedGraphics/GraphicsBitmap.h>
|
||||
#include <SharedGraphics/Painter.h>
|
||||
#include <LibGUI/GScrollBar.h>
|
||||
#include <AK/FileSystemPath.h>
|
||||
#include "DirectoryView.h"
|
||||
|
||||
DirectoryView::DirectoryView(GWidget* parent)
|
||||
: GWidget(parent)
|
||||
{
|
||||
m_directory_icon = GraphicsBitmap::load_from_file("/res/icons/folder16.rgb", { 16, 16 });
|
||||
m_file_icon = GraphicsBitmap::load_from_file("/res/icons/file16.rgb", { 16, 16 });
|
||||
m_symlink_icon = GraphicsBitmap::load_from_file("/res/icons/link16.rgb", { 16, 16 });
|
||||
|
||||
m_scrollbar = new GScrollBar(this);
|
||||
m_scrollbar->set_step(4);
|
||||
m_scrollbar->set_big_step(30);
|
||||
m_scrollbar->on_change = [this] (int) {
|
||||
update();
|
||||
};
|
||||
}
|
||||
|
||||
DirectoryView::~DirectoryView()
|
||||
{
|
||||
}
|
||||
|
||||
void DirectoryView::resize_event(GResizeEvent& event)
|
||||
{
|
||||
m_scrollbar->set_relative_rect(event.size().width() - 16, 0, 16, event.size().height());
|
||||
}
|
||||
|
||||
void DirectoryView::open(const String& path)
|
||||
{
|
||||
if (m_path == path)
|
||||
return;
|
||||
m_path = path;
|
||||
reload();
|
||||
if (on_path_change)
|
||||
on_path_change(m_path);
|
||||
update();
|
||||
}
|
||||
|
||||
void DirectoryView::reload()
|
||||
{
|
||||
DIR* dirp = opendir(m_path.characters());
|
||||
if (!dirp) {
|
||||
perror("opendir");
|
||||
exit(1);
|
||||
}
|
||||
m_directories.clear();
|
||||
m_files.clear();
|
||||
while (auto* de = readdir(dirp)) {
|
||||
Entry entry;
|
||||
entry.name = de->d_name;
|
||||
struct stat st;
|
||||
int rc = lstat(String::format("%s/%s", m_path.characters(), de->d_name).characters(), &st);
|
||||
if (rc < 0) {
|
||||
perror("lstat");
|
||||
continue;
|
||||
}
|
||||
entry.size = st.st_size;
|
||||
entry.mode = st.st_mode;
|
||||
auto& entries = S_ISDIR(st.st_mode) ? m_directories : m_files;
|
||||
entries.append(move(entry));
|
||||
}
|
||||
closedir(dirp);
|
||||
int excess_height = max(0, (item_count() * item_height()) - height());
|
||||
m_scrollbar->set_range(0, excess_height);
|
||||
}
|
||||
|
||||
const GraphicsBitmap& DirectoryView::icon_for(const Entry& entry) const
|
||||
{
|
||||
if (S_ISDIR(entry.mode))
|
||||
return *m_directory_icon;
|
||||
if (S_ISLNK(entry.mode))
|
||||
return *m_symlink_icon;
|
||||
return *m_file_icon;
|
||||
}
|
||||
|
||||
static String pretty_byte_size(size_t size)
|
||||
{
|
||||
return String::format("%u", size);
|
||||
}
|
||||
|
||||
bool DirectoryView::should_show_size_for(const Entry& entry) const
|
||||
{
|
||||
return S_ISREG(entry.mode);
|
||||
}
|
||||
|
||||
Rect DirectoryView::row_rect(int item_index) const
|
||||
{
|
||||
return { 0, item_index * item_height(), width(), item_height() };
|
||||
}
|
||||
|
||||
void DirectoryView::mousedown_event(GMouseEvent& event)
|
||||
{
|
||||
if (event.button() == GMouseButton::Left) {
|
||||
for (int i = 0; i < item_count(); ++i) {
|
||||
if (!row_rect(i).contains(event.position()))
|
||||
continue;
|
||||
auto& entry = this->entry(i);
|
||||
if (entry.is_directory()) {
|
||||
FileSystemPath new_path(String::format("%s/%s", m_path.characters(), entry.name.characters()));
|
||||
open(new_path.string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryView::paint_event(GPaintEvent&)
|
||||
{
|
||||
Painter painter(*this);
|
||||
|
||||
painter.translate(0, -m_scrollbar->value());
|
||||
|
||||
int horizontal_padding = 5;
|
||||
int icon_size = 16;
|
||||
int painted_item_index = 0;
|
||||
|
||||
auto process_entries = [&] (const Vector<Entry>& entries) {
|
||||
for (size_t i = 0; i < entries.size(); ++i, ++painted_item_index) {
|
||||
auto& entry = entries[i];
|
||||
int y = painted_item_index * item_height();
|
||||
Rect icon_rect(horizontal_padding, y, icon_size, item_height());
|
||||
Rect name_rect(icon_rect.right() + horizontal_padding, y, 100, item_height());
|
||||
Rect size_rect(name_rect.right() + horizontal_padding, y, 64, item_height());
|
||||
painter.fill_rect(row_rect(painted_item_index), i % 2 ? Color::LightGray : Color::White);
|
||||
painter.blit_with_alpha(icon_rect.location(), icon_for(entry), { 0, 0, icon_size, icon_size });
|
||||
painter.draw_text(name_rect, entry.name, Painter::TextAlignment::CenterLeft, Color::Black);
|
||||
if (should_show_size_for(entry))
|
||||
painter.draw_text(size_rect, pretty_byte_size(entry.size), Painter::TextAlignment::CenterRight, Color::Black);
|
||||
}
|
||||
};
|
||||
|
||||
process_entries(m_directories);
|
||||
process_entries(m_files);
|
||||
|
||||
Rect unpainted_rect(0, painted_item_index * item_height(), width(), height());
|
||||
unpainted_rect.intersect(rect());
|
||||
painter.fill_rect(unpainted_rect, Color::White);
|
||||
}
|
55
Applications/FileManager/DirectoryView.h
Normal file
55
Applications/FileManager/DirectoryView.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibGUI/GWidget.h>
|
||||
#include <AK/Function.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
class GScrollBar;
|
||||
|
||||
class DirectoryView final : public GWidget {
|
||||
public:
|
||||
DirectoryView(GWidget* parent);
|
||||
virtual ~DirectoryView() override;
|
||||
|
||||
void open(const String& path);
|
||||
void reload();
|
||||
|
||||
Function<void(const String&)> on_path_change;
|
||||
|
||||
int item_height() const { return 16; }
|
||||
int item_count() const { return m_directories.size() + m_files.size(); }
|
||||
|
||||
private:
|
||||
virtual void paint_event(GPaintEvent&) override;
|
||||
virtual void resize_event(GResizeEvent&) override;
|
||||
virtual void mousedown_event(GMouseEvent&) override;
|
||||
|
||||
Rect row_rect(int item_index) const;
|
||||
|
||||
struct Entry {
|
||||
String name;
|
||||
size_t size { 0 };
|
||||
mode_t mode { 0 };
|
||||
|
||||
bool is_directory() const { return S_ISDIR(mode); }
|
||||
};
|
||||
|
||||
const Entry& entry(size_t index) const
|
||||
{
|
||||
if (index < m_directories.size())
|
||||
return m_directories[index];
|
||||
return m_files[index - m_directories.size()];
|
||||
}
|
||||
const GraphicsBitmap& icon_for(const Entry&) const;
|
||||
bool should_show_size_for(const Entry&) const;
|
||||
|
||||
Vector<Entry> m_files;
|
||||
Vector<Entry> m_directories;
|
||||
|
||||
String m_path;
|
||||
RetainPtr<GraphicsBitmap> m_directory_icon;
|
||||
RetainPtr<GraphicsBitmap> m_file_icon;
|
||||
RetainPtr<GraphicsBitmap> m_symlink_icon;
|
||||
|
||||
GScrollBar* m_scrollbar { nullptr };
|
||||
};
|
35
Applications/FileManager/Makefile
Normal file
35
Applications/FileManager/Makefile
Normal file
|
@ -0,0 +1,35 @@
|
|||
OBJS = \
|
||||
DirectoryView.o \
|
||||
main.o
|
||||
|
||||
APP = FileManager
|
||||
|
||||
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 -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 = -MMD -MP $(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 -e _start --gc-sections
|
||||
|
||||
all: $(APP)
|
||||
|
||||
$(APP): $(OBJS)
|
||||
$(LD) -o $(APP) $(LDFLAGS) $(OBJS) ../LibGUI/LibGUI.a ../LibC/LibC.a
|
||||
|
||||
.cpp.o:
|
||||
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
|
||||
-include $(OBJS:%.o=%.d)
|
||||
|
||||
clean:
|
||||
@echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d
|
||||
|
47
Applications/FileManager/main.cpp
Normal file
47
Applications/FileManager/main.cpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
#include <SharedGraphics/GraphicsBitmap.h>
|
||||
#include <LibGUI/GWindow.h>
|
||||
#include <LibGUI/GWidget.h>
|
||||
#include <LibGUI/GButton.h>
|
||||
#include <LibGUI/GListBox.h>
|
||||
#include <LibGUI/GEventLoop.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include "DirectoryView.h"
|
||||
|
||||
static GWindow* make_window();
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
GEventLoop loop;
|
||||
|
||||
auto* window = make_window();
|
||||
window->set_should_exit_app_on_close(true);
|
||||
window->show();
|
||||
|
||||
return loop.exec();
|
||||
}
|
||||
|
||||
GWindow* make_window()
|
||||
{
|
||||
auto* window = new GWindow;
|
||||
window->set_title("FileManager");
|
||||
window->set_rect(20, 200, 240, 300);
|
||||
|
||||
auto* widget = new GWidget;
|
||||
window->set_main_widget(widget);
|
||||
|
||||
auto* directory_view = new DirectoryView(widget);
|
||||
directory_view->set_relative_rect({ 0, 0, 240, 300 });
|
||||
|
||||
directory_view->on_path_change = [window] (const String& new_path) {
|
||||
window->set_title(String::format("FileManager: %s", new_path.characters()));
|
||||
};
|
||||
|
||||
directory_view->open("/");
|
||||
|
||||
return window;
|
||||
}
|
||||
|
3
Applications/FontEditor/.gitignore
vendored
Normal file
3
Applications/FontEditor/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.o
|
||||
*.d
|
||||
FontEditor
|
247
Applications/FontEditor/FontEditor.cpp
Normal file
247
Applications/FontEditor/FontEditor.cpp
Normal file
|
@ -0,0 +1,247 @@
|
|||
#include "FontEditor.h"
|
||||
#include <SharedGraphics/Painter.h>
|
||||
#include <LibGUI/GButton.h>
|
||||
#include <LibGUI/GLabel.h>
|
||||
#include <LibGUI/GTextBox.h>
|
||||
|
||||
FontEditorWidget::FontEditorWidget(const String& path, RetainPtr<Font>&& edited_font, GWidget* parent)
|
||||
: GWidget(parent)
|
||||
, m_edited_font(move(edited_font))
|
||||
{
|
||||
if (path.is_empty())
|
||||
m_path = "/saved.font";
|
||||
else
|
||||
m_path = path;
|
||||
|
||||
m_glyph_map_widget = new GlyphMapWidget(*m_edited_font, this);
|
||||
m_glyph_map_widget->move_to({ 90, 5 });
|
||||
|
||||
m_glyph_editor_widget = new GlyphEditorWidget(*m_edited_font, this);
|
||||
m_glyph_editor_widget->move_to({ 5, 5 });
|
||||
|
||||
m_name_textbox = new GTextBox(this);
|
||||
m_name_textbox->set_relative_rect({ 5, 135, 100, 20 });
|
||||
m_name_textbox->set_text(m_edited_font->name());
|
||||
m_name_textbox->on_change = [this] (GTextBox&) {
|
||||
m_edited_font->set_name(m_name_textbox->text());
|
||||
};
|
||||
|
||||
auto* save_button = new GButton(this);
|
||||
save_button->set_caption("Save");
|
||||
save_button->set_relative_rect({ 5, 170, 100, 20 });
|
||||
save_button->on_click = [this] (GButton&) {
|
||||
dbgprintf("write to file: '%s'\n", m_path.characters());
|
||||
m_edited_font->write_to_file(m_path);
|
||||
};
|
||||
|
||||
auto* quit_button = new GButton(this);
|
||||
quit_button->set_caption("Quit");
|
||||
quit_button->set_relative_rect({ 110, 170, 100, 20 });
|
||||
quit_button->on_click = [] (GButton&) {
|
||||
exit(0);
|
||||
};
|
||||
|
||||
auto* info_label = new GLabel(this);
|
||||
info_label->set_relative_rect({ 5, 110, 100, 20 });
|
||||
|
||||
auto* demo_label_1 = new GLabel(this);
|
||||
demo_label_1->set_font(m_edited_font.copy_ref());
|
||||
demo_label_1->set_text("quick fox jumps nightly above wizard.");
|
||||
demo_label_1->set_relative_rect({ 110, 120, 300, 20 });
|
||||
|
||||
auto* demo_label_2 = new GLabel(this);
|
||||
demo_label_2->set_font(m_edited_font.copy_ref());
|
||||
demo_label_2->set_text("QUICK FOX JUMPS NIGHTLY ABOVE WIZARD!");
|
||||
demo_label_2->set_relative_rect({ 110, 140, 300, 20 });
|
||||
|
||||
m_glyph_editor_widget->on_glyph_altered = [this, demo_label_1, demo_label_2] {
|
||||
m_glyph_map_widget->update();
|
||||
demo_label_1->update();
|
||||
demo_label_2->update();
|
||||
};
|
||||
|
||||
m_glyph_map_widget->on_glyph_selected = [this, info_label] (byte glyph) {
|
||||
m_glyph_editor_widget->set_glyph(glyph);
|
||||
info_label->set_text(String::format("0x%b (%c)", glyph, glyph));
|
||||
};
|
||||
|
||||
m_glyph_map_widget->set_selected_glyph('A');
|
||||
}
|
||||
|
||||
FontEditorWidget::~FontEditorWidget()
|
||||
{
|
||||
}
|
||||
|
||||
GlyphMapWidget::GlyphMapWidget(Font& mutable_font, GWidget* parent)
|
||||
: GWidget(parent)
|
||||
, m_font(mutable_font)
|
||||
{
|
||||
set_relative_rect({ 0, 0, preferred_width(), preferred_height() });
|
||||
}
|
||||
|
||||
GlyphMapWidget::~GlyphMapWidget()
|
||||
{
|
||||
}
|
||||
|
||||
int GlyphMapWidget::preferred_width() const
|
||||
{
|
||||
return columns() * (font().glyph_width() + m_horizontal_spacing) + 2;
|
||||
}
|
||||
|
||||
int GlyphMapWidget::preferred_height() const
|
||||
{
|
||||
return rows() * (font().glyph_height() + m_vertical_spacing) + 2;
|
||||
}
|
||||
|
||||
void GlyphMapWidget::set_selected_glyph(byte glyph)
|
||||
{
|
||||
if (m_selected_glyph == glyph)
|
||||
return;
|
||||
m_selected_glyph = glyph;
|
||||
if (on_glyph_selected)
|
||||
on_glyph_selected(glyph);
|
||||
update();
|
||||
}
|
||||
|
||||
Rect GlyphMapWidget::get_outer_rect(byte glyph) const
|
||||
{
|
||||
int row = glyph / columns();
|
||||
int column = glyph % columns();
|
||||
return {
|
||||
column * (font().glyph_width() + m_horizontal_spacing) + 1,
|
||||
row * (font().glyph_height() + m_vertical_spacing) + 1,
|
||||
font().glyph_width() + m_horizontal_spacing,
|
||||
font().glyph_height() + m_horizontal_spacing
|
||||
};
|
||||
}
|
||||
|
||||
void GlyphMapWidget::paint_event(GPaintEvent&)
|
||||
{
|
||||
Painter painter(*this);
|
||||
painter.set_font(font());
|
||||
painter.fill_rect(rect(), Color::White);
|
||||
painter.draw_rect(rect(), Color::Black);
|
||||
|
||||
byte glyph = 0;
|
||||
|
||||
for (int row = 0; row < rows(); ++row) {
|
||||
for (int column = 0; column < columns(); ++column, ++glyph) {
|
||||
Rect outer_rect = get_outer_rect(glyph);
|
||||
Rect inner_rect(
|
||||
outer_rect.x() + m_horizontal_spacing / 2,
|
||||
outer_rect.y() + m_vertical_spacing / 2,
|
||||
font().glyph_width(),
|
||||
font().glyph_height()
|
||||
);
|
||||
if (glyph == m_selected_glyph) {
|
||||
painter.fill_rect(outer_rect, Color::Red);
|
||||
painter.draw_glyph(inner_rect.location(), glyph, Color::White);
|
||||
} else {
|
||||
painter.draw_glyph(inner_rect.location(), glyph, Color::Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_focused())
|
||||
painter.draw_focus_rect(rect());
|
||||
}
|
||||
|
||||
void GlyphMapWidget::mousedown_event(GMouseEvent& event)
|
||||
{
|
||||
// FIXME: This is a silly loop.
|
||||
for (unsigned glyph = 0; glyph < 256; ++glyph) {
|
||||
if (get_outer_rect(glyph).contains(event.position())) {
|
||||
set_selected_glyph(glyph);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GlyphEditorWidget::GlyphEditorWidget(Font& mutable_font, GWidget* parent)
|
||||
: GWidget(parent)
|
||||
, m_font(mutable_font)
|
||||
{
|
||||
set_relative_rect({ 0, 0, preferred_width(), preferred_height() });
|
||||
}
|
||||
|
||||
GlyphEditorWidget::~GlyphEditorWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void GlyphEditorWidget::set_glyph(byte glyph)
|
||||
{
|
||||
if (m_glyph == glyph)
|
||||
return;
|
||||
m_glyph = glyph;
|
||||
update();
|
||||
}
|
||||
|
||||
void GlyphEditorWidget::paint_event(GPaintEvent&)
|
||||
{
|
||||
Painter painter(*this);
|
||||
painter.fill_rect(rect(), Color::White);
|
||||
painter.draw_rect(rect(), Color::Black);
|
||||
|
||||
for (int y = 0; y < font().glyph_height(); ++y)
|
||||
painter.draw_line({ 0, y * m_scale }, { font().glyph_width() * m_scale, y * m_scale }, Color::Black);
|
||||
|
||||
for (int x = 0; x < font().glyph_width(); ++x)
|
||||
painter.draw_line({ x * m_scale, 0 }, { x * m_scale, font().glyph_height() * m_scale }, Color::Black);
|
||||
|
||||
painter.translate(1, 1);
|
||||
|
||||
auto bitmap = font().glyph_bitmap(m_glyph);
|
||||
|
||||
for (int y = 0; y < font().glyph_height(); ++y) {
|
||||
for (int x = 0; x < font().glyph_width(); ++x) {
|
||||
Rect rect { x * m_scale, y * m_scale, m_scale, m_scale };
|
||||
if (bitmap.bit_at(x, y))
|
||||
painter.fill_rect(rect, Color::Black);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_focused()) {
|
||||
painter.translate(-1, -1);
|
||||
painter.draw_focus_rect(rect());
|
||||
}
|
||||
}
|
||||
|
||||
void GlyphEditorWidget::mousedown_event(GMouseEvent& event)
|
||||
{
|
||||
draw_at_mouse(event);
|
||||
}
|
||||
|
||||
void GlyphEditorWidget::mousemove_event(GMouseEvent& event)
|
||||
{
|
||||
if (event.buttons() & (GMouseButton::Left | GMouseButton::Right))
|
||||
draw_at_mouse(event);
|
||||
}
|
||||
|
||||
void GlyphEditorWidget::draw_at_mouse(const GMouseEvent& event)
|
||||
{
|
||||
bool set = event.buttons() & GMouseButton::Left;
|
||||
bool unset = event.buttons() & GMouseButton::Right;
|
||||
if (!(set ^ unset))
|
||||
return;
|
||||
int x = (event.x() - 1) / m_scale;
|
||||
int y = (event.y() - 1) / m_scale;
|
||||
auto bitmap = font().glyph_bitmap(m_glyph);
|
||||
ASSERT((unsigned)x < bitmap.width());
|
||||
ASSERT((unsigned)y < bitmap.height());
|
||||
if (bitmap.bit_at(x, y) == set)
|
||||
return;
|
||||
bitmap.set_bit_at(x, y, set);
|
||||
if (on_glyph_altered)
|
||||
on_glyph_altered();
|
||||
update();
|
||||
}
|
||||
|
||||
int GlyphEditorWidget::preferred_width() const
|
||||
{
|
||||
return font().glyph_width() * m_scale + 1;
|
||||
}
|
||||
|
||||
int GlyphEditorWidget::preferred_height() const
|
||||
{
|
||||
return font().glyph_height() * m_scale + 1;
|
||||
}
|
85
Applications/FontEditor/FontEditor.h
Normal file
85
Applications/FontEditor/FontEditor.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibGUI/GWidget.h>
|
||||
#include <AK/Function.h>
|
||||
|
||||
class GlyphEditorWidget;
|
||||
class GlyphMapWidget;
|
||||
class GTextBox;
|
||||
|
||||
class FontEditorWidget final : public GWidget {
|
||||
public:
|
||||
FontEditorWidget(const String& path, RetainPtr<Font>&&, GWidget* parent = nullptr);
|
||||
virtual ~FontEditorWidget() override;
|
||||
|
||||
private:
|
||||
RetainPtr<Font> m_edited_font;
|
||||
|
||||
GlyphMapWidget* m_glyph_map_widget { nullptr };
|
||||
GlyphEditorWidget* m_glyph_editor_widget { nullptr };
|
||||
GTextBox* m_name_textbox { nullptr };
|
||||
|
||||
String m_path;
|
||||
};
|
||||
|
||||
class GlyphMapWidget final : public GWidget {
|
||||
public:
|
||||
GlyphMapWidget(Font&, GWidget* parent);
|
||||
virtual ~GlyphMapWidget() override;
|
||||
|
||||
byte selected_glyph() const { return m_selected_glyph; }
|
||||
void set_selected_glyph(byte);
|
||||
|
||||
int rows() const { return m_rows; }
|
||||
int columns() const { return 256 / m_rows; }
|
||||
|
||||
int preferred_width() const;
|
||||
int preferred_height() const;
|
||||
|
||||
Font& font() { return *m_font; }
|
||||
const Font& font() const { return *m_font; }
|
||||
|
||||
Function<void(byte)> on_glyph_selected;
|
||||
|
||||
private:
|
||||
virtual void paint_event(GPaintEvent&) override;
|
||||
virtual void mousedown_event(GMouseEvent&) override;
|
||||
virtual bool accepts_focus() const override { return true; }
|
||||
|
||||
Rect get_outer_rect(byte glyph) const;
|
||||
|
||||
RetainPtr<Font> m_font;
|
||||
int m_rows { 8 };
|
||||
int m_horizontal_spacing { 2 };
|
||||
int m_vertical_spacing { 2 };
|
||||
byte m_selected_glyph { 0 };
|
||||
};
|
||||
|
||||
class GlyphEditorWidget final : public GWidget {
|
||||
public:
|
||||
GlyphEditorWidget(Font&, GWidget* parent);
|
||||
virtual ~GlyphEditorWidget() override;
|
||||
|
||||
byte glyph() const { return m_glyph; }
|
||||
void set_glyph(byte);
|
||||
|
||||
int preferred_width() const;
|
||||
int preferred_height() const;
|
||||
|
||||
Font& font() { return *m_font; }
|
||||
const Font& font() const { return *m_font; }
|
||||
|
||||
Function<void()> on_glyph_altered;
|
||||
|
||||
private:
|
||||
virtual void paint_event(GPaintEvent&) override;
|
||||
virtual void mousedown_event(GMouseEvent&) override;
|
||||
virtual void mousemove_event(GMouseEvent&) override;
|
||||
virtual bool accepts_focus() const override { return true; }
|
||||
|
||||
void draw_at_mouse(const GMouseEvent&);
|
||||
|
||||
RetainPtr<Font> m_font;
|
||||
byte m_glyph { 0 };
|
||||
int m_scale { 10 };
|
||||
};
|
35
Applications/FontEditor/Makefile
Normal file
35
Applications/FontEditor/Makefile
Normal file
|
@ -0,0 +1,35 @@
|
|||
OBJS = \
|
||||
FontEditor.o \
|
||||
main.o
|
||||
|
||||
APP = FontEditor
|
||||
|
||||
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 -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 = -MMD -MP $(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 -e _start --gc-sections
|
||||
|
||||
all: $(APP)
|
||||
|
||||
$(APP): $(OBJS)
|
||||
$(LD) -o $(APP) $(LDFLAGS) $(OBJS) ../LibGUI/LibGUI.a ../LibC/LibC.a
|
||||
|
||||
.cpp.o:
|
||||
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
|
||||
-include $(OBJS:%.o=%.d)
|
||||
|
||||
clean:
|
||||
@echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d
|
||||
|
35
Applications/FontEditor/main.cpp
Normal file
35
Applications/FontEditor/main.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include "FontEditor.h"
|
||||
#include <LibGUI/GEventLoop.h>
|
||||
#include <LibGUI/GWindow.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
RetainPtr<Font> edited_font;
|
||||
String path;
|
||||
|
||||
if (argc == 2) {
|
||||
path = argv[1];
|
||||
edited_font = Font::load_from_file(path);
|
||||
if (!edited_font) {
|
||||
fprintf(stderr, "Couldn't load font: %s\n", path.characters());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (edited_font)
|
||||
edited_font = edited_font->clone();
|
||||
else
|
||||
edited_font = Font::default_font().clone();
|
||||
|
||||
GEventLoop loop;
|
||||
auto* window = new GWindow;
|
||||
window->set_title("FontEditor");
|
||||
window->set_rect({ 50, 50, 420, 200 });
|
||||
auto* font_editor = new FontEditorWidget(path, move(edited_font));
|
||||
font_editor->set_relative_rect({ 0, 0, 420, 200 });
|
||||
window->set_main_widget(font_editor);
|
||||
window->set_should_exit_app_on_close(true);
|
||||
window->show();
|
||||
return loop.exec();
|
||||
}
|
3
Applications/Launcher/.gitignore
vendored
Normal file
3
Applications/Launcher/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.o
|
||||
*.d
|
||||
Launcher
|
34
Applications/Launcher/Makefile
Normal file
34
Applications/Launcher/Makefile
Normal file
|
@ -0,0 +1,34 @@
|
|||
OBJS = \
|
||||
main.o
|
||||
|
||||
APP = Launcher
|
||||
|
||||
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 -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 = -MMD -MP $(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 -e _start --gc-sections
|
||||
|
||||
all: $(APP)
|
||||
|
||||
$(APP): $(OBJS)
|
||||
$(LD) -o $(APP) $(LDFLAGS) $(OBJS) ../LibGUI/LibGUI.a ../LibC/LibC.a
|
||||
|
||||
.cpp.o:
|
||||
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
|
||||
-include $(OBJS:%.o=%.d)
|
||||
|
||||
clean:
|
||||
@echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d
|
||||
|
80
Applications/Launcher/main.cpp
Normal file
80
Applications/Launcher/main.cpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
#include <SharedGraphics/GraphicsBitmap.h>
|
||||
#include <LibGUI/GWindow.h>
|
||||
#include <LibGUI/GWidget.h>
|
||||
#include <LibGUI/GButton.h>
|
||||
#include <LibGUI/GEventLoop.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
static GWindow* make_launcher_window();
|
||||
|
||||
void handle_sigchld(int)
|
||||
{
|
||||
dbgprintf("Launcher(%d) Got SIGCHLD\n", getpid());
|
||||
int pid = waitpid(-1, nullptr, 0);
|
||||
dbgprintf("Launcher(%d) waitpid() returned %d\n", getpid(), pid);
|
||||
ASSERT(pid > 0);
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
signal(SIGCHLD, handle_sigchld);
|
||||
|
||||
GEventLoop loop;
|
||||
|
||||
auto* launcher_window = make_launcher_window();
|
||||
launcher_window->set_should_exit_app_on_close(true);
|
||||
launcher_window->show();
|
||||
|
||||
return loop.exec();
|
||||
}
|
||||
|
||||
class LauncherButton final : public GButton {
|
||||
public:
|
||||
LauncherButton(const String& icon_path, const String& exec_path, GWidget* parent)
|
||||
: GButton(parent)
|
||||
, m_executable_path(exec_path)
|
||||
{
|
||||
set_icon(GraphicsBitmap::load_from_file(icon_path, { 32, 32 }));
|
||||
resize(50, 50);
|
||||
on_click = [this] (GButton&) {
|
||||
pid_t child_pid = fork();
|
||||
if (!child_pid) {
|
||||
int rc = execl(m_executable_path.characters(), m_executable_path.characters(), nullptr);
|
||||
if (rc < 0)
|
||||
perror("execl");
|
||||
}
|
||||
};
|
||||
}
|
||||
virtual ~LauncherButton() { }
|
||||
|
||||
private:
|
||||
String m_executable_path;
|
||||
};
|
||||
|
||||
GWindow* make_launcher_window()
|
||||
{
|
||||
auto* window = new GWindow;
|
||||
window->set_title("Launcher");
|
||||
window->set_rect(50, 50, 300, 60);
|
||||
|
||||
auto* widget = new GWidget;
|
||||
window->set_main_widget(widget);
|
||||
|
||||
auto* terminal_button = new LauncherButton("/res/icons/Terminal.rgb", "/bin/Terminal", widget);
|
||||
terminal_button->move_to(5, 5);
|
||||
|
||||
auto* font_editor_button = new LauncherButton("/res/icons/FontEditor.rgb", "/bin/FontEditor", widget);
|
||||
font_editor_button->move_to(60, 5);
|
||||
|
||||
auto* file_manager_button = new LauncherButton("/res/icons/FileManager.rgb", "/bin/FileManager", widget);
|
||||
file_manager_button->move_to(115, 5);
|
||||
|
||||
auto* guitest_button = new LauncherButton("/res/icons/generic.rgb", "/bin/guitest", widget);
|
||||
guitest_button->move_to(170, 5);
|
||||
|
||||
return window;
|
||||
}
|
3
Applications/Terminal/.gitignore
vendored
Normal file
3
Applications/Terminal/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.o
|
||||
*.d
|
||||
Terminal
|
35
Applications/Terminal/Makefile
Normal file
35
Applications/Terminal/Makefile
Normal file
|
@ -0,0 +1,35 @@
|
|||
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 -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 = -MMD -MP $(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 -e _start --gc-sections
|
||||
|
||||
all: $(APP)
|
||||
|
||||
$(APP): $(OBJS)
|
||||
$(LD) -o $(APP) $(LDFLAGS) $(OBJS) ../LibC/LibC.a
|
||||
|
||||
.cpp.o:
|
||||
@echo "CXX $<"; $(CXX) $(CXXFLAGS) -o $@ -c $<
|
||||
|
||||
-include $(OBJS:%.o=%.d)
|
||||
|
||||
clean:
|
||||
@echo "CLEAN"; rm -f $(APPS) $(OBJS) *.d
|
||||
|
748
Applications/Terminal/Terminal.cpp
Normal file
748
Applications/Terminal/Terminal.cpp
Normal file
|
@ -0,0 +1,748 @@
|
|||
#include "Terminal.h"
|
||||
#include "XtermColors.h"
|
||||
#include <AK/AKString.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <SharedGraphics/Font.h>
|
||||
#include <SharedGraphics/Painter.h>
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <LibC/stdlib.h>
|
||||
#include <LibC/unistd.h>
|
||||
#include <LibC/stdio.h>
|
||||
#include <LibC/gui.h>
|
||||
|
||||
//#define TERMINAL_DEBUG
|
||||
|
||||
void Terminal::create_window()
|
||||
{
|
||||
m_pixel_width = m_columns * font().glyph_width() + m_inset * 2;
|
||||
m_pixel_height = (m_rows * (font().glyph_height() + m_line_spacing)) + (m_inset * 2) - m_line_spacing;
|
||||
|
||||
GUI_WindowParameters 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);
|
||||
}
|
||||
|
||||
// NOTE: We never release the backing store.
|
||||
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);
|
||||
}
|
||||
|
||||
Terminal::Terminal()
|
||||
: m_font(Font::default_font())
|
||||
{
|
||||
m_line_height = font().glyph_height() + m_line_spacing;
|
||||
|
||||
set_size(80, 25);
|
||||
m_horizontal_tabs = static_cast<byte*>(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_lines = new Line*[rows()];
|
||||
for (size_t i = 0; i < rows(); ++i)
|
||||
m_lines[i] = new Line(columns());
|
||||
}
|
||||
|
||||
Terminal::Line::Line(word columns)
|
||||
: length(columns)
|
||||
{
|
||||
characters = new byte[length];
|
||||
attributes = new Attribute[length];
|
||||
did_paint = false;
|
||||
memset(characters, ' ', length);
|
||||
}
|
||||
|
||||
Terminal::Line::~Line()
|
||||
{
|
||||
delete [] characters;
|
||||
delete [] attributes;
|
||||
}
|
||||
|
||||
void Terminal::Line::clear(Attribute attribute)
|
||||
{
|
||||
if (dirty) {
|
||||
memset(characters, ' ', length);
|
||||
for (word i = 0 ; i < length; ++i)
|
||||
attributes[i] = attribute;
|
||||
return;
|
||||
}
|
||||
for (unsigned i = 0 ; i < length; ++i) {
|
||||
if (characters[i] != ' ')
|
||||
dirty = true;
|
||||
characters[i] = ' ';
|
||||
}
|
||||
for (unsigned i = 0 ; i < length; ++i) {
|
||||
if (attributes[i] != attribute)
|
||||
dirty = true;
|
||||
attributes[i] = attribute;
|
||||
}
|
||||
}
|
||||
|
||||
Terminal::~Terminal()
|
||||
{
|
||||
for (size_t i = 0; i < m_rows; ++i)
|
||||
delete m_lines[i];
|
||||
delete [] m_lines;
|
||||
free(m_horizontal_tabs);
|
||||
}
|
||||
|
||||
void Terminal::clear()
|
||||
{
|
||||
for (size_t i = 0; i < rows(); ++i)
|
||||
line(i).clear(m_current_attribute);
|
||||
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 parse_uint(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;
|
||||
}
|
||||
|
||||
static inline Color lookup_color(unsigned color)
|
||||
{
|
||||
return xterm_colors[color];
|
||||
}
|
||||
|
||||
void Terminal::escape$m(const Vector<unsigned>& params)
|
||||
{
|
||||
if (params.size() == 3 && params[1] == 5) {
|
||||
if (params[0] == 38) {
|
||||
m_current_attribute.foreground_color = params[2];
|
||||
return;
|
||||
} else if (params[0] == 48) {
|
||||
m_current_attribute.background_color = params[2];
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (auto param : params) {
|
||||
switch (param) {
|
||||
case 0:
|
||||
// Reset
|
||||
m_current_attribute.reset();
|
||||
break;
|
||||
case 1:
|
||||
// Bold
|
||||
//m_current_attribute.bold = true;
|
||||
break;
|
||||
case 30:
|
||||
case 31:
|
||||
case 32:
|
||||
case 33:
|
||||
case 34:
|
||||
case 35:
|
||||
case 36:
|
||||
case 37:
|
||||
// Foreground color
|
||||
m_current_attribute.foreground_color = param - 30;
|
||||
break;
|
||||
case 40:
|
||||
case 41:
|
||||
case 42:
|
||||
case 43:
|
||||
case 44:
|
||||
case 45:
|
||||
case 46:
|
||||
case 47:
|
||||
// Background color
|
||||
m_current_attribute.background_color = param - 40;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::escape$s(const Vector<unsigned>&)
|
||||
{
|
||||
m_saved_cursor_row = m_cursor_row;
|
||||
m_saved_cursor_column = m_cursor_column;
|
||||
}
|
||||
|
||||
void Terminal::escape$u(const Vector<unsigned>&)
|
||||
{
|
||||
set_cursor(m_saved_cursor_row, m_saved_cursor_column);
|
||||
}
|
||||
|
||||
void Terminal::escape$H(const Vector<unsigned>& 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<unsigned>& params)
|
||||
{
|
||||
int num = 1;
|
||||
if (params.size() >= 1)
|
||||
num = params[0];
|
||||
if (num == 0)
|
||||
num = 1;
|
||||
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$B(const Vector<unsigned>& params)
|
||||
{
|
||||
int num = 1;
|
||||
if (params.size() >= 1)
|
||||
num = params[0];
|
||||
if (num == 0)
|
||||
num = 1;
|
||||
int new_row = (int)m_cursor_row + num;
|
||||
if (new_row >= m_rows)
|
||||
new_row = m_rows - 1;
|
||||
set_cursor(new_row, m_cursor_column);
|
||||
}
|
||||
|
||||
void Terminal::escape$C(const Vector<unsigned>& params)
|
||||
{
|
||||
int num = 1;
|
||||
if (params.size() >= 1)
|
||||
num = params[0];
|
||||
if (num == 0)
|
||||
num = 1;
|
||||
int new_column = (int)m_cursor_column + num;
|
||||
if (new_column >= m_columns)
|
||||
new_column = m_columns - 1;
|
||||
set_cursor(m_cursor_row, new_column);
|
||||
}
|
||||
|
||||
void Terminal::escape$D(const Vector<unsigned>& params)
|
||||
{
|
||||
int num = 1;
|
||||
if (params.size() >= 1)
|
||||
num = params[0];
|
||||
if (num == 0)
|
||||
num = 1;
|
||||
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$K(const Vector<unsigned>& params)
|
||||
{
|
||||
int mode = 0;
|
||||
if (params.size() >= 1)
|
||||
mode = params[0];
|
||||
switch (mode) {
|
||||
case 0:
|
||||
// Clear from cursor to end of line.
|
||||
for (int i = m_cursor_column; i < m_columns; ++i) {
|
||||
put_character_at(m_cursor_row, i, ' ');
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// FIXME: Clear from cursor to beginning of screen.
|
||||
unimplemented_escape();
|
||||
break;
|
||||
case 2:
|
||||
unimplemented_escape();
|
||||
break;
|
||||
default:
|
||||
unimplemented_escape();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::escape$J(const Vector<unsigned>& params)
|
||||
{
|
||||
int mode = 0;
|
||||
if (params.size() >= 1)
|
||||
mode = params[0];
|
||||
switch (mode) {
|
||||
case 0:
|
||||
// Clear from cursor to end of screen.
|
||||
for (int i = m_cursor_column; i < m_columns; ++i) {
|
||||
put_character_at(m_cursor_row, i, ' ');
|
||||
}
|
||||
for (int row = m_cursor_row + 1; row < m_rows; ++row) {
|
||||
for (int column = 0; column < m_columns; ++column) {
|
||||
put_character_at(row, column, ' ');
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// FIXME: Clear from cursor to beginning of screen.
|
||||
unimplemented_escape();
|
||||
break;
|
||||
case 2:
|
||||
clear();
|
||||
break;
|
||||
case 3:
|
||||
// FIXME: <esc>[3J should also clear the scrollback buffer.
|
||||
clear();
|
||||
break;
|
||||
default:
|
||||
unimplemented_escape();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::escape$M(const Vector<unsigned>& params)
|
||||
{
|
||||
int count = 1;
|
||||
if (params.size() >= 1)
|
||||
count = params[0];
|
||||
|
||||
if (count == 1 && m_cursor_row == 0) {
|
||||
scroll_up();
|
||||
return;
|
||||
}
|
||||
|
||||
int max_count = m_rows - m_cursor_row;
|
||||
count = min(count, max_count);
|
||||
|
||||
dbgprintf("Delete %d line(s) starting from %d\n", count, m_cursor_row);
|
||||
// FIXME: Implement.
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void Terminal::execute_xterm_command()
|
||||
{
|
||||
m_final = '@';
|
||||
bool ok;
|
||||
unsigned value = parse_uint(String((const char*)m_xterm_param1.data(), m_xterm_param1.size()), ok);
|
||||
if (ok) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
set_window_title(String((const char*)m_xterm_param2.data(), m_xterm_param2.size()));
|
||||
break;
|
||||
default:
|
||||
unimplemented_xterm_escape();
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_xterm_param1.clear_with_capacity();
|
||||
m_xterm_param2.clear_with_capacity();
|
||||
}
|
||||
|
||||
void Terminal::execute_escape_sequence(byte final)
|
||||
{
|
||||
m_final = final;
|
||||
auto paramparts = String((const char*)m_parameters.data(), m_parameters.size()).split(';');
|
||||
Vector<unsigned> params;
|
||||
for (auto& parampart : paramparts) {
|
||||
bool ok;
|
||||
unsigned value = parse_uint(parampart, ok);
|
||||
if (!ok) {
|
||||
m_parameters.clear_with_capacity();
|
||||
m_intermediates.clear_with_capacity();
|
||||
// FIXME: Should we do something else?
|
||||
return;
|
||||
}
|
||||
params.append(value);
|
||||
}
|
||||
switch (final) {
|
||||
case 'A': escape$A(params); break;
|
||||
case 'B': escape$B(params); break;
|
||||
case 'C': escape$C(params); break;
|
||||
case 'D': escape$D(params); break;
|
||||
case 'H': escape$H(params); break;
|
||||
case 'J': escape$J(params); break;
|
||||
case 'K': escape$K(params); break;
|
||||
case 'M': escape$M(params); break;
|
||||
case 'm': escape$m(params); break;
|
||||
case 's': escape$s(params); break;
|
||||
case 'u': escape$u(params); break;
|
||||
default:
|
||||
dbgprintf("Terminal::execute_escape_sequence: Unhandled final '%c'\n", final);
|
||||
break;
|
||||
}
|
||||
|
||||
m_parameters.clear_with_capacity();
|
||||
m_intermediates.clear_with_capacity();
|
||||
}
|
||||
|
||||
void Terminal::newline()
|
||||
{
|
||||
word new_row = m_cursor_row;
|
||||
if (m_cursor_row == (rows() - 1)) {
|
||||
scroll_up();
|
||||
} else {
|
||||
++new_row;
|
||||
}
|
||||
set_cursor(new_row, 0);
|
||||
}
|
||||
|
||||
void Terminal::scroll_up()
|
||||
{
|
||||
// NOTE: We have to invalidate the cursor first.
|
||||
invalidate_cursor();
|
||||
delete m_lines[0];
|
||||
for (word row = 1; row < rows(); ++row)
|
||||
m_lines[row - 1] = m_lines[row];
|
||||
m_lines[m_rows - 1] = new Line(m_columns);
|
||||
++m_rows_to_scroll_backing_store;
|
||||
}
|
||||
|
||||
void Terminal::set_cursor(unsigned a_row, unsigned a_column)
|
||||
{
|
||||
unsigned row = min(a_row, m_rows - 1u);
|
||||
unsigned column = min(a_column, m_columns - 1u);
|
||||
if (row == m_cursor_row && column == m_cursor_column)
|
||||
return;
|
||||
ASSERT(row < rows());
|
||||
ASSERT(column < columns());
|
||||
invalidate_cursor();
|
||||
m_cursor_row = row;
|
||||
m_cursor_column = column;
|
||||
if (column != columns() - 1)
|
||||
m_stomp = false;
|
||||
invalidate_cursor();
|
||||
}
|
||||
|
||||
void Terminal::put_character_at(unsigned row, unsigned column, byte ch)
|
||||
{
|
||||
ASSERT(row < rows());
|
||||
ASSERT(column < columns());
|
||||
auto& line = this->line(row);
|
||||
if ((line.characters[column] == ch) && (line.attributes[column] == m_current_attribute))
|
||||
return;
|
||||
line.characters[column] = ch;
|
||||
line.attributes[column] = m_current_attribute;
|
||||
line.dirty = true;
|
||||
}
|
||||
|
||||
void Terminal::on_char(byte ch)
|
||||
{
|
||||
#ifdef TERMINAL_DEBUG
|
||||
dbgprintf("Terminal::on_char: %b (%c), fg=%u, bg=%u\n", ch, ch, m_current_attribute.foreground_color, m_current_attribute.background_color);
|
||||
#endif
|
||||
switch (m_escape_state) {
|
||||
case ExpectBracket:
|
||||
if (ch == '[')
|
||||
m_escape_state = ExpectParameter;
|
||||
else if (ch == ']')
|
||||
m_escape_state = ExpectXtermParameter1;
|
||||
else
|
||||
m_escape_state = Normal;
|
||||
return;
|
||||
case ExpectXtermParameter1:
|
||||
if (ch != ';') {
|
||||
m_xterm_param1.append(ch);
|
||||
return;
|
||||
}
|
||||
m_escape_state = ExpectXtermParameter2;
|
||||
return;
|
||||
case ExpectXtermParameter2:
|
||||
if (ch != '\007') {
|
||||
m_xterm_param2.append(ch);
|
||||
return;
|
||||
}
|
||||
m_escape_state = ExpectXtermFinal;
|
||||
// fall through
|
||||
case ExpectXtermFinal:
|
||||
m_escape_state = Normal;
|
||||
if (ch == '\007')
|
||||
execute_xterm_command();
|
||||
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;
|
||||
}
|
||||
return;
|
||||
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 '\r':
|
||||
set_cursor(m_cursor_row, 0);
|
||||
return;
|
||||
case '\n':
|
||||
newline();
|
||||
return;
|
||||
}
|
||||
|
||||
auto new_column = m_cursor_column + 1;
|
||||
if (new_column < columns()) {
|
||||
put_character_at(m_cursor_row, m_cursor_column, ch);
|
||||
set_cursor(m_cursor_row, new_column);
|
||||
} else {
|
||||
if (m_stomp) {
|
||||
m_stomp = false;
|
||||
newline();
|
||||
put_character_at(m_cursor_row, m_cursor_column, ch);
|
||||
set_cursor(m_cursor_row, 1);
|
||||
} else {
|
||||
// Curious: We wait once on the right-hand side
|
||||
m_stomp = true;
|
||||
put_character_at(m_cursor_row, m_cursor_column, ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::inject_string(const String& str)
|
||||
{
|
||||
for (size_t i = 0; i < str.length(); ++i)
|
||||
on_char(str[i]);
|
||||
}
|
||||
|
||||
void Terminal::unimplemented_escape()
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.appendf("((Unimplemented escape: %c", m_final);
|
||||
if (!m_parameters.is_empty()) {
|
||||
builder.append(" parameters:");
|
||||
for (size_t i = 0; i < m_parameters.size(); ++i)
|
||||
builder.append((char)m_parameters[i]);
|
||||
}
|
||||
if (!m_intermediates.is_empty()) {
|
||||
builder.append(" intermediates:");
|
||||
for (size_t i = 0; i < m_intermediates.size(); ++i)
|
||||
builder.append((char)m_intermediates[i]);
|
||||
}
|
||||
builder.append("))");
|
||||
inject_string(builder.to_string());
|
||||
}
|
||||
|
||||
void Terminal::unimplemented_xterm_escape()
|
||||
{
|
||||
auto message = String::format("((Unimplemented xterm escape: %c))\n", m_final);
|
||||
inject_string(message);
|
||||
}
|
||||
|
||||
void Terminal::set_size(word columns, word rows)
|
||||
{
|
||||
m_columns = columns;
|
||||
m_rows = rows;
|
||||
}
|
||||
|
||||
Rect Terminal::glyph_rect(word row, word column)
|
||||
{
|
||||
int y = row * m_line_height;
|
||||
int x = column * font().glyph_width();
|
||||
return { x + m_inset, y + m_inset, font().glyph_width(), font().glyph_height() };
|
||||
}
|
||||
|
||||
Rect Terminal::row_rect(word row)
|
||||
{
|
||||
int y = row * m_line_height;
|
||||
Rect rect = { m_inset, y + m_inset, font().glyph_width() * m_columns, font().glyph_height() };
|
||||
rect.inflate(0, m_line_spacing);
|
||||
return rect;
|
||||
}
|
||||
|
||||
bool Terminal::Line::has_only_one_background_color() const
|
||||
{
|
||||
if (!length)
|
||||
return true;
|
||||
// FIXME: Cache this result?
|
||||
auto color = attributes[0].background_color;
|
||||
for (size_t i = 1; i < length; ++i) {
|
||||
if (attributes[i].background_color != color)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Terminal::paint()
|
||||
{
|
||||
Rect rect { 0, 0, m_pixel_width, m_pixel_height };
|
||||
Painter painter(*m_backing);
|
||||
|
||||
for (size_t i = 0; i < rows(); ++i)
|
||||
line(i).did_paint = false;
|
||||
|
||||
if (m_rows_to_scroll_backing_store && m_rows_to_scroll_backing_store < m_rows) {
|
||||
int first_scanline = m_inset;
|
||||
int second_scanline = m_inset + (m_rows_to_scroll_backing_store * m_line_height);
|
||||
int num_rows_to_memcpy = m_rows - m_rows_to_scroll_backing_store;
|
||||
int scanlines_to_copy = (num_rows_to_memcpy * m_line_height) - m_line_spacing;
|
||||
fast_dword_copy(
|
||||
m_backing->scanline(first_scanline),
|
||||
m_backing->scanline(second_scanline),
|
||||
scanlines_to_copy * m_pixel_width
|
||||
);
|
||||
m_need_full_invalidation = true;
|
||||
line(max(0, m_cursor_row - m_rows_to_scroll_backing_store)).dirty = true;
|
||||
}
|
||||
m_rows_to_scroll_backing_store = 0;
|
||||
|
||||
invalidate_cursor();
|
||||
|
||||
for (word row = 0; row < m_rows; ++row) {
|
||||
auto& line = this->line(row);
|
||||
if (!line.dirty)
|
||||
continue;
|
||||
line.dirty = false;
|
||||
bool has_only_one_background_color = line.has_only_one_background_color();
|
||||
if (has_only_one_background_color) {
|
||||
painter.fill_rect(row_rect(row), lookup_color(line.attributes[0].background_color));
|
||||
}
|
||||
for (word column = 0; column < m_columns; ++column) {
|
||||
bool should_reverse_fill_for_cursor = m_in_active_window && row == m_cursor_row && column == m_cursor_column;
|
||||
auto& attribute = line.attributes[column];
|
||||
line.did_paint = true;
|
||||
char ch = line.characters[column];
|
||||
auto character_rect = glyph_rect(row, column);
|
||||
if (!has_only_one_background_color || should_reverse_fill_for_cursor) {
|
||||
auto cell_rect = character_rect.inflated(0, m_line_spacing);
|
||||
painter.fill_rect(cell_rect, lookup_color(should_reverse_fill_for_cursor ? attribute.foreground_color : attribute.background_color));
|
||||
}
|
||||
if (ch == ' ')
|
||||
continue;
|
||||
painter.draw_glyph(character_rect.location(), ch, lookup_color(should_reverse_fill_for_cursor ? attribute.background_color : attribute.foreground_color));
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_in_active_window) {
|
||||
auto cell_rect = glyph_rect(m_cursor_row, m_cursor_column).inflated(0, m_line_spacing);
|
||||
painter.draw_rect(cell_rect, lookup_color(line(m_cursor_row).attributes[m_cursor_column].foreground_color));
|
||||
}
|
||||
|
||||
line(m_cursor_row).did_paint = true;
|
||||
|
||||
if (m_belling) {
|
||||
m_need_full_invalidation = true;
|
||||
painter.draw_rect(rect, Color::Red);
|
||||
}
|
||||
|
||||
if (m_need_full_invalidation) {
|
||||
did_paint();
|
||||
m_need_full_invalidation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Rect painted_rect;
|
||||
for (int i = 0; i < m_rows; ++i) {
|
||||
if (line(i).did_paint)
|
||||
painted_rect = painted_rect.united(row_rect(i));
|
||||
}
|
||||
did_paint(painted_rect);
|
||||
}
|
||||
|
||||
void Terminal::did_paint(const Rect& a_rect)
|
||||
{
|
||||
GUI_Rect rect = a_rect;
|
||||
int rc = gui_notify_paint_finished(m_window_id, a_rect.is_null() ? nullptr : &rect);
|
||||
if (rc < 0) {
|
||||
perror("gui_notify_paint_finished");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::update()
|
||||
{
|
||||
Rect rect;
|
||||
for (int i = 0; i < m_rows; ++i) {
|
||||
if (line(i).did_paint)
|
||||
rect = rect.united(row_rect(i));
|
||||
}
|
||||
GUI_Rect gui_rect = rect;
|
||||
int rc = gui_invalidate_window(m_window_id, rect.is_null() ? nullptr : &gui_rect);
|
||||
if (rc < 0) {
|
||||
perror("gui_invalidate_window");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::set_window_title(const String& title)
|
||||
{
|
||||
int rc = gui_set_window_title(m_window_id, title.characters(), title.length());
|
||||
if (rc < 0) {
|
||||
perror("gui_set_window_title");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void Terminal::set_in_active_window(bool b)
|
||||
{
|
||||
if (m_in_active_window == b)
|
||||
return;
|
||||
m_in_active_window = b;
|
||||
invalidate_cursor();
|
||||
update();
|
||||
}
|
||||
|
||||
void Terminal::invalidate_cursor()
|
||||
{
|
||||
line(m_cursor_row).dirty = true;
|
||||
}
|
143
Applications/Terminal/Terminal.h
Normal file
143
Applications/Terminal/Terminal.h
Normal file
|
@ -0,0 +1,143 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/AKString.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <SharedGraphics/GraphicsBitmap.h>
|
||||
#include <SharedGraphics/Rect.h>
|
||||
|
||||
class Font;
|
||||
|
||||
class Terminal {
|
||||
public:
|
||||
Terminal();
|
||||
~Terminal();
|
||||
|
||||
void create_window();
|
||||
void paint();
|
||||
void on_char(byte);
|
||||
|
||||
void set_in_active_window(bool);
|
||||
void update();
|
||||
|
||||
private:
|
||||
Font& font() { return *m_font; }
|
||||
void scroll_up();
|
||||
void newline();
|
||||
void set_cursor(unsigned row, unsigned column);
|
||||
void put_character_at(unsigned row, unsigned column, byte ch);
|
||||
void invalidate_cursor();
|
||||
void did_paint(const Rect& = Rect());
|
||||
void invalidate_window(const Rect& = Rect());
|
||||
void set_window_title(const String&);
|
||||
|
||||
void inject_string(const String&);
|
||||
void unimplemented_escape();
|
||||
void unimplemented_xterm_escape();
|
||||
|
||||
void escape$A(const Vector<unsigned>&);
|
||||
void escape$B(const Vector<unsigned>&);
|
||||
void escape$C(const Vector<unsigned>&);
|
||||
void escape$D(const Vector<unsigned>&);
|
||||
void escape$H(const Vector<unsigned>&);
|
||||
void escape$J(const Vector<unsigned>&);
|
||||
void escape$K(const Vector<unsigned>&);
|
||||
void escape$M(const Vector<unsigned>&);
|
||||
void escape$m(const Vector<unsigned>&);
|
||||
void escape$s(const Vector<unsigned>&);
|
||||
void escape$u(const Vector<unsigned>&);
|
||||
|
||||
void clear();
|
||||
|
||||
void set_size(word columns, word rows);
|
||||
word columns() const { return m_columns; }
|
||||
word rows() const { return m_rows; }
|
||||
Rect glyph_rect(word row, word column);
|
||||
Rect row_rect(word row);
|
||||
|
||||
struct Attribute {
|
||||
Attribute() { reset(); }
|
||||
void reset()
|
||||
{
|
||||
foreground_color = 7;
|
||||
background_color = 0;
|
||||
//bold = false;
|
||||
}
|
||||
byte foreground_color;
|
||||
byte background_color;
|
||||
//bool bold : 1;
|
||||
bool operator==(const Attribute& other) const
|
||||
{
|
||||
return foreground_color == other.foreground_color && background_color == other.background_color;
|
||||
}
|
||||
bool operator!=(const Attribute& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
struct Line {
|
||||
explicit Line(word columns);
|
||||
~Line();
|
||||
void clear(Attribute);
|
||||
bool has_only_one_background_color() const;
|
||||
byte* characters { nullptr };
|
||||
Attribute* attributes { nullptr };
|
||||
bool did_paint { false };
|
||||
bool dirty { false };
|
||||
word length { 0 };
|
||||
};
|
||||
Line& line(size_t index) { ASSERT(index < m_rows); return *m_lines[index]; }
|
||||
|
||||
Line** m_lines { 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 };
|
||||
bool m_stomp { false };
|
||||
|
||||
Attribute m_current_attribute;
|
||||
|
||||
void execute_escape_sequence(byte final);
|
||||
void execute_xterm_command();
|
||||
|
||||
enum EscapeState {
|
||||
Normal,
|
||||
ExpectBracket,
|
||||
ExpectParameter,
|
||||
ExpectIntermediate,
|
||||
ExpectFinal,
|
||||
|
||||
ExpectXtermParameter1,
|
||||
ExpectXtermParameter2,
|
||||
ExpectXtermFinal,
|
||||
};
|
||||
EscapeState m_escape_state { Normal };
|
||||
Vector<byte> m_parameters;
|
||||
Vector<byte> m_intermediates;
|
||||
Vector<byte> m_xterm_param1;
|
||||
Vector<byte> m_xterm_param2;
|
||||
byte m_final { 0 };
|
||||
byte* m_horizontal_tabs { nullptr };
|
||||
bool m_belling { false };
|
||||
|
||||
int m_window_id { 0 };
|
||||
RetainPtr<GraphicsBitmap> m_backing;
|
||||
|
||||
int m_pixel_width { 0 };
|
||||
int m_pixel_height { 0 };
|
||||
int m_rows_to_scroll_backing_store { 0 };
|
||||
|
||||
int m_inset { 2 };
|
||||
int m_line_spacing { 4 };
|
||||
int m_line_height { 0 };
|
||||
|
||||
bool m_in_active_window { false };
|
||||
bool m_need_full_invalidation { false };
|
||||
|
||||
RetainPtr<Font> m_font;
|
||||
};
|
260
Applications/Terminal/XtermColors.h
Normal file
260
Applications/Terminal/XtermColors.h
Normal file
|
@ -0,0 +1,260 @@
|
|||
#pragma once
|
||||
|
||||
static const unsigned xterm_colors[256] = {
|
||||
0x000000,
|
||||
0xcc0000,
|
||||
0x3e9a06,
|
||||
0xc4a000,
|
||||
0x3465a4,
|
||||
0x75507b,
|
||||
0x06989a,
|
||||
0xeeeeec,
|
||||
0x555753,
|
||||
0xef2929,
|
||||
0x8ae234,
|
||||
0xfce94f,
|
||||
0x729fcf,
|
||||
0xad7fa8,
|
||||
0x34e2e2,
|
||||
0xFFFFFF,
|
||||
0x000000,
|
||||
0x00005f,
|
||||
0x000087,
|
||||
0x0000af,
|
||||
0x0000d7,
|
||||
0x0000ff,
|
||||
0x005f00,
|
||||
0x005f5f,
|
||||
0x005f87,
|
||||
0x005faf,
|
||||
0x005fd7,
|
||||
0x005fff,
|
||||
0x008700,
|
||||
0x00875f,
|
||||
0x008787,
|
||||
0x0087af,
|
||||
0x0087d7,
|
||||
0x0087ff,
|
||||
0x00af00,
|
||||
0x00af5f,
|
||||
0x00af87,
|
||||
0x00afaf,
|
||||
0x00afd7,
|
||||
0x00afff,
|
||||
0x00d700,
|
||||
0x00d75f,
|
||||
0x00d787,
|
||||
0x00d7af,
|
||||
0x00d7d7,
|
||||
0x00d7ff,
|
||||
0x00ff00,
|
||||
0x00ff5f,
|
||||
0x00ff87,
|
||||
0x00ffaf,
|
||||
0x00ffd7,
|
||||
0x00ffff,
|
||||
0x5f0000,
|
||||
0x5f005f,
|
||||
0x5f0087,
|
||||
0x5f00af,
|
||||
0x5f00d7,
|
||||
0x5f00ff,
|
||||
0x5f5f00,
|
||||
0x5f5f5f,
|
||||
0x5f5f87,
|
||||
0x5f5faf,
|
||||
0x5f5fd7,
|
||||
0x5f5fff,
|
||||
0x5f8700,
|
||||
0x5f875f,
|
||||
0x5f8787,
|
||||
0x5f87af,
|
||||
0x5f87d7,
|
||||
0x5f87ff,
|
||||
0x5faf00,
|
||||
0x5faf5f,
|
||||
0x5faf87,
|
||||
0x5fafaf,
|
||||
0x5fafd7,
|
||||
0x5fafff,
|
||||
0x5fd700,
|
||||
0x5fd75f,
|
||||
0x5fd787,
|
||||
0x5fd7af,
|
||||
0x5fd7d7,
|
||||
0x5fd7ff,
|
||||
0x5fff00,
|
||||
0x5fff5f,
|
||||
0x5fff87,
|
||||
0x5fffaf,
|
||||
0x5fffd7,
|
||||
0x5fffff,
|
||||
0x870000,
|
||||
0x87005f,
|
||||
0x870087,
|
||||
0x8700af,
|
||||
0x8700d7,
|
||||
0x8700ff,
|
||||
0x875f00,
|
||||
0x875f5f,
|
||||
0x875f87,
|
||||
0x875faf,
|
||||
0x875fd7,
|
||||
0x875fff,
|
||||
0x878700,
|
||||
0x87875f,
|
||||
0x878787,
|
||||
0x8787af,
|
||||
0x8787d7,
|
||||
0x8787ff,
|
||||
0x87af00,
|
||||
0x87af5f,
|
||||
0x87af87,
|
||||
0x87afaf,
|
||||
0x87afd7,
|
||||
0x87afff,
|
||||
0x87d700,
|
||||
0x87d75f,
|
||||
0x87d787,
|
||||
0x87d7af,
|
||||
0x87d7d7,
|
||||
0x87d7ff,
|
||||
0x87ff00,
|
||||
0x87ff5f,
|
||||
0x87ff87,
|
||||
0x87ffaf,
|
||||
0x87ffd7,
|
||||
0x87ffff,
|
||||
0xaf0000,
|
||||
0xaf005f,
|
||||
0xaf0087,
|
||||
0xaf00af,
|
||||
0xaf00d7,
|
||||
0xaf00ff,
|
||||
0xaf5f00,
|
||||
0xaf5f5f,
|
||||
0xaf5f87,
|
||||
0xaf5faf,
|
||||
0xaf5fd7,
|
||||
0xaf5fff,
|
||||
0xaf8700,
|
||||
0xaf875f,
|
||||
0xaf8787,
|
||||
0xaf87af,
|
||||
0xaf87d7,
|
||||
0xaf87ff,
|
||||
0xafaf00,
|
||||
0xafaf5f,
|
||||
0xafaf87,
|
||||
0xafafaf,
|
||||
0xafafd7,
|
||||
0xafafff,
|
||||
0xafd700,
|
||||
0xafd75f,
|
||||
0xafd787,
|
||||
0xafd7af,
|
||||
0xafd7d7,
|
||||
0xafd7ff,
|
||||
0xafff00,
|
||||
0xafff5f,
|
||||
0xafff87,
|
||||
0xafffaf,
|
||||
0xafffd7,
|
||||
0xafffff,
|
||||
0xd70000,
|
||||
0xd7005f,
|
||||
0xd70087,
|
||||
0xd700af,
|
||||
0xd700d7,
|
||||
0xd700ff,
|
||||
0xd75f00,
|
||||
0xd75f5f,
|
||||
0xd75f87,
|
||||
0xd75faf,
|
||||
0xd75fd7,
|
||||
0xd75fff,
|
||||
0xd78700,
|
||||
0xd7875f,
|
||||
0xd78787,
|
||||
0xd787af,
|
||||
0xd787d7,
|
||||
0xd787ff,
|
||||
0xd7af00,
|
||||
0xd7af5f,
|
||||
0xd7af87,
|
||||
0xd7afaf,
|
||||
0xd7afd7,
|
||||
0xd7afff,
|
||||
0xd7d700,
|
||||
0xd7d75f,
|
||||
0xd7d787,
|
||||
0xd7d7af,
|
||||
0xd7d7d7,
|
||||
0xd7d7ff,
|
||||
0xd7ff00,
|
||||
0xd7ff5f,
|
||||
0xd7ff87,
|
||||
0xd7ffaf,
|
||||
0xd7ffd7,
|
||||
0xd7ffff,
|
||||
0xff0000,
|
||||
0xff005f,
|
||||
0xff0087,
|
||||
0xff00af,
|
||||
0xff00d7,
|
||||
0xff00ff,
|
||||
0xff5f00,
|
||||
0xff5f5f,
|
||||
0xff5f87,
|
||||
0xff5faf,
|
||||
0xff5fd7,
|
||||
0xff5fff,
|
||||
0xff8700,
|
||||
0xff875f,
|
||||
0xff8787,
|
||||
0xff87af,
|
||||
0xff87d7,
|
||||
0xff87ff,
|
||||
0xffaf00,
|
||||
0xffaf5f,
|
||||
0xffaf87,
|
||||
0xffafaf,
|
||||
0xffafd7,
|
||||
0xffafff,
|
||||
0xffd700,
|
||||
0xffd75f,
|
||||
0xffd787,
|
||||
0xffd7af,
|
||||
0xffd7d7,
|
||||
0xffd7ff,
|
||||
0xffff00,
|
||||
0xffff5f,
|
||||
0xffff87,
|
||||
0xffffaf,
|
||||
0xffffd7,
|
||||
0xffffff,
|
||||
0x080808,
|
||||
0x121212,
|
||||
0x1c1c1c,
|
||||
0x262626,
|
||||
0x303030,
|
||||
0x3a3a3a,
|
||||
0x444444,
|
||||
0x4e4e4e,
|
||||
0x585858,
|
||||
0x626262,
|
||||
0x6c6c6c,
|
||||
0x767676,
|
||||
0x808080,
|
||||
0x8a8a8a,
|
||||
0x949494,
|
||||
0x9e9e9e,
|
||||
0xa8a8a8,
|
||||
0xb2b2b2,
|
||||
0xbcbcbc,
|
||||
0xc6c6c6,
|
||||
0xd0d0d0,
|
||||
0xdadada,
|
||||
0xe4e4e4,
|
||||
0xeeeeee,
|
||||
};
|
155
Applications/Terminal/main.cpp
Normal file
155
Applications/Terminal/main.cpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
#include <SharedGraphics/Font.h>
|
||||
#include <SharedGraphics/GraphicsBitmap.h>
|
||||
#include <SharedGraphics/Painter.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <LibC/gui.h>
|
||||
#include "Terminal.h"
|
||||
#include <Kernel/KeyCode.h>
|
||||
|
||||
static void make_shell(int ptm_fd)
|
||||
{
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
const char* tty_name = ptsname(ptm_fd);
|
||||
if (!tty_name) {
|
||||
perror("ptsname");
|
||||
exit(1);
|
||||
}
|
||||
int rc = 0;
|
||||
close(ptm_fd);
|
||||
int pts_fd = open(tty_name, O_RDWR);
|
||||
rc = ioctl(0, TIOCNOTTY);
|
||||
if (rc < 0) {
|
||||
perror("ioctl(TIOCNOTTY)");
|
||||
exit(1);
|
||||
}
|
||||
close(0);
|
||||
close(1);
|
||||
close(2);
|
||||
dup2(pts_fd, 0);
|
||||
dup2(pts_fd, 1);
|
||||
dup2(pts_fd, 2);
|
||||
close(pts_fd);
|
||||
rc = ioctl(0, TIOCSCTTY);
|
||||
if (rc < 0) {
|
||||
perror("ioctl(TIOCSCTTY)");
|
||||
exit(1);
|
||||
}
|
||||
rc = execvp("/bin/sh", nullptr);
|
||||
if (rc < 0) {
|
||||
perror("execve");
|
||||
exit(1);
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
static int max(int a, int b)
|
||||
{
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
int ptm_fd = open("/dev/ptmx", O_RDWR);
|
||||
if (ptm_fd < 0) {
|
||||
perror("open(ptmx)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
make_shell(ptm_fd);
|
||||
|
||||
int event_fd = open("/dev/gui_events", O_RDONLY);
|
||||
if (event_fd < 0) {
|
||||
perror("open");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Terminal terminal;
|
||||
terminal.create_window();
|
||||
terminal.update();
|
||||
|
||||
for (;;) {
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(ptm_fd, &rfds);
|
||||
FD_SET(event_fd, &rfds);
|
||||
int nfds = select(max(ptm_fd, event_fd) + 1, &rfds, nullptr, nullptr, nullptr);
|
||||
if (nfds < 0) {
|
||||
dbgprintf("Terminal(%u) select() failed :( errno=%d\n", getpid(), errno);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (FD_ISSET(ptm_fd, &rfds)) {
|
||||
byte buffer[4096];
|
||||
ssize_t nread = read(ptm_fd, buffer, sizeof(buffer));
|
||||
if (nread < 0) {
|
||||
dbgprintf("Terminal read error: %s\n", strerror(errno));
|
||||
perror("read(ptm)");
|
||||
continue;
|
||||
}
|
||||
if (nread == 0) {
|
||||
dbgprintf("Terminal: EOF on master pty, closing.\n");
|
||||
break;
|
||||
}
|
||||
for (ssize_t i = 0; i < nread; ++i)
|
||||
terminal.on_char(buffer[i]);
|
||||
terminal.update();
|
||||
}
|
||||
|
||||
if (FD_ISSET(event_fd, &rfds)) {
|
||||
GUI_Event event;
|
||||
ssize_t nread = read(event_fd, &event, sizeof(event));
|
||||
if (nread < 0) {
|
||||
perror("read(event)");
|
||||
return 1;
|
||||
}
|
||||
assert(nread != 0);
|
||||
assert(nread == sizeof(event));
|
||||
|
||||
if (event.type == GUI_Event::Type::Paint) {
|
||||
terminal.paint();
|
||||
} else if (event.type == GUI_Event::Type::KeyDown) {
|
||||
char ch = event.key.character;
|
||||
if (event.key.ctrl) {
|
||||
if (ch >= 'a' && ch <= 'z') {
|
||||
ch = ch - 'a' + 1;
|
||||
} else if (ch == '\\') {
|
||||
ch = 0x1c;
|
||||
}
|
||||
}
|
||||
switch (event.key.key) {
|
||||
case KeyCode::Key_Up:
|
||||
write(ptm_fd, "\033[A", 3);
|
||||
break;
|
||||
case KeyCode::Key_Down:
|
||||
write(ptm_fd, "\033[B", 3);
|
||||
break;
|
||||
case KeyCode::Key_Right:
|
||||
write(ptm_fd, "\033[C", 3);
|
||||
break;
|
||||
case KeyCode::Key_Left:
|
||||
write(ptm_fd, "\033[D", 3);
|
||||
break;
|
||||
default:
|
||||
write(ptm_fd, &ch, 1);
|
||||
}
|
||||
} else if (event.type == GUI_Event::Type::WindowActivated) {
|
||||
terminal.set_in_active_window(true);
|
||||
} else if (event.type == GUI_Event::Type::WindowDeactivated) {
|
||||
terminal.set_in_active_window(false);
|
||||
} else if (event.type == GUI_Event::Type::WindowCloseRequest) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue