1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 10:08:12 +00:00
serenity/Userland/Services/Taskbar/TaskbarWindow.cpp
david072 70f7c10ab9 Taskbar+WindowServer: Adding to Quick Launch via windows :^)
You can now add applications to Quick Launch via the context
menu option of their windows. Clicking it creates an event with the
stored PID of the process that created the window. The Taskbar receives
the event and tells the QuickLaunchWidget to add the PID, which then
gets the executable using /sys/kernel/processes. It also looks for an
AppFile using the name from the process object and if there is one, it
uses that, since it should contain a better formatted name.
2023-11-09 23:35:52 +01:00

407 lines
15 KiB
C++

/*
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Spencer Dixon <spencercdixon@gmail.com>
* Copyright (c) 2023, David Ganz <david.g.ganz@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/MessageBox.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 = set_main_widget<TaskbarWidget>();
main_widget->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 = main_widget->add<GUI::Widget>();
m_task_button_container->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 = main_widget->add<GUI::Frame>();
m_applet_area_container->set_frame_style(Gfx::FrameStyle::SunkenPanel);
m_clock_widget = main_widget->add<Taskbar::ClockWidget>();
m_show_desktop_button = main_widget->add<GUI::Button>();
m_show_desktop_button->set_tooltip("Show Desktop"_string);
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);
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(StringView domain, StringView group, StringView key, StringView 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(), 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(String::from_deprecated_string(window.title()).release_value_but_fixme_should_propagate_errors());
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;
}
case GUI::Event::WM_AddToQuickLaunch: {
auto add_event = static_cast<GUI::WMAddToQuickLaunchEvent&>(event);
auto result = m_quick_launch->add_from_pid(add_event.pid());
if (result.is_error()) {
dbgln("Couldn't add pid {} to quick launch menu: {}", add_event.pid(), result.error());
GUI::MessageBox::show_error(this, String::formatted("Failed to add to Quick Launch: {}", result.release_error()).release_value_but_fixme_should_propagate_errors());
} else if (!result.release_value()) {
dbgln("Couldn't add pid {} to quick launch menu due to an unexpected error", add_event.pid());
GUI::MessageBox::show_error(this, "Failed to add to Quick Launch due to an unexpected error."sv);
}
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);
}