Serenity Operating System
1/*
2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Debug.h>
8#include <AK/HashMap.h>
9#include <AK/IDAllocator.h>
10#include <AK/JsonObject.h>
11#include <AK/NeverDestroyed.h>
12#include <AK/ScopeGuard.h>
13#include <LibCore/EventLoop.h>
14#include <LibCore/MimeData.h>
15#include <LibGUI/Action.h>
16#include <LibGUI/Application.h>
17#include <LibGUI/ConnectionToWindowManagerServer.h>
18#include <LibGUI/ConnectionToWindowServer.h>
19#include <LibGUI/Desktop.h>
20#include <LibGUI/Event.h>
21#include <LibGUI/MenuItem.h>
22#include <LibGUI/Menubar.h>
23#include <LibGUI/Painter.h>
24#include <LibGUI/Widget.h>
25#include <LibGUI/Window.h>
26#include <LibGfx/Bitmap.h>
27#include <fcntl.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <unistd.h>
31
32namespace GUI {
33
34static i32 s_next_backing_store_serial;
35static IDAllocator s_window_id_allocator;
36
37class WindowBackingStore {
38public:
39 explicit WindowBackingStore(NonnullRefPtr<Gfx::Bitmap> bitmap)
40 : m_bitmap(move(bitmap))
41 , m_serial(++s_next_backing_store_serial)
42 , m_visible_size(m_bitmap->size())
43 {
44 }
45
46 Gfx::Bitmap& bitmap() { return *m_bitmap; }
47 Gfx::Bitmap const& bitmap() const { return *m_bitmap; }
48
49 Gfx::IntSize size() const { return m_bitmap->size(); }
50
51 i32 serial() const { return m_serial; }
52
53 Gfx::IntSize visible_size() const { return m_visible_size; }
54 void set_visible_size(Gfx::IntSize visible_size) { m_visible_size = visible_size; }
55
56private:
57 NonnullRefPtr<Gfx::Bitmap> m_bitmap;
58 const i32 m_serial;
59 Gfx::IntSize m_visible_size;
60};
61
62static NeverDestroyed<HashTable<Window*>> all_windows;
63static NeverDestroyed<HashMap<int, Window*>> reified_windows;
64
65Window* Window::from_window_id(int window_id)
66{
67 auto it = reified_windows->find(window_id);
68 if (it != reified_windows->end())
69 return (*it).value;
70 return nullptr;
71}
72
73Window::Window(Core::Object* parent)
74 : Core::Object(parent)
75 , m_menubar(Menubar::construct())
76{
77 if (parent)
78 set_window_mode(WindowMode::Passive);
79
80 all_windows->set(this);
81 m_rect_when_windowless = { -5000, -5000, 0, 0 };
82 m_title_when_windowless = "GUI::Window";
83
84 register_property(
85 "title",
86 [this] { return title(); },
87 [this](auto& value) {
88 set_title(value.to_deprecated_string());
89 return true;
90 });
91
92 register_property("visible", [this] { return is_visible(); });
93 register_property("active", [this] { return is_active(); });
94
95 REGISTER_BOOL_PROPERTY("minimizable", is_minimizable, set_minimizable);
96 REGISTER_BOOL_PROPERTY("resizable", is_resizable, set_resizable);
97 REGISTER_BOOL_PROPERTY("fullscreen", is_fullscreen, set_fullscreen);
98 REGISTER_RECT_PROPERTY("rect", rect, set_rect);
99 REGISTER_SIZE_PROPERTY("base_size", base_size, set_base_size);
100 REGISTER_SIZE_PROPERTY("size_increment", size_increment, set_size_increment);
101 REGISTER_BOOL_PROPERTY("obey_widget_min_size", is_obeying_widget_min_size, set_obey_widget_min_size);
102}
103
104Window::~Window()
105{
106 all_windows->remove(this);
107 hide();
108}
109
110void Window::close()
111{
112 hide();
113 if (on_close)
114 on_close();
115}
116
117void Window::move_to_front()
118{
119 if (!is_visible())
120 return;
121
122 ConnectionToWindowServer::the().async_move_window_to_front(m_window_id);
123}
124
125void Window::show()
126{
127 if (is_visible())
128 return;
129
130 auto* parent_window = find_parent_window();
131
132 m_window_id = s_window_id_allocator.allocate();
133
134 Gfx::IntRect launch_origin_rect;
135 if (auto* launch_origin_rect_string = getenv("__libgui_launch_origin_rect")) {
136 auto parts = StringView { launch_origin_rect_string, strlen(launch_origin_rect_string) }.split_view(',');
137 if (parts.size() == 4) {
138 launch_origin_rect = Gfx::IntRect {
139 parts[0].to_int().value_or(0),
140 parts[1].to_int().value_or(0),
141 parts[2].to_int().value_or(0),
142 parts[3].to_int().value_or(0),
143 };
144 }
145 unsetenv("__libgui_launch_origin_rect");
146 }
147
148 update_min_size();
149
150 ConnectionToWindowServer::the().async_create_window(
151 m_window_id,
152 m_rect_when_windowless,
153 !m_moved_by_client,
154 m_has_alpha_channel,
155 m_minimizable,
156 m_closeable,
157 m_resizable,
158 m_fullscreen,
159 m_frameless,
160 m_forced_shadow,
161 m_opacity_when_windowless,
162 m_alpha_hit_threshold,
163 m_base_size,
164 m_size_increment,
165 m_minimum_size_when_windowless,
166 m_resize_aspect_ratio,
167 (i32)m_window_type,
168 (i32)m_window_mode,
169 m_title_when_windowless,
170 parent_window ? parent_window->window_id() : 0,
171 launch_origin_rect);
172 m_visible = true;
173 m_visible_for_timer_purposes = true;
174
175 apply_icon();
176
177 m_menubar->for_each_menu([&](Menu& menu) {
178 menu.realize_menu_if_needed();
179 ConnectionToWindowServer::the().async_add_menu(m_window_id, menu.menu_id());
180 return IterationDecision::Continue;
181 });
182
183 set_maximized(m_maximized);
184 reified_windows->set(m_window_id, this);
185 Application::the()->did_create_window({});
186 update();
187}
188
189Window* Window::find_parent_window()
190{
191 for (auto* ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
192 if (is<Window>(ancestor))
193 return static_cast<Window*>(ancestor);
194 }
195 return nullptr;
196}
197
198void Window::server_did_destroy()
199{
200 reified_windows->remove(m_window_id);
201 m_window_id = 0;
202 m_visible = false;
203 m_pending_paint_event_rects.clear();
204 m_back_store = nullptr;
205 m_front_store = nullptr;
206 m_cursor = Gfx::StandardCursor::None;
207}
208
209void Window::hide()
210{
211 if (!is_visible())
212 return;
213
214 // NOTE: Don't bother asking WindowServer to destroy windows during application teardown.
215 // All our windows will be automatically garbage-collected by WindowServer anyway.
216 if (GUI::Application::in_teardown())
217 return;
218
219 m_rect_when_windowless = rect();
220
221 auto destroyed_window_ids = ConnectionToWindowServer::the().destroy_window(m_window_id);
222 server_did_destroy();
223
224 for (auto child_window_id : destroyed_window_ids) {
225 if (auto* window = Window::from_window_id(child_window_id)) {
226 window->server_did_destroy();
227 }
228 }
229
230 if (auto* app = Application::the()) {
231 bool app_has_visible_windows = false;
232 for (auto& window : *all_windows) {
233 if (window->is_visible()) {
234 app_has_visible_windows = true;
235 break;
236 }
237 }
238 if (!app_has_visible_windows)
239 app->did_delete_last_window({});
240 }
241}
242
243void Window::set_title(DeprecatedString title)
244{
245 m_title_when_windowless = move(title);
246 if (!is_visible())
247 return;
248 ConnectionToWindowServer::the().async_set_window_title(m_window_id, m_title_when_windowless);
249}
250
251DeprecatedString Window::title() const
252{
253 if (!is_visible())
254 return m_title_when_windowless;
255 return ConnectionToWindowServer::the().get_window_title(m_window_id);
256}
257
258Gfx::IntRect Window::applet_rect_on_screen() const
259{
260 VERIFY(m_window_type == WindowType::Applet);
261 return ConnectionToWindowServer::the().get_applet_rect_on_screen(m_window_id);
262}
263
264Gfx::IntRect Window::rect() const
265{
266 if (!is_visible())
267 return m_rect_when_windowless;
268 return ConnectionToWindowServer::the().get_window_rect(m_window_id);
269}
270
271void Window::set_rect(Gfx::IntRect const& a_rect)
272{
273 if (a_rect.location() != m_rect_when_windowless.location()) {
274 m_moved_by_client = true;
275 }
276
277 m_rect_when_windowless = a_rect;
278 if (!is_visible()) {
279 if (m_main_widget)
280 m_main_widget->resize(m_rect_when_windowless.size());
281 return;
282 }
283 auto window_rect = ConnectionToWindowServer::the().set_window_rect(m_window_id, a_rect);
284 if (m_back_store && m_back_store->size() != window_rect.size())
285 m_back_store = nullptr;
286 if (m_front_store && m_front_store->size() != window_rect.size())
287 m_front_store = nullptr;
288 if (m_main_widget)
289 m_main_widget->resize(window_rect.size());
290}
291
292Gfx::IntSize Window::minimum_size() const
293{
294 if (!is_visible())
295 return m_minimum_size_when_windowless;
296
297 return ConnectionToWindowServer::the().get_window_minimum_size(m_window_id);
298}
299
300void Window::set_minimum_size(Gfx::IntSize size)
301{
302 VERIFY(size.width() >= 0 && size.height() >= 0);
303 VERIFY(!is_obeying_widget_min_size());
304 m_minimum_size_when_windowless = size;
305
306 if (is_visible())
307 ConnectionToWindowServer::the().async_set_window_minimum_size(m_window_id, size);
308}
309
310void Window::center_on_screen()
311{
312 set_rect(rect().centered_within(Desktop::the().rect()));
313}
314
315void Window::center_within(Window const& other)
316{
317 if (this == &other)
318 return;
319 set_rect(rect().centered_within(other.rect()));
320}
321
322void Window::set_window_type(WindowType window_type)
323{
324 m_window_type = window_type;
325}
326
327void Window::set_window_mode(WindowMode mode)
328{
329 VERIFY(!is_visible());
330 m_window_mode = mode;
331}
332
333void Window::make_window_manager(unsigned event_mask)
334{
335 GUI::ConnectionToWindowManagerServer::the().async_set_event_mask(event_mask);
336 GUI::ConnectionToWindowManagerServer::the().async_set_manager_window(m_window_id);
337}
338
339bool Window::are_cursors_the_same(AK::Variant<Gfx::StandardCursor, NonnullRefPtr<Gfx::Bitmap const>> const& left, AK::Variant<Gfx::StandardCursor, NonnullRefPtr<Gfx::Bitmap const>> const& right) const
340{
341 if (left.has<Gfx::StandardCursor>() != right.has<Gfx::StandardCursor>())
342 return false;
343 if (left.has<Gfx::StandardCursor>())
344 return left.get<Gfx::StandardCursor>() == right.get<Gfx::StandardCursor>();
345 return left.get<NonnullRefPtr<Gfx::Bitmap const>>().ptr() == right.get<NonnullRefPtr<Gfx::Bitmap const>>().ptr();
346}
347
348void Window::set_cursor(Gfx::StandardCursor cursor)
349{
350 if (are_cursors_the_same(m_cursor, cursor))
351 return;
352 m_cursor = cursor;
353 update_cursor();
354}
355
356void Window::set_cursor(NonnullRefPtr<Gfx::Bitmap const> cursor)
357{
358 if (are_cursors_the_same(m_cursor, cursor))
359 return;
360 m_cursor = cursor;
361 update_cursor();
362}
363
364void Window::handle_drop_event(DropEvent& event)
365{
366 if (!m_main_widget)
367 return;
368 auto result = m_main_widget->hit_test(event.position());
369 auto local_event = make<DropEvent>(result.local_position, event.text(), event.mime_data());
370 VERIFY(result.widget);
371 result.widget->dispatch_event(*local_event, this);
372
373 Application::the()->set_drag_hovered_widget({}, nullptr);
374}
375
376void Window::handle_mouse_event(MouseEvent& event)
377{
378 if (!m_main_widget)
379 return;
380 auto result = m_main_widget->hit_test(event.position());
381 VERIFY(result.widget);
382
383 if (m_automatic_cursor_tracking_widget) {
384 auto window_relative_rect = m_automatic_cursor_tracking_widget->window_relative_rect();
385 Gfx::IntPoint local_point { event.x() - window_relative_rect.x(), event.y() - window_relative_rect.y() };
386 auto local_event = MouseEvent((Event::Type)event.type(), local_point, event.buttons(), event.button(), event.modifiers(), event.wheel_delta_x(), event.wheel_delta_y(), event.wheel_raw_delta_x(), event.wheel_raw_delta_y());
387 m_automatic_cursor_tracking_widget->dispatch_event(local_event, this);
388 if (event.buttons() == 0) {
389 m_automatic_cursor_tracking_widget = nullptr;
390 } else {
391 auto is_hovered = m_automatic_cursor_tracking_widget.ptr() == result.widget.ptr();
392 set_hovered_widget(is_hovered ? m_automatic_cursor_tracking_widget.ptr() : nullptr);
393 }
394 return;
395 }
396 set_hovered_widget(result.widget);
397 if (event.buttons() != 0 && !m_automatic_cursor_tracking_widget)
398 m_automatic_cursor_tracking_widget = *result.widget;
399 auto local_event = MouseEvent((Event::Type)event.type(), result.local_position, event.buttons(), event.button(), event.modifiers(), event.wheel_delta_x(), event.wheel_delta_y(), event.wheel_raw_delta_x(), event.wheel_raw_delta_y());
400 result.widget->dispatch_event(local_event, this);
401}
402
403Gfx::IntSize Window::backing_store_size(Gfx::IntSize window_size) const
404{
405 if (!m_resizing)
406 return window_size;
407
408 int const backing_margin_during_resize = 64;
409 return { window_size.width() + backing_margin_during_resize, window_size.height() + backing_margin_during_resize };
410}
411
412void Window::handle_multi_paint_event(MultiPaintEvent& event)
413{
414 if (!is_visible())
415 return;
416 if (!m_main_widget)
417 return;
418 auto rects = event.rects();
419 if (!m_pending_paint_event_rects.is_empty()) {
420 // It's possible that there had been some calls to update() that
421 // haven't been flushed. We can handle these right now, avoiding
422 // another round trip.
423 rects.extend(move(m_pending_paint_event_rects));
424 }
425 VERIFY(!rects.is_empty());
426
427 // Throw away our backing store if its size is different, and we've stopped resizing or double buffering is disabled.
428 // This ensures that we shrink the backing store after a resize, and that we do not get flickering artifacts when
429 // directly painting into a shared active backing store.
430 if (m_back_store && (!m_resizing || !m_double_buffering_enabled) && m_back_store->size() != event.window_size())
431 m_back_store = nullptr;
432
433 // Discard our backing store if it's unable to contain the new window size. Smaller is fine though, that prevents
434 // lots of backing store allocations during a resize.
435 if (m_back_store && !m_back_store->size().contains(event.window_size()))
436 m_back_store = nullptr;
437
438 bool created_new_backing_store = false;
439 if (!m_back_store) {
440 m_back_store = create_backing_store(backing_store_size(event.window_size())).release_value_but_fixme_should_propagate_errors();
441 created_new_backing_store = true;
442 } else if (m_double_buffering_enabled) {
443 bool was_purged = false;
444 bool bitmap_has_memory = m_back_store->bitmap().set_nonvolatile(was_purged);
445 if (!bitmap_has_memory) {
446 // We didn't have enough memory to make the bitmap non-volatile!
447 // Fall back to single-buffered mode for this window.
448 // FIXME: Once we have a way to listen for system memory pressure notifications,
449 // it would be cool to transition back into double-buffered mode once
450 // the coast is clear.
451 dbgln("Not enough memory to make backing store non-volatile. Falling back to single-buffered mode.");
452 m_double_buffering_enabled = false;
453 m_back_store = move(m_front_store);
454 created_new_backing_store = true;
455 } else if (was_purged) {
456 // The backing store bitmap was cleared, but it does have memory.
457 // Act as if it's a new backing store so the entire window gets repainted.
458 created_new_backing_store = true;
459 }
460 }
461
462 if (created_new_backing_store) {
463 rects.clear();
464 rects.append({ {}, event.window_size() });
465 }
466
467 for (auto& rect : rects) {
468 PaintEvent paint_event(rect);
469 m_main_widget->dispatch_event(paint_event, this);
470 }
471 m_back_store->set_visible_size(event.window_size());
472
473 if (m_double_buffering_enabled)
474 flip(rects);
475 else if (created_new_backing_store)
476 set_current_backing_store(*m_back_store, true);
477
478 if (is_visible())
479 ConnectionToWindowServer::the().async_did_finish_painting(m_window_id, rects);
480}
481
482void Window::propagate_shortcuts_up_to_application(KeyEvent& event, Widget* widget)
483{
484 VERIFY(event.type() == Event::KeyDown);
485 auto shortcut = Shortcut(event.modifiers(), event.key());
486 Action* action = nullptr;
487
488 if (widget) {
489 VERIFY(widget->window() == this);
490
491 do {
492 action = widget->action_for_shortcut(shortcut);
493 if (action)
494 break;
495
496 widget = widget->parent_widget();
497 } while (widget);
498 }
499
500 if (!action)
501 action = action_for_shortcut(shortcut);
502 if (!action)
503 action = Application::the()->action_for_shortcut(shortcut);
504
505 if (action) {
506 action->process_event(*this, event);
507 return;
508 }
509
510 event.ignore();
511}
512
513void Window::handle_key_event(KeyEvent& event)
514{
515 if (!m_focused_widget && event.type() == Event::KeyDown && event.key() == Key_Tab && !event.ctrl() && !event.alt() && !event.super()) {
516 focus_a_widget_if_possible(FocusSource::Keyboard);
517 }
518
519 if (m_default_return_key_widget && event.key() == Key_Return)
520 if (!m_focused_widget || !is<Button>(m_focused_widget.ptr()))
521 return default_return_key_widget()->dispatch_event(event, this);
522
523 if (m_focused_widget)
524 m_focused_widget->dispatch_event(event, this);
525 else if (m_main_widget)
526 m_main_widget->dispatch_event(event, this);
527
528 if (event.is_accepted())
529 return;
530
531 if (is_blocking() || is_popup())
532 return;
533
534 // Only process shortcuts if this is a keydown event.
535 if (event.type() == Event::KeyDown)
536 propagate_shortcuts_up_to_application(event, nullptr);
537}
538
539void Window::handle_resize_event(ResizeEvent& event)
540{
541 auto new_size = event.size();
542
543 // When the user is done resizing, we receive a last resize event with our actual size.
544 m_resizing = new_size != m_rect_when_windowless.size();
545
546 if (!m_pending_paint_event_rects.is_empty()) {
547 m_pending_paint_event_rects.clear_with_capacity();
548 m_pending_paint_event_rects.append({ {}, new_size });
549 }
550 m_rect_when_windowless.set_size(new_size);
551 if (m_main_widget)
552 m_main_widget->set_relative_rect({ {}, new_size });
553}
554
555void Window::handle_input_preemption_event(Core::Event& event)
556{
557 if (on_input_preemption_change)
558 on_input_preemption_change(event.type() == Event::WindowInputPreempted);
559 if (!m_focused_widget)
560 return;
561 m_focused_widget->set_focus_preempted(event.type() == Event::WindowInputPreempted);
562 m_focused_widget->update();
563}
564
565void Window::handle_became_active_or_inactive_event(Core::Event& event)
566{
567 if (event.type() == Event::WindowBecameActive)
568 Application::the()->window_did_become_active({}, *this);
569 else
570 Application::the()->window_did_become_inactive({}, *this);
571 if (on_active_window_change)
572 on_active_window_change(event.type() == Event::WindowBecameActive);
573 if (m_main_widget)
574 m_main_widget->dispatch_event(event, this);
575 if (m_focused_widget) {
576 if (event.type() == Event::WindowBecameActive)
577 m_focused_widget->set_focus_preempted(false);
578 m_focused_widget->update();
579 }
580}
581
582void Window::handle_close_request()
583{
584 if (on_close_request) {
585 if (on_close_request() == Window::CloseRequestDecision::StayOpen)
586 return;
587 }
588 close();
589}
590
591void Window::handle_theme_change_event(ThemeChangeEvent& event)
592{
593 if (!m_main_widget)
594 return;
595 auto dispatch_theme_change = [&](auto& widget, auto recursive) {
596 widget.dispatch_event(event, this);
597 widget.for_each_child_widget([&](auto& widget) -> IterationDecision {
598 widget.dispatch_event(event, this);
599 recursive(widget, recursive);
600 return IterationDecision::Continue;
601 });
602 };
603 dispatch_theme_change(*m_main_widget.ptr(), dispatch_theme_change);
604}
605
606void Window::handle_fonts_change_event(FontsChangeEvent& event)
607{
608 if (!m_main_widget)
609 return;
610 auto dispatch_fonts_change = [&](auto& widget, auto recursive) {
611 widget.dispatch_event(event, this);
612 widget.for_each_child_widget([&](auto& widget) -> IterationDecision {
613 widget.dispatch_event(event, this);
614 recursive(widget, recursive);
615 return IterationDecision::Continue;
616 });
617 };
618 dispatch_fonts_change(*m_main_widget.ptr(), dispatch_fonts_change);
619}
620
621void Window::handle_screen_rects_change_event(ScreenRectsChangeEvent& event)
622{
623 if (!m_main_widget)
624 return;
625 auto dispatch_screen_rects_change = [&](auto& widget, auto recursive) {
626 widget.dispatch_event(event, this);
627 widget.for_each_child_widget([&](auto& widget) -> IterationDecision {
628 widget.dispatch_event(event, this);
629 recursive(widget, recursive);
630 return IterationDecision::Continue;
631 });
632 };
633 dispatch_screen_rects_change(*m_main_widget.ptr(), dispatch_screen_rects_change);
634 screen_rects_change_event(event);
635}
636
637void Window::handle_applet_area_rect_change_event(AppletAreaRectChangeEvent& event)
638{
639 if (!m_main_widget)
640 return;
641 auto dispatch_applet_area_rect_change = [&](auto& widget, auto recursive) {
642 widget.dispatch_event(event, this);
643 widget.for_each_child_widget([&](auto& widget) -> IterationDecision {
644 widget.dispatch_event(event, this);
645 recursive(widget, recursive);
646 return IterationDecision::Continue;
647 });
648 };
649 dispatch_applet_area_rect_change(*m_main_widget.ptr(), dispatch_applet_area_rect_change);
650 applet_area_rect_change_event(event);
651}
652
653void Window::handle_drag_move_event(DragEvent& event)
654{
655 if (!m_main_widget)
656 return;
657 auto result = m_main_widget->hit_test(event.position());
658 VERIFY(result.widget);
659
660 Application::the()->set_drag_hovered_widget({}, result.widget, result.local_position, event.mime_types());
661
662 // NOTE: Setting the drag hovered widget may have executed arbitrary code, so re-check that the widget is still there.
663 if (!result.widget)
664 return;
665
666 if (result.widget->has_pending_drop()) {
667 DragEvent drag_move_event(static_cast<Event::Type>(event.type()), result.local_position, event.mime_types());
668 result.widget->dispatch_event(drag_move_event, this);
669 }
670}
671
672void Window::enter_event(Core::Event&)
673{
674}
675
676void Window::leave_event(Core::Event&)
677{
678}
679
680void Window::handle_entered_event(Core::Event& event)
681{
682 enter_event(event);
683}
684
685void Window::handle_left_event(Core::Event& event)
686{
687 set_hovered_widget(nullptr);
688 Application::the()->set_drag_hovered_widget({}, nullptr);
689 leave_event(event);
690}
691
692void Window::event(Core::Event& event)
693{
694 ScopeGuard guard([&] {
695 // Accept the event so it doesn't bubble up to parent windows!
696 event.accept();
697 });
698 if (event.type() == Event::Drop)
699 return handle_drop_event(static_cast<DropEvent&>(event));
700
701 if (event.type() == Event::MouseUp || event.type() == Event::MouseDown || event.type() == Event::MouseDoubleClick || event.type() == Event::MouseMove || event.type() == Event::MouseWheel)
702 return handle_mouse_event(static_cast<MouseEvent&>(event));
703
704 if (event.type() == Event::MultiPaint)
705 return handle_multi_paint_event(static_cast<MultiPaintEvent&>(event));
706
707 if (event.type() == Event::KeyUp || event.type() == Event::KeyDown)
708 return handle_key_event(static_cast<KeyEvent&>(event));
709
710 if (event.type() == Event::WindowBecameActive || event.type() == Event::WindowBecameInactive)
711 return handle_became_active_or_inactive_event(event);
712
713 if (event.type() == Event::WindowInputPreempted || event.type() == Event::WindowInputRestored)
714 return handle_input_preemption_event(event);
715
716 if (event.type() == Event::WindowCloseRequest)
717 return handle_close_request();
718
719 if (event.type() == Event::WindowEntered)
720 return handle_entered_event(event);
721
722 if (event.type() == Event::WindowLeft)
723 return handle_left_event(event);
724
725 if (event.type() == Event::Resize)
726 return handle_resize_event(static_cast<ResizeEvent&>(event));
727
728 if (event.type() > Event::__Begin_WM_Events && event.type() < Event::__End_WM_Events)
729 return wm_event(static_cast<WMEvent&>(event));
730
731 if (event.type() == Event::DragMove)
732 return handle_drag_move_event(static_cast<DragEvent&>(event));
733
734 if (event.type() == Event::ThemeChange)
735 return handle_theme_change_event(static_cast<ThemeChangeEvent&>(event));
736
737 if (event.type() == Event::FontsChange)
738 return handle_fonts_change_event(static_cast<FontsChangeEvent&>(event));
739
740 if (event.type() == Event::ScreenRectsChange)
741 return handle_screen_rects_change_event(static_cast<ScreenRectsChangeEvent&>(event));
742
743 if (event.type() == Event::AppletAreaRectChange)
744 return handle_applet_area_rect_change_event(static_cast<AppletAreaRectChangeEvent&>(event));
745
746 Core::Object::event(event);
747}
748
749bool Window::is_visible() const
750{
751 return m_visible;
752}
753
754void Window::update()
755{
756 auto rect = this->rect();
757 update({ 0, 0, rect.width(), rect.height() });
758}
759
760void Window::force_update()
761{
762 if (!is_visible())
763 return;
764 auto rect = this->rect();
765 ConnectionToWindowServer::the().async_invalidate_rect(m_window_id, { { 0, 0, rect.width(), rect.height() } }, true);
766}
767
768void Window::update(Gfx::IntRect const& a_rect)
769{
770 if (!is_visible())
771 return;
772
773 for (auto& pending_rect : m_pending_paint_event_rects) {
774 if (pending_rect.contains(a_rect)) {
775 dbgln_if(UPDATE_COALESCING_DEBUG, "Ignoring {} since it's contained by pending rect {}", a_rect, pending_rect);
776 return;
777 }
778 }
779
780 if (m_pending_paint_event_rects.is_empty()) {
781 deferred_invoke([this] {
782 auto rects = move(m_pending_paint_event_rects);
783 if (rects.is_empty())
784 return;
785 ConnectionToWindowServer::the().async_invalidate_rect(m_window_id, rects, false);
786 });
787 }
788 m_pending_paint_event_rects.append(a_rect);
789}
790
791void Window::set_main_widget(Widget* widget)
792{
793 if (m_main_widget == widget)
794 return;
795 if (m_main_widget) {
796 m_main_widget->set_window(nullptr);
797 remove_child(*m_main_widget);
798 }
799 m_main_widget = widget;
800 if (m_main_widget) {
801 add_child(*widget);
802 auto new_window_rect = rect();
803 auto new_widget_min_size = m_main_widget->effective_min_size();
804 new_window_rect.set_width(max(new_window_rect.width(), MUST(new_widget_min_size.width().shrink_value())));
805 new_window_rect.set_height(max(new_window_rect.height(), MUST(new_widget_min_size.height().shrink_value())));
806 set_rect(new_window_rect);
807 m_main_widget->set_relative_rect({ {}, new_window_rect.size() });
808 m_main_widget->set_window(this);
809 if (m_main_widget->focus_policy() != FocusPolicy::NoFocus)
810 m_main_widget->set_focus(true);
811 }
812 update();
813}
814
815void Window::set_default_return_key_widget(Widget* widget)
816{
817 if (m_default_return_key_widget == widget)
818 return;
819 m_default_return_key_widget = widget;
820}
821
822void Window::set_focused_widget(Widget* widget, FocusSource source)
823{
824 if (m_focused_widget == widget)
825 return;
826
827 WeakPtr<Widget> previously_focused_widget = m_focused_widget;
828 m_focused_widget = widget;
829
830 if (!m_focused_widget && m_previously_focused_widget)
831 m_focused_widget = m_previously_focused_widget;
832
833 if (m_default_return_key_widget && m_default_return_key_widget->on_focus_change)
834 m_default_return_key_widget->on_focus_change(m_default_return_key_widget->is_focused(), source);
835
836 if (previously_focused_widget) {
837 Core::EventLoop::current().post_event(*previously_focused_widget, make<FocusEvent>(Event::FocusOut, source));
838 previously_focused_widget->update();
839 if (previously_focused_widget && previously_focused_widget->on_focus_change)
840 previously_focused_widget->on_focus_change(previously_focused_widget->is_focused(), source);
841 m_previously_focused_widget = previously_focused_widget;
842 }
843 if (m_focused_widget) {
844 Core::EventLoop::current().post_event(*m_focused_widget, make<FocusEvent>(Event::FocusIn, source));
845 m_focused_widget->update();
846 if (m_focused_widget && m_focused_widget->on_focus_change)
847 m_focused_widget->on_focus_change(m_focused_widget->is_focused(), source);
848 }
849}
850
851void Window::set_automatic_cursor_tracking_widget(Widget* widget)
852{
853 if (widget == m_automatic_cursor_tracking_widget)
854 return;
855 m_automatic_cursor_tracking_widget = widget;
856}
857
858void Window::set_has_alpha_channel(bool value)
859{
860 if (m_has_alpha_channel == value)
861 return;
862 m_has_alpha_channel = value;
863 if (!is_visible())
864 return;
865
866 m_pending_paint_event_rects.clear();
867 m_back_store = nullptr;
868 m_front_store = nullptr;
869
870 ConnectionToWindowServer::the().async_set_window_has_alpha_channel(m_window_id, value);
871 update();
872}
873
874void Window::set_double_buffering_enabled(bool value)
875{
876 VERIFY(!is_visible());
877 m_double_buffering_enabled = value;
878}
879
880void Window::set_opacity(float opacity)
881{
882 m_opacity_when_windowless = opacity;
883 if (!is_visible())
884 return;
885 ConnectionToWindowServer::the().async_set_window_opacity(m_window_id, opacity);
886}
887
888void Window::set_alpha_hit_threshold(float threshold)
889{
890 if (threshold < 0.0f)
891 threshold = 0.0f;
892 else if (threshold > 1.0f)
893 threshold = 1.0f;
894 if (m_alpha_hit_threshold == threshold)
895 return;
896 m_alpha_hit_threshold = threshold;
897 if (!is_visible())
898 return;
899 ConnectionToWindowServer::the().async_set_window_alpha_hit_threshold(m_window_id, threshold);
900}
901
902void Window::set_hovered_widget(Widget* widget)
903{
904 if (widget == m_hovered_widget)
905 return;
906
907 if (m_hovered_widget)
908 Core::EventLoop::current().post_event(*m_hovered_widget, make<Event>(Event::Leave));
909
910 m_hovered_widget = widget;
911
912 if (m_hovered_widget)
913 Core::EventLoop::current().post_event(*m_hovered_widget, make<Event>(Event::Enter));
914
915 auto* app = Application::the();
916 if (app && app->hover_debugging_enabled())
917 update();
918}
919
920void Window::set_current_backing_store(WindowBackingStore& backing_store, bool flush_immediately) const
921{
922 auto& bitmap = backing_store.bitmap();
923 ConnectionToWindowServer::the().set_window_backing_store(
924 m_window_id,
925 32,
926 bitmap.pitch(),
927 bitmap.anonymous_buffer().fd(),
928 backing_store.serial(),
929 bitmap.has_alpha_channel(),
930 bitmap.size(),
931 backing_store.visible_size(),
932 flush_immediately);
933}
934
935void Window::flip(Vector<Gfx::IntRect, 32> const& dirty_rects)
936{
937 swap(m_front_store, m_back_store);
938
939 set_current_backing_store(*m_front_store);
940
941 if (!m_back_store || m_back_store->size() != m_front_store->size()) {
942 m_back_store = create_backing_store(m_front_store->size()).release_value_but_fixme_should_propagate_errors();
943 memcpy(m_back_store->bitmap().scanline(0), m_front_store->bitmap().scanline(0), m_front_store->bitmap().size_in_bytes());
944 m_back_store->bitmap().set_volatile();
945 return;
946 }
947
948 // Copy whatever was painted from the front to the back.
949 Painter painter(m_back_store->bitmap());
950 for (auto& dirty_rect : dirty_rects)
951 painter.blit(dirty_rect.location(), m_front_store->bitmap(), dirty_rect, 1.0f, false);
952
953 m_back_store->bitmap().set_volatile();
954}
955
956ErrorOr<NonnullOwnPtr<WindowBackingStore>> Window::create_backing_store(Gfx::IntSize size)
957{
958 auto format = m_has_alpha_channel ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888;
959
960 VERIFY(!size.is_empty());
961 size_t pitch = Gfx::Bitmap::minimum_pitch(size.width(), format);
962 size_t size_in_bytes = size.height() * pitch;
963
964 auto buffer = TRY(Core::AnonymousBuffer::create_with_size(round_up_to_power_of_two(size_in_bytes, PAGE_SIZE)));
965
966 // FIXME: Plumb scale factor here eventually.
967 auto bitmap = TRY(Gfx::Bitmap::create_with_anonymous_buffer(format, buffer, size, 1, {}));
968 return make<WindowBackingStore>(bitmap);
969}
970
971void Window::wm_event(WMEvent&)
972{
973}
974
975void Window::screen_rects_change_event(ScreenRectsChangeEvent&)
976{
977}
978
979void Window::applet_area_rect_change_event(AppletAreaRectChangeEvent&)
980{
981}
982
983void Window::set_icon(Gfx::Bitmap const* icon)
984{
985 if (m_icon == icon)
986 return;
987
988 Gfx::IntSize icon_size = icon ? icon->size() : Gfx::IntSize(16, 16);
989
990 auto new_icon = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, icon_size).release_value_but_fixme_should_propagate_errors();
991 if (icon) {
992 Painter painter(*new_icon);
993 painter.blit({ 0, 0 }, *icon, icon->rect());
994 }
995 m_icon = move(new_icon);
996
997 apply_icon();
998}
999
1000void Window::apply_icon()
1001{
1002 if (!m_icon)
1003 return;
1004
1005 if (!is_visible())
1006 return;
1007
1008 ConnectionToWindowServer::the().async_set_window_icon_bitmap(m_window_id, m_icon->to_shareable_bitmap());
1009}
1010
1011void Window::start_interactive_resize(ResizeDirection resize_direction)
1012{
1013 ConnectionToWindowServer::the().async_start_window_resize(m_window_id, (i32)resize_direction);
1014}
1015
1016Vector<Widget&> Window::focusable_widgets(FocusSource source) const
1017{
1018 if (!m_main_widget)
1019 return {};
1020
1021 HashTable<Widget*> seen_widgets;
1022 Vector<Widget&> collected_widgets;
1023
1024 Function<void(Widget&)> collect_focusable_widgets = [&](auto& widget) {
1025 bool widget_accepts_focus = false;
1026 switch (source) {
1027 case FocusSource::Keyboard:
1028 widget_accepts_focus = has_flag(widget.focus_policy(), FocusPolicy::TabFocus);
1029 break;
1030 case FocusSource::Mouse:
1031 widget_accepts_focus = has_flag(widget.focus_policy(), FocusPolicy::ClickFocus);
1032 break;
1033 case FocusSource::Programmatic:
1034 widget_accepts_focus = widget.focus_policy() != FocusPolicy::NoFocus;
1035 break;
1036 }
1037
1038 if (widget_accepts_focus) {
1039 auto& effective_focus_widget = widget.focus_proxy() ? *widget.focus_proxy() : widget;
1040 if (seen_widgets.set(&effective_focus_widget) == AK::HashSetResult::InsertedNewEntry)
1041 collected_widgets.append(effective_focus_widget);
1042 }
1043 widget.for_each_child_widget([&](auto& child) {
1044 if (!child.is_visible())
1045 return IterationDecision::Continue;
1046 if (!child.is_enabled())
1047 return IterationDecision::Continue;
1048 if (!child.is_auto_focusable())
1049 return IterationDecision::Continue;
1050 collect_focusable_widgets(child);
1051 return IterationDecision::Continue;
1052 });
1053 };
1054
1055 collect_focusable_widgets(const_cast<Widget&>(*m_main_widget));
1056 return collected_widgets;
1057}
1058
1059void Window::set_fullscreen(bool fullscreen)
1060{
1061 if (m_fullscreen == fullscreen)
1062 return;
1063 m_fullscreen = fullscreen;
1064 if (!is_visible())
1065 return;
1066 ConnectionToWindowServer::the().async_set_fullscreen(m_window_id, fullscreen);
1067}
1068
1069void Window::set_frameless(bool frameless)
1070{
1071 if (m_frameless == frameless)
1072 return;
1073 m_frameless = frameless;
1074 if (!is_visible())
1075 return;
1076 ConnectionToWindowServer::the().async_set_frameless(m_window_id, frameless);
1077
1078 if (!frameless)
1079 apply_icon();
1080}
1081
1082void Window::set_forced_shadow(bool shadow)
1083{
1084 if (m_forced_shadow == shadow)
1085 return;
1086 m_forced_shadow = shadow;
1087 if (!is_visible())
1088 return;
1089 ConnectionToWindowServer::the().async_set_forced_shadow(m_window_id, shadow);
1090}
1091
1092void Window::set_obey_widget_min_size(bool obey_widget_min_size)
1093{
1094 if (m_obey_widget_min_size != obey_widget_min_size) {
1095 m_obey_widget_min_size = obey_widget_min_size;
1096 schedule_relayout();
1097 }
1098}
1099
1100void Window::set_maximized(bool maximized)
1101{
1102 m_maximized = maximized;
1103 if (!is_visible())
1104 return;
1105
1106 ConnectionToWindowServer::the().async_set_maximized(m_window_id, maximized);
1107}
1108
1109void Window::set_minimized(bool minimized)
1110{
1111 if (!is_minimizable())
1112 return;
1113
1114 m_minimized = minimized;
1115 if (!is_visible())
1116 return;
1117
1118 ConnectionToWindowServer::the().async_set_minimized(m_window_id, minimized);
1119}
1120
1121void Window::update_min_size()
1122{
1123 if (main_widget()) {
1124 main_widget()->do_layout();
1125 if (m_obey_widget_min_size) {
1126 auto min_size = main_widget()->effective_min_size();
1127 Gfx::IntSize size = { MUST(min_size.width().shrink_value()), MUST(min_size.height().shrink_value()) };
1128 m_minimum_size_when_windowless = size;
1129 if (is_visible())
1130 ConnectionToWindowServer::the().async_set_window_minimum_size(m_window_id, size);
1131 }
1132 }
1133}
1134
1135void Window::schedule_relayout()
1136{
1137 if (m_layout_pending || !is_visible())
1138 return;
1139 m_layout_pending = true;
1140 deferred_invoke([this] {
1141 update_min_size();
1142 update();
1143 m_layout_pending = false;
1144 });
1145}
1146
1147void Window::refresh_system_theme()
1148{
1149 ConnectionToWindowServer::the().async_refresh_system_theme();
1150}
1151
1152void Window::for_each_window(Badge<ConnectionToWindowServer>, Function<void(Window&)> callback)
1153{
1154 for (auto& e : *reified_windows) {
1155 VERIFY(e.value);
1156 callback(*e.value);
1157 }
1158}
1159
1160void Window::update_all_windows(Badge<ConnectionToWindowServer>)
1161{
1162 for (auto& e : *reified_windows) {
1163 e.value->force_update();
1164 }
1165}
1166
1167void Window::notify_state_changed(Badge<ConnectionToWindowServer>, bool minimized, bool maximized, bool occluded)
1168{
1169 m_visible_for_timer_purposes = !minimized && !occluded;
1170
1171 m_maximized = maximized;
1172
1173 // When double buffering is enabled, minimization/occlusion means we can mark the front bitmap volatile (in addition to the back bitmap.)
1174 // When double buffering is disabled, there is only the back bitmap (which we can now mark volatile!)
1175 auto& store = m_double_buffering_enabled ? m_front_store : m_back_store;
1176 if (!store)
1177 return;
1178 if (minimized || occluded) {
1179 store->bitmap().set_volatile();
1180 } else {
1181 bool was_purged = false;
1182 bool bitmap_has_memory = store->bitmap().set_nonvolatile(was_purged);
1183 if (!bitmap_has_memory) {
1184 // Not enough memory to make the bitmap non-volatile. Lose the bitmap and schedule an update.
1185 // Let the paint system figure out what to do.
1186 store = nullptr;
1187 update();
1188 } else if (was_purged) {
1189 // The bitmap memory was purged by the kernel, but we have all-new zero-filled pages.
1190 // Schedule an update to regenerate the bitmap.
1191 update();
1192 }
1193 }
1194}
1195
1196Action* Window::action_for_shortcut(Shortcut const& shortcut)
1197{
1198 return Action::find_action_for_shortcut(*this, shortcut);
1199}
1200
1201void Window::set_base_size(Gfx::IntSize base_size)
1202{
1203 if (m_base_size == base_size)
1204 return;
1205 m_base_size = base_size;
1206 if (is_visible())
1207 ConnectionToWindowServer::the().async_set_window_base_size_and_size_increment(m_window_id, m_base_size, m_size_increment);
1208}
1209
1210void Window::set_size_increment(Gfx::IntSize size_increment)
1211{
1212 if (m_size_increment == size_increment)
1213 return;
1214 m_size_increment = size_increment;
1215 if (is_visible())
1216 ConnectionToWindowServer::the().async_set_window_base_size_and_size_increment(m_window_id, m_base_size, m_size_increment);
1217}
1218
1219void Window::set_resize_aspect_ratio(Optional<Gfx::IntSize> const& ratio)
1220{
1221 if (m_resize_aspect_ratio == ratio)
1222 return;
1223
1224 m_resize_aspect_ratio = ratio;
1225 if (is_visible())
1226 ConnectionToWindowServer::the().async_set_window_resize_aspect_ratio(m_window_id, m_resize_aspect_ratio);
1227}
1228
1229void Window::did_add_widget(Badge<Widget>, Widget&)
1230{
1231 if (!m_focused_widget)
1232 focus_a_widget_if_possible(FocusSource::Mouse);
1233}
1234
1235void Window::did_remove_widget(Badge<Widget>, Widget& widget)
1236{
1237 if (m_focused_widget == &widget)
1238 m_focused_widget = nullptr;
1239 if (m_hovered_widget == &widget)
1240 m_hovered_widget = nullptr;
1241 if (m_automatic_cursor_tracking_widget == &widget)
1242 m_automatic_cursor_tracking_widget = nullptr;
1243}
1244
1245void Window::set_progress(Optional<int> progress)
1246{
1247 VERIFY(m_window_id);
1248 ConnectionToWindowServer::the().async_set_window_progress(m_window_id, progress);
1249}
1250
1251void Window::update_cursor()
1252{
1253 auto new_cursor = m_cursor;
1254
1255 auto is_usable_cursor = [](auto& cursor) {
1256 return cursor.template has<NonnullRefPtr<Gfx::Bitmap const>>() || cursor.template get<Gfx::StandardCursor>() != Gfx::StandardCursor::None;
1257 };
1258
1259 // NOTE: If there's an automatic cursor tracking widget, we retain its cursor until tracking stops.
1260 if (auto widget = m_automatic_cursor_tracking_widget) {
1261 if (is_usable_cursor(widget->override_cursor()))
1262 new_cursor = widget->override_cursor();
1263 } else if (auto widget = m_hovered_widget) {
1264 if (is_usable_cursor(widget->override_cursor()))
1265 new_cursor = widget->override_cursor();
1266 }
1267
1268 if (are_cursors_the_same(m_effective_cursor, new_cursor))
1269 return;
1270 m_effective_cursor = new_cursor;
1271
1272 if (new_cursor.has<NonnullRefPtr<Gfx::Bitmap const>>())
1273 ConnectionToWindowServer::the().async_set_window_custom_cursor(m_window_id, new_cursor.get<NonnullRefPtr<Gfx::Bitmap const>>()->to_shareable_bitmap());
1274 else
1275 ConnectionToWindowServer::the().async_set_window_cursor(m_window_id, (u32)new_cursor.get<Gfx::StandardCursor>());
1276}
1277
1278void Window::focus_a_widget_if_possible(FocusSource source)
1279{
1280 auto focusable_widgets = this->focusable_widgets(source);
1281 if (!focusable_widgets.is_empty())
1282 set_focused_widget(&focusable_widgets[0], source);
1283}
1284
1285void Window::did_disable_focused_widget(Badge<Widget>)
1286{
1287 focus_a_widget_if_possible(FocusSource::Mouse);
1288}
1289
1290bool Window::is_active() const
1291{
1292 VERIFY(Application::the());
1293 return this == Application::the()->active_window();
1294}
1295
1296Gfx::Bitmap* Window::back_bitmap()
1297{
1298 return m_back_store ? &m_back_store->bitmap() : nullptr;
1299}
1300
1301ErrorOr<void> Window::try_add_menu(NonnullRefPtr<Menu> menu)
1302{
1303 TRY(m_menubar->try_add_menu({}, move(menu)));
1304 if (m_window_id) {
1305 menu->realize_menu_if_needed();
1306 ConnectionToWindowServer::the().async_add_menu(m_window_id, menu->menu_id());
1307 }
1308 return {};
1309}
1310
1311ErrorOr<NonnullRefPtr<Menu>> Window::try_add_menu(DeprecatedString name)
1312{
1313 auto menu = TRY(m_menubar->try_add_menu({}, move(name)));
1314 if (m_window_id) {
1315 menu->realize_menu_if_needed();
1316 ConnectionToWindowServer::the().async_add_menu(m_window_id, menu->menu_id());
1317 }
1318 return menu;
1319}
1320
1321Menu& Window::add_menu(DeprecatedString name)
1322{
1323 auto menu = MUST(try_add_menu(move(name)));
1324 return *menu;
1325}
1326
1327void Window::flash_menubar_menu_for(MenuItem const& menu_item)
1328{
1329 if (!Desktop::the().system_effects().flash_menus())
1330 return;
1331 auto menu_id = menu_item.menu_id();
1332 if (menu_id < 0)
1333 return;
1334
1335 ConnectionToWindowServer::the().async_flash_menubar_menu(m_window_id, menu_id);
1336}
1337
1338bool Window::is_modified() const
1339{
1340 if (!m_window_id)
1341 return false;
1342 return ConnectionToWindowServer::the().is_window_modified(m_window_id);
1343}
1344
1345void Window::set_modified(bool modified)
1346{
1347 if (!m_window_id)
1348 return;
1349 ConnectionToWindowServer::the().async_set_window_modified(m_window_id, modified);
1350}
1351
1352void Window::flush_pending_paints_immediately()
1353{
1354 if (!m_window_id)
1355 return;
1356 if (m_pending_paint_event_rects.is_empty())
1357 return;
1358 MultiPaintEvent paint_event(move(m_pending_paint_event_rects), size());
1359 handle_multi_paint_event(paint_event);
1360}
1361
1362void Window::set_always_on_top(bool always_on_top)
1363{
1364 if (!m_window_id)
1365 return;
1366 ConnectionToWindowServer::the().set_always_on_top(m_window_id, always_on_top);
1367}
1368
1369}