diff options
author | Andreas Kling <kling@serenityos.org> | 2021-03-25 22:55:10 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-03-25 22:55:10 +0100 |
commit | 73a2045c5bc45ad1f815e113b59c4f174c204d08 (patch) | |
tree | 118f77cf5e4849428a39cdcc794ab6fc57a93cd0 | |
parent | 32c6e31f4cefd03b6a79495d662298065dc40c16 (diff) | |
download | serenity-73a2045c5bc45ad1f815e113b59c4f174c204d08.zip |
Taskbar: Add start menu :^)
This will replace the system menu in the top left of the screen.
The button behavior doesn't feel quite perfect yet, but this will
work for now!
-rw-r--r-- | Userland/Services/Taskbar/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Services/Taskbar/ShutdownDialog.cpp | 120 | ||||
-rw-r--r-- | Userland/Services/Taskbar/ShutdownDialog.h | 43 | ||||
-rw-r--r-- | Userland/Services/Taskbar/TaskbarWindow.cpp | 16 | ||||
-rw-r--r-- | Userland/Services/Taskbar/TaskbarWindow.h | 3 | ||||
-rw-r--r-- | Userland/Services/Taskbar/main.cpp | 181 |
6 files changed, 360 insertions, 4 deletions
diff --git a/Userland/Services/Taskbar/CMakeLists.txt b/Userland/Services/Taskbar/CMakeLists.txt index 3b0d10674e..14386c01fc 100644 --- a/Userland/Services/Taskbar/CMakeLists.txt +++ b/Userland/Services/Taskbar/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES main.cpp ClockWidget.cpp + ShutdownDialog.cpp TaskbarButton.cpp TaskbarWindow.cpp WindowList.cpp diff --git a/Userland/Services/Taskbar/ShutdownDialog.cpp b/Userland/Services/Taskbar/ShutdownDialog.cpp new file mode 100644 index 0000000000..76799dd4e1 --- /dev/null +++ b/Userland/Services/Taskbar/ShutdownDialog.cpp @@ -0,0 +1,120 @@ +/* + * 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 "ShutdownDialog.h" +#include <AK/String.h> +#include <AK/Vector.h> +#include <LibGUI/BoxLayout.h> +#include <LibGUI/Button.h> +#include <LibGUI/Label.h> +#include <LibGUI/RadioButton.h> +#include <LibGUI/Widget.h> +#include <LibGfx/Font.h> +#include <LibGfx/FontDatabase.h> + +struct Option { + String title; + Vector<char const*> cmd; + bool enabled; + bool default_action; +}; + +static const Vector<Option> 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*> ShutdownDialog::show() +{ + auto dialog = ShutdownDialog::construct(); + auto rc = dialog->exec(); + if (rc == ExecResult::ExecOK && dialog->m_selected_option != -1) + return options[dialog->m_selected_option].cmd; + + return {}; +} + +ShutdownDialog::ShutdownDialog() + : Dialog(nullptr) +{ + resize(180, 180 + ((static_cast<int>(options.size()) - 3) * 16)); + center_on_screen(); + set_resizable(false); + set_title("SerenityOS"); + set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/power.png")); + + // Request WindowServer to re-update us on the current theme as we might've not been alive for the last notification. + refresh_system_theme(); + + 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_fixed_height(16); + header.set_font(Gfx::FontDatabase::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](auto) { + done(ExecResult::ExecOK); + }; + ok_button.set_text("OK"); + + auto& cancel_button = button_box.add<GUI::Button>(); + cancel_button.on_click = [this](auto) { + done(ExecResult::ExecCancel); + }; + cancel_button.set_text("Cancel"); +} + +ShutdownDialog::~ShutdownDialog() +{ +} diff --git a/Userland/Services/Taskbar/ShutdownDialog.h b/Userland/Services/Taskbar/ShutdownDialog.h new file mode 100644 index 0000000000..73f581916c --- /dev/null +++ b/Userland/Services/Taskbar/ShutdownDialog.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#pragma once + +#include <AK/Vector.h> +#include <LibGUI/Dialog.h> + +class ShutdownDialog : public GUI::Dialog { + C_OBJECT(ShutdownDialog); + +public: + static Vector<char const*> show(); + +private: + ShutdownDialog(); + virtual ~ShutdownDialog() override; + + int m_selected_option { -1 }; +}; diff --git a/Userland/Services/Taskbar/TaskbarWindow.cpp b/Userland/Services/Taskbar/TaskbarWindow.cpp index 0f9d80ba32..b2b027e4d6 100644 --- a/Userland/Services/Taskbar/TaskbarWindow.cpp +++ b/Userland/Services/Taskbar/TaskbarWindow.cpp @@ -36,9 +36,11 @@ #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/WindowServerConnection.h> +#include <LibGfx/FontDatabase.h> #include <LibGfx/Palette.h> #include <serenity.h> #include <stdio.h> @@ -69,7 +71,8 @@ private: } }; -TaskbarWindow::TaskbarWindow() +TaskbarWindow::TaskbarWindow(NonnullRefPtr<GUI::Menu> start_menu) + : m_start_menu(move(start_menu)) { set_window_type(GUI::WindowType::Taskbar); set_title("Taskbar"); @@ -82,6 +85,17 @@ TaskbarWindow::TaskbarWindow() main_widget.set_layout<GUI::HorizontalBoxLayout>(); main_widget.layout()->set_margins({ 3, 2, 3, 2 }); + auto& start_button = main_widget.add<GUI::Button>("Serenity"); + start_button.set_font(Gfx::FontDatabase::default_bold_font()); + start_button.set_icon_spacing(0); + start_button.set_fixed_width(80); + auto app_icon = GUI::Icon::default_icon("ladybug"); + start_button.set_icon(app_icon.bitmap_for_size(16)); + + start_button.on_click = [&](auto) { + m_start_menu->popup(start_button.screen_relative_rect().top_left()); + }; + create_quick_launch_bar(); m_task_button_container = main_widget.add<GUI::Widget>(); diff --git a/Userland/Services/Taskbar/TaskbarWindow.h b/Userland/Services/Taskbar/TaskbarWindow.h index 9e0e73878d..e88309c55c 100644 --- a/Userland/Services/Taskbar/TaskbarWindow.h +++ b/Userland/Services/Taskbar/TaskbarWindow.h @@ -39,7 +39,7 @@ public: static int taskbar_height() { return 28; } private: - TaskbarWindow(); + explicit TaskbarWindow(NonnullRefPtr<GUI::Menu> start_menu); void create_quick_launch_bar(); void on_screen_rect_change(const Gfx::IntRect&); NonnullRefPtr<GUI::Button> create_button(const WindowIdentifier&); @@ -50,6 +50,7 @@ private: virtual void wm_event(GUI::WMEvent&) override; + NonnullRefPtr<GUI::Menu> m_start_menu; RefPtr<GUI::Widget> m_task_button_container; RefPtr<Gfx::Bitmap> m_default_icon; }; diff --git a/Userland/Services/Taskbar/main.cpp b/Userland/Services/Taskbar/main.cpp index 02cc6a0af8..04e743dadc 100644 --- a/Userland/Services/Taskbar/main.cpp +++ b/Userland/Services/Taskbar/main.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,14 +24,29 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "ShutdownDialog.h" #include "TaskbarWindow.h" +#include <AK/Debug.h> +#include <AK/LexicalPath.h> +#include <AK/QuickSort.h> +#include <LibCore/ConfigFile.h> +#include <LibCore/DirIterator.h> #include <LibCore/EventLoop.h> +#include <LibDesktop/AppFile.h> +#include <LibGUI/ActionGroup.h> #include <LibGUI/Application.h> +#include <LibGUI/Menu.h> +#include <LibGUI/WindowServerConnection.h> +#include <serenity.h> #include <signal.h> +#include <spawn.h> #include <stdio.h> #include <sys/wait.h> #include <unistd.h> +static Vector<String> discover_apps_and_categories(); +static NonnullRefPtr<GUI::Menu> build_system_menu(); + int main(int argc, char** argv) { if (pledge("stdio recvfd sendfd accept proc exec rpath unix cpath fattr sigaction", nullptr) < 0) { @@ -51,8 +66,170 @@ int main(int argc, char** argv) return 1; } - auto window = TaskbarWindow::construct(); + auto menu = build_system_menu(); + menu->realize_menu_if_needed(); + + auto window = TaskbarWindow::construct(move(menu)); window->show(); return app->exec(); } + +struct AppMetadata { + String executable; + String name; + String category; +}; +Vector<AppMetadata> g_apps; + +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; + +Vector<String> discover_apps_and_categories() +{ + HashTable<String> seen_app_categories; + Desktop::AppFile::for_each([&](auto af) { + g_apps.append({ af->executable(), af->name(), af->category() }); + seen_app_categories.set(af->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); + + return sorted_app_categories; +} + +NonnullRefPtr<GUI::Menu> build_system_menu() +{ + const Vector<String> sorted_app_categories = discover_apps_and_categories(); + auto system_menu = GUI::Menu::construct("\xE2\x9A\xA1"); // HIGH VOLTAGE SIGN + + // First we construct all the necessary app category submenus. + HashMap<String, NonnullRefPtr<GUI::Menu>> app_category_menus; + auto category_icons = Core::ConfigFile::open("/res/icons/SystemMenu.ini"); + for (const auto& category : sorted_app_categories) { + if (app_category_menus.contains(category)) + continue; + auto& category_menu = system_menu->add_submenu(category); + auto category_icon_path = category_icons->read_entry("16x16", category); + if (!category_icon_path.is_empty()) { + auto icon = Gfx::Bitmap::load_from_file(category_icon_path); + category_menu.set_icon(icon); + } + 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) { + auto icon = GUI::FileIconProvider::icon_for_executable(app.executable).bitmap_for_size(16); + + if constexpr (SYSTEM_MENU_DEBUG) { + if (icon) + dbgln("App {} has icon with size {}", app.name, icon->size()); + } + + auto parent_menu = app_category_menus.get(app.category).value_or(*system_menu); + parent_menu->add_action(GUI::Action::create(app.name, icon, [app_identifier](auto&) { + dbgln("Activated app with ID {}", app_identifier); + const auto& bin = g_apps[app_identifier].executable; + pid_t child_pid; + const char* argv[] = { bin.characters(), nullptr }; + if ((errno = posix_spawn(&child_pid, bin.characters(), nullptr, nullptr, const_cast<char**>(argv), environ))) { + perror("posix_spawn"); + } else { + if (disown(child_pid) < 0) + perror("disown"); + } + })); + ++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"); + g_themes_menu->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/themes.png")); + + { + Core::DirIterator dt("/res/themes", Core::DirIterator::SkipDots); + while (dt.has_next()) { + auto theme_name = dt.next_path(); + auto theme_path = String::formatted("/res/themes/{}", theme_name); + g_themes.append({ LexicalPath(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]; + dbgln("Theme switched to {} at path {}", theme.name, theme.path); + auto response = GUI::WindowServerConnection::the().send_sync<Messages::WindowServer::SetSystemTheme>(theme.path, theme.name); + VERIFY(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("Run...", Gfx::Bitmap::load_from_file("/res/icons/16x16/app-run.png"), [](auto&) { + pid_t child_pid; + const char* argv[] = { "/bin/Run", nullptr }; + if ((errno = posix_spawn(&child_pid, "/bin/Run", nullptr, nullptr, const_cast<char**>(argv), environ))) { + perror("posix_spawn"); + } else { + if (disown(child_pid) < 0) + perror("disown"); + } + })); + system_menu->add_action(GUI::Action::create("About SerenityOS", Gfx::Bitmap::load_from_file("/res/icons/16x16/ladybug.png"), [](auto&) { + pid_t child_pid; + const char* argv[] = { "/bin/About", nullptr }; + if ((errno = posix_spawn(&child_pid, "/bin/About", nullptr, nullptr, const_cast<char**>(argv), environ))) { + perror("posix_spawn"); + } else { + if (disown(child_pid) < 0) + perror("disown"); + } + })); + system_menu->add_separator(); + system_menu->add_action(GUI::Action::create("Exit...", Gfx::Bitmap::load_from_file("/res/icons/16x16/power.png"), [](auto&) { + auto command = ShutdownDialog::show(); + + if (command.size() == 0) + return; + + pid_t child_pid; + if ((errno = posix_spawn(&child_pid, command[0], nullptr, nullptr, const_cast<char**>(command.data()), environ))) { + perror("posix_spawn"); + } else { + if (disown(child_pid) < 0) + perror("disown"); + } + })); + + return system_menu; +} |