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:
Timothy 2021-06-30 06:13:06 -07:00 committed by Andreas Kling
parent 41ce2debda
commit 73ae5200a9
Notes: sideshowbarker 2024-07-18 09:25:53 +09:00
6 changed files with 109 additions and 18 deletions

View File

@ -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)

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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;