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