
This is common enough to warrant its own setting by now - but it's also partially a workaround. Since app files currently only support a single executable path with no arguments, we resort to generating wrapper scripts for port launchers with arguments - and then the executable is that shell script. We also moved from manually specifying icon files to embedding them in executables. As shell scripts can't have icons embedded in them, a different solution is needed - this one solves the common case of running a CLI program in a terminal, and still allows embedding of icons in the executable itself as no shell script is needed, meaning it will be shown in the taskbar and system menu. The second use case of actually passing arguments to the executable itself (and not just "Terminal -e ...") is not covered by this and still requires an external script (meaning no icon for now), but I think that can easily be solved by adding something like an "Arguments" field to app files. :^)
132 lines
3.2 KiB
C++
132 lines
3.2 KiB
C++
/*
|
|
* Copyright (c) 2020, Linus Groh <linusg@serenityos.org>
|
|
* Copyright (c) 2021, Spencer Dixon <spencercdixon@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Function.h>
|
|
#include <AK/Vector.h>
|
|
#include <LibCore/ConfigFile.h>
|
|
#include <LibCore/DirIterator.h>
|
|
#include <LibDesktop/AppFile.h>
|
|
#include <errno.h>
|
|
#include <serenity.h>
|
|
#include <spawn.h>
|
|
|
|
namespace Desktop {
|
|
|
|
NonnullRefPtr<AppFile> AppFile::get_for_app(const StringView& app_name)
|
|
{
|
|
auto path = String::formatted("{}/{}.af", APP_FILES_DIRECTORY, app_name);
|
|
return open(path);
|
|
}
|
|
|
|
NonnullRefPtr<AppFile> AppFile::open(const StringView& path)
|
|
{
|
|
return adopt_ref(*new AppFile(path));
|
|
}
|
|
|
|
void AppFile::for_each(Function<void(NonnullRefPtr<AppFile>)> callback, const StringView& directory)
|
|
{
|
|
Core::DirIterator di(directory, Core::DirIterator::SkipDots);
|
|
if (di.has_error())
|
|
return;
|
|
while (di.has_next()) {
|
|
auto name = di.next_path();
|
|
if (!name.ends_with(".af"))
|
|
continue;
|
|
auto path = String::formatted("{}/{}", directory, name);
|
|
auto af = AppFile::open(path);
|
|
if (!af->is_valid())
|
|
continue;
|
|
callback(af);
|
|
}
|
|
}
|
|
|
|
AppFile::AppFile(const StringView& path)
|
|
: m_config(Core::ConfigFile::open(path))
|
|
, m_valid(validate())
|
|
{
|
|
}
|
|
|
|
AppFile::~AppFile()
|
|
{
|
|
}
|
|
|
|
bool AppFile::validate() const
|
|
{
|
|
if (m_config->read_entry("App", "Name").trim_whitespace().is_empty())
|
|
return false;
|
|
if (m_config->read_entry("App", "Executable").trim_whitespace().is_empty())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
String AppFile::name() const
|
|
{
|
|
auto name = m_config->read_entry("App", "Name").trim_whitespace();
|
|
VERIFY(!name.is_empty());
|
|
return name;
|
|
}
|
|
|
|
String AppFile::executable() const
|
|
{
|
|
auto executable = m_config->read_entry("App", "Executable").trim_whitespace();
|
|
VERIFY(!executable.is_empty());
|
|
return executable;
|
|
}
|
|
|
|
String AppFile::category() const
|
|
{
|
|
return m_config->read_entry("App", "Category").trim_whitespace();
|
|
}
|
|
|
|
bool AppFile::run_in_terminal() const
|
|
{
|
|
return m_config->read_bool_entry("App", "RunInTerminal", false);
|
|
}
|
|
|
|
Vector<String> AppFile::launcher_file_types() const
|
|
{
|
|
Vector<String> file_types;
|
|
for (auto& entry : m_config->read_entry("Launcher", "FileTypes").split(',')) {
|
|
entry = entry.trim_whitespace();
|
|
if (!entry.is_empty())
|
|
file_types.append(entry);
|
|
}
|
|
return file_types;
|
|
}
|
|
|
|
Vector<String> AppFile::launcher_protocols() const
|
|
{
|
|
Vector<String> protocols;
|
|
for (auto& entry : m_config->read_entry("Launcher", "Protocols").split(',')) {
|
|
entry = entry.trim_whitespace();
|
|
if (!entry.is_empty())
|
|
protocols.append(entry);
|
|
}
|
|
return protocols;
|
|
}
|
|
|
|
bool AppFile::spawn() const
|
|
{
|
|
if (!is_valid())
|
|
return false;
|
|
|
|
pid_t child_pid;
|
|
const char* argv[] = { executable().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 false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|