mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-10 13:00:29 +03:00
TextEditor+LibGUI: Use unveil and FileSystemAccessServer
Making use of the new FileSystemAccessServer we are able to use unveil without restricting our ability to open and save files. A file argument will be unveiled automatically however all other files require user action via the FileSystemAccessServer to gain access.
This commit is contained in:
parent
41ce2debda
commit
73ae5200a9
Notes:
sideshowbarker
2024-07-18 09:25:53 +09:00
Author: https://github.com/timmot Commit: https://github.com/SerenityOS/serenity/commit/73ae5200a97 Pull-request: https://github.com/SerenityOS/serenity/pull/7964 Reviewed-by: https://github.com/MaxWipfli Reviewed-by: https://github.com/gunnarbeutner
@ -2,7 +2,7 @@ serenity_component(
|
||||
TextEditor
|
||||
RECOMMENDED
|
||||
TARGETS TextEditor
|
||||
DEPENDS ImageDecoder RequestServer WebContent
|
||||
DEPENDS ImageDecoder RequestServer WebContent FileSystemAccessServer
|
||||
)
|
||||
|
||||
compile_gml(TextEditorWindow.gml TextEditorWindowGML.h text_editor_window_gml)
|
||||
|
@ -44,6 +44,7 @@
|
||||
namespace TextEditor {
|
||||
|
||||
MainWidget::MainWidget()
|
||||
: m_file_system_access_client(FileSystemAccessClient::construct())
|
||||
{
|
||||
load_from_gml(text_editor_window_gml);
|
||||
|
||||
@ -259,9 +260,9 @@ MainWidget::MainWidget()
|
||||
});
|
||||
|
||||
m_open_action = GUI::CommonActions::make_open_action([this](auto&) {
|
||||
Optional<String> open_path = GUI::FilePicker::get_open_filepath(window());
|
||||
auto fd_response = m_file_system_access_client->prompt_open_file(Core::StandardPaths::home_directory(), Core::OpenMode::ReadWrite);
|
||||
|
||||
if (!open_path.has_value())
|
||||
if (fd_response.error() != 0)
|
||||
return;
|
||||
|
||||
if (editor().document().is_modified()) {
|
||||
@ -272,28 +273,36 @@ MainWidget::MainWidget()
|
||||
return;
|
||||
}
|
||||
|
||||
open_file(open_path.value());
|
||||
read_file_and_close(fd_response.fd()->take_fd(), fd_response.chosen_file().value());
|
||||
});
|
||||
|
||||
m_save_as_action = GUI::CommonActions::make_save_as_action([&](auto&) {
|
||||
Optional<String> save_path = GUI::FilePicker::get_save_filepath(window(), m_name.is_null() ? "Untitled" : m_name, m_extension.is_null() ? "txt" : m_extension);
|
||||
if (!save_path.has_value())
|
||||
auto response = m_file_system_access_client->prompt_save_file(m_name.is_null() ? "Untitled" : m_name, m_extension.is_null() ? "txt" : m_extension, Core::StandardPaths::home_directory(), Core::OpenMode::Truncate | Core::OpenMode::WriteOnly);
|
||||
|
||||
if (response.error() != 0)
|
||||
return;
|
||||
|
||||
if (!m_editor->write_to_file(save_path.value())) {
|
||||
if (!m_editor->write_to_file_and_close(response.fd()->take_fd())) {
|
||||
GUI::MessageBox::show(window(), "Unable to save file.\n", "Error", GUI::MessageBox::Type::Error);
|
||||
return;
|
||||
}
|
||||
// FIXME: It would be cool if this would propagate from GUI::TextDocument somehow.
|
||||
window()->set_modified(false);
|
||||
|
||||
set_path(save_path.value());
|
||||
dbgln("Wrote document to {}", save_path.value());
|
||||
set_path(response.chosen_file().value());
|
||||
dbgln("Wrote document to {}", response.chosen_file().value());
|
||||
});
|
||||
|
||||
m_save_action = GUI::CommonActions::make_save_action([&](auto&) {
|
||||
if (!m_path.is_empty()) {
|
||||
if (!m_editor->write_to_file(m_path)) {
|
||||
auto response = m_file_system_access_client->request_file(m_path, Core::OpenMode::Truncate | Core::OpenMode::WriteOnly);
|
||||
|
||||
if (response.error() != 0)
|
||||
return;
|
||||
|
||||
int fd = response.fd()->take_fd();
|
||||
|
||||
if (!m_editor->write_to_file_and_close(fd)) {
|
||||
GUI::MessageBox::show(window(), "Unable to save file.\n", "Error", GUI::MessageBox::Type::Error);
|
||||
} else {
|
||||
// FIXME: It would be cool if this would propagate from GUI::TextDocument somehow.
|
||||
@ -647,10 +656,12 @@ void MainWidget::update_title()
|
||||
window()->set_title(builder.to_string());
|
||||
}
|
||||
|
||||
bool MainWidget::open_file(const String& path)
|
||||
bool MainWidget::read_file_and_close(int fd, String const& path)
|
||||
{
|
||||
auto file = Core::File::construct(path);
|
||||
if (!file->open(Core::OpenMode::ReadOnly) && file->error() != ENOENT) {
|
||||
VERIFY(path.starts_with("/"sv));
|
||||
auto file = Core::File::construct();
|
||||
|
||||
if (!file->open(fd, Core::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes) && file->error() != ENOENT) {
|
||||
GUI::MessageBox::show(window(), String::formatted("Opening \"{}\" failed: {}", path, strerror(errno)), "Error", GUI::MessageBox::Type::Error);
|
||||
return false;
|
||||
}
|
||||
@ -706,7 +717,12 @@ void MainWidget::drop_event(GUI::DropEvent& event)
|
||||
GUI::MessageBox::show(window(), "TextEditor can only open one file at a time!", "One at a time please!", GUI::MessageBox::Type::Error);
|
||||
return;
|
||||
}
|
||||
open_file(urls.first().path());
|
||||
auto file_response = m_file_system_access_client->request_file(urls.first().path(), Core::OpenMode::ReadOnly);
|
||||
|
||||
if (file_response.error() != 0)
|
||||
return;
|
||||
|
||||
read_file_and_close(file_response.fd()->take_fd(), urls.first().path());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,22 +8,43 @@
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <FileSystemAccessServer/ClientConnection.h>
|
||||
#include <FileSystemAccessServer/FileSystemAccessClientEndpoint.h>
|
||||
#include <FileSystemAccessServer/FileSystemAccessServerEndpoint.h>
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGUI/Application.h>
|
||||
#include <LibGUI/Icon.h>
|
||||
#include <LibGUI/TextEditor.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
#include <LibGUI/Window.h>
|
||||
#include <LibIPC/ServerConnection.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
|
||||
namespace TextEditor {
|
||||
|
||||
class FileSystemAccessClient final
|
||||
: public IPC::ServerConnection<FileSystemAccessClientEndpoint, FileSystemAccessServerEndpoint>
|
||||
, public FileSystemAccessClientEndpoint {
|
||||
C_OBJECT(FileSystemAccessClient)
|
||||
|
||||
public:
|
||||
virtual void die() override
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
explicit FileSystemAccessClient()
|
||||
: IPC::ServerConnection<FileSystemAccessClientEndpoint, FileSystemAccessServerEndpoint>(*this, "/tmp/portal/filesystemaccess")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class MainWidget final : public GUI::Widget {
|
||||
C_OBJECT(MainWidget);
|
||||
|
||||
public:
|
||||
virtual ~MainWidget() override;
|
||||
bool open_file(const String& path);
|
||||
bool read_file_and_close(int fd, String const& path);
|
||||
bool request_close();
|
||||
|
||||
GUI::TextEditor& editor() { return *m_editor; }
|
||||
@ -53,6 +74,8 @@ private:
|
||||
|
||||
virtual void drop_event(GUI::DropEvent&) override;
|
||||
|
||||
NonnullRefPtr<FileSystemAccessClient> m_file_system_access_client;
|
||||
|
||||
RefPtr<GUI::TextEditor> m_editor;
|
||||
String m_path;
|
||||
String m_name;
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include "FileArgument.h"
|
||||
#include "MainWidget.h"
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/StandardPaths.h>
|
||||
#include <LibGUI/Menubar.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
@ -30,6 +32,43 @@ int main(int argc, char** argv)
|
||||
|
||||
parser.parse(argc, argv);
|
||||
|
||||
if (file_to_edit) {
|
||||
FileArgument parsed_argument(file_to_edit);
|
||||
|
||||
auto path_to_unveil = Core::File::real_path_for(parsed_argument.filename());
|
||||
if (unveil(path_to_unveil.characters(), "rwc") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (unveil(Core::StandardPaths::config_directory().characters(), "rw") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/res", "r") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/tmp/portal/launch", "rw") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/tmp/portal/webcontent", "rw") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (unveil("/tmp/portal/filesystemaccess", "rw") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
unveil(nullptr, nullptr);
|
||||
|
||||
StringView preview_mode_view = preview_mode;
|
||||
|
||||
auto app_icon = GUI::Icon::default_icon("app-text-editor");
|
||||
@ -67,8 +106,15 @@ int main(int argc, char** argv)
|
||||
if (file_to_edit) {
|
||||
// A file name was passed, parse any possible line and column numbers included.
|
||||
FileArgument parsed_argument(file_to_edit);
|
||||
if (!text_widget.open_file(parsed_argument.filename()))
|
||||
auto absolute_path = Core::File::real_path_for(parsed_argument.filename());
|
||||
auto file = Core::File::open(absolute_path, Core::OpenMode::ReadWrite);
|
||||
|
||||
if (file.is_error())
|
||||
return 1;
|
||||
|
||||
if (!text_widget.read_file_and_close(file.value()->leak_fd(), absolute_path))
|
||||
return 1;
|
||||
|
||||
text_widget.editor().set_cursor_and_focus_line(parsed_argument.line().value_or(1) - 1, parsed_argument.column().value_or(0));
|
||||
}
|
||||
text_widget.update_title();
|
||||
|
@ -1198,7 +1198,7 @@ void TextEditor::timer_event(Core::TimerEvent&)
|
||||
update_cursor();
|
||||
}
|
||||
|
||||
bool TextEditor::write_to_file(const String& path)
|
||||
bool TextEditor::write_to_file(String const& path)
|
||||
{
|
||||
int fd = open(path.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd < 0) {
|
||||
@ -1206,6 +1206,11 @@ bool TextEditor::write_to_file(const String& path)
|
||||
return false;
|
||||
}
|
||||
|
||||
return write_to_file_and_close(fd);
|
||||
}
|
||||
|
||||
bool TextEditor::write_to_file_and_close(int fd)
|
||||
{
|
||||
ScopeGuard fd_guard = [fd] { close(fd); };
|
||||
|
||||
off_t file_size = 0;
|
||||
|
@ -116,7 +116,8 @@ public:
|
||||
TextRange normalized_selection() const { return m_selection.normalized(); }
|
||||
|
||||
void insert_at_cursor_or_replace_selection(const StringView&);
|
||||
bool write_to_file(const String& path);
|
||||
bool write_to_file(String const& path);
|
||||
bool write_to_file_and_close(int fd);
|
||||
bool has_selection() const { return m_selection.is_valid(); }
|
||||
String selected_text() const;
|
||||
size_t number_of_selected_words() const;
|
||||
|
Loading…
Reference in New Issue
Block a user