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
RECOMMENDED
TARGETS PixelPaint
DEPENDS ImageDecoder
DEPENDS ImageDecoder FileSystemAccessServer
)
compile_gml(PixelPaintWindow.gml PixelPaintWindowGML.h pixel_paint_window_gml)
@ -42,4 +42,4 @@ set(SOURCES
)
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/MappedFile.h>
#include <AK/StringBuilder.h>
#include <LibCore/File.h>
#include <LibGUI/Painter.h>
#include <LibGfx/BMPWriter.h>
#include <LibGfx/Bitmap.h>
@ -85,13 +84,27 @@ RefPtr<Image> Image::try_create_from_bitmap(NonnullRefPtr<Gfx::Bitmap> bitmap)
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);
if (file_or_error.is_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 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();
}
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())
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();
}
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
{
StringBuilder builder;
@ -202,11 +273,12 @@ RefPtr<Gfx::Bitmap> Image::try_compose_bitmap(Gfx::BitmapFormat format) const
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));
if (file_or_error.is_error())
return file_or_error.error();
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() };
auto bitmap_format = preserve_alpha_channel ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888;
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;
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 {};
}
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));
if (file_or_error.is_error())
return file_or_error.error();
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() };
auto bitmap_format = preserve_alpha_channel ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888;
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 };
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 {};

View file

@ -12,6 +12,7 @@
#include <AK/RefPtr.h>
#include <AK/Result.h>
#include <AK/Vector.h>
#include <LibCore/File.h>
#include <LibGUI/Command.h>
#include <LibGUI/Forward.h>
#include <LibGfx/Forward.h>
@ -40,7 +41,8 @@ protected:
class Image : public RefCounted<Image> {
public:
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>);
// 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 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> export_bmp_to_file(String const& file_path, bool preserve_alpha_channel);
Result<void, String> export_png_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_fd_and_close(int fd, bool preserve_alpha_channel);
void move_layer_to_front(Layer&);
void move_layer_to_back(Layer&);
@ -89,7 +92,9 @@ public:
private:
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_modify_layer_stack();

View file

@ -95,7 +95,7 @@ PaletteWidget::PaletteWidget()
bottom_color_container.set_layout<GUI::HorizontalBoxLayout>();
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()) {
GUI::MessageBox::show_error(window(), String::formatted("Loading default palette failed: {}", result.error()));
display_color_list(fallback_colors());
@ -192,14 +192,8 @@ Vector<Color> PaletteWidget::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;
while (file.can_read_line()) {
@ -224,20 +218,39 @@ Result<Vector<Color>, String> PaletteWidget::load_palette_file(String const& fil
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())
return file_or_error.error();
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) {
file.write(color.to_string_without_alpha());
file.write("\n");
file->write(color.to_string_without_alpha());
file->write("\n");
}
file.close();
file->close();
return {};
}

View file

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

View file

@ -21,10 +21,10 @@
#include <Applications/PixelPaint/PixelPaintWindowGML.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibFileSystemAccessClient/Client.h>
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/Clipboard.h>
#include <LibGUI/FilePicker.h>
#include <LibGUI/Icon.h>
#include <LibGUI/Menubar.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.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 window = GUI::Window::construct();
@ -111,7 +150,7 @@ int main(int argc, char** argv)
window);
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()) {
GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}", path));
return;
@ -121,26 +160,38 @@ int main(int argc, char** argv)
layer_list_widget.set_image(&image);
};
auto open_image_action = GUI::CommonActions::make_open_action([&](auto&) {
Optional<String> open_path = GUI::FilePicker::get_open_filepath(window);
if (!open_path.has_value())
auto open_image_fd = [&](int fd, auto& path) {
auto image_or_error = PixelPaint::Image::try_create_from_fd_and_close(fd, path);
if (image_or_error.is_error()) {
GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}, {}", path, image_or_error.error()));
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* editor = current_image_editor();
if (!editor)
return;
auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "pp");
if (!save_path.has_value())
auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "pp");
if (save_result.error != 0)
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()) {
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;
}
editor->image().set_path(save_path.value());
editor->image().set_path(*save_result.chosen_file);
});
auto& file_menu = window->add_menu("&File");
@ -155,11 +206,11 @@ int main(int argc, char** argv)
auto* editor = current_image_editor();
if (!editor)
return;
auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "bmp");
if (!save_path.has_value())
auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "bmp");
if (save_result.error != 0)
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 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())
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(
"As &PNG", [&](auto&) {
auto* editor = current_image_editor();
auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "png");
if (!save_path.has_value())
auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "png");
if (save_result.error != 0)
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 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())
GUI::MessageBox::show_error(window, String::formatted("Export to PNG failed: {}", result.error()));
},
@ -270,11 +321,11 @@ int main(int argc, char** argv)
window));
edit_menu.add_action(GUI::Action::create(
"&Load Color Palette", [&](auto&) {
auto open_path = GUI::FilePicker::get_open_filepath(window, "Load Color Palette");
if (!open_path.has_value())
auto open_result = FileSystemAccessClient::Client::the().open_file(window->window_id(), "Load Color Palette");
if (open_result.error != 0)
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()) {
GUI::MessageBox::show_error(window, String::formatted("Loading color palette failed: {}", result.error()));
return;
@ -285,11 +336,11 @@ int main(int argc, char** argv)
window));
edit_menu.add_action(GUI::Action::create(
"Sa&ve Color Palette", [&](auto&) {
auto save_path = GUI::FilePicker::get_save_filepath(window, "untitled", "palette");
if (!save_path.has_value())
auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "palette");
if (save_result.error != 0)
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())
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(image_file_real_path)) {
open_image_file(image_file_real_path);
if (Core::File::exists(file_to_edit_full_path)) {
open_image_file(file_to_edit_full_path);
} else {
auto image = PixelPaint::Image::try_create_with_size({ 480, 360 });