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 "Window.h"
28#include "ClientConnection.h"
29#include "Event.h"
30#include "EventLoop.h"
31#include "Screen.h"
32#include "WindowClientEndpoint.h"
33#include "WindowManager.h"
34#include <AK/Badge.h>
35
36namespace WindowServer {
37
38static String default_window_icon_path()
39{
40 return "/res/icons/16x16/window.png";
41}
42
43static Gfx::Bitmap& default_window_icon()
44{
45 static Gfx::Bitmap* s_icon;
46 if (!s_icon)
47 s_icon = Gfx::Bitmap::load_from_file(default_window_icon_path()).leak_ref();
48 return *s_icon;
49}
50
51Window::Window(Core::Object& parent, WindowType type)
52 : Core::Object(&parent)
53 , m_type(type)
54 , m_icon(default_window_icon())
55 , m_frame(*this)
56{
57 WindowManager::the().add_window(*this);
58}
59
60Window::Window(ClientConnection& client, WindowType window_type, int window_id, bool modal, bool minimizable, bool resizable, bool fullscreen)
61 : Core::Object(&client)
62 , m_client(&client)
63 , m_type(window_type)
64 , m_modal(modal)
65 , m_minimizable(minimizable)
66 , m_resizable(resizable)
67 , m_fullscreen(fullscreen)
68 , m_window_id(window_id)
69 , m_client_id(client.client_id())
70 , m_icon(default_window_icon())
71 , m_frame(*this)
72{
73 // FIXME: This should not be hard-coded here.
74 if (m_type == WindowType::Taskbar) {
75 m_wm_event_mask = WMEventMask::WindowStateChanges | WMEventMask::WindowRemovals | WMEventMask::WindowIconChanges;
76 m_listens_to_wm_events = true;
77 }
78 WindowManager::the().add_window(*this);
79}
80
81Window::~Window()
82{
83 // Detach from client at the start of teardown since we don't want
84 // to confuse things by trying to send messages to it.
85 m_client = nullptr;
86
87 WindowManager::the().remove_window(*this);
88}
89
90void Window::set_title(const String& title)
91{
92 if (m_title == title)
93 return;
94 m_title = title;
95 WindowManager::the().notify_title_changed(*this);
96}
97
98void Window::set_rect(const Gfx::Rect& rect)
99{
100 ASSERT(!rect.is_empty());
101 Gfx::Rect old_rect;
102 if (m_rect == rect)
103 return;
104 old_rect = m_rect;
105 m_rect = rect;
106 if (!m_client && (!m_backing_store || old_rect.size() != rect.size())) {
107 m_backing_store = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, m_rect.size());
108 }
109 m_frame.notify_window_rect_changed(old_rect, rect);
110}
111
112void Window::handle_mouse_event(const MouseEvent& event)
113{
114 set_automatic_cursor_tracking_enabled(event.buttons() != 0);
115
116 switch (event.type()) {
117 case Event::MouseMove:
118 m_client->post_message(Messages::WindowClient::MouseMove(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta(), event.is_drag(), event.drag_data_type()));
119 break;
120 case Event::MouseDown:
121 m_client->post_message(Messages::WindowClient::MouseDown(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
122 break;
123 case Event::MouseDoubleClick:
124 m_client->post_message(Messages::WindowClient::MouseDoubleClick(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
125 break;
126 case Event::MouseUp:
127 m_client->post_message(Messages::WindowClient::MouseUp(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
128 break;
129 case Event::MouseWheel:
130 m_client->post_message(Messages::WindowClient::MouseWheel(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta()));
131 break;
132 default:
133 ASSERT_NOT_REACHED();
134 }
135}
136
137void Window::update_menu_item_text(PopupMenuItem item)
138{
139 if (m_window_menu) {
140 m_window_menu->item((int)item).set_text(item == PopupMenuItem::Minimize ? (m_minimized ? "Unminimize" : "Minimize") : (m_maximized ? "Restore" : "Maximize"));
141 m_window_menu->redraw();
142 }
143}
144
145void Window::update_menu_item_enabled(PopupMenuItem item)
146{
147 if (m_window_menu) {
148 m_window_menu->item((int)item).set_enabled(item == PopupMenuItem::Minimize ? m_minimizable : m_resizable);
149 m_window_menu->redraw();
150 }
151}
152
153void Window::set_minimized(bool minimized)
154{
155 if (m_minimized == minimized)
156 return;
157 if (minimized && !m_minimizable)
158 return;
159 if (is_blocked_by_modal_window())
160 return;
161 m_minimized = minimized;
162 update_menu_item_text(PopupMenuItem::Minimize);
163 start_minimize_animation();
164 if (!minimized)
165 request_update({ {}, size() });
166 invalidate();
167 WindowManager::the().notify_minimization_state_changed(*this);
168}
169
170void Window::set_minimizable(bool minimizable)
171{
172 if (m_minimizable == minimizable)
173 return;
174 m_minimizable = minimizable;
175 update_menu_item_enabled(PopupMenuItem::Minimize);
176 // TODO: Hide/show (or alternatively change enabled state of) window minimize button dynamically depending on value of m_minimizable
177}
178
179void Window::set_opacity(float opacity)
180{
181 if (m_opacity == opacity)
182 return;
183 m_opacity = opacity;
184 WindowManager::the().notify_opacity_changed(*this);
185}
186
187void Window::set_occluded(bool occluded)
188{
189 if (m_occluded == occluded)
190 return;
191 m_occluded = occluded;
192 WindowManager::the().notify_occlusion_state_changed(*this);
193}
194
195void Window::set_maximized(bool maximized)
196{
197 if (m_maximized == maximized)
198 return;
199 if (maximized && !is_resizable())
200 return;
201 if (is_blocked_by_modal_window())
202 return;
203 set_tiled(WindowTileType::None);
204 m_maximized = maximized;
205 update_menu_item_text(PopupMenuItem::Maximize);
206 auto old_rect = m_rect;
207 if (maximized) {
208 m_unmaximized_rect = m_rect;
209 set_rect(WindowManager::the().maximized_window_rect(*this));
210 } else {
211 set_rect(m_unmaximized_rect);
212 }
213 m_frame.did_set_maximized({}, maximized);
214 Core::EventLoop::current().post_event(*this, make<ResizeEvent>(old_rect, m_rect));
215}
216
217void Window::set_resizable(bool resizable)
218{
219 if (m_resizable == resizable)
220 return;
221 m_resizable = resizable;
222 update_menu_item_enabled(PopupMenuItem::Maximize);
223 // TODO: Hide/show (or alternatively change enabled state of) window maximize button dynamically depending on value of is_resizable()
224}
225
226void Window::event(Core::Event& event)
227{
228 if (!m_client) {
229 ASSERT(parent());
230 event.ignore();
231 return;
232 }
233
234 if (is_blocked_by_modal_window())
235 return;
236
237 if (static_cast<Event&>(event).is_mouse_event())
238 return handle_mouse_event(static_cast<const MouseEvent&>(event));
239
240 switch (event.type()) {
241 case Event::WindowEntered:
242 m_client->post_message(Messages::WindowClient::WindowEntered(m_window_id));
243 break;
244 case Event::WindowLeft:
245 m_client->post_message(Messages::WindowClient::WindowLeft(m_window_id));
246 break;
247 case Event::KeyDown:
248 m_client->post_message(
249 Messages::WindowClient::KeyDown(m_window_id,
250 (u8) static_cast<const KeyEvent&>(event).character(),
251 (u32) static_cast<const KeyEvent&>(event).key(),
252 static_cast<const KeyEvent&>(event).modifiers()));
253 break;
254 case Event::KeyUp:
255 m_client->post_message(
256 Messages::WindowClient::KeyUp(m_window_id,
257 (u8) static_cast<const KeyEvent&>(event).character(),
258 (u32) static_cast<const KeyEvent&>(event).key(),
259 static_cast<const KeyEvent&>(event).modifiers()));
260 break;
261 case Event::WindowActivated:
262 m_client->post_message(Messages::WindowClient::WindowActivated(m_window_id));
263 break;
264 case Event::WindowDeactivated:
265 m_client->post_message(Messages::WindowClient::WindowDeactivated(m_window_id));
266 break;
267 case Event::WindowCloseRequest:
268 m_client->post_message(Messages::WindowClient::WindowCloseRequest(m_window_id));
269 break;
270 case Event::WindowResized:
271 m_client->post_message(
272 Messages::WindowClient::WindowResized(
273 m_window_id,
274 static_cast<const ResizeEvent&>(event).old_rect(),
275 static_cast<const ResizeEvent&>(event).rect()));
276 break;
277 default:
278 break;
279 }
280}
281
282void Window::set_global_cursor_tracking_enabled(bool enabled)
283{
284 m_global_cursor_tracking_enabled = enabled;
285}
286
287void Window::set_visible(bool b)
288{
289 if (m_visible == b)
290 return;
291 m_visible = b;
292 invalidate();
293}
294
295void Window::invalidate()
296{
297 WindowManager::the().invalidate(*this);
298}
299
300void Window::invalidate(const Gfx::Rect& rect)
301{
302 WindowManager::the().invalidate(*this, rect);
303}
304
305bool Window::is_active() const
306{
307 return WindowManager::the().active_window() == this;
308}
309
310bool Window::is_blocked_by_modal_window() const
311{
312 return !is_modal() && client() && client()->is_showing_modal_window();
313}
314
315void Window::set_default_icon()
316{
317 m_icon = default_window_icon();
318}
319
320void Window::request_update(const Gfx::Rect& rect, bool ignore_occlusion)
321{
322 if (m_pending_paint_rects.is_empty()) {
323 deferred_invoke([this, ignore_occlusion](auto&) {
324 client()->post_paint_message(*this, ignore_occlusion);
325 });
326 }
327 m_pending_paint_rects.add(rect);
328}
329
330void Window::popup_window_menu(const Gfx::Point& position)
331{
332 if (!m_window_menu) {
333 m_window_menu = Menu::construct(nullptr, -1, "(Window Menu)");
334 m_window_menu->set_window_menu_of(*this);
335
336 m_window_menu->add_item(make<MenuItem>(*m_window_menu, 1, m_minimized ? "Unminimize" : "Minimize"));
337 m_window_menu->add_item(make<MenuItem>(*m_window_menu, 2, m_maximized ? "Restore" : "Maximize"));
338 m_window_menu->add_item(make<MenuItem>(*m_window_menu, MenuItem::Type::Separator));
339 m_window_menu->add_item(make<MenuItem>(*m_window_menu, 3, "Close"));
340
341 m_window_menu->item((int)PopupMenuItem::Minimize).set_enabled(m_minimizable);
342 m_window_menu->item((int)PopupMenuItem::Maximize).set_enabled(m_resizable);
343
344 m_window_menu->on_item_activation = [&](auto& item) {
345 switch (item.identifier()) {
346 case 1:
347 set_minimized(!m_minimized);
348 if (!m_minimized)
349 WindowManager::the().move_to_front_and_make_active(*this);
350 break;
351 case 2:
352 set_maximized(!m_maximized);
353 if (m_minimized)
354 set_minimized(false);
355 WindowManager::the().move_to_front_and_make_active(*this);
356 break;
357 case 3:
358 request_close();
359 break;
360 }
361 };
362 }
363 m_window_menu->popup(position);
364}
365
366void Window::request_close()
367{
368 Event close_request(Event::WindowCloseRequest);
369 event(close_request);
370}
371
372void Window::set_fullscreen(bool fullscreen)
373{
374 if (m_fullscreen == fullscreen)
375 return;
376 m_fullscreen = fullscreen;
377 Gfx::Rect new_window_rect = m_rect;
378 if (m_fullscreen) {
379 m_saved_nonfullscreen_rect = m_rect;
380 new_window_rect = Screen::the().rect();
381 } else if (!m_saved_nonfullscreen_rect.is_empty()) {
382 new_window_rect = m_saved_nonfullscreen_rect;
383 }
384 Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect, new_window_rect));
385 set_rect(new_window_rect);
386}
387
388void Window::set_tiled(WindowTileType tiled)
389{
390 if (m_tiled == tiled)
391 return;
392 m_tiled = tiled;
393 auto old_rect = m_rect;
394
395 int frame_width = (m_frame.rect().width() - m_rect.width()) / 2;
396 switch (tiled) {
397 case WindowTileType::None:
398 set_rect(m_untiled_rect);
399 break;
400 case WindowTileType::Left:
401 m_untiled_rect = m_rect;
402 set_rect(0,
403 WindowManager::the().maximized_window_rect(*this).y(),
404 Screen::the().width() / 2 - frame_width,
405 WindowManager::the().maximized_window_rect(*this).height());
406 break;
407 case WindowTileType::Right:
408 m_untiled_rect = m_rect;
409 set_rect(Screen::the().width() / 2 + frame_width,
410 WindowManager::the().maximized_window_rect(*this).y(),
411 Screen::the().width() / 2 - frame_width,
412 WindowManager::the().maximized_window_rect(*this).height());
413 break;
414 }
415 Core::EventLoop::current().post_event(*this, make<ResizeEvent>(old_rect, m_rect));
416}
417
418void Window::detach_client(Badge<ClientConnection>)
419{
420 m_client = nullptr;
421}
422
423}