Launcher: Provide launcher details to LibDesktop

This allows applications to get additional information,
such as name or icons, for each launch handler.
This commit is contained in:
Tom 2020-07-13 18:55:09 -06:00 committed by Andreas Kling
parent 7498024805
commit 535113bac4
7 changed files with 208 additions and 26 deletions

View file

@ -24,6 +24,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/JsonObject.h>
#include <AK/URL.h>
#include <LaunchServer/LaunchClientEndpoint.h>
#include <LaunchServer/LaunchServerEndpoint.h>
@ -33,6 +34,29 @@
namespace Desktop {
auto Launcher::Details::from_details_str(const String& details_str) -> NonnullRefPtr<Details>
{
auto details = adopt(*new Details);
auto json = JsonValue::from_string(details_str);
ASSERT(json.has_value());
auto obj = json.value().as_object();
details->executable = obj.get("executable").to_string();
details->name = obj.get("name").to_string();
if (auto type_value = obj.get_ptr("type")) {
auto type_str = type_value->to_string();
if (type_str == "userpreferred")
details->launcher_type = LauncherType::UserPreferred;
else if (type_str == "userdefault")
details->launcher_type = LauncherType::UserDefault;
}
if (auto icons_value = obj.get_ptr("icons")) {
icons_value->as_object().for_each_member([&](auto& name, auto& value) {
details->icons.set(name, value.to_string());
});
}
return details;
}
class LaunchServerConnection : public IPC::ServerConnection<LaunchClientEndpoint, LaunchServerEndpoint>
, public LaunchClientEndpoint {
C_OBJECT(LaunchServerConnection)
@ -57,10 +81,26 @@ bool Launcher::open(const URL& url, const String& handler_name)
return connection->send_sync<Messages::LaunchServer::OpenURL>(url, handler_name)->response();
}
bool Launcher::open(const URL& url, const Details& details)
{
return open(url, details.executable);
}
Vector<String> Launcher::get_handlers_for_url(const URL& url)
{
auto connection = LaunchServerConnection::construct();
return connection->send_sync<Messages::LaunchServer::GetHandlersForURL>(url.to_string())->handlers();
}
auto Launcher::get_handlers_with_details_for_url(const URL& url) -> NonnullRefPtrVector<Details>
{
auto connection = LaunchServerConnection::construct();
auto details = connection->send_sync<Messages::LaunchServer::GetHandlersWithDetailsForURL>(url.to_string())->handlers_details();
NonnullRefPtrVector<Details> handlers_with_details;
for (auto& value : details) {
handlers_with_details.append(Details::from_details_str(value));
}
return handlers_with_details;
}
}

View file

@ -27,13 +27,35 @@
#pragma once
#include <AK/Forward.h>
#include <AK/HashMap.h>
#include <AK/NonnullRefPtrVector.h>
#include <AK/RefCounted.h>
#include <AK/String.h>
namespace Desktop {
class Launcher {
public:
enum class LauncherType {
Default = 0,
UserPreferred,
UserDefault
};
struct Details: public RefCounted<Details> {
String name;
String executable;
HashMap<String, String> icons;
LauncherType launcher_type { LauncherType::Default };
static NonnullRefPtr<Details> from_details_str(const String&);
};
static bool open(const URL&, const String& handler_name = {});
static bool open(const URL&, const Details& details);
static Vector<String> get_handlers_for_url(const URL&);
static NonnullRefPtrVector<Details> get_handlers_with_details_for_url(const URL&);
};
}

View file

@ -67,4 +67,11 @@ OwnPtr<Messages::LaunchServer::GetHandlersForURLResponse> ClientConnection::hand
return make<Messages::LaunchServer::GetHandlersForURLResponse>(result);
}
OwnPtr<Messages::LaunchServer::GetHandlersWithDetailsForURLResponse> ClientConnection::handle(const Messages::LaunchServer::GetHandlersWithDetailsForURL& request)
{
URL url(request.url());
auto result = Launcher::the().handlers_with_details_for_url(url);
return make<Messages::LaunchServer::GetHandlersWithDetailsForURLResponse>(result);
}
}

View file

@ -45,5 +45,6 @@ private:
virtual OwnPtr<Messages::LaunchServer::GreetResponse> handle(const Messages::LaunchServer::Greet&) override;
virtual OwnPtr<Messages::LaunchServer::OpenURLResponse> handle(const Messages::LaunchServer::OpenURL&) override;
virtual OwnPtr<Messages::LaunchServer::GetHandlersForURLResponse> handle(const Messages::LaunchServer::GetHandlersForURL&) override;
virtual OwnPtr<Messages::LaunchServer::GetHandlersWithDetailsForURLResponse> handle(const Messages::LaunchServer::GetHandlersWithDetailsForURL&) override;
};
}

View file

@ -3,4 +3,5 @@ endpoint LaunchServer = 101
Greet() => (i32 client_id)
OpenURL(URL url, String handler_name) => (bool response)
GetHandlersForURL(URL url) => (Vector<String> handlers)
GetHandlersWithDetailsForURL(URL url) => (Vector<String> handlers_details)
}

View file

@ -26,7 +26,11 @@
#include "Launcher.h"
#include <AK/Function.h>
#include <AK/JsonObject.h>
#include <AK/JsonObjectSerializer.h>
#include <AK/JsonValue.h>
#include <AK/LexicalPath.h>
#include <AK/StringBuilder.h>
#include <LibCore/ConfigFile.h>
#include <LibCore/DirIterator.h>
#include <spawn.h>
@ -38,6 +42,47 @@ namespace LaunchServer {
static Launcher* s_the;
static bool spawn(String executable, String argument);
String Handler::name_from_executable(const StringView& executable)
{
auto separator = executable.find_last_of('/');
if (separator.has_value()) {
auto start = separator.value() + 1;
return executable.substring_view(start, executable.length() - start);
}
return executable;
}
void Handler::from_executable(Type handler_type, const String& executable)
{
this->handler_type = handler_type;
this->name = name_from_executable(executable);
this->executable = executable;
}
String Handler::to_details_str() const
{
StringBuilder builder;
JsonObjectSerializer obj { builder };
obj.add("executable", executable);
obj.add("name", name);
switch (handler_type) {
case Type::UserDefault:
obj.add("type", "userdefault");
break;
case Type::UserPreferred:
obj.add("type", "userpreferred");
break;
default:
break;
}
JsonObject icons_obj;
for (auto& icon : icons)
icons_obj.set(icon.key, icon.value);
obj.add("icons", move(icons_obj));
obj.finish();
return builder.build();
}
Launcher::Launcher()
{
ASSERT(s_the == nullptr);
@ -61,6 +106,14 @@ void Launcher::load_handlers(const String& af_dir)
return table;
};
auto load_hashmap = [](auto& af, auto& group) {
HashMap<String, String> map;
auto keys = af->keys(group);
for (auto& key : keys)
map.set(key, af->read_entry(group, key));
return map;
};
Core::DirIterator dt(af_dir, Core::DirIterator::SkipDots);
while (dt.has_next()) {
@ -73,7 +126,8 @@ void Launcher::load_handlers(const String& af_dir)
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 });
auto icons = load_hashmap(af, "Icons");
m_handlers.set(app_executable, { Handler::Type::Default, move(app_name), app_executable, move(file_types), move(protocols), move(icons) });
}
}
@ -90,12 +144,42 @@ void Launcher::load_config(const Core::ConfigFile& cfg)
Vector<String> Launcher::handlers_for_url(const URL& url)
{
if (url.protocol() == "file")
return handlers_for_path(url.path());
Vector<String> handlers;
if (url.protocol() == "file") {
for_each_handler_for_path(url.path(), [&](auto& handler) -> bool {
handlers.append(handler.executable);
return true;
});
} else {
for_each_handler(url.protocol(), m_protocol_handlers, [&](const auto& handler) -> bool {
if (handler.handler_type != Handler::Type::Default || handler.protocols.contains(url.protocol())) {
handlers.append(handler.executable);
return true;
}
return false;
});
}
return handlers;
}
return handlers_for(url.protocol(), m_protocol_handlers, [](auto& handler, auto& key) {
return handler.protocols.contains(key);
});
Vector<String> Launcher::handlers_with_details_for_url(const URL& url)
{
Vector<String> handlers;
if (url.protocol() == "file") {
for_each_handler_for_path(url.path(), [&](auto& handler) -> bool {
handlers.append(handler.to_details_str());
return true;
});
} else {
for_each_handler(url.protocol(), m_protocol_handlers, [&](const auto& handler) -> bool {
if (handler.handler_type != Handler::Type::Default || handler.protocols.contains(url.protocol())) {
handlers.append(handler.to_details_str());
return true;
}
return false;
});
}
return handlers;
}
bool Launcher::open_url(const URL& url, const String& handler_name)
@ -135,6 +219,19 @@ bool spawn(String executable, String argument)
return true;
}
Handler Launcher::get_handler_for_executable(Handler::Type handler_type, const String& executable) const
{
Handler handler;
auto existing_handler = m_handlers.get(executable);
if (existing_handler.has_value()) {
handler = existing_handler.value();
handler.handler_type = handler_type;
} else {
handler.from_executable(handler_type, executable);
}
return handler;
}
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 = user_preferences.get(key);
@ -151,45 +248,46 @@ bool Launcher::open_with_user_preferences(const HashMap<String, String>& user_pr
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)
void Launcher::for_each_handler(const String& key, HashMap<String, String>& user_preference, Function<bool(const Handler&)> f)
{
Vector<String> handlers;
auto user_preferred = user_preference.get(key);
if (user_preferred.has_value())
handlers.append(user_preferred.value());
f(get_handler_for_executable(Handler::Type::UserPreferred, user_preferred.value()));
size_t counted = 0;
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);
if (f(handler.value))
counted++;
}
auto user_default = user_preference.get("*");
if (handlers.size() == 0 && user_default.has_value())
handlers.append(user_default.value());
return handlers;
if (counted == 0 && user_default.has_value())
f(get_handler_for_executable(Handler::Type::UserDefault, user_default.value()));
}
Vector<String> Launcher::handlers_for_path(const String& path)
void Launcher::for_each_handler_for_path(const String& path, Function<bool(const Handler&)> f)
{
struct stat st;
if (stat(path.characters(), &st) < 0) {
perror("stat");
return {};
return;
}
// TODO: Make directory opening configurable
if (S_ISDIR(st.st_mode))
return { "/bin/FileManager" };
if (S_ISDIR(st.st_mode)) {
f(get_handler_for_executable(Handler::Type::Default, "/bin/FileManager"));
return;
}
auto extension = LexicalPath(path).extension().to_lowercase();
return handlers_for(extension, m_file_handlers, [](auto& handler, auto& key) {
return handler.file_types.contains(key);
for_each_handler(extension, m_file_handlers, [&](const auto& handler) -> bool {
if (handler.handler_type != Handler::Type::Default || handler.file_types.contains(extension))
return f(handler);
return false;
});
}

View file

@ -34,10 +34,21 @@
namespace LaunchServer {
struct Handler {
enum class Type {
Default = 0,
UserPreferred,
UserDefault
};
Type handler_type;
String name;
String executable;
HashTable<String> file_types;
HashTable<String> protocols;
HashTable<String> file_types {};
HashTable<String> protocols {};
HashMap<String, String> icons {};
static String name_from_executable(const StringView&);
void from_executable(Type, const String&);
String to_details_str() const;
};
class Launcher {
@ -49,14 +60,16 @@ public:
void load_config(const Core::ConfigFile&);
bool open_url(const URL&, const String& handler_name);
Vector<String> handlers_for_url(const URL&);
Vector<String> handlers_with_details_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&);
Handler get_handler_for_executable(Handler::Type, const String&) const;
void for_each_handler(const String& key, HashMap<String, String>& user_preferences, Function<bool(const Handler&)> f);
void for_each_handler_for_path(const String&, Function<bool(const Handler&)> f);
bool open_file_url(const URL&);
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);