opuntiaOS - an operating system targeting x86 and ARMv7
at master 20 kB view raw
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