Skip to content

Commit

Permalink
LaunchServer: Discover handlers from *.af files, allow launching base…
Browse files Browse the repository at this point in the history
…d 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 :)
  • Loading branch information
niax authored and awesomekling committed May 18, 2020
1 parent 36996bd commit 3c5f75e
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 17 deletions.
4 changes: 4 additions & 0 deletions Base/res/apps/Browser.af
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions Base/res/apps/TextEditor.af
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ Category=Utilities
[Icons]
16x16=/res/icons/TextEditor16.png
32x32=/res/icons/32x32/app-texteditor.png

[Launcher]
FileTypes=txt,md,html
6 changes: 3 additions & 3 deletions Libraries/LibDesktop/Launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ class LaunchServerConnection : public IPC::ServerConnection<LaunchClientEndpoint
: 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();
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)
Expand Down
2 changes: 1 addition & 1 deletion Libraries/LibDesktop/Launcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> get_handlers_for_url(const URL&);
};

Expand Down
2 changes: 1 addition & 1 deletion Services/LaunchServer/ClientConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ OwnPtr<Messages::LaunchServer::GreetResponse> ClientConnection::handle(const Mes
OwnPtr<Messages::LaunchServer::OpenUrlResponse> 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<Messages::LaunchServer::OpenUrlResponse>(result);
}

Expand Down
2 changes: 1 addition & 1 deletion Services/LaunchServer/LaunchServer.ipc
Original file line number Diff line number Diff line change
@@ -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<String> handlers)
}
92 changes: 83 additions & 9 deletions Services/LaunchServer/Launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -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")) {
Expand All @@ -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)
Expand All @@ -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);

Expand All @@ -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;
Expand All @@ -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)
Expand All @@ -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");
}

}
16 changes: 14 additions & 2 deletions Services/LaunchServer/Launcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,38 @@
#pragma once

#include <AK/HashMap.h>
#include <AK/HashTable.h>
#include <AK/URL.h>
#include <LibCore/ConfigFile.h>

namespace LaunchServer {

struct Handler {
String name;
String executable;
HashTable<String> file_types;
HashTable<String> 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<String> handlers_for_url(const URL&);

private:
HashMap<String, Handler> m_handlers;
HashMap<String, String> m_protocol_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&);
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);
};
}
1 change: 1 addition & 0 deletions Services/LaunchServer/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 3c5f75e

Please sign in to comment.