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