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