summaryrefslogtreecommitdiff
path: root/Userland/Services/Taskbar
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Services/Taskbar')
-rw-r--r--Userland/Services/Taskbar/CMakeLists.txt9
-rw-r--r--Userland/Services/Taskbar/TaskbarButton.cpp162
-rw-r--r--Userland/Services/Taskbar/TaskbarButton.h48
-rw-r--r--Userland/Services/Taskbar/TaskbarWindow.cpp314
-rw-r--r--Userland/Services/Taskbar/TaskbarWindow.h53
-rw-r--r--Userland/Services/Taskbar/WindowIdentifier.h63
-rw-r--r--Userland/Services/Taskbar/WindowList.cpp71
-rw-r--r--Userland/Services/Taskbar/WindowList.h115
-rw-r--r--Userland/Services/Taskbar/main.cpp57
9 files changed, 892 insertions, 0 deletions
diff --git a/Userland/Services/Taskbar/CMakeLists.txt b/Userland/Services/Taskbar/CMakeLists.txt
new file mode 100644
index 0000000000..83de9ee9c8
--- /dev/null
+++ b/Userland/Services/Taskbar/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(SOURCES
+ main.cpp
+ TaskbarButton.cpp
+ TaskbarWindow.cpp
+ WindowList.cpp
+)
+
+serenity_bin(Taskbar)
+target_link_libraries(Taskbar LibGUI LibDesktop)
diff --git a/Userland/Services/Taskbar/TaskbarButton.cpp b/Userland/Services/Taskbar/TaskbarButton.cpp
new file mode 100644
index 0000000000..69103ca5e0
--- /dev/null
+++ b/Userland/Services/Taskbar/TaskbarButton.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "TaskbarButton.h"
+#include "WindowList.h"
+#include <LibGUI/Action.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/WindowServerConnection.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/Palette.h>
+#include <LibGfx/StylePainter.h>
+
+TaskbarButton::TaskbarButton(const WindowIdentifier& identifier)
+ : m_identifier(identifier)
+{
+}
+
+TaskbarButton::~TaskbarButton()
+{
+}
+
+void TaskbarButton::context_menu_event(GUI::ContextMenuEvent&)
+{
+ GUI::WindowServerConnection::the().post_message(Messages::WindowServer::WM_PopupWindowMenu(m_identifier.client_id(), m_identifier.window_id(), screen_relative_rect().location()));
+}
+
+void TaskbarButton::update_taskbar_rect()
+{
+ GUI::WindowServerConnection::the().post_message(
+ Messages::WindowServer::WM_SetWindowTaskbarRect(
+ m_identifier.client_id(),
+ m_identifier.window_id(),
+ screen_relative_rect()));
+}
+
+void TaskbarButton::clear_taskbar_rect()
+{
+ GUI::WindowServerConnection::the().post_message(
+ Messages::WindowServer::WM_SetWindowTaskbarRect(
+ m_identifier.client_id(),
+ m_identifier.window_id(),
+ {}));
+}
+
+void TaskbarButton::resize_event(GUI::ResizeEvent& event)
+{
+ update_taskbar_rect();
+ return GUI::Button::resize_event(event);
+}
+
+static void paint_custom_progress_bar(GUI::Painter& painter, const Gfx::IntRect& rect, const Gfx::IntRect& text_rect, const Palette& palette, int min, int max, int value, const StringView& text, const Gfx::Font& font, Gfx::TextAlignment text_alignment)
+{
+ float range_size = max - min;
+ float progress = (value - min) / range_size;
+ float progress_width = progress * rect.width();
+
+ Gfx::IntRect progress_rect { rect.x(), rect.y(), (int)progress_width, rect.height() };
+
+ {
+ Gfx::PainterStateSaver saver(painter);
+ painter.add_clip_rect(progress_rect);
+
+ Color start_color = palette.active_window_border1();
+ Color end_color = palette.active_window_border2();
+ painter.fill_rect_with_gradient(rect, start_color, end_color);
+
+ if (!text.is_null()) {
+ painter.draw_text(text_rect.translated(1, 1), text, font, text_alignment, palette.base_text(), Gfx::TextElision::Right);
+ painter.draw_text(text_rect, text, font, text_alignment, palette.base_text().inverted(), Gfx::TextElision::Right);
+ }
+ }
+
+ Gfx::IntRect hole_rect { (int)progress_width, 0, (int)(rect.width() - progress_width), rect.height() };
+ hole_rect.move_by(rect.location());
+ hole_rect.set_right_without_resize(rect.right());
+ Gfx::PainterStateSaver saver(painter);
+ painter.add_clip_rect(hole_rect);
+ if (!text.is_null())
+ painter.draw_text(text_rect, text, font, text_alignment, palette.base_text(), Gfx::TextElision::Right);
+}
+
+void TaskbarButton::paint_event(GUI::PaintEvent& event)
+{
+ ASSERT(icon());
+ auto& icon = *this->icon();
+ auto& font = is_checked() ? Gfx::FontDatabase::default_bold_font() : this->font();
+ auto& window = WindowList::the().ensure_window(m_identifier);
+
+ GUI::Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+
+ Gfx::StylePainter::paint_button(painter, rect(), palette(), button_style(), is_being_pressed(), is_hovered(), is_checked(), is_enabled());
+
+ if (text().is_empty())
+ return;
+
+ bool has_progress = window.progress() >= 0 && window.progress() <= 100;
+
+ auto content_rect = rect().shrunken(8, 2);
+ auto icon_location = content_rect.center().translated(-(icon.width() / 2), -(icon.height() / 2));
+ if (!text().is_empty())
+ icon_location.set_x(content_rect.x());
+
+ if (!text().is_empty()) {
+ content_rect.move_by(icon.width() + 4, 0);
+ content_rect.set_width(content_rect.width() - icon.width() - 4);
+ }
+
+ Gfx::IntRect text_rect { 0, 0, font.width(text()), font.glyph_height() };
+ if (text_rect.width() > content_rect.width())
+ text_rect.set_width(content_rect.width());
+ text_rect.align_within(content_rect, text_alignment());
+
+ if (is_being_pressed() || is_checked()) {
+ text_rect.move_by(1, 1);
+ icon_location.move_by(1, 1);
+ }
+
+ if (has_progress) {
+ auto adjusted_rect = rect().shrunken(4, 4);
+ if (is_being_pressed() || is_checked()) {
+ adjusted_rect.set_height(adjusted_rect.height() + 1);
+ }
+ paint_custom_progress_bar(painter, adjusted_rect, text_rect, palette(), 0, 100, window.progress(), text(), font, text_alignment());
+ }
+
+ if (is_enabled()) {
+ if (is_hovered())
+ painter.blit_brightened(icon_location, icon, icon.rect());
+ else
+ painter.blit(icon_location, icon, icon.rect());
+ } else {
+ painter.blit_disabled(icon_location, icon, icon.rect(), palette());
+ }
+
+ if (!has_progress)
+ paint_text(painter, text_rect, font, text_alignment());
+}
diff --git a/Userland/Services/Taskbar/TaskbarButton.h b/Userland/Services/Taskbar/TaskbarButton.h
new file mode 100644
index 0000000000..b990c04d03
--- /dev/null
+++ b/Userland/Services/Taskbar/TaskbarButton.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "WindowIdentifier.h"
+#include <LibGUI/Button.h>
+
+class TaskbarButton final : public GUI::Button {
+ C_OBJECT(TaskbarButton)
+public:
+ virtual ~TaskbarButton() override;
+
+ void update_taskbar_rect();
+ void clear_taskbar_rect();
+
+private:
+ explicit TaskbarButton(const WindowIdentifier&);
+
+ virtual void context_menu_event(GUI::ContextMenuEvent&) override;
+ virtual void resize_event(GUI::ResizeEvent&) override;
+ virtual void paint_event(GUI::PaintEvent&) override;
+
+ WindowIdentifier m_identifier;
+};
diff --git a/Userland/Services/Taskbar/TaskbarWindow.cpp b/Userland/Services/Taskbar/TaskbarWindow.cpp
new file mode 100644
index 0000000000..fbc7946634
--- /dev/null
+++ b/Userland/Services/Taskbar/TaskbarWindow.cpp
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "TaskbarWindow.h"
+#include "TaskbarButton.h"
+#include <AK/SharedBuffer.h>
+#include <LibCore/ConfigFile.h>
+#include <LibCore/StandardPaths.h>
+#include <LibDesktop/AppFile.h>
+#include <LibGUI/BoxLayout.h>
+#include <LibGUI/Button.h>
+#include <LibGUI/Desktop.h>
+#include <LibGUI/Frame.h>
+#include <LibGUI/Icon.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/Window.h>
+#include <LibGUI/WindowServerConnection.h>
+#include <LibGfx/Palette.h>
+#include <serenity.h>
+#include <stdio.h>
+
+//#define EVENT_DEBUG
+
+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()
+{
+ set_window_type(GUI::WindowType::Taskbar);
+ set_title("Taskbar");
+
+ on_screen_rect_change(GUI::Desktop::the().rect());
+
+ GUI::Desktop::the().on_rect_change = [this](const Gfx::IntRect& rect) { on_screen_rect_change(rect); };
+
+ auto& widget = set_main_widget<TaskbarWidget>();
+ widget.set_layout<GUI::HorizontalBoxLayout>();
+ widget.layout()->set_margins({ 3, 2, 3, 2 });
+ widget.layout()->set_spacing(3);
+
+ m_default_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window.png");
+
+ create_quick_launch_bar();
+}
+
+TaskbarWindow::~TaskbarWindow()
+{
+}
+
+void TaskbarWindow::create_quick_launch_bar()
+{
+ auto& quick_launch_bar = main_widget()->add<GUI::Frame>();
+ quick_launch_bar.set_layout<GUI::HorizontalBoxLayout>();
+ quick_launch_bar.layout()->set_spacing(0);
+ quick_launch_bar.layout()->set_margins({ 3, 0, 3, 0 });
+ quick_launch_bar.set_frame_thickness(0);
+
+ int total_width = 6;
+ bool first = true;
+
+ auto config = Core::ConfigFile::get_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();
+ 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](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);
+ }
+ execl(app_executable.characters(), app_executable.characters(), nullptr);
+ perror("execl");
+ ASSERT_NOT_REACHED();
+ } else {
+ if (disown(pid) < 0)
+ perror("disown");
+ }
+ };
+
+ if (!first)
+ total_width += quick_launch_bar.layout()->spacing();
+ first = false;
+ total_width += button_size;
+ }
+
+ quick_launch_bar.set_fixed_size(total_width, 24);
+}
+
+void TaskbarWindow::on_screen_rect_change(const Gfx::IntRect& rect)
+{
+ Gfx::IntRect new_rect { rect.x(), rect.bottom() - taskbar_height() + 1, rect.width(), taskbar_height() };
+ set_rect(new_rect);
+}
+
+NonnullRefPtr<GUI::Button> TaskbarWindow::create_button(const WindowIdentifier& identifier)
+{
+ auto& button = main_widget()->add<TaskbarButton>(identifier);
+ button.set_min_size(20, 23);
+ button.set_max_size(140, 23);
+ button.set_text_alignment(Gfx::TextAlignment::CenterLeft);
+ button.set_icon(*m_default_icon);
+ return button;
+}
+
+static bool should_include_window(GUI::WindowType window_type, bool is_frameless)
+{
+ return window_type == GUI::WindowType::Normal && !is_frameless;
+}
+
+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::WindowServerConnection::the().post_message(Messages::WindowServer::WM_SetActiveWindow(identifier.client_id(), identifier.window_id()));
+ } else {
+ GUI::WindowServerConnection::the().post_message(Messages::WindowServer::WM_SetWindowMinimized(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_checked(show_as_active);
+}
+
+::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::wm_event(GUI::WMEvent& event)
+{
+ WindowIdentifier identifier { event.client_id(), event.window_id() };
+ switch (event.type()) {
+ case GUI::Event::WM_WindowRemoved: {
+#ifdef 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());
+#endif
+ if (auto* window = WindowList::the().window(identifier))
+ remove_window_button(*window, true);
+ WindowList::the().remove_window(identifier);
+ update();
+ break;
+ }
+ case GUI::Event::WM_WindowRectChanged: {
+#ifdef 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());
+#endif
+ break;
+ }
+
+ case GUI::Event::WM_WindowIconBitmapChanged: {
+ auto& changed_event = static_cast<GUI::WMWindowIconBitmapChangedEvent&>(event);
+#ifdef EVENT_DEBUG
+ dbgln("WM_WindowIconBitmapChanged: client_id={}, window_id={}, icon_buffer_id={}",
+ changed_event.client_id(),
+ changed_event.window_id(),
+ changed_event.icon_buffer_id());
+#endif
+ if (auto* window = WindowList::the().window(identifier)) {
+ auto buffer = SharedBuffer::create_from_shbuf_id(changed_event.icon_buffer_id());
+ ASSERT(buffer);
+ if (window->button())
+ window->button()->set_icon(Gfx::Bitmap::create_with_shared_buffer(Gfx::BitmapFormat::RGBA32, *buffer, changed_event.icon_size()));
+ }
+ break;
+ }
+
+ case GUI::Event::WM_WindowStateChanged: {
+ auto& changed_event = static_cast<GUI::WMWindowStateChangedEvent&>(event);
+#ifdef 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());
+#endif
+ if (!should_include_window(changed_event.window_type(), changed_event.is_frameless()))
+ 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());
+
+ 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
+ ASSERT(window.is_modal());
+ update_window_button(*window_owner, window.is_active());
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
diff --git a/Userland/Services/Taskbar/TaskbarWindow.h b/Userland/Services/Taskbar/TaskbarWindow.h
new file mode 100644
index 0000000000..be55a00daf
--- /dev/null
+++ b/Userland/Services/Taskbar/TaskbarWindow.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "WindowList.h"
+#include <LibGUI/Widget.h>
+#include <LibGUI/Window.h>
+
+class TaskbarWindow final : public GUI::Window {
+ C_OBJECT(TaskbarWindow)
+public:
+ TaskbarWindow();
+ virtual ~TaskbarWindow() override;
+
+ int taskbar_height() const { return 28; }
+
+private:
+ void create_quick_launch_bar();
+ void on_screen_rect_change(const Gfx::IntRect&);
+ NonnullRefPtr<GUI::Button> create_button(const WindowIdentifier&);
+ void add_window_button(::Window&, const WindowIdentifier&);
+ void remove_window_button(::Window&, bool);
+ void update_window_button(::Window&, bool);
+ ::Window* find_window_owner(::Window&) const;
+
+ virtual void wm_event(GUI::WMEvent&) override;
+
+ RefPtr<Gfx::Bitmap> m_default_icon;
+};
diff --git a/Userland/Services/Taskbar/WindowIdentifier.h b/Userland/Services/Taskbar/WindowIdentifier.h
new file mode 100644
index 0000000000..c6e996992c
--- /dev/null
+++ b/Userland/Services/Taskbar/WindowIdentifier.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Traits.h>
+
+class WindowIdentifier {
+public:
+ WindowIdentifier() = default;
+ WindowIdentifier(int client_id, int window_id)
+ : m_client_id(client_id)
+ , m_window_id(window_id)
+ {
+ }
+
+ int client_id() const { return m_client_id; }
+ int window_id() const { return m_window_id; }
+
+ bool operator==(const WindowIdentifier& other) const
+ {
+ return m_client_id == other.m_client_id && m_window_id == other.m_window_id;
+ }
+
+ bool is_valid() const
+ {
+ return m_client_id != -1 && m_window_id != -1;
+ }
+
+private:
+ int m_client_id { -1 };
+ int m_window_id { -1 };
+};
+
+namespace AK {
+template<>
+struct Traits<WindowIdentifier> : public GenericTraits<WindowIdentifier> {
+ static unsigned hash(const WindowIdentifier& w) { return pair_int_hash(w.client_id(), w.window_id()); }
+};
+}
diff --git a/Userland/Services/Taskbar/WindowList.cpp b/Userland/Services/Taskbar/WindowList.cpp
new file mode 100644
index 0000000000..84021be160
--- /dev/null
+++ b/Userland/Services/Taskbar/WindowList.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "WindowList.h"
+
+WindowList& WindowList::the()
+{
+ static WindowList* s_the;
+ if (!s_the)
+ s_the = new WindowList;
+ return *s_the;
+}
+
+Window* WindowList::find_parent(const Window& window)
+{
+ if (!window.parent_identifier().is_valid())
+ return nullptr;
+ for (auto& it : m_windows) {
+ auto& w = *it.value;
+ if (w.identifier() == window.parent_identifier())
+ return &w;
+ }
+ return nullptr;
+}
+
+Window* WindowList::window(const WindowIdentifier& identifier)
+{
+ auto it = m_windows.find(identifier);
+ if (it != m_windows.end())
+ return it->value;
+ return nullptr;
+}
+
+Window& WindowList::ensure_window(const WindowIdentifier& identifier)
+{
+ auto it = m_windows.find(identifier);
+ if (it != m_windows.end())
+ return *it->value;
+ auto window = make<Window>(identifier);
+ auto& window_ref = *window;
+ m_windows.set(identifier, move(window));
+ return window_ref;
+}
+
+void WindowList::remove_window(const WindowIdentifier& identifier)
+{
+ m_windows.remove(identifier);
+}
diff --git a/Userland/Services/Taskbar/WindowList.h b/Userland/Services/Taskbar/WindowList.h
new file mode 100644
index 0000000000..b86b509926
--- /dev/null
+++ b/Userland/Services/Taskbar/WindowList.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "WindowIdentifier.h"
+#include <AK/HashMap.h>
+#include <AK/String.h>
+#include <LibGUI/Button.h>
+#include <LibGfx/Rect.h>
+
+class Window {
+public:
+ explicit Window(const WindowIdentifier& identifier)
+ : m_identifier(identifier)
+ {
+ }
+
+ ~Window()
+ {
+ if (m_button)
+ m_button->remove_from_parent();
+ }
+
+ const WindowIdentifier& identifier() const { return m_identifier; }
+
+ void set_parent_identifier(const WindowIdentifier& parent_identifier) { m_parent_identifier = parent_identifier; }
+ const WindowIdentifier& parent_identifier() const { return m_parent_identifier; }
+
+ String title() const { return m_title; }
+ void set_title(const String& title) { m_title = title; }
+
+ Gfx::IntRect rect() const { return m_rect; }
+ void set_rect(const Gfx::IntRect& rect) { m_rect = rect; }
+
+ GUI::Button* button() { return m_button; }
+ void set_button(GUI::Button* button) { m_button = button; }
+
+ void set_active(bool active) { m_active = active; }
+ bool is_active() const { return m_active; }
+
+ void set_minimized(bool minimized) { m_minimized = minimized; }
+ bool is_minimized() const { return m_minimized; }
+
+ void set_modal(bool modal) { m_modal = modal; }
+ bool is_modal() const { return m_modal; }
+
+ void set_progress(int progress)
+ {
+ if (m_progress == progress)
+ return;
+ m_progress = progress;
+ if (m_button)
+ m_button->update();
+ }
+
+ int progress() const { return m_progress; }
+
+ const Gfx::Bitmap* icon() const { return m_icon.ptr(); }
+
+private:
+ WindowIdentifier m_identifier;
+ WindowIdentifier m_parent_identifier;
+ String m_title;
+ Gfx::IntRect m_rect;
+ RefPtr<GUI::Button> m_button;
+ RefPtr<Gfx::Bitmap> m_icon;
+ bool m_active { false };
+ bool m_minimized { false };
+ bool m_modal { false };
+ int m_progress { -1 };
+};
+
+class WindowList {
+public:
+ static WindowList& the();
+
+ template<typename Callback>
+ void for_each_window(Callback callback)
+ {
+ for (auto& it : m_windows)
+ callback(*it.value);
+ }
+
+ Window* find_parent(const Window&);
+ Window* window(const WindowIdentifier&);
+ Window& ensure_window(const WindowIdentifier&);
+ void remove_window(const WindowIdentifier&);
+
+private:
+ HashMap<WindowIdentifier, NonnullOwnPtr<Window>> m_windows;
+};
diff --git a/Userland/Services/Taskbar/main.cpp b/Userland/Services/Taskbar/main.cpp
new file mode 100644
index 0000000000..3917285497
--- /dev/null
+++ b/Userland/Services/Taskbar/main.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "TaskbarWindow.h"
+#include <LibCore/EventLoop.h>
+#include <LibGUI/Application.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/wait.h>
+
+int main(int argc, char** argv)
+{
+ if (pledge("stdio shared_buffer accept proc exec rpath unix cpath fattr sigaction", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ auto app = GUI::Application::construct(argc, argv);
+ app->event_loop().register_signal(SIGCHLD, [](int) {
+ // Wait all available children
+ while (waitpid(-1, nullptr, WNOHANG) > 0)
+ ;
+ });
+
+ if (pledge("stdio shared_buffer accept proc exec rpath", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ TaskbarWindow window;
+ window.show();
+
+ return app->exec();
+}