From 3c5f75ed5347668d09c8cc488c601f5d6b1b7bd3 Mon Sep 17 00:00:00 2001 From: Nicholas Hollett Date: Sat, 16 May 2020 14:16:43 +0100 Subject: [PATCH] LaunchServer: Discover handlers from *.af files, allow launching based on a known handler Adds metadata about apps for what file types and protocols they can handle, then consumes that in the LaunchServer. The LaunchServer can then use that to offer multiple options for what apps can open a given URL. Callers can then pass back the handler name to the LaunchServer to use an alternate app :) --- Base/res/apps/Browser.af | 4 + Base/res/apps/TextEditor.af | 3 + Libraries/LibDesktop/Launcher.cpp | 6 +- Libraries/LibDesktop/Launcher.h | 2 +- Services/LaunchServer/ClientConnection.cpp | 2 +- Services/LaunchServer/LaunchServer.ipc | 2 +- Services/LaunchServer/Launcher.cpp | 92 +++++++++++++++++++--- Services/LaunchServer/Launcher.h | 16 +++- Services/LaunchServer/main.cpp | 1 + 9 files changed, 111 insertions(+), 17 deletions(-) diff --git a/Base/res/apps/Browser.af b/Base/res/apps/Browser.af index acd6b598172745..895d570f00d310 100644 --- a/Base/res/apps/Browser.af +++ b/Base/res/apps/Browser.af @@ -6,3 +6,7 @@ Category=Internet [Icons] 16x16=/res/icons/16x16/app-browser.png 32x32=/res/icons/32x32/app-browser.png + +[Launcher] +FileTypes=html,md +Protocols=http,https diff --git a/Base/res/apps/TextEditor.af b/Base/res/apps/TextEditor.af index e66c69cc5cece1..4916d03e26a1eb 100644 --- a/Base/res/apps/TextEditor.af +++ b/Base/res/apps/TextEditor.af @@ -6,3 +6,6 @@ Category=Utilities [Icons] 16x16=/res/icons/TextEditor16.png 32x32=/res/icons/32x32/app-texteditor.png + +[Launcher] +FileTypes=txt,md,html diff --git a/Libraries/LibDesktop/Launcher.cpp b/Libraries/LibDesktop/Launcher.cpp index 43fd5a7c3b98db..0d35c0c618ade3 100644 --- a/Libraries/LibDesktop/Launcher.cpp +++ b/Libraries/LibDesktop/Launcher.cpp @@ -48,13 +48,13 @@ class LaunchServerConnection : public IPC::ServerConnection(*this, "/tmp/portal/launch") { } - virtual void handle(const Messages::LaunchClient::Dummy&) override { } + virtual void handle(const Messages::LaunchClient::Dummy&) override {} }; -bool Launcher::open(const URL& url) +bool Launcher::open(const URL& url, const String& handler_name) { auto connection = LaunchServerConnection::construct(); - return connection->send_sync(url.to_string())->response(); + return connection->send_sync(url.to_string(), handler_name)->response(); } Vector Launcher::get_handlers_for_url(const URL& url) diff --git a/Libraries/LibDesktop/Launcher.h b/Libraries/LibDesktop/Launcher.h index 7acf2cc5b8494a..b8c792a01b42e3 100644 --- a/Libraries/LibDesktop/Launcher.h +++ b/Libraries/LibDesktop/Launcher.h @@ -32,7 +32,7 @@ namespace Desktop { class Launcher { public: - static bool open(const URL&); + static bool open(const URL&, const String& handler_name = {}); static Vector get_handlers_for_url(const URL&); }; diff --git a/Services/LaunchServer/ClientConnection.cpp b/Services/LaunchServer/ClientConnection.cpp index 35bc944b09dca5..e69d6fce86439a 100644 --- a/Services/LaunchServer/ClientConnection.cpp +++ b/Services/LaunchServer/ClientConnection.cpp @@ -56,7 +56,7 @@ OwnPtr ClientConnection::handle(const Mes OwnPtr ClientConnection::handle(const Messages::LaunchServer::OpenUrl& request) { URL url(request.url()); - auto result = Launcher::the().open_url(url); + auto result = Launcher::the().open_url(url, request.handler_name()); return make(result); } diff --git a/Services/LaunchServer/LaunchServer.ipc b/Services/LaunchServer/LaunchServer.ipc index f2bb3bbd1db339..a4d31a00edf19c 100644 --- a/Services/LaunchServer/LaunchServer.ipc +++ b/Services/LaunchServer/LaunchServer.ipc @@ -1,6 +1,6 @@ endpoint LaunchServer = 101 { Greet() => (i32 client_id) - OpenUrl(String url) => (bool response) + OpenUrl(String url, String handler_name) => (bool response) GetHandlersForURL(String url) => (Vector handlers) } diff --git a/Services/LaunchServer/Launcher.cpp b/Services/LaunchServer/Launcher.cpp index bda5ad70a09b24..62145eb17f4f8c 100644 --- a/Services/LaunchServer/Launcher.cpp +++ b/Services/LaunchServer/Launcher.cpp @@ -26,7 +26,9 @@ #include "Launcher.h" #include +#include #include +#include #include #include @@ -47,6 +49,33 @@ Launcher& Launcher::the() return *s_the; } +void Launcher::load_handlers(const String& af_dir) +{ + auto load_hashtable = [](auto& af, auto& key) { + HashTable table; + + auto config_value = af->read_entry("Launcher", key, {}); + for (auto& key : config_value.split(',')) + table.set(key.to_lowercase()); + + return table; + }; + + Core::DirIterator dt(af_dir, Core::DirIterator::SkipDots); + while (dt.has_next()) { + auto af_name = dt.next_path(); + auto af_path = String::format("%s/%s", af_dir.characters(), af_name.characters()); + auto af = Core::ConfigFile::open(af_path); + if (!af->has_key("App", "Name") || !af->has_key("App", "Executable")) + continue; + auto app_name = af->read_entry("App", "Name"); + 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 }); + } +} + void Launcher::load_config(const Core::ConfigFile& cfg) { for (auto key : cfg.keys("FileType")) { @@ -63,15 +92,35 @@ Vector Launcher::handlers_for_url(const URL& url) if (url.protocol() == "file") return handlers_for_path(url.path()); - return { m_protocol_handlers.get(url.protocol()).value_or(m_protocol_handlers.get("*").value_or({})) }; + return handlers_for(url.protocol(), m_protocol_handlers, [](auto& handler, auto& key) { + return handler.protocols.contains(key); + }); } -bool Launcher::open_url(const URL& url) +bool Launcher::open_url(const URL& url, const String& handler_name) { + if (!handler_name.is_null()) + return open_with_handler_name(url, handler_name); + if (url.protocol() == "file") return open_file_url(url); - return open_with_handlers(m_protocol_handlers, url.protocol(), url.to_string(), "/bin/Browser"); + return open_with_user_preferences(m_protocol_handlers, url.protocol(), url.to_string(), "/bin/Browser"); +} + +bool Launcher::open_with_handler_name(const URL& url, const String& handler_name) +{ + auto handler_optional = m_handlers.get(handler_name); + if (!handler_optional.has_value()) + return false; + + auto& handler = handler_optional.value(); + String argument; + if (url.protocol() == "file") + argument = url.path(); + else + argument = url.to_string(); + return spawn(handler.executable, argument); } bool spawn(String executable, String argument) @@ -91,14 +140,14 @@ bool spawn(String executable, String argument) return true; } -bool Launcher::open_with_handlers(const HashMap& handlers, const String key, const String argument, const String default_program) +bool Launcher::open_with_user_preferences(const HashMap& user_preferences, const String key, const String argument, const String default_program) { - auto program_path = handlers.get(key); + auto program_path = user_preferences.get(key); if (program_path.has_value()) return spawn(program_path.value(), argument); // There wasn't a handler for this, so try the fallback instead - program_path = handlers.get("*"); + program_path = user_preferences.get("*"); if (program_path.has_value()) return spawn(program_path.value(), argument); @@ -107,6 +156,29 @@ bool Launcher::open_with_handlers(const HashMap& handlers, const return spawn(default_program, argument); } +Vector Launcher::handlers_for(const String& key, HashMap& user_preference, Function handler_matches) +{ + Vector handlers; + + auto user_preferred = user_preference.get(key); + if (user_preferred.has_value()) + handlers.append(user_preferred.value()); + + 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); + } + + auto user_default = user_preference.get("*"); + if (handlers.size() == 0 && user_default.has_value()) + handlers.append(user_default.value()); + + return handlers; +} + Vector Launcher::handlers_for_path(const String& path) { struct stat st; @@ -120,7 +192,10 @@ Vector Launcher::handlers_for_path(const String& path) return { "/bin/FileManager" }; auto extension = FileSystemPath(path).extension().to_lowercase(); - return { m_file_handlers.get(extension).value_or(m_file_handlers.get("*").value_or({})) }; + + return handlers_for(extension, m_file_handlers, [](auto& handler, auto& key) { + return handler.file_types.contains(key); + }); } bool Launcher::open_file_url(const URL& url) @@ -142,7 +217,6 @@ bool Launcher::open_file_url(const URL& url) String extension = {}; if (extension_parts.size() > 1) extension = extension_parts.last(); - return open_with_handlers(m_file_handlers, extension, url.path(), "/bin/TextEdit"); + return open_with_user_preferences(m_file_handlers, extension, url.path(), "/bin/TextEdit"); } - } diff --git a/Services/LaunchServer/Launcher.h b/Services/LaunchServer/Launcher.h index 0559c3c04b5693..ff241c6c7f8713 100644 --- a/Services/LaunchServer/Launcher.h +++ b/Services/LaunchServer/Launcher.h @@ -27,26 +27,38 @@ #pragma once #include +#include #include #include namespace LaunchServer { +struct Handler { + String name; + String executable; + HashTable file_types; + HashTable protocols; +}; + class Launcher { public: Launcher(); static Launcher& the(); + void load_handlers(const String& af_dir); void load_config(const Core::ConfigFile&); - bool open_url(const URL&); + bool open_url(const URL&, const String& handler_name); Vector handlers_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&); bool open_file_url(const URL&); - bool open_with_handlers(const HashMap& handlers, const String key, const String argument, const String default_program); + 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); }; } diff --git a/Services/LaunchServer/main.cpp b/Services/LaunchServer/main.cpp index 61b2533d69d464..97c83fcbfbb856 100644 --- a/Services/LaunchServer/main.cpp +++ b/Services/LaunchServer/main.cpp @@ -42,6 +42,7 @@ int main(int argc, char** argv) auto launcher = LaunchServer::Launcher(); + launcher.load_handlers("/res/apps"); launcher.load_config(Core::ConfigFile::get_for_app("LaunchServer")); if (pledge("stdio accept rpath proc exec", nullptr) < 0) {