Serenity Operating System
at master 395 lines 15 kB view raw
1/* 2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2021, Spencer Dixon <spencercdixon@gmail.com> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "TaskbarWindow.h" 9#include "ClockWidget.h" 10#include "QuickLaunchWidget.h" 11#include "TaskbarButton.h" 12#include <AK/Debug.h> 13#include <AK/Error.h> 14#include <AK/String.h> 15#include <LibCore/StandardPaths.h> 16#include <LibGUI/BoxLayout.h> 17#include <LibGUI/Button.h> 18#include <LibGUI/ConnectionToWindowManagerServer.h> 19#include <LibGUI/ConnectionToWindowServer.h> 20#include <LibGUI/Desktop.h> 21#include <LibGUI/Frame.h> 22#include <LibGUI/Icon.h> 23#include <LibGUI/Menu.h> 24#include <LibGUI/Painter.h> 25#include <LibGUI/Window.h> 26#include <LibGfx/Font/FontDatabase.h> 27#include <LibGfx/Palette.h> 28#include <serenity.h> 29#include <stdio.h> 30 31class TaskbarWidget final : public GUI::Widget { 32 C_OBJECT(TaskbarWidget); 33 34public: 35 virtual ~TaskbarWidget() override = default; 36 37private: 38 TaskbarWidget() = default; 39 40 virtual void paint_event(GUI::PaintEvent& event) override 41 { 42 GUI::Painter painter(*this); 43 painter.add_clip_rect(event.rect()); 44 painter.fill_rect(rect(), palette().button()); 45 painter.draw_line({ 0, 1 }, { width() - 1, 1 }, palette().threed_highlight()); 46 } 47 48 virtual void did_layout() override 49 { 50 WindowList::the().for_each_window([&](auto& window) { 51 if (auto* button = window.button()) 52 static_cast<TaskbarButton*>(button)->update_taskbar_rect(); 53 }); 54 } 55}; 56 57ErrorOr<NonnullRefPtr<TaskbarWindow>> TaskbarWindow::create() 58{ 59 auto window = TRY(AK::adopt_nonnull_ref_or_enomem(new (nothrow) TaskbarWindow())); 60 TRY(window->populate_taskbar()); 61 TRY(window->load_assistant()); 62 return window; 63} 64 65TaskbarWindow::TaskbarWindow() 66{ 67 set_window_type(GUI::WindowType::Taskbar); 68 set_title("Taskbar"); 69 70 on_screen_rects_change(GUI::Desktop::the().rects(), GUI::Desktop::the().main_screen_index()); 71} 72 73ErrorOr<void> TaskbarWindow::populate_taskbar() 74{ 75 auto main_widget = TRY(set_main_widget<TaskbarWidget>()); 76 TRY(main_widget->try_set_layout<GUI::HorizontalBoxLayout>(GUI::Margins { 2, 3, 0, 3 })); 77 78 m_quick_launch = TRY(Taskbar::QuickLaunchWidget::create()); 79 TRY(main_widget->try_add_child(*m_quick_launch)); 80 81 m_task_button_container = TRY(main_widget->try_add<GUI::Widget>()); 82 TRY(m_task_button_container->try_set_layout<GUI::HorizontalBoxLayout>(GUI::Margins {}, 3)); 83 84 m_default_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/window.png"sv)); 85 86 m_applet_area_container = TRY(main_widget->try_add<GUI::Frame>()); 87 m_applet_area_container->set_frame_thickness(1); 88 m_applet_area_container->set_frame_shape(Gfx::FrameShape::Box); 89 m_applet_area_container->set_frame_shadow(Gfx::FrameShadow::Sunken); 90 91 m_clock_widget = TRY(main_widget->try_add<Taskbar::ClockWidget>()); 92 93 m_show_desktop_button = TRY(main_widget->try_add<GUI::Button>()); 94 m_show_desktop_button->set_tooltip("Show Desktop"); 95 m_show_desktop_button->set_icon(TRY(GUI::Icon::try_create_default_icon("desktop"sv)).bitmap_for_size(16)); 96 m_show_desktop_button->set_button_style(Gfx::ButtonStyle::Coolbar); 97 m_show_desktop_button->set_fixed_size(24, 24); 98 m_show_desktop_button->on_click = TaskbarWindow::show_desktop_button_clicked; 99 100 return {}; 101} 102 103ErrorOr<void> TaskbarWindow::load_assistant() 104{ 105 auto af_path = TRY(String::formatted("{}/{}", Desktop::AppFile::APP_FILES_DIRECTORY, "Assistant.af")); 106 m_assistant_app_file = Desktop::AppFile::open(af_path); 107 108 return {}; 109} 110 111void TaskbarWindow::add_system_menu(NonnullRefPtr<GUI::Menu> system_menu) 112{ 113 m_system_menu = move(system_menu); 114 115 m_start_button = GUI::Button::construct("Serenity"_string.release_value_but_fixme_should_propagate_errors()); 116 set_start_button_font(Gfx::FontDatabase::default_font().bold_variant()); 117 m_start_button->set_icon_spacing(0); 118 auto app_icon = GUI::Icon::default_icon("ladyball"sv); 119 m_start_button->set_icon(app_icon.bitmap_for_size(16)); 120 m_start_button->set_menu(m_system_menu); 121 122 GUI::Widget* main = main_widget(); 123 main->insert_child_before(*m_start_button, *m_quick_launch); 124} 125 126void TaskbarWindow::config_string_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value) 127{ 128 if (domain == "Taskbar" && group == "Clock" && key == "TimeFormat") { 129 m_clock_widget->update_format(value); 130 update_applet_area(); 131 } 132} 133 134void TaskbarWindow::show_desktop_button_clicked(unsigned) 135{ 136 toggle_show_desktop(); 137} 138 139void TaskbarWindow::toggle_show_desktop() 140{ 141 GUI::ConnectionToWindowManagerServer::the().async_toggle_show_desktop(); 142} 143 144void TaskbarWindow::on_screen_rects_change(Vector<Gfx::IntRect, 4> const& rects, size_t main_screen_index) 145{ 146 auto const& rect = rects[main_screen_index]; 147 Gfx::IntRect new_rect { rect.x(), rect.bottom() - taskbar_height() + 1, rect.width(), taskbar_height() }; 148 set_rect(new_rect); 149 update_applet_area(); 150} 151 152void TaskbarWindow::update_applet_area() 153{ 154 // NOTE: Widget layout is normally lazy, but here we have to force it right away so we can tell 155 // WindowServer where to place the applet area window. 156 if (!main_widget()) 157 return; 158 main_widget()->do_layout(); 159 auto new_rect = Gfx::IntRect({}, m_applet_area_size).centered_within(m_applet_area_container->screen_relative_rect()); 160 GUI::ConnectionToWindowManagerServer::the().async_set_applet_area_position(new_rect.location()); 161} 162 163NonnullRefPtr<GUI::Button> TaskbarWindow::create_button(WindowIdentifier const& identifier) 164{ 165 auto& button = m_task_button_container->add<TaskbarButton>(identifier); 166 button.set_min_size(20, 21); 167 button.set_max_size(140, 21); 168 button.set_text_alignment(Gfx::TextAlignment::CenterLeft); 169 button.set_icon(*m_default_icon); 170 return button; 171} 172 173void TaskbarWindow::add_window_button(::Window& window, WindowIdentifier const& identifier) 174{ 175 if (window.button()) 176 return; 177 window.set_button(create_button(identifier)); 178 auto* button = window.button(); 179 button->on_click = [window = &window, identifier](auto) { 180 if (window->is_minimized() || !window->is_active()) 181 GUI::ConnectionToWindowManagerServer::the().async_set_active_window(identifier.client_id(), identifier.window_id()); 182 else if (!window->is_blocked()) 183 GUI::ConnectionToWindowManagerServer::the().async_set_window_minimized(identifier.client_id(), identifier.window_id(), true); 184 }; 185} 186 187void TaskbarWindow::remove_window_button(::Window& window, bool was_removed) 188{ 189 auto* button = window.button(); 190 if (!button) 191 return; 192 if (!was_removed) 193 static_cast<TaskbarButton*>(button)->clear_taskbar_rect(); 194 window.set_button(nullptr); 195 button->remove_from_parent(); 196} 197 198void TaskbarWindow::update_window_button(::Window& window, bool show_as_active) 199{ 200 auto* button = window.button(); 201 if (!button) 202 return; 203 button->set_text(String::from_deprecated_string(window.title()).release_value_but_fixme_should_propagate_errors()); 204 button->set_tooltip(window.title()); 205 button->set_checked(show_as_active); 206 button->set_visible(is_window_on_current_workspace(window)); 207} 208 209void TaskbarWindow::event(Core::Event& event) 210{ 211 switch (event.type()) { 212 case GUI::Event::MouseDown: { 213 // If the cursor is at the edge/corner of the screen but technically not within the start button (or other taskbar buttons), 214 // we adjust it so that the nearest button ends up being clicked anyways. 215 216 auto& mouse_event = static_cast<GUI::MouseEvent&>(event); 217 int const ADJUSTMENT = 4; 218 auto adjusted_x = AK::clamp(mouse_event.x(), ADJUSTMENT, width() - ADJUSTMENT); 219 auto adjusted_y = AK::min(mouse_event.y(), height() - ADJUSTMENT); 220 Gfx::IntPoint adjusted_point = { adjusted_x, adjusted_y }; 221 222 if (adjusted_point != mouse_event.position()) { 223 GUI::ConnectionToWindowServer::the().async_set_global_cursor_position(position() + adjusted_point); 224 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() }; 225 Window::event(adjusted_event); 226 return; 227 } 228 break; 229 } 230 case GUI::Event::FontsChange: 231 set_start_button_font(Gfx::FontDatabase::default_font().bold_variant()); 232 break; 233 } 234 Window::event(event); 235} 236 237void TaskbarWindow::wm_event(GUI::WMEvent& event) 238{ 239 WindowIdentifier identifier { event.client_id(), event.window_id() }; 240 switch (event.type()) { 241 case GUI::Event::WM_WindowRemoved: { 242 if constexpr (EVENT_DEBUG) { 243 auto& removed_event = static_cast<GUI::WMWindowRemovedEvent&>(event); 244 dbgln("WM_WindowRemoved: client_id={}, window_id={}", 245 removed_event.client_id(), 246 removed_event.window_id()); 247 } 248 if (auto* window = WindowList::the().window(identifier)) 249 remove_window_button(*window, true); 250 WindowList::the().remove_window(identifier); 251 update(); 252 break; 253 } 254 case GUI::Event::WM_WindowRectChanged: { 255 if constexpr (EVENT_DEBUG) { 256 auto& changed_event = static_cast<GUI::WMWindowRectChangedEvent&>(event); 257 dbgln("WM_WindowRectChanged: client_id={}, window_id={}, rect={}", 258 changed_event.client_id(), 259 changed_event.window_id(), 260 changed_event.rect()); 261 } 262 break; 263 } 264 265 case GUI::Event::WM_WindowIconBitmapChanged: { 266 auto& changed_event = static_cast<GUI::WMWindowIconBitmapChangedEvent&>(event); 267 if (auto* window = WindowList::the().window(identifier)) { 268 if (window->button()) { 269 auto icon = changed_event.bitmap(); 270 if (icon->height() != taskbar_icon_size() || icon->width() != taskbar_icon_size()) { 271 auto sw = taskbar_icon_size() / (float)icon->width(); 272 auto sh = taskbar_icon_size() / (float)icon->height(); 273 auto scaled_bitmap_or_error = icon->scaled(sw, sh); 274 if (scaled_bitmap_or_error.is_error()) 275 window->button()->set_icon(nullptr); 276 else 277 window->button()->set_icon(scaled_bitmap_or_error.release_value()); 278 } else { 279 window->button()->set_icon(icon); 280 } 281 } 282 } 283 break; 284 } 285 286 case GUI::Event::WM_WindowStateChanged: { 287 auto& changed_event = static_cast<GUI::WMWindowStateChangedEvent&>(event); 288 if constexpr (EVENT_DEBUG) { 289 dbgln("WM_WindowStateChanged: client_id={}, window_id={}, title={}, rect={}, is_active={}, is_blocked={}, is_minimized={}", 290 changed_event.client_id(), 291 changed_event.window_id(), 292 changed_event.title(), 293 changed_event.rect(), 294 changed_event.is_active(), 295 changed_event.is_blocked(), 296 changed_event.is_minimized()); 297 } 298 if (changed_event.window_type() != GUI::WindowType::Normal || changed_event.is_frameless()) { 299 if (auto* window = WindowList::the().window(identifier)) 300 remove_window_button(*window, false); 301 break; 302 } 303 auto& window = WindowList::the().ensure_window(identifier); 304 window.set_title(changed_event.title()); 305 window.set_rect(changed_event.rect()); 306 window.set_active(changed_event.is_active()); 307 window.set_blocked(changed_event.is_blocked()); 308 window.set_minimized(changed_event.is_minimized()); 309 window.set_progress(changed_event.progress()); 310 window.set_workspace(changed_event.workspace_row(), changed_event.workspace_column()); 311 add_window_button(window, identifier); 312 update_window_button(window, window.is_active()); 313 break; 314 } 315 case GUI::Event::WM_AppletAreaSizeChanged: { 316 auto& changed_event = static_cast<GUI::WMAppletAreaSizeChangedEvent&>(event); 317 m_applet_area_size = changed_event.size(); 318 m_applet_area_container->set_fixed_size(changed_event.size().width() + 8, 21); 319 update_applet_area(); 320 break; 321 } 322 case GUI::Event::WM_SuperKeyPressed: { 323 if (!m_system_menu) 324 break; 325 326 if (m_system_menu->is_visible()) { 327 m_system_menu->dismiss(); 328 } else { 329 m_system_menu->popup(m_start_button->screen_relative_rect().top_left()); 330 } 331 break; 332 } 333 case GUI::Event::WM_SuperSpaceKeyPressed: { 334 if (!m_assistant_app_file->spawn()) 335 warnln("failed to spawn 'Assistant' when requested via Super+Space"); 336 break; 337 } 338 case GUI::Event::WM_SuperDKeyPressed: { 339 toggle_show_desktop(); 340 break; 341 } 342 case GUI::Event::WM_SuperDigitKeyPressed: { 343 auto& digit_event = static_cast<GUI::WMSuperDigitKeyPressedEvent&>(event); 344 auto index = digit_event.digit() != 0 ? digit_event.digit() - 1 : 9; 345 346 for (auto& widget : m_task_button_container->child_widgets()) { 347 // NOTE: The button might be invisible depending on the current workspace 348 if (!widget.is_visible()) 349 continue; 350 351 if (index == 0) { 352 static_cast<TaskbarButton&>(widget).click(); 353 break; 354 } 355 356 --index; 357 } 358 break; 359 } 360 case GUI::Event::WM_WorkspaceChanged: { 361 auto& changed_event = static_cast<GUI::WMWorkspaceChangedEvent&>(event); 362 workspace_change_event(changed_event.current_row(), changed_event.current_column()); 363 break; 364 } 365 default: 366 break; 367 } 368} 369 370void TaskbarWindow::screen_rects_change_event(GUI::ScreenRectsChangeEvent& event) 371{ 372 on_screen_rects_change(event.rects(), event.main_screen_index()); 373} 374 375bool TaskbarWindow::is_window_on_current_workspace(::Window& window) const 376{ 377 return window.workspace_row() == m_current_workspace_row && window.workspace_column() == m_current_workspace_column; 378} 379 380void TaskbarWindow::workspace_change_event(unsigned current_row, unsigned current_column) 381{ 382 m_current_workspace_row = current_row; 383 m_current_workspace_column = current_column; 384 385 WindowList::the().for_each_window([&](auto& window) { 386 if (auto* button = window.button()) 387 button->set_visible(is_window_on_current_workspace(window)); 388 }); 389} 390 391void TaskbarWindow::set_start_button_font(Gfx::Font const& font) 392{ 393 m_start_button->set_font(font); 394 m_start_button->set_fixed_size(font.width(m_start_button->text()) + 30, 21); 395}