123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- /*
- * 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 "TaskbarButton.h"
- #include <AK/Debug.h>
- #include <LibCore/ConfigFile.h>
- #include <LibCore/StandardPaths.h>
- #include <LibGUI/BoxLayout.h>
- #include <LibGUI/Button.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 <LibGUI/WindowManagerServerConnection.h>
- #include <LibGUI/WindowServerConnection.h>
- #include <LibGfx/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 { }
- private:
- TaskbarWidget() { }
- 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();
- });
- }
- };
- TaskbarWindow::TaskbarWindow(NonnullRefPtr<GUI::Menu> start_menu)
- : m_start_menu(move(start_menu))
- {
- set_window_type(GUI::WindowType::Taskbar);
- set_title("Taskbar");
- on_screen_rects_change(GUI::Desktop::the().rects(), GUI::Desktop::the().main_screen_index());
- auto& main_widget = set_main_widget<TaskbarWidget>();
- main_widget.set_layout<GUI::HorizontalBoxLayout>();
- main_widget.layout()->set_margins({ 3, 1, 1, 3 });
- m_start_button = GUI::Button::construct("Serenity");
- 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");
- m_start_button->set_icon(app_icon.bitmap_for_size(16));
- m_start_button->set_menu(m_start_menu);
- main_widget.add_child(*m_start_button);
- create_quick_launch_bar();
- m_task_button_container = main_widget.add<GUI::Widget>();
- m_task_button_container->set_layout<GUI::HorizontalBoxLayout>();
- m_task_button_container->layout()->set_spacing(3);
- m_default_icon = Gfx::Bitmap::try_load_from_file("/res/icons/16x16/window.png");
- m_applet_area_container = main_widget.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);
- main_widget.add<Taskbar::ClockWidget>();
- m_show_desktop_button = GUI::Button::construct();
- m_show_desktop_button->set_tooltip("Show Desktop");
- m_show_desktop_button->set_icon(GUI::Icon::default_icon("desktop").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;
- main_widget.add_child(*m_show_desktop_button);
- auto af_path = String::formatted("{}/{}", Desktop::AppFile::APP_FILES_DIRECTORY, "Assistant.af");
- m_assistant_app_file = Desktop::AppFile::open(af_path);
- }
- TaskbarWindow::~TaskbarWindow()
- {
- }
- void TaskbarWindow::show_desktop_button_clicked(unsigned)
- {
- GUI::WindowManagerServerConnection::the().async_toggle_show_desktop();
- }
- void TaskbarWindow::create_quick_launch_bar()
- {
- auto& quick_launch_bar = main_widget()->add<GUI::Frame>();
- quick_launch_bar.set_shrink_to_fit(true);
- quick_launch_bar.set_layout<GUI::HorizontalBoxLayout>();
- quick_launch_bar.layout()->set_spacing(0);
- quick_launch_bar.set_frame_thickness(0);
- auto config = Core::ConfigFile::open_for_app("Taskbar");
- constexpr const char* quick_launch = "QuickLaunch";
- // FIXME: Core::ConfigFile does not keep the order of the entries.
- for (auto& name : config->keys(quick_launch)) {
- auto af_name = config->read_entry(quick_launch, name);
- auto af_path = String::formatted("{}/{}", Desktop::AppFile::APP_FILES_DIRECTORY, af_name);
- auto af = Desktop::AppFile::open(af_path);
- if (!af->is_valid())
- continue;
- auto app_executable = af->executable();
- auto app_run_in_terminal = af->run_in_terminal();
- const int button_size = 24;
- auto& button = quick_launch_bar.add<GUI::Button>();
- button.set_fixed_size(button_size, button_size);
- button.set_button_style(Gfx::ButtonStyle::Coolbar);
- button.set_icon(af->icon().bitmap_for_size(16));
- button.set_tooltip(af->name());
- button.on_click = [app_executable, app_run_in_terminal](auto) {
- pid_t pid = fork();
- if (pid < 0) {
- perror("fork");
- } else if (pid == 0) {
- if (chdir(Core::StandardPaths::home_directory().characters()) < 0) {
- perror("chdir");
- exit(1);
- }
- if (app_run_in_terminal)
- execl("/bin/Terminal", "Terminal", "-e", app_executable.characters(), nullptr);
- else
- execl(app_executable.characters(), app_executable.characters(), nullptr);
- perror("execl");
- VERIFY_NOT_REACHED();
- } else {
- if (disown(pid) < 0)
- perror("disown");
- }
- };
- }
- quick_launch_bar.set_fixed_height(24);
- }
- void TaskbarWindow::on_screen_rects_change(const Vector<Gfx::IntRect, 4>& rects, size_t main_screen_index)
- {
- const auto& 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();
- Gfx::IntRect new_rect { {}, m_applet_area_size };
- new_rect.center_within(m_applet_area_container->screen_relative_rect());
- GUI::WindowManagerServerConnection::the().async_set_applet_area_position(new_rect.location());
- }
- NonnullRefPtr<GUI::Button> TaskbarWindow::create_button(const WindowIdentifier& 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, const WindowIdentifier& identifier)
- {
- if (window.button())
- return;
- window.set_button(create_button(identifier));
- auto* button = window.button();
- button->on_click = [window = &window, identifier, button](auto) {
- // We need to look at the button's checked state here to figure
- // out if the application is active or not. That's because this
- // button's window may not actually be active when a modal window
- // is displayed, in which case window->is_active() would return
- // false because window is the modal window's owner (which is not
- // active)
- if (window->is_minimized() || !button->is_checked()) {
- GUI::WindowManagerServerConnection::the().async_set_active_window(identifier.client_id(), identifier.window_id());
- } else {
- GUI::WindowManagerServerConnection::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(window.title());
- button->set_tooltip(window.title());
- button->set_checked(show_as_active);
- button->set_visible(is_window_on_current_virtual_desktop(window));
- }
- ::Window* TaskbarWindow::find_window_owner(::Window& window) const
- {
- if (!window.is_modal())
- return &window;
- ::Window* parent = nullptr;
- auto* current_window = &window;
- while (current_window) {
- parent = WindowList::the().find_parent(*current_window);
- if (!parent || !parent->is_modal())
- break;
- current_window = parent;
- }
- return parent;
- }
- 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);
- const int 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::WindowServerConnection::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() };
- 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 = icon->scaled(sw, sh);
- window->button()->set_icon(move(scaled_bitmap));
- } 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_minimized={}",
- changed_event.client_id(),
- changed_event.window_id(),
- changed_event.title(),
- changed_event.rect(),
- changed_event.is_active(),
- 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_parent_identifier({ changed_event.parent_client_id(), changed_event.parent_window_id() });
- if (!window.is_modal())
- add_window_button(window, identifier);
- else
- remove_window_button(window, false);
- window.set_title(changed_event.title());
- window.set_rect(changed_event.rect());
- window.set_modal(changed_event.is_modal());
- window.set_active(changed_event.is_active());
- window.set_minimized(changed_event.is_minimized());
- window.set_progress(changed_event.progress());
- window.set_virtual_desktop(changed_event.virtual_desktop_row(), changed_event.virtual_desktop_column());
- auto* window_owner = find_window_owner(window);
- if (window_owner == &window) {
- update_window_button(window, window.is_active());
- } else if (window_owner) {
- // check the window owner's button if the modal's window button
- // would have been checked
- VERIFY(window.is_modal());
- update_window_button(*window_owner, 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_start_menu->is_visible()) {
- m_start_menu->dismiss();
- } else {
- m_start_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_VirtualDesktopChanged: {
- auto& changed_event = static_cast<GUI::WMVirtualDesktopChangedEvent&>(event);
- virtual_desktop_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_virtual_desktop(::Window& window) const
- {
- return window.virtual_desktop_row() == m_current_virtual_desktop_row && window.virtual_desktop_column() == m_current_virtual_desktop_column;
- }
- void TaskbarWindow::virtual_desktop_change_event(unsigned current_row, unsigned current_column)
- {
- m_current_virtual_desktop_row = current_row;
- m_current_virtual_desktop_column = current_column;
- WindowList::the().for_each_window([&](auto& window) {
- if (auto* button = window.button())
- button->set_visible(is_window_on_current_virtual_desktop(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);
- }
|