mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-24 10:22:05 -05:00
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:
parent
7498024805
commit
535113bac4
7 changed files with 208 additions and 26 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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&);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue