1
Fork 0
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:
Andreas Kling 2019-02-10 08:35:01 +01:00
parent 29f2a22d34
commit 2cf1dd5b6f
27 changed files with 30 additions and 26 deletions

3
Applications/Clock/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
*.o
*.d
Clock

View 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();
}

View 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 };
};

View 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

View 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
View file

@ -0,0 +1,3 @@
*.o
*.d
FileManager

View 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);
}

View 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 };
};

View 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

View 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
View file

@ -0,0 +1,3 @@
*.o
*.d
FontEditor

View 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;
}

View 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 };
};

View 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

View 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
View file

@ -0,0 +1,3 @@
*.o
*.d
Launcher

View 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

View 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
View file

@ -0,0 +1,3 @@
*.o
*.d
Terminal

View 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

View 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(&params);
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;
}

View 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;
};

View 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,
};

View 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;
}