diff --git a/Userland/Applications/TextEditor/CMakeLists.txt b/Userland/Applications/TextEditor/CMakeLists.txt index a586210eae..94d15da792 100644 --- a/Userland/Applications/TextEditor/CMakeLists.txt +++ b/Userland/Applications/TextEditor/CMakeLists.txt @@ -15,4 +15,4 @@ set(SOURCES ) serenity_app(TextEditor ICON app-text-editor) -target_link_libraries(TextEditor LibWeb LibMarkdown LibGUI LibShell LibRegex LibDesktop LibCpp LibJS LibSQL) +target_link_libraries(TextEditor LibWeb LibMarkdown LibGUI LibShell LibRegex LibDesktop LibCpp LibJS LibSQL LibFileSystemAccessClient) diff --git a/Userland/Applications/TextEditor/MainWidget.cpp b/Userland/Applications/TextEditor/MainWidget.cpp index 8ed368b6ec..bcf4a06b40 100644 --- a/Userland/Applications/TextEditor/MainWidget.cpp +++ b/Userland/Applications/TextEditor/MainWidget.cpp @@ -44,7 +44,6 @@ namespace TextEditor { MainWidget::MainWidget() - : m_file_system_access_client(FileSystemAccessClient::construct()) { load_from_gml(text_editor_window_gml); @@ -260,11 +259,11 @@ MainWidget::MainWidget() }); m_open_action = GUI::CommonActions::make_open_action([this](auto&) { - auto fd_response = m_file_system_access_client->prompt_open_file(Core::StandardPaths::home_directory(), Core::OpenMode::ReadOnly); + auto response = FileSystemAccessClient::Client::the().open_file(window()->window_id()); - if (fd_response.error() != 0) { - if (fd_response.error() != -1) - GUI::MessageBox::show_error(window(), String::formatted("Opening \"{}\" failed: {}", fd_response.chosen_file().value(), strerror(fd_response.error()))); + if (response.error != 0) { + if (response.error != -1) + GUI::MessageBox::show_error(window(), String::formatted("Opening \"{}\" failed: {}", *response.chosen_file, strerror(response.error))); return; } @@ -276,40 +275,40 @@ MainWidget::MainWidget() return; } - read_file_and_close(fd_response.fd()->take_fd(), fd_response.chosen_file().value()); + read_file_and_close(*response.fd, *response.chosen_file); }); m_save_as_action = GUI::CommonActions::make_save_as_action([&](auto&) { - 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); + auto response = FileSystemAccessClient::Client::the().save_file(window()->window_id(), m_name, m_extension); - if (response.error() != 0) { - if (response.error() != -1) - GUI::MessageBox::show_error(window(), String::formatted("Saving \"{}\" failed: {}", response.chosen_file().value(), strerror(response.error()))); + if (response.error != 0) { + if (response.error != -1) + GUI::MessageBox::show_error(window(), String::formatted("Saving \"{}\" failed: {}", *response.chosen_file, strerror(response.error))); return; } - if (!m_editor->write_to_file_and_close(response.fd()->take_fd())) { + if (!m_editor->write_to_file_and_close(*response.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(response.chosen_file().value()); - dbgln("Wrote document to {}", response.chosen_file().value()); + set_path(*response.chosen_file); + dbgln("Wrote document to {}", *response.chosen_file); }); m_save_action = GUI::CommonActions::make_save_action([&](auto&) { if (!m_path.is_empty()) { - auto response = m_file_system_access_client->request_file(m_path, Core::OpenMode::Truncate | Core::OpenMode::WriteOnly); + auto response = FileSystemAccessClient::Client::the().request_file(window()->window_id(), m_path, Core::OpenMode::Truncate | Core::OpenMode::WriteOnly); - if (response.error() != 0) { - if (response.error() != -1) - GUI::MessageBox::show_error(window(), String::formatted("Unable to save file: {}", strerror(response.error()))); + if (response.error != 0) { + if (response.error != -1) + GUI::MessageBox::show_error(window(), String::formatted("Unable to save file: {}", strerror(response.error))); return; } - int fd = response.fd()->take_fd(); + int fd = *response.fd; if (!m_editor->write_to_file_and_close(fd)) { GUI::MessageBox::show(window(), "Unable to save file.\n", "Error", GUI::MessageBox::Type::Error); @@ -726,12 +725,14 @@ 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; } - auto file_response = m_file_system_access_client->request_file(urls.first().path(), Core::OpenMode::ReadOnly); - if (file_response.error() != 0) + // TODO: A drop event should be considered user consent for opening a file + auto file_response = FileSystemAccessClient::Client::the().request_file(window()->window_id(), urls.first().path(), Core::OpenMode::ReadOnly); + + if (file_response.error != 0) return; - read_file_and_close(file_response.fd()->take_fd(), urls.first().path()); + read_file_and_close(*file_response.fd, urls.first().path()); } } diff --git a/Userland/Applications/TextEditor/MainWidget.h b/Userland/Applications/TextEditor/MainWidget.h index 190a206349..b72d537c00 100644 --- a/Userland/Applications/TextEditor/MainWidget.h +++ b/Userland/Applications/TextEditor/MainWidget.h @@ -8,37 +8,17 @@ #include #include -#include -#include -#include +#include #include #include #include #include #include #include -#include #include namespace TextEditor { -class FileSystemAccessClient final - : public IPC::ServerConnection - , public FileSystemAccessClientEndpoint { - C_OBJECT(FileSystemAccessClient) - -public: - virtual void die() override - { - } - -private: - explicit FileSystemAccessClient() - : IPC::ServerConnection(*this, "/tmp/portal/filesystemaccess") - { - } -}; - class MainWidget final : public GUI::Widget { C_OBJECT(MainWidget); @@ -74,8 +54,6 @@ private: virtual void drop_event(GUI::DropEvent&) override; - NonnullRefPtr m_file_system_access_client; - RefPtr m_editor; String m_path; String m_name; diff --git a/Userland/Libraries/CMakeLists.txt b/Userland/Libraries/CMakeLists.txt index e023df3bc0..5299c46e8a 100644 --- a/Userland/Libraries/CMakeLists.txt +++ b/Userland/Libraries/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(LibDesktop) add_subdirectory(LibDiff) add_subdirectory(LibDl) add_subdirectory(LibELF) +add_subdirectory(LibFileSystemAccessClient) add_subdirectory(LibGemini) add_subdirectory(LibGfx) add_subdirectory(LibGL) diff --git a/Userland/Libraries/LibFileSystemAccessClient/CMakeLists.txt b/Userland/Libraries/LibFileSystemAccessClient/CMakeLists.txt new file mode 100644 index 0000000000..e8cc8600c5 --- /dev/null +++ b/Userland/Libraries/LibFileSystemAccessClient/CMakeLists.txt @@ -0,0 +1,12 @@ +set(SOURCES + Client.cpp +) + +set(GENERATED_SOURCES + ../../Services/FileSystemAccessServer/FileSystemAccessClientEndpoint.h + ../../Services/FileSystemAccessServer/FileSystemAccessServerEndpoint.h +) + +serenity_lib(LibFileSystemAccessClient filesystemaccessclient) +target_link_libraries(LibFileSystemAccessClient LibIPC) +add_dependencies(LibFileSystemAccessClient WindowServer) diff --git a/Userland/Libraries/LibFileSystemAccessClient/Client.cpp b/Userland/Libraries/LibFileSystemAccessClient/Client.cpp new file mode 100644 index 0000000000..dbd9463314 --- /dev/null +++ b/Userland/Libraries/LibFileSystemAccessClient/Client.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021, timmot + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// FIXME: LibIPC Decoder and Encoder are sensitive to include order here +// clang-format off +#include +// clang-format on +#include +#include +#include + +namespace FileSystemAccessClient { + +static RefPtr s_the = nullptr; + +Client& Client::the() +{ + if (!s_the || !s_the->is_open()) + s_the = Client::construct(); + return *s_the; +} + +Result Client::request_file(i32 parent_window_id, String const& path, Core::OpenMode mode) +{ + m_promise = Core::Promise::construct(); + auto window_server_client_id = GUI::WindowServerConnection::the().expose_client_id(); + + async_request_file(window_server_client_id, parent_window_id, path, mode); + + return m_promise->await(); +} + +Result Client::open_file(i32 parent_window_id) +{ + m_promise = Core::Promise::construct(); + auto window_server_client_id = GUI::WindowServerConnection::the().expose_client_id(); + + async_prompt_open_file(window_server_client_id, parent_window_id, Core::StandardPaths::home_directory(), Core::OpenMode::ReadOnly); + + return m_promise->await(); +} + +Result Client::save_file(i32 parent_window_id, String const& name, String const ext) +{ + m_promise = Core::Promise::construct(); + auto window_server_client_id = GUI::WindowServerConnection::the().expose_client_id(); + + async_prompt_save_file(window_server_client_id, parent_window_id, name.is_null() ? "Untitled" : name, ext.is_null() ? "txt" : ext, Core::StandardPaths::home_directory(), Core::OpenMode::Truncate | Core::OpenMode::WriteOnly); + + return m_promise->await(); +} + +void Client::handle_prompt_end(i32 error, Optional const& fd, Optional const& chosen_file) +{ + VERIFY(m_promise); + + if (error == 0) { + m_promise->resolve({ error, fd->take_fd(), *chosen_file }); + } else { + m_promise->resolve({ error, {}, chosen_file }); + } +} + +void Client::die() +{ + if (m_promise) + handle_prompt_end(ECONNRESET, {}, ""); +} + +} diff --git a/Userland/Libraries/LibFileSystemAccessClient/Client.h b/Userland/Libraries/LibFileSystemAccessClient/Client.h new file mode 100644 index 0000000000..3fb7feb04f --- /dev/null +++ b/Userland/Libraries/LibFileSystemAccessClient/Client.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021, timmot + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace FileSystemAccessClient { + +struct Result { + i32 error; + Optional fd; + Optional chosen_file; +}; + +class Client final + : public IPC::ServerConnection + , public FileSystemAccessClientEndpoint { + C_OBJECT(Client) + +public: + Result request_file(i32 parent_window_id, String const& path, Core::OpenMode mode); + Result open_file(i32 parent_window_id); + Result save_file(i32 parent_window_id, String const& name, String const ext); + + static Client& the(); + +protected: + void die() override; + +private: + explicit Client() + : IPC::ServerConnection(*this, "/tmp/portal/filesystemaccess") + { + } + + virtual void handle_prompt_end(i32 error, Optional const& fd, Optional const& chosen_file) override; + + RefPtr> m_promise; +}; + +} diff --git a/Userland/Services/FileSystemAccessServer/CMakeLists.txt b/Userland/Services/FileSystemAccessServer/CMakeLists.txt index 67eeacb84a..9bcce5ee1a 100644 --- a/Userland/Services/FileSystemAccessServer/CMakeLists.txt +++ b/Userland/Services/FileSystemAccessServer/CMakeLists.txt @@ -16,3 +16,4 @@ set(SOURCES serenity_bin(FileSystemAccessServer) target_link_libraries(FileSystemAccessServer LibCore LibIPC LibGUI) +add_dependencies(FileSystemAccessServer WindowServer) diff --git a/Userland/Services/FileSystemAccessServer/ClientConnection.cpp b/Userland/Services/FileSystemAccessServer/ClientConnection.cpp index d863a22c47..aaa2e78055 100644 --- a/Userland/Services/FileSystemAccessServer/ClientConnection.cpp +++ b/Userland/Services/FileSystemAccessServer/ClientConnection.cpp @@ -4,6 +4,10 @@ * SPDX-License-Identifier: BSD-2-Clause */ +// FIXME: LibIPC Decoder and Encoder are sensitive to include order here +// clang-format off +#include +// clang-format on #include #include #include @@ -33,7 +37,20 @@ void ClientConnection::die() exit(0); } -Messages::FileSystemAccessServer::RequestFileResponse ClientConnection::request_file(String const& path, Core::OpenMode const& requested_access) +RefPtr ClientConnection::create_dummy_child_window(i32 window_server_client_id, i32 parent_window_id) +{ + auto window = GUI::Window::construct(); + window->set_opacity(0); + window->set_frameless(true); + auto rect = GUI::WindowServerConnection::the().get_window_rect_from_client(window_server_client_id, parent_window_id); + window->set_rect(rect); + window->show(); + GUI::WindowServerConnection::the().async_set_window_parent_from_client(window_server_client_id, parent_window_id, window->window_id()); + + return window; +} + +void ClientConnection::request_file(i32 window_server_client_id, i32 parent_window_id, String const& path, Core::OpenMode const& requested_access) { VERIFY(path.starts_with("/"sv)); @@ -47,21 +64,23 @@ Messages::FileSystemAccessServer::RequestFileResponse ClientConnection::request_ approved = has_flag(maybe_permissions.value(), relevant_permissions); if (!approved) { - StringBuilder builder; - if (has_flag(requested_access, Core::OpenMode::ReadOnly)) - builder.append('r'); + String access_string; - if (has_flag(requested_access, Core::OpenMode::WriteOnly)) - builder.append('w'); - - auto access_string = builder.to_string(); + if (has_flag(requested_access, Core::OpenMode::ReadWrite)) + access_string = "read and write"; + else if (has_flag(requested_access, Core::OpenMode::ReadOnly)) + access_string = "read from"; + else if (has_flag(requested_access, Core::OpenMode::WriteOnly)) + access_string = "write to"; auto pid = this->socket().peer_pid(); auto exe_link = LexicalPath("/proc").append(String::number(pid)).append("exe").string(); auto exe_path = Core::File::real_path_for(exe_link); auto exe_name = LexicalPath::basename(exe_path); - auto result = GUI::MessageBox::show(nullptr, String::formatted("Give {} ({}) \"{}\" access to \"{}\"?", exe_name, pid, access_string, path), "File Permissions Requested", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::YesNo); + auto main_window = create_dummy_child_window(window_server_client_id, parent_window_id); + + auto result = GUI::MessageBox::show(main_window, String::formatted("Allow {} ({}) to {} \"{}\"?", exe_name, pid, access_string, path), "File Permissions Requested", GUI::MessageBox::Type::Warning, GUI::MessageBox::InputType::YesNo); approved = result == GUI::MessageBox::ExecYes; @@ -80,40 +99,40 @@ Messages::FileSystemAccessServer::RequestFileResponse ClientConnection::request_ if (file.is_error()) { dbgln("FileSystemAccessServer: Couldn't open {}, error {}", path, file.error()); - - return { errno, Optional {} }; + async_handle_prompt_end(errno, Optional {}, path); + } else { + async_handle_prompt_end(0, IPC::File(file.value()->leak_fd(), IPC::File::CloseAfterSending), path); } - - return { 0, IPC::File(file.value()->leak_fd(), IPC::File::CloseAfterSending) }; + } else { + async_handle_prompt_end(-1, Optional {}, path); } - - return { -1, Optional {} }; } -Messages::FileSystemAccessServer::PromptOpenFileResponse ClientConnection::prompt_open_file(String const& path_to_view, Core::OpenMode const& requested_access) +void ClientConnection::prompt_open_file(i32 window_server_client_id, i32 parent_window_id, String const& path_to_view, Core::OpenMode const& requested_access) { auto relevant_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly); VERIFY(relevant_permissions != Core::OpenMode::NotOpen); - auto main_window = GUI::Window::construct(); + auto main_window = create_dummy_child_window(window_server_client_id, parent_window_id); + auto user_picked_file = GUI::FilePicker::get_open_filepath(main_window, "Select file", path_to_view); - return prompt_helper(user_picked_file, requested_access); + prompt_helper(user_picked_file, requested_access); } -Messages::FileSystemAccessServer::PromptSaveFileResponse ClientConnection::prompt_save_file(String const& name, String const& ext, String const& path_to_view, Core::OpenMode const& requested_access) +void ClientConnection::prompt_save_file(i32 window_server_client_id, i32 parent_window_id, String const& name, String const& ext, String const& path_to_view, Core::OpenMode const& requested_access) { auto relevant_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly); VERIFY(relevant_permissions != Core::OpenMode::NotOpen); - auto main_window = GUI::Window::construct(); + auto main_window = create_dummy_child_window(window_server_client_id, parent_window_id); + auto user_picked_file = GUI::FilePicker::get_save_filepath(main_window, name, ext, path_to_view); - return prompt_helper(user_picked_file, requested_access); + prompt_helper(user_picked_file, requested_access); } -template -T ClientConnection::prompt_helper(Optional const& user_picked_file, Core::OpenMode const& requested_access) +void ClientConnection::prompt_helper(Optional const& user_picked_file, Core::OpenMode const& requested_access) { if (user_picked_file.has_value()) { VERIFY(user_picked_file->starts_with("/"sv)); @@ -122,20 +141,20 @@ T ClientConnection::prompt_helper(Optional const& user_picked_file, Core if (file.is_error()) { dbgln("FileSystemAccessServer: Couldn't open {}, error {}", user_picked_file.value(), file.error()); - return { errno, Optional {}, user_picked_file.value() }; + async_handle_prompt_end(errno, Optional {}, user_picked_file); + } else { + auto maybe_permissions = m_approved_files.get(user_picked_file.value()); + auto new_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly); + if (maybe_permissions.has_value()) + new_permissions |= maybe_permissions.value(); + + m_approved_files.set(user_picked_file.value(), new_permissions); + + async_handle_prompt_end(0, IPC::File(file.value()->leak_fd(), IPC::File::CloseAfterSending), user_picked_file); } - - auto maybe_permissions = m_approved_files.get(user_picked_file.value()); - auto new_permissions = requested_access & (Core::OpenMode::ReadOnly | Core::OpenMode::WriteOnly); - if (maybe_permissions.has_value()) - new_permissions |= maybe_permissions.value(); - - m_approved_files.set(user_picked_file.value(), new_permissions); - - return { 0, IPC::File(file.value()->leak_fd(), IPC::File::CloseAfterSending), user_picked_file.value() }; + } else { + async_handle_prompt_end(-1, Optional {}, Optional {}); } - - return { -1, Optional {}, Optional {} }; } } diff --git a/Userland/Services/FileSystemAccessServer/ClientConnection.h b/Userland/Services/FileSystemAccessServer/ClientConnection.h index 2f738bb4e7..9261bc029c 100644 --- a/Userland/Services/FileSystemAccessServer/ClientConnection.h +++ b/Userland/Services/FileSystemAccessServer/ClientConnection.h @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace FileSystemAccessServer { @@ -25,12 +26,12 @@ public: virtual void die() override; private: - virtual Messages::FileSystemAccessServer::RequestFileResponse request_file(String const&, Core::OpenMode const&) override; - virtual Messages::FileSystemAccessServer::PromptOpenFileResponse prompt_open_file(String const&, Core::OpenMode const&) override; - virtual Messages::FileSystemAccessServer::PromptSaveFileResponse prompt_save_file(String const&, String const&, String const&, Core::OpenMode const&) override; + virtual void request_file(i32, i32, String const&, Core::OpenMode const&) override; + virtual void prompt_open_file(i32, i32, String const&, Core::OpenMode const&) override; + virtual void prompt_save_file(i32, i32, String const&, String const&, String const&, Core::OpenMode const&) override; - template - T prompt_helper(Optional const&, Core::OpenMode const&); + void prompt_helper(Optional const&, Core::OpenMode const&); + RefPtr create_dummy_child_window(i32, i32); HashMap m_approved_files; }; diff --git a/Userland/Services/FileSystemAccessServer/FileSystemAccessClient.ipc b/Userland/Services/FileSystemAccessServer/FileSystemAccessClient.ipc index fa99132c8f..180d23304b 100644 --- a/Userland/Services/FileSystemAccessServer/FileSystemAccessClient.ipc +++ b/Userland/Services/FileSystemAccessServer/FileSystemAccessClient.ipc @@ -1,3 +1,4 @@ endpoint FileSystemAccessClient { + handle_prompt_end(i32 error, Optional fd, Optional chosen_file) =| } diff --git a/Userland/Services/FileSystemAccessServer/FileSystemAccessServer.ipc b/Userland/Services/FileSystemAccessServer/FileSystemAccessServer.ipc index 6f10d9ba61..15c3735d8e 100644 --- a/Userland/Services/FileSystemAccessServer/FileSystemAccessServer.ipc +++ b/Userland/Services/FileSystemAccessServer/FileSystemAccessServer.ipc @@ -3,7 +3,7 @@ endpoint FileSystemAccessServer { - request_file(String path, Core::OpenMode requested_access) => (i32 error, Optional fd) - prompt_open_file(String path_to_view, Core::OpenMode requested_access) => (i32 error, Optional fd, Optional chosen_file) - prompt_save_file(String title, String ext, String path_to_view, Core::OpenMode requested_access) => (i32 error, Optional fd, Optional chosen_file) + request_file(i32 window_server_client_id, i32 window_id, String path, Core::OpenMode requested_access) =| + prompt_open_file(i32 window_server_client_id, i32 window_id, String path_to_view, Core::OpenMode requested_access) =| + prompt_save_file(i32 window_server_client_id, i32 window_id,String title, String ext, String path_to_view, Core::OpenMode requested_access) =| }