1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 05:07:45 +00:00

PixelPaint: Use unveil to hide file system

This commit is contained in:
Timothy 2021-08-06 14:37:53 +10:00 committed by Andreas Kling
parent 62af82f494
commit 1927dbf025
6 changed files with 206 additions and 64 deletions

View file

@ -2,7 +2,7 @@ serenity_component(
PixelPaint PixelPaint
RECOMMENDED RECOMMENDED
TARGETS PixelPaint TARGETS PixelPaint
DEPENDS ImageDecoder DEPENDS ImageDecoder FileSystemAccessServer
) )
compile_gml(PixelPaintWindow.gml PixelPaintWindowGML.h pixel_paint_window_gml) compile_gml(PixelPaintWindow.gml PixelPaintWindowGML.h pixel_paint_window_gml)
@ -42,4 +42,4 @@ set(SOURCES
) )
serenity_app(PixelPaint ICON app-pixel-paint) serenity_app(PixelPaint ICON app-pixel-paint)
target_link_libraries(PixelPaint LibImageDecoderClient LibGUI LibGfx) target_link_libraries(PixelPaint LibImageDecoderClient LibGUI LibGfx LibFileSystemAccessClient)

View file

@ -13,7 +13,6 @@
#include <AK/LexicalPath.h> #include <AK/LexicalPath.h>
#include <AK/MappedFile.h> #include <AK/MappedFile.h>
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <LibCore/File.h>
#include <LibGUI/Painter.h> #include <LibGUI/Painter.h>
#include <LibGfx/BMPWriter.h> #include <LibGfx/BMPWriter.h>
#include <LibGfx/Bitmap.h> #include <LibGfx/Bitmap.h>
@ -85,13 +84,27 @@ RefPtr<Image> Image::try_create_from_bitmap(NonnullRefPtr<Gfx::Bitmap> bitmap)
return image; return image;
} }
Result<NonnullRefPtr<Image>, String> Image::try_create_from_pixel_paint_file(String const& file_path) Result<NonnullRefPtr<Image>, String> Image::try_create_from_pixel_paint_fd(int fd, String const& file_path)
{
auto file = Core::File::construct();
file->open(fd, Core::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::No);
if (file->has_error())
return String { file->error_string() };
return try_create_from_pixel_paint_file(file, file_path);
}
Result<NonnullRefPtr<Image>, String> Image::try_create_from_pixel_paint_path(String const& file_path)
{ {
auto file_or_error = Core::File::open(file_path, Core::OpenMode::ReadOnly); auto file_or_error = Core::File::open(file_path, Core::OpenMode::ReadOnly);
if (file_or_error.is_error()) if (file_or_error.is_error())
return file_or_error.error(); return file_or_error.error();
auto& file = *file_or_error.value(); return try_create_from_pixel_paint_file(*file_or_error.value(), file_path);
}
Result<NonnullRefPtr<Image>, String> Image::try_create_from_pixel_paint_file(Core::File& file, String const& file_path)
{
auto contents = file.read_all(); auto contents = file.read_all();
auto json_or_error = JsonValue::from_string(contents); auto json_or_error = JsonValue::from_string(contents);
@ -137,9 +150,33 @@ Result<NonnullRefPtr<Image>, String> Image::try_create_from_pixel_paint_file(Str
return image.release_nonnull(); return image.release_nonnull();
} }
Result<NonnullRefPtr<Image>, String> Image::try_create_from_file(String const& file_path) Result<NonnullRefPtr<Image>, String> Image::try_create_from_fd_and_close(int fd, String const& file_path)
{ {
auto image_or_error = try_create_from_pixel_paint_file(file_path); auto image_or_error = try_create_from_pixel_paint_fd(fd, file_path);
if (!image_or_error.is_error()) {
close(fd);
return image_or_error.release_value();
}
auto file_or_error = MappedFile::map_from_fd_and_close(fd, file_path);
if (file_or_error.is_error())
return String::formatted("Unable to mmap file {}", file_or_error.error().string());
auto& mapped_file = *file_or_error.value();
// FIXME: Find a way to avoid the memory copy here.
auto bitmap = try_decode_bitmap(ByteBuffer::copy(mapped_file.bytes()));
if (!bitmap)
return String { "Unable to decode image"sv };
auto image = Image::try_create_from_bitmap(bitmap.release_nonnull());
if (!image)
return String { "Unable to allocate Image"sv };
image->set_path(file_path);
return image.release_nonnull();
}
Result<NonnullRefPtr<Image>, String> Image::try_create_from_path(String const& file_path)
{
auto image_or_error = try_create_from_pixel_paint_path(file_path);
if (!image_or_error.is_error()) if (!image_or_error.is_error())
return image_or_error.release_value(); return image_or_error.release_value();
@ -159,6 +196,40 @@ Result<NonnullRefPtr<Image>, String> Image::try_create_from_file(String const& f
return image.release_nonnull(); return image.release_nonnull();
} }
Result<void, String> Image::write_to_fd_and_close(int fd) const
{
StringBuilder builder;
JsonObjectSerializer json(builder);
json.add("width", m_size.width());
json.add("height", m_size.height());
{
auto json_layers = json.add_array("layers");
for (const auto& layer : m_layers) {
Gfx::BMPWriter bmp_dumber;
auto json_layer = json_layers.add_object();
json_layer.add("width", layer.size().width());
json_layer.add("height", layer.size().height());
json_layer.add("name", layer.name());
json_layer.add("locationx", layer.location().x());
json_layer.add("locationy", layer.location().y());
json_layer.add("opacity_percent", layer.opacity_percent());
json_layer.add("visible", layer.is_visible());
json_layer.add("selected", layer.is_selected());
json_layer.add("bitmap", encode_base64(bmp_dumber.dump(layer.bitmap())));
}
}
json.finish();
auto file = Core::File::construct();
file->open(fd, Core::OpenMode::WriteOnly | Core::OpenMode::Truncate, Core::File::ShouldCloseFileDescriptor::Yes);
if (file->has_error())
return String { file->error_string() };
if (!file->write(builder.string_view()))
return String { file->error_string() };
return {};
}
Result<void, String> Image::write_to_file(const String& file_path) const Result<void, String> Image::write_to_file(const String& file_path) const
{ {
StringBuilder builder; StringBuilder builder;
@ -202,11 +273,12 @@ RefPtr<Gfx::Bitmap> Image::try_compose_bitmap(Gfx::BitmapFormat format) const
return bitmap; return bitmap;
} }
Result<void, String> Image::export_bmp_to_file(String const& file_path, bool preserve_alpha_channel) Result<void, String> Image::export_bmp_to_fd_and_close(int fd, bool preserve_alpha_channel)
{ {
auto file_or_error = Core::File::open(file_path, (Core::OpenMode)(Core::OpenMode::WriteOnly | Core::OpenMode::Truncate)); auto file = Core::File::construct();
if (file_or_error.is_error()) file->open(fd, Core::OpenMode::WriteOnly | Core::OpenMode::Truncate, Core::File::ShouldCloseFileDescriptor::Yes);
return file_or_error.error(); if (file->has_error())
return String { file->error_string() };
auto bitmap_format = preserve_alpha_channel ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888; auto bitmap_format = preserve_alpha_channel ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888;
auto bitmap = try_compose_bitmap(bitmap_format); auto bitmap = try_compose_bitmap(bitmap_format);
@ -216,18 +288,18 @@ Result<void, String> Image::export_bmp_to_file(String const& file_path, bool pre
Gfx::BMPWriter dumper; Gfx::BMPWriter dumper;
auto encoded_data = dumper.dump(bitmap); auto encoded_data = dumper.dump(bitmap);
auto& file = *file_or_error.value(); if (!file->write(encoded_data.data(), encoded_data.size()))
if (!file.write(encoded_data.data(), encoded_data.size()))
return String { "Failed to write encoded BMP data to file"sv }; return String { "Failed to write encoded BMP data to file"sv };
return {}; return {};
} }
Result<void, String> Image::export_png_to_file(String const& file_path, bool preserve_alpha_channel) Result<void, String> Image::export_png_to_fd_and_close(int fd, bool preserve_alpha_channel)
{ {
auto file_or_error = Core::File::open(file_path, (Core::OpenMode)(Core::OpenMode::WriteOnly | Core::OpenMode::Truncate)); auto file = Core::File::construct();
if (file_or_error.is_error()) file->open(fd, Core::OpenMode::WriteOnly | Core::OpenMode::Truncate, Core::File::ShouldCloseFileDescriptor::Yes);
return file_or_error.error(); if (file->has_error())
return String { file->error_string() };
auto bitmap_format = preserve_alpha_channel ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888; auto bitmap_format = preserve_alpha_channel ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888;
auto bitmap = try_compose_bitmap(bitmap_format); auto bitmap = try_compose_bitmap(bitmap_format);
@ -235,8 +307,7 @@ Result<void, String> Image::export_png_to_file(String const& file_path, bool pre
return String { "Failed to allocate bitmap for encoding"sv }; return String { "Failed to allocate bitmap for encoding"sv };
auto encoded_data = Gfx::PNGWriter::encode(*bitmap); auto encoded_data = Gfx::PNGWriter::encode(*bitmap);
auto& file = *file_or_error.value(); if (!file->write(encoded_data.data(), encoded_data.size()))
if (!file.write(encoded_data.data(), encoded_data.size()))
return String { "Failed to write encoded PNG data to file"sv }; return String { "Failed to write encoded PNG data to file"sv };
return {}; return {};

View file

@ -12,6 +12,7 @@
#include <AK/RefPtr.h> #include <AK/RefPtr.h>
#include <AK/Result.h> #include <AK/Result.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibCore/File.h>
#include <LibGUI/Command.h> #include <LibGUI/Command.h>
#include <LibGUI/Forward.h> #include <LibGUI/Forward.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
@ -40,7 +41,8 @@ protected:
class Image : public RefCounted<Image> { class Image : public RefCounted<Image> {
public: public:
static RefPtr<Image> try_create_with_size(Gfx::IntSize const&); static RefPtr<Image> try_create_with_size(Gfx::IntSize const&);
static Result<NonnullRefPtr<Image>, String> try_create_from_file(String const& file_path); static Result<NonnullRefPtr<Image>, String> try_create_from_fd_and_close(int fd, String const& file_path);
static Result<NonnullRefPtr<Image>, String> try_create_from_path(String const& file_path);
static RefPtr<Image> try_create_from_bitmap(NonnullRefPtr<Gfx::Bitmap>); static RefPtr<Image> try_create_from_bitmap(NonnullRefPtr<Gfx::Bitmap>);
// This generates a new Bitmap with the final image (all layers composed according to their attributes.) // This generates a new Bitmap with the final image (all layers composed according to their attributes.)
@ -58,9 +60,10 @@ public:
void restore_snapshot(Image const&); void restore_snapshot(Image const&);
void paint_into(GUI::Painter&, Gfx::IntRect const& dest_rect) const; void paint_into(GUI::Painter&, Gfx::IntRect const& dest_rect) const;
Result<void, String> write_to_fd_and_close(int fd) const;
Result<void, String> write_to_file(String const& file_path) const; Result<void, String> write_to_file(String const& file_path) const;
Result<void, String> export_bmp_to_file(String const& file_path, bool preserve_alpha_channel); Result<void, String> export_bmp_to_fd_and_close(int fd, bool preserve_alpha_channel);
Result<void, String> export_png_to_file(String const& file_path, bool preserve_alpha_channel); Result<void, String> export_png_to_fd_and_close(int fd, bool preserve_alpha_channel);
void move_layer_to_front(Layer&); void move_layer_to_front(Layer&);
void move_layer_to_back(Layer&); void move_layer_to_back(Layer&);
@ -89,7 +92,9 @@ public:
private: private:
explicit Image(Gfx::IntSize const&); explicit Image(Gfx::IntSize const&);
static Result<NonnullRefPtr<Image>, String> try_create_from_pixel_paint_file(String const& file_path); static Result<NonnullRefPtr<Image>, String> try_create_from_pixel_paint_fd(int fd, String const& file_path);
static Result<NonnullRefPtr<Image>, String> try_create_from_pixel_paint_path(String const& file_path);
static Result<NonnullRefPtr<Image>, String> try_create_from_pixel_paint_file(Core::File& file, String const& file_path);
void did_change(Gfx::IntRect const& modified_rect = {}); void did_change(Gfx::IntRect const& modified_rect = {});
void did_modify_layer_stack(); void did_modify_layer_stack();

View file

@ -95,7 +95,7 @@ PaletteWidget::PaletteWidget()
bottom_color_container.set_layout<GUI::HorizontalBoxLayout>(); bottom_color_container.set_layout<GUI::HorizontalBoxLayout>();
bottom_color_container.layout()->set_spacing(1); bottom_color_container.layout()->set_spacing(1);
auto result = load_palette_file("/res/color-palettes/default.palette"); auto result = load_palette_path("/res/color-palettes/default.palette");
if (result.is_error()) { if (result.is_error()) {
GUI::MessageBox::show_error(window(), String::formatted("Loading default palette failed: {}", result.error())); GUI::MessageBox::show_error(window(), String::formatted("Loading default palette failed: {}", result.error()));
display_color_list(fallback_colors()); display_color_list(fallback_colors());
@ -192,14 +192,8 @@ Vector<Color> PaletteWidget::colors()
return colors; return colors;
} }
Result<Vector<Color>, String> PaletteWidget::load_palette_file(String const& file_path) Result<Vector<Color>, String> PaletteWidget::load_palette_file(Core::File& file)
{ {
auto file_or_error = Core::File::open(file_path, Core::OpenMode::ReadOnly);
if (file_or_error.is_error())
return file_or_error.error();
auto& file = *file_or_error.value();
Vector<Color> palette; Vector<Color> palette;
while (file.can_read_line()) { while (file.can_read_line()) {
@ -224,20 +218,39 @@ Result<Vector<Color>, String> PaletteWidget::load_palette_file(String const& fil
return palette; return palette;
} }
Result<void, String> PaletteWidget::save_palette_file(Vector<Color> palette, String const& file_path) Result<Vector<Color>, String> PaletteWidget::load_palette_fd_and_close(int fd)
{ {
auto file_or_error = Core::File::open(file_path, Core::OpenMode::WriteOnly); auto file = Core::File::construct();
file->open(fd, Core::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes);
if (file->has_error())
return String { file->error_string() };
return load_palette_file(file);
}
Result<Vector<Color>, String> PaletteWidget::load_palette_path(String const& file_path)
{
auto file_or_error = Core::File::open(file_path, Core::OpenMode::ReadOnly);
if (file_or_error.is_error()) if (file_or_error.is_error())
return file_or_error.error(); return file_or_error.error();
auto& file = *file_or_error.value(); auto& file = *file_or_error.value();
return load_palette_file(file);
}
Result<void, String> PaletteWidget::save_palette_fd_and_close(Vector<Color> palette, int fd)
{
auto file = Core::File::construct();
file->open(fd, Core::OpenMode::WriteOnly, Core::File::ShouldCloseFileDescriptor::Yes);
if (file->has_error())
return String { file->error_string() };
for (auto& color : palette) { for (auto& color : palette) {
file.write(color.to_string_without_alpha()); file->write(color.to_string_without_alpha());
file.write("\n"); file->write("\n");
} }
file.close(); file->close();
return {}; return {};
} }

View file

@ -28,13 +28,16 @@ public:
Vector<Color> colors(); Vector<Color> colors();
static Result<Vector<Color>, String> load_palette_file(String const&); static Result<Vector<Color>, String> load_palette_fd_and_close(int);
static Result<void, String> save_palette_file(Vector<Color>, String const&); static Result<Vector<Color>, String> load_palette_path(String const&);
static Result<void, String> save_palette_fd_and_close(Vector<Color>, int);
static Vector<Color> fallback_colors(); static Vector<Color> fallback_colors();
void set_image_editor(ImageEditor&); void set_image_editor(ImageEditor&);
private: private:
static Result<Vector<Color>, String> load_palette_file(Core::File&);
explicit PaletteWidget(); explicit PaletteWidget();
ImageEditor* m_editor { nullptr }; ImageEditor* m_editor { nullptr };

View file

@ -21,10 +21,10 @@
#include <Applications/PixelPaint/PixelPaintWindowGML.h> #include <Applications/PixelPaint/PixelPaintWindowGML.h>
#include <LibCore/ArgsParser.h> #include <LibCore/ArgsParser.h>
#include <LibCore/File.h> #include <LibCore/File.h>
#include <LibFileSystemAccessClient/Client.h>
#include <LibGUI/Action.h> #include <LibGUI/Action.h>
#include <LibGUI/Application.h> #include <LibGUI/Application.h>
#include <LibGUI/Clipboard.h> #include <LibGUI/Clipboard.h>
#include <LibGUI/FilePicker.h>
#include <LibGUI/Icon.h> #include <LibGUI/Icon.h>
#include <LibGUI/Menubar.h> #include <LibGUI/Menubar.h>
#include <LibGUI/MessageBox.h> #include <LibGUI/MessageBox.h>
@ -50,6 +50,45 @@ int main(int argc, char** argv)
args_parser.add_positional_argument(image_file, "Image file to open", "path", Core::ArgsParser::Required::No); args_parser.add_positional_argument(image_file, "Image file to open", "path", Core::ArgsParser::Required::No);
args_parser.parse(argc, argv); args_parser.parse(argc, argv);
String file_to_edit_full_path;
if (image_file) {
file_to_edit_full_path = Core::File::absolute_path(image_file);
VERIFY(!file_to_edit_full_path.is_empty());
if (Core::File::exists(file_to_edit_full_path)) {
dbgln("unveil for: {}", file_to_edit_full_path);
if (unveil(file_to_edit_full_path.characters(), "r") < 0) {
perror("unveil");
return 1;
}
}
}
if (unveil("/res", "r") < 0) {
perror("unveil");
return 1;
}
if (unveil("/tmp/portal/clipboard", "rw") < 0) {
perror("unveil");
return 1;
}
if (unveil("/tmp/portal/filesystemaccess", "rw") < 0) {
perror("unveil");
return 1;
}
if (unveil("/tmp/portal/image", "rw") < 0) {
perror("unveil");
return 1;
}
if (unveil(nullptr, nullptr) < 0) {
perror("unveil");
return 1;
}
auto app_icon = GUI::Icon::default_icon("app-pixel-paint"); auto app_icon = GUI::Icon::default_icon("app-pixel-paint");
auto window = GUI::Window::construct(); auto window = GUI::Window::construct();
@ -111,7 +150,7 @@ int main(int argc, char** argv)
window); window);
auto open_image_file = [&](auto& path) { auto open_image_file = [&](auto& path) {
auto image_or_error = PixelPaint::Image::try_create_from_file(path); auto image_or_error = PixelPaint::Image::try_create_from_path(path);
if (image_or_error.is_error()) { if (image_or_error.is_error()) {
GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}", path)); GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}", path));
return; return;
@ -121,26 +160,38 @@ int main(int argc, char** argv)
layer_list_widget.set_image(&image); layer_list_widget.set_image(&image);
}; };
auto open_image_action = GUI::CommonActions::make_open_action([&](auto&) { auto open_image_fd = [&](int fd, auto& path) {
Optional<String> open_path = GUI::FilePicker::get_open_filepath(window); auto image_or_error = PixelPaint::Image::try_create_from_fd_and_close(fd, path);
if (!open_path.has_value()) if (image_or_error.is_error()) {
GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}, {}", path, image_or_error.error()));
return; return;
open_image_file(open_path.value()); }
auto& image = *image_or_error.value();
create_new_editor(image);
layer_list_widget.set_image(&image);
};
auto open_image_action = GUI::CommonActions::make_open_action([&](auto&) {
auto result = FileSystemAccessClient::Client::the().open_file(window->window_id());
if (result.error != 0)
return;
open_image_fd(*result.fd, *result.chosen_file);
}); });
auto save_image_as_action = GUI::CommonActions::make_save_as_action([&](auto&) { auto save_image_as_action = GUI::CommonActions::make_save_as_action([&](auto&) {
auto* editor = current_image_editor(); auto* editor = current_image_editor();
if (!editor) if (!editor)
return; return;
auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "pp"); auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "pp");
if (!save_path.has_value()) if (save_result.error != 0)
return; return;
auto result = editor->image().write_to_file(save_path.value()); auto result = editor->image().write_to_fd_and_close(*save_result.fd);
if (result.is_error()) { if (result.is_error()) {
GUI::MessageBox::show_error(window, String::formatted("Could not save {}: {}", save_path.value(), result.error())); GUI::MessageBox::show_error(window, String::formatted("Could not save {}: {}", *save_result.chosen_file, result.error()));
return; return;
} }
editor->image().set_path(save_path.value()); editor->image().set_path(*save_result.chosen_file);
}); });
auto& file_menu = window->add_menu("&File"); auto& file_menu = window->add_menu("&File");
@ -155,11 +206,11 @@ int main(int argc, char** argv)
auto* editor = current_image_editor(); auto* editor = current_image_editor();
if (!editor) if (!editor)
return; return;
auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "bmp"); auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "bmp");
if (!save_path.has_value()) if (save_result.error != 0)
return; return;
auto preserve_alpha_channel = GUI::MessageBox::show(window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo); auto preserve_alpha_channel = GUI::MessageBox::show(window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
auto result = editor->image().export_bmp_to_file(save_path.value(), preserve_alpha_channel == GUI::MessageBox::ExecYes); auto result = editor->image().export_bmp_to_fd_and_close(*save_result.fd, preserve_alpha_channel == GUI::MessageBox::ExecYes);
if (result.is_error()) if (result.is_error())
GUI::MessageBox::show_error(window, String::formatted("Export to BMP failed: {}", result.error())); GUI::MessageBox::show_error(window, String::formatted("Export to BMP failed: {}", result.error()));
}, },
@ -168,11 +219,11 @@ int main(int argc, char** argv)
GUI::Action::create( GUI::Action::create(
"As &PNG", [&](auto&) { "As &PNG", [&](auto&) {
auto* editor = current_image_editor(); auto* editor = current_image_editor();
auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "png"); auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "png");
if (!save_path.has_value()) if (save_result.error != 0)
return; return;
auto preserve_alpha_channel = GUI::MessageBox::show(window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo); auto preserve_alpha_channel = GUI::MessageBox::show(window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
auto result = editor->image().export_png_to_file(save_path.value(), preserve_alpha_channel == GUI::MessageBox::ExecYes); auto result = editor->image().export_png_to_fd_and_close(*save_result.fd, preserve_alpha_channel == GUI::MessageBox::ExecYes);
if (result.is_error()) if (result.is_error())
GUI::MessageBox::show_error(window, String::formatted("Export to PNG failed: {}", result.error())); GUI::MessageBox::show_error(window, String::formatted("Export to PNG failed: {}", result.error()));
}, },
@ -270,11 +321,11 @@ int main(int argc, char** argv)
window)); window));
edit_menu.add_action(GUI::Action::create( edit_menu.add_action(GUI::Action::create(
"&Load Color Palette", [&](auto&) { "&Load Color Palette", [&](auto&) {
auto open_path = GUI::FilePicker::get_open_filepath(window, "Load Color Palette"); auto open_result = FileSystemAccessClient::Client::the().open_file(window->window_id(), "Load Color Palette");
if (!open_path.has_value()) if (open_result.error != 0)
return; return;
auto result = PixelPaint::PaletteWidget::load_palette_file(open_path.value()); auto result = PixelPaint::PaletteWidget::load_palette_fd_and_close(*open_result.fd);
if (result.is_error()) { if (result.is_error()) {
GUI::MessageBox::show_error(window, String::formatted("Loading color palette failed: {}", result.error())); GUI::MessageBox::show_error(window, String::formatted("Loading color palette failed: {}", result.error()));
return; return;
@ -285,11 +336,11 @@ int main(int argc, char** argv)
window)); window));
edit_menu.add_action(GUI::Action::create( edit_menu.add_action(GUI::Action::create(
"Sa&ve Color Palette", [&](auto&) { "Sa&ve Color Palette", [&](auto&) {
auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "palette"); auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "palette");
if (!save_path.has_value()) if (save_result.error != 0)
return; return;
auto result = PixelPaint::PaletteWidget::save_palette_file(palette_widget.colors(), save_path.value()); auto result = PixelPaint::PaletteWidget::save_palette_fd_and_close(palette_widget.colors(), *save_result.fd);
if (result.is_error()) if (result.is_error())
GUI::MessageBox::show_error(window, String::formatted("Writing color palette failed: {}", result.error())); GUI::MessageBox::show_error(window, String::formatted("Writing color palette failed: {}", result.error()));
}, },
@ -693,9 +744,8 @@ int main(int argc, char** argv)
}); });
}; };
auto image_file_real_path = Core::File::real_path_for(image_file); if (Core::File::exists(file_to_edit_full_path)) {
if (Core::File::exists(image_file_real_path)) { open_image_file(file_to_edit_full_path);
open_image_file(image_file_real_path);
} else { } else {
auto image = PixelPaint::Image::try_create_with_size({ 480, 360 }); auto image = PixelPaint::Image::try_create_with_size({ 480, 360 });