diff options
author | Andreas Kling <kling@serenityos.org> | 2020-05-08 22:00:41 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-05-08 22:00:41 +0200 |
commit | 239fd334059823619e0af199e9b5618a320e15f9 (patch) | |
tree | 194b7e99e39295d91a21efa2c31d98743ebdad8b /Services | |
parent | cf3b58fbe8f836c13e44d6152d78960aff6089ef (diff) | |
download | serenity-239fd334059823619e0af199e9b5618a320e15f9.zip |
Services: Move Taskbar and SystemMenu from Applications to Services
Diffstat (limited to 'Services')
-rw-r--r-- | Services/SystemMenu/Makefile | 9 | ||||
-rw-r--r-- | Services/SystemMenu/PowerDialog.cpp | 119 | ||||
-rw-r--r-- | Services/SystemMenu/PowerDialog.h | 41 | ||||
-rw-r--r-- | Services/SystemMenu/main.cpp | 218 | ||||
-rw-r--r-- | Services/Taskbar/Makefile | 11 | ||||
-rw-r--r-- | Services/Taskbar/TaskbarButton.cpp | 53 | ||||
-rw-r--r-- | Services/Taskbar/TaskbarButton.h | 44 | ||||
-rw-r--r-- | Services/Taskbar/TaskbarWindow.cpp | 221 | ||||
-rw-r--r-- | Services/Taskbar/TaskbarWindow.h | 47 | ||||
-rw-r--r-- | Services/Taskbar/WindowIdentifier.h | 57 | ||||
-rw-r--r-- | Services/Taskbar/WindowList.cpp | 68 | ||||
-rw-r--r-- | Services/Taskbar/WindowList.h | 96 | ||||
-rw-r--r-- | Services/Taskbar/main.cpp | 56 |
13 files changed, 1040 insertions, 0 deletions
diff --git a/Services/SystemMenu/Makefile b/Services/SystemMenu/Makefile new file mode 100644 index 0000000000..58a9369e16 --- /dev/null +++ b/Services/SystemMenu/Makefile @@ -0,0 +1,9 @@ +OBJS = \ + main.o \ + PowerDialog.o + +PROGRAM = SystemMenu + +LIB_DEPS = GUI Gfx IPC Core + +include ../../Makefile.common diff --git a/Services/SystemMenu/PowerDialog.cpp b/Services/SystemMenu/PowerDialog.cpp new file mode 100644 index 0000000000..f1416a73fc --- /dev/null +++ b/Services/SystemMenu/PowerDialog.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * 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 <AK/String.h> +#include <AK/Vector.h> +#include <LibGUI/BoxLayout.h> +#include <LibGUI/Button.h> +#include <LibGUI/Desktop.h> +#include <LibGUI/Label.h> +#include <LibGUI/RadioButton.h> +#include <LibGUI/Widget.h> +#include <LibGfx/Font.h> + +#include "PowerDialog.h" + +struct PowerOption { + String title; + Vector<char const*> cmd; + bool enabled; + bool default_action; +}; + +static const Vector<PowerOption> options = { + { "Shut down", { "/bin/shutdown", "--now", nullptr }, true, true }, + { "Restart", { "/bin/reboot", nullptr }, true, false }, + { "Log out", {}, false, false }, + { "Sleep", {}, false, false }, +}; + +Vector<char const*> PowerDialog::show() +{ + auto rc = PowerDialog::construct()->exec(); + if(rc == ExecResult::ExecOK) + return options[rc].cmd; + + return {}; +} + +PowerDialog::PowerDialog() + : GUI::Dialog(nullptr) +{ + Gfx::Rect rect({ 0, 0, 180, 180 + ((static_cast<int>(options.size()) - 3) * 16) }); + rect.center_within(GUI::Desktop::the().rect()); + set_rect(rect); + set_resizable(false); + set_title("SerenityOS"); + set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/power.png")); + + auto& main = set_main_widget<GUI::Widget>(); + main.set_layout<GUI::VerticalBoxLayout>(); + main.layout()->set_margins({ 8, 8, 8, 8 }); + main.layout()->set_spacing(8); + main.set_fill_with_background_color(true); + + auto& header = main.add<GUI::Label>(); + header.set_text("What would you like to do?"); + header.set_preferred_size(0, 16); + header.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed); + header.set_font(Gfx::Font::default_bold_font()); + + for (size_t i = 0; i < options.size(); i++) { + auto action = options[i]; + auto& radio = main.add<GUI::RadioButton>(); + radio.set_enabled(action.enabled); + radio.set_text(action.title); + + radio.on_checked = [this, i](auto) { + m_selected_option = i; + }; + + if (action.default_action) { + radio.set_checked(true); + m_selected_option = i; + } + } + + auto& button_box = main.add<GUI::Widget>(); + button_box.set_layout<GUI::HorizontalBoxLayout>(); + button_box.layout()->set_spacing(8); + + auto& ok_button = button_box.add<GUI::Button>(); + ok_button.on_click = [this] { + done(ExecResult::ExecOK); + }; + ok_button.set_text("OK"); + + auto& cancel_button = button_box.add<GUI::Button>(); + cancel_button.on_click = [this] { + done(ExecResult::ExecCancel); + }; + cancel_button.set_text("Cancel"); +} + +PowerDialog::~PowerDialog() +{ +} diff --git a/Services/SystemMenu/PowerDialog.h b/Services/SystemMenu/PowerDialog.h new file mode 100644 index 0000000000..1a1c18a8d3 --- /dev/null +++ b/Services/SystemMenu/PowerDialog.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * 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 <AK/Vector.h> +#include <LibCore/Object.h> +#include <LibGUI/Dialog.h> + +class PowerDialog : public GUI::Dialog { + C_OBJECT(PowerDialog) +public: + static Vector<char const*> show(); + +private: + PowerDialog(); + ~PowerDialog(); + + int m_selected_option { -1 }; +}; diff --git a/Services/SystemMenu/main.cpp b/Services/SystemMenu/main.cpp new file mode 100644 index 0000000000..483a8b8b6a --- /dev/null +++ b/Services/SystemMenu/main.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (c) 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 "PowerDialog.h" +#include <AK/FileSystemPath.h> +#include <AK/QuickSort.h> +#include <LibCore/ConfigFile.h> +#include <LibCore/DirIterator.h> +#include <LibCore/StandardPaths.h> +#include <LibGUI/Action.h> +#include <LibGUI/ActionGroup.h> +#include <LibGUI/Application.h> +#include <LibGUI/Menu.h> +#include <LibGUI/WindowServerConnection.h> +#include <LibGfx/Bitmap.h> + +//#define SYSTEM_MENU_DEBUG + +struct AppMetadata { + String executable; + String name; + String icon_path; + String category; +}; +Vector<AppMetadata> g_apps; + +HashMap<String, NonnullRefPtr<GUI::Menu>> g_app_category_menus; + +struct ThemeMetadata { + String name; + String path; +}; + +Color g_menu_selection_color; + +Vector<ThemeMetadata> g_themes; +RefPtr<GUI::Menu> g_themes_menu; +GUI::ActionGroup g_themes_group; + +static NonnullRefPtr<GUI::Menu> build_system_menu(); + +int main(int argc, char** argv) +{ + GUI::Application app(argc, argv); + + auto menu = build_system_menu(); + menu->realize_menu_if_needed(); + + GUI::WindowServerConnection::the().send_sync<Messages::WindowServer::SetSystemMenu>(menu->menu_id()); + + if (pledge("stdio shared_buffer accept rpath proc exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (chdir(Core::StandardPaths::home_directory().characters()) < 0) { + perror("chdir"); + return 1; + } + + if (unveil("/bin", "x")) { + perror("unveil"); + return 1; + } + + if (unveil("/res", "r")) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + return app.exec(); +} + +NonnullRefPtr<GUI::Menu> build_system_menu() +{ + HashTable<String> seen_app_categories; + { + Core::DirIterator dt("/res/apps", Core::DirIterator::SkipDots); + while (dt.has_next()) { + auto af_name = dt.next_path(); + auto af_path = String::format("/res/apps/%s", af_name.characters()); + auto af = Core::ConfigFile::open(af_path); + if (!af->has_key("App", "Name") || !af->has_key("App", "Executable")) + continue; + auto app_name = af->read_entry("App", "Name"); + auto app_executable = af->read_entry("App", "Executable"); + auto app_category = af->read_entry("App", "Category"); + auto app_icon_path = af->read_entry("Icons", "16x16"); + g_apps.append({ app_executable, app_name, app_icon_path, app_category }); + seen_app_categories.set(app_category); + } + quick_sort(g_apps, [](auto& a, auto& b) { return a.name < b.name; }); + } + + Vector<String> sorted_app_categories; + for (auto& category : seen_app_categories) + sorted_app_categories.append(category); + quick_sort(sorted_app_categories); + + auto system_menu = GUI::Menu::construct("\xE2\x9A\xA1"); // HIGH VOLTAGE SIGN + + // First we construct all the necessary app category submenus. + for (const auto& category : sorted_app_categories) { + + if (g_app_category_menus.contains(category)) + continue; + auto& category_menu = system_menu->add_submenu(category); + g_app_category_menus.set(category, category_menu); + } + + // Then we create and insert all the app menu items into the right place. + int app_identifier = 0; + for (const auto& app : g_apps) { + RefPtr<Gfx::Bitmap> icon; + if (!app.icon_path.is_empty()) + icon = Gfx::Bitmap::load_from_file(app.icon_path); + +#ifdef SYSTEM_MENU_DEBUG + if (icon) + dbg() << "App " << app.name << " has icon with size " << icon->size(); +#endif + + auto parent_menu = g_app_category_menus.get(app.category).value_or(*system_menu); + parent_menu->add_action(GUI::Action::create(app.name, icon.ptr(), [app_identifier](auto&) { + dbg() << "Activated app with ID " << app_identifier; + if (fork() == 0) { + const auto& bin = g_apps[app_identifier].executable; + if (execl(bin.characters(), bin.characters(), nullptr) < 0) + perror("execl"); + ASSERT_NOT_REACHED(); + } + })); + ++app_identifier; + } + + system_menu->add_separator(); + + g_themes_group.set_exclusive(true); + g_themes_group.set_unchecking_allowed(false); + + g_themes_menu = &system_menu->add_submenu("Themes"); + + { + Core::DirIterator dt("/res/themes", Core::DirIterator::SkipDots); + while (dt.has_next()) { + auto theme_name = dt.next_path(); + auto theme_path = String::format("/res/themes/%s", theme_name.characters()); + g_themes.append({ FileSystemPath(theme_name).title(), theme_path }); + } + quick_sort(g_themes, [](auto& a, auto& b) { return a.name < b.name; }); + } + + auto current_theme_name = GUI::WindowServerConnection::the().send_sync<Messages::WindowServer::GetSystemTheme>()->theme_name(); + + { + int theme_identifier = 0; + for (auto& theme : g_themes) { + auto action = GUI::Action::create_checkable(theme.name, [theme_identifier](auto&) { + auto& theme = g_themes[theme_identifier]; + dbg() << "Theme switched to " << theme.name << " at path " << theme.path; + auto response = GUI::WindowServerConnection::the().send_sync<Messages::WindowServer::SetSystemTheme>(theme.path, theme.name); + ASSERT(response->success()); + }); + if (theme.name == current_theme_name) + action->set_checked(true); + g_themes_group.add_action(action); + g_themes_menu->add_action(action); + ++theme_identifier; + } + } + + system_menu->add_separator(); + system_menu->add_action(GUI::Action::create("About...", Gfx::Bitmap::load_from_file("/res/icons/16x16/ladybug.png"), [](auto&) { + if (fork() == 0) { + execl("/bin/About", "/bin/About", nullptr); + ASSERT_NOT_REACHED(); + } + })); + system_menu->add_separator(); + system_menu->add_action(GUI::Action::create("Exit...", [](auto&) { + Vector<char const*> command = PowerDialog::show(); + + if (command.size() == 0) + return; + + if (fork() == 0) { + execv(command[0], const_cast<char* const*>(command.data())); + ASSERT_NOT_REACHED(); + } + })); + + return system_menu; +} diff --git a/Services/Taskbar/Makefile b/Services/Taskbar/Makefile new file mode 100644 index 0000000000..f40e2a3032 --- /dev/null +++ b/Services/Taskbar/Makefile @@ -0,0 +1,11 @@ +OBJS = \ + TaskbarWindow.o \ + TaskbarButton.o \ + WindowList.o \ + main.o + +PROGRAM = Taskbar + +LIB_DEPS = GUI Gfx IPC Core + +include ../../Makefile.common diff --git a/Services/Taskbar/TaskbarButton.cpp b/Services/Taskbar/TaskbarButton.cpp new file mode 100644 index 0000000000..68e0441211 --- /dev/null +++ b/Services/Taskbar/TaskbarButton.cpp @@ -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. + */ + +#include "TaskbarButton.h" +#include <LibGUI/Action.h> +#include <LibGUI/WindowServerConnection.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::resize_event(GUI::ResizeEvent& event) +{ + GUI::WindowServerConnection::the().post_message( + Messages::WindowServer::WM_SetWindowTaskbarRect( + m_identifier.client_id(), + m_identifier.window_id(), + screen_relative_rect())); + return GUI::Button::resize_event(event); +} diff --git a/Services/Taskbar/TaskbarButton.h b/Services/Taskbar/TaskbarButton.h new file mode 100644 index 0000000000..d8852cfcc1 --- /dev/null +++ b/Services/Taskbar/TaskbarButton.h @@ -0,0 +1,44 @@ +/* + * 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; + +private: + explicit TaskbarButton(const WindowIdentifier&); + + virtual void context_menu_event(GUI::ContextMenuEvent&) override; + virtual void resize_event(GUI::ResizeEvent&) override; + + WindowIdentifier m_identifier; +}; diff --git a/Services/Taskbar/TaskbarWindow.cpp b/Services/Taskbar/TaskbarWindow.cpp new file mode 100644 index 0000000000..7e2638ebd0 --- /dev/null +++ b/Services/Taskbar/TaskbarWindow.cpp @@ -0,0 +1,221 @@ +/* + * 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 <LibGUI/BoxLayout.h> +#include <LibGUI/Button.h> +#include <LibGUI/Desktop.h> +#include <LibGUI/Frame.h> +#include <LibGUI/Window.h> +#include <stdio.h> + +//#define EVENT_DEBUG + +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::Rect& rect) { on_screen_rect_change(rect); }; + + auto& widget = set_main_widget<GUI::Frame>(); + widget.set_fill_with_background_color(true); + widget.set_layout<GUI::HorizontalBoxLayout>(); + widget.layout()->set_margins({ 3, 2, 3, 2 }); + widget.layout()->set_spacing(3); + widget.set_frame_thickness(1); + widget.set_frame_shape(Gfx::FrameShape::Panel); + widget.set_frame_shadow(Gfx::FrameShadow::Raised); + + m_default_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window.png"); + + WindowList::the().aid_create_button = [this](auto& identifier) { + return create_button(identifier); + }; + + 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_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed); + quick_launch_bar.set_layout<GUI::HorizontalBoxLayout>(); + quick_launch_bar.layout()->set_spacing(3); + 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); + ASSERT(!af_name.is_null()); + auto af_path = String::format("/res/apps/%s", af_name.characters()); + auto af = Core::ConfigFile::open(af_path); + auto app_executable = af->read_entry("App", "Executable"); + auto app_icon_path = af->read_entry("Icons", "16x16"); + + auto& button = quick_launch_bar.add<GUI::Button>(); + button.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed); + button.set_preferred_size(22, 22); + button.set_button_style(Gfx::ButtonStyle::CoolBar); + + button.set_icon(Gfx::Bitmap::load_from_file(app_icon_path)); + button.set_tooltip(name); + button.on_click = [app_executable] { + 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(); + } + }; + + if (!first) + total_width += 3; + first = false; + total_width += 22; + } + + quick_launch_bar.set_preferred_size(total_width, 22); +} + +void TaskbarWindow::on_screen_rect_change(const Gfx::Rect& rect) +{ + Gfx::Rect 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_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed); + button.set_preferred_size(140, 22); + 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::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); + dbgprintf("WM_WindowRemoved: client_id=%d, window_id=%d\n", + removed_event.client_id(), + removed_event.window_id()); +#endif + WindowList::the().remove_window(identifier); + update(); + break; + } + case GUI::Event::WM_WindowRectChanged: { +#ifdef EVENT_DEBUG + auto& changed_event = static_cast<GUI::WMWindowRectChangedEvent&>(event); + dbgprintf("WM_WindowRectChanged: client_id=%d, window_id=%d, rect=%s\n", + changed_event.client_id(), + changed_event.window_id(), + changed_event.rect().to_string().characters()); +#endif + break; + } + + case GUI::Event::WM_WindowIconBitmapChanged: { + auto& changed_event = static_cast<GUI::WMWindowIconBitmapChangedEvent&>(event); +#ifdef EVENT_DEBUG + dbgprintf("WM_WindowIconBitmapChanged: client_id=%d, window_id=%d, icon_buffer_id=%d\n", + 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); + 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 + dbgprintf("WM_WindowStateChanged: client_id=%d, window_id=%d, title=%s, rect=%s, is_active=%u, is_minimized=%u\n", + changed_event.client_id(), + changed_event.window_id(), + changed_event.title().characters(), + changed_event.rect().to_string().characters(), + 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_title(changed_event.title()); + window.set_rect(changed_event.rect()); + window.set_active(changed_event.is_active()); + window.set_minimized(changed_event.is_minimized()); + if (window.is_minimized()) { + window.button()->set_foreground_color(Color::DarkGray); + window.button()->set_text(String::format("[%s]", changed_event.title().characters())); + } else { + window.button()->set_foreground_color(Color::Black); + window.button()->set_text(changed_event.title()); + } + window.button()->set_checked(changed_event.is_active()); + break; + } + default: + break; + } +} diff --git a/Services/Taskbar/TaskbarWindow.h b/Services/Taskbar/TaskbarWindow.h new file mode 100644 index 0000000000..5825012439 --- /dev/null +++ b/Services/Taskbar/TaskbarWindow.h @@ -0,0 +1,47 @@ +/* + * 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" +#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::Rect&); + NonnullRefPtr<GUI::Button> create_button(const WindowIdentifier&); + + virtual void wm_event(GUI::WMEvent&) override; + + RefPtr<Gfx::Bitmap> m_default_icon; +}; diff --git a/Services/Taskbar/WindowIdentifier.h b/Services/Taskbar/WindowIdentifier.h new file mode 100644 index 0000000000..cdb645449a --- /dev/null +++ b/Services/Taskbar/WindowIdentifier.h @@ -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. + */ + +#pragma once + +#include <AK/Traits.h> + +class WindowIdentifier { +public: + 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; + } + +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/Services/Taskbar/WindowList.cpp b/Services/Taskbar/WindowList.cpp new file mode 100644 index 0000000000..948dcb0452 --- /dev/null +++ b/Services/Taskbar/WindowList.cpp @@ -0,0 +1,68 @@ +/* + * 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" +#include <LibGUI/WindowServerConnection.h> + +WindowList& WindowList::the() +{ + static WindowList* s_the; + if (!s_the) + s_the = new WindowList; + return *s_the; +} + +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); + window->set_button(aid_create_button(identifier)); + window->button()->on_click = [window = window.ptr(), identifier] { + if (window->is_minimized() || !window->is_active()) { + 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)); + } + }; + 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/Services/Taskbar/WindowList.h b/Services/Taskbar/WindowList.h new file mode 100644 index 0000000000..90d842c3f4 --- /dev/null +++ b/Services/Taskbar/WindowList.h @@ -0,0 +1,96 @@ +/* + * 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/String.h> +#include <AK/HashMap.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(); + } + + WindowIdentifier identifier() const { return m_identifier; } + + String title() const { return m_title; } + void set_title(const String& title) { m_title = title; } + + Gfx::Rect rect() const { return m_rect; } + void set_rect(const Gfx::Rect& 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; } + + const Gfx::Bitmap* icon() const { return m_icon.ptr(); } + +private: + WindowIdentifier m_identifier; + String m_title; + Gfx::Rect m_rect; + RefPtr<GUI::Button> m_button; + RefPtr<Gfx::Bitmap> m_icon; + bool m_active { false }; + bool m_minimized { false }; +}; + +class WindowList { +public: + static WindowList& the(); + + template<typename Callback> + void for_each_window(Callback callback) + { + for (auto& it : m_windows) + callback(*it.value); + } + + Window* window(const WindowIdentifier&); + Window& ensure_window(const WindowIdentifier&); + void remove_window(const WindowIdentifier&); + + Function<NonnullRefPtr<GUI::Button>(const WindowIdentifier&)> aid_create_button; + +private: + HashMap<WindowIdentifier, NonnullOwnPtr<Window>> m_windows; +}; diff --git a/Services/Taskbar/main.cpp b/Services/Taskbar/main.cpp new file mode 100644 index 0000000000..33b96cb465 --- /dev/null +++ b/Services/Taskbar/main.cpp @@ -0,0 +1,56 @@ +/* + * 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 <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", nullptr) < 0) { + perror("pledge"); + return 1; + } + + GUI::Application app(argc, argv); + + if (pledge("stdio shared_buffer accept proc exec rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + TaskbarWindow window; + window.show(); + + signal(SIGCHLD, [](int signo) { + (void)signo; + wait(nullptr); + }); + + return app.exec(); +} |