1/*
2 * Copyright (C) 2020-2022 The opuntiaOS Project Authors.
3 * + Contributed by Nikita Melekhin <nimelehin@gmail.com>
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9#include "WindowManager.h"
10#include "../../shared/MessageContent/MouseAction.h"
11#include "../Devices/Screen.h"
12#include "../Managers/CursorManager.h"
13#include <libfoundation/KeyboardMapping.h>
14#include <libfoundation/Logger.h>
15
16// #define WM_DEBUG
17
18namespace WinServer {
19
20static PopupData WindowPopupData {};
21
22WindowManager* s_WinServer_WindowManager_the = nullptr;
23
24WindowManager::WindowManager()
25 : m_screen(Screen::the())
26 , m_connection(Connection::the())
27 , m_compositor(Compositor::the())
28 , m_cursor_manager(CursorManager::the())
29 , m_event_loop(LFoundation::EventLoop::the())
30 , m_std_menubar_content()
31 , m_visible_area(m_screen.bounds())
32{
33 s_WinServer_WindowManager_the = this;
34 shrink_visible_area(menu_bar().height(), 0);
35#ifdef TARGET_DESKTOP
36 menu_bar().set_background_color(LG::Color::LightSystemOpaque128);
37#endif // TARGET_DESKTOP
38}
39
40void WindowManager::setup_dock(Window* window)
41{
42#ifdef TARGET_DESKTOP
43 window->make_frameless();
44 window->bounds().set_y(m_screen.bounds().max_y() - window->bounds().height() + 1);
45 window->content_bounds().set_y(m_screen.bounds().max_y() - window->bounds().height() + 1);
46 shrink_visible_area(0, window->bounds().height());
47#endif // TARGET_DESKTOP
48 window->set_event_mask(WindowEvent::IconChange | WindowEvent::WindowStatus | WindowEvent::WindowCreation | WindowEvent::TitleChange);
49 m_dock.set_window(window);
50}
51
52void WindowManager::setup_applist(Window* window)
53{
54#ifdef TARGET_DESKTOP
55 window->make_frameless();
56 const size_t coorx = (visible_area().max_x() - window->bounds().width()) / 2;
57 const size_t coory = visible_area().max_y() - window->bounds().height() - 8;
58 window->bounds().set_x(coorx);
59 window->content_bounds().set_x(coorx);
60 window->bounds().set_y(coory);
61 window->content_bounds().set_y(coory);
62#endif // TARGET_DESKTOP
63 m_applist.set_window(window);
64 minimize_window(*window);
65}
66
67void WindowManager::add_system_window(Window* window)
68{
69 switch (window->type()) {
70 case WindowType::Homescreen:
71 setup_dock(window);
72 break;
73
74 case WindowType::AppList:
75 setup_applist(window);
76 break;
77
78 default:
79 break;
80 }
81}
82
83void WindowManager::add_window(Window* window)
84{
85 m_windows.push_back(window);
86 set_active_window(window);
87
88 if (window->type() != WindowType::Standard) {
89 add_system_window(window);
90 }
91 notify_window_creation(window->id());
92}
93
94void WindowManager::remove_attention_from_window(Window* window)
95{
96 if (movable_window() == window) {
97 m_movable_window = nullptr;
98 }
99 if (active_window() == window) {
100 set_active_window(nullptr);
101 }
102 if (hovered_window() == window) {
103 set_hovered_window(nullptr);
104 }
105}
106
107void WindowManager::on_window_became_invisible(Window* window)
108{
109 if (window->type() == WindowType::Standard && active_window() == window) {
110#ifdef TARGET_DESKTOP
111 menu_bar().set_menubar_content(nullptr, m_compositor);
112#elif TARGET_MOBILE
113 menu_bar().set_style(StatusBarStyle::StandardOpaque);
114 m_compositor.invalidate(menu_bar().bounds());
115#endif
116 }
117 m_compositor.invalidate(window->bounds());
118}
119
120void WindowManager::remove_window(Window* window_ptr)
121{
122 notify_window_status_changed(window_ptr->id(), WindowStatusUpdateType::Removed);
123 m_windows.erase(std::find(m_windows.begin(), m_windows.end(), window_ptr));
124 on_window_became_invisible(window_ptr);
125 remove_attention_from_window(window_ptr);
126 delete window_ptr;
127}
128
129void WindowManager::minimize_window(Window& window)
130{
131 Window* window_ptr = &window;
132 notify_window_status_changed(window.id(), WindowStatusUpdateType::Minimized);
133 window.set_visible(false);
134 m_windows.erase(std::find(m_windows.begin(), m_windows.end(), window_ptr));
135 m_windows.push_back(window_ptr);
136 on_window_became_invisible(window_ptr);
137 remove_attention_from_window(window_ptr);
138}
139
140void WindowManager::resize_window(Window& window, const LG::Size& size)
141{
142 window.did_size_change(size);
143 send_event(new ResizeMessage(window.connection_id(), window.id(), LG::Rect(0, 0, size.width(), size.height())));
144 m_compositor.invalidate(window.bounds());
145}
146
147void WindowManager::maximize_window(Window& window)
148{
149 size_t fullscreen_h = m_screen.height();
150 fullscreen_h = visible_area().height();
151
152 const size_t vertical_borders = Desktop::WindowFrame::std_top_border_size() + Desktop::WindowFrame::std_bottom_border_size();
153 const size_t horizontal_borders = Desktop::WindowFrame::std_left_border_size() + Desktop::WindowFrame::std_right_border_size();
154
155 move_window(&window, -window.bounds().min_x(), -(window.bounds().min_y() - menu_bar().height()));
156 resize_window(window, { m_screen.width() - horizontal_borders, fullscreen_h - vertical_borders });
157}
158
159void WindowManager::start_window_move(Window& window)
160{
161 m_movable_window = &window;
162}
163
164WindowManager::Window* WindowManager::top_window_in_view(WindowType type) const
165{
166 for (auto it = m_windows.begin(); it != m_windows.end(); it++) {
167 auto* window = (*it);
168 if (window->type() == type) {
169 return window;
170 }
171 }
172 return nullptr;
173}
174
175#ifdef TARGET_DESKTOP
176void WindowManager::on_active_window_will_change()
177{
178 if (m_active_window->type() == WindowType::AppList) {
179 m_active_window->set_visible(false);
180 on_window_became_invisible(m_active_window);
181 }
182}
183
184void WindowManager::on_active_window_did_change()
185{
186}
187
188void WindowManager::bring_system_windows_to_front()
189{
190 if (m_dock.has_value()) {
191 do_bring_to_front(*m_dock.window());
192 }
193}
194
195void WindowManager::bring_to_front(Window& window)
196{
197 auto* prev_window = top_window_in_view(WindowType::Standard);
198 do_bring_to_front(window);
199 bring_system_windows_to_front();
200
201 window.set_visible(true);
202 window.frame().set_active(true);
203 m_compositor.invalidate(window.bounds());
204 if (prev_window && prev_window->id() != window.id()) {
205 prev_window->frame().set_active(false);
206 prev_window->frame().invalidate(m_compositor);
207 }
208 if (window.type() == WindowType::Standard) {
209 menu_bar().set_menubar_content(&window.menubar_content(), m_compositor);
210 } else {
211 menu_bar().set_menubar_content(nullptr, m_compositor);
212 }
213}
214#elif TARGET_MOBILE
215void WindowManager::on_active_window_will_change()
216{
217}
218
219void WindowManager::on_active_window_did_change()
220{
221 // If current active_window has become NULL, try to restore the lastest.
222 if (active_window() == nullptr) {
223 if (auto top_window = m_windows.begin(); top_window != m_windows.end()) {
224 m_active_window = *top_window;
225 }
226 }
227}
228
229void WindowManager::bring_system_windows_to_front()
230{
231}
232
233void WindowManager::bring_to_front(Window& window)
234{
235 do_bring_to_front(window);
236 bring_system_windows_to_front();
237 window.set_visible(true);
238 m_active_window = &window;
239 m_compositor.invalidate(window.bounds());
240 if (window.type() == WindowType::Standard) {
241 menu_bar().set_style(window.style());
242 m_compositor.invalidate(menu_bar().bounds());
243 }
244}
245#endif // TARGET_DESKTOP
246
247#ifdef TARGET_DESKTOP
248bool WindowManager::continue_window_move()
249{
250 if (!movable_window()) {
251 return false;
252 }
253
254 if (!m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
255 m_movable_window = nullptr;
256 return true;
257 }
258
259 auto bounds = movable_window()->bounds();
260 m_compositor.invalidate(movable_window()->bounds());
261 move_window(movable_window(), m_cursor_manager.get<CursorManager::Params::OffsetX>(), m_cursor_manager.get<CursorManager::Params::OffsetY>());
262 bounds.unite(movable_window()->bounds());
263 m_compositor.invalidate(bounds);
264 return true;
265}
266#endif // TARGET_DESKTOP
267
268void WindowManager::update_mouse_position(std::unique_ptr<LFoundation::Event> mouse_event)
269{
270 auto invalidate_bounds = m_cursor_manager.current_cursor().bounds();
271 invalidate_bounds.origin().set(m_cursor_manager.draw_position());
272 m_compositor.invalidate(invalidate_bounds);
273
274 m_cursor_manager.update_position((WinServer::MouseEvent*)mouse_event.get());
275
276 invalidate_bounds.origin().set(m_cursor_manager.draw_position());
277 m_compositor.invalidate(invalidate_bounds);
278}
279
280#ifdef TARGET_DESKTOP
281void WindowManager::receive_mouse_event(std::unique_ptr<LFoundation::Event> event)
282{
283 update_mouse_position(std::move(event));
284 if (continue_window_move()) {
285 return;
286 }
287
288 // Checking and dispatching mouse move for Popup.
289 if (popup().visible() && popup().bounds().contains(m_cursor_manager.x(), m_cursor_manager.y())) {
290 popup().on_mouse_move(m_cursor_manager);
291 if (m_cursor_manager.is_changed<CursorManager::Params::Buttons>()) {
292 popup().on_mouse_status_change(m_cursor_manager);
293 }
294 return;
295 } else {
296 popup().on_mouse_leave(m_cursor_manager);
297 if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
298 popup().set_visible(false);
299 }
300 }
301
302 // Checking and dispatching mouse move for MenuBar.
303 if (menu_bar().bounds().contains(m_cursor_manager.x(), m_cursor_manager.y())) {
304 menu_bar().on_mouse_move(m_cursor_manager);
305 if (m_cursor_manager.is_changed<CursorManager::Params::Buttons>()) {
306 menu_bar().on_mouse_status_change(m_cursor_manager);
307 }
308 return;
309 } else if (menu_bar().is_hovered()) {
310 menu_bar().on_mouse_leave(m_cursor_manager);
311 }
312
313 Window* curr_hovered_window = nullptr;
314 Window* window_under_mouse_ptr = nullptr;
315
316 for (auto* window_ptr : m_windows) {
317 auto& window = *window_ptr;
318 if (!window.visible()) {
319 continue;
320 }
321
322 if (window.bounds().contains(m_cursor_manager.x(), m_cursor_manager.y())) {
323 window_under_mouse_ptr = window_ptr;
324 break;
325 }
326 }
327
328 if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>() || m_cursor_manager.pressed<CursorManager::Params::RightButton>()) {
329 if (!window_under_mouse_ptr && m_active_window) {
330 menu_bar().set_menubar_content(nullptr, m_compositor);
331 m_compositor.invalidate(m_active_window->bounds());
332 m_active_window->frame().set_active(false);
333 set_active_window(nullptr);
334 } else if (m_active_window != window_under_mouse_ptr) {
335 set_active_window(window_under_mouse_ptr);
336 }
337 }
338
339 if (!window_under_mouse_ptr) {
340 if (hovered_window()) {
341 send_event(new MouseLeaveMessage(hovered_window()->connection_id(), hovered_window()->id(), 0, 0));
342 }
343 return;
344 }
345 auto& window = *window_under_mouse_ptr;
346
347 if (window.content_bounds().contains(m_cursor_manager.x(), m_cursor_manager.y())) {
348 if (window.type() == WindowType::Standard && active_window() != &window) {
349 curr_hovered_window = nullptr;
350 } else {
351 LG::Point<int> point(m_cursor_manager.x(), m_cursor_manager.y());
352 point.offset_by(-window.content_bounds().origin());
353 send_event(new MouseMoveMessage(window.connection_id(), window.id(), point.x(), point.y()));
354 curr_hovered_window = &window;
355 }
356 } else if (m_cursor_manager.is_changed<CursorManager::Params::LeftButton>() && m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
357 auto tap_point = LG::Point<int>(m_cursor_manager.x() - window.frame().bounds().min_x(), m_cursor_manager.y() - window.frame().bounds().min_y());
358 window.frame().receive_tap_event(tap_point);
359 start_window_move(window);
360 }
361
362 if (hovered_window() && hovered_window() != curr_hovered_window) {
363 send_event(new MouseLeaveMessage(hovered_window()->connection_id(), hovered_window()->id(), 0, 0));
364 }
365 set_hovered_window(curr_hovered_window);
366
367 if (hovered_window() && m_cursor_manager.is_changed<CursorManager::Params::Buttons>()) {
368 LG::Point<int> point(m_cursor_manager.x(), m_cursor_manager.y());
369 point.offset_by(-window.content_bounds().origin());
370
371 auto buttons_state = MouseActionState();
372 if (m_cursor_manager.is_changed<CursorManager::Params::LeftButton>()) {
373 // TODO: May be remove if?
374 if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
375 buttons_state.set(MouseActionType::LeftMouseButtonPressed);
376 } else {
377 buttons_state.set(MouseActionType::LeftMouseButtonReleased);
378 }
379 }
380
381 send_event(new MouseActionMessage(window.connection_id(), window.id(), buttons_state.state(), point.x(), point.y()));
382 }
383
384 if (hovered_window() && m_cursor_manager.is_changed<CursorManager::Params::Wheel>()) {
385 auto* window = hovered_window();
386 auto data = m_cursor_manager.get<CursorManager::Params::Wheel>();
387 send_event(new MouseWheelMessage(window->connection_id(), window->id(), data, m_cursor_manager.x(), m_cursor_manager.y()));
388 }
389}
390#elif TARGET_MOBILE
391void WindowManager::receive_mouse_event(std::unique_ptr<LFoundation::Event> event)
392{
393 update_mouse_position(std::move(event));
394
395 if (m_compositor.control_bar().control_button_bounds().contains(m_cursor_manager.x(), m_cursor_manager.y()) && active_window()) {
396 if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
397 switch (active_window()->type()) {
398 case WindowType::Standard:
399 remove_window(active_window());
400 break;
401 case WindowType::AppList:
402 minimize_window(*active_window());
403 break;
404
405 case WindowType::Homescreen:
406 default:
407 break;
408 }
409 }
410 return;
411 }
412
413 // Tap emulation
414 if (m_cursor_manager.is_changed<CursorManager::Params::Buttons>() && active_window()) {
415 auto window = active_window();
416 auto buttons_state = MouseActionState();
417 LG::Point<int> point(m_cursor_manager.x(), m_cursor_manager.y());
418 point.offset_by(-window->content_bounds().origin());
419 if (m_cursor_manager.is_changed<CursorManager::Params::LeftButton>()) {
420 // TODO: May be remove if?
421 if (m_cursor_manager.pressed<CursorManager::Params::LeftButton>()) {
422 buttons_state.set(MouseActionType::LeftMouseButtonPressed);
423 } else {
424 buttons_state.set(MouseActionType::LeftMouseButtonReleased);
425 }
426 }
427 send_event(new MouseActionMessage(window->connection_id(), window->id(), buttons_state.state(), point.x(), point.y()));
428 }
429}
430#endif // TARGET_MOBILE
431
432void WindowManager::receive_keyboard_event(std::unique_ptr<LFoundation::Event> event)
433{
434 auto* keyboard_event = reinterpret_cast<KeyboardEvent*>(event.release());
435 if (active_window()) {
436 auto window = active_window();
437 send_event(new KeyboardMessage(window->connection_id(), window->id(), keyboard_event->packet().key));
438 }
439 delete keyboard_event;
440}
441
442void WindowManager::receive_event(std::unique_ptr<LFoundation::Event> event)
443{
444 if (event->type() == WinServer::Event::Type::MouseEvent) {
445 receive_mouse_event(std::move(event));
446 } else if (event->type() == WinServer::Event::Type::KeyboardEvent) {
447 receive_keyboard_event(std::move(event));
448 }
449}
450
451// Notifiers
452
453bool WindowManager::notify_listner_about_window_creation(const Window& win, int changed_window_id)
454{
455#ifdef WM_DEBUG
456 Logger::debug << "notify_listner_about_window_status " << win.id() << " that " << changed_window_id << " " << type << std::endl;
457#endif
458 auto* changed_window_ptr = window(changed_window_id);
459 if (!changed_window_ptr) {
460 return false;
461 }
462 send_event(new NotifyWindowCreateMessage(win.connection_id(), win.id(), changed_window_ptr->bundle_id(), changed_window_ptr->icon_path(), changed_window_id, changed_window_ptr->type()));
463 return true;
464}
465
466bool WindowManager::notify_listner_about_window_status(const Window& win, int changed_window_id, WindowStatusUpdateType type)
467{
468#ifdef WM_DEBUG
469 Logger::debug << "notify_listner_about_window_status " << win.id() << " that " << changed_window_id << " " << type << std::endl;
470#endif
471 auto* changed_window_ptr = window(changed_window_id);
472 if (!changed_window_ptr) {
473 return false;
474 }
475 send_event(new NotifyWindowStatusChangedMessage(win.connection_id(), win.id(), changed_window_id, (int)type));
476 return true;
477}
478
479bool WindowManager::notify_listner_about_changed_icon(const Window& win, int changed_window_id)
480{
481#ifdef WM_DEBUG
482 Logger::debug << "notify_listner_about_changed_icon " << win.id() << " that " << changed_window_id << std::endl;
483#endif
484 auto* changed_window_ptr = window(changed_window_id);
485 if (!changed_window_ptr) {
486 return false;
487 }
488 send_event(new NotifyWindowIconChangedMessage(win.connection_id(), win.id(), changed_window_id, changed_window_ptr->icon_path()));
489 return true;
490}
491
492bool WindowManager::notify_listner_about_changed_title(const Window& win, int changed_window_id)
493{
494#ifdef WM_DEBUG
495 Logger::debug << "notify_listner_about_changed_title " << win.id() << " that " << changed_window_id << std::endl;
496#endif
497 auto* changed_window_ptr = window(changed_window_id);
498 if (!changed_window_ptr) {
499 return false;
500 }
501 send_event(new NotifyWindowTitleChangedMessage(win.connection_id(), win.id(), changed_window_id, changed_window_ptr->app_title()));
502 return true;
503}
504
505void WindowManager::notify_window_creation(int changed_window_id)
506{
507 for (auto* window_ptr : m_windows) {
508 auto& window = *window_ptr;
509 if (window.event_mask() & WindowEvent::WindowCreation) {
510 notify_listner_about_window_creation(window, changed_window_id);
511 }
512 }
513}
514
515void WindowManager::notify_window_status_changed(int changed_window_id, WindowStatusUpdateType type)
516{
517 for (auto* window_ptr : m_windows) {
518 auto& window = *window_ptr;
519 if (window.event_mask() & WindowEvent::WindowStatus) {
520 notify_listner_about_window_status(window, changed_window_id, type);
521 }
522 }
523}
524
525void WindowManager::notify_window_icon_changed(int changed_window_id)
526{
527 for (auto* window_ptr : m_windows) {
528 auto& window = *window_ptr;
529 if (window.event_mask() & WindowEvent::IconChange) {
530 notify_listner_about_changed_icon(window, changed_window_id);
531 }
532 }
533}
534
535void WindowManager::notify_window_title_changed(int changed_window_id)
536{
537 for (auto* window_ptr : m_windows) {
538 auto& window = *window_ptr;
539 if (window.event_mask() & WindowEvent::TitleChange) {
540 notify_listner_about_changed_title(window, changed_window_id);
541 }
542 }
543}
544
545#ifdef TARGET_DESKTOP
546void WindowManager::on_window_style_change(Window& window)
547{
548 if (window.visible()) {
549 window.frame().invalidate(m_compositor);
550 }
551}
552#elif TARGET_MOBILE
553void WindowManager::on_window_style_change(Window& window)
554{
555 if (active_window() == &window && window.type() == WindowType::Standard) {
556 menu_bar().set_style(window.style());
557 m_compositor.invalidate(menu_bar().bounds());
558 }
559}
560#endif
561
562void WindowManager::on_window_menubar_change(Window& window)
563{
564 if (m_active_window == &window) {
565 menu_bar().invalidate_menubar_panel(m_compositor);
566 }
567}
568
569void WindowManager::on_window_misbehave(Window& window, ViolationClass viocls)
570{
571 switch (viocls) {
572 case ViolationClass::Ignorable:
573 case ViolationClass::Moderate:
574 break;
575
576 case ViolationClass::Serious:
577 // TODO: Currently we only remove the window, but all apps
578 // should be stopped with with a signal.
579 remove_window(&window);
580 break;
581
582 default:
583 break;
584 }
585}
586
587} // namespace WinServer