Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "TaskbarWindow.h"
28#include "TaskbarButton.h"
29#include <AK/SharedBuffer.h>
30#include <LibCore/ConfigFile.h>
31#include <LibGUI/BoxLayout.h>
32#include <LibGUI/Button.h>
33#include <LibGUI/Desktop.h>
34#include <LibGUI/Frame.h>
35#include <LibGUI/Window.h>
36#include <stdio.h>
37
38//#define EVENT_DEBUG
39
40TaskbarWindow::TaskbarWindow()
41{
42 set_window_type(GUI::WindowType::Taskbar);
43 set_title("Taskbar");
44
45 on_screen_rect_change(GUI::Desktop::the().rect());
46
47 GUI::Desktop::the().on_rect_change = [this](const Gfx::Rect& rect) { on_screen_rect_change(rect); };
48
49 auto widget = GUI::Frame::construct();
50 widget->set_fill_with_background_color(true);
51 widget->set_layout(make<GUI::HorizontalBoxLayout>());
52 widget->layout()->set_margins({ 3, 2, 3, 2 });
53 widget->layout()->set_spacing(3);
54 widget->set_frame_thickness(1);
55 widget->set_frame_shape(Gfx::FrameShape::Panel);
56 widget->set_frame_shadow(Gfx::FrameShadow::Raised);
57 set_main_widget(widget);
58
59 WindowList::the().aid_create_button = [this](auto& identifier) {
60 return create_button(identifier);
61 };
62
63 create_quick_launch_bar();
64}
65
66TaskbarWindow::~TaskbarWindow()
67{
68}
69
70void TaskbarWindow::create_quick_launch_bar()
71{
72 auto quick_launch_bar = main_widget()->add<GUI::Frame>();
73 quick_launch_bar->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed);
74 quick_launch_bar->set_layout(make<GUI::HorizontalBoxLayout>());
75 quick_launch_bar->layout()->set_spacing(3);
76 quick_launch_bar->layout()->set_margins({ 3, 0, 3, 0 });
77 quick_launch_bar->set_frame_thickness(1);
78 quick_launch_bar->set_frame_shape(Gfx::FrameShape::Container);
79 quick_launch_bar->set_frame_shadow(Gfx::FrameShadow::Raised);
80
81 int total_width = 6;
82 bool first = true;
83
84 auto config = Core::ConfigFile::get_for_app("Taskbar");
85 constexpr const char* quick_launch = "QuickLaunch";
86
87 // FIXME: Core::ConfigFile does not keep the order of the entries.
88 for (auto& name : config->keys(quick_launch)) {
89 auto af_name = config->read_entry(quick_launch, name);
90 ASSERT(!af_name.is_null());
91 auto af_path = String::format("/res/apps/%s", af_name.characters());
92 auto af = Core::ConfigFile::open(af_path);
93 auto app_executable = af->read_entry("App", "Executable");
94 auto app_icon_path = af->read_entry("Icons", "16x16");
95
96 auto button = quick_launch_bar->add<GUI::Button>();
97 button->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed);
98 button->set_preferred_size(22, 22);
99 button->set_button_style(Gfx::ButtonStyle::CoolBar);
100
101 button->set_icon(Gfx::Bitmap::load_from_file(app_icon_path));
102 // FIXME: the tooltip ends up outside the screen rect.
103 button->set_tooltip(name);
104 button->on_click = [app_executable](auto&) {
105 pid_t pid = fork();
106 if (pid < 0) {
107 perror("fork");
108 } else if (pid == 0) {
109 execl(app_executable.characters(), app_executable.characters(), nullptr);
110 perror("execl");
111 ASSERT_NOT_REACHED();
112 }
113 };
114
115 if (!first)
116 total_width += 3;
117 first = false;
118 total_width += 22;
119 }
120
121 quick_launch_bar->set_preferred_size(total_width, 22);
122}
123
124void TaskbarWindow::on_screen_rect_change(const Gfx::Rect& rect)
125{
126 Gfx::Rect new_rect { rect.x(), rect.bottom() - taskbar_height() + 1, rect.width(), taskbar_height() };
127 set_rect(new_rect);
128}
129
130NonnullRefPtr<GUI::Button> TaskbarWindow::create_button(const WindowIdentifier& identifier)
131{
132 auto button = main_widget()->add<TaskbarButton>(identifier);
133 button->set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed);
134 button->set_preferred_size(140, 22);
135 button->set_checkable(true);
136 button->set_text_alignment(Gfx::TextAlignment::CenterLeft);
137 return button;
138}
139
140static bool should_include_window(GUI::WindowType window_type)
141{
142 return window_type == GUI::WindowType::Normal;
143}
144
145void TaskbarWindow::wm_event(GUI::WMEvent& event)
146{
147 WindowIdentifier identifier { event.client_id(), event.window_id() };
148 switch (event.type()) {
149 case GUI::Event::WM_WindowRemoved: {
150#ifdef EVENT_DEBUG
151 auto& removed_event = static_cast<GUI::WMWindowRemovedEvent&>(event);
152 dbgprintf("WM_WindowRemoved: client_id=%d, window_id=%d\n",
153 removed_event.client_id(),
154 removed_event.window_id());
155#endif
156 WindowList::the().remove_window(identifier);
157 update();
158 break;
159 }
160 case GUI::Event::WM_WindowRectChanged: {
161#ifdef EVENT_DEBUG
162 auto& changed_event = static_cast<GUI::WMWindowRectChangedEvent&>(event);
163 dbgprintf("WM_WindowRectChanged: client_id=%d, window_id=%d, rect=%s\n",
164 changed_event.client_id(),
165 changed_event.window_id(),
166 changed_event.rect().to_string().characters());
167#endif
168 break;
169 }
170
171 case GUI::Event::WM_WindowIconBitmapChanged: {
172 auto& changed_event = static_cast<GUI::WMWindowIconBitmapChangedEvent&>(event);
173#ifdef EVENT_DEBUG
174 dbgprintf("WM_WindowIconBitmapChanged: client_id=%d, window_id=%d, icon_buffer_id=%d\n",
175 changed_event.client_id(),
176 changed_event.window_id(),
177 changed_event.icon_buffer_id());
178#endif
179 if (auto* window = WindowList::the().window(identifier)) {
180 auto buffer = SharedBuffer::create_from_shared_buffer_id(changed_event.icon_buffer_id());
181 ASSERT(buffer);
182 window->button()->set_icon(Gfx::Bitmap::create_with_shared_buffer(Gfx::BitmapFormat::RGBA32, *buffer, changed_event.icon_size()));
183 }
184 break;
185 }
186
187 case GUI::Event::WM_WindowStateChanged: {
188 auto& changed_event = static_cast<GUI::WMWindowStateChangedEvent&>(event);
189#ifdef EVENT_DEBUG
190 dbgprintf("WM_WindowStateChanged: client_id=%d, window_id=%d, title=%s, rect=%s, is_active=%u, is_minimized=%u\n",
191 changed_event.client_id(),
192 changed_event.window_id(),
193 changed_event.title().characters(),
194 changed_event.rect().to_string().characters(),
195 changed_event.is_active(),
196 changed_event.is_minimized());
197#endif
198 if (!should_include_window(changed_event.window_type()))
199 break;
200 auto& window = WindowList::the().ensure_window(identifier);
201 window.set_title(changed_event.title());
202 window.set_rect(changed_event.rect());
203 window.set_active(changed_event.is_active());
204 window.set_minimized(changed_event.is_minimized());
205 if (window.is_minimized()) {
206 window.button()->set_foreground_color(Color::DarkGray);
207 window.button()->set_text(String::format("[%s]", changed_event.title().characters()));
208 } else {
209 window.button()->set_foreground_color(Color::Black);
210 window.button()->set_text(changed_event.title());
211 }
212 window.button()->set_checked(changed_event.is_active());
213 break;
214 }
215 default:
216 break;
217 }
218}