Selaa lähdekoodia

Start working on a simple Launcher app.

Let GButton have an optional icon (GraphicsBitmap) that gets rendered in the
middle of the button if present.

Also add GraphicsBitmap::load_from_file() which allows mmap'ed RGBA32 files.
I wrote a little program to take "raw" files from GIMP and swizzle them into
the correct byte order.
Andreas Kling 6 vuotta sitten
vanhempi
commit
887b4a7a1a

BIN
Base/res/icons/FontEditor.png


BIN
Base/res/icons/FontEditor.rgb


BIN
Base/res/icons/Terminal.png


BIN
Base/res/icons/Terminal.rgb


BIN
Base/res/icons/file.png


BIN
Base/res/icons/file.rgb


BIN
Base/res/icons/folder.png


BIN
Base/res/icons/folder.rgb


BIN
Base/res/icons/generic.png


BIN
Base/res/icons/generic.rgb


+ 5 - 1
Kernel/init.cpp

@@ -26,7 +26,8 @@
 #include "BochsVGADevice.h"
 #include "BochsVGADevice.h"
 
 
 //#define SPAWN_GUITEST
 //#define SPAWN_GUITEST
-#define SPAWN_GUITEST2
+#define SPAWN_LAUNCHER
+//#define SPAWN_GUITEST2
 #define SPAWN_CLOCK
 #define SPAWN_CLOCK
 //#define SPAWN_FONTEDITOR
 //#define SPAWN_FONTEDITOR
 //#define SPAWN_MULTIPLE_SHELLS
 //#define SPAWN_MULTIPLE_SHELLS
@@ -113,6 +114,9 @@ static void init_stage2()
 #ifdef SPAWN_GUITEST2
 #ifdef SPAWN_GUITEST2
     Process::create_user_process("/bin/guitest2", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0);
     Process::create_user_process("/bin/guitest2", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0);
 #endif
 #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
 #ifdef SPAWN_CLOCK
     Process::create_user_process("/bin/Clock", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0);
     Process::create_user_process("/bin/Clock", (uid_t)100, (gid_t)100, (pid_t)0, error, { }, move(environment), tty0);
 #endif
 #endif

+ 2 - 0
Kernel/makeall.sh

@@ -14,6 +14,8 @@ make -C ../FontEditor clean && \
 make -C ../FontEditor && \
 make -C ../FontEditor && \
 make -C ../Clock clean && \
 make -C ../Clock clean && \
 make -C ../Clock && \
 make -C ../Clock && \
+make -C ../Launcher clean && \
+make -C ../Launcher && \
 make clean &&\
 make clean &&\
 make && \
 make && \
 sudo ./sync.sh
 sudo ./sync.sh

+ 2 - 0
Kernel/makeuserland.sh

@@ -10,6 +10,8 @@ make -C ../Terminal clean && \
 make -C ../Terminal && \
 make -C ../Terminal && \
 make -C ../Clock clean && \
 make -C ../Clock clean && \
 make -C ../Clock && \
 make -C ../Clock && \
+make -C ../Launcher clean && \
+make -C ../Launcher && \
 make -C ../Userland clean && \
 make -C ../Userland clean && \
 make -C ../Userland && \
 make -C ../Userland && \
 sudo ./sync.sh
 sudo ./sync.sh

+ 1 - 0
Kernel/sync.sh

@@ -47,6 +47,7 @@ cp -v ../Userland/guitest2 mnt/bin/guitest2
 cp -v ../Userland/sysctl mnt/bin/sysctl
 cp -v ../Userland/sysctl mnt/bin/sysctl
 cp -v ../Terminal/Terminal mnt/bin/Terminal
 cp -v ../Terminal/Terminal mnt/bin/Terminal
 cp -v ../FontEditor/FontEditor mnt/bin/FontEditor
 cp -v ../FontEditor/FontEditor mnt/bin/FontEditor
+cp -v ../Launcher/Launcher mnt/bin/Launcher
 cp -v ../Clock/Clock mnt/bin/Clock
 cp -v ../Clock/Clock mnt/bin/Clock
 ln -s FontEditor mnt/bin/ff
 ln -s FontEditor mnt/bin/ff
 ln -s Clock mnt/bin/cl
 ln -s Clock mnt/bin/cl

+ 3 - 0
Launcher/.gitignore

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

BIN
Launcher/Launcher


+ 34 - 0
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
+

+ 79 - 0
Launcher/main.cpp

@@ -0,0 +1,79 @@
+#include <SharedGraphics/GraphicsBitmap.h>
+#include <LibGUI/GWindow.h>
+#include <LibGUI/GWidget.h>
+#include <LibGUI/GButton.h>
+#include <LibGUI/GEventLoop.h>
+#include <signal.h>
+#include <unistd.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();
+}
+
+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;
+}

+ 13 - 5
LibGUI/GButton.cpp

@@ -55,11 +55,19 @@ void GButton::paint_event(GPaintEvent&)
         painter.draw_line({ 2, height() - 3 }, { width() - 2, height() - 3 }, shadow_color);
         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);
+        }
     }
     }
 }
 }
 
 

+ 6 - 0
LibGUI/GButton.h

@@ -3,6 +3,7 @@
 #include "GWidget.h"
 #include "GWidget.h"
 #include <AK/AKString.h>
 #include <AK/AKString.h>
 #include <AK/Function.h>
 #include <AK/Function.h>
+#include <SharedGraphics/GraphicsBitmap.h>
 
 
 class GButton final : public GWidget {
 class GButton final : public GWidget {
 public:
 public:
@@ -12,6 +13,10 @@ public:
     String caption() const { return m_caption; }
     String caption() const { return m_caption; }
     void set_caption(String&&);
     void set_caption(String&&);
 
 
+    void set_icon(RetainPtr<GraphicsBitmap>&& icon) { m_icon = move(icon); }
+    const GraphicsBitmap* icon() const { return m_icon.ptr(); }
+    GraphicsBitmap* icon() { return m_icon.ptr(); }
+
     Function<void(GButton&)> on_click;
     Function<void(GButton&)> on_click;
 
 
 private:
 private:
@@ -23,6 +28,7 @@ private:
     virtual const char* class_name() const override { return "GButton"; }
     virtual const char* class_name() const override { return "GButton"; }
 
 
     String m_caption;
     String m_caption;
+    RetainPtr<GraphicsBitmap> m_icon;
     bool m_being_pressed { false };
     bool m_being_pressed { false };
     bool m_tracking_cursor { false };
     bool m_tracking_cursor { false };
 };
 };

+ 14 - 3
SharedGraphics/Color.h

@@ -29,9 +29,20 @@ public:
     Color(byte r, byte g, byte b) : m_value((r << 16) | (g << 8) | b) { }
     Color(byte r, byte g, byte b) : m_value((r << 16) | (g << 8) | b) { }
     Color(RGBA32 rgba) : m_value(rgba) { }
     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; }
     RGBA32 value() const { return m_value; }
 
 

+ 56 - 1
SharedGraphics/GraphicsBitmap.cpp

@@ -1,5 +1,4 @@
 #include "GraphicsBitmap.h"
 #include "GraphicsBitmap.h"
-#include <AK/kmalloc.h>
 
 
 #ifdef KERNEL
 #ifdef KERNEL
 #include <Kernel/Process.h>
 #include <Kernel/Process.h>
@@ -7,6 +6,14 @@
 #include <WindowServer/WSMessageLoop.h>
 #include <WindowServer/WSMessageLoop.h>
 #endif
 #endif
 
 
+#ifdef USERLAND
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#endif
+
 #ifdef KERNEL
 #ifdef KERNEL
 RetainPtr<GraphicsBitmap> GraphicsBitmap::create(Process& process, const Size& size)
 RetainPtr<GraphicsBitmap> GraphicsBitmap::create(Process& process, const Size& size)
 {
 {
@@ -39,6 +46,49 @@ RetainPtr<GraphicsBitmap> GraphicsBitmap::create_wrapper(const Size& size, RGBA3
     return adopt(*new GraphicsBitmap(size, data));
     return adopt(*new GraphicsBitmap(size, data));
 }
 }
 
 
+RetainPtr<GraphicsBitmap> 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)
 GraphicsBitmap::GraphicsBitmap(const Size& size, RGBA32* data)
     : m_size(size)
     : m_size(size)
     , m_data(data)
     , m_data(data)
@@ -53,6 +103,11 @@ GraphicsBitmap::~GraphicsBitmap()
         m_client_process->deallocate_region(*m_client_region);
         m_client_process->deallocate_region(*m_client_region);
     if (m_server_region)
     if (m_server_region)
         WSMessageLoop::the().server_process().deallocate_region(*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
 #endif
     m_data = nullptr;
     m_data = nullptr;
 }
 }

+ 6 - 0
SharedGraphics/GraphicsBitmap.h

@@ -5,6 +5,7 @@
 #include "Size.h"
 #include "Size.h"
 #include <AK/Retainable.h>
 #include <AK/Retainable.h>
 #include <AK/RetainPtr.h>
 #include <AK/RetainPtr.h>
+#include <AK/AKString.h>
 
 
 #ifdef KERNEL
 #ifdef KERNEL
 #include "Process.h"
 #include "Process.h"
@@ -16,6 +17,7 @@ public:
     static RetainPtr<GraphicsBitmap> create(Process&, const Size&);
     static RetainPtr<GraphicsBitmap> create(Process&, const Size&);
 #endif
 #endif
     static RetainPtr<GraphicsBitmap> create_wrapper(const Size&, RGBA32*);
     static RetainPtr<GraphicsBitmap> create_wrapper(const Size&, RGBA32*);
+    static RetainPtr<GraphicsBitmap> load_from_file(const String& path, const Size&);
     ~GraphicsBitmap();
     ~GraphicsBitmap();
 
 
     RGBA32* scanline(int y);
     RGBA32* scanline(int y);
@@ -42,6 +44,10 @@ private:
     RGBA32* m_data { nullptr };
     RGBA32* m_data { nullptr };
     size_t m_pitch { 0 };
     size_t m_pitch { 0 };
 
 
+#ifdef USERLAND
+    bool m_mmaped { false };
+#endif
+
 #ifdef KERNEL
 #ifdef KERNEL
     WeakPtr<Process> m_client_process;
     WeakPtr<Process> m_client_process;
     Region* m_client_region { nullptr };
     Region* m_client_region { nullptr };

+ 28 - 0
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)
 void Painter::blit(const Point& position, const GraphicsBitmap& source, const Rect& src_rect)
 {
 {
     Rect dst_rect(position, src_rect.size());
     Rect dst_rect(position, src_rect.size());
+    dst_rect.move_by(m_translation);
     dst_rect.intersect(m_clip_rect);
     dst_rect.intersect(m_clip_rect);
 
 
     RGBA32* dst = m_target->scanline(dst_rect.y()) + dst_rect.x();
     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)
 void Painter::set_clip_rect(const Rect& rect)
 {
 {
     m_clip_rect = Rect::intersection(rect, m_target->rect());
     m_clip_rect = Rect::intersection(rect, m_target->rect());

+ 1 - 0
SharedGraphics/Painter.h

@@ -32,6 +32,7 @@ public:
     void draw_line(const Point&, const Point&, Color);
     void draw_line(const Point&, const Point&, Color);
     void draw_focus_rect(const Rect&);
     void draw_focus_rect(const Rect&);
     void blit(const Point&, const GraphicsBitmap&, const Rect& src_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 };
     enum class TextAlignment { TopLeft, CenterLeft, Center, CenterRight };
     void draw_text(const Rect&, const String&, TextAlignment = TextAlignment::TopLeft, Color = Color());
     void draw_text(const Rect&, const String&, TextAlignment = TextAlignment::TopLeft, Color = Color());

+ 7 - 0
SharedGraphics/Point.h

@@ -26,6 +26,13 @@ public:
         move_by(delta.x(), delta.y());
         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&);
     void constrain(const Rect&);
 
 
     bool operator==(const Point& other) const
     bool operator==(const Point& other) const

+ 2 - 0
SharedGraphics/Size.h

@@ -13,6 +13,8 @@ public:
     int width() const { return m_width; }
     int width() const { return m_width; }
     int height() const { return m_height; }
     int height() const { return m_height; }
 
 
+    int area() const { return width() * height(); }
+
     void set_width(int w) { m_width = w; }
     void set_width(int w) { m_width = w; }
     void set_height(int h) { m_height = h; }
     void set_height(int h) { m_height = h; }
 
 

+ 1 - 1
Userland/guitest2.cpp

@@ -78,7 +78,7 @@ GWindow* make_font_test_window()
 GWindow* make_launcher_window()
 GWindow* make_launcher_window()
 {
 {
     auto* window = new GWindow;
     auto* window = new GWindow;
-    window->set_title("Launcher");
+    window->set_title("guitest2");
     window->set_rect({ 100, 400, 100, 230 });
     window->set_rect({ 100, 400, 100, 230 });
 
 
     auto* widget = new GWidget;
     auto* widget = new GWidget;

+ 33 - 0
Utilities/convert-raw-to-rgb.cpp

@@ -0,0 +1,33 @@
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+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;
+}