/* * Copyright (c) 2020, Linus Groh * Copyright (c) 2021, Spencer Dixon * Copyright (c) 2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include namespace Desktop { ByteString AppFile::app_file_path_for_app(StringView app_name) { return ByteString::formatted("{}/{}.af", APP_FILES_DIRECTORY, app_name); } bool AppFile::exists_for_app(StringView app_name) { return FileSystem::exists(app_file_path_for_app(app_name)); } NonnullRefPtr AppFile::get_for_app(StringView app_name) { return open(app_file_path_for_app(app_name)); } NonnullRefPtr AppFile::open(StringView path) { return adopt_ref(*new AppFile(path)); } void AppFile::for_each(Function)> callback, 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"sv)) continue; auto path = ByteString::formatted("{}/{}", directory, name); auto af = AppFile::open(path); if (!af->is_valid()) continue; callback(af); } } AppFile::AppFile(StringView path) : m_config(Core::ConfigFile::open(path).release_value_but_fixme_should_propagate_errors()) , m_valid(validate()) { } 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; } ByteString AppFile::name() const { auto name = m_config->read_entry("App", "Name").trim_whitespace().replace("&"sv, ""sv); VERIFY(!name.is_empty()); return name; } ByteString AppFile::menu_name() const { auto name = m_config->read_entry("App", "Name").trim_whitespace(); VERIFY(!name.is_empty()); return name; } ByteString AppFile::executable() const { auto executable = m_config->read_entry("App", "Executable").trim_whitespace(); VERIFY(!executable.is_empty()); return executable; } ByteString AppFile::description() const { return m_config->read_entry("App", "Description").trim_whitespace(); } ByteString AppFile::category() const { return m_config->read_entry("App", "Category").trim_whitespace(); } ByteString AppFile::working_directory() const { return m_config->read_entry("App", "WorkingDirectory").trim_whitespace(); } ByteString AppFile::icon_path() const { return m_config->read_entry("App", "IconPath").trim_whitespace(); } GUI::Icon AppFile::icon() const { auto override_icon = icon_path(); // FIXME: support pointing to actual .ico files if (!override_icon.is_empty()) return GUI::FileIconProvider::icon_for_path(override_icon); return GUI::FileIconProvider::icon_for_path(executable()); } bool AppFile::run_in_terminal() const { return m_config->read_bool_entry("App", "RunInTerminal", false); } bool AppFile::requires_root() const { return m_config->read_bool_entry("App", "RequiresRoot", false); } bool AppFile::exclude_from_system_menu() const { return m_config->read_bool_entry("App", "ExcludeFromSystemMenu", false); } Vector AppFile::launcher_mime_types() const { Vector mime_types; for (auto& entry : m_config->read_entry("Launcher", "MimeTypes").split(',')) { entry = entry.trim_whitespace(); if (!entry.is_empty()) mime_types.append(entry); } return mime_types; } Vector AppFile::launcher_file_types() const { Vector 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 AppFile::launcher_protocols() const { Vector 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(ReadonlySpan arguments) const { if (!is_valid()) return false; auto pid = Core::Process::spawn(executable(), arguments, working_directory()); if (pid.is_error()) return false; return true; } bool AppFile::spawn_with_escalation(ReadonlySpan user_arguments) const { if (!is_valid()) return false; StringView exe; Vector args; // FIXME: These single quotes won't be enough for executables with single quotes in their name. auto pls_with_executable = ByteString::formatted("/bin/pls '{}'", executable()); if (run_in_terminal() && !requires_root()) { exe = "/bin/Terminal"sv; args = { "-e"sv, executable().view() }; } else if (!run_in_terminal() && requires_root()) { exe = "/bin/Escalator"sv; args = { executable().view() }; } else if (run_in_terminal() && requires_root()) { exe = "/bin/Terminal"sv; args = { "-e"sv, pls_with_executable.view() }; } else { exe = executable().view(); } args.extend(Vector(user_arguments)); auto pid = Core::Process::spawn(exe, args.span(), working_directory().is_empty() ? Core::StandardPaths::home_directory() : working_directory()); if (pid.is_error()) return false; return true; } void AppFile::spawn_with_escalation_or_show_error(GUI::Window& window, ReadonlySpan arguments) const { if (!spawn_with_escalation(arguments)) GUI::MessageBox::show_error(&window, ByteString::formatted("Failed to spawn {} with escalation", executable())); } }