diff --git a/Libraries/LibDesktop/Launcher.cpp b/Libraries/LibDesktop/Launcher.cpp index 1b9ac355f0..8a42beebc8 100644 --- a/Libraries/LibDesktop/Launcher.cpp +++ b/Libraries/LibDesktop/Launcher.cpp @@ -24,6 +24,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include @@ -33,6 +34,29 @@ namespace Desktop { +auto Launcher::Details::from_details_str(const String& details_str) -> NonnullRefPtr
+{ + auto details = adopt(*new Details); + auto json = JsonValue::from_string(details_str); + ASSERT(json.has_value()); + auto obj = json.value().as_object(); + details->executable = obj.get("executable").to_string(); + details->name = obj.get("name").to_string(); + if (auto type_value = obj.get_ptr("type")) { + auto type_str = type_value->to_string(); + if (type_str == "userpreferred") + details->launcher_type = LauncherType::UserPreferred; + else if (type_str == "userdefault") + details->launcher_type = LauncherType::UserDefault; + } + if (auto icons_value = obj.get_ptr("icons")) { + icons_value->as_object().for_each_member([&](auto& name, auto& value) { + details->icons.set(name, value.to_string()); + }); + } + return details; +} + class LaunchServerConnection : public IPC::ServerConnection , public LaunchClientEndpoint { C_OBJECT(LaunchServerConnection) @@ -57,10 +81,26 @@ bool Launcher::open(const URL& url, const String& handler_name) return connection->send_sync(url, handler_name)->response(); } +bool Launcher::open(const URL& url, const Details& details) +{ + return open(url, details.executable); +} + Vector Launcher::get_handlers_for_url(const URL& url) { auto connection = LaunchServerConnection::construct(); return connection->send_sync(url.to_string())->handlers(); } +auto Launcher::get_handlers_with_details_for_url(const URL& url) -> NonnullRefPtrVector
+{ + auto connection = LaunchServerConnection::construct(); + auto details = connection->send_sync(url.to_string())->handlers_details(); + NonnullRefPtrVector
handlers_with_details; + for (auto& value : details) { + handlers_with_details.append(Details::from_details_str(value)); + } + return handlers_with_details; +} + } diff --git a/Libraries/LibDesktop/Launcher.h b/Libraries/LibDesktop/Launcher.h index b8c792a01b..0dc0748231 100644 --- a/Libraries/LibDesktop/Launcher.h +++ b/Libraries/LibDesktop/Launcher.h @@ -27,13 +27,35 @@ #pragma once #include +#include +#include +#include +#include namespace Desktop { class Launcher { public: + enum class LauncherType { + Default = 0, + UserPreferred, + UserDefault + }; + + struct Details: public RefCounted
{ + + String name; + String executable; + HashMap icons; + LauncherType launcher_type { LauncherType::Default }; + + static NonnullRefPtr
from_details_str(const String&); + }; + static bool open(const URL&, const String& handler_name = {}); + static bool open(const URL&, const Details& details); static Vector get_handlers_for_url(const URL&); + static NonnullRefPtrVector
get_handlers_with_details_for_url(const URL&); }; } diff --git a/Services/LaunchServer/ClientConnection.cpp b/Services/LaunchServer/ClientConnection.cpp index 533ae8cbff..46f716564e 100644 --- a/Services/LaunchServer/ClientConnection.cpp +++ b/Services/LaunchServer/ClientConnection.cpp @@ -67,4 +67,11 @@ OwnPtr ClientConnection::hand return make(result); } +OwnPtr ClientConnection::handle(const Messages::LaunchServer::GetHandlersWithDetailsForURL& request) +{ + URL url(request.url()); + auto result = Launcher::the().handlers_with_details_for_url(url); + return make(result); +} + } diff --git a/Services/LaunchServer/ClientConnection.h b/Services/LaunchServer/ClientConnection.h index 79019a86e8..f919b328f8 100644 --- a/Services/LaunchServer/ClientConnection.h +++ b/Services/LaunchServer/ClientConnection.h @@ -45,5 +45,6 @@ private: virtual OwnPtr handle(const Messages::LaunchServer::Greet&) override; virtual OwnPtr handle(const Messages::LaunchServer::OpenURL&) override; virtual OwnPtr handle(const Messages::LaunchServer::GetHandlersForURL&) override; + virtual OwnPtr handle(const Messages::LaunchServer::GetHandlersWithDetailsForURL&) override; }; } diff --git a/Services/LaunchServer/LaunchServer.ipc b/Services/LaunchServer/LaunchServer.ipc index feeb87015d..f6871ef0a8 100644 --- a/Services/LaunchServer/LaunchServer.ipc +++ b/Services/LaunchServer/LaunchServer.ipc @@ -3,4 +3,5 @@ endpoint LaunchServer = 101 Greet() => (i32 client_id) OpenURL(URL url, String handler_name) => (bool response) GetHandlersForURL(URL url) => (Vector handlers) + GetHandlersWithDetailsForURL(URL url) => (Vector handlers_details) } diff --git a/Services/LaunchServer/Launcher.cpp b/Services/LaunchServer/Launcher.cpp index 698bca0082..4715de42a6 100644 --- a/Services/LaunchServer/Launcher.cpp +++ b/Services/LaunchServer/Launcher.cpp @@ -26,7 +26,11 @@ #include "Launcher.h" #include +#include +#include +#include #include +#include #include #include #include @@ -38,6 +42,47 @@ namespace LaunchServer { static Launcher* s_the; static bool spawn(String executable, String argument); +String Handler::name_from_executable(const StringView& executable) +{ + auto separator = executable.find_last_of('/'); + if (separator.has_value()) { + auto start = separator.value() + 1; + return executable.substring_view(start, executable.length() - start); + } + return executable; +} + +void Handler::from_executable(Type handler_type, const String& executable) +{ + this->handler_type = handler_type; + this->name = name_from_executable(executable); + this->executable = executable; +} + +String Handler::to_details_str() const +{ + StringBuilder builder; + JsonObjectSerializer obj { builder }; + obj.add("executable", executable); + obj.add("name", name); + switch (handler_type) { + case Type::UserDefault: + obj.add("type", "userdefault"); + break; + case Type::UserPreferred: + obj.add("type", "userpreferred"); + break; + default: + break; + } + JsonObject icons_obj; + for (auto& icon : icons) + icons_obj.set(icon.key, icon.value); + obj.add("icons", move(icons_obj)); + obj.finish(); + return builder.build(); +} + Launcher::Launcher() { ASSERT(s_the == nullptr); @@ -61,6 +106,14 @@ void Launcher::load_handlers(const String& af_dir) return table; }; + auto load_hashmap = [](auto& af, auto& group) { + HashMap map; + auto keys = af->keys(group); + for (auto& key : keys) + map.set(key, af->read_entry(group, key)); + + return map; + }; Core::DirIterator dt(af_dir, Core::DirIterator::SkipDots); while (dt.has_next()) { @@ -73,7 +126,8 @@ void Launcher::load_handlers(const String& af_dir) auto app_executable = af->read_entry("App", "Executable"); auto file_types = load_hashtable(af, "FileTypes"); auto protocols = load_hashtable(af, "Protocols"); - m_handlers.set(app_executable, { app_name, app_executable, file_types, protocols }); + auto icons = load_hashmap(af, "Icons"); + m_handlers.set(app_executable, { Handler::Type::Default, move(app_name), app_executable, move(file_types), move(protocols), move(icons) }); } } @@ -90,12 +144,42 @@ void Launcher::load_config(const Core::ConfigFile& cfg) Vector Launcher::handlers_for_url(const URL& url) { - if (url.protocol() == "file") - return handlers_for_path(url.path()); + Vector handlers; + if (url.protocol() == "file") { + for_each_handler_for_path(url.path(), [&](auto& handler) -> bool { + handlers.append(handler.executable); + return true; + }); + } else { + for_each_handler(url.protocol(), m_protocol_handlers, [&](const auto& handler) -> bool { + if (handler.handler_type != Handler::Type::Default || handler.protocols.contains(url.protocol())) { + handlers.append(handler.executable); + return true; + } + return false; + }); + } + return handlers; +} - return handlers_for(url.protocol(), m_protocol_handlers, [](auto& handler, auto& key) { - return handler.protocols.contains(key); - }); +Vector Launcher::handlers_with_details_for_url(const URL& url) +{ + Vector handlers; + if (url.protocol() == "file") { + for_each_handler_for_path(url.path(), [&](auto& handler) -> bool { + handlers.append(handler.to_details_str()); + return true; + }); + } else { + for_each_handler(url.protocol(), m_protocol_handlers, [&](const auto& handler) -> bool { + if (handler.handler_type != Handler::Type::Default || handler.protocols.contains(url.protocol())) { + handlers.append(handler.to_details_str()); + return true; + } + return false; + }); + } + return handlers; } bool Launcher::open_url(const URL& url, const String& handler_name) @@ -135,6 +219,19 @@ bool spawn(String executable, String argument) return true; } +Handler Launcher::get_handler_for_executable(Handler::Type handler_type, const String& executable) const +{ + Handler handler; + auto existing_handler = m_handlers.get(executable); + if (existing_handler.has_value()) { + handler = existing_handler.value(); + handler.handler_type = handler_type; + } else { + handler.from_executable(handler_type, executable); + } + return handler; +} + bool Launcher::open_with_user_preferences(const HashMap& user_preferences, const String key, const String argument, const String default_program) { auto program_path = user_preferences.get(key); @@ -151,45 +248,46 @@ bool Launcher::open_with_user_preferences(const HashMap& user_pr return spawn(default_program, argument); } -Vector Launcher::handlers_for(const String& key, HashMap& user_preference, Function handler_matches) +void Launcher::for_each_handler(const String& key, HashMap& user_preference, Function f) { - Vector handlers; - auto user_preferred = user_preference.get(key); if (user_preferred.has_value()) - handlers.append(user_preferred.value()); + f(get_handler_for_executable(Handler::Type::UserPreferred, user_preferred.value())); + size_t counted = 0; for (auto& handler : m_handlers) { // Skip over the existing item in the list if (user_preferred.has_value() && user_preferred.value() == handler.value.executable) continue; - if (handler_matches(handler.value, key)) - handlers.append(handler.value.executable); + if (f(handler.value)) + counted++; } auto user_default = user_preference.get("*"); - if (handlers.size() == 0 && user_default.has_value()) - handlers.append(user_default.value()); - - return handlers; + if (counted == 0 && user_default.has_value()) + f(get_handler_for_executable(Handler::Type::UserDefault, user_default.value())); } -Vector Launcher::handlers_for_path(const String& path) +void Launcher::for_each_handler_for_path(const String& path, Function f) { struct stat st; if (stat(path.characters(), &st) < 0) { perror("stat"); - return {}; + return; } // TODO: Make directory opening configurable - if (S_ISDIR(st.st_mode)) - return { "/bin/FileManager" }; + if (S_ISDIR(st.st_mode)) { + f(get_handler_for_executable(Handler::Type::Default, "/bin/FileManager")); + return; + } auto extension = LexicalPath(path).extension().to_lowercase(); - return handlers_for(extension, m_file_handlers, [](auto& handler, auto& key) { - return handler.file_types.contains(key); + for_each_handler(extension, m_file_handlers, [&](const auto& handler) -> bool { + if (handler.handler_type != Handler::Type::Default || handler.file_types.contains(extension)) + return f(handler); + return false; }); } diff --git a/Services/LaunchServer/Launcher.h b/Services/LaunchServer/Launcher.h index ff241c6c7f..2796cdb960 100644 --- a/Services/LaunchServer/Launcher.h +++ b/Services/LaunchServer/Launcher.h @@ -34,10 +34,21 @@ namespace LaunchServer { struct Handler { + enum class Type { + Default = 0, + UserPreferred, + UserDefault + }; + Type handler_type; String name; String executable; - HashTable file_types; - HashTable protocols; + HashTable file_types {}; + HashTable protocols {}; + HashMap icons {}; + + static String name_from_executable(const StringView&); + void from_executable(Type, const String&); + String to_details_str() const; }; class Launcher { @@ -49,14 +60,16 @@ public: void load_config(const Core::ConfigFile&); bool open_url(const URL&, const String& handler_name); Vector handlers_for_url(const URL&); + Vector handlers_with_details_for_url(const URL&); private: HashMap m_handlers; HashMap m_protocol_handlers; HashMap m_file_handlers; - Vector handlers_for(const String& key, HashMap& user_preferences, Function handler_matches); - Vector handlers_for_path(const String&); + Handler get_handler_for_executable(Handler::Type, const String&) const; + void for_each_handler(const String& key, HashMap& user_preferences, Function f); + void for_each_handler_for_path(const String&, Function f); bool open_file_url(const URL&); bool open_with_user_preferences(const HashMap& user_preferences, const String key, const String argument, const String default_program); bool open_with_handler_name(const URL&, const String& handler_name);