/* * Copyright (c) 2021, Fabian Blatz * * SPDX-License-Identifier: BSD-2-Clause */ #include "QuickLaunchWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Taskbar { constexpr auto quick_launch = "QuickLaunch"sv; constexpr int quick_launch_button_size = 24; ErrorOr QuickLaunchEntryAppFile::launch() const { auto executable = m_app_file->executable(); pid_t pid = TRY(Core::System::fork()); if (pid == 0) { if (chdir(Core::StandardPaths::home_directory().characters()) < 0) { perror("chdir"); exit(1); } if (m_app_file->run_in_terminal()) execl("/bin/Terminal", "Terminal", "-e", executable.characters(), nullptr); else execl(executable.characters(), executable.characters(), nullptr); perror("execl"); VERIFY_NOT_REACHED(); } else TRY(Core::System::disown(pid)); return {}; } ErrorOr QuickLaunchEntryExecutable::launch() const { TRY(Core::Process::spawn(m_path)); return {}; } GUI::Icon QuickLaunchEntryExecutable::icon() const { return GUI::FileIconProvider::icon_for_executable(m_path); } String QuickLaunchEntryExecutable::name() const { return LexicalPath { m_path }.basename(); } ErrorOr QuickLaunchEntryFile::launch() const { if (!Desktop::Launcher::open(URL::create_with_url_or_path(m_path))) { // FIXME: LaunchServer doesn't inform us about errors return Error::from_string_literal("Failed to open file"); } return {}; } GUI::Icon QuickLaunchEntryFile::icon() const { return GUI::FileIconProvider::icon_for_path(m_path); } String QuickLaunchEntryFile::name() const { // '=' is a special character in config files return m_path; } QuickLaunchWidget::QuickLaunchWidget() { set_shrink_to_fit(true); set_layout(); layout()->set_spacing(0); set_frame_thickness(0); set_fixed_height(24); m_context_menu = GUI::Menu::construct(); m_context_menu_default_action = GUI::Action::create("&Remove", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/delete.png"sv).release_value_but_fixme_should_propagate_errors(), [this](auto&) { Config::remove_key("Taskbar"sv, quick_launch, m_context_menu_app_name); auto button = find_child_of_type_named(m_context_menu_app_name); if (button) { remove_child(*button); } }); m_context_menu->add_action(*m_context_menu_default_action); auto keys = Config::list_keys("Taskbar"sv, quick_launch); for (auto& name : keys) { auto value = Config::read_string("Taskbar"sv, quick_launch, name); auto entry = QuickLaunchEntry::create_from_config_value(value); if (!entry) continue; add_or_adjust_button(name, entry.release_nonnull()); } } OwnPtr QuickLaunchEntry::create_from_config_value(StringView value) { if (!value.starts_with('/') && value.ends_with(".af"sv)) { auto af_path = String::formatted("{}/{}", Desktop::AppFile::APP_FILES_DIRECTORY, value); return make(Desktop::AppFile::open(af_path)); } return create_from_path(value); } OwnPtr QuickLaunchEntry::create_from_path(StringView path) { if (path.ends_with(".af"sv)) return make(Desktop::AppFile::open(path)); auto stat_or_error = Core::System::stat(path); if (stat_or_error.is_error()) { dbgln("Failed to stat quick launch entry file: {}", stat_or_error.release_error()); return {}; } auto stat = stat_or_error.release_value(); if (S_ISREG(stat.st_mode) && (stat.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) return make(path); return make(path); } static String sanitize_entry_name(String const& name) { return name.replace(" "sv, ""sv, ReplaceMode::All).replace("="sv, ""sv, ReplaceMode::All); } void QuickLaunchWidget::add_or_adjust_button(String const& button_name, NonnullOwnPtr&& entry) { auto file_name_to_watch = entry->file_name_to_watch(); if (!file_name_to_watch.is_null()) { if (!m_watcher) { // FIXME: Propagate errors m_watcher = MUST(Core::FileWatcher::create()); m_watcher->on_change = [this](Core::FileWatcherEvent const& event) { auto name = sanitize_entry_name(event.event_path); dbgln("Removing QuickLaunch entry {}", name); auto button = find_child_of_type_named(name); if (button) remove_child(*button); }; } // FIXME: Propagate errors MUST(m_watcher->add_watch(file_name_to_watch, Core::FileWatcherEvent::Type::Deleted)); } auto button = find_child_of_type_named(button_name); if (!button) button = &add(); button->set_fixed_size(quick_launch_button_size, quick_launch_button_size); button->set_button_style(Gfx::ButtonStyle::Coolbar); auto icon = entry->icon(); button->set_icon(icon.bitmap_for_size(16)); button->set_tooltip(entry->name()); button->set_name(button_name); button->on_click = [entry = move(entry), this](auto) { auto result = entry->launch(); if (result.is_error()) { // FIXME: This message box is displayed in a weird position GUI::MessageBox::show_error(window(), String::formatted("Failed to open quick launch entry: {}", result.release_error())); } }; button->on_context_menu_request = [this, button_name](auto& context_menu_event) { m_context_menu_app_name = button_name; m_context_menu->popup(context_menu_event.screen_position(), m_context_menu_default_action); }; } void QuickLaunchWidget::config_key_was_removed(String const& domain, String const& group, String const& key) { if (domain == "Taskbar" && group == quick_launch) { auto button = find_child_of_type_named(key); if (button) remove_child(*button); } } void QuickLaunchWidget::config_string_did_change(String const& domain, String const& group, String const& key, String const& value) { if (domain == "Taskbar" && group == quick_launch) { auto entry = QuickLaunchEntry::create_from_config_value(value); if (!entry) return; add_or_adjust_button(key, entry.release_nonnull()); } } void QuickLaunchWidget::drag_enter_event(GUI::DragEvent& event) { auto const& mime_types = event.mime_types(); if (mime_types.contains_slow("text/uri-list")) event.accept(); } void QuickLaunchWidget::drop_event(GUI::DropEvent& event) { event.accept(); if (event.mime_data().has_urls()) { auto urls = event.mime_data().urls(); for (auto& url : urls) { auto entry = QuickLaunchEntry::create_from_path(url.path()); if (entry) { auto item_name = sanitize_entry_name(entry->name()); add_or_adjust_button(item_name, entry.release_nonnull()); Config::write_string("Taskbar"sv, quick_launch, item_name, url.path()); } } } } }