diff --git a/Base/res/icons/FontEditor.png b/Base/res/icons/FontEditor.png new file mode 100644 index 00000000000..0ee4ffbde50 Binary files /dev/null and b/Base/res/icons/FontEditor.png differ diff --git a/Base/res/icons/FontEditor.rgb b/Base/res/icons/FontEditor.rgb new file mode 100644 index 00000000000..6a8ecfa6b67 Binary files /dev/null and b/Base/res/icons/FontEditor.rgb differ diff --git a/Base/res/icons/Terminal.png b/Base/res/icons/Terminal.png new file mode 100644 index 00000000000..2d396863000 Binary files /dev/null and b/Base/res/icons/Terminal.png differ diff --git a/Base/res/icons/Terminal.rgb b/Base/res/icons/Terminal.rgb new file mode 100644 index 00000000000..ea24005df56 Binary files /dev/null and b/Base/res/icons/Terminal.rgb differ diff --git a/Base/res/icons/file.png b/Base/res/icons/file.png new file mode 100644 index 00000000000..69b08a3942c Binary files /dev/null and b/Base/res/icons/file.png differ diff --git a/Base/res/icons/file.rgb b/Base/res/icons/file.rgb new file mode 100644 index 00000000000..7c71465d85d Binary files /dev/null and b/Base/res/icons/file.rgb differ diff --git a/Base/res/icons/folder.png b/Base/res/icons/folder.png new file mode 100644 index 00000000000..3be71f1b4f8 Binary files /dev/null and b/Base/res/icons/folder.png differ diff --git a/Base/res/icons/folder.rgb b/Base/res/icons/folder.rgb new file mode 100644 index 00000000000..fb5db913491 Binary files /dev/null and b/Base/res/icons/folder.rgb differ diff --git a/Base/res/icons/generic.png b/Base/res/icons/generic.png new file mode 100644 index 00000000000..49aa4322dd0 Binary files /dev/null and b/Base/res/icons/generic.png differ diff --git a/Base/res/icons/generic.rgb b/Base/res/icons/generic.rgb new file mode 100644 index 00000000000..ba39a5eb0e8 Binary files /dev/null and b/Base/res/icons/generic.rgb differ diff --git a/Kernel/init.cpp b/Kernel/init.cpp index 492e90420c8..a92b9a02e53 100644 --- a/Kernel/init.cpp +++ b/Kernel/init.cpp @@ -26,7 +26,8 @@ #include "BochsVGADevice.h" //#define SPAWN_GUITEST -#define SPAWN_GUITEST2 +#define SPAWN_LAUNCHER +//#define SPAWN_GUITEST2 #define SPAWN_CLOCK //#define SPAWN_FONTEDITOR //#define SPAWN_MULTIPLE_SHELLS @@ -113,6 +114,9 @@ static void init_stage2() #ifdef SPAWN_GUITEST2 Process::create_user_process("/bin/guitest2", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0); #endif +#ifdef SPAWN_LAUNCHER + Process::create_user_process("/bin/Launcher", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0); +#endif #ifdef SPAWN_CLOCK Process::create_user_process("/bin/Clock", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0); #endif diff --git a/Kernel/makeall.sh b/Kernel/makeall.sh index 8c41d8f3819..efdb843520e 100755 --- a/Kernel/makeall.sh +++ b/Kernel/makeall.sh @@ -14,6 +14,8 @@ make -C ../FontEditor clean && \ make -C ../FontEditor && \ make -C ../Clock clean && \ make -C ../Clock && \ +make -C ../Launcher clean && \ +make -C ../Launcher && \ make clean &&\ make && \ sudo ./sync.sh diff --git a/Kernel/makeuserland.sh b/Kernel/makeuserland.sh index cedeb0e99c8..1a227dfc303 100755 --- a/Kernel/makeuserland.sh +++ b/Kernel/makeuserland.sh @@ -10,6 +10,8 @@ make -C ../Terminal clean && \ make -C ../Terminal && \ make -C ../Clock clean && \ make -C ../Clock && \ +make -C ../Launcher clean && \ +make -C ../Launcher && \ make -C ../Userland clean && \ make -C ../Userland && \ sudo ./sync.sh diff --git a/Kernel/sync.sh b/Kernel/sync.sh index 9c93e0ffaf9..6cceecb2434 100755 --- a/Kernel/sync.sh +++ b/Kernel/sync.sh @@ -47,6 +47,7 @@ cp -v ../Userland/guitest2 mnt/bin/guitest2 cp -v ../Userland/sysctl mnt/bin/sysctl cp -v ../Terminal/Terminal mnt/bin/Terminal cp -v ../FontEditor/FontEditor mnt/bin/FontEditor +cp -v ../Launcher/Launcher mnt/bin/Launcher cp -v ../Clock/Clock mnt/bin/Clock ln -s FontEditor mnt/bin/ff ln -s Clock mnt/bin/cl diff --git a/Launcher/.gitignore b/Launcher/.gitignore new file mode 100644 index 00000000000..ea286a24a65 --- /dev/null +++ b/Launcher/.gitignore @@ -0,0 +1,3 @@ +*.o +*.d +FontEditor diff --git a/Launcher/Launcher b/Launcher/Launcher new file mode 100755 index 00000000000..16f315c9e5c Binary files /dev/null and b/Launcher/Launcher differ diff --git a/Launcher/Makefile b/Launcher/Makefile new file mode 100644 index 00000000000..29b43227006 --- /dev/null +++ b/Launcher/Makefile @@ -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 -mregparm=3 -m32 -fno-exceptions -fno-rtti -fmerge-all-constants -fno-unroll-loops -fno-pie -fno-pic +OPTIMIZATION_FLAGS = -Oz -fno-asynchronous-unwind-tables +INCLUDE_FLAGS = -I.. -I. -I../LibC + +DEFINES = -DSERENITY -DSANITIZE_PTRS -DUSERLAND + +CXXFLAGS = -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 + diff --git a/Launcher/main.cpp b/Launcher/main.cpp new file mode 100644 index 00000000000..60abbb74cce --- /dev/null +++ b/Launcher/main.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include + +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(); +} + +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); + widget->set_relative_rect({ 0, 0, 300, 60 }); + + auto* terminal_button = new GButton(widget); + terminal_button->set_relative_rect({ 5, 5, 50, 50 }); + terminal_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/Terminal.rgb", { 32, 32 })); + + terminal_button->on_click = [] (GButton&) { + pid_t child_pid = fork(); + if (!child_pid) { + execve("/bin/Terminal", nullptr, nullptr); + ASSERT_NOT_REACHED(); + } + }; + + auto* font_editor_button = new GButton(widget); + font_editor_button->set_relative_rect({ 60, 5, 50, 50 }); + font_editor_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/FontEditor.rgb", { 32, 32 })); + + font_editor_button->on_click = [] (GButton&) { + pid_t child_pid = fork(); + if (!child_pid) { + execve("/bin/FontEditor", nullptr, nullptr); + ASSERT_NOT_REACHED(); + } + }; + + auto* guitest_editor_button = new GButton(widget); + guitest_editor_button->set_relative_rect({ 115, 5, 50, 50 }); + guitest_editor_button->set_icon(GraphicsBitmap::load_from_file("/res/icons/generic.rgb", { 32, 32 })); + + guitest_editor_button->on_click = [] (GButton&) { + pid_t child_pid = fork(); + if (!child_pid) { + execve("/bin/guitest", nullptr, nullptr); + ASSERT_NOT_REACHED(); + } + }; + + return window; +} diff --git a/LibGUI/GButton.cpp b/LibGUI/GButton.cpp index b94a863f757..2309ee0be00 100644 --- a/LibGUI/GButton.cpp +++ b/LibGUI/GButton.cpp @@ -55,11 +55,19 @@ void GButton::paint_event(GPaintEvent&) painter.draw_line({ 2, height() - 3 }, { width() - 2, height() - 3 }, shadow_color); } - if (!caption().is_empty()) { - auto text_rect = rect(); - if (m_being_pressed) - text_rect.move_by(1, 1); - painter.draw_text(text_rect, caption(), Painter::TextAlignment::Center, Color::Black); + if (!caption().is_empty() || m_icon) { + auto content_rect = rect(); + auto icon_location = m_icon ? content_rect.center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2)) : Point(); + if (m_being_pressed) { + content_rect.move_by(1, 1); + icon_location.move_by(1, 1); + } + if (m_icon) { + painter.blit_with_alpha(icon_location, *m_icon, m_icon->rect()); + painter.draw_text(content_rect, caption(), Painter::TextAlignment::Center, Color::Black); + } else { + painter.draw_text(content_rect, caption(), Painter::TextAlignment::Center, Color::Black); + } } } diff --git a/LibGUI/GButton.h b/LibGUI/GButton.h index b5164ca26ec..6c40d844010 100644 --- a/LibGUI/GButton.h +++ b/LibGUI/GButton.h @@ -3,6 +3,7 @@ #include "GWidget.h" #include #include +#include class GButton final : public GWidget { public: @@ -12,6 +13,10 @@ public: String caption() const { return m_caption; } void set_caption(String&&); + void set_icon(RetainPtr&& icon) { m_icon = move(icon); } + const GraphicsBitmap* icon() const { return m_icon.ptr(); } + GraphicsBitmap* icon() { return m_icon.ptr(); } + Function on_click; private: @@ -23,6 +28,7 @@ private: virtual const char* class_name() const override { return "GButton"; } String m_caption; + RetainPtr m_icon; bool m_being_pressed { false }; bool m_tracking_cursor { false }; }; diff --git a/SharedGraphics/Color.h b/SharedGraphics/Color.h index b38ecbb5abb..934e6bdd138 100644 --- a/SharedGraphics/Color.h +++ b/SharedGraphics/Color.h @@ -29,9 +29,20 @@ public: Color(byte r, byte g, byte b) : m_value((r << 16) | (g << 8) | b) { } Color(RGBA32 rgba) : m_value(rgba) { } - int red() const { return (m_value >> 16) & 0xff; } - int green() const { return (m_value >> 8) & 0xff; } - int blue() const { return m_value & 0xff; } + byte red() const { return (m_value >> 16) & 0xff; } + byte green() const { return (m_value >> 8) & 0xff; } + byte blue() const { return m_value & 0xff; } + byte alpha() const { return (m_value >> 24) & 0xff; } + + Color blend(Color source) const + { + RGBA32 redblue1 = ((0x100u - source.alpha()) * (m_value & 0xff00ff)) >> 8; + RGBA32 redblue2 = (source.alpha() * (source.m_value & 0xff00ff)) >> 8; + RGBA32 green1 = ((0x100u - source.alpha()) * (m_value & 0x00ff00)) >> 8; + RGBA32 green2 = (source.alpha() * (source.m_value & 0x00ff00)) >> 8; + return Color(((redblue1 | redblue2) & 0xff00ff) + ((green1 | green2) & 0x00ff00)); + } + RGBA32 value() const { return m_value; } diff --git a/SharedGraphics/GraphicsBitmap.cpp b/SharedGraphics/GraphicsBitmap.cpp index 8c8b6c09490..d812218cfec 100644 --- a/SharedGraphics/GraphicsBitmap.cpp +++ b/SharedGraphics/GraphicsBitmap.cpp @@ -1,5 +1,4 @@ #include "GraphicsBitmap.h" -#include #ifdef KERNEL #include @@ -7,6 +6,14 @@ #include #endif +#ifdef USERLAND +#include +#include +#include +#include +#include +#endif + #ifdef KERNEL RetainPtr GraphicsBitmap::create(Process& process, const Size& size) { @@ -39,6 +46,49 @@ RetainPtr GraphicsBitmap::create_wrapper(const Size& size, RGBA3 return adopt(*new GraphicsBitmap(size, data)); } +RetainPtr GraphicsBitmap::load_from_file(const String& path, const Size& size) +{ +#ifdef USERLAND + int fd = open(path.characters(), O_RDONLY, 0644); + if (fd < 0) { + dbgprintf("open(%s) got fd=%d, failed: %s\n", path.characters(), fd, strerror(errno)); + perror("open"); + return nullptr; + } + + auto* mapped_file = (RGBA32*)mmap(nullptr, size.area() * 4, PROT_READ, MAP_SHARED, fd, 0); + if (mapped_file == MAP_FAILED) { + int rc = close(fd); + ASSERT(rc == 0); + return nullptr; + } +#else + int error; + auto descriptor = VFS::the().open(path, error, 0, 0, *VFS::the().root_inode()); + if (!descriptor) { + kprintf("Failed to load GraphicsBitmap from file (%s)\n", path.characters()); + ASSERT_NOT_REACHED(); + } + auto* region = current->allocate_file_backed_region(LinearAddress(), size.area() * 4, descriptor->inode(), ".rgb file", /*readable*/true, /*writable*/false); + region->page_in(); + auto* mapped_file = (RGBA32*)region->laddr().get(); +#endif + + +#ifdef USERLAND + int rc = close(fd); + ASSERT(rc == 0); +#endif + auto bitmap = create_wrapper(size, mapped_file); +#ifdef KERNEL + bitmap->m_server_region = region; +#else + bitmap->m_mmaped = true; +#endif + + return bitmap; +} + GraphicsBitmap::GraphicsBitmap(const Size& size, RGBA32* data) : m_size(size) , m_data(data) @@ -53,6 +103,11 @@ GraphicsBitmap::~GraphicsBitmap() m_client_process->deallocate_region(*m_client_region); if (m_server_region) WSMessageLoop::the().server_process().deallocate_region(*m_server_region); +#else + if (m_mmaped) { + int rc = munmap(m_data, m_size.area() * 4); + ASSERT(rc == 0); + } #endif m_data = nullptr; } diff --git a/SharedGraphics/GraphicsBitmap.h b/SharedGraphics/GraphicsBitmap.h index de3f934ad26..861e0d41700 100644 --- a/SharedGraphics/GraphicsBitmap.h +++ b/SharedGraphics/GraphicsBitmap.h @@ -5,6 +5,7 @@ #include "Size.h" #include #include +#include #ifdef KERNEL #include "Process.h" @@ -16,6 +17,7 @@ public: static RetainPtr create(Process&, const Size&); #endif static RetainPtr create_wrapper(const Size&, RGBA32*); + static RetainPtr load_from_file(const String& path, const Size&); ~GraphicsBitmap(); RGBA32* scanline(int y); @@ -42,6 +44,10 @@ private: RGBA32* m_data { nullptr }; size_t m_pitch { 0 }; +#ifdef USERLAND + bool m_mmaped { false }; +#endif + #ifdef KERNEL WeakPtr m_client_process; Region* m_client_region { nullptr }; diff --git a/SharedGraphics/Painter.cpp b/SharedGraphics/Painter.cpp index 486352a9584..8f7ead9c213 100644 --- a/SharedGraphics/Painter.cpp +++ b/SharedGraphics/Painter.cpp @@ -368,6 +368,7 @@ void Painter::draw_focus_rect(const Rect& rect) void Painter::blit(const Point& position, const GraphicsBitmap& source, const Rect& src_rect) { Rect dst_rect(position, src_rect.size()); + dst_rect.move_by(m_translation); dst_rect.intersect(m_clip_rect); RGBA32* dst = m_target->scanline(dst_rect.y()) + dst_rect.x(); @@ -383,6 +384,33 @@ void Painter::blit(const Point& position, const GraphicsBitmap& source, const Re } } +void Painter::blit_with_alpha(const Point& position, const GraphicsBitmap& source, const Rect& src_rect) +{ + Rect dst_rect(position, src_rect.size()); + dst_rect.move_by(m_translation); + dst_rect.intersect(m_clip_rect); + + RGBA32* dst = m_target->scanline(dst_rect.y()) + dst_rect.x(); + const RGBA32* src = source.scanline(src_rect.top()) + src_rect.left(); + + const unsigned dst_skip = m_target->width(); + const unsigned src_skip = source.width(); + + for (int i = dst_rect.height() - 1; i >= 0; --i) { + for (int x = 0; x < dst_rect.width(); ++x) { + byte alpha = Color(src[x]).alpha(); + if (alpha == 0xff) + dst[x] = src[x]; + else if (!alpha) + continue; + else + dst[x] = Color(dst[x]).blend(src[x]).value(); + } + dst += dst_skip; + src += src_skip; + } +} + void Painter::set_clip_rect(const Rect& rect) { m_clip_rect = Rect::intersection(rect, m_target->rect()); diff --git a/SharedGraphics/Painter.h b/SharedGraphics/Painter.h index dd9b807f2b0..eef0675c496 100644 --- a/SharedGraphics/Painter.h +++ b/SharedGraphics/Painter.h @@ -32,6 +32,7 @@ public: void draw_line(const Point&, const Point&, Color); void draw_focus_rect(const Rect&); void blit(const Point&, const GraphicsBitmap&, const Rect& src_rect); + void blit_with_alpha(const Point&, const GraphicsBitmap&, const Rect& src_rect); enum class TextAlignment { TopLeft, CenterLeft, Center, CenterRight }; void draw_text(const Rect&, const String&, TextAlignment = TextAlignment::TopLeft, Color = Color()); diff --git a/SharedGraphics/Point.h b/SharedGraphics/Point.h index 222eddcb46e..a0c0e3a4aa0 100644 --- a/SharedGraphics/Point.h +++ b/SharedGraphics/Point.h @@ -26,6 +26,13 @@ public: move_by(delta.x(), delta.y()); } + Point translated(int dx, int dy) + { + Point point = *this; + point.move_by(dx, dy); + return point; + } + void constrain(const Rect&); bool operator==(const Point& other) const diff --git a/SharedGraphics/Size.h b/SharedGraphics/Size.h index 4e5a4ea8110..87f2c5b088c 100644 --- a/SharedGraphics/Size.h +++ b/SharedGraphics/Size.h @@ -13,6 +13,8 @@ public: int width() const { return m_width; } int height() const { return m_height; } + int area() const { return width() * height(); } + void set_width(int w) { m_width = w; } void set_height(int h) { m_height = h; } diff --git a/Userland/guitest2.cpp b/Userland/guitest2.cpp index 54187c82a62..45e7759df31 100644 --- a/Userland/guitest2.cpp +++ b/Userland/guitest2.cpp @@ -78,7 +78,7 @@ GWindow* make_font_test_window() GWindow* make_launcher_window() { auto* window = new GWindow; - window->set_title("Launcher"); + window->set_title("guitest2"); window->set_rect({ 100, 400, 100, 230 }); auto* widget = new GWidget; diff --git a/Utilities/convert-raw-to-rgb.cpp b/Utilities/convert-raw-to-rgb.cpp new file mode 100644 index 00000000000..15e98682330 --- /dev/null +++ b/Utilities/convert-raw-to-rgb.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +int main(int argc, char**argv) +{ + int fd = open(argv[1], O_RDONLY); + if (fd < 0) { + perror("open"); + return 1; + } + for (;;) { + unsigned buffer; + ssize_t nread = read(fd, &buffer, sizeof(buffer)); + if (nread == 0) + break; + if (nread < 0) { + perror("read"); + return 1; + } + unsigned converted = buffer & 0xff00ff00; + converted |= (buffer & 0xff0000) >> 16; + converted |= (buffer & 0xff) << 16; + write(1, &converted, sizeof(unsigned)); + } + int rc = close(fd); + if (rc < 0) { + perror("close"); + return 1; + } + return 0; +}