Serenity Operating System
at hosted 441 lines 14 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 "Window.h" 28#include "ClientConnection.h" 29#include "Event.h" 30#include "EventLoop.h" 31#include "Screen.h" 32#include "WindowClientEndpoint.h" 33#include "WindowManager.h" 34#include <AK/Badge.h> 35 36namespace WindowServer { 37 38static String default_window_icon_path() 39{ 40 return "/res/icons/16x16/window.png"; 41} 42 43static Gfx::Bitmap& default_window_icon() 44{ 45 static Gfx::Bitmap* s_icon; 46 if (!s_icon) 47 s_icon = Gfx::Bitmap::load_from_file(default_window_icon_path()).leak_ref(); 48 return *s_icon; 49} 50 51Window::Window(Core::Object& parent, WindowType type) 52 : Core::Object(&parent) 53 , m_type(type) 54 , m_icon(default_window_icon()) 55 , m_frame(*this) 56{ 57 WindowManager::the().add_window(*this); 58} 59 60Window::Window(ClientConnection& client, WindowType window_type, int window_id, bool modal, bool minimizable, bool resizable, bool fullscreen) 61 : Core::Object(&client) 62 , m_client(&client) 63 , m_type(window_type) 64 , m_modal(modal) 65 , m_minimizable(minimizable) 66 , m_resizable(resizable) 67 , m_fullscreen(fullscreen) 68 , m_window_id(window_id) 69 , m_client_id(client.client_id()) 70 , m_icon(default_window_icon()) 71 , m_frame(*this) 72{ 73 // FIXME: This should not be hard-coded here. 74 if (m_type == WindowType::Taskbar) { 75 m_wm_event_mask = WMEventMask::WindowStateChanges | WMEventMask::WindowRemovals | WMEventMask::WindowIconChanges; 76 m_listens_to_wm_events = true; 77 } 78 79 WindowManager::the().add_window(*this); 80} 81 82Window::~Window() 83{ 84 // Detach from client at the start of teardown since we don't want 85 // to confuse things by trying to send messages to it. 86 m_client = nullptr; 87 88 WindowManager::the().remove_window(*this); 89} 90 91void Window::set_title(const String& title) 92{ 93 if (m_title == title) 94 return; 95 m_title = title; 96 WindowManager::the().notify_title_changed(*this); 97} 98 99void Window::set_rect(const Gfx::Rect& rect) 100{ 101 ASSERT(!rect.is_empty()); 102 Gfx::Rect old_rect; 103 if (m_rect == rect) 104 return; 105 old_rect = m_rect; 106 m_rect = rect; 107 if (!m_client && (!m_backing_store || old_rect.size() != rect.size())) { 108 m_backing_store = Gfx::Bitmap::create(Gfx::BitmapFormat::RGB32, m_rect.size()); 109 } 110 m_frame.notify_window_rect_changed(old_rect, rect); 111} 112 113void Window::handle_mouse_event(const MouseEvent& event) 114{ 115 set_automatic_cursor_tracking_enabled(event.buttons() != 0); 116 117 switch (event.type()) { 118 case Event::MouseMove: 119 m_client->post_message(Messages::WindowClient::MouseMove(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta(), event.is_drag(), event.drag_data_type())); 120 break; 121 case Event::MouseDown: 122 m_client->post_message(Messages::WindowClient::MouseDown(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta())); 123 break; 124 case Event::MouseDoubleClick: 125 m_client->post_message(Messages::WindowClient::MouseDoubleClick(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta())); 126 break; 127 case Event::MouseUp: 128 m_client->post_message(Messages::WindowClient::MouseUp(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta())); 129 break; 130 case Event::MouseWheel: 131 m_client->post_message(Messages::WindowClient::MouseWheel(m_window_id, event.position(), (u32)event.button(), event.buttons(), event.modifiers(), event.wheel_delta())); 132 break; 133 default: 134 ASSERT_NOT_REACHED(); 135 } 136} 137 138void Window::update_menu_item_text(PopupMenuItem item) 139{ 140 if (m_window_menu) { 141 m_window_menu->item((int)item).set_text(item == PopupMenuItem::Minimize ? (m_minimized ? "Unminimize" : "Minimize") : (m_maximized ? "Restore" : "Maximize")); 142 m_window_menu->redraw(); 143 } 144} 145 146void Window::update_menu_item_enabled(PopupMenuItem item) 147{ 148 if (m_window_menu) { 149 m_window_menu->item((int)item).set_enabled(item == PopupMenuItem::Minimize ? m_minimizable : m_resizable); 150 m_window_menu->redraw(); 151 } 152} 153 154void Window::set_minimized(bool minimized) 155{ 156 if (m_minimized == minimized) 157 return; 158 if (minimized && !m_minimizable) 159 return; 160 if (is_blocked_by_modal_window()) 161 return; 162 m_minimized = minimized; 163 update_menu_item_text(PopupMenuItem::Minimize); 164 start_minimize_animation(); 165 if (!minimized) 166 request_update({ {}, size() }); 167 invalidate(); 168 WindowManager::the().notify_minimization_state_changed(*this); 169} 170 171void Window::set_minimizable(bool minimizable) 172{ 173 if (m_minimizable == minimizable) 174 return; 175 m_minimizable = minimizable; 176 update_menu_item_enabled(PopupMenuItem::Minimize); 177 // TODO: Hide/show (or alternatively change enabled state of) window minimize button dynamically depending on value of m_minimizable 178} 179 180void Window::set_opacity(float opacity) 181{ 182 if (m_opacity == opacity) 183 return; 184 m_opacity = opacity; 185 WindowManager::the().notify_opacity_changed(*this); 186} 187 188void Window::set_occluded(bool occluded) 189{ 190 if (m_occluded == occluded) 191 return; 192 m_occluded = occluded; 193 WindowManager::the().notify_occlusion_state_changed(*this); 194} 195 196void Window::set_maximized(bool maximized) 197{ 198 if (m_maximized == maximized) 199 return; 200 if (maximized && !is_resizable()) 201 return; 202 if (is_blocked_by_modal_window()) 203 return; 204 set_tiled(WindowTileType::None); 205 m_maximized = maximized; 206 update_menu_item_text(PopupMenuItem::Maximize); 207 auto old_rect = m_rect; 208 if (maximized) { 209 m_unmaximized_rect = m_rect; 210 set_rect(WindowManager::the().maximized_window_rect(*this)); 211 } else { 212 set_rect(m_unmaximized_rect); 213 } 214 m_frame.did_set_maximized({}, maximized); 215 Core::EventLoop::current().post_event(*this, make<ResizeEvent>(old_rect, m_rect)); 216} 217 218void Window::set_resizable(bool resizable) 219{ 220 if (m_resizable == resizable) 221 return; 222 m_resizable = resizable; 223 update_menu_item_enabled(PopupMenuItem::Maximize); 224 // TODO: Hide/show (or alternatively change enabled state of) window maximize button dynamically depending on value of is_resizable() 225} 226 227void Window::event(Core::Event& event) 228{ 229 if (!m_client) { 230 ASSERT(parent()); 231 event.ignore(); 232 return; 233 } 234 235 if (is_blocked_by_modal_window()) 236 return; 237 238 if (static_cast<Event&>(event).is_mouse_event()) 239 return handle_mouse_event(static_cast<const MouseEvent&>(event)); 240 241 switch (event.type()) { 242 case Event::WindowEntered: 243 m_client->post_message(Messages::WindowClient::WindowEntered(m_window_id)); 244 break; 245 case Event::WindowLeft: 246 m_client->post_message(Messages::WindowClient::WindowLeft(m_window_id)); 247 break; 248 case Event::KeyDown: 249 m_client->post_message( 250 Messages::WindowClient::KeyDown(m_window_id, 251 (u8) static_cast<const KeyEvent&>(event).character(), 252 (u32) static_cast<const KeyEvent&>(event).key(), 253 static_cast<const KeyEvent&>(event).modifiers())); 254 break; 255 case Event::KeyUp: 256 m_client->post_message( 257 Messages::WindowClient::KeyUp(m_window_id, 258 (u8) static_cast<const KeyEvent&>(event).character(), 259 (u32) static_cast<const KeyEvent&>(event).key(), 260 static_cast<const KeyEvent&>(event).modifiers())); 261 break; 262 case Event::WindowActivated: 263 m_client->post_message(Messages::WindowClient::WindowActivated(m_window_id)); 264 break; 265 case Event::WindowDeactivated: 266 m_client->post_message(Messages::WindowClient::WindowDeactivated(m_window_id)); 267 break; 268 case Event::WindowCloseRequest: 269 m_client->post_message(Messages::WindowClient::WindowCloseRequest(m_window_id)); 270 break; 271 case Event::WindowResized: 272 m_client->post_message( 273 Messages::WindowClient::WindowResized( 274 m_window_id, 275 static_cast<const ResizeEvent&>(event).old_rect(), 276 static_cast<const ResizeEvent&>(event).rect())); 277 break; 278 default: 279 break; 280 } 281} 282 283void Window::set_global_cursor_tracking_enabled(bool enabled) 284{ 285 m_global_cursor_tracking_enabled = enabled; 286} 287 288void Window::set_visible(bool b) 289{ 290 if (m_visible == b) 291 return; 292 m_visible = b; 293 invalidate(); 294} 295 296void Window::invalidate() 297{ 298 WindowManager::the().invalidate(*this); 299} 300 301void Window::invalidate(const Gfx::Rect& rect) 302{ 303 WindowManager::the().invalidate(*this, rect); 304} 305 306bool Window::is_active() const 307{ 308 return WindowManager::the().active_window() == this; 309} 310 311bool Window::is_blocked_by_modal_window() const 312{ 313 return !is_modal() && client() && client()->is_showing_modal_window(); 314} 315 316void Window::set_default_icon() 317{ 318 m_icon = default_window_icon(); 319} 320 321void Window::request_update(const Gfx::Rect& rect, bool ignore_occlusion) 322{ 323 if (m_pending_paint_rects.is_empty()) { 324 deferred_invoke([this, ignore_occlusion](auto&) { 325 client()->post_paint_message(*this, ignore_occlusion); 326 }); 327 } 328 m_pending_paint_rects.add(rect); 329} 330 331void Window::popup_window_menu(const Gfx::Point& position) 332{ 333 if (!m_window_menu) { 334 m_window_menu = Menu::construct(nullptr, -1, "(Window Menu)"); 335 m_window_menu->set_window_menu_of(*this); 336 337 m_window_menu->add_item(make<MenuItem>(*m_window_menu, 1, m_minimized ? "Unminimize" : "Minimize")); 338 m_window_menu->add_item(make<MenuItem>(*m_window_menu, 2, m_maximized ? "Restore" : "Maximize")); 339 m_window_menu->add_item(make<MenuItem>(*m_window_menu, MenuItem::Type::Separator)); 340 m_window_menu->add_item(make<MenuItem>(*m_window_menu, 3, "Close")); 341 342 m_window_menu->item((int)PopupMenuItem::Minimize).set_enabled(m_minimizable); 343 m_window_menu->item((int)PopupMenuItem::Maximize).set_enabled(m_resizable); 344 345 m_window_menu->on_item_activation = [&](auto& item) { 346 switch (item.identifier()) { 347 case 1: 348 set_minimized(!m_minimized); 349 if (!m_minimized) 350 WindowManager::the().move_to_front_and_make_active(*this); 351 break; 352 case 2: 353 set_maximized(!m_maximized); 354 if (m_minimized) 355 set_minimized(false); 356 WindowManager::the().move_to_front_and_make_active(*this); 357 break; 358 case 3: 359 request_close(); 360 break; 361 } 362 }; 363 } 364 m_window_menu->popup(position); 365} 366 367void Window::request_close() 368{ 369 Event close_request(Event::WindowCloseRequest); 370 event(close_request); 371} 372 373void Window::set_fullscreen(bool fullscreen) 374{ 375 if (m_fullscreen == fullscreen) 376 return; 377 m_fullscreen = fullscreen; 378 Gfx::Rect new_window_rect = m_rect; 379 if (m_fullscreen) { 380 m_saved_nonfullscreen_rect = m_rect; 381 new_window_rect = Screen::the().rect(); 382 } else if (!m_saved_nonfullscreen_rect.is_empty()) { 383 new_window_rect = m_saved_nonfullscreen_rect; 384 } 385 Core::EventLoop::current().post_event(*this, make<ResizeEvent>(m_rect, new_window_rect)); 386 set_rect(new_window_rect); 387} 388 389Gfx::Rect Window::tiled_rect(WindowTileType tiled) const 390{ 391 int frame_width = (m_frame.rect().width() - m_rect.width()) / 2; 392 switch (tiled) { 393 case WindowTileType::None: 394 return m_untiled_rect; 395 case WindowTileType::Left: 396 return Gfx::Rect(0, 397 WindowManager::the().maximized_window_rect(*this).y(), 398 Screen::the().width() / 2 - frame_width, 399 WindowManager::the().maximized_window_rect(*this).height()); 400 case WindowTileType::Right: 401 return Gfx::Rect(Screen::the().width() / 2 + frame_width, 402 WindowManager::the().maximized_window_rect(*this).y(), 403 Screen::the().width() / 2 - frame_width, 404 WindowManager::the().maximized_window_rect(*this).height()); 405 default : 406 ASSERT_NOT_REACHED(); 407 } 408} 409 410void Window::set_tiled(WindowTileType tiled) 411{ 412 if (m_tiled == tiled) 413 return; 414 415 m_tiled = tiled; 416 auto old_rect = m_rect; 417 if (tiled != WindowTileType::None) 418 m_untiled_rect = m_rect; 419 set_rect(tiled_rect(tiled)); 420 Core::EventLoop::current().post_event(*this, make<ResizeEvent>(old_rect, m_rect)); 421} 422 423void Window::detach_client(Badge<ClientConnection>) 424{ 425 m_client = nullptr; 426} 427 428void Window::recalculate_rect() 429{ 430 if (!is_resizable()) 431 return; 432 433 auto old_rect = m_rect; 434 if (m_tiled != WindowTileType::None) 435 set_rect(tiled_rect(m_tiled)); 436 else if (is_maximized()) 437 set_rect(WindowManager::the().maximized_window_rect(*this)); 438 Core::EventLoop::current().post_event(*this, make<ResizeEvent>(old_rect, m_rect)); 439} 440 441}