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