mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-30 18:12:45 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			221 lines
		
	
	
	
		
			6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			221 lines
		
	
	
	
		
			6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2020, Linus Groh <linusg@serenityos.org>
 | |
|  * Copyright (c) 2021, Spencer Dixon <spencercdixon@gmail.com>
 | |
|  * Copyright (c) 2022, the SerenityOS developers.
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include <AK/Function.h>
 | |
| #include <AK/Vector.h>
 | |
| #include <LibCore/ConfigFile.h>
 | |
| #include <LibCore/DirIterator.h>
 | |
| #include <LibCore/Process.h>
 | |
| #include <LibCore/StandardPaths.h>
 | |
| #include <LibDesktop/AppFile.h>
 | |
| #include <LibFileSystem/FileSystem.h>
 | |
| #include <LibGUI/MessageBox.h>
 | |
| 
 | |
| 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> AppFile::get_for_app(StringView app_name)
 | |
| {
 | |
|     return open(app_file_path_for_app(app_name));
 | |
| }
 | |
| 
 | |
| NonnullRefPtr<AppFile> AppFile::open(StringView path)
 | |
| {
 | |
|     return adopt_ref(*new AppFile(path));
 | |
| }
 | |
| 
 | |
| void AppFile::for_each(Function<void(NonnullRefPtr<AppFile>)> 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<ByteString> AppFile::launcher_mime_types() const
 | |
| {
 | |
|     Vector<ByteString> 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<ByteString> AppFile::launcher_file_types() const
 | |
| {
 | |
|     Vector<ByteString> 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<ByteString> AppFile::launcher_protocols() const
 | |
| {
 | |
|     Vector<ByteString> 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<StringView> 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<StringView> user_arguments) const
 | |
| {
 | |
|     if (!is_valid())
 | |
|         return false;
 | |
| 
 | |
|     StringView exe;
 | |
|     Vector<StringView, 2> 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<StringView> arguments) const
 | |
| {
 | |
|     if (!spawn_with_escalation(arguments))
 | |
|         GUI::MessageBox::show_error(&window, ByteString::formatted("Failed to spawn {} with escalation", executable()));
 | |
| }
 | |
| 
 | |
| }
 | 
