mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 16:42:44 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			395 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
 | |
|  * Copyright (c) 2021, Spencer Dixon <spencercdixon@gmail.com>
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include "TaskbarWindow.h"
 | |
| #include "ClockWidget.h"
 | |
| #include "QuickLaunchWidget.h"
 | |
| #include "TaskbarButton.h"
 | |
| #include <AK/Debug.h>
 | |
| #include <AK/Error.h>
 | |
| #include <AK/String.h>
 | |
| #include <LibCore/StandardPaths.h>
 | |
| #include <LibGUI/BoxLayout.h>
 | |
| #include <LibGUI/Button.h>
 | |
| #include <LibGUI/ConnectionToWindowManagerServer.h>
 | |
| #include <LibGUI/ConnectionToWindowServer.h>
 | |
| #include <LibGUI/Desktop.h>
 | |
| #include <LibGUI/Frame.h>
 | |
| #include <LibGUI/Icon.h>
 | |
| #include <LibGUI/Menu.h>
 | |
| #include <LibGUI/Painter.h>
 | |
| #include <LibGUI/Window.h>
 | |
| #include <LibGfx/Font/FontDatabase.h>
 | |
| #include <LibGfx/Palette.h>
 | |
| #include <serenity.h>
 | |
| #include <stdio.h>
 | |
| 
 | |
| class TaskbarWidget final : public GUI::Widget {
 | |
|     C_OBJECT(TaskbarWidget);
 | |
| 
 | |
| public:
 | |
|     virtual ~TaskbarWidget() override = default;
 | |
| 
 | |
| private:
 | |
|     TaskbarWidget() = default;
 | |
| 
 | |
|     virtual void paint_event(GUI::PaintEvent& event) override
 | |
|     {
 | |
|         GUI::Painter painter(*this);
 | |
|         painter.add_clip_rect(event.rect());
 | |
|         painter.fill_rect(rect(), palette().button());
 | |
|         painter.draw_line({ 0, 1 }, { width() - 1, 1 }, palette().threed_highlight());
 | |
|     }
 | |
| 
 | |
|     virtual void did_layout() override
 | |
|     {
 | |
|         WindowList::the().for_each_window([&](auto& window) {
 | |
|             if (auto* button = window.button())
 | |
|                 static_cast<TaskbarButton*>(button)->update_taskbar_rect();
 | |
|         });
 | |
|     }
 | |
| };
 | |
| 
 | |
| ErrorOr<NonnullRefPtr<TaskbarWindow>> TaskbarWindow::create()
 | |
| {
 | |
|     auto window = TRY(AK::adopt_nonnull_ref_or_enomem(new (nothrow) TaskbarWindow()));
 | |
|     TRY(window->populate_taskbar());
 | |
|     TRY(window->load_assistant());
 | |
|     return window;
 | |
| }
 | |
| 
 | |
| TaskbarWindow::TaskbarWindow()
 | |
| {
 | |
|     set_window_type(GUI::WindowType::Taskbar);
 | |
|     set_title("Taskbar");
 | |
| 
 | |
|     on_screen_rects_change(GUI::Desktop::the().rects(), GUI::Desktop::the().main_screen_index());
 | |
| }
 | |
| 
 | |
| ErrorOr<void> TaskbarWindow::populate_taskbar()
 | |
| {
 | |
|     auto main_widget = TRY(set_main_widget<TaskbarWidget>());
 | |
|     TRY(main_widget->try_set_layout<GUI::HorizontalBoxLayout>(GUI::Margins { 2, 3, 0, 3 }));
 | |
| 
 | |
|     m_quick_launch = TRY(Taskbar::QuickLaunchWidget::create());
 | |
|     TRY(main_widget->try_add_child(*m_quick_launch));
 | |
| 
 | |
|     m_task_button_container = TRY(main_widget->try_add<GUI::Widget>());
 | |
|     TRY(m_task_button_container->try_set_layout<GUI::HorizontalBoxLayout>(GUI::Margins {}, 3));
 | |
| 
 | |
|     m_default_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/window.png"sv));
 | |
| 
 | |
|     m_applet_area_container = TRY(main_widget->try_add<GUI::Frame>());
 | |
|     m_applet_area_container->set_frame_thickness(1);
 | |
|     m_applet_area_container->set_frame_shape(Gfx::FrameShape::Box);
 | |
|     m_applet_area_container->set_frame_shadow(Gfx::FrameShadow::Sunken);
 | |
| 
 | |
|     m_clock_widget = TRY(main_widget->try_add<Taskbar::ClockWidget>());
 | |
| 
 | |
|     m_show_desktop_button = TRY(main_widget->try_add<GUI::Button>());
 | |
|     m_show_desktop_button->set_tooltip("Show Desktop");
 | |
|     m_show_desktop_button->set_icon(TRY(GUI::Icon::try_create_default_icon("desktop"sv)).bitmap_for_size(16));
 | |
|     m_show_desktop_button->set_button_style(Gfx::ButtonStyle::Coolbar);
 | |
|     m_show_desktop_button->set_fixed_size(24, 24);
 | |
|     m_show_desktop_button->on_click = TaskbarWindow::show_desktop_button_clicked;
 | |
| 
 | |
|     return {};
 | |
| }
 | |
| 
 | |
| ErrorOr<void> TaskbarWindow::load_assistant()
 | |
| {
 | |
|     auto af_path = TRY(String::formatted("{}/{}", Desktop::AppFile::APP_FILES_DIRECTORY, "Assistant.af"));
 | |
|     m_assistant_app_file = Desktop::AppFile::open(af_path);
 | |
| 
 | |
|     return {};
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::add_system_menu(NonnullRefPtr<GUI::Menu> system_menu)
 | |
| {
 | |
|     m_system_menu = move(system_menu);
 | |
| 
 | |
|     m_start_button = GUI::Button::construct("Serenity"_string.release_value_but_fixme_should_propagate_errors());
 | |
|     set_start_button_font(Gfx::FontDatabase::default_font().bold_variant());
 | |
|     m_start_button->set_icon_spacing(0);
 | |
|     auto app_icon = GUI::Icon::default_icon("ladyball"sv);
 | |
|     m_start_button->set_icon(app_icon.bitmap_for_size(16));
 | |
|     m_start_button->set_menu(m_system_menu);
 | |
| 
 | |
|     GUI::Widget* main = main_widget();
 | |
|     main->insert_child_before(*m_start_button, *m_quick_launch);
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::config_string_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value)
 | |
| {
 | |
|     if (domain == "Taskbar" && group == "Clock" && key == "TimeFormat") {
 | |
|         m_clock_widget->update_format(value);
 | |
|         update_applet_area();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::show_desktop_button_clicked(unsigned)
 | |
| {
 | |
|     toggle_show_desktop();
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::toggle_show_desktop()
 | |
| {
 | |
|     GUI::ConnectionToWindowManagerServer::the().async_toggle_show_desktop();
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::on_screen_rects_change(Vector<Gfx::IntRect, 4> const& rects, size_t main_screen_index)
 | |
| {
 | |
|     auto const& rect = rects[main_screen_index];
 | |
|     Gfx::IntRect new_rect { rect.x(), rect.bottom() - taskbar_height() + 1, rect.width(), taskbar_height() };
 | |
|     set_rect(new_rect);
 | |
|     update_applet_area();
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::update_applet_area()
 | |
| {
 | |
|     // NOTE: Widget layout is normally lazy, but here we have to force it right away so we can tell
 | |
|     //       WindowServer where to place the applet area window.
 | |
|     if (!main_widget())
 | |
|         return;
 | |
|     main_widget()->do_layout();
 | |
|     auto new_rect = Gfx::IntRect({}, m_applet_area_size).centered_within(m_applet_area_container->screen_relative_rect());
 | |
|     GUI::ConnectionToWindowManagerServer::the().async_set_applet_area_position(new_rect.location());
 | |
| }
 | |
| 
 | |
| NonnullRefPtr<GUI::Button> TaskbarWindow::create_button(WindowIdentifier const& identifier)
 | |
| {
 | |
|     auto& button = m_task_button_container->add<TaskbarButton>(identifier);
 | |
|     button.set_min_size(20, 21);
 | |
|     button.set_max_size(140, 21);
 | |
|     button.set_text_alignment(Gfx::TextAlignment::CenterLeft);
 | |
|     button.set_icon(*m_default_icon);
 | |
|     return button;
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::add_window_button(::Window& window, WindowIdentifier const& identifier)
 | |
| {
 | |
|     if (window.button())
 | |
|         return;
 | |
|     window.set_button(create_button(identifier));
 | |
|     auto* button = window.button();
 | |
|     button->on_click = [window = &window, identifier](auto) {
 | |
|         if (window->is_minimized() || !window->is_active())
 | |
|             GUI::ConnectionToWindowManagerServer::the().async_set_active_window(identifier.client_id(), identifier.window_id());
 | |
|         else if (!window->is_blocked())
 | |
|             GUI::ConnectionToWindowManagerServer::the().async_set_window_minimized(identifier.client_id(), identifier.window_id(), true);
 | |
|     };
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::remove_window_button(::Window& window, bool was_removed)
 | |
| {
 | |
|     auto* button = window.button();
 | |
|     if (!button)
 | |
|         return;
 | |
|     if (!was_removed)
 | |
|         static_cast<TaskbarButton*>(button)->clear_taskbar_rect();
 | |
|     window.set_button(nullptr);
 | |
|     button->remove_from_parent();
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::update_window_button(::Window& window, bool show_as_active)
 | |
| {
 | |
|     auto* button = window.button();
 | |
|     if (!button)
 | |
|         return;
 | |
|     button->set_text(String::from_deprecated_string(window.title()).release_value_but_fixme_should_propagate_errors());
 | |
|     button->set_tooltip(window.title());
 | |
|     button->set_checked(show_as_active);
 | |
|     button->set_visible(is_window_on_current_workspace(window));
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::event(Core::Event& event)
 | |
| {
 | |
|     switch (event.type()) {
 | |
|     case GUI::Event::MouseDown: {
 | |
|         // If the cursor is at the edge/corner of the screen but technically not within the start button (or other taskbar buttons),
 | |
|         // we adjust it so that the nearest button ends up being clicked anyways.
 | |
| 
 | |
|         auto& mouse_event = static_cast<GUI::MouseEvent&>(event);
 | |
|         int const ADJUSTMENT = 4;
 | |
|         auto adjusted_x = AK::clamp(mouse_event.x(), ADJUSTMENT, width() - ADJUSTMENT);
 | |
|         auto adjusted_y = AK::min(mouse_event.y(), height() - ADJUSTMENT);
 | |
|         Gfx::IntPoint adjusted_point = { adjusted_x, adjusted_y };
 | |
| 
 | |
|         if (adjusted_point != mouse_event.position()) {
 | |
|             GUI::ConnectionToWindowServer::the().async_set_global_cursor_position(position() + adjusted_point);
 | |
|             GUI::MouseEvent adjusted_event = { (GUI::Event::Type)mouse_event.type(), adjusted_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers(), mouse_event.wheel_delta_x(), mouse_event.wheel_delta_y(), mouse_event.wheel_raw_delta_x(), mouse_event.wheel_raw_delta_y() };
 | |
|             Window::event(adjusted_event);
 | |
|             return;
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|     case GUI::Event::FontsChange:
 | |
|         set_start_button_font(Gfx::FontDatabase::default_font().bold_variant());
 | |
|         break;
 | |
|     }
 | |
|     Window::event(event);
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::wm_event(GUI::WMEvent& event)
 | |
| {
 | |
|     WindowIdentifier identifier { event.client_id(), event.window_id() };
 | |
|     switch (event.type()) {
 | |
|     case GUI::Event::WM_WindowRemoved: {
 | |
|         if constexpr (EVENT_DEBUG) {
 | |
|             auto& removed_event = static_cast<GUI::WMWindowRemovedEvent&>(event);
 | |
|             dbgln("WM_WindowRemoved: client_id={}, window_id={}",
 | |
|                 removed_event.client_id(),
 | |
|                 removed_event.window_id());
 | |
|         }
 | |
|         if (auto* window = WindowList::the().window(identifier))
 | |
|             remove_window_button(*window, true);
 | |
|         WindowList::the().remove_window(identifier);
 | |
|         update();
 | |
|         break;
 | |
|     }
 | |
|     case GUI::Event::WM_WindowRectChanged: {
 | |
|         if constexpr (EVENT_DEBUG) {
 | |
|             auto& changed_event = static_cast<GUI::WMWindowRectChangedEvent&>(event);
 | |
|             dbgln("WM_WindowRectChanged: client_id={}, window_id={}, rect={}",
 | |
|                 changed_event.client_id(),
 | |
|                 changed_event.window_id(),
 | |
|                 changed_event.rect());
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     case GUI::Event::WM_WindowIconBitmapChanged: {
 | |
|         auto& changed_event = static_cast<GUI::WMWindowIconBitmapChangedEvent&>(event);
 | |
|         if (auto* window = WindowList::the().window(identifier)) {
 | |
|             if (window->button()) {
 | |
|                 auto icon = changed_event.bitmap();
 | |
|                 if (icon->height() != taskbar_icon_size() || icon->width() != taskbar_icon_size()) {
 | |
|                     auto sw = taskbar_icon_size() / (float)icon->width();
 | |
|                     auto sh = taskbar_icon_size() / (float)icon->height();
 | |
|                     auto scaled_bitmap_or_error = icon->scaled(sw, sh);
 | |
|                     if (scaled_bitmap_or_error.is_error())
 | |
|                         window->button()->set_icon(nullptr);
 | |
|                     else
 | |
|                         window->button()->set_icon(scaled_bitmap_or_error.release_value());
 | |
|                 } else {
 | |
|                     window->button()->set_icon(icon);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     case GUI::Event::WM_WindowStateChanged: {
 | |
|         auto& changed_event = static_cast<GUI::WMWindowStateChangedEvent&>(event);
 | |
|         if constexpr (EVENT_DEBUG) {
 | |
|             dbgln("WM_WindowStateChanged: client_id={}, window_id={}, title={}, rect={}, is_active={}, is_blocked={}, is_minimized={}",
 | |
|                 changed_event.client_id(),
 | |
|                 changed_event.window_id(),
 | |
|                 changed_event.title(),
 | |
|                 changed_event.rect(),
 | |
|                 changed_event.is_active(),
 | |
|                 changed_event.is_blocked(),
 | |
|                 changed_event.is_minimized());
 | |
|         }
 | |
|         if (changed_event.window_type() != GUI::WindowType::Normal || changed_event.is_frameless()) {
 | |
|             if (auto* window = WindowList::the().window(identifier))
 | |
|                 remove_window_button(*window, false);
 | |
|             break;
 | |
|         }
 | |
|         auto& window = WindowList::the().ensure_window(identifier);
 | |
|         window.set_title(changed_event.title());
 | |
|         window.set_rect(changed_event.rect());
 | |
|         window.set_active(changed_event.is_active());
 | |
|         window.set_blocked(changed_event.is_blocked());
 | |
|         window.set_minimized(changed_event.is_minimized());
 | |
|         window.set_progress(changed_event.progress());
 | |
|         window.set_workspace(changed_event.workspace_row(), changed_event.workspace_column());
 | |
|         add_window_button(window, identifier);
 | |
|         update_window_button(window, window.is_active());
 | |
|         break;
 | |
|     }
 | |
|     case GUI::Event::WM_AppletAreaSizeChanged: {
 | |
|         auto& changed_event = static_cast<GUI::WMAppletAreaSizeChangedEvent&>(event);
 | |
|         m_applet_area_size = changed_event.size();
 | |
|         m_applet_area_container->set_fixed_size(changed_event.size().width() + 8, 21);
 | |
|         update_applet_area();
 | |
|         break;
 | |
|     }
 | |
|     case GUI::Event::WM_SuperKeyPressed: {
 | |
|         if (!m_system_menu)
 | |
|             break;
 | |
| 
 | |
|         if (m_system_menu->is_visible()) {
 | |
|             m_system_menu->dismiss();
 | |
|         } else {
 | |
|             m_system_menu->popup(m_start_button->screen_relative_rect().top_left());
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|     case GUI::Event::WM_SuperSpaceKeyPressed: {
 | |
|         if (!m_assistant_app_file->spawn())
 | |
|             warnln("failed to spawn 'Assistant' when requested via Super+Space");
 | |
|         break;
 | |
|     }
 | |
|     case GUI::Event::WM_SuperDKeyPressed: {
 | |
|         toggle_show_desktop();
 | |
|         break;
 | |
|     }
 | |
|     case GUI::Event::WM_SuperDigitKeyPressed: {
 | |
|         auto& digit_event = static_cast<GUI::WMSuperDigitKeyPressedEvent&>(event);
 | |
|         auto index = digit_event.digit() != 0 ? digit_event.digit() - 1 : 9;
 | |
| 
 | |
|         for (auto& widget : m_task_button_container->child_widgets()) {
 | |
|             // NOTE: The button might be invisible depending on the current workspace
 | |
|             if (!widget.is_visible())
 | |
|                 continue;
 | |
| 
 | |
|             if (index == 0) {
 | |
|                 static_cast<TaskbarButton&>(widget).click();
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             --index;
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|     case GUI::Event::WM_WorkspaceChanged: {
 | |
|         auto& changed_event = static_cast<GUI::WMWorkspaceChangedEvent&>(event);
 | |
|         workspace_change_event(changed_event.current_row(), changed_event.current_column());
 | |
|         break;
 | |
|     }
 | |
|     default:
 | |
|         break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::screen_rects_change_event(GUI::ScreenRectsChangeEvent& event)
 | |
| {
 | |
|     on_screen_rects_change(event.rects(), event.main_screen_index());
 | |
| }
 | |
| 
 | |
| bool TaskbarWindow::is_window_on_current_workspace(::Window& window) const
 | |
| {
 | |
|     return window.workspace_row() == m_current_workspace_row && window.workspace_column() == m_current_workspace_column;
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::workspace_change_event(unsigned current_row, unsigned current_column)
 | |
| {
 | |
|     m_current_workspace_row = current_row;
 | |
|     m_current_workspace_column = current_column;
 | |
| 
 | |
|     WindowList::the().for_each_window([&](auto& window) {
 | |
|         if (auto* button = window.button())
 | |
|             button->set_visible(is_window_on_current_workspace(window));
 | |
|     });
 | |
| }
 | |
| 
 | |
| void TaskbarWindow::set_start_button_font(Gfx::Font const& font)
 | |
| {
 | |
|     m_start_button->set_font(font);
 | |
|     m_start_button->set_fixed_size(font.width(m_start_button->text()) + 30, 21);
 | |
| }
 | 
