mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 06:57:46 +00:00
Services: Move to Userland/Services/
This commit is contained in:
parent
4055b03291
commit
c7ac7e6eaf
170 changed files with 4 additions and 4 deletions
13
Userland/Services/LaunchServer/CMakeLists.txt
Normal file
13
Userland/Services/LaunchServer/CMakeLists.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
compile_ipc(LaunchServer.ipc LaunchServerEndpoint.h)
|
||||
compile_ipc(LaunchClient.ipc LaunchClientEndpoint.h)
|
||||
|
||||
set(SOURCES
|
||||
ClientConnection.cpp
|
||||
Launcher.cpp
|
||||
main.cpp
|
||||
LaunchClientEndpoint.h
|
||||
LaunchServerEndpoint.h
|
||||
)
|
||||
|
||||
serenity_bin(LaunchServer)
|
||||
target_link_libraries(LaunchServer LibCore LibIPC LibDesktop)
|
159
Userland/Services/LaunchServer/ClientConnection.cpp
Normal file
159
Userland/Services/LaunchServer/ClientConnection.cpp
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Nicholas Hollett <niax@niax.co.uk>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ClientConnection.h"
|
||||
#include "Launcher.h"
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/URL.h>
|
||||
#include <LaunchServer/LaunchClientEndpoint.h>
|
||||
|
||||
namespace LaunchServer {
|
||||
|
||||
static HashMap<int, RefPtr<ClientConnection>> s_connections;
|
||||
ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> client_socket, int client_id)
|
||||
: IPC::ClientConnection<LaunchClientEndpoint, LaunchServerEndpoint>(*this, move(client_socket), client_id)
|
||||
{
|
||||
s_connections.set(client_id, *this);
|
||||
}
|
||||
|
||||
ClientConnection::~ClientConnection()
|
||||
{
|
||||
}
|
||||
|
||||
void ClientConnection::die()
|
||||
{
|
||||
s_connections.remove(client_id());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::LaunchServer::GreetResponse> ClientConnection::handle(const Messages::LaunchServer::Greet&)
|
||||
{
|
||||
return make<Messages::LaunchServer::GreetResponse>(client_id());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::LaunchServer::OpenURLResponse> ClientConnection::handle(const Messages::LaunchServer::OpenURL& request)
|
||||
{
|
||||
if (!m_allowlist.is_empty()) {
|
||||
bool allowed = false;
|
||||
for (auto& allowed_handler : m_allowlist) {
|
||||
if (allowed_handler.handler_name == request.handler_name()
|
||||
&& (allowed_handler.any_url || allowed_handler.urls.contains_slow(request.url()))) {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!allowed) {
|
||||
// You are not on the list, go home!
|
||||
did_misbehave(String::formatted("Client requested a combination of handler/URL that was not on the list: '{}' with '{}'", request.handler_name(), request.url()).characters());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
URL url(request.url());
|
||||
auto result = Launcher::the().open_url(url, request.handler_name());
|
||||
return make<Messages::LaunchServer::OpenURLResponse>(result);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::LaunchServer::GetHandlersForURLResponse> ClientConnection::handle(const Messages::LaunchServer::GetHandlersForURL& request)
|
||||
{
|
||||
URL url(request.url());
|
||||
auto result = Launcher::the().handlers_for_url(url);
|
||||
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);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::LaunchServer::AddAllowedURLResponse> ClientConnection::handle(const Messages::LaunchServer::AddAllowedURL& request)
|
||||
{
|
||||
if (m_allowlist_is_sealed) {
|
||||
did_misbehave("Got request to add more allowed handlers after list was sealed");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!request.url().is_valid()) {
|
||||
did_misbehave("Got request to allow invalid URL");
|
||||
return {};
|
||||
}
|
||||
|
||||
m_allowlist.empend(String(), false, Vector<URL> { request.url() });
|
||||
|
||||
return make<Messages::LaunchServer::AddAllowedURLResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::LaunchServer::AddAllowedHandlerWithAnyURLResponse> ClientConnection::handle(const Messages::LaunchServer::AddAllowedHandlerWithAnyURL& request)
|
||||
{
|
||||
if (m_allowlist_is_sealed) {
|
||||
did_misbehave("Got request to add more allowed handlers after list was sealed");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (request.handler_name().is_empty()) {
|
||||
did_misbehave("Got request to allow empty handler name");
|
||||
return {};
|
||||
}
|
||||
|
||||
m_allowlist.empend(request.handler_name(), true, Vector<URL>());
|
||||
|
||||
return make<Messages::LaunchServer::AddAllowedHandlerWithAnyURLResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLsResponse> ClientConnection::handle(const Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLs& request)
|
||||
{
|
||||
if (m_allowlist_is_sealed) {
|
||||
did_misbehave("Got request to add more allowed handlers after list was sealed");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (request.handler_name().is_empty()) {
|
||||
did_misbehave("Got request to allow empty handler name");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (request.urls().is_empty()) {
|
||||
did_misbehave("Got request to allow empty URL list");
|
||||
return {};
|
||||
}
|
||||
|
||||
m_allowlist.empend(request.handler_name(), false, request.urls());
|
||||
|
||||
return make<Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLsResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::LaunchServer::SealAllowlistResponse> ClientConnection::handle(const Messages::LaunchServer::SealAllowlist&)
|
||||
{
|
||||
if (m_allowlist_is_sealed) {
|
||||
did_misbehave("Got more than one request to seal the allowed handlers list");
|
||||
return {};
|
||||
}
|
||||
|
||||
return make<Messages::LaunchServer::SealAllowlistResponse>();
|
||||
}
|
||||
|
||||
}
|
64
Userland/Services/LaunchServer/ClientConnection.h
Normal file
64
Userland/Services/LaunchServer/ClientConnection.h
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Nicholas Hollett <niax@niax.co.uk>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LaunchServer/LaunchClientEndpoint.h>
|
||||
#include <LaunchServer/LaunchServerEndpoint.h>
|
||||
#include <LibIPC/ClientConnection.h>
|
||||
|
||||
namespace LaunchServer {
|
||||
|
||||
class ClientConnection final : public IPC::ClientConnection<LaunchClientEndpoint, LaunchServerEndpoint>
|
||||
, public LaunchServerEndpoint {
|
||||
C_OBJECT(ClientConnection)
|
||||
public:
|
||||
~ClientConnection() override;
|
||||
|
||||
virtual void die() override;
|
||||
|
||||
private:
|
||||
explicit ClientConnection(NonnullRefPtr<Core::LocalSocket>, int client_id);
|
||||
|
||||
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;
|
||||
virtual OwnPtr<Messages::LaunchServer::AddAllowedURLResponse> handle(const Messages::LaunchServer::AddAllowedURL&) override;
|
||||
virtual OwnPtr<Messages::LaunchServer::AddAllowedHandlerWithAnyURLResponse> handle(const Messages::LaunchServer::AddAllowedHandlerWithAnyURL&) override;
|
||||
virtual OwnPtr<Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLsResponse> handle(const Messages::LaunchServer::AddAllowedHandlerWithOnlySpecificURLs&) override;
|
||||
virtual OwnPtr<Messages::LaunchServer::SealAllowlistResponse> handle(const Messages::LaunchServer::SealAllowlist&) override;
|
||||
|
||||
struct AllowlistEntry {
|
||||
String handler_name;
|
||||
bool any_url { false };
|
||||
Vector<URL> urls;
|
||||
};
|
||||
|
||||
Vector<AllowlistEntry> m_allowlist;
|
||||
bool m_allowlist_is_sealed { false };
|
||||
};
|
||||
}
|
4
Userland/Services/LaunchServer/LaunchClient.ipc
Normal file
4
Userland/Services/LaunchServer/LaunchClient.ipc
Normal file
|
@ -0,0 +1,4 @@
|
|||
endpoint LaunchClient = 102
|
||||
{
|
||||
Dummy() =|
|
||||
}
|
12
Userland/Services/LaunchServer/LaunchServer.ipc
Normal file
12
Userland/Services/LaunchServer/LaunchServer.ipc
Normal file
|
@ -0,0 +1,12 @@
|
|||
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)
|
||||
|
||||
AddAllowedURL(URL url) => ()
|
||||
AddAllowedHandlerWithAnyURL(String handler_name) => ()
|
||||
AddAllowedHandlerWithOnlySpecificURLs(String handler_name, Vector<URL> urls) => ()
|
||||
SealAllowlist() => ()
|
||||
}
|
309
Userland/Services/LaunchServer/Launcher.cpp
Normal file
309
Userland/Services/LaunchServer/Launcher.cpp
Normal file
|
@ -0,0 +1,309 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Nicholas Hollett <niax@niax.co.uk>, Andreas Kling <kling@serenityos.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#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 <LibDesktop/AppFile.h>
|
||||
#include <serenity.h>
|
||||
#include <spawn.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
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::Application:
|
||||
obj.add("type", "app");
|
||||
break;
|
||||
case Type::UserDefault:
|
||||
obj.add("type", "userdefault");
|
||||
break;
|
||||
case Type::UserPreferred:
|
||||
obj.add("type", "userpreferred");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
obj.finish();
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
Launcher::Launcher()
|
||||
{
|
||||
ASSERT(s_the == nullptr);
|
||||
s_the = this;
|
||||
}
|
||||
|
||||
Launcher& Launcher::the()
|
||||
{
|
||||
ASSERT(s_the);
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
void Launcher::load_handlers(const String& af_dir)
|
||||
{
|
||||
Desktop::AppFile::for_each([&](auto af) {
|
||||
auto app_name = af->name();
|
||||
auto app_executable = af->executable();
|
||||
HashTable<String> file_types;
|
||||
for (auto& file_type : af->launcher_file_types())
|
||||
file_types.set(file_type);
|
||||
HashTable<String> protocols;
|
||||
for (auto& protocol : af->launcher_protocols())
|
||||
protocols.set(protocol);
|
||||
m_handlers.set(app_executable, { Handler::Type::Default, app_name, app_executable, file_types, protocols });
|
||||
},
|
||||
af_dir);
|
||||
}
|
||||
|
||||
void Launcher::load_config(const Core::ConfigFile& cfg)
|
||||
{
|
||||
for (auto key : cfg.keys("FileType")) {
|
||||
auto handler = cfg.read_entry("FileType", key).trim_whitespace();
|
||||
if (handler.is_empty())
|
||||
continue;
|
||||
m_file_handlers.set(key.to_lowercase(), handler);
|
||||
}
|
||||
|
||||
for (auto key : cfg.keys("Protocol")) {
|
||||
auto handler = cfg.read_entry("Protocol", key).trim_whitespace();
|
||||
if (handler.is_empty())
|
||||
continue;
|
||||
m_protocol_handlers.set(key.to_lowercase(), handler);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<String> Launcher::handlers_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.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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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_user_preferences(m_protocol_handlers, url.protocol(), url.to_string());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
pid_t child_pid;
|
||||
const char* argv[] = { executable.characters(), argument.characters(), nullptr };
|
||||
if ((errno = posix_spawn(&child_pid, executable.characters(), nullptr, nullptr, const_cast<char**>(argv), environ))) {
|
||||
perror("posix_spawn");
|
||||
return false;
|
||||
} else {
|
||||
if (disown(child_pid) < 0)
|
||||
perror("disown");
|
||||
}
|
||||
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);
|
||||
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 = user_preferences.get("*");
|
||||
if (program_path.has_value())
|
||||
return spawn(program_path.value(), argument);
|
||||
|
||||
// Absolute worst case, try the provided default program, if any
|
||||
if (!default_program.is_empty())
|
||||
return spawn(default_program, argument);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Launcher::for_each_handler(const String& key, HashMap<String, String>& user_preference, Function<bool(const Handler&)> f)
|
||||
{
|
||||
auto user_preferred = user_preference.get(key);
|
||||
if (user_preferred.has_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 (f(handler.value))
|
||||
counted++;
|
||||
}
|
||||
|
||||
auto user_default = user_preference.get("*");
|
||||
if (counted == 0 && user_default.has_value())
|
||||
f(get_handler_for_executable(Handler::Type::UserDefault, user_default.value()));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO: Make directory opening configurable
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
f(get_handler_for_executable(Handler::Type::Default, "/bin/FileManager"));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((st.st_mode & S_IFMT) == S_IFREG && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
|
||||
f(get_handler_for_executable(Handler::Type::Application, path));
|
||||
|
||||
auto extension = LexicalPath(path).extension().to_lowercase();
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
bool Launcher::open_file_url(const URL& url)
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(url.path().characters(), &st) < 0) {
|
||||
perror("stat");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Make directory opening configurable
|
||||
if (S_ISDIR(st.st_mode))
|
||||
return spawn("/bin/FileManager", url.path());
|
||||
|
||||
if ((st.st_mode & S_IFMT) == S_IFREG && st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
|
||||
return spawn(url.path(), {});
|
||||
|
||||
auto extension_parts = url.path().to_lowercase().split('.');
|
||||
String extension = {};
|
||||
if (extension_parts.size() > 1)
|
||||
extension = extension_parts.last();
|
||||
return open_with_user_preferences(m_file_handlers, extension, url.path(), "/bin/TextEditor");
|
||||
}
|
||||
}
|
78
Userland/Services/LaunchServer/Launcher.h
Normal file
78
Userland/Services/LaunchServer/Launcher.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Nicholas Hollett <niax@niax.co.uk>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/URL.h>
|
||||
#include <LibCore/ConfigFile.h>
|
||||
#include <LibDesktop/AppFile.h>
|
||||
|
||||
namespace LaunchServer {
|
||||
|
||||
struct Handler {
|
||||
enum class Type {
|
||||
Default = 0,
|
||||
Application,
|
||||
UserPreferred,
|
||||
UserDefault
|
||||
};
|
||||
Type handler_type;
|
||||
String name;
|
||||
String executable;
|
||||
HashTable<String> file_types {};
|
||||
HashTable<String> protocols {};
|
||||
|
||||
static String name_from_executable(const StringView&);
|
||||
void from_executable(Type, const String&);
|
||||
String to_details_str() const;
|
||||
};
|
||||
|
||||
class Launcher {
|
||||
public:
|
||||
Launcher();
|
||||
static Launcher& the();
|
||||
|
||||
void load_handlers(const String& af_dir = Desktop::AppFile::APP_FILES_DIRECTORY);
|
||||
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;
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
65
Userland/Services/LaunchServer/main.cpp
Normal file
65
Userland/Services/LaunchServer/main.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Nicholas Hollett <niax@niax.co.uk>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "ClientConnection.h"
|
||||
#include "Launcher.h"
|
||||
#include <LibCore/ConfigFile.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/LocalServer.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
|
||||
{
|
||||
Core::EventLoop event_loop;
|
||||
auto server = Core::LocalServer::construct();
|
||||
|
||||
auto launcher = LaunchServer::Launcher();
|
||||
|
||||
launcher.load_handlers();
|
||||
launcher.load_config(Core::ConfigFile::get_for_app("LaunchServer"));
|
||||
|
||||
if (pledge("stdio accept rpath proc exec", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool ok = server->take_over_from_system_server();
|
||||
ASSERT(ok);
|
||||
server->on_ready_to_accept = [&] {
|
||||
auto client_socket = server->accept();
|
||||
if (!client_socket) {
|
||||
dbgln("LaunchServer: accept failed.");
|
||||
return;
|
||||
}
|
||||
static int s_next_client_id = 0;
|
||||
int client_id = ++s_next_client_id;
|
||||
dbgln("Received connection");
|
||||
IPC::new_client_connection<LaunchServer::ClientConnection>(client_socket.release_nonnull(), client_id);
|
||||
};
|
||||
|
||||
return event_loop.exec();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue