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 <LibCore/UserInfo.h>
32#include <LibGUI/BoxLayout.h>
33#include <LibGUI/Button.h>
34#include <LibGUI/Desktop.h>
35#include <LibGUI/Frame.h>
36#include <LibGUI/Window.h>
37#include <stdio.h>
38
39//#define EVENT_DEBUG
40
41TaskbarWindow::TaskbarWindow()
42{
43 set_window_type(GUI::WindowType::Taskbar);
44 set_title("Taskbar");
45
46 on_screen_rect_change(GUI::Desktop::the().rect());
47
48 GUI::Desktop::the().on_rect_change = [this](const Gfx::Rect& rect) { on_screen_rect_change(rect); };
49
50 auto& widget = set_main_widget<GUI::Frame>();
51 widget.set_fill_with_background_color(true);
52 widget.set_layout<GUI::HorizontalBoxLayout>();
53 widget.layout()->set_margins({ 3, 2, 3, 2 });
54 widget.layout()->set_spacing(3);
55 widget.set_frame_thickness(1);
56 widget.set_frame_shape(Gfx::FrameShape::Panel);
57 widget.set_frame_shadow(Gfx::FrameShadow::Raised);
58
59 m_default_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window.png");
60
61 WindowList::the().aid_create_button = [this](auto& identifier) {
62 return create_button(identifier);
63 };
64
65 create_quick_launch_bar();
66}
67
68TaskbarWindow::~TaskbarWindow()
69{
70}
71
72void TaskbarWindow::create_quick_launch_bar()
73{
74 auto& quick_launch_bar = main_widget()->add<GUI::Frame>();
75 quick_launch_bar.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed);
76 quick_launch_bar.set_layout<GUI::HorizontalBoxLayout>();
77 quick_launch_bar.layout()->set_spacing(3);
78 quick_launch_bar.layout()->set_margins({ 3, 0, 3, 0 });
79 quick_launch_bar.set_frame_thickness(1);
80 quick_launch_bar.set_frame_shape(Gfx::FrameShape::Container);
81 quick_launch_bar.set_frame_shadow(Gfx::FrameShadow::Raised);
82
83 int total_width = 6;
84 bool first = true;
85
86 auto config = Core::ConfigFile::get_for_app("Taskbar");
87 constexpr const char* quick_launch = "QuickLaunch";
88
89 // FIXME: Core::ConfigFile does not keep the order of the entries.
90 for (auto& name : config->keys(quick_launch)) {
91 auto af_name = config->read_entry(quick_launch, name);
92 ASSERT(!af_name.is_null());
93 auto af_path = String::format("/res/apps/%s", af_name.characters());
94 auto af = Core::ConfigFile::open(af_path);
95 auto app_executable = af->read_entry("App", "Executable");
96 auto app_icon_path = af->read_entry("Icons", "16x16");
97
98 auto& button = quick_launch_bar.add<GUI::Button>();
99 button.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed);
100 button.set_preferred_size(22, 22);
101 button.set_button_style(Gfx::ButtonStyle::CoolBar);
102
103 button.set_icon(Gfx::Bitmap::load_from_file(app_icon_path));
104 button.set_tooltip(name);
105 button.on_click = [app_executable] {
106 pid_t pid = fork();
107 if (pid < 0) {
108 perror("fork");
109 } else if (pid == 0) {
110 if (chdir(get_current_user_home_path().characters()) < 0) {
111 perror("chdir");
112 exit(1);
113 }
114 execl(app_executable.characters(), app_executable.characters(), nullptr);
115 perror("execl");
116 ASSERT_NOT_REACHED();
117 }
118 };
119
120 if (!first)
121 total_width += 3;
122 first = false;
123 total_width += 22;
124 }
125
126 quick_launch_bar.set_preferred_size(total_width, 22);
127}
128
129void TaskbarWindow::on_screen_rect_change(const Gfx::Rect& rect)
130{
131 Gfx::Rect new_rect { rect.x(), rect.bottom() - taskbar_height() + 1, rect.width(), taskbar_height() };
132 set_rect(new_rect);
133}
134
135NonnullRefPtr<GUI::Button> TaskbarWindow::create_button(const WindowIdentifier& identifier)
136{
137 auto& button = main_widget()->add<TaskbarButton>(identifier);
138 button.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fixed);
139 button.set_preferred_size(140, 22);
140 button.set_checkable(true);
141 button.set_text_alignment(Gfx::TextAlignment::CenterLeft);
142 button.set_icon(*m_default_icon);
143 return button;
144}
145
146static bool should_include_window(GUI::WindowType window_type)
147{
148 return window_type == GUI::WindowType::Normal;
149}
150
151void TaskbarWindow::wm_event(GUI::WMEvent& event)
152{
153 WindowIdentifier identifier { event.client_id(), event.window_id() };
154 switch (event.type()) {
155 case GUI::Event::WM_WindowRemoved: {
156#ifdef EVENT_DEBUG
157 auto& removed_event = static_cast<GUI::WMWindowRemovedEvent&>(event);
158 dbgprintf("WM_WindowRemoved: client_id=%d, window_id=%d\n",
159 removed_event.client_id(),
160 removed_event.window_id());
161#endif
162 WindowList::the().remove_window(identifier);
163 update();
164 break;
165 }
166 case GUI::Event::WM_WindowRectChanged: {
167#ifdef EVENT_DEBUG
168 auto& changed_event = static_cast<GUI::WMWindowRectChangedEvent&>(event);
169 dbgprintf("WM_WindowRectChanged: client_id=%d, window_id=%d, rect=%s\n",
170 changed_event.client_id(),
171 changed_event.window_id(),
172 changed_event.rect().to_string().characters());
173#endif
174 break;
175 }
176
177 case GUI::Event::WM_WindowIconBitmapChanged: {
178 auto& changed_event = static_cast<GUI::WMWindowIconBitmapChangedEvent&>(event);
179#ifdef EVENT_DEBUG
180 dbgprintf("WM_WindowIconBitmapChanged: client_id=%d, window_id=%d, icon_buffer_id=%d\n",
181 changed_event.client_id(),
182 changed_event.window_id(),
183 changed_event.icon_buffer_id());
184#endif
185 if (auto* window = WindowList::the().window(identifier)) {
186 auto buffer = SharedBuffer::create_from_shbuf_id(changed_event.icon_buffer_id());
187 ASSERT(buffer);
188 window->button()->set_icon(Gfx::Bitmap::create_with_shared_buffer(Gfx::BitmapFormat::RGBA32, *buffer, changed_event.icon_size()));
189 }
190 break;
191 }
192
193 case GUI::Event::WM_WindowStateChanged: {
194 auto& changed_event = static_cast<GUI::WMWindowStateChangedEvent&>(event);
195#ifdef EVENT_DEBUG
196 dbgprintf("WM_WindowStateChanged: client_id=%d, window_id=%d, title=%s, rect=%s, is_active=%u, is_minimized=%u\n",
197 changed_event.client_id(),
198 changed_event.window_id(),
199 changed_event.title().characters(),
200 changed_event.rect().to_string().characters(),
201 changed_event.is_active(),
202 changed_event.is_minimized());
203#endif
204 if (!should_include_window(changed_event.window_type()))
205 break;
206 auto& window = WindowList::the().ensure_window(identifier);
207 window.set_title(changed_event.title());
208 window.set_rect(changed_event.rect());
209 window.set_active(changed_event.is_active());
210 window.set_minimized(changed_event.is_minimized());
211 if (window.is_minimized()) {
212 window.button()->set_foreground_color(Color::DarkGray);
213 window.button()->set_text(String::format("[%s]", changed_event.title().characters()));
214 } else {
215 window.button()->set_foreground_color(Color::Black);
216 window.button()->set_text(changed_event.title());
217 }
218 window.button()->set_checked(changed_event.is_active());
219 break;
220 }
221 default:
222 break;
223 }
224}