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 "WindowManager.h"
28#include "Compositor.h"
29#include "EventLoop.h"
30#include "Menu.h"
31#include "MenuBar.h"
32#include "MenuItem.h"
33#include "Screen.h"
34#include "Window.h"
35#include <AK/LogStream.h>
36#include <AK/SharedBuffer.h>
37#include <AK/StdLibExtras.h>
38#include <AK/Vector.h>
39#include <LibGfx/CharacterBitmap.h>
40#include <LibGfx/Font.h>
41#include <LibGfx/Painter.h>
42#include <LibGfx/StylePainter.h>
43#include <LibGfx/SystemTheme.h>
44#include <WindowServer/AppletManager.h>
45#include <WindowServer/Button.h>
46#include <WindowServer/ClientConnection.h>
47#include <WindowServer/Cursor.h>
48#include <WindowServer/WindowClientEndpoint.h>
49#include <errno.h>
50#include <stdio.h>
51#include <serenity.h>
52#include <time.h>
53#include <unistd.h>
54
55//#define WINDOWMANAGER_DEBUG
56//#define RESIZE_DEBUG
57//#define MOVE_DEBUG
58//#define DOUBLECLICK_DEBUG
59
60namespace WindowServer {
61
62static WindowManager* s_the;
63
64WindowManager& WindowManager::the()
65{
66 ASSERT(s_the);
67 return *s_the;
68}
69
70WindowManager::WindowManager(const Gfx::PaletteImpl& palette)
71 : m_palette(palette)
72{
73 s_the = this;
74
75 reload_config(false);
76
77 invalidate();
78 Compositor::the().compose();
79}
80
81WindowManager::~WindowManager()
82{
83}
84
85NonnullRefPtr<Cursor> WindowManager::get_cursor(const String& name, const Gfx::Point& hotspot)
86{
87 auto path = m_wm_config->read_entry("Cursor", name, "/res/cursors/arrow.png");
88 auto gb = Gfx::Bitmap::load_from_file(path);
89 if (gb)
90 return Cursor::create(*gb, hotspot);
91 return Cursor::create(*Gfx::Bitmap::load_from_file("/res/cursors/arrow.png"));
92}
93
94NonnullRefPtr<Cursor> WindowManager::get_cursor(const String& name)
95{
96 auto path = m_wm_config->read_entry("Cursor", name, "/res/cursors/arrow.png");
97 auto gb = Gfx::Bitmap::load_from_file(path);
98
99 if (gb)
100 return Cursor::create(*gb);
101 return Cursor::create(*Gfx::Bitmap::load_from_file("/res/cursors/arrow.png"));
102}
103
104void WindowManager::reload_config(bool set_screen)
105{
106 m_wm_config = Core::ConfigFile::open("/etc/WindowServer/WindowServer.ini");
107
108 m_double_click_speed = m_wm_config->read_num_entry("Input", "DoubleClickSpeed", 250);
109
110 if (set_screen) {
111 set_resolution(m_wm_config->read_num_entry("Screen", "Width", 1920), m_wm_config->read_num_entry("Screen", "Height", 1080));
112 }
113
114 m_arrow_cursor = get_cursor("Arrow", { 2, 2 });
115 m_hand_cursor = get_cursor("Hand", { 8, 4 });
116 m_resize_horizontally_cursor = get_cursor("ResizeH");
117 m_resize_vertically_cursor = get_cursor("ResizeV");
118 m_resize_diagonally_tlbr_cursor = get_cursor("ResizeDTLBR");
119 m_resize_diagonally_bltr_cursor = get_cursor("ResizeDBLTR");
120 m_i_beam_cursor = get_cursor("IBeam");
121 m_disallowed_cursor = get_cursor("Disallowed");
122 m_move_cursor = get_cursor("Move");
123 m_drag_cursor = get_cursor("Drag");
124}
125
126const Gfx::Font& WindowManager::font() const
127{
128 return Gfx::Font::default_font();
129}
130
131const Gfx::Font& WindowManager::window_title_font() const
132{
133 return Gfx::Font::default_bold_font();
134}
135
136bool WindowManager::set_resolution(int width, int height)
137{
138 bool success = Compositor::the().set_resolution(width, height);
139 MenuManager::the().set_needs_window_resize();
140 ClientConnection::for_each_client([&](ClientConnection& client) {
141 client.notify_about_new_screen_rect(Screen::the().rect());
142 });
143 if (success) {
144 for_each_window([](Window& window) {
145 window.recalculate_rect();
146 return IterationDecision::Continue;
147 });
148 }
149 if (m_wm_config) {
150 if (success) {
151 dbg() << "Saving resolution: " << Gfx::Size(width, height) << " to config file at " << m_wm_config->file_name();
152 m_wm_config->write_num_entry("Screen", "Width", width);
153 m_wm_config->write_num_entry("Screen", "Height", height);
154 m_wm_config->sync();
155 } else {
156 dbg() << "Saving fallback resolution: " << resolution() << " to config file at " << m_wm_config->file_name();
157 m_wm_config->write_num_entry("Screen", "Width", resolution().width());
158 m_wm_config->write_num_entry("Screen", "Height", resolution().height());
159 m_wm_config->sync();
160 }
161 }
162 return success;
163}
164
165Gfx::Size WindowManager::resolution() const
166{
167 return Screen::the().size();
168}
169
170void WindowManager::add_window(Window& window)
171{
172 m_windows_in_order.append(&window);
173
174 if (window.is_fullscreen()) {
175 Core::EventLoop::current().post_event(window, make<ResizeEvent>(window.rect(), Screen::the().rect()));
176 window.set_rect(Screen::the().rect());
177 }
178
179 set_active_window(&window);
180 if (m_switcher.is_visible() && window.type() != WindowType::WindowSwitcher)
181 m_switcher.refresh();
182
183 recompute_occlusions();
184
185 if (window.listens_to_wm_events()) {
186 for_each_window([&](Window& other_window) {
187 if (&window != &other_window) {
188 tell_wm_listener_about_window(window, other_window);
189 tell_wm_listener_about_window_icon(window, other_window);
190 }
191 return IterationDecision::Continue;
192 });
193 }
194
195 tell_wm_listeners_window_state_changed(window);
196}
197
198void WindowManager::move_to_front_and_make_active(Window& window)
199{
200 if (window.is_blocked_by_modal_window())
201 return;
202
203 if (m_windows_in_order.tail() != &window)
204 invalidate(window);
205 m_windows_in_order.remove(&window);
206 m_windows_in_order.append(&window);
207
208 recompute_occlusions();
209
210 set_active_window(&window);
211
212 if (m_switcher.is_visible()) {
213 m_switcher.refresh();
214 m_switcher.select_window(window);
215 set_highlight_window(&window);
216 }
217}
218
219void WindowManager::remove_window(Window& window)
220{
221 invalidate(window);
222 m_windows_in_order.remove(&window);
223 if (window.is_active())
224 pick_new_active_window();
225 if (m_switcher.is_visible() && window.type() != WindowType::WindowSwitcher)
226 m_switcher.refresh();
227
228 recompute_occlusions();
229
230 for_each_window_listening_to_wm_events([&window](Window& listener) {
231 if (!(listener.wm_event_mask() & WMEventMask::WindowRemovals))
232 return IterationDecision::Continue;
233 if (!window.is_internal())
234 listener.client()->post_message(Messages::WindowClient::WM_WindowRemoved(listener.window_id(), window.client_id(), window.window_id()));
235 return IterationDecision::Continue;
236 });
237}
238
239void WindowManager::tell_wm_listener_about_window(Window& listener, Window& window)
240{
241 if (!(listener.wm_event_mask() & WMEventMask::WindowStateChanges))
242 return;
243 if (window.is_internal())
244 return;
245 listener.client()->post_message(Messages::WindowClient::WM_WindowStateChanged(listener.window_id(), window.client_id(), window.window_id(), window.is_active(), window.is_minimized(), (i32)window.type(), window.title(), window.rect()));
246}
247
248void WindowManager::tell_wm_listener_about_window_rect(Window& listener, Window& window)
249{
250 if (!(listener.wm_event_mask() & WMEventMask::WindowRectChanges))
251 return;
252 if (window.is_internal())
253 return;
254 listener.client()->post_message(Messages::WindowClient::WM_WindowRectChanged(listener.window_id(), window.client_id(), window.window_id(), window.rect()));
255}
256
257void WindowManager::tell_wm_listener_about_window_icon(Window& listener, Window& window)
258{
259 if (!(listener.wm_event_mask() & WMEventMask::WindowIconChanges))
260 return;
261 if (window.is_internal())
262 return;
263 if (window.icon().shbuf_id() == -1)
264 return;
265#ifdef WINDOWMANAGER_DEBUG
266 dbg() << "WindowServer: Sharing icon buffer " << window.icon().shbuf_id() << " with PID " << listener.client()->client_pid();
267#endif
268 if (shbuf_allow_pid(window.icon().shbuf_id(), listener.client()->client_pid()) < 0) {
269 ASSERT_NOT_REACHED();
270 }
271 listener.client()->post_message(Messages::WindowClient::WM_WindowIconBitmapChanged(listener.window_id(), window.client_id(), window.window_id(), window.icon().shbuf_id(), window.icon().size()));
272}
273
274void WindowManager::tell_wm_listeners_window_state_changed(Window& window)
275{
276 for_each_window_listening_to_wm_events([&](Window& listener) {
277 tell_wm_listener_about_window(listener, window);
278 return IterationDecision::Continue;
279 });
280}
281
282void WindowManager::tell_wm_listeners_window_icon_changed(Window& window)
283{
284 for_each_window_listening_to_wm_events([&](Window& listener) {
285 tell_wm_listener_about_window_icon(listener, window);
286 return IterationDecision::Continue;
287 });
288}
289
290void WindowManager::tell_wm_listeners_window_rect_changed(Window& window)
291{
292 for_each_window_listening_to_wm_events([&](Window& listener) {
293 tell_wm_listener_about_window_rect(listener, window);
294 return IterationDecision::Continue;
295 });
296}
297
298void WindowManager::notify_title_changed(Window& window)
299{
300 if (window.type() != WindowType::Normal)
301 return;
302#ifdef WINDOWMANAGER_DEBUG
303 dbg() << "[WM] Window{" << &window << "} title set to \"" << window.title() << '"';
304#endif
305 invalidate(window.frame().rect());
306 if (m_switcher.is_visible())
307 m_switcher.refresh();
308
309 tell_wm_listeners_window_state_changed(window);
310}
311
312void WindowManager::notify_rect_changed(Window& window, const Gfx::Rect& old_rect, const Gfx::Rect& new_rect)
313{
314 UNUSED_PARAM(old_rect);
315 UNUSED_PARAM(new_rect);
316#ifdef RESIZE_DEBUG
317 dbg() << "[WM] Window " << &window << " rect changed " << old_rect << " -> " << new_rect;
318#endif
319 if (m_switcher.is_visible() && window.type() != WindowType::WindowSwitcher)
320 m_switcher.refresh();
321
322 recompute_occlusions();
323
324 tell_wm_listeners_window_rect_changed(window);
325
326 MenuManager::the().refresh();
327}
328
329void WindowManager::recompute_occlusions()
330{
331 for_each_visible_window_from_back_to_front([&](Window& window) {
332 if (m_switcher.is_visible()) {
333 window.set_occluded(false);
334 } else {
335 if (any_opaque_window_above_this_one_contains_rect(window, window.frame().rect()))
336 window.set_occluded(true);
337 else
338 window.set_occluded(false);
339 }
340 return IterationDecision::Continue;
341 });
342}
343
344void WindowManager::notify_opacity_changed(Window&)
345{
346 recompute_occlusions();
347}
348
349void WindowManager::notify_minimization_state_changed(Window& window)
350{
351 tell_wm_listeners_window_state_changed(window);
352
353 if (window.client())
354 window.client()->post_message(Messages::WindowClient::WindowStateChanged(window.window_id(), window.is_minimized(), window.is_occluded()));
355
356 if (window.is_active() && window.is_minimized())
357 pick_new_active_window();
358}
359
360void WindowManager::notify_occlusion_state_changed(Window& window)
361{
362 if (window.client())
363 window.client()->post_message(Messages::WindowClient::WindowStateChanged(window.window_id(), window.is_minimized(), window.is_occluded()));
364}
365
366void WindowManager::pick_new_active_window()
367{
368 bool new_window_picked = false;
369 for_each_visible_window_of_type_from_front_to_back(WindowType::Normal, [&](Window& candidate) {
370 set_active_window(&candidate);
371 new_window_picked = true;
372 return IterationDecision::Break;
373 });
374 if (!new_window_picked)
375 set_active_window(nullptr);
376}
377
378void WindowManager::start_window_move(Window& window, const MouseEvent& event)
379{
380#ifdef MOVE_DEBUG
381 dbg() << "[WM] Begin moving Window{" << &window << "}";
382#endif
383 move_to_front_and_make_active(window);
384 m_move_window = window.make_weak_ptr();
385 m_move_origin = event.position();
386 m_move_window_origin = window.position();
387 invalidate(window);
388}
389
390void WindowManager::start_window_resize(Window& window, const Gfx::Point& position, MouseButton button)
391{
392 move_to_front_and_make_active(window);
393 constexpr ResizeDirection direction_for_hot_area[3][3] = {
394 { ResizeDirection::UpLeft, ResizeDirection::Up, ResizeDirection::UpRight },
395 { ResizeDirection::Left, ResizeDirection::None, ResizeDirection::Right },
396 { ResizeDirection::DownLeft, ResizeDirection::Down, ResizeDirection::DownRight },
397 };
398 Gfx::Rect outer_rect = window.frame().rect();
399 ASSERT(outer_rect.contains(position));
400 int window_relative_x = position.x() - outer_rect.x();
401 int window_relative_y = position.y() - outer_rect.y();
402 int hot_area_row = min(2, window_relative_y / (outer_rect.height() / 3));
403 int hot_area_column = min(2, window_relative_x / (outer_rect.width() / 3));
404 m_resize_direction = direction_for_hot_area[hot_area_row][hot_area_column];
405 if (m_resize_direction == ResizeDirection::None) {
406 ASSERT(!m_resize_window);
407 return;
408 }
409
410#ifdef RESIZE_DEBUG
411 dbg() << "[WM] Begin resizing Window{" << &window << "}";
412#endif
413 m_resizing_mouse_button = button;
414 m_resize_window = window.make_weak_ptr();
415 ;
416 m_resize_origin = position;
417 m_resize_window_original_rect = window.rect();
418
419 invalidate(window);
420}
421
422void WindowManager::start_window_resize(Window& window, const MouseEvent& event)
423{
424 start_window_resize(window, event.position(), event.button());
425}
426
427bool WindowManager::process_ongoing_window_move(MouseEvent& event, Window*& hovered_window)
428{
429 if (!m_move_window)
430 return false;
431 if (event.type() == Event::MouseUp && event.button() == MouseButton::Left) {
432#ifdef MOVE_DEBUG
433 dbg() << "[WM] Finish moving Window{" << m_move_window << "}";
434#endif
435
436 invalidate(*m_move_window);
437 if (m_move_window->rect().contains(event.position()))
438 hovered_window = m_move_window;
439 if (m_move_window->is_resizable()) {
440 process_event_for_doubleclick(*m_move_window, event);
441 if (event.type() == Event::MouseDoubleClick) {
442#if defined(DOUBLECLICK_DEBUG)
443 dbg() << "[WM] Click up became doubleclick!";
444#endif
445 m_move_window->set_maximized(!m_move_window->is_maximized());
446 }
447 }
448 m_move_window = nullptr;
449 return true;
450 }
451 if (event.type() == Event::MouseMove) {
452
453#ifdef MOVE_DEBUG
454 dbg() << "[WM] Moving, origin: " << m_move_origin << ", now: " << event.position();
455 if (m_move_window->is_maximized()) {
456 dbg() << " [!] The window is still maximized. Not moving yet.";
457 }
458
459#endif
460
461 const int maximization_deadzone = 2;
462
463 if (m_move_window->is_maximized()) {
464 auto pixels_moved_from_start = event.position().pixels_moved(m_move_origin);
465 // dbg() << "[WM] " << pixels_moved_from_start << " moved since start of window move";
466 if (pixels_moved_from_start > 5) {
467 // dbg() << "[WM] de-maximizing window";
468 m_move_origin = event.position();
469 if (m_move_origin.y() <= maximization_deadzone)
470 return true;
471 auto width_before_resize = m_move_window->width();
472 m_move_window->set_maximized(false);
473 m_move_window->move_to(m_move_origin.x() - (m_move_window->width() * ((float)m_move_origin.x() / width_before_resize)), m_move_origin.y());
474 m_move_window_origin = m_move_window->position();
475 }
476 } else {
477 bool is_resizable = m_move_window->is_resizable();
478 auto pixels_moved_from_start = event.position().pixels_moved(m_move_origin);
479 const int tiling_deadzone = 5;
480
481 if (is_resizable && event.y() <= maximization_deadzone) {
482 m_move_window->set_tiled(WindowTileType::None);
483 m_move_window->set_maximized(true);
484 return true;
485 }
486 if (is_resizable && event.x() <= tiling_deadzone) {
487 m_move_window->set_tiled(WindowTileType::Left);
488 } else if (is_resizable && event.x() >= Screen::the().width() - tiling_deadzone) {
489 m_move_window->set_tiled(WindowTileType::Right);
490 } else if (pixels_moved_from_start > 5 || m_move_window->tiled() == WindowTileType::None) {
491 m_move_window->set_tiled(WindowTileType::None);
492 Gfx::Point pos = m_move_window_origin.translated(event.position() - m_move_origin);
493 m_move_window->set_position_without_repaint(pos);
494 if (m_move_window->rect().contains(event.position()))
495 hovered_window = m_move_window;
496 }
497 return true;
498 }
499 }
500 return false;
501}
502
503bool WindowManager::process_ongoing_window_resize(const MouseEvent& event, Window*& hovered_window)
504{
505 if (!m_resize_window)
506 return false;
507
508 if (event.type() == Event::MouseUp && event.button() == m_resizing_mouse_button) {
509#ifdef RESIZE_DEBUG
510 dbg() << "[WM] Finish resizing Window{" << m_resize_window << "}";
511#endif
512 Core::EventLoop::current().post_event(*m_resize_window, make<ResizeEvent>(m_resize_window->rect(), m_resize_window->rect()));
513 invalidate(*m_resize_window);
514 if (m_resize_window->rect().contains(event.position()))
515 hovered_window = m_resize_window;
516 m_resize_window = nullptr;
517 m_resizing_mouse_button = MouseButton::None;
518 return true;
519 }
520
521 if (event.type() != Event::MouseMove)
522 return false;
523
524 auto old_rect = m_resize_window->rect();
525
526 int diff_x = event.x() - m_resize_origin.x();
527 int diff_y = event.y() - m_resize_origin.y();
528
529 int change_w = 0;
530 int change_h = 0;
531
532 switch (m_resize_direction) {
533 case ResizeDirection::DownRight:
534 change_w = diff_x;
535 change_h = diff_y;
536 break;
537 case ResizeDirection::Right:
538 change_w = diff_x;
539 break;
540 case ResizeDirection::UpRight:
541 change_w = diff_x;
542 change_h = -diff_y;
543 break;
544 case ResizeDirection::Up:
545 change_h = -diff_y;
546 break;
547 case ResizeDirection::UpLeft:
548 change_w = -diff_x;
549 change_h = -diff_y;
550 break;
551 case ResizeDirection::Left:
552 change_w = -diff_x;
553 break;
554 case ResizeDirection::DownLeft:
555 change_w = -diff_x;
556 change_h = diff_y;
557 break;
558 case ResizeDirection::Down:
559 change_h = diff_y;
560 break;
561 default:
562 ASSERT_NOT_REACHED();
563 }
564
565 auto new_rect = m_resize_window_original_rect;
566
567 // First, size the new rect.
568 Gfx::Size minimum_size { 50, 50 };
569
570 new_rect.set_width(max(minimum_size.width(), new_rect.width() + change_w));
571 new_rect.set_height(max(minimum_size.height(), new_rect.height() + change_h));
572
573 if (!m_resize_window->size_increment().is_null()) {
574 int horizontal_incs = (new_rect.width() - m_resize_window->base_size().width()) / m_resize_window->size_increment().width();
575 new_rect.set_width(m_resize_window->base_size().width() + horizontal_incs * m_resize_window->size_increment().width());
576 int vertical_incs = (new_rect.height() - m_resize_window->base_size().height()) / m_resize_window->size_increment().height();
577 new_rect.set_height(m_resize_window->base_size().height() + vertical_incs * m_resize_window->size_increment().height());
578 }
579
580 // Second, set its position so that the sides of the window
581 // that end up moving are the same ones as the user is dragging,
582 // no matter which part of the logic above caused us to decide
583 // to resize by this much.
584 switch (m_resize_direction) {
585 case ResizeDirection::DownRight:
586 case ResizeDirection::Right:
587 case ResizeDirection::Down:
588 break;
589 case ResizeDirection::Left:
590 case ResizeDirection::Up:
591 case ResizeDirection::UpLeft:
592 new_rect.set_right_without_resize(m_resize_window_original_rect.right());
593 new_rect.set_bottom_without_resize(m_resize_window_original_rect.bottom());
594 break;
595 case ResizeDirection::UpRight:
596 new_rect.set_bottom_without_resize(m_resize_window_original_rect.bottom());
597 break;
598 case ResizeDirection::DownLeft:
599 new_rect.set_right_without_resize(m_resize_window_original_rect.right());
600 break;
601 default:
602 ASSERT_NOT_REACHED();
603 }
604
605 if (new_rect.contains(event.position()))
606 hovered_window = m_resize_window;
607
608 if (m_resize_window->rect() == new_rect)
609 return true;
610#ifdef RESIZE_DEBUG
611 dbg() << "[WM] Resizing, original: " << m_resize_window_original_rect << ", now: " << new_rect;
612#endif
613 m_resize_window->set_rect(new_rect);
614 Core::EventLoop::current().post_event(*m_resize_window, make<ResizeEvent>(old_rect, new_rect));
615 return true;
616}
617
618bool WindowManager::process_ongoing_drag(MouseEvent& event, Window*& hovered_window)
619{
620 if (!m_dnd_client)
621 return false;
622
623 if (event.type() == Event::MouseMove) {
624 // We didn't let go of the drag yet, see if we should send some drag move events..
625 for_each_visible_window_from_front_to_back([&](Window& window) {
626 if (!window.rect().contains(event.position()))
627 return IterationDecision::Continue;
628 hovered_window = &window;
629 auto translated_event = event.translated(-window.position());
630 translated_event.set_drag(true);
631 translated_event.set_drag_data_type(m_dnd_data_type);
632 deliver_mouse_event(window, translated_event);
633 return IterationDecision::Break;
634 });
635 }
636
637 if (!(event.type() == Event::MouseUp && event.button() == MouseButton::Left))
638 return true;
639
640 hovered_window = nullptr;
641 for_each_visible_window_from_front_to_back([&](auto& window) {
642 if (window.frame().rect().contains(event.position())) {
643 hovered_window = &window;
644 return IterationDecision::Break;
645 }
646 return IterationDecision::Continue;
647 });
648
649 if (hovered_window) {
650 m_dnd_client->post_message(Messages::WindowClient::DragAccepted());
651 if (hovered_window->client()) {
652 auto translated_event = event.translated(-hovered_window->position());
653 hovered_window->client()->post_message(Messages::WindowClient::DragDropped(hovered_window->window_id(), translated_event.position(), m_dnd_text, m_dnd_data_type, m_dnd_data));
654 }
655 } else {
656 m_dnd_client->post_message(Messages::WindowClient::DragCancelled());
657 }
658
659 end_dnd_drag();
660 return true;
661}
662
663void WindowManager::set_cursor_tracking_button(Button* button)
664{
665 m_cursor_tracking_button = button ? button->make_weak_ptr() : nullptr;
666}
667
668auto WindowManager::DoubleClickInfo::metadata_for_button(MouseButton button) -> ClickMetadata&
669{
670 switch (button) {
671 case MouseButton::Left:
672 return m_left;
673 case MouseButton::Right:
674 return m_right;
675 case MouseButton::Middle:
676 return m_middle;
677 default:
678 ASSERT_NOT_REACHED();
679 }
680}
681
682// #define DOUBLECLICK_DEBUG
683
684void WindowManager::process_event_for_doubleclick(Window& window, MouseEvent& event)
685{
686 // We only care about button presses (because otherwise it's not a doubleclick, duh!)
687 ASSERT(event.type() == Event::MouseUp);
688
689 if (&window != m_double_click_info.m_clicked_window) {
690 // we either haven't clicked anywhere, or we haven't clicked on this
691 // window. set the current click window, and reset the timers.
692#if defined(DOUBLECLICK_DEBUG)
693 dbg() << "Initial mouseup on window " << &window << " (previous was " << m_double_click_info.m_clicked_window << ')';
694#endif
695 m_double_click_info.m_clicked_window = window.make_weak_ptr();
696 m_double_click_info.reset();
697 }
698
699 auto& metadata = m_double_click_info.metadata_for_button(event.button());
700
701 // if the clock is invalid, we haven't clicked with this button on this
702 // window yet, so there's nothing to do.
703 if (!metadata.clock.is_valid()) {
704 metadata.clock.start();
705 } else {
706 int elapsed_since_last_click = metadata.clock.elapsed();
707 metadata.clock.start();
708 if (elapsed_since_last_click < m_double_click_speed) {
709 auto diff = event.position() - metadata.last_position;
710 auto distance_travelled_squared = diff.x() * diff.x() + diff.y() * diff.y();
711 if (distance_travelled_squared > (m_max_distance_for_double_click * m_max_distance_for_double_click)) {
712 // too far; try again
713 metadata.clock.start();
714 } else {
715#if defined(DOUBLECLICK_DEBUG)
716 dbg() << "Transforming MouseUp to MouseDoubleClick (" << elapsed_since_last_click << " < " << m_double_click_speed << ")!";
717#endif
718 event = MouseEvent(Event::MouseDoubleClick, event.position(), event.buttons(), event.button(), event.modifiers(), event.wheel_delta());
719 // invalidate this now we've delivered a doubleclick, otherwise
720 // tripleclick will deliver two doubleclick events (incorrectly).
721 metadata.clock = {};
722 }
723 } else {
724 // too slow; try again
725 metadata.clock.start();
726 }
727 }
728
729 metadata.last_position = event.position();
730}
731
732void WindowManager::deliver_mouse_event(Window& window, MouseEvent& event)
733{
734 window.dispatch_event(event);
735 if (event.type() == Event::MouseUp) {
736 process_event_for_doubleclick(window, event);
737 if (event.type() == Event::MouseDoubleClick)
738 window.dispatch_event(event);
739 }
740}
741
742void WindowManager::process_mouse_event(MouseEvent& event, Window*& hovered_window)
743{
744 hovered_window = nullptr;
745
746 if (process_ongoing_drag(event, hovered_window))
747 return;
748
749 if (process_ongoing_window_move(event, hovered_window))
750 return;
751
752 if (process_ongoing_window_resize(event, hovered_window))
753 return;
754
755 if (m_cursor_tracking_button)
756 return m_cursor_tracking_button->on_mouse_event(event.translated(-m_cursor_tracking_button->screen_rect().location()));
757
758 // This is quite hackish, but it's how the Button hover effect is implemented.
759 if (m_hovered_button && event.type() == Event::MouseMove)
760 m_hovered_button->on_mouse_event(event.translated(-m_hovered_button->screen_rect().location()));
761
762 HashTable<Window*> windows_who_received_mouse_event_due_to_cursor_tracking;
763
764 for (auto* window = m_windows_in_order.tail(); window; window = window->prev()) {
765 if (!window->global_cursor_tracking())
766 continue;
767 ASSERT(window->is_visible()); // Maybe this should be supported? Idk. Let's catch it and think about it later.
768 ASSERT(!window->is_minimized()); // Maybe this should also be supported? Idk.
769 windows_who_received_mouse_event_due_to_cursor_tracking.set(window);
770 auto translated_event = event.translated(-window->position());
771 deliver_mouse_event(*window, translated_event);
772 }
773
774 // FIXME: Now that the menubar has a dedicated window, is this special-casing really necessary?
775 if (MenuManager::the().has_open_menu() || (!active_window_is_modal() && menubar_rect().contains(event.position()))) {
776 clear_resize_candidate();
777 MenuManager::the().dispatch_event(event);
778 return;
779 }
780
781 Window* event_window_with_frame = nullptr;
782
783 if (m_active_input_window) {
784 // At this point, we have delivered the start of an input sequence to a
785 // client application. We must keep delivering to that client
786 // application until the input sequence is done.
787 //
788 // This prevents e.g. moving on one window out of the bounds starting
789 // a move in that other unrelated window, and other silly shenanigans.
790 if (!windows_who_received_mouse_event_due_to_cursor_tracking.contains(m_active_input_window)) {
791 auto translated_event = event.translated(-m_active_input_window->position());
792 deliver_mouse_event(*m_active_input_window, translated_event);
793 windows_who_received_mouse_event_due_to_cursor_tracking.set(m_active_input_window.ptr());
794 }
795 if (event.type() == Event::MouseUp && event.buttons() == 0) {
796 m_active_input_window = nullptr;
797 }
798
799 for_each_visible_window_from_front_to_back([&](auto& window) {
800 if (window.frame().rect().contains(event.position())) {
801 hovered_window = &window;
802 return IterationDecision::Break;
803 }
804 return IterationDecision::Continue;
805 });
806 } else {
807 for_each_visible_window_from_front_to_back([&](Window& window) {
808 auto window_frame_rect = window.frame().rect();
809 if (!window_frame_rect.contains(event.position()))
810 return IterationDecision::Continue;
811
812 if (&window != m_resize_candidate.ptr())
813 clear_resize_candidate();
814
815 // First check if we should initiate a move or resize (Logo+LMB or Logo+RMB).
816 // In those cases, the event is swallowed by the window manager.
817 if (window.is_movable()) {
818 if (!window.is_fullscreen() && m_keyboard_modifiers == Mod_Logo && event.type() == Event::MouseDown && event.button() == MouseButton::Left) {
819 hovered_window = &window;
820 start_window_move(window, event);
821 m_moved_or_resized_since_logo_keydown = true;
822 return IterationDecision::Break;
823 }
824 if (window.is_resizable() && m_keyboard_modifiers == Mod_Logo && event.type() == Event::MouseDown && event.button() == MouseButton::Right && !window.is_blocked_by_modal_window()) {
825 hovered_window = &window;
826 start_window_resize(window, event);
827 m_moved_or_resized_since_logo_keydown = true;
828 return IterationDecision::Break;
829 }
830 }
831
832 if (m_keyboard_modifiers == Mod_Logo && event.type() == Event::MouseWheel) {
833 float opacity_change = -event.wheel_delta() * 0.05f;
834 float new_opacity = window.opacity() + opacity_change;
835 if (new_opacity < 0.05f)
836 new_opacity = 0.05f;
837 if (new_opacity > 1.0f)
838 new_opacity = 1.0f;
839 window.set_opacity(new_opacity);
840 window.invalidate();
841 return IterationDecision::Break;
842 }
843
844 // Well okay, let's see if we're hitting the frame or the window inside the frame.
845 if (window.rect().contains(event.position())) {
846 if (window.type() == WindowType::Normal && event.type() == Event::MouseDown)
847 move_to_front_and_make_active(window);
848
849 hovered_window = &window;
850 if (!window.global_cursor_tracking() && !windows_who_received_mouse_event_due_to_cursor_tracking.contains(&window)) {
851 auto translated_event = event.translated(-window.position());
852 deliver_mouse_event(window, translated_event);
853 if (event.type() == Event::MouseDown) {
854 m_active_input_window = window.make_weak_ptr();
855 }
856 }
857 return IterationDecision::Break;
858 }
859
860 // We are hitting the frame, pass the event along to WindowFrame.
861 window.frame().on_mouse_event(event.translated(-window_frame_rect.location()));
862 event_window_with_frame = &window;
863 return IterationDecision::Break;
864 });
865
866 // Clicked outside of any window
867 if (!hovered_window && !event_window_with_frame && event.type() == Event::MouseDown)
868 set_active_window(nullptr);
869 }
870
871 if (event_window_with_frame != m_resize_candidate.ptr())
872 clear_resize_candidate();
873}
874
875void WindowManager::clear_resize_candidate()
876{
877 if (m_resize_candidate)
878 Compositor::the().invalidate_cursor();
879 m_resize_candidate = nullptr;
880}
881
882bool WindowManager::any_opaque_window_contains_rect(const Gfx::Rect& rect)
883{
884 bool found_containing_window = false;
885 for_each_visible_window_from_back_to_front([&](Window& window) {
886 if (window.is_minimized())
887 return IterationDecision::Continue;
888 if (window.opacity() < 1.0f)
889 return IterationDecision::Continue;
890 if (window.has_alpha_channel()) {
891 // FIXME: Just because the window has an alpha channel doesn't mean it's not opaque.
892 // Maybe there's some way we could know this?
893 return IterationDecision::Continue;
894 }
895 if (window.frame().rect().contains(rect)) {
896 found_containing_window = true;
897 return IterationDecision::Break;
898 }
899 return IterationDecision::Continue;
900 });
901 return found_containing_window;
902};
903
904bool WindowManager::any_opaque_window_above_this_one_contains_rect(const Window& a_window, const Gfx::Rect& rect)
905{
906 bool found_containing_window = false;
907 bool checking = false;
908 for_each_visible_window_from_back_to_front([&](Window& window) {
909 if (&window == &a_window) {
910 checking = true;
911 return IterationDecision::Continue;
912 }
913 if (!checking)
914 return IterationDecision::Continue;
915 if (!window.is_visible())
916 return IterationDecision::Continue;
917 if (window.is_minimized())
918 return IterationDecision::Continue;
919 if (window.opacity() < 1.0f)
920 return IterationDecision::Continue;
921 if (window.has_alpha_channel())
922 return IterationDecision::Continue;
923 if (window.frame().rect().contains(rect)) {
924 found_containing_window = true;
925 return IterationDecision::Break;
926 }
927 return IterationDecision::Continue;
928 });
929 return found_containing_window;
930};
931
932Gfx::Rect WindowManager::menubar_rect() const
933{
934 if (active_fullscreen_window())
935 return {};
936 return MenuManager::the().menubar_rect();
937}
938
939void WindowManager::event(Core::Event& event)
940{
941 if (static_cast<Event&>(event).is_mouse_event()) {
942 Window* hovered_window = nullptr;
943 process_mouse_event(static_cast<MouseEvent&>(event), hovered_window);
944 set_hovered_window(hovered_window);
945 return;
946 }
947
948 if (static_cast<Event&>(event).is_key_event()) {
949 auto& key_event = static_cast<const KeyEvent&>(event);
950 m_keyboard_modifiers = key_event.modifiers();
951
952 if (key_event.type() == Event::KeyDown && key_event.key() == Key_Escape && m_dnd_client) {
953 m_dnd_client->post_message(Messages::WindowClient::DragCancelled());
954 end_dnd_drag();
955 return;
956 }
957
958 if (key_event.key() == Key_Logo) {
959 if (key_event.type() == Event::KeyUp) {
960 if (!m_moved_or_resized_since_logo_keydown && !m_switcher.is_visible() && !m_move_window && !m_resize_window) {
961 MenuManager::the().toggle_system_menu();
962 return;
963 }
964
965 } else if (key_event.type() == Event::KeyDown) {
966 m_moved_or_resized_since_logo_keydown = false;
967 }
968 }
969
970 if (MenuManager::the().current_menu()) {
971 MenuManager::the().dispatch_event(event);
972 return;
973 }
974
975 if (key_event.type() == Event::KeyDown && ((key_event.modifiers() == Mod_Logo && key_event.key() == Key_Tab) || (key_event.modifiers() == (Mod_Logo | Mod_Shift) && key_event.key() == Key_Tab)))
976 m_switcher.show();
977 if (m_switcher.is_visible()) {
978 m_switcher.on_key_event(key_event);
979 return;
980 }
981
982 if (m_active_window) {
983 if (key_event.type() == Event::KeyDown && key_event.modifiers() == Mod_Logo) {
984 if (key_event.key() == Key_Down) {
985 m_moved_or_resized_since_logo_keydown = true;
986 if (m_active_window->is_resizable() && m_active_window->is_maximized()) {
987 m_active_window->set_maximized(false);
988 return;
989 }
990 if (m_active_window->is_minimizable())
991 m_active_window->set_minimized(true);
992 return;
993 }
994 if (m_active_window->is_resizable()) {
995 if (key_event.key() == Key_Up) {
996 m_moved_or_resized_since_logo_keydown = true;
997 m_active_window->set_maximized(!m_active_window->is_maximized());
998 return;
999 }
1000 if (key_event.key() == Key_Left) {
1001 m_moved_or_resized_since_logo_keydown = true;
1002 if (m_active_window->tiled() != WindowTileType::None) {
1003 m_active_window->set_tiled(WindowTileType::None);
1004 return;
1005 }
1006 if (m_active_window->is_maximized())
1007 m_active_window->set_maximized(false);
1008 m_active_window->set_tiled(WindowTileType::Left);
1009 return;
1010 }
1011 if (key_event.key() == Key_Right) {
1012 m_moved_or_resized_since_logo_keydown = true;
1013 if (m_active_window->tiled() != WindowTileType::None) {
1014 m_active_window->set_tiled(WindowTileType::None);
1015 return;
1016 }
1017 if (m_active_window->is_maximized())
1018 m_active_window->set_maximized(false);
1019 m_active_window->set_tiled(WindowTileType::Right);
1020 return;
1021 }
1022 }
1023 }
1024 m_active_window->dispatch_event(event);
1025 return;
1026 }
1027 }
1028
1029 Core::Object::event(event);
1030}
1031
1032void WindowManager::set_highlight_window(Window* window)
1033{
1034 if (window == m_highlight_window)
1035 return;
1036 if (auto* previous_highlight_window = m_highlight_window.ptr())
1037 invalidate(*previous_highlight_window);
1038 m_highlight_window = window ? window->make_weak_ptr() : nullptr;
1039 if (m_highlight_window)
1040 invalidate(*m_highlight_window);
1041}
1042
1043void WindowManager::set_active_window(Window* window)
1044{
1045 if (window && window->is_blocked_by_modal_window())
1046 return;
1047
1048 if (window && window->type() != WindowType::Normal)
1049 return;
1050
1051 if (window == m_active_window)
1052 return;
1053
1054 auto* previously_active_window = m_active_window.ptr();
1055
1056 ClientConnection* previously_active_client = nullptr;
1057 ClientConnection* active_client = nullptr;
1058
1059 if (previously_active_window) {
1060 previously_active_client = previously_active_window->client();
1061 Core::EventLoop::current().post_event(*previously_active_window, make<Event>(Event::WindowDeactivated));
1062 invalidate(*previously_active_window);
1063 m_active_window = nullptr;
1064 tell_wm_listeners_window_state_changed(*previously_active_window);
1065 }
1066
1067 if (window) {
1068 m_active_window = window->make_weak_ptr();
1069 active_client = m_active_window->client();
1070 Core::EventLoop::current().post_event(*m_active_window, make<Event>(Event::WindowActivated));
1071 invalidate(*m_active_window);
1072
1073 auto* client = window->client();
1074 ASSERT(client);
1075 MenuManager::the().set_current_menubar(client->app_menubar());
1076 tell_wm_listeners_window_state_changed(*m_active_window);
1077 } else {
1078 MenuManager::the().set_current_menubar(nullptr);
1079 }
1080
1081 if (active_client != previously_active_client) {
1082 if (previously_active_client)
1083 previously_active_client->deboost();
1084 if (active_client)
1085 active_client->boost();
1086 }
1087}
1088
1089void WindowManager::set_hovered_window(Window* window)
1090{
1091 if (m_hovered_window == window)
1092 return;
1093
1094 if (m_hovered_window)
1095 Core::EventLoop::current().post_event(*m_hovered_window, make<Event>(Event::WindowLeft));
1096
1097 m_hovered_window = window ? window->make_weak_ptr() : nullptr;
1098
1099 if (m_hovered_window)
1100 Core::EventLoop::current().post_event(*m_hovered_window, make<Event>(Event::WindowEntered));
1101}
1102
1103void WindowManager::invalidate()
1104{
1105 Compositor::the().invalidate();
1106}
1107
1108void WindowManager::invalidate(const Gfx::Rect& rect)
1109{
1110 Compositor::the().invalidate(rect);
1111}
1112
1113void WindowManager::invalidate(const Window& window)
1114{
1115 invalidate(window.frame().rect());
1116}
1117
1118void WindowManager::invalidate(const Window& window, const Gfx::Rect& rect)
1119{
1120 if (window.type() == WindowType::MenuApplet) {
1121 AppletManager::the().invalidate_applet(window, rect);
1122 return;
1123 }
1124
1125 if (rect.is_empty()) {
1126 invalidate(window);
1127 return;
1128 }
1129 auto outer_rect = window.frame().rect();
1130 auto inner_rect = rect;
1131 inner_rect.move_by(window.position());
1132 // FIXME: This seems slightly wrong; the inner rect shouldn't intersect the border part of the outer rect.
1133 inner_rect.intersect(outer_rect);
1134 invalidate(inner_rect);
1135}
1136
1137const ClientConnection* WindowManager::active_client() const
1138{
1139 if (m_active_window)
1140 return m_active_window->client();
1141 return nullptr;
1142}
1143
1144void WindowManager::notify_client_changed_app_menubar(ClientConnection& client)
1145{
1146 if (active_client() == &client)
1147 MenuManager::the().set_current_menubar(client.app_menubar());
1148}
1149
1150const Cursor& WindowManager::active_cursor() const
1151{
1152 if (m_dnd_client)
1153 return *m_drag_cursor;
1154
1155 if (m_move_window)
1156 return *m_move_cursor;
1157
1158 if (m_resize_window || m_resize_candidate) {
1159 switch (m_resize_direction) {
1160 case ResizeDirection::Up:
1161 case ResizeDirection::Down:
1162 return *m_resize_vertically_cursor;
1163 case ResizeDirection::Left:
1164 case ResizeDirection::Right:
1165 return *m_resize_horizontally_cursor;
1166 case ResizeDirection::UpLeft:
1167 case ResizeDirection::DownRight:
1168 return *m_resize_diagonally_tlbr_cursor;
1169 case ResizeDirection::UpRight:
1170 case ResizeDirection::DownLeft:
1171 return *m_resize_diagonally_bltr_cursor;
1172 case ResizeDirection::None:
1173 break;
1174 }
1175 }
1176
1177 if (m_hovered_window && m_hovered_window->override_cursor())
1178 return *m_hovered_window->override_cursor();
1179
1180 return *m_arrow_cursor;
1181}
1182
1183void WindowManager::set_hovered_button(Button* button)
1184{
1185 m_hovered_button = button ? button->make_weak_ptr() : nullptr;
1186}
1187
1188void WindowManager::set_resize_candidate(Window& window, ResizeDirection direction)
1189{
1190 m_resize_candidate = window.make_weak_ptr();
1191 m_resize_direction = direction;
1192}
1193
1194ResizeDirection WindowManager::resize_direction_of_window(const Window& window)
1195{
1196 if (&window != m_resize_window)
1197 return ResizeDirection::None;
1198 return m_resize_direction;
1199}
1200
1201Gfx::Rect WindowManager::maximized_window_rect(const Window& window) const
1202{
1203 Gfx::Rect rect = Screen::the().rect();
1204
1205 // Subtract window title bar (leaving the border)
1206 rect.set_y(rect.y() + window.frame().title_bar_rect().height());
1207 rect.set_height(rect.height() - window.frame().title_bar_rect().height());
1208
1209 // Subtract menu bar
1210 rect.set_y(rect.y() + menubar_rect().height());
1211 rect.set_height(rect.height() - menubar_rect().height());
1212
1213 // Subtract taskbar window height if present
1214 const_cast<WindowManager*>(this)->for_each_visible_window_of_type_from_back_to_front(WindowType::Taskbar, [&rect](Window& taskbar_window) {
1215 rect.set_height(rect.height() - taskbar_window.height());
1216 return IterationDecision::Break;
1217 });
1218
1219 return rect;
1220}
1221
1222void WindowManager::start_dnd_drag(ClientConnection& client, const String& text, Gfx::Bitmap* bitmap, const String& data_type, const String& data)
1223{
1224 ASSERT(!m_dnd_client);
1225 m_dnd_client = client.make_weak_ptr();
1226 m_dnd_text = text;
1227 m_dnd_bitmap = bitmap;
1228 m_dnd_data_type = data_type;
1229 m_dnd_data = data;
1230 Compositor::the().invalidate_cursor();
1231 m_active_input_window = nullptr;
1232}
1233
1234void WindowManager::end_dnd_drag()
1235{
1236 ASSERT(m_dnd_client);
1237 Compositor::the().invalidate_cursor();
1238 m_dnd_client = nullptr;
1239 m_dnd_text = {};
1240 m_dnd_bitmap = nullptr;
1241}
1242
1243Gfx::Rect WindowManager::dnd_rect() const
1244{
1245 int bitmap_width = m_dnd_bitmap ? m_dnd_bitmap->width() : 0;
1246 int bitmap_height = m_dnd_bitmap ? m_dnd_bitmap->height() : 0;
1247 int width = font().width(m_dnd_text) + bitmap_width;
1248 int height = max((int)font().glyph_height(), bitmap_height);
1249 auto location = Compositor::the().current_cursor_rect().center().translated(8, 8);
1250 return Gfx::Rect(location, { width, height }).inflated(4, 4);
1251}
1252
1253bool WindowManager::update_theme(String theme_path, String theme_name)
1254{
1255 auto new_theme = Gfx::load_system_theme(theme_path);
1256 if (!new_theme)
1257 return false;
1258 ASSERT(new_theme);
1259 Gfx::set_system_theme(*new_theme);
1260 m_palette = Gfx::PaletteImpl::create_with_shared_buffer(*new_theme);
1261 HashTable<ClientConnection*> notified_clients;
1262 for_each_window([&](Window& window) {
1263 if (window.client()) {
1264 if (!notified_clients.contains(window.client())) {
1265 window.client()->post_message(Messages::WindowClient::UpdateSystemTheme(Gfx::current_system_theme_buffer_id()));
1266 notified_clients.set(window.client());
1267 }
1268 }
1269 return IterationDecision::Continue;
1270 });
1271 MenuManager::the().did_change_theme();
1272 auto wm_config = Core::ConfigFile::open("/etc/WindowServer/WindowServer.ini");
1273 wm_config->write_entry("Theme", "Name", theme_name);
1274 wm_config->sync();
1275 invalidate();
1276 return true;
1277}
1278
1279}