Serenity Operating System
at master 2390 lines 93 kB view raw
1/* 2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "WindowManager.h" 8#include "Compositor.h" 9#include "EventLoop.h" 10#include "Menu.h" 11#include "Screen.h" 12#include "Window.h" 13#include <AK/Debug.h> 14#include <AK/StdLibExtras.h> 15#include <AK/Vector.h> 16#include <LibGfx/Bitmap.h> 17#include <LibGfx/CharacterBitmap.h> 18#include <LibGfx/Font/Font.h> 19#include <LibGfx/StylePainter.h> 20#include <LibGfx/SystemTheme.h> 21#include <Services/Taskbar/TaskbarWindow.h> 22#include <WindowServer/AppletManager.h> 23#include <WindowServer/Button.h> 24#include <WindowServer/ConnectionFromClient.h> 25#include <WindowServer/Cursor.h> 26#include <WindowServer/WindowClientEndpoint.h> 27 28namespace WindowServer { 29 30static WindowManager* s_the; 31 32WindowManager& WindowManager::the() 33{ 34 VERIFY(s_the); 35 return *s_the; 36} 37 38WindowManager::WindowManager(Gfx::PaletteImpl& palette) 39 : m_switcher(WindowSwitcher::construct()) 40 , m_keymap_switcher(KeymapSwitcher::construct()) 41 , m_palette(palette) 42{ 43 s_the = this; 44 45 { 46 // If we haven't created any window stacks, at least create the stationary/main window stack 47 auto row = adopt_own(*new RemoveReference<decltype(*m_window_stacks[0])>()); 48 auto main_window_stack = adopt_own(*new WindowStack(0, 0)); 49 main_window_stack->set_stationary_window_stack(*main_window_stack); 50 m_current_window_stack = main_window_stack.ptr(); 51 row->append(move(main_window_stack)); 52 m_window_stacks.append(move(row)); 53 } 54 55 reload_config(); 56 57 m_keymap_switcher->on_keymap_change = [&](DeprecatedString const& keymap) { 58 for_each_window_manager([&keymap](WMConnectionFromClient& conn) { 59 if (!(conn.event_mask() & WMEventMask::KeymapChanged)) 60 return IterationDecision::Continue; 61 if (conn.window_id() < 0) 62 return IterationDecision::Continue; 63 64 conn.async_keymap_changed(conn.window_id(), keymap); 65 return IterationDecision::Continue; 66 }); 67 }; 68 69 Compositor::the().did_construct_window_manager({}); 70} 71 72void WindowManager::reload_config() 73{ 74 unsigned workspace_rows = (unsigned)g_config->read_num_entry("Workspaces", "Rows", default_window_stack_rows); 75 unsigned workspace_columns = (unsigned)g_config->read_num_entry("Workspaces", "Columns", default_window_stack_columns); 76 if (workspace_rows == 0 || workspace_columns == 0 || workspace_rows > max_window_stack_rows || workspace_columns > max_window_stack_columns) { 77 workspace_rows = default_window_stack_rows; 78 workspace_columns = default_window_stack_columns; 79 } 80 apply_workspace_settings(workspace_rows, workspace_columns, false); 81 82 m_double_click_speed = g_config->read_num_entry("Input", "DoubleClickSpeed", 250); 83 m_mouse_buttons_switched = g_config->read_bool_entry("Mouse", "ButtonsSwitched", false); 84 m_natural_scroll = g_config->read_bool_entry("Mouse", "NaturalScroll", false); 85 m_cursor_highlight_radius = g_config->read_num_entry("Mouse", "CursorHighlightRadius", 25); 86 Color default_highlight_color = Color::NamedColor::Red; 87 default_highlight_color.set_alpha(110); 88 m_cursor_highlight_color = Color::from_string(g_config->read_entry("Mouse", "CursorHighlightColor")).value_or(default_highlight_color); 89 apply_cursor_theme(g_config->read_entry("Mouse", "CursorTheme", "Default")); 90 91 auto reload_graphic = [&](RefPtr<MultiScaleBitmaps>& bitmap, DeprecatedString const& name) { 92 if (bitmap) { 93 if (!bitmap->load(name)) 94 bitmap = nullptr; 95 } else { 96 bitmap = MultiScaleBitmaps::create(name); 97 } 98 }; 99 100 reload_graphic(m_overlay_rect_shadow, g_config->read_entry("Graphics", "OverlayRectShadow")); 101 Compositor::the().invalidate_after_theme_or_font_change(); 102 103 WindowFrame::reload_config(); 104 105 load_system_effects(); 106} 107 108Gfx::Font const& WindowManager::font() const 109{ 110 return Gfx::FontDatabase::default_font(); 111} 112 113Gfx::Font const& WindowManager::window_title_font() const 114{ 115 return Gfx::FontDatabase::window_title_font(); 116} 117 118bool WindowManager::set_screen_layout(ScreenLayout&& screen_layout, bool save, DeprecatedString& error_msg) 119{ 120 if (!Screen::apply_layout(move(screen_layout), error_msg)) 121 return false; 122 123 reload_icon_bitmaps_after_scale_change(); 124 125 tell_wms_screen_rects_changed(); 126 127 for_each_window_stack([&](auto& window_stack) { 128 window_stack.for_each_window([](Window& window) { 129 window.screens().clear_with_capacity(); 130 window.recalculate_rect(); 131 return IterationDecision::Continue; 132 }); 133 return IterationDecision::Continue; 134 }); 135 136 Compositor::the().screen_resolution_changed(); 137 138 if (save) 139 Screen::layout().save_config(*g_config); 140 return true; 141} 142 143ScreenLayout WindowManager::get_screen_layout() const 144{ 145 return Screen::layout(); 146} 147 148bool WindowManager::save_screen_layout(DeprecatedString& error_msg) 149{ 150 if (!Screen::layout().save_config(*g_config)) { 151 error_msg = "Could not save"; 152 return false; 153 } 154 return true; 155} 156 157bool WindowManager::apply_workspace_settings(unsigned rows, unsigned columns, bool save) 158{ 159 VERIFY(rows != 0); 160 VERIFY(rows <= max_window_stack_rows); 161 VERIFY(columns != 0); 162 VERIFY(columns <= max_window_stack_columns); 163 164 auto current_rows = window_stack_rows(); 165 auto current_columns = window_stack_columns(); 166 if (rows != current_rows || columns != current_columns) { 167 auto& current_window_stack = this->current_window_stack(); 168 auto current_stack_row = current_window_stack.row(); 169 auto current_stack_column = current_window_stack.column(); 170 bool need_rerender = false; 171 bool removing_current_stack = current_stack_row > rows - 1 || current_stack_column > columns - 1; 172 auto new_current_row = min(current_stack_row, rows - 1); 173 auto new_current_column = min(current_stack_column, columns - 1); 174 175 // Collect all windows that were moved. We can't tell the wms at this point because 176 // the current window stack may not be valid anymore, until after the move is complete 177 Vector<Window*, 32> windows_moved; 178 179 auto merge_window_stack = [&](WindowStack& from_stack, WindowStack& into_stack) { 180 auto move_to = WindowStack::MoveAllWindowsTo::Back; 181 182 // TODO: Figure out a better algorithm. We basically always layer it on top, unless 183 // it's either being moved to window stack we're viewing or that we will be viewing. 184 // In that case we would want to make sure the window stack ends up on top (with no 185 // change to the active window) 186 bool moving_to_new_current_stack = into_stack.row() == new_current_row && into_stack.column() == new_current_column; 187 if (moving_to_new_current_stack) 188 move_to = WindowStack::MoveAllWindowsTo::Front; 189 from_stack.move_all_windows(into_stack, windows_moved, move_to); 190 }; 191 192 // While we have too many rows, merge each row too many into the new bottom row 193 while (current_rows > rows) { 194 auto& row = *m_window_stacks[rows]; 195 for (size_t column_index = 0; column_index < row.size(); column_index++) { 196 merge_window_stack(*row[column_index], *(*m_window_stacks[rows - 1])[column_index]); 197 if (rows - 1 == current_stack_row && column_index == current_stack_column) 198 need_rerender = true; 199 } 200 m_window_stacks.remove(rows); 201 current_rows--; 202 } 203 // While we have too many columns, merge each column too many into the new right most column 204 while (current_columns > columns) { 205 for (size_t row_index = 0; row_index < current_rows; row_index++) { 206 auto& row = *m_window_stacks[row_index]; 207 merge_window_stack(*row[columns], *row[columns - 1]); 208 if (row_index == current_stack_row && columns - 1 == current_stack_column) 209 need_rerender = true; 210 row.remove(columns); 211 } 212 current_columns--; 213 } 214 // Add more rows if necessary 215 while (rows > current_rows) { 216 auto row = adopt_own(*new RemoveReference<decltype(*m_window_stacks[0])>()); 217 for (size_t column_index = 0; column_index < columns; column_index++) { 218 auto window_stack = adopt_own(*new WindowStack(current_rows, column_index)); 219 window_stack->set_stationary_window_stack(*(*m_window_stacks[0])[0]); 220 row->append(move(window_stack)); 221 } 222 m_window_stacks.append(move(row)); 223 current_rows++; 224 } 225 // Add more columns if necessary 226 while (columns > current_columns) { 227 for (size_t row_index = 0; row_index < current_rows; row_index++) { 228 auto& row = m_window_stacks[row_index]; 229 while (row->size() < columns) { 230 auto window_stack = adopt_own(*new WindowStack(row_index, row->size())); 231 window_stack->set_stationary_window_stack(*(*m_window_stacks[0])[0]); 232 row->append(move(window_stack)); 233 } 234 } 235 current_columns++; 236 } 237 238 if (removing_current_stack) { 239 // If we're on a window stack that was removed, we need to move... 240 m_current_window_stack = (*m_window_stacks[new_current_row])[new_current_column]; 241 Compositor::the().set_current_window_stack_no_transition(*m_current_window_stack); 242 need_rerender = false; // The compositor already called invalidate_for_window_stack_merge_or_change for us 243 } 244 245 for (auto* window_moved : windows_moved) 246 WindowManager::the().tell_wms_window_state_changed(*window_moved); 247 248 tell_wms_screen_rects_changed(); // updates the available workspaces 249 if (current_stack_row != new_current_row || current_stack_column != new_current_column) 250 tell_wms_current_window_stack_changed(); 251 252 if (need_rerender) 253 Compositor::the().invalidate_for_window_stack_merge_or_change(); 254 } 255 256 if (save) { 257 g_config->write_num_entry("Workspaces", "Rows", window_stack_rows()); 258 g_config->write_num_entry("Workspaces", "Columns", window_stack_columns()); 259 return !g_config->sync().is_error(); 260 } 261 return true; 262} 263 264void WindowManager::set_acceleration_factor(double factor) 265{ 266 ScreenInput::the().set_acceleration_factor(factor); 267 dbgln("Saving acceleration factor {} to config file at {}", factor, g_config->filename()); 268 g_config->write_entry("Mouse", "AccelerationFactor", DeprecatedString::formatted("{}", factor)); 269 sync_config_to_disk(); 270} 271 272void WindowManager::set_scroll_step_size(unsigned step_size) 273{ 274 ScreenInput::the().set_scroll_step_size(step_size); 275 dbgln("Saving scroll step size {} to config file at {}", step_size, g_config->filename()); 276 g_config->write_entry("Mouse", "ScrollStepSize", DeprecatedString::number(step_size)); 277 sync_config_to_disk(); 278} 279 280void WindowManager::set_double_click_speed(int speed) 281{ 282 VERIFY(speed >= double_click_speed_min && speed <= double_click_speed_max); 283 m_double_click_speed = speed; 284 dbgln("Saving double-click speed {} to config file at {}", speed, g_config->filename()); 285 g_config->write_entry("Input", "DoubleClickSpeed", DeprecatedString::number(speed)); 286 sync_config_to_disk(); 287} 288 289int WindowManager::double_click_speed() const 290{ 291 return m_double_click_speed; 292} 293 294void WindowManager::set_mouse_buttons_switched(bool switched) 295{ 296 m_mouse_buttons_switched = switched; 297 dbgln("Saving mouse buttons switched state {} to config file at {}", switched, g_config->filename()); 298 g_config->write_bool_entry("Mouse", "ButtonsSwitched", switched); 299 sync_config_to_disk(); 300} 301 302bool WindowManager::are_mouse_buttons_switched() const 303{ 304 return m_mouse_buttons_switched; 305} 306 307void WindowManager::set_natural_scroll(bool inverted) 308{ 309 m_natural_scroll = inverted; 310 dbgln("Saving scroll inverted state {} to config file at {}", inverted, g_config->filename()); 311 g_config->write_bool_entry("Mouse", "NaturalScroll", inverted); 312 sync_config_to_disk(); 313} 314 315bool WindowManager::is_natural_scroll() const 316{ 317 return m_natural_scroll; 318} 319 320WindowStack& WindowManager::window_stack_for_window(Window& window) 321{ 322 if (is_stationary_window_type(window.type())) 323 return *(*m_window_stacks[0])[0]; 324 if (auto* parent = window.parent_window(); parent && !is_stationary_window_type(parent->type())) 325 return parent->window_stack(); 326 return current_window_stack(); 327} 328 329void WindowManager::add_window(Window& window) 330{ 331 auto& window_stack = window_stack_for_window(window); 332 bool is_first_window = window_stack.is_empty(); 333 334 window_stack.add(window); 335 336 if (window.is_fullscreen()) { 337 auto& screen = Screen::main(); // TODO: support fullscreen windows on other screens! 338 Core::EventLoop::current().post_event(window, make<ResizeEvent>(screen.rect())); 339 window.set_rect(screen.rect()); 340 } 341 342 if (window.type() != WindowType::Desktop || is_first_window) 343 set_active_window(&window); 344 345 if (window.type() == WindowType::Popup) 346 notify_active_window_input_preempted(); 347 348 if (m_switcher->is_visible() && window.type() != WindowType::WindowSwitcher) 349 m_switcher->refresh(); 350 351 Compositor::the().invalidate_occlusions(); 352 353 window.invalidate(true, true); 354 355 tell_wms_window_state_changed(window); 356} 357 358void WindowManager::move_to_front_and_make_active(Window& window) 359{ 360 for_each_window_in_modal_chain(window, [&](auto& w) { 361 w.set_minimized(false); 362 w.window_stack().move_to_front(w); 363 return IterationDecision::Continue; 364 }); 365 366 if (auto* blocker = window.blocking_modal_window()) { 367 blocker->window_stack().move_to_front(*blocker); 368 set_active_window(blocker); 369 } else { 370 window.window_stack().move_to_front(window); 371 set_active_window(&window); 372 373 for (auto& child : window.child_windows()) { 374 if (!child || !child->is_modal()) 375 continue; 376 if (child->is_rendering_above()) 377 child->window_stack().move_to_front(*child); 378 } 379 } 380 381 if (m_switcher->is_visible()) { 382 m_switcher->refresh(); 383 if (!window.is_modal()) { 384 m_switcher->select_window(window); 385 set_highlight_window(&window); 386 } 387 } 388 389 Compositor::the().invalidate_occlusions(); 390} 391 392void WindowManager::remove_window(Window& window) 393{ 394 check_hide_geometry_overlay(window); 395 auto* active = active_window(); 396 bool was_modal = window.is_modal(); // This requires the window to be on a window stack still! 397 window.window_stack().remove(window); 398 if (active == &window) 399 pick_new_active_window(&window); 400 401 if (window.type() == WindowType::Popup) 402 notify_active_window_input_restored(); 403 404 window.invalidate_last_rendered_screen_rects_now(); 405 406 if (m_switcher->is_visible() && window.type() != WindowType::WindowSwitcher) 407 m_switcher->refresh(); 408 409 Compositor::the().invalidate_occlusions(); 410 411 for_each_window_manager([&](WMConnectionFromClient& conn) { 412 if (conn.window_id() < 0 || !(conn.event_mask() & WMEventMask::WindowRemovals)) 413 return IterationDecision::Continue; 414 if (!window.is_internal() && !was_modal) 415 conn.async_window_removed(conn.window_id(), window.client_id(), window.window_id()); 416 return IterationDecision::Continue; 417 }); 418} 419 420void WindowManager::greet_window_manager(WMConnectionFromClient& conn) 421{ 422 if (conn.window_id() < 0) 423 return; 424 425 tell_wm_about_current_window_stack(conn); 426 427 for_each_window_stack([&](auto& window_stack) { 428 window_stack.for_each_window([&](Window& other_window) { 429 tell_wm_about_window(conn, other_window); 430 tell_wm_about_window_icon(conn, other_window); 431 return IterationDecision::Continue; 432 }); 433 return IterationDecision::Continue; 434 }); 435 if (auto* applet_area_window = AppletManager::the().window()) 436 tell_wms_applet_area_size_changed(applet_area_window->size()); 437} 438 439void WindowManager::tell_wm_about_window(WMConnectionFromClient& conn, Window& window) 440{ 441 if (conn.window_id() < 0) 442 return; 443 if (!(conn.event_mask() & WMEventMask::WindowStateChanges)) 444 return; 445 if (window.is_internal()) 446 return; 447 if (window.blocking_modal_window()) 448 return; 449 450 Window* modeless = window.modeless_ancestor(); 451 if (!modeless) 452 return; 453 bool is_blocked = modeless->blocking_modal_window(); 454 auto is_active = for_each_window_in_modal_chain(*modeless, [&](auto& w) { 455 if (w.is_active()) 456 return IterationDecision::Break; 457 return IterationDecision::Continue; 458 }); 459 460 auto& window_stack = is_stationary_window_type(modeless->type()) ? current_window_stack() : modeless->window_stack(); 461 conn.async_window_state_changed(conn.window_id(), modeless->client_id(), modeless->window_id(), window_stack.row(), window_stack.column(), is_active, is_blocked, modeless->is_minimized(), modeless->is_frameless(), (i32)modeless->type(), modeless->computed_title(), modeless->rect(), modeless->progress()); 462} 463 464void WindowManager::tell_wm_about_window_rect(WMConnectionFromClient& conn, Window& window) 465{ 466 if (conn.window_id() < 0) 467 return; 468 if (!(conn.event_mask() & WMEventMask::WindowRectChanges)) 469 return; 470 if (window.is_internal()) 471 return; 472 conn.async_window_rect_changed(conn.window_id(), window.client_id(), window.window_id(), window.rect()); 473} 474 475void WindowManager::tell_wm_about_window_icon(WMConnectionFromClient& conn, Window& window) 476{ 477 if (conn.window_id() < 0) 478 return; 479 if (!(conn.event_mask() & WMEventMask::WindowIconChanges)) 480 return; 481 if (window.is_internal()) 482 return; 483 conn.async_window_icon_bitmap_changed(conn.window_id(), window.client_id(), window.window_id(), window.icon().to_shareable_bitmap()); 484} 485 486void WindowManager::tell_wm_about_current_window_stack(WMConnectionFromClient& conn) 487{ 488 if (conn.window_id() < 0) 489 return; 490 if (!(conn.event_mask() & WMEventMask::WorkspaceChanges)) 491 return; 492 auto& window_stack = current_window_stack(); 493 conn.async_workspace_changed(conn.window_id(), window_stack.row(), window_stack.column()); 494} 495 496void WindowManager::tell_wms_window_state_changed(Window& window) 497{ 498 for_each_window_manager([&](WMConnectionFromClient& conn) { 499 tell_wm_about_window(conn, window); 500 return IterationDecision::Continue; 501 }); 502} 503 504void WindowManager::tell_wms_window_icon_changed(Window& window) 505{ 506 for_each_window_manager([&](WMConnectionFromClient& conn) { 507 tell_wm_about_window_icon(conn, window); 508 return IterationDecision::Continue; 509 }); 510} 511 512void WindowManager::tell_wms_window_rect_changed(Window& window) 513{ 514 for_each_window_manager([&](WMConnectionFromClient& conn) { 515 tell_wm_about_window_rect(conn, window); 516 return IterationDecision::Continue; 517 }); 518} 519 520void WindowManager::tell_wms_screen_rects_changed() 521{ 522 ConnectionFromClient::for_each_client([&](ConnectionFromClient& client) { 523 client.notify_about_new_screen_rects(); 524 }); 525} 526 527void WindowManager::tell_wms_applet_area_size_changed(Gfx::IntSize size) 528{ 529 for_each_window_manager([&](WMConnectionFromClient& conn) { 530 if (conn.window_id() < 0) 531 return IterationDecision::Continue; 532 533 conn.async_applet_area_size_changed(conn.window_id(), size); 534 return IterationDecision::Continue; 535 }); 536} 537 538void WindowManager::tell_wms_super_key_pressed() 539{ 540 for_each_window_manager([](WMConnectionFromClient& conn) { 541 if (conn.window_id() < 0) 542 return IterationDecision::Continue; 543 544 conn.async_super_key_pressed(conn.window_id()); 545 return IterationDecision::Continue; 546 }); 547} 548 549void WindowManager::tell_wms_super_space_key_pressed() 550{ 551 for_each_window_manager([](WMConnectionFromClient& conn) { 552 if (conn.window_id() < 0) 553 return IterationDecision::Continue; 554 555 conn.async_super_space_key_pressed(conn.window_id()); 556 return IterationDecision::Continue; 557 }); 558} 559 560void WindowManager::tell_wms_super_d_key_pressed() 561{ 562 for_each_window_manager([](WMConnectionFromClient& conn) { 563 if (conn.window_id() < 0) 564 return IterationDecision::Continue; 565 566 conn.async_super_d_key_pressed(conn.window_id()); 567 return IterationDecision::Continue; 568 }); 569} 570 571void WindowManager::tell_wms_super_digit_key_pressed(u8 digit) 572{ 573 for_each_window_manager([digit](WMConnectionFromClient& conn) { 574 if (conn.window_id() < 0) 575 return IterationDecision::Continue; 576 577 conn.async_super_digit_key_pressed(conn.window_id(), digit); 578 return IterationDecision::Continue; 579 }); 580} 581 582void WindowManager::tell_wms_current_window_stack_changed() 583{ 584 for_each_window_manager([&](WMConnectionFromClient& conn) { 585 tell_wm_about_current_window_stack(conn); 586 return IterationDecision::Continue; 587 }); 588} 589 590static bool window_type_has_title(WindowType type) 591{ 592 return type == WindowType::Normal; 593} 594 595void WindowManager::notify_modified_changed(Window& window) 596{ 597 if (m_switcher->is_visible()) 598 m_switcher->refresh(); 599 600 tell_wms_window_state_changed(window); 601} 602 603void WindowManager::notify_title_changed(Window& window) 604{ 605 if (!window_type_has_title(window.type())) 606 return; 607 608 dbgln_if(WINDOWMANAGER_DEBUG, "[WM] Window({}) title set to '{}'", &window, window.title()); 609 610 if (m_switcher->is_visible()) 611 m_switcher->refresh(); 612 613 tell_wms_window_state_changed(window); 614} 615 616void WindowManager::notify_rect_changed(Window& window, Gfx::IntRect const& old_rect, Gfx::IntRect const& new_rect) 617{ 618 dbgln_if(RESIZE_DEBUG, "[WM] Window({}) rect changed {} -> {}", &window, old_rect, new_rect); 619 620 if (m_switcher->is_visible() && window.type() != WindowType::WindowSwitcher) 621 m_switcher->refresh(); 622 623 tell_wms_window_rect_changed(window); 624 625 if (window.type() == WindowType::Applet) 626 AppletManager::the().relayout(); 627 628 reevaluate_hover_state_for_window(&window); 629} 630 631void WindowManager::notify_opacity_changed(Window&) 632{ 633 Compositor::the().invalidate_occlusions(); 634} 635 636void WindowManager::notify_minimization_state_changed(Window& window) 637{ 638 tell_wms_window_state_changed(window); 639 640 if (window.client()) 641 window.client()->async_window_state_changed(window.window_id(), window.is_minimized(), window.is_maximized(), window.is_occluded()); 642 643 if (window.is_active() && window.is_minimized()) 644 pick_new_active_window(&window); 645} 646 647void WindowManager::notify_occlusion_state_changed(Window& window) 648{ 649 if (window.client()) 650 window.client()->async_window_state_changed(window.window_id(), window.is_minimized(), window.is_maximized(), window.is_occluded()); 651} 652 653void WindowManager::notify_progress_changed(Window& window) 654{ 655 tell_wms_window_state_changed(window); 656} 657 658void WindowManager::pick_new_active_window(Window* previous_active) 659{ 660 Window* desktop = nullptr; 661 auto result = for_each_visible_window_from_front_to_back([&](Window& candidate) { 662 if (candidate.type() == WindowType::Desktop) 663 desktop = &candidate; 664 if (candidate.type() != WindowType::Normal) 665 return IterationDecision::Continue; 666 if (candidate.is_destroyed()) 667 return IterationDecision::Continue; 668 if (!previous_active || previous_active != &candidate) { 669 set_active_window(&candidate); 670 return IterationDecision::Break; 671 } 672 return IterationDecision::Continue; 673 }); 674 675 if (result != IterationDecision::Break) 676 set_active_window(desktop); 677} 678 679void WindowManager::check_hide_geometry_overlay(Window& window) 680{ 681 if (&window == m_move_window.ptr() || &window == m_resize_window.ptr()) 682 m_geometry_overlay = nullptr; 683} 684 685void WindowManager::start_window_move(Window& window, Gfx::IntPoint origin) 686{ 687 MenuManager::the().close_everyone(); 688 689 dbgln_if(MOVE_DEBUG, "[WM] Begin moving Window({})", &window); 690 691 move_to_front_and_make_active(window); 692 m_move_window = window; 693 m_move_window->set_default_positioned(false); 694 m_move_origin = origin; 695 m_move_window_origin = window.position(); 696 m_move_window_cursor_position = window.is_tiled() ? to_floating_cursor_position(m_mouse_down_origin) : m_mouse_down_origin; 697 if (system_effects().geometry() == ShowGeometry::OnMoveAndResize || system_effects().geometry() == ShowGeometry::OnMoveOnly) { 698 m_geometry_overlay = Compositor::the().create_overlay<WindowGeometryOverlay>(window); 699 m_geometry_overlay->set_enabled(true); 700 } 701 window.invalidate(true, true); 702} 703 704void WindowManager::start_window_move(Window& window, MouseEvent const& event) 705{ 706 start_window_move(window, event.position()); 707} 708 709void WindowManager::start_window_resize(Window& window, Gfx::IntPoint position, MouseButton button, ResizeDirection resize_direction) 710{ 711 MenuManager::the().close_everyone(); 712 713 move_to_front_and_make_active(window); 714 m_resize_direction = resize_direction; 715 if (m_resize_direction == ResizeDirection::None) { 716 VERIFY(!m_resize_window); 717 return; 718 } 719 720 dbgln_if(RESIZE_DEBUG, "[WM] Begin resizing Window({})", &window); 721 722 m_resizing_mouse_button = button; 723 m_resize_window = window; 724 m_resize_origin = position; 725 m_resize_window_original_rect = window.rect(); 726 if (system_effects().geometry() == ShowGeometry::OnMoveAndResize || system_effects().geometry() == ShowGeometry::OnResizeOnly) { 727 m_geometry_overlay = Compositor::the().create_overlay<WindowGeometryOverlay>(window); 728 m_geometry_overlay->set_enabled(true); 729 } 730 731 set_automatic_cursor_tracking_window(nullptr); 732 733 window.invalidate(true, true); 734} 735 736void WindowManager::start_window_resize(Window& window, MouseEvent const& event, ResizeDirection resize_direction) 737{ 738 start_window_resize(window, event.position(), event.button(), resize_direction); 739} 740 741bool WindowManager::process_ongoing_window_move(MouseEvent& event) 742{ 743 if (!m_move_window) 744 return false; 745 if (event.type() == Event::MouseUp && event.button() == MouseButton::Primary) { 746 747 dbgln_if(MOVE_DEBUG, "[WM] Finish moving Window({})", m_move_window); 748 749 if (!m_move_window->is_tiled()) 750 m_move_window->set_floating_rect(m_move_window->rect()); 751 752 Core::EventLoop::current().post_event(*m_move_window, make<MoveEvent>(m_move_window->rect())); 753 m_move_window->invalidate(true, true); 754 if (m_move_window->is_resizable()) { 755 process_event_for_doubleclick(*m_move_window, event); 756 if (event.type() == Event::MouseDoubleClick) { 757 dbgln_if(DOUBLECLICK_DEBUG, "[WM] Click up became doubleclick!"); 758 m_move_window->set_maximized(!m_move_window->is_maximized()); 759 } 760 } 761 m_move_window = nullptr; 762 m_geometry_overlay = nullptr; 763 return true; 764 } 765 if (event.type() == Event::MouseMove) { 766 if constexpr (MOVE_DEBUG) { 767 dbgln("[WM] Moving, origin: {}, now: {}", m_move_origin, event.position()); 768 if (m_move_window->is_maximized()) 769 dbgln(" [!] The window is still maximized. Not moving yet."); 770 } 771 772 int const tiling_deadzone = 10; 773 int const secondary_deadzone = 2; 774 auto& cursor_screen = Screen::closest_to_location(event.position()); 775 auto desktop = desktop_rect(cursor_screen); 776 auto desktop_relative_to_screen = desktop.translated(-cursor_screen.rect().location()); 777 if (m_move_window->is_maximized()) { 778 auto pixels_moved_from_start = event.position().pixels_moved(m_move_origin); 779 if (pixels_moved_from_start > 5) { 780 dbgln_if(MOVE_DEBUG, "[WM] de-maximizing window"); 781 m_move_origin = event.position(); 782 if (m_move_origin.y() <= secondary_deadzone + desktop.top()) 783 return true; 784 Gfx::IntPoint adjusted_position = event.position().translated(-m_move_window_cursor_position); 785 m_move_window->set_maximized(false); 786 m_move_window->move_to(adjusted_position); 787 m_move_window_origin = m_move_window->position(); 788 } 789 } else { 790 bool is_resizable = m_move_window->is_resizable(); 791 auto pixels_moved_from_start = event.position().pixels_moved(m_move_origin); 792 793 auto event_location_relative_to_screen = event.position().translated(-cursor_screen.rect().location()); 794 if (is_resizable && event_location_relative_to_screen.x() <= tiling_deadzone) { 795 if (event_location_relative_to_screen.y() <= tiling_deadzone + desktop_relative_to_screen.top()) 796 m_move_window->set_tiled(WindowTileType::TopLeft); 797 else if (event_location_relative_to_screen.y() >= desktop_relative_to_screen.height() - tiling_deadzone) 798 m_move_window->set_tiled(WindowTileType::BottomLeft); 799 else 800 m_move_window->set_tiled(WindowTileType::Left); 801 } else if (is_resizable && event_location_relative_to_screen.x() >= cursor_screen.width() - tiling_deadzone) { 802 if (event_location_relative_to_screen.y() <= tiling_deadzone + desktop.top()) 803 m_move_window->set_tiled(WindowTileType::TopRight); 804 else if (event_location_relative_to_screen.y() >= desktop_relative_to_screen.height() - tiling_deadzone) 805 m_move_window->set_tiled(WindowTileType::BottomRight); 806 else 807 m_move_window->set_tiled(WindowTileType::Right); 808 } else if (is_resizable && event_location_relative_to_screen.y() <= secondary_deadzone + desktop_relative_to_screen.top()) { 809 m_move_window->set_tiled(WindowTileType::Top); 810 } else if (is_resizable && event_location_relative_to_screen.y() >= desktop_relative_to_screen.bottom() - secondary_deadzone) { 811 m_move_window->set_tiled(WindowTileType::Bottom); 812 } else if (!m_move_window->is_tiled()) { 813 Gfx::IntPoint pos = m_move_window_origin.translated(event.position() - m_move_origin); 814 m_move_window->set_position_without_repaint(pos); 815 } else if (pixels_moved_from_start > 5) { 816 Gfx::IntPoint adjusted_position = event.position().translated(-m_move_window_cursor_position); 817 m_move_window->set_untiled(); 818 m_move_window->move_to(adjusted_position); 819 m_move_origin = event.position(); 820 m_move_window_origin = m_move_window->position(); 821 } 822 } 823 if (system_effects().geometry() == ShowGeometry::OnMoveAndResize || system_effects().geometry() == ShowGeometry::OnMoveOnly) { 824 m_geometry_overlay->window_rect_changed(); 825 } 826 } 827 Core::EventLoop::current().post_event(*m_move_window, make<MoveEvent>(m_move_window->rect())); 828 return true; 829} 830 831Gfx::IntPoint WindowManager::to_floating_cursor_position(Gfx::IntPoint origin) const 832{ 833 VERIFY(m_move_window); 834 835 Gfx::IntPoint new_position; 836 auto dist_from_right = m_move_window->rect().width() - origin.x(); 837 auto dist_from_bottom = m_move_window->rect().height() - origin.y(); 838 auto floating_width = m_move_window->floating_rect().width(); 839 auto floating_height = m_move_window->floating_rect().height(); 840 841 if (origin.x() < dist_from_right && origin.x() < floating_width / 2) 842 new_position.set_x(origin.x()); 843 else if (dist_from_right < origin.x() && dist_from_right < floating_width / 2) 844 new_position.set_x(floating_width - dist_from_right); 845 else 846 new_position.set_x(floating_width / 2); 847 848 if (origin.y() < dist_from_bottom && origin.y() < floating_height / 2) 849 new_position.set_y(origin.y()); 850 else if (dist_from_bottom < origin.y() && dist_from_bottom < floating_height / 2) 851 new_position.set_y(floating_height - dist_from_bottom); 852 else 853 new_position.set_y(floating_height / 2); 854 855 return new_position; 856} 857 858bool WindowManager::process_ongoing_window_resize(MouseEvent const& event) 859{ 860 if (!m_resize_window) 861 return false; 862 863 // Deliver MouseDoubleClick events to the frame 864 if (event.type() == Event::MouseUp) { 865 auto& frame = m_resize_window->frame(); 866 auto frame_event = event.translated(-frame.rect().location()); 867 process_event_for_doubleclick(*m_resize_window, frame_event); 868 if (frame_event.type() == Event::MouseDoubleClick) 869 frame.handle_mouse_event(frame_event); 870 } 871 872 if (event.type() == Event::MouseUp && event.button() == m_resizing_mouse_button) { 873 dbgln_if(RESIZE_DEBUG, "[WM] Finish resizing Window({})", m_resize_window); 874 875 if (!m_resize_window->is_tiled()) 876 m_resize_window->set_floating_rect(m_resize_window->rect()); 877 878 Core::EventLoop::current().post_event(*m_resize_window, make<ResizeEvent>(m_resize_window->rect())); 879 m_resize_window->invalidate(true, true); 880 m_resize_window = nullptr; 881 m_geometry_overlay = nullptr; 882 m_resizing_mouse_button = MouseButton::None; 883 return true; 884 } 885 886 if (event.type() != Event::MouseMove) 887 return true; 888 889 auto& cursor_screen = ScreenInput::the().cursor_location_screen(); 890 auto& closest_screen = Screen::closest_to_rect(m_resize_window->rect()); 891 if (&cursor_screen == &closest_screen) { 892 constexpr auto hot_zone = 10; 893 Gfx::IntRect tiling_rect = desktop_rect(cursor_screen).shrunken({ hot_zone, hot_zone }); 894 if ((m_resize_direction == ResizeDirection::Up || m_resize_direction == ResizeDirection::Down) 895 && (event.y() >= tiling_rect.bottom() || event.y() <= tiling_rect.top())) { 896 m_resize_window->set_tiled(WindowTileType::VerticallyMaximized); 897 return true; 898 } 899 if ((m_resize_direction == ResizeDirection::Left || m_resize_direction == ResizeDirection::Right) 900 && (event.x() >= tiling_rect.right() || event.x() <= tiling_rect.left())) { 901 m_resize_window->set_tiled(WindowTileType::HorizontallyMaximized); 902 return true; 903 } 904 } 905 906 int diff_x = event.x() - m_resize_origin.x(); 907 int diff_y = event.y() - m_resize_origin.y(); 908 909 int change_w = 0; 910 int change_h = 0; 911 912 switch (m_resize_direction) { 913 case ResizeDirection::DownRight: 914 change_w = diff_x; 915 change_h = diff_y; 916 break; 917 case ResizeDirection::Right: 918 change_w = diff_x; 919 break; 920 case ResizeDirection::UpRight: 921 change_w = diff_x; 922 change_h = -diff_y; 923 break; 924 case ResizeDirection::Up: 925 change_h = -diff_y; 926 break; 927 case ResizeDirection::UpLeft: 928 change_w = -diff_x; 929 change_h = -diff_y; 930 break; 931 case ResizeDirection::Left: 932 change_w = -diff_x; 933 break; 934 case ResizeDirection::DownLeft: 935 change_w = -diff_x; 936 change_h = diff_y; 937 break; 938 case ResizeDirection::Down: 939 change_h = diff_y; 940 break; 941 default: 942 VERIFY_NOT_REACHED(); 943 } 944 945 auto new_rect = m_resize_window_original_rect; 946 947 // First, size the new rect. 948 new_rect.set_width(new_rect.width() + change_w); 949 new_rect.set_height(new_rect.height() + change_h); 950 951 m_resize_window->apply_minimum_size(new_rect); 952 953 if (!m_resize_window->size_increment().is_empty()) { 954 int horizontal_incs = (new_rect.width() - m_resize_window->base_size().width()) / m_resize_window->size_increment().width(); 955 new_rect.set_width(m_resize_window->base_size().width() + horizontal_incs * m_resize_window->size_increment().width()); 956 int vertical_incs = (new_rect.height() - m_resize_window->base_size().height()) / m_resize_window->size_increment().height(); 957 new_rect.set_height(m_resize_window->base_size().height() + vertical_incs * m_resize_window->size_increment().height()); 958 } 959 960 if (m_resize_window->resize_aspect_ratio().has_value()) { 961 auto& ratio = m_resize_window->resize_aspect_ratio().value(); 962 auto base_size = m_resize_window->base_size(); 963 if (abs(change_w) > abs(change_h)) { 964 new_rect.set_height(base_size.height() + (new_rect.width() - base_size.width()) * ratio.height() / ratio.width()); 965 } else { 966 new_rect.set_width(base_size.width() + (new_rect.height() - base_size.height()) * ratio.width() / ratio.height()); 967 } 968 } 969 970 // Second, set its position so that the sides of the window 971 // that end up moving are the same ones as the user is dragging, 972 // no matter which part of the logic above caused us to decide 973 // to resize by this much. 974 switch (m_resize_direction) { 975 case ResizeDirection::DownRight: 976 case ResizeDirection::Right: 977 case ResizeDirection::Down: 978 break; 979 case ResizeDirection::Left: 980 case ResizeDirection::Up: 981 case ResizeDirection::UpLeft: 982 new_rect.set_right_without_resize(m_resize_window_original_rect.right()); 983 new_rect.set_bottom_without_resize(m_resize_window_original_rect.bottom()); 984 break; 985 case ResizeDirection::UpRight: 986 new_rect.set_bottom_without_resize(m_resize_window_original_rect.bottom()); 987 break; 988 case ResizeDirection::DownLeft: 989 new_rect.set_right_without_resize(m_resize_window_original_rect.right()); 990 break; 991 default: 992 VERIFY_NOT_REACHED(); 993 } 994 995 if (m_resize_window->rect() == new_rect) 996 return true; 997 998 if (m_resize_window->is_tiled()) { 999 // Check if we should be un-tiling the window. This should happen when one side touching 1000 // the screen border changes. We need to un-tile because while it is tiled, rendering is 1001 // constrained to the screen where it's tiled on, and if one of these sides move we should 1002 // no longer constrain rendering to that screen. Changing the sides not touching a screen 1003 // border however is fine as long as the screen contains the entire window. 1004 m_resize_window->check_untile_due_to_resize(new_rect); 1005 } 1006 dbgln_if(RESIZE_DEBUG, "[WM] Resizing, original: {}, now: {}", m_resize_window_original_rect, new_rect); 1007 1008 if (m_resize_window->rect().location() != m_resize_window_original_rect.location()) { 1009 m_resize_window->set_default_positioned(false); 1010 } 1011 1012 m_resize_window->set_rect(new_rect); 1013 if (system_effects().geometry() == ShowGeometry::OnMoveAndResize || system_effects().geometry() == ShowGeometry::OnResizeOnly) { 1014 m_geometry_overlay->window_rect_changed(); 1015 } 1016 Core::EventLoop::current().post_event(*m_resize_window, make<ResizeEvent>(new_rect)); 1017 return true; 1018} 1019 1020bool WindowManager::process_ongoing_drag(MouseEvent& event) 1021{ 1022 if (!m_dnd_client) 1023 return false; 1024 1025 if (event.type() == Event::MouseMove) { 1026 m_dnd_overlay->cursor_moved(); 1027 1028 // We didn't let go of the drag yet, see if we should send some drag move events.. 1029 if (auto* window = hovered_window()) { 1030 event.set_drag(true); 1031 event.set_mime_data(*m_dnd_mime_data); 1032 deliver_mouse_event(*window, event); 1033 } else { 1034 set_accepts_drag(false); 1035 } 1036 } 1037 1038 if (!(event.type() == Event::MouseUp && event.button() == MouseButton::Primary)) 1039 return true; 1040 1041 if (auto* window = hovered_window()) { 1042 m_dnd_client->async_drag_accepted(); 1043 if (window->client()) { 1044 auto translated_event = event.translated(-window->position()); 1045 window->client()->async_drag_dropped(window->window_id(), translated_event.position(), m_dnd_text, m_dnd_mime_data->all_data()); 1046 } 1047 } else { 1048 m_dnd_client->async_drag_cancelled(); 1049 } 1050 1051 end_dnd_drag(); 1052 return true; 1053} 1054 1055void WindowManager::set_cursor_tracking_button(Button* button) 1056{ 1057 m_cursor_tracking_button = button; 1058} 1059 1060auto WindowManager::DoubleClickInfo::metadata_for_button(MouseButton button) const -> ClickMetadata const& 1061{ 1062 switch (button) { 1063 case MouseButton::Primary: 1064 return m_primary; 1065 case MouseButton::Secondary: 1066 return m_secondary; 1067 case MouseButton::Middle: 1068 return m_middle; 1069 case MouseButton::Backward: 1070 return m_backward; 1071 case MouseButton::Forward: 1072 return m_forward; 1073 default: 1074 VERIFY_NOT_REACHED(); 1075 } 1076} 1077 1078auto WindowManager::DoubleClickInfo::metadata_for_button(MouseButton button) -> ClickMetadata& 1079{ 1080 switch (button) { 1081 case MouseButton::Primary: 1082 return m_primary; 1083 case MouseButton::Secondary: 1084 return m_secondary; 1085 case MouseButton::Middle: 1086 return m_middle; 1087 case MouseButton::Backward: 1088 return m_backward; 1089 case MouseButton::Forward: 1090 return m_forward; 1091 default: 1092 VERIFY_NOT_REACHED(); 1093 } 1094} 1095 1096bool WindowManager::is_considered_doubleclick(MouseEvent const& event, DoubleClickInfo::ClickMetadata const& metadata) const 1097{ 1098 i64 elapsed_ms_since_last_click = metadata.clock.elapsed(); 1099 if (elapsed_ms_since_last_click < m_double_click_speed) { 1100 auto diff = event.position() - metadata.last_position; 1101 auto distance_travelled_squared = diff.x() * diff.x() + diff.y() * diff.y(); 1102 if (distance_travelled_squared <= (m_max_distance_for_double_click * m_max_distance_for_double_click)) 1103 return true; 1104 } 1105 return false; 1106} 1107 1108void WindowManager::system_menu_doubleclick(Window& window, MouseEvent const& event) 1109{ 1110 // This is a special case. Basically, we're trying to determine whether 1111 // double clicking on the window menu icon happened. In this case, the 1112 // WindowFrame only receives a MouseDown event, and since the window 1113 // menu pops up, it does not see the MouseUp event. But, if they subsequently 1114 // click there again, the menu is closed and we receive a MouseUp event. 1115 // So, in order to be able to detect a double click when a menu is being 1116 // opened by the MouseDown event, we need to consider the MouseDown event 1117 // as a potential double-click trigger 1118 VERIFY(event.type() == Event::MouseDown); 1119 1120 auto& metadata = m_double_click_info.metadata_for_button(event.button()); 1121 if (&window != m_double_click_info.m_clicked_window) { 1122 // we either haven't clicked anywhere, or we haven't clicked on this 1123 // window. set the current click window, and reset the timers. 1124 1125 dbgln_if(DOUBLECLICK_DEBUG, "Initial mousedown on Window({}) for menus (previous was {})", &window, m_double_click_info.m_clicked_window); 1126 1127 m_double_click_info.m_clicked_window = window; 1128 m_double_click_info.reset(); 1129 } 1130 1131 metadata.last_position = event.position(); 1132 metadata.clock.start(); 1133} 1134 1135bool WindowManager::is_menu_doubleclick(Window& window, MouseEvent const& event) const 1136{ 1137 VERIFY(event.type() == Event::MouseUp); 1138 1139 if (&window != m_double_click_info.m_clicked_window) 1140 return false; 1141 1142 auto& metadata = m_double_click_info.metadata_for_button(event.button()); 1143 if (!metadata.clock.is_valid()) 1144 return false; 1145 1146 return is_considered_doubleclick(event, metadata); 1147} 1148 1149void WindowManager::process_event_for_doubleclick(Window& window, MouseEvent& event) 1150{ 1151 // We only care about button presses (because otherwise it's not a doubleclick, duh!) 1152 VERIFY(event.type() == Event::MouseUp); 1153 1154 if (&window != m_double_click_info.m_clicked_window) { 1155 // we either haven't clicked anywhere, or we haven't clicked on this 1156 // window. set the current click window, and reset the timers. 1157 dbgln_if(DOUBLECLICK_DEBUG, "Initial mouseup on Window({}) for menus (previous was {})", &window, m_double_click_info.m_clicked_window); 1158 1159 m_double_click_info.m_clicked_window = window; 1160 m_double_click_info.reset(); 1161 } 1162 1163 auto& metadata = m_double_click_info.metadata_for_button(event.button()); 1164 1165 if (!metadata.clock.is_valid() || !is_considered_doubleclick(event, metadata)) { 1166 // either the clock is invalid because we haven't clicked on this 1167 // button on this window yet, so there's nothing to do, or this 1168 // isn't considered to be a double click. either way, restart the 1169 // clock 1170 metadata.clock.start(); 1171 } else { 1172 dbgln_if(DOUBLECLICK_DEBUG, "Transforming MouseUp to MouseDoubleClick ({} < {})!", metadata.clock.elapsed(), m_double_click_speed); 1173 1174 event = MouseEvent(Event::MouseDoubleClick, event.position(), event.buttons(), event.button(), event.modifiers(), event.wheel_delta_x(), event.wheel_delta_y()); 1175 // invalidate this now we've delivered a doubleclick, otherwise 1176 // tripleclick will deliver two doubleclick events (incorrectly). 1177 metadata.clock = {}; 1178 } 1179 1180 metadata.last_position = event.position(); 1181} 1182 1183void WindowManager::deliver_mouse_event(Window& window, MouseEvent const& event) 1184{ 1185 auto translated_event = event.translated(-window.position()); 1186 window.dispatch_event(translated_event); 1187 if (translated_event.type() == Event::MouseUp) { 1188 process_event_for_doubleclick(window, translated_event); 1189 if (translated_event.type() == Event::MouseDoubleClick) 1190 window.dispatch_event(translated_event); 1191 } 1192} 1193 1194bool WindowManager::process_ongoing_active_input_mouse_event(MouseEvent const& event) 1195{ 1196 auto* input_tracking_window = automatic_cursor_tracking_window(); 1197 if (!input_tracking_window) 1198 return false; 1199 1200 // At this point, we have delivered the start of an input sequence to a 1201 // client application. We must keep delivering to that client 1202 // application until the input sequence is done. 1203 // 1204 // This prevents e.g. moving on one window out of the bounds starting 1205 // a move in that other unrelated window, and other silly shenanigans. 1206 deliver_mouse_event(*input_tracking_window, event); 1207 1208 if (event.type() == Event::MouseUp && event.buttons() == 0) 1209 set_automatic_cursor_tracking_window(nullptr); 1210 1211 return true; 1212} 1213 1214bool WindowManager::process_mouse_event_for_titlebar_buttons(MouseEvent const& event) 1215{ 1216 if (m_cursor_tracking_button) { 1217 m_cursor_tracking_button->on_mouse_event(event.translated(-m_cursor_tracking_button->screen_rect().location())); 1218 return true; 1219 } 1220 1221 // This is quite hackish, but it's how the Button hover effect is implemented. 1222 if (m_hovered_button && event.type() == Event::MouseMove) 1223 m_hovered_button->on_mouse_event(event.translated(-m_hovered_button->screen_rect().location())); 1224 1225 return false; 1226} 1227 1228void WindowManager::process_mouse_event_for_window(HitTestResult& result, MouseEvent const& event) 1229{ 1230 auto& window = *result.window; 1231 auto* blocking_modal_window = window.blocking_modal_window(); 1232 1233 if (event.type() == Event::MouseDown) { 1234 m_mouse_down_origin = result.is_frame_hit 1235 ? event.position().translated(-window.position()) 1236 : result.window_relative_position; 1237 } 1238 1239 // First check if we should initiate a move or resize (Super+LMB or Super+RMB). 1240 // In those cases, the event is swallowed by the window manager. 1241 if (!blocking_modal_window && window.is_movable()) { 1242 if (!window.is_fullscreen() && m_keyboard_modifiers == Mod_Super && event.type() == Event::MouseDown && event.button() == MouseButton::Primary) { 1243 start_window_move(window, event); 1244 return; 1245 } 1246 if (window.is_resizable() && m_keyboard_modifiers == Mod_Super && event.type() == Event::MouseDown && event.button() == MouseButton::Secondary && !window.blocking_modal_window()) { 1247 constexpr ResizeDirection direction_for_hot_area[3][3] = { 1248 { ResizeDirection::UpLeft, ResizeDirection::Up, ResizeDirection::UpRight }, 1249 { ResizeDirection::Left, ResizeDirection::None, ResizeDirection::Right }, 1250 { ResizeDirection::DownLeft, ResizeDirection::Down, ResizeDirection::DownRight }, 1251 }; 1252 Gfx::IntRect outer_rect = window.frame().rect(); 1253 if (!outer_rect.contains(event.position())) { 1254 // FIXME: This used to be an VERIFY but crashing WindowServer over this seems silly. 1255 dbgln("FIXME: !outer_rect.contains(position): outer_rect={}, position={}", outer_rect, event.position()); 1256 } 1257 int window_relative_x = event.position().x() - outer_rect.x(); 1258 int window_relative_y = event.position().y() - outer_rect.y(); 1259 int hot_area_row = min(2, window_relative_y / (outer_rect.height() / 3)); 1260 int hot_area_column = min(2, window_relative_x / (outer_rect.width() / 3)); 1261 ResizeDirection resize_direction = direction_for_hot_area[hot_area_row][hot_area_column]; 1262 start_window_resize(window, event, resize_direction); 1263 return; 1264 } 1265 } 1266 1267 if (event.type() == Event::MouseDown) { 1268 if (window.type() == WindowType::Normal) 1269 move_to_front_and_make_active(window); 1270 else if (window.type() == WindowType::Desktop) 1271 set_active_window(&window); 1272 } 1273 1274 if (blocking_modal_window && window.type() != WindowType::Popup) { 1275 if (event.type() == Event::Type::MouseDown) { 1276 // We're clicking on something that's blocked by a modal window. 1277 // Flash the modal window to let the user know about it. 1278 blocking_modal_window->frame().start_flash_animation(); 1279 } 1280 // Don't send mouse events to windows blocked by a modal child. 1281 return; 1282 } 1283 1284 if (result.is_frame_hit) { 1285 // We are hitting the frame, pass the event along to WindowFrame. 1286 window.frame().handle_mouse_event(event.translated(-window.frame().rect().location())); 1287 return; 1288 } 1289 1290 if (!window.is_automatic_cursor_tracking()) 1291 deliver_mouse_event(window, event); 1292 1293 if (event.type() == Event::MouseDown) 1294 set_automatic_cursor_tracking_window(&window); 1295} 1296 1297void WindowManager::process_mouse_event(MouseEvent& event) 1298{ 1299 // 0. Forget the resize candidate (window that we could initiate a resize of from the current cursor position.) 1300 // A new resize candidate may be determined if we hit an appropriate part of a window. 1301 clear_resize_candidate(); 1302 1303 // 1. Process ongoing drag events. This is done first to avoid clashing with global cursor tracking. 1304 if (process_ongoing_drag(event)) 1305 return; 1306 1307 // 2. Send the mouse event to all clients with global cursor tracking enabled. 1308 ConnectionFromClient::for_each_client([&](ConnectionFromClient& conn) { 1309 if (conn.does_global_mouse_tracking()) { 1310 conn.async_track_mouse_move(event.position()); 1311 } 1312 }); 1313 // The automatic cursor tracking window is excluded here because we're sending the event to it 1314 // in the next step. 1315 for_each_visible_window_from_front_to_back([&](Window& window) { 1316 if (window.is_automatic_cursor_tracking() && &window != automatic_cursor_tracking_window()) 1317 deliver_mouse_event(window, event); 1318 return IterationDecision::Continue; 1319 }); 1320 1321 // 3. If there's an automatic cursor tracking window, all mouse events go there. 1322 // Tracking ends after all mouse buttons have been released. 1323 if (process_ongoing_active_input_mouse_event(event)) 1324 return; 1325 1326 // 4. If there's a window being moved around, take care of that. 1327 if (process_ongoing_window_move(event)) 1328 return; 1329 1330 // 5. If there's a window being resized, take care of that. 1331 if (process_ongoing_window_resize(event)) 1332 return; 1333 1334 // 6. If the event is inside a titlebar button, WindowServer implements all 1335 // the behavior for those buttons internally. 1336 if (process_mouse_event_for_titlebar_buttons(event)) 1337 return; 1338 1339 // 7. If there are menus open, deal with them now. (FIXME: This needs to be cleaned up & simplified!) 1340 bool hitting_menu_in_window_with_active_menu = [&] { 1341 if (!m_window_with_active_menu) 1342 return false; 1343 auto& frame = m_window_with_active_menu->frame(); 1344 return frame.menubar_rect().contains(event.position().translated(-frame.rect().location())); 1345 }(); 1346 1347 // FIXME: This is quite hackish, we clear the hovered menu before potentially setting the same menu 1348 // as hovered again. This makes sure that the hovered state doesn't linger after moving the 1349 // cursor away from a hovered menu. 1350 MenuManager::the().set_hovered_menu(nullptr); 1351 1352 if (MenuManager::the().has_open_menu() 1353 || hitting_menu_in_window_with_active_menu) { 1354 1355 if (!hitting_menu_in_window_with_active_menu) { 1356 MenuManager::the().dispatch_event(event); 1357 return; 1358 } 1359 } 1360 1361 // 8. Hit test the window stack to see what's under the cursor. 1362 auto result = current_window_stack().hit_test(event.position()); 1363 1364 if (!result.has_value()) 1365 return; 1366 1367 auto* event_window = result.value().window.ptr(); 1368 if (auto* popup_window = foremost_popup_window()) { 1369 if (event_window == popup_window) 1370 process_mouse_event_for_window(result.value(), event); 1371 else if (event.buttons()) 1372 popup_window->request_close(); 1373 return; 1374 } 1375 1376 process_mouse_event_for_window(result.value(), event); 1377} 1378 1379void WindowManager::reevaluate_hover_state_for_window(Window* updated_window) 1380{ 1381 if (m_dnd_client || m_resize_window || m_move_window || m_cursor_tracking_button || MenuManager::the().has_open_menu()) 1382 return; 1383 1384 auto cursor_location = ScreenInput::the().cursor_location(); 1385 auto* currently_hovered = hovered_window(); 1386 if (updated_window) { 1387 if (!(updated_window == currently_hovered || updated_window->frame().rect().contains(cursor_location) || (currently_hovered && currently_hovered->frame().rect().contains(cursor_location)))) 1388 return; 1389 } 1390 1391 Window* hovered_window = nullptr; 1392 if (auto* fullscreen_window = active_fullscreen_window()) { 1393 if (fullscreen_window->hit_test(cursor_location).has_value()) 1394 hovered_window = fullscreen_window; 1395 } else { 1396 hovered_window = current_window_stack().window_at(cursor_location); 1397 } 1398 1399 if (set_hovered_window(hovered_window)) { 1400 if (currently_hovered && m_resize_candidate == currently_hovered) 1401 clear_resize_candidate(); 1402 1403 if (hovered_window) { 1404 // Send a fake MouseMove event. This allows the new hovering window 1405 // to determine which widget we're hovering, and also update the cursor 1406 // accordingly. We do this because this re-evaluation of the currently 1407 // hovered window wasn't triggered by a mouse move event, but rather 1408 // e.g. a hit-test result change due to a transparent window repaint. 1409 if (hovered_window->hit_test(cursor_location, false).has_value()) { 1410 MouseEvent event(Event::MouseMove, cursor_location.translated(-hovered_window->rect().location()), 0, MouseButton::None, 0); 1411 hovered_window->dispatch_event(event); 1412 } else if (!hovered_window->is_frameless()) { 1413 MouseEvent event(Event::MouseMove, cursor_location.translated(-hovered_window->frame().rect().location()), 0, MouseButton::None, 0); 1414 hovered_window->frame().handle_mouse_event(event); 1415 } 1416 } 1417 } 1418} 1419 1420void WindowManager::clear_resize_candidate() 1421{ 1422 if (m_resize_candidate) 1423 Compositor::the().invalidate_cursor(); 1424 m_resize_candidate = nullptr; 1425} 1426 1427Gfx::IntRect WindowManager::desktop_rect(Screen& screen) const 1428{ 1429 if (active_fullscreen_window()) 1430 return Screen::main().rect(); // TODO: we should support fullscreen windows on any screen 1431 auto screen_rect = screen.rect(); 1432 if (screen.is_main_screen()) 1433 screen_rect.set_height(screen.height() - TaskbarWindow::taskbar_height()); 1434 return screen_rect; 1435} 1436 1437Gfx::IntRect WindowManager::arena_rect_for_type(Screen& screen, WindowType type) const 1438{ 1439 switch (type) { 1440 case WindowType::Desktop: 1441 return Screen::bounding_rect(); 1442 case WindowType::Normal: 1443 return desktop_rect(screen); 1444 case WindowType::Menu: 1445 case WindowType::WindowSwitcher: 1446 case WindowType::Taskbar: 1447 case WindowType::Tooltip: 1448 case WindowType::Applet: 1449 case WindowType::Notification: 1450 case WindowType::Popup: 1451 case WindowType::Autocomplete: 1452 return screen.rect(); 1453 default: 1454 VERIFY_NOT_REACHED(); 1455 } 1456} 1457 1458void WindowManager::event(Core::Event& event) 1459{ 1460 if (static_cast<Event&>(event).is_mouse_event()) { 1461 auto& mouse_event = static_cast<MouseEvent&>(event); 1462 if (mouse_event.type() != Event::MouseMove) 1463 m_previous_event_was_super_keydown = false; 1464 1465 process_mouse_event(mouse_event); 1466 m_last_processed_buttons = mouse_event.buttons(); 1467 auto include_window_frame = m_dnd_client ? WindowStack::IncludeWindowFrame::Yes : WindowStack::IncludeWindowFrame::No; 1468 // TODO: handle transitioning between two stacks 1469 set_hovered_window(current_window_stack().window_at(mouse_event.position(), include_window_frame)); 1470 return; 1471 } 1472 1473 if (static_cast<Event&>(event).is_key_event()) { 1474 process_key_event(static_cast<KeyEvent&>(event)); 1475 return; 1476 } 1477 1478 Core::Object::event(event); 1479} 1480 1481bool WindowManager::is_window_in_modal_chain(Window& chain_window, Window& other_window) 1482{ 1483 auto result = for_each_window_in_modal_chain(chain_window, [&](auto& window) { 1484 if (&other_window == &window) 1485 return IterationDecision::Break; 1486 return IterationDecision::Continue; 1487 }); 1488 return result; 1489} 1490 1491void WindowManager::switch_to_window_stack(WindowStack& window_stack, Window* carry_window, bool show_overlay) 1492{ 1493 m_carry_window_to_new_stack.clear(); 1494 m_switching_to_window_stack = &window_stack; 1495 if (carry_window && !is_stationary_window_type(carry_window->type()) && &carry_window->window_stack() != &window_stack) { 1496 auto& from_stack = carry_window->window_stack(); 1497 1498 for_each_visible_window_from_back_to_front([&](Window& window) { 1499 if (is_stationary_window_type(window.type())) 1500 return IterationDecision::Continue; 1501 if (&window.window_stack() != &carry_window->window_stack()) 1502 return IterationDecision::Continue; 1503 if (&window == carry_window || is_window_in_modal_chain(*carry_window, window)) 1504 m_carry_window_to_new_stack.append(window); 1505 return IterationDecision::Continue; 1506 }, 1507 &from_stack); 1508 1509 auto* from_active_window = from_stack.active_window(); 1510 bool did_carry_active_window = false; 1511 for (auto& window : m_carry_window_to_new_stack) { 1512 if (window == from_active_window) 1513 did_carry_active_window = true; 1514 window->set_moving_to_another_stack(true); 1515 VERIFY(&window->window_stack() == &from_stack); 1516 from_stack.remove(*window); 1517 window_stack.add(*window); 1518 } 1519 // Before we change to the new stack, find a new active window on the stack we're switching from 1520 if (did_carry_active_window) 1521 pick_new_active_window(from_active_window); 1522 1523 // Now switch to the new stack 1524 m_current_window_stack = &window_stack; 1525 if (did_carry_active_window && from_active_window) 1526 set_active_window(from_active_window); 1527 1528 // Because we moved windows between stacks we need to invalidate occlusions 1529 Compositor::the().invalidate_occlusions(); 1530 } else { 1531 m_current_window_stack = &window_stack; 1532 } 1533 1534 Compositor::the().switch_to_window_stack(window_stack, show_overlay); 1535} 1536 1537void WindowManager::did_switch_window_stack(Badge<Compositor>, WindowStack& previous_stack, WindowStack& new_stack) 1538{ 1539 VERIFY(&previous_stack != &new_stack); 1540 1541 // We are being notified by the compositor, it should not be switching right now! 1542 VERIFY(!Compositor::the().is_switching_window_stacks()); 1543 1544 if (m_switching_to_window_stack == &new_stack) { 1545 m_switching_to_window_stack = nullptr; 1546 if (!m_carry_window_to_new_stack.is_empty()) { 1547 // switched_to_stack may be different from the stack where the windows were 1548 // carried to when the user rapidly tries to switch stacks, so make sure to 1549 // only reset the moving flag if we arrived at our final destination 1550 for (auto& window_ref : m_carry_window_to_new_stack) { 1551 if (auto* window = window_ref.ptr()) { 1552 window->set_moving_to_another_stack(false); 1553 tell_wms_window_state_changed(*window); 1554 } 1555 } 1556 m_carry_window_to_new_stack.clear(); 1557 } 1558 } 1559 1560 auto* previous_stack_active_window = previous_stack.active_window(); 1561 auto* new_stack_active_window = new_stack.active_window(); 1562 if (previous_stack_active_window != new_stack_active_window) { 1563 if (previous_stack_active_window && is_stationary_window_type(previous_stack_active_window->type())) 1564 notify_previous_active_window(*previous_stack_active_window); 1565 if (new_stack_active_window && is_stationary_window_type(new_stack_active_window->type())) 1566 notify_new_active_window(*new_stack_active_window); 1567 } 1568 1569 if (!new_stack_active_window) 1570 pick_new_active_window(nullptr); 1571 1572 reevaluate_hover_state_for_window(); 1573 1574 tell_wms_current_window_stack_changed(); 1575} 1576 1577void WindowManager::process_key_event(KeyEvent& event) 1578{ 1579 m_keyboard_modifiers = event.modifiers(); 1580 1581 // Escape key cancels an ongoing drag. 1582 if (event.type() == Event::KeyDown && event.key() == Key_Escape && m_dnd_client) { 1583 // Notify the drag-n-drop client that the drag was cancelled. 1584 m_dnd_client->async_drag_cancelled(); 1585 1586 // Also notify the currently hovered window (if any) that the ongoing drag was cancelled. 1587 if (m_hovered_window && m_hovered_window->client() && m_hovered_window->client() != m_dnd_client) 1588 m_hovered_window->client()->async_drag_cancelled(); 1589 1590 end_dnd_drag(); 1591 return; 1592 } 1593 1594 if (event.type() == Event::KeyDown && (event.modifiers() == (Mod_Ctrl | Mod_Super | Mod_Shift) && event.key() == Key_I)) { 1595 reload_icon_bitmaps_after_scale_change(); 1596 Compositor::the().invalidate_screen(); 1597 return; 1598 } 1599 1600 if (event.type() == Event::KeyDown && event.key() == Key_Super) { 1601 m_previous_event_was_super_keydown = true; 1602 } else if (m_previous_event_was_super_keydown) { 1603 m_previous_event_was_super_keydown = false; 1604 if (!m_dnd_client && !automatic_cursor_tracking_window() && event.type() == Event::KeyUp && event.key() == Key_Super) { 1605 tell_wms_super_key_pressed(); 1606 return; 1607 } 1608 1609 if (event.type() == Event::KeyDown && event.key() == Key_Space) { 1610 tell_wms_super_space_key_pressed(); 1611 return; 1612 } 1613 1614 if (event.type() == Event::KeyDown && event.key() == Key_D) { 1615 tell_wms_super_d_key_pressed(); 1616 return; 1617 } 1618 1619 if (event.type() == Event::KeyDown && event.key() >= Key_0 && event.key() <= Key_9) { 1620 auto digit = event.key() - Key_0; 1621 tell_wms_super_digit_key_pressed(digit); 1622 return; 1623 } 1624 } 1625 1626 if (MenuManager::the().current_menu() && event.key() != Key_Super) { 1627 MenuManager::the().dispatch_event(event); 1628 return; 1629 } 1630 1631 if (event.type() == Event::KeyDown) { 1632 if ((event.modifiers() == Mod_Super && event.key() == Key_Tab) || (event.modifiers() == (Mod_Super | Mod_Shift) && event.key() == Key_Tab)) 1633 m_switcher->show(WindowSwitcher::Mode::ShowAllWindows); 1634 else if ((event.modifiers() == Mod_Alt && event.key() == Key_Tab) || (event.modifiers() == (Mod_Alt | Mod_Shift) && event.key() == Key_Tab)) 1635 m_switcher->show(WindowSwitcher::Mode::ShowCurrentDesktop); 1636 } 1637 if (m_switcher->is_visible()) { 1638 request_close_fragile_windows(); 1639 m_switcher->on_key_event(event); 1640 return; 1641 } 1642 1643 if (event.type() == Event::KeyDown && (event.modifiers() == (Mod_Alt | Mod_Shift) && (event.key() == Key_Shift || event.key() == Key_Alt))) { 1644 m_keymap_switcher->next_keymap(); 1645 return; 1646 } 1647 1648 if (event.type() == Event::KeyDown && (event.modifiers() == (Mod_Ctrl | Mod_Alt) || event.modifiers() == (Mod_Ctrl | Mod_Shift | Mod_Alt)) && (window_stack_columns() > 1 || window_stack_rows() > 1)) { 1649 auto& current_stack = current_window_stack(); 1650 auto row = current_stack.row(); 1651 auto column = current_stack.column(); 1652 auto handle_window_stack_switch_key = [&]() { 1653 switch (event.key()) { 1654 case Key_Left: 1655 if (column == 0) 1656 return true; 1657 column--; 1658 return true; 1659 case Key_Right: 1660 if (column + 1 >= m_window_stacks[0]->size()) 1661 return true; 1662 column++; 1663 return true; 1664 case Key_Up: 1665 if (row == 0) 1666 return true; 1667 row--; 1668 return true; 1669 case Key_Down: 1670 if (row + 1 >= m_window_stacks.size()) 1671 return true; 1672 row++; 1673 return true; 1674 default: 1675 return false; 1676 } 1677 }; 1678 if (handle_window_stack_switch_key()) { 1679 request_close_fragile_windows(); 1680 Window* carry_window = nullptr; 1681 auto& new_window_stack = *(*m_window_stacks[row])[column]; 1682 if (&new_window_stack != &current_stack) { 1683 if (event.modifiers() == (Mod_Ctrl | Mod_Shift | Mod_Alt)) 1684 carry_window = this->active_window(); 1685 } 1686 // Call switch_to_window_stack even if we're not going to switch to another stack. 1687 // We'll show the window stack switch overlay briefly! 1688 switch_to_window_stack(new_window_stack, carry_window); 1689 return; 1690 } 1691 } 1692 1693 auto* event_window = current_window_stack().active_window(); 1694 if (auto* popup_window = foremost_popup_window()) 1695 event_window = popup_window; 1696 if (!event_window) 1697 return; 1698 1699 if (event.type() == Event::KeyDown && event.modifiers() == Mod_Super) { 1700 if (event.key() == Key_H) { 1701 m_cursor_highlight_enabled = !m_cursor_highlight_enabled; 1702 Compositor::the().invalidate_cursor(); 1703 return; 1704 } 1705 if (event_window->type() != WindowType::Desktop) { 1706 if (event.key() == Key_Down) { 1707 if (event_window->is_resizable() && event_window->is_maximized()) { 1708 maximize_windows(*event_window, false); 1709 return; 1710 } 1711 if (event_window->is_minimizable() && !event_window->is_modal()) 1712 minimize_windows(*event_window, true); 1713 return; 1714 } 1715 if (event_window->is_resizable()) { 1716 if (event.key() == Key_Up) { 1717 maximize_windows(*event_window, !event_window->is_maximized()); 1718 return; 1719 } 1720 if (event.key() == Key_Left) { 1721 if (event_window->tile_type() == WindowTileType::Left) { 1722 event_window->set_untiled(); 1723 return; 1724 } 1725 if (event_window->is_maximized()) 1726 maximize_windows(*event_window, false); 1727 event_window->set_tiled(WindowTileType::Left); 1728 return; 1729 } 1730 if (event.key() == Key_Right) { 1731 if (event_window->tile_type() == WindowTileType::Right) { 1732 event_window->set_untiled(); 1733 return; 1734 } 1735 if (event_window->is_maximized()) 1736 maximize_windows(*event_window, false); 1737 event_window->set_tiled(WindowTileType::Right); 1738 return; 1739 } 1740 } 1741 } 1742 } 1743 1744 if (event.type() == Event::KeyDown && event.modifiers() == (Mod_Super | Mod_Alt) && event_window->type() != WindowType::Desktop) { 1745 if (event_window->is_resizable()) { 1746 if (event.key() == Key_Right || event.key() == Key_Left) { 1747 if (event_window->tile_type() == WindowTileType::HorizontallyMaximized) { 1748 event_window->set_untiled(); 1749 return; 1750 } 1751 if (event_window->is_maximized()) 1752 maximize_windows(*event_window, false); 1753 event_window->set_tiled(WindowTileType::HorizontallyMaximized); 1754 return; 1755 } 1756 if (event.key() == Key_Up || event.key() == Key_Down) { 1757 if (event_window->tile_type() == WindowTileType::VerticallyMaximized) { 1758 event_window->set_untiled(); 1759 return; 1760 } 1761 if (event_window->is_maximized()) 1762 maximize_windows(*event_window, false); 1763 event_window->set_tiled(WindowTileType::VerticallyMaximized); 1764 return; 1765 } 1766 } 1767 } 1768 1769 event_window->dispatch_event(event); 1770} 1771 1772void WindowManager::set_highlight_window(Window* new_highlight_window) 1773{ 1774 // NOTE: The highlight window is global across all stacks. That's because we 1775 // can only have one and we want to be able to highlight it during transitions 1776 auto* previous_highlight_window = highlight_window(); 1777 if (new_highlight_window == previous_highlight_window) 1778 return; 1779 if (!new_highlight_window) 1780 m_highlight_window = nullptr; 1781 else 1782 m_highlight_window = new_highlight_window->make_weak_ptr<Window>(); 1783 1784 if (previous_highlight_window) { 1785 reevaluate_hover_state_for_window(previous_highlight_window); 1786 previous_highlight_window->invalidate(true, true); 1787 Compositor::the().invalidate_screen(previous_highlight_window->frame().render_rect()); 1788 } 1789 if (new_highlight_window) { 1790 reevaluate_hover_state_for_window(new_highlight_window); 1791 new_highlight_window->invalidate(true, true); 1792 Compositor::the().invalidate_screen(new_highlight_window->frame().render_rect()); 1793 } 1794 1795 if (active_fullscreen_window()) { 1796 // Find the Taskbar window and invalidate it so it draws correctly 1797 for_each_visible_window_from_back_to_front([](Window& window) { 1798 if (window.type() == WindowType::Taskbar) { 1799 window.invalidate(); 1800 return IterationDecision::Break; 1801 } 1802 return IterationDecision::Continue; 1803 }); 1804 } 1805 1806 // Invalidate occlusions in case the state change changes geometry 1807 Compositor::the().invalidate_occlusions(); 1808} 1809 1810static bool window_type_can_become_active(WindowType type) 1811{ 1812 return type == WindowType::Normal || type == WindowType::Desktop; 1813} 1814 1815void WindowManager::set_active_window(Window* new_active_window) 1816{ 1817 if (new_active_window) { 1818 if (auto* blocker = new_active_window->blocking_modal_window()) { 1819 VERIFY(blocker->is_modal()); 1820 VERIFY(blocker != new_active_window); 1821 new_active_window = blocker; 1822 } 1823 1824 if (!window_type_can_become_active(new_active_window->type())) 1825 return; 1826 } 1827 1828 auto& window_stack = current_window_stack(); 1829 if (new_active_window == window_stack.active_window()) 1830 return; 1831 1832 if (auto* previously_active_window = window_stack.active_window()) { 1833 window_stack.set_active_window(nullptr); 1834 set_automatic_cursor_tracking_window(nullptr); 1835 notify_previous_active_window(*previously_active_window); 1836 } 1837 1838 if (new_active_window) { 1839 request_close_fragile_windows(); 1840 window_stack.set_active_window(new_active_window); 1841 notify_new_active_window(*new_active_window); 1842 reevaluate_hover_state_for_window(new_active_window); 1843 } 1844 1845 // Window shapes may have changed (e.g. shadows for inactive/active windows) 1846 Compositor::the().invalidate_occlusions(); 1847} 1848 1849void WindowManager::notify_new_active_window(Window& new_active_window) 1850{ 1851 Core::EventLoop::current().post_event(new_active_window, make<Event>(Event::WindowActivated)); 1852 new_active_window.invalidate(true, true); 1853 tell_wms_window_state_changed(new_active_window); 1854} 1855 1856void WindowManager::notify_previous_active_window(Window& previously_active_window) 1857{ 1858 for (auto& child_window : previously_active_window.child_windows()) { 1859 if (child_window && child_window->type() == WindowType::Tooltip) 1860 child_window->request_close(); 1861 } 1862 Core::EventLoop::current().post_event(previously_active_window, make<Event>(Event::WindowDeactivated)); 1863 previously_active_window.invalidate(true, true); 1864 1865 tell_wms_window_state_changed(previously_active_window); 1866} 1867 1868void WindowManager::notify_active_window_input_preempted() 1869{ 1870 if (active_window()) 1871 Core::EventLoop::current().post_event(*active_window(), make<Event>(Event::WindowInputPreempted)); 1872} 1873 1874void WindowManager::notify_active_window_input_restored() 1875{ 1876 if (active_window()) 1877 Core::EventLoop::current().post_event(*active_window(), make<Event>(Event::WindowInputRestored)); 1878} 1879 1880bool WindowManager::set_hovered_window(Window* window) 1881{ 1882 if (m_hovered_window == window) 1883 return false; 1884 1885 if (m_hovered_window) 1886 Core::EventLoop::current().post_event(*m_hovered_window, make<Event>(Event::WindowLeft)); 1887 1888 m_hovered_window = window; 1889 1890 if (m_hovered_window) 1891 Core::EventLoop::current().post_event(*m_hovered_window, make<Event>(Event::WindowEntered)); 1892 return true; 1893} 1894 1895ConnectionFromClient const* WindowManager::active_client() const 1896{ 1897 if (auto* window = const_cast<WindowManager*>(this)->current_window_stack().active_window()) 1898 return window->client(); 1899 return nullptr; 1900} 1901 1902Cursor const& WindowManager::active_cursor() const 1903{ 1904 if (m_dnd_client) { 1905 if (m_dnd_accepts_drag) 1906 return *m_drag_copy_cursor; 1907 return *m_drag_cursor; 1908 } 1909 1910 if (m_move_window) 1911 return *m_move_cursor; 1912 1913 if (m_resize_window || m_resize_candidate) { 1914 switch (m_resize_direction) { 1915 case ResizeDirection::Up: 1916 case ResizeDirection::Down: 1917 return *m_resize_vertically_cursor; 1918 case ResizeDirection::Left: 1919 case ResizeDirection::Right: 1920 return *m_resize_horizontally_cursor; 1921 case ResizeDirection::UpLeft: 1922 case ResizeDirection::DownRight: 1923 return *m_resize_diagonally_tlbr_cursor; 1924 case ResizeDirection::UpRight: 1925 case ResizeDirection::DownLeft: 1926 return *m_resize_diagonally_bltr_cursor; 1927 case ResizeDirection::None: 1928 break; 1929 default: 1930 VERIFY_NOT_REACHED(); 1931 break; 1932 } 1933 } 1934 1935 if (m_automatic_cursor_tracking_window) { 1936 if (m_automatic_cursor_tracking_window->cursor()) 1937 return *m_automatic_cursor_tracking_window->cursor(); 1938 } else if (m_hovered_window) { 1939 if (auto* modal_window = const_cast<Window&>(*m_hovered_window).blocking_modal_window()) { 1940 if (modal_window->cursor()) 1941 return *modal_window->cursor(); 1942 } else if (m_hovered_window->cursor()) { 1943 return *m_hovered_window->cursor(); 1944 } 1945 } 1946 1947 return *m_arrow_cursor; 1948} 1949 1950void WindowManager::set_hovered_button(Button* button) 1951{ 1952 m_hovered_button = button; 1953} 1954 1955void WindowManager::set_resize_candidate(Window& window, ResizeDirection direction) 1956{ 1957 m_resize_candidate = window; 1958 m_resize_direction = direction; 1959} 1960 1961ResizeDirection WindowManager::resize_direction_of_window(Window const& window) 1962{ 1963 if (&window != m_resize_window) 1964 return ResizeDirection::None; 1965 return m_resize_direction; 1966} 1967 1968Gfx::IntRect WindowManager::tiled_window_rect(Window const& window, WindowTileType tile_type, bool relative_to_window_screen) const 1969{ 1970 VERIFY(tile_type != WindowTileType::None); 1971 1972 auto& screen = Screen::closest_to_rect(window.frame().rect()); 1973 auto rect = desktop_rect(screen); 1974 1975 if (tile_type == WindowTileType::Maximized) { 1976 auto border_thickness = palette().window_border_thickness(); 1977 rect.inflate(border_thickness * 2, border_thickness * 2); 1978 } 1979 1980 if (tile_type == WindowTileType::Left 1981 || tile_type == WindowTileType::TopLeft 1982 || tile_type == WindowTileType::BottomLeft) { 1983 rect.set_width(rect.width() / 2); 1984 } 1985 1986 if (tile_type == WindowTileType::Right 1987 || tile_type == WindowTileType::TopRight 1988 || tile_type == WindowTileType::BottomRight) { 1989 rect.set_width(rect.width() / 2); 1990 rect.set_x(rect.width()); 1991 } 1992 1993 if (tile_type == WindowTileType::Top 1994 || tile_type == WindowTileType::TopLeft 1995 || tile_type == WindowTileType::TopRight) { 1996 rect.set_height(rect.height() / 2); 1997 } 1998 1999 if (tile_type == WindowTileType::Bottom 2000 || tile_type == WindowTileType::BottomLeft 2001 || tile_type == WindowTileType::BottomRight) { 2002 auto half_screen_remainder = rect.height() % 2; 2003 rect.set_height(rect.height() / 2 + half_screen_remainder); 2004 rect.set_y(rect.height() - half_screen_remainder); 2005 } 2006 2007 Gfx::IntRect window_rect = window.rect(); 2008 Gfx::IntRect window_frame_rect = window.frame().rect(); 2009 2010 if (tile_type == WindowTileType::VerticallyMaximized) { 2011 rect.set_x(window_rect.x()); 2012 rect.set_width(window_rect.width()); 2013 } else { 2014 rect.set_x(rect.x() + window_rect.x() - window_frame_rect.x()); 2015 rect.set_width(rect.width() - window_frame_rect.width() + window_rect.width()); 2016 } 2017 2018 if (tile_type == WindowTileType::HorizontallyMaximized) { 2019 rect.set_y(window_rect.y()); 2020 rect.set_height(window_rect.height()); 2021 } else { 2022 rect.set_y(rect.y() + window_rect.y() - window_frame_rect.y()); 2023 rect.set_height(rect.height() - window_frame_rect.height() + window_rect.height()); 2024 } 2025 2026 if (relative_to_window_screen) 2027 rect.translate_by(-screen.rect().location()); 2028 return rect; 2029} 2030 2031void WindowManager::start_dnd_drag(ConnectionFromClient& client, DeprecatedString const& text, Gfx::Bitmap const* bitmap, Core::MimeData const& mime_data) 2032{ 2033 VERIFY(!m_dnd_client); 2034 m_dnd_client = client; 2035 m_dnd_text = text; 2036 Compositor::the().invalidate_cursor(true); 2037 m_dnd_overlay = Compositor::the().create_overlay<DndOverlay>(text, bitmap); 2038 m_dnd_overlay->set_enabled(true); 2039 m_dnd_mime_data = mime_data; 2040 set_automatic_cursor_tracking_window(nullptr); 2041} 2042 2043void WindowManager::end_dnd_drag() 2044{ 2045 VERIFY(m_dnd_client); 2046 Compositor::the().invalidate_cursor(); 2047 m_dnd_client = nullptr; 2048 m_dnd_text = {}; 2049 m_dnd_overlay = nullptr; 2050 m_dnd_accepts_drag = false; 2051} 2052 2053void WindowManager::set_accepts_drag(bool accepts) 2054{ 2055 VERIFY(m_dnd_client); 2056 m_dnd_accepts_drag = accepts; 2057 Compositor::the().invalidate_cursor(); 2058} 2059 2060void WindowManager::invalidate_after_theme_or_font_change() 2061{ 2062 Compositor::the().set_background_color(g_config->read_entry("Background", "Color", palette().desktop_background().to_deprecated_string())); 2063 WindowFrame::reload_config(); 2064 for_each_window_stack([&](auto& window_stack) { 2065 window_stack.for_each_window([&](Window& window) { 2066 window.frame().theme_changed(); 2067 window.menubar().font_changed(window.rect()); 2068 return IterationDecision::Continue; 2069 }); 2070 return IterationDecision::Continue; 2071 }); 2072 ConnectionFromClient::for_each_client([&](ConnectionFromClient& client) { 2073 client.notify_about_theme_change(); 2074 }); 2075 MenuManager::the().did_change_theme(); 2076 AppletManager::the().did_change_theme(); 2077 Compositor::the().invalidate_after_theme_or_font_change(); 2078} 2079 2080bool WindowManager::update_theme(DeprecatedString theme_path, DeprecatedString theme_name, bool keep_desktop_background, Optional<DeprecatedString> const& color_scheme_path) 2081{ 2082 auto error_or_new_theme = Gfx::load_system_theme(theme_path, color_scheme_path); 2083 if (error_or_new_theme.is_error()) { 2084 dbgln("WindowManager: Updating theme failed, error {}", error_or_new_theme.error()); 2085 return false; 2086 } 2087 auto new_theme = error_or_new_theme.release_value(); 2088 m_theme_overridden = false; 2089 Gfx::set_system_theme(new_theme); 2090 m_palette = Gfx::PaletteImpl::create_with_anonymous_buffer(new_theme); 2091 g_config->write_entry("Theme", "Name", theme_name); 2092 if (color_scheme_path.has_value() && color_scheme_path.value() != "Custom"sv) { 2093 g_config->write_bool_entry("Theme", "LoadCustomColorScheme", true); 2094 g_config->write_entry("Theme", "CustomColorSchemePath", color_scheme_path.value()); 2095 m_preferred_color_scheme = color_scheme_path.value(); 2096 } else if (!color_scheme_path.has_value()) { 2097 g_config->write_bool_entry("Theme", "LoadCustomColorScheme", false); 2098 g_config->remove_entry("Theme", "CustomColorSchemePath"); 2099 m_preferred_color_scheme = OptionalNone(); 2100 } 2101 if (!keep_desktop_background) 2102 g_config->remove_entry("Background", "Color"); 2103 if (!sync_config_to_disk()) 2104 return false; 2105 invalidate_after_theme_or_font_change(); 2106 return true; 2107} 2108 2109bool WindowManager::set_theme_override(Core::AnonymousBuffer const& theme_override) 2110{ 2111 if (!theme_override.is_valid()) 2112 return false; 2113 m_theme_overridden = true; 2114 Gfx::set_system_theme(theme_override); 2115 m_palette = Gfx::PaletteImpl::create_with_anonymous_buffer(theme_override); 2116 invalidate_after_theme_or_font_change(); 2117 return true; 2118} 2119 2120Optional<Core::AnonymousBuffer> WindowManager::get_theme_override() const 2121{ 2122 if (!m_theme_overridden) 2123 return {}; 2124 return Gfx::current_system_theme_buffer(); 2125} 2126 2127void WindowManager::clear_theme_override() 2128{ 2129 m_theme_overridden = false; 2130 auto previous_theme_name = g_config->read_entry("Theme", "Name"); 2131 auto previous_theme = MUST(Gfx::load_system_theme(DeprecatedString::formatted("/res/themes/{}.ini", previous_theme_name), m_preferred_color_scheme)); 2132 Gfx::set_system_theme(previous_theme); 2133 m_palette = Gfx::PaletteImpl::create_with_anonymous_buffer(previous_theme); 2134 invalidate_after_theme_or_font_change(); 2135} 2136 2137void WindowManager::did_popup_a_menu(Badge<Menu>) 2138{ 2139 // Clear any ongoing input gesture 2140 auto* window = automatic_cursor_tracking_window(); 2141 if (!window) 2142 return; 2143 window->set_automatic_cursor_tracking_enabled(false); 2144 set_automatic_cursor_tracking_window(nullptr); 2145} 2146 2147void WindowManager::minimize_windows(Window& window, bool minimized) 2148{ 2149 for_each_window_in_modal_chain(window, [&](auto& w) { 2150 w.set_minimized(minimized); 2151 return IterationDecision::Continue; 2152 }); 2153} 2154 2155void WindowManager::hide_windows(Window& window, bool hidden) 2156{ 2157 for_each_window_in_modal_chain(window, [&](auto& w) { 2158 w.set_hidden(hidden); 2159 if (!hidden) 2160 pick_new_active_window(&window); 2161 return IterationDecision::Continue; 2162 }); 2163} 2164 2165void WindowManager::maximize_windows(Window& window, bool maximized) 2166{ 2167 for_each_window_in_modal_chain(window, [&](auto& w) { 2168 if (&window == &w) { 2169 window.set_maximized(maximized); 2170 return IterationDecision::Continue; 2171 } 2172 if (w.is_minimized()) 2173 w.set_minimized(false); 2174 return IterationDecision::Continue; 2175 }); 2176} 2177 2178void WindowManager::set_always_on_top(Window& window, bool always_on_top) 2179{ 2180 for_each_window_in_modal_chain(window, [&](auto& w) { 2181 w.set_always_on_top(always_on_top); 2182 return IterationDecision::Continue; 2183 }); 2184} 2185 2186Gfx::IntPoint WindowManager::get_recommended_window_position(Gfx::IntPoint desired) 2187{ 2188 // FIXME: Find a better source for the width and height to shift by. 2189 Gfx::IntPoint shift(8, Gfx::WindowTheme::current().titlebar_height(Gfx::WindowTheme::WindowType::Normal, Gfx::WindowTheme::WindowMode::Other, palette()) + 10); 2190 2191 Window const* overlap_window = nullptr; 2192 current_window_stack().for_each_visible_window_of_type_from_front_to_back(WindowType::Normal, [&](Window& window) { 2193 if (window.is_default_positioned() && (!overlap_window || overlap_window->window_id() < window.window_id())) { 2194 overlap_window = &window; 2195 } 2196 return IterationDecision::Continue; 2197 }); 2198 2199 Gfx::IntPoint point; 2200 if (overlap_window) { 2201 auto& screen = Screen::closest_to_location(desired); 2202 auto available_rect = desktop_rect(screen); 2203 point = overlap_window->position() + shift; 2204 point = { point.x() % screen.width(), 2205 (point.y() >= available_rect.height()) 2206 ? Gfx::WindowTheme::current().titlebar_height(Gfx::WindowTheme::WindowType::Normal, Gfx::WindowTheme::WindowMode::Other, palette()) 2207 : point.y() }; 2208 } else { 2209 point = desired; 2210 } 2211 2212 return point; 2213} 2214 2215void WindowManager::reload_icon_bitmaps_after_scale_change() 2216{ 2217 reload_config(); 2218 for_each_window_stack([&](auto& window_stack) { 2219 window_stack.for_each_window([&](Window& window) { 2220 auto& window_frame = window.frame(); 2221 window_frame.theme_changed(); 2222 return IterationDecision::Continue; 2223 }); 2224 return IterationDecision::Continue; 2225 }); 2226} 2227 2228void WindowManager::set_window_with_active_menu(Window* window) 2229{ 2230 if (m_window_with_active_menu == window) 2231 return; 2232 if (window) 2233 m_window_with_active_menu = window->make_weak_ptr<Window>(); 2234 else 2235 m_window_with_active_menu = nullptr; 2236} 2237 2238WindowStack& WindowManager::get_rendering_window_stacks(WindowStack*& transitioning_window_stack) 2239{ 2240 return Compositor::the().get_rendering_window_stacks(transitioning_window_stack); 2241} 2242 2243void WindowManager::apply_cursor_theme(DeprecatedString const& theme_name) 2244{ 2245 auto theme_path = DeprecatedString::formatted("/res/cursor-themes/{}/{}", theme_name, "Config.ini"); 2246 auto cursor_theme_config_or_error = Core::ConfigFile::open(theme_path); 2247 if (cursor_theme_config_or_error.is_error()) { 2248 dbgln("Unable to open cursor theme '{}': {}", theme_path, cursor_theme_config_or_error.error()); 2249 return; 2250 } 2251 auto cursor_theme_config = cursor_theme_config_or_error.release_value(); 2252 2253 auto* current_cursor = Compositor::the().current_cursor(); 2254 auto reload_cursor = [&](RefPtr<Cursor const>& cursor, DeprecatedString const& name) { 2255 bool is_current_cursor = current_cursor && current_cursor == cursor.ptr(); 2256 2257 static auto const s_default_cursor_path = "/res/cursor-themes/Default/arrow.x2y2.png"sv; 2258 cursor = Cursor::create(DeprecatedString::formatted("/res/cursor-themes/{}/{}", theme_name, cursor_theme_config->read_entry("Cursor", name)), s_default_cursor_path); 2259 2260 if (is_current_cursor) { 2261 Compositor::the().current_cursor_was_reloaded(cursor.ptr()); 2262 2263 if (m_hovered_window) { 2264 if (auto* modal_window = const_cast<Window&>(*m_hovered_window).blocking_modal_window()) { 2265 modal_window->set_cursor(cursor); 2266 } else if (m_hovered_window->cursor()) { 2267 m_hovered_window->set_cursor(cursor); 2268 } 2269 } 2270 } 2271 }; 2272 2273 reload_cursor(m_hidden_cursor, "Hidden"); 2274 reload_cursor(m_arrow_cursor, "Arrow"); 2275 reload_cursor(m_hand_cursor, "Hand"); 2276 reload_cursor(m_help_cursor, "Help"); 2277 reload_cursor(m_resize_horizontally_cursor, "ResizeH"); 2278 reload_cursor(m_resize_vertically_cursor, "ResizeV"); 2279 reload_cursor(m_resize_diagonally_tlbr_cursor, "ResizeDTLBR"); 2280 reload_cursor(m_resize_diagonally_bltr_cursor, "ResizeDBLTR"); 2281 reload_cursor(m_resize_column_cursor, "ResizeColumn"); 2282 reload_cursor(m_resize_row_cursor, "ResizeRow"); 2283 reload_cursor(m_i_beam_cursor, "IBeam"); 2284 reload_cursor(m_disallowed_cursor, "Disallowed"); 2285 reload_cursor(m_move_cursor, "Move"); 2286 reload_cursor(m_drag_cursor, "Drag"); 2287 reload_cursor(m_drag_copy_cursor, "DragCopy"); 2288 reload_cursor(m_wait_cursor, "Wait"); 2289 reload_cursor(m_crosshair_cursor, "Crosshair"); 2290 reload_cursor(m_eyedropper_cursor, "Eyedropper"); 2291 reload_cursor(m_zoom_cursor, "Zoom"); 2292 2293 Compositor::the().invalidate_cursor(); 2294 g_config->write_entry("Mouse", "CursorTheme", theme_name); 2295 sync_config_to_disk(); 2296} 2297 2298void WindowManager::set_cursor_highlight_radius(int radius) 2299{ 2300 // TODO: Validate radius 2301 m_cursor_highlight_radius = radius; 2302 Compositor::the().invalidate_cursor(); 2303 g_config->write_num_entry("Mouse", "CursorHighlightRadius", radius); 2304 sync_config_to_disk(); 2305} 2306 2307void WindowManager::set_cursor_highlight_color(Gfx::Color color) 2308{ 2309 m_cursor_highlight_color = color; 2310 Compositor::the().invalidate_cursor(); 2311 g_config->write_entry("Mouse", "CursorHighlightColor", color.to_deprecated_string()); 2312 sync_config_to_disk(); 2313} 2314 2315void WindowManager::apply_system_effects(Vector<bool> effects, ShowGeometry geometry) 2316{ 2317 if (m_system_effects == SystemEffects { effects, geometry }) 2318 return; 2319 2320 m_system_effects = { effects, geometry }; 2321 g_config->write_bool_entry("Effects", "AnimateMenus", m_system_effects.animate_menus()); 2322 g_config->write_bool_entry("Effects", "FlashMenus", m_system_effects.flash_menus()); 2323 g_config->write_bool_entry("Effects", "AnimateWindows", m_system_effects.animate_windows()); 2324 g_config->write_bool_entry("Effects", "SmoothScrolling", m_system_effects.smooth_scrolling()); 2325 g_config->write_bool_entry("Effects", "TabAccents", m_system_effects.tab_accents()); 2326 g_config->write_bool_entry("Effects", "SplitterKnurls", m_system_effects.splitter_knurls()); 2327 g_config->write_bool_entry("Effects", "Tooltips", m_system_effects.tooltips()); 2328 g_config->write_bool_entry("Effects", "MenuShadow", m_system_effects.menu_shadow()); 2329 g_config->write_bool_entry("Effects", "WindowShadow", m_system_effects.window_shadow()); 2330 g_config->write_bool_entry("Effects", "TooltipShadow", m_system_effects.tooltip_shadow()); 2331 g_config->write_entry("Effects", "ShowGeometry", ShowGeometryTools::enum_to_string(geometry)); 2332 sync_config_to_disk(); 2333} 2334 2335void WindowManager::load_system_effects() 2336{ 2337 Vector<bool> effects = { 2338 g_config->read_bool_entry("Effects", "AnimateMenus", true), 2339 g_config->read_bool_entry("Effects", "FlashMenus", true), 2340 g_config->read_bool_entry("Effects", "AnimateWindows", true), 2341 g_config->read_bool_entry("Effects", "SmoothScrolling", true), 2342 g_config->read_bool_entry("Effects", "TabAccents", true), 2343 g_config->read_bool_entry("Effects", "SplitterKnurls", true), 2344 g_config->read_bool_entry("Effects", "Tooltips", true), 2345 g_config->read_bool_entry("Effects", "MenuShadow", true), 2346 g_config->read_bool_entry("Effects", "WindowShadow", true), 2347 g_config->read_bool_entry("Effects", "TooltipShadow", true) 2348 }; 2349 ShowGeometry geometry = ShowGeometryTools::string_to_enum(g_config->read_entry("Effects", "ShowGeometry", "OnMoveAndResize")); 2350 m_system_effects = { effects, geometry }; 2351 2352 ConnectionFromClient::for_each_client([&](auto& client) { 2353 client.async_update_system_effects(effects); 2354 }); 2355} 2356 2357bool WindowManager::sync_config_to_disk() 2358{ 2359 if (auto result = g_config->sync(); result.is_error()) { 2360 dbgln("Failed to save config file: {}", result.error()); 2361 return false; 2362 } 2363 return true; 2364} 2365 2366Window* WindowManager::foremost_popup_window(WindowStack& stack) 2367{ 2368 Window* popup_window = nullptr; 2369 for_each_visible_window_from_front_to_back([&](Window& window) { 2370 if (window.type() == WindowType::Popup) { 2371 popup_window = &window; 2372 return IterationDecision::Break; 2373 } 2374 return IterationDecision::Continue; 2375 }, 2376 &stack); 2377 return popup_window; 2378} 2379 2380void WindowManager::request_close_fragile_windows(WindowStack& stack) 2381{ 2382 for_each_visible_window_from_back_to_front([&](Window& window) { 2383 if (is_fragile_window_type(window.type())) 2384 window.request_close(); 2385 return IterationDecision::Continue; 2386 }, 2387 &stack); 2388} 2389 2390}