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