1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 15:27:34 +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:
Nicholas Hollett 2020-05-16 14:16:43 +01:00 committed by Andreas Kling
parent 36996bd720
commit 3c5f75ed53
9 changed files with 111 additions and 17 deletions

View file

@ -26,7 +26,9 @@
#include "Launcher.h"
#include <AK/FileSystemPath.h>
#include <AK/Function.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/DirIterator.h>
#include <stdio.h>
#include <sys/stat.h>
@ -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<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)
{
for (auto key : cfg.keys("FileType")) {
@ -63,15 +92,35 @@ Vector<String> 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<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())
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<String, String>& handlers, const
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)
{
struct stat st;
@ -120,7 +192,10 @@ Vector<String> 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");
}
}