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