mirror of
https://github.com/SerenityOS/serenity
synced 2024-10-15 20:33:10 +00:00
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 :)
This commit is contained in:
parent
36996bd720
commit
3c5f75ed53
|
@ -6,3 +6,7 @@ Category=Internet
|
||||||
[Icons]
|
[Icons]
|
||||||
16x16=/res/icons/16x16/app-browser.png
|
16x16=/res/icons/16x16/app-browser.png
|
||||||
32x32=/res/icons/32x32/app-browser.png
|
32x32=/res/icons/32x32/app-browser.png
|
||||||
|
|
||||||
|
[Launcher]
|
||||||
|
FileTypes=html,md
|
||||||
|
Protocols=http,https
|
||||||
|
|
|
@ -6,3 +6,6 @@ Category=Utilities
|
||||||
[Icons]
|
[Icons]
|
||||||
16x16=/res/icons/TextEditor16.png
|
16x16=/res/icons/TextEditor16.png
|
||||||
32x32=/res/icons/32x32/app-texteditor.png
|
32x32=/res/icons/32x32/app-texteditor.png
|
||||||
|
|
||||||
|
[Launcher]
|
||||||
|
FileTypes=txt,md,html
|
||||||
|
|
|
@ -48,13 +48,13 @@ private:
|
||||||
: IPC::ServerConnection<LaunchClientEndpoint, LaunchServerEndpoint>(*this, "/tmp/portal/launch")
|
: IPC::ServerConnection<LaunchClientEndpoint, LaunchServerEndpoint>(*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();
|
auto connection = LaunchServerConnection::construct();
|
||||||
return connection->send_sync<Messages::LaunchServer::OpenUrl>(url.to_string())->response();
|
return connection->send_sync<Messages::LaunchServer::OpenUrl>(url.to_string(), handler_name)->response();
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<String> Launcher::get_handlers_for_url(const URL& url)
|
Vector<String> Launcher::get_handlers_for_url(const URL& url)
|
||||||
|
|
|
@ -32,7 +32,7 @@ namespace Desktop {
|
||||||
|
|
||||||
class Launcher {
|
class Launcher {
|
||||||
public:
|
public:
|
||||||
static bool open(const URL&);
|
static bool open(const URL&, const String& handler_name = {});
|
||||||
static Vector<String> get_handlers_for_url(const URL&);
|
static Vector<String> get_handlers_for_url(const URL&);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ OwnPtr<Messages::LaunchServer::GreetResponse> ClientConnection::handle(const Mes
|
||||||
OwnPtr<Messages::LaunchServer::OpenUrlResponse> ClientConnection::handle(const Messages::LaunchServer::OpenUrl& request)
|
OwnPtr<Messages::LaunchServer::OpenUrlResponse> ClientConnection::handle(const Messages::LaunchServer::OpenUrl& request)
|
||||||
{
|
{
|
||||||
URL url(request.url());
|
URL url(request.url());
|
||||||
auto result = Launcher::the().open_url(url);
|
auto result = Launcher::the().open_url(url, request.handler_name());
|
||||||
return make<Messages::LaunchServer::OpenUrlResponse>(result);
|
return make<Messages::LaunchServer::OpenUrlResponse>(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
endpoint LaunchServer = 101
|
endpoint LaunchServer = 101
|
||||||
{
|
{
|
||||||
Greet() => (i32 client_id)
|
Greet() => (i32 client_id)
|
||||||
OpenUrl(String url) => (bool response)
|
OpenUrl(String url, String handler_name) => (bool response)
|
||||||
GetHandlersForURL(String url) => (Vector<String> handlers)
|
GetHandlersForURL(String url) => (Vector<String> handlers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,9 @@
|
||||||
|
|
||||||
#include "Launcher.h"
|
#include "Launcher.h"
|
||||||
#include <AK/FileSystemPath.h>
|
#include <AK/FileSystemPath.h>
|
||||||
|
#include <AK/Function.h>
|
||||||
#include <LibCore/ConfigFile.h>
|
#include <LibCore/ConfigFile.h>
|
||||||
|
#include <LibCore/DirIterator.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
@ -47,6 +49,33 @@ Launcher& Launcher::the()
|
||||||
return *s_the;
|
return *s_the;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Launcher::load_handlers(const String& af_dir)
|
||||||
|
{
|
||||||
|
auto load_hashtable = [](auto& af, auto& key) {
|
||||||
|
HashTable<String> 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)
|
void Launcher::load_config(const Core::ConfigFile& cfg)
|
||||||
{
|
{
|
||||||
for (auto key : cfg.keys("FileType")) {
|
for (auto key : cfg.keys("FileType")) {
|
||||||
|
@ -63,15 +92,35 @@ Vector<String> Launcher::handlers_for_url(const URL& url)
|
||||||
if (url.protocol() == "file")
|
if (url.protocol() == "file")
|
||||||
return handlers_for_path(url.path());
|
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")
|
if (url.protocol() == "file")
|
||||||
return open_file_url(url);
|
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)
|
bool spawn(String executable, String argument)
|
||||||
|
@ -91,14 +140,14 @@ bool spawn(String executable, String argument)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Launcher::open_with_handlers(const HashMap<String, String>& handlers, const String key, const String argument, const String default_program)
|
bool Launcher::open_with_user_preferences(const HashMap<String, String>& 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())
|
if (program_path.has_value())
|
||||||
return spawn(program_path.value(), argument);
|
return spawn(program_path.value(), argument);
|
||||||
|
|
||||||
// There wasn't a handler for this, so try the fallback instead
|
// 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())
|
if (program_path.has_value())
|
||||||
return spawn(program_path.value(), argument);
|
return spawn(program_path.value(), argument);
|
||||||
|
|
||||||
|
@ -107,6 +156,29 @@ bool Launcher::open_with_handlers(const HashMap<String, String>& handlers, const
|
||||||
return spawn(default_program, argument);
|
return spawn(default_program, argument);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector<String> Launcher::handlers_for(const String& key, HashMap<String, String>& user_preference, Function<bool(Handler&, const String&)> handler_matches)
|
||||||
|
{
|
||||||
|
Vector<String> 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<String> Launcher::handlers_for_path(const String& path)
|
Vector<String> Launcher::handlers_for_path(const String& path)
|
||||||
{
|
{
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
@ -120,7 +192,10 @@ Vector<String> Launcher::handlers_for_path(const String& path)
|
||||||
return { "/bin/FileManager" };
|
return { "/bin/FileManager" };
|
||||||
|
|
||||||
auto extension = FileSystemPath(path).extension().to_lowercase();
|
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)
|
bool Launcher::open_file_url(const URL& url)
|
||||||
|
@ -142,7 +217,6 @@ bool Launcher::open_file_url(const URL& url)
|
||||||
String extension = {};
|
String extension = {};
|
||||||
if (extension_parts.size() > 1)
|
if (extension_parts.size() > 1)
|
||||||
extension = extension_parts.last();
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,26 +27,38 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
|
#include <AK/HashTable.h>
|
||||||
#include <AK/URL.h>
|
#include <AK/URL.h>
|
||||||
#include <LibCore/ConfigFile.h>
|
#include <LibCore/ConfigFile.h>
|
||||||
|
|
||||||
namespace LaunchServer {
|
namespace LaunchServer {
|
||||||
|
|
||||||
|
struct Handler {
|
||||||
|
String name;
|
||||||
|
String executable;
|
||||||
|
HashTable<String> file_types;
|
||||||
|
HashTable<String> protocols;
|
||||||
|
};
|
||||||
|
|
||||||
class Launcher {
|
class Launcher {
|
||||||
public:
|
public:
|
||||||
Launcher();
|
Launcher();
|
||||||
static Launcher& the();
|
static Launcher& the();
|
||||||
|
|
||||||
|
void load_handlers(const String& af_dir);
|
||||||
void load_config(const Core::ConfigFile&);
|
void load_config(const Core::ConfigFile&);
|
||||||
bool open_url(const URL&);
|
bool open_url(const URL&, const String& handler_name);
|
||||||
Vector<String> handlers_for_url(const URL&);
|
Vector<String> handlers_for_url(const URL&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
HashMap<String, Handler> m_handlers;
|
||||||
HashMap<String, String> m_protocol_handlers;
|
HashMap<String, String> m_protocol_handlers;
|
||||||
HashMap<String, String> m_file_handlers;
|
HashMap<String, String> m_file_handlers;
|
||||||
|
|
||||||
|
Vector<String> handlers_for(const String& key, HashMap<String, String>& user_preferences, Function<bool(Handler&, const String&)> handler_matches);
|
||||||
Vector<String> handlers_for_path(const String&);
|
Vector<String> handlers_for_path(const String&);
|
||||||
bool open_file_url(const URL&);
|
bool open_file_url(const URL&);
|
||||||
bool open_with_handlers(const HashMap<String, String>& handlers, const String key, const String argument, const String default_program);
|
bool open_with_user_preferences(const HashMap<String, String>& user_preferences, const String key, const String argument, const String default_program);
|
||||||
|
bool open_with_handler_name(const URL&, const String& handler_name);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ int main(int argc, char** argv)
|
||||||
|
|
||||||
auto launcher = LaunchServer::Launcher();
|
auto launcher = LaunchServer::Launcher();
|
||||||
|
|
||||||
|
launcher.load_handlers("/res/apps");
|
||||||
launcher.load_config(Core::ConfigFile::get_for_app("LaunchServer"));
|
launcher.load_config(Core::ConfigFile::get_for_app("LaunchServer"));
|
||||||
|
|
||||||
if (pledge("stdio accept rpath proc exec", nullptr) < 0) {
|
if (pledge("stdio accept rpath proc exec", nullptr) < 0) {
|
||||||
|
|
Loading…
Reference in a new issue