Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Badge.h>
8#include <LibGfx/Bitmap.h>
9#include <LibGfx/StandardCursor.h>
10#include <LibGfx/SystemTheme.h>
11#include <WindowServer/AppletManager.h>
12#include <WindowServer/Compositor.h>
13#include <WindowServer/ConnectionFromClient.h>
14#include <WindowServer/Menu.h>
15#include <WindowServer/MenuItem.h>
16#include <WindowServer/Screen.h>
17#include <WindowServer/Window.h>
18#include <WindowServer/WindowClientEndpoint.h>
19#include <WindowServer/WindowManager.h>
20#include <WindowServer/WindowSwitcher.h>
21#include <errno.h>
22#include <stdio.h>
23#include <unistd.h>
24
25namespace WindowServer {
26
27HashMap<int, NonnullRefPtr<ConnectionFromClient>>* s_connections;
28
29void ConnectionFromClient::for_each_client(Function<void(ConnectionFromClient&)> callback)
30{
31 if (!s_connections)
32 return;
33 for (auto& it : *s_connections) {
34 callback(*it.value);
35 }
36}
37
38ConnectionFromClient* ConnectionFromClient::from_client_id(int client_id)
39{
40 if (!s_connections)
41 return nullptr;
42 auto it = s_connections->find(client_id);
43 if (it == s_connections->end())
44 return nullptr;
45 return (*it).value.ptr();
46}
47
48ConnectionFromClient::ConnectionFromClient(NonnullOwnPtr<Core::LocalSocket> client_socket, int client_id)
49 : IPC::ConnectionFromClient<WindowClientEndpoint, WindowServerEndpoint>(*this, move(client_socket), client_id)
50{
51 if (!s_connections)
52 s_connections = new HashMap<int, NonnullRefPtr<ConnectionFromClient>>;
53 s_connections->set(client_id, *this);
54
55 auto& wm = WindowManager::the();
56 async_fast_greet(Screen::rects(), Screen::main().index(), wm.window_stack_rows(), wm.window_stack_columns(), Gfx::current_system_theme_buffer(), Gfx::FontDatabase::default_font_query(), Gfx::FontDatabase::fixed_width_font_query(), Gfx::FontDatabase::window_title_font_query(), wm.system_effects().effects(), client_id);
57}
58
59ConnectionFromClient::~ConnectionFromClient()
60{
61 auto& wm = WindowManager::the();
62 if (wm.dnd_client() == this)
63 wm.end_dnd_drag();
64
65 if (m_has_display_link)
66 Compositor::the().decrement_display_link_count({});
67
68 MenuManager::the().close_all_menus_from_client({}, *this);
69 auto windows = move(m_windows);
70 for (auto& window : windows) {
71 window.value->detach_client({});
72 if (window.value->type() == WindowType::Applet)
73 AppletManager::the().remove_applet(window.value);
74 }
75
76 if (m_show_screen_number)
77 Compositor::the().decrement_show_screen_number({});
78}
79
80void ConnectionFromClient::die()
81{
82 deferred_invoke([this] {
83 s_connections->remove(client_id());
84 });
85}
86
87void ConnectionFromClient::notify_about_new_screen_rects()
88{
89 auto& wm = WindowManager::the();
90 async_screen_rects_changed(Screen::rects(), Screen::main().index(), wm.window_stack_rows(), wm.window_stack_columns());
91}
92
93void ConnectionFromClient::create_menu(i32 menu_id, DeprecatedString const& menu_title)
94{
95 auto menu = Menu::construct(this, menu_id, menu_title);
96 m_menus.set(menu_id, move(menu));
97}
98
99void ConnectionFromClient::destroy_menu(i32 menu_id)
100{
101 auto it = m_menus.find(menu_id);
102 if (it == m_menus.end()) {
103 did_misbehave("DestroyMenu: Bad menu ID");
104 return;
105 }
106 auto& menu = *(*it).value;
107 menu.close();
108 m_menus.remove(it);
109 remove_child(menu);
110}
111
112void ConnectionFromClient::add_menu(i32 window_id, i32 menu_id)
113{
114 auto it = m_windows.find(window_id);
115 auto jt = m_menus.find(menu_id);
116 if (it == m_windows.end()) {
117 did_misbehave("AddMenu: Bad window ID");
118 return;
119 }
120 if (jt == m_menus.end()) {
121 did_misbehave("AddMenu: Bad menu ID");
122 return;
123 }
124 auto& window = *(*it).value;
125 auto& menu = *(*jt).value;
126 window.add_menu(menu);
127}
128
129void ConnectionFromClient::add_menu_item(i32 menu_id, i32 identifier, i32 submenu_id,
130 DeprecatedString const& text, bool enabled, bool visible, bool checkable, bool checked, bool is_default,
131 DeprecatedString const& shortcut, Gfx::ShareableBitmap const& icon, bool exclusive)
132{
133 auto it = m_menus.find(menu_id);
134 if (it == m_menus.end()) {
135 dbgln("AddMenuItem: Bad menu ID: {}", menu_id);
136 return;
137 }
138 auto& menu = *(*it).value;
139 auto menu_item = make<MenuItem>(menu, identifier, text, shortcut, enabled, visible, checkable, checked);
140 if (is_default)
141 menu_item->set_default(true);
142 menu_item->set_icon(icon.bitmap());
143 menu_item->set_submenu_id(submenu_id);
144 menu_item->set_exclusive(exclusive);
145 menu.add_item(move(menu_item));
146}
147
148void ConnectionFromClient::popup_menu(i32 menu_id, Gfx::IntPoint screen_position, Gfx::IntRect const& button_rect)
149{
150 auto position = screen_position;
151 auto it = m_menus.find(menu_id);
152 if (it == m_menus.end()) {
153 did_misbehave("PopupMenu: Bad menu ID");
154 return;
155 }
156 auto& menu = *(*it).value;
157 if (!button_rect.is_empty())
158 menu.open_button_menu(position, button_rect);
159 else
160 menu.popup(position);
161}
162
163void ConnectionFromClient::dismiss_menu(i32 menu_id)
164{
165 auto it = m_menus.find(menu_id);
166 if (it == m_menus.end()) {
167 did_misbehave("DismissMenu: Bad menu ID");
168 return;
169 }
170 auto& menu = *(*it).value;
171 menu.close();
172}
173
174void ConnectionFromClient::update_menu_item(i32 menu_id, i32 identifier, [[maybe_unused]] i32 submenu_id,
175 DeprecatedString const& text, bool enabled, bool visible, bool checkable, bool checked, bool is_default,
176 DeprecatedString const& shortcut, Gfx::ShareableBitmap const& icon)
177{
178 auto it = m_menus.find(menu_id);
179 if (it == m_menus.end()) {
180 did_misbehave("UpdateMenuItem: Bad menu ID");
181 return;
182 }
183 auto& menu = *(*it).value;
184 auto* menu_item = menu.item_with_identifier(identifier);
185 if (!menu_item) {
186 did_misbehave("UpdateMenuItem: Bad menu item identifier");
187 return;
188 }
189 menu_item->set_icon(icon.bitmap());
190 menu_item->set_text(text);
191 menu_item->set_shortcut_text(shortcut);
192 menu_item->set_enabled(enabled);
193 menu_item->set_visible(visible);
194 menu_item->set_checkable(checkable);
195 menu_item->set_default(is_default);
196 if (checkable)
197 menu_item->set_checked(checked);
198
199 menu.redraw(*menu_item);
200}
201
202void ConnectionFromClient::remove_menu_item(i32 menu_id, i32 identifier)
203{
204 auto it = m_menus.find(menu_id);
205 if (it == m_menus.end()) {
206 did_misbehave("RemoveMenuItem: Bad menu ID");
207 return;
208 }
209 auto& menu = *(*it).value;
210 if (!menu.remove_item_with_identifier(identifier))
211 did_misbehave("RemoveMenuItem: Bad menu item identifier");
212}
213
214void ConnectionFromClient::flash_menubar_menu(i32 window_id, i32 menu_id)
215{
216 auto itw = m_windows.find(window_id);
217 if (itw == m_windows.end()) {
218 did_misbehave("FlashMenubarMenu: Bad window ID");
219 return;
220 }
221 auto& window = *(*itw).value;
222
223 auto itm = m_menus.find(menu_id);
224 if (itm == m_menus.end()) {
225 did_misbehave("FlashMenubarMenu: Bad menu ID");
226 return;
227 }
228 auto& menu = *(*itm).value;
229
230 if (window.menubar().flash_menu(&menu)) {
231 window.frame().invalidate_menubar();
232
233 if (m_flashed_menu_timer && m_flashed_menu_timer->is_active()) {
234 m_flashed_menu_timer->on_timeout();
235 m_flashed_menu_timer->stop();
236 }
237
238 m_flashed_menu_timer = Core::Timer::create_single_shot(75, [weak_window = window.make_weak_ptr<Window>()] {
239 if (!weak_window)
240 return;
241 weak_window->menubar().flash_menu(nullptr);
242 weak_window->frame().invalidate_menubar();
243 }).release_value_but_fixme_should_propagate_errors();
244 m_flashed_menu_timer->start();
245 } else if (m_flashed_menu_timer) {
246 m_flashed_menu_timer->restart();
247 }
248}
249
250void ConnectionFromClient::add_menu_separator(i32 menu_id)
251{
252 auto it = m_menus.find(menu_id);
253 if (it == m_menus.end()) {
254 did_misbehave("AddMenuSeparator: Bad menu ID");
255 return;
256 }
257 auto& menu = *(*it).value;
258 menu.add_item(make<MenuItem>(menu, MenuItem::Separator));
259}
260
261void ConnectionFromClient::move_window_to_front(i32 window_id)
262{
263 auto it = m_windows.find(window_id);
264 if (it == m_windows.end()) {
265 did_misbehave("MoveWindowToFront: Bad window ID");
266 return;
267 }
268 WindowManager::the().move_to_front_and_make_active(*(*it).value);
269}
270
271void ConnectionFromClient::set_fullscreen(i32 window_id, bool fullscreen)
272{
273 auto it = m_windows.find(window_id);
274 if (it == m_windows.end()) {
275 did_misbehave("SetFullscreen: Bad window ID");
276 return;
277 }
278 it->value->set_fullscreen(fullscreen);
279}
280
281void ConnectionFromClient::set_frameless(i32 window_id, bool frameless)
282{
283 auto it = m_windows.find(window_id);
284 if (it == m_windows.end()) {
285 did_misbehave("SetFrameless: Bad window ID");
286 return;
287 }
288 it->value->set_frameless(frameless);
289 WindowManager::the().tell_wms_window_state_changed(*it->value);
290}
291
292void ConnectionFromClient::set_forced_shadow(i32 window_id, bool shadow)
293{
294 auto it = m_windows.find(window_id);
295 if (it == m_windows.end()) {
296 did_misbehave("SetForcedShadow: Bad window ID");
297 return;
298 }
299 it->value->set_forced_shadow(shadow);
300 it->value->invalidate();
301 Compositor::the().invalidate_occlusions();
302}
303
304void ConnectionFromClient::set_window_opacity(i32 window_id, float opacity)
305{
306 auto it = m_windows.find(window_id);
307 if (it == m_windows.end()) {
308 did_misbehave("SetWindowOpacity: Bad window ID");
309 return;
310 }
311 it->value->set_opacity(opacity);
312}
313
314Messages::WindowServer::SetWallpaperResponse ConnectionFromClient::set_wallpaper(Gfx::ShareableBitmap const& bitmap)
315{
316 return Compositor::the().set_wallpaper(bitmap.bitmap());
317}
318
319void ConnectionFromClient::set_background_color(DeprecatedString const& background_color)
320{
321 Compositor::the().set_background_color(background_color);
322}
323
324void ConnectionFromClient::set_wallpaper_mode(DeprecatedString const& mode)
325{
326 Compositor::the().set_wallpaper_mode(mode);
327}
328
329Messages::WindowServer::GetWallpaperResponse ConnectionFromClient::get_wallpaper()
330{
331 return Compositor::the().wallpaper_bitmap()->to_shareable_bitmap();
332}
333
334Messages::WindowServer::SetScreenLayoutResponse ConnectionFromClient::set_screen_layout(ScreenLayout const& screen_layout, bool save)
335{
336 DeprecatedString error_msg;
337 bool success = WindowManager::the().set_screen_layout(ScreenLayout(screen_layout), save, error_msg);
338 return { success, move(error_msg) };
339}
340
341Messages::WindowServer::GetScreenLayoutResponse ConnectionFromClient::get_screen_layout()
342{
343 return { WindowManager::the().get_screen_layout() };
344}
345
346Messages::WindowServer::SaveScreenLayoutResponse ConnectionFromClient::save_screen_layout()
347{
348 DeprecatedString error_msg;
349 bool success = WindowManager::the().save_screen_layout(error_msg);
350 return { success, move(error_msg) };
351}
352
353Messages::WindowServer::ApplyWorkspaceSettingsResponse ConnectionFromClient::apply_workspace_settings(u32 rows, u32 columns, bool save)
354{
355 if (rows == 0 || columns == 0 || rows > WindowManager::max_window_stack_rows || columns > WindowManager::max_window_stack_columns)
356 return { false };
357
358 return { WindowManager::the().apply_workspace_settings(rows, columns, save) };
359}
360
361Messages::WindowServer::GetWorkspaceSettingsResponse ConnectionFromClient::get_workspace_settings()
362{
363 auto& wm = WindowManager::the();
364 return { (unsigned)wm.window_stack_rows(), (unsigned)wm.window_stack_columns(), WindowManager::max_window_stack_rows, WindowManager::max_window_stack_columns };
365}
366
367void ConnectionFromClient::show_screen_numbers(bool show)
368{
369 if (m_show_screen_number == show)
370 return;
371 m_show_screen_number = show;
372 if (show)
373 Compositor::the().increment_show_screen_number({});
374 else
375 Compositor::the().decrement_show_screen_number({});
376}
377
378void ConnectionFromClient::set_window_title(i32 window_id, DeprecatedString const& title)
379{
380 auto it = m_windows.find(window_id);
381 if (it == m_windows.end()) {
382 did_misbehave("SetWindowTitle: Bad window ID");
383 return;
384 }
385 it->value->set_title(title);
386}
387
388Messages::WindowServer::GetWindowTitleResponse ConnectionFromClient::get_window_title(i32 window_id)
389{
390 auto it = m_windows.find(window_id);
391 if (it == m_windows.end()) {
392 did_misbehave("GetWindowTitle: Bad window ID");
393 return nullptr;
394 }
395 return it->value->title();
396}
397
398Messages::WindowServer::IsMaximizedResponse ConnectionFromClient::is_maximized(i32 window_id)
399{
400 auto it = m_windows.find(window_id);
401 if (it == m_windows.end()) {
402 did_misbehave("IsMaximized: Bad window ID");
403 return nullptr;
404 }
405 return it->value->is_maximized();
406}
407
408void ConnectionFromClient::set_maximized(i32 window_id, bool maximized)
409{
410 auto it = m_windows.find(window_id);
411 if (it == m_windows.end()) {
412 did_misbehave("SetMaximized: Bad window ID");
413 return;
414 }
415 it->value->set_maximized(maximized);
416}
417
418Messages::WindowServer::IsMinimizedResponse ConnectionFromClient::is_minimized(i32 window_id)
419{
420 auto it = m_windows.find(window_id);
421 if (it == m_windows.end()) {
422 did_misbehave("IsMinimized: Bad window ID");
423 return nullptr;
424 }
425 return it->value->is_minimized();
426}
427
428void ConnectionFromClient::set_minimized(i32 window_id, bool minimized)
429{
430 auto it = m_windows.find(window_id);
431 if (it == m_windows.end()) {
432 did_misbehave("SetMinimized: Bad window ID");
433 return;
434 }
435 it->value->set_minimized(minimized);
436}
437
438void ConnectionFromClient::set_window_icon_bitmap(i32 window_id, Gfx::ShareableBitmap const& icon)
439{
440 auto it = m_windows.find(window_id);
441 if (it == m_windows.end()) {
442 did_misbehave("SetWindowIconBitmap: Bad window ID");
443 return;
444 }
445 auto& window = *(*it).value;
446
447 if (icon.is_valid()) {
448 window.set_icon(*icon.bitmap());
449 } else {
450 window.set_default_icon();
451 }
452
453 window.frame().invalidate_titlebar();
454 WindowManager::the().tell_wms_window_icon_changed(window);
455}
456
457Messages::WindowServer::SetWindowRectResponse ConnectionFromClient::set_window_rect(i32 window_id, Gfx::IntRect const& rect)
458{
459 auto it = m_windows.find(window_id);
460 if (it == m_windows.end()) {
461 did_misbehave("SetWindowRect: Bad window ID");
462 return nullptr;
463 }
464 auto& window = *(*it).value;
465 if (window.is_fullscreen()) {
466 dbgln("ConnectionFromClient: Ignoring SetWindowRect request for fullscreen window");
467 return nullptr;
468 }
469 if (rect.width() > INT16_MAX || rect.height() > INT16_MAX) {
470 did_misbehave(DeprecatedString::formatted("SetWindowRect: Bad window sizing(width={}, height={}), dimension exceeds INT16_MAX", rect.width(), rect.height()).characters());
471 return nullptr;
472 }
473
474 if (rect.location() != window.rect().location()) {
475 window.set_default_positioned(false);
476 }
477 auto new_rect = rect;
478 window.apply_minimum_size(new_rect);
479 window.set_rect(new_rect);
480 window.request_update(window.rect());
481 return window.rect();
482}
483
484Messages::WindowServer::GetWindowRectResponse ConnectionFromClient::get_window_rect(i32 window_id)
485{
486 auto it = m_windows.find(window_id);
487 if (it == m_windows.end()) {
488 did_misbehave("GetWindowRect: Bad window ID");
489 return nullptr;
490 }
491 return it->value->rect();
492}
493
494static Gfx::IntSize calculate_minimum_size_for_window(Window const& window)
495{
496 if (window.is_frameless())
497 return { 0, 0 };
498
499 // NOTE: Windows with a title bar have a minimum size enforced by the system,
500 // because we want to always keep their title buttons accessible.
501 if (window.type() == WindowType::Normal) {
502 auto palette = WindowManager::the().palette();
503
504 int required_width = 0;
505 // Padding on left and right of window title content.
506 // FIXME: This seems like it should be defined in the theme.
507 required_width += 2 + 2;
508 // App icon
509 required_width += 16;
510 // Padding between icon and buttons
511 required_width += 2;
512 // Close button
513 required_width += palette.window_title_button_width();
514 // Maximize button
515 if (window.is_resizable())
516 required_width += palette.window_title_button_width();
517 // Minimize button
518 if (window.is_minimizable())
519 required_width += palette.window_title_button_width();
520
521 return { required_width, 0 };
522 }
523
524 return { 0, 0 };
525}
526
527void ConnectionFromClient::set_window_minimum_size(i32 window_id, Gfx::IntSize size)
528{
529 auto it = m_windows.find(window_id);
530 if (it == m_windows.end()) {
531 did_misbehave("SetWindowMinimumSize: Bad window ID");
532 return;
533 }
534 auto& window = *(*it).value;
535 if (window.is_fullscreen()) {
536 dbgln("ConnectionFromClient: Ignoring SetWindowMinimumSize request for fullscreen window");
537 return;
538 }
539
540 auto system_window_minimum_size = calculate_minimum_size_for_window(window);
541 window.set_minimum_size({ max(size.width(), system_window_minimum_size.width()),
542 max(size.height(), system_window_minimum_size.height()) });
543
544 if (window.width() < window.minimum_size().width() || window.height() < window.minimum_size().height()) {
545 // New minimum size is larger than the current window size, resize accordingly.
546 auto new_rect = window.rect();
547 bool did_size_clamp = window.apply_minimum_size(new_rect);
548 window.set_rect(new_rect);
549 window.request_update(window.rect());
550
551 if (did_size_clamp)
552 window.refresh_client_size();
553 }
554}
555
556Messages::WindowServer::GetWindowMinimumSizeResponse ConnectionFromClient::get_window_minimum_size(i32 window_id)
557{
558 auto it = m_windows.find(window_id);
559 if (it == m_windows.end()) {
560 did_misbehave("GetWindowMinimumSize: Bad window ID");
561 return nullptr;
562 }
563 return it->value->minimum_size();
564}
565
566Messages::WindowServer::GetAppletRectOnScreenResponse ConnectionFromClient::get_applet_rect_on_screen(i32 window_id)
567{
568 auto it = m_windows.find(window_id);
569 if (it == m_windows.end()) {
570 did_misbehave("GetAppletRectOnScreen: Bad window ID");
571 return nullptr;
572 }
573
574 Gfx::IntRect applet_area_rect;
575 if (auto* applet_area_window = AppletManager::the().window())
576 applet_area_rect = applet_area_window->rect();
577
578 return it->value->rect_in_applet_area().translated(applet_area_rect.location());
579}
580
581Window* ConnectionFromClient::window_from_id(i32 window_id)
582{
583 auto it = m_windows.find(window_id);
584 if (it == m_windows.end())
585 return nullptr;
586 return it->value.ptr();
587}
588
589void ConnectionFromClient::create_window(i32 window_id, Gfx::IntRect const& rect,
590 bool auto_position, bool has_alpha_channel, bool minimizable, bool closeable, bool resizable,
591 bool fullscreen, bool frameless, bool forced_shadow, float opacity,
592 float alpha_hit_threshold, Gfx::IntSize base_size, Gfx::IntSize size_increment,
593 Gfx::IntSize minimum_size, Optional<Gfx::IntSize> const& resize_aspect_ratio, i32 type, i32 mode,
594 DeprecatedString const& title, i32 parent_window_id, Gfx::IntRect const& launch_origin_rect)
595{
596 Window* parent_window = nullptr;
597 if (parent_window_id) {
598 parent_window = window_from_id(parent_window_id);
599 if (!parent_window) {
600 did_misbehave("CreateWindow with bad parent_window_id");
601 return;
602 }
603
604 if (auto* blocker = parent_window->blocking_modal_window(); blocker && mode == (i32)WindowMode::Blocking) {
605 did_misbehave("CreateWindow with illegal mode: reciprocally blocked");
606 return;
607 }
608 }
609
610 if (type < 0 || type >= (i32)WindowType::_Count) {
611 did_misbehave("CreateWindow with a bad type");
612 return;
613 }
614
615 if (mode < 0 || mode >= (i32)WindowMode::_Count) {
616 did_misbehave("CreateWindow with a bad mode");
617 return;
618 }
619
620 if (m_windows.contains(window_id)) {
621 did_misbehave("CreateWindow with already-used window ID");
622 return;
623 }
624
625 auto window = Window::construct(*this, (WindowType)type, (WindowMode)mode, window_id, minimizable, closeable, frameless, resizable, fullscreen, parent_window);
626
627 window->set_forced_shadow(forced_shadow);
628
629 if (!launch_origin_rect.is_empty())
630 window->start_launch_animation(launch_origin_rect);
631
632 window->set_has_alpha_channel(has_alpha_channel);
633 window->set_title(title);
634 if (!fullscreen) {
635 Gfx::IntRect new_rect = rect;
636 if (auto_position && window->is_movable()) {
637 new_rect = { WindowManager::the().get_recommended_window_position({ 100, 100 }), rect.size() };
638 window->set_default_positioned(true);
639 }
640 auto system_window_minimum_size = calculate_minimum_size_for_window(window);
641 window->set_minimum_size({ max(minimum_size.width(), system_window_minimum_size.width()),
642 max(minimum_size.height(), system_window_minimum_size.height()) });
643 bool did_size_clamp = window->apply_minimum_size(new_rect);
644 window->set_rect(new_rect);
645
646 if (did_size_clamp)
647 window->refresh_client_size();
648 }
649 if (window->type() == WindowType::Desktop) {
650 window->set_rect(Screen::bounding_rect());
651 window->recalculate_rect();
652 }
653 window->set_opacity(opacity);
654 window->set_alpha_hit_threshold(alpha_hit_threshold);
655 window->set_size_increment(size_increment);
656 window->set_base_size(base_size);
657 if (resize_aspect_ratio.has_value() && !resize_aspect_ratio.value().is_empty())
658 window->set_resize_aspect_ratio(resize_aspect_ratio);
659 window->invalidate(true, true);
660 if (window->type() == WindowType::Applet)
661 AppletManager::the().add_applet(*window);
662 m_windows.set(window_id, move(window));
663}
664
665void ConnectionFromClient::destroy_window(Window& window, Vector<i32>& destroyed_window_ids)
666{
667 for (auto& child_window : window.child_windows()) {
668 if (!child_window)
669 continue;
670 VERIFY(child_window->window_id() != window.window_id());
671 destroy_window(*child_window, destroyed_window_ids);
672 }
673
674 destroyed_window_ids.append(window.window_id());
675
676 if (window.type() == WindowType::Applet)
677 AppletManager::the().remove_applet(window);
678
679 window.destroy();
680 remove_child(window);
681 m_windows.remove(window.window_id());
682}
683
684Messages::WindowServer::DestroyWindowResponse ConnectionFromClient::destroy_window(i32 window_id)
685{
686 auto it = m_windows.find(window_id);
687 if (it == m_windows.end()) {
688 did_misbehave("DestroyWindow: Bad window ID");
689 return nullptr;
690 }
691 auto& window = *(*it).value;
692 Vector<i32> destroyed_window_ids;
693 destroy_window(window, destroyed_window_ids);
694 return destroyed_window_ids;
695}
696
697void ConnectionFromClient::post_paint_message(Window& window, bool ignore_occlusion)
698{
699 auto rect_set = window.take_pending_paint_rects();
700 if (window.is_minimized() || (!ignore_occlusion && window.is_occluded()))
701 return;
702
703 async_paint(window.window_id(), window.size(), rect_set.rects());
704}
705
706void ConnectionFromClient::invalidate_rect(i32 window_id, Vector<Gfx::IntRect> const& rects, bool ignore_occlusion)
707{
708 auto it = m_windows.find(window_id);
709 if (it == m_windows.end()) {
710 did_misbehave("InvalidateRect: Bad window ID");
711 return;
712 }
713 auto& window = *(*it).value;
714 for (size_t i = 0; i < rects.size(); ++i)
715 window.request_update(rects[i].intersected(Gfx::Rect { {}, window.size() }), ignore_occlusion);
716}
717
718void ConnectionFromClient::did_finish_painting(i32 window_id, Vector<Gfx::IntRect> const& rects)
719{
720 auto it = m_windows.find(window_id);
721 if (it == m_windows.end()) {
722 did_misbehave("DidFinishPainting: Bad window ID");
723 return;
724 }
725 auto& window = *(*it).value;
726 for (auto& rect : rects)
727 window.invalidate(rect);
728 if (window.has_alpha_channel() && window.alpha_hit_threshold() > 0.0f)
729 WindowManager::the().reevaluate_hover_state_for_window(&window);
730
731 WindowSwitcher::the().refresh_if_needed();
732}
733
734void ConnectionFromClient::set_window_backing_store(i32 window_id, [[maybe_unused]] i32 bpp,
735 [[maybe_unused]] i32 pitch, IPC::File const& anon_file, i32 serial, bool has_alpha_channel,
736 Gfx::IntSize size, Gfx::IntSize visible_size, bool flush_immediately)
737{
738 auto it = m_windows.find(window_id);
739 if (it == m_windows.end()) {
740 did_misbehave("SetWindowBackingStore: Bad window ID");
741 return;
742 }
743 auto& window = *(*it).value;
744 if (window.last_backing_store() && window.last_backing_store_serial() == serial) {
745 window.swap_backing_stores();
746 } else {
747 // FIXME: Plumb scale factor here eventually.
748 auto buffer_or_error = Core::AnonymousBuffer::create_from_anon_fd(anon_file.take_fd(), pitch * size.height());
749 if (buffer_or_error.is_error()) {
750 did_misbehave("SetWindowBackingStore: Failed to create anonymous buffer for window backing store");
751 return;
752 }
753 auto backing_store_or_error = Gfx::Bitmap::create_with_anonymous_buffer(
754 has_alpha_channel ? Gfx::BitmapFormat::BGRA8888 : Gfx::BitmapFormat::BGRx8888,
755 buffer_or_error.release_value(),
756 size,
757 1,
758 {});
759 if (backing_store_or_error.is_error()) {
760 did_misbehave("");
761 }
762 window.set_backing_store(backing_store_or_error.release_value(), serial);
763 }
764 window.set_backing_store_visible_size(visible_size);
765
766 if (flush_immediately)
767 window.invalidate(false);
768}
769
770void ConnectionFromClient::set_global_mouse_tracking(bool enabled)
771{
772 m_does_global_mouse_tracking = enabled;
773}
774
775void ConnectionFromClient::set_window_cursor(i32 window_id, i32 cursor_type)
776{
777 auto it = m_windows.find(window_id);
778 if (it == m_windows.end()) {
779 did_misbehave("SetWindowCursor: Bad window ID");
780 return;
781 }
782 auto& window = *(*it).value;
783 if (cursor_type < 0 || cursor_type >= (i32)Gfx::StandardCursor::__Count) {
784 did_misbehave("SetWindowCursor: Bad cursor type");
785 return;
786 }
787 window.set_cursor(Cursor::create((Gfx::StandardCursor)cursor_type));
788 if (&window == WindowManager::the().hovered_window())
789 Compositor::the().invalidate_cursor();
790}
791
792void ConnectionFromClient::set_window_custom_cursor(i32 window_id, Gfx::ShareableBitmap const& cursor)
793{
794 auto it = m_windows.find(window_id);
795 if (it == m_windows.end()) {
796 did_misbehave("SetWindowCustomCursor: Bad window ID");
797 return;
798 }
799
800 auto& window = *(*it).value;
801 if (!cursor.is_valid()) {
802 did_misbehave("SetWindowCustomCursor: Bad cursor");
803 return;
804 }
805
806 window.set_cursor(Cursor::create(*cursor.bitmap(), 1));
807 Compositor::the().invalidate_cursor();
808}
809
810void ConnectionFromClient::set_window_has_alpha_channel(i32 window_id, bool has_alpha_channel)
811{
812 auto it = m_windows.find(window_id);
813 if (it == m_windows.end()) {
814 did_misbehave("SetWindowHasAlphaChannel: Bad window ID");
815 return;
816 }
817 it->value->set_has_alpha_channel(has_alpha_channel);
818}
819
820void ConnectionFromClient::set_window_alpha_hit_threshold(i32 window_id, float threshold)
821{
822 auto it = m_windows.find(window_id);
823 if (it == m_windows.end()) {
824 did_misbehave("SetWindowAlphaHitThreshold: Bad window ID");
825 return;
826 }
827 it->value->set_alpha_hit_threshold(threshold);
828}
829
830void ConnectionFromClient::start_window_resize(i32 window_id, i32 resize_direction)
831{
832 auto it = m_windows.find(window_id);
833 if (it == m_windows.end()) {
834 did_misbehave("WM_StartWindowResize: Bad window ID");
835 return;
836 }
837 if (resize_direction < 0 || resize_direction >= (i32)ResizeDirection::__Count) {
838 did_misbehave("WM_StartWindowResize: Bad resize direction");
839 return;
840 }
841 auto& window = *(*it).value;
842 if (!window.is_resizable()) {
843 dbgln("Client wants to start resizing a non-resizable window");
844 return;
845 }
846 // FIXME: We are cheating a bit here by using the current cursor location and hard-coding the left button.
847 // Maybe the client should be allowed to specify what initiated this request?
848 WindowManager::the().start_window_resize(window, ScreenInput::the().cursor_location(), MouseButton::Primary, (ResizeDirection)resize_direction);
849}
850
851Messages::WindowServer::StartDragResponse ConnectionFromClient::start_drag(DeprecatedString const& text, HashMap<DeprecatedString, ByteBuffer> const& mime_data, Gfx::ShareableBitmap const& drag_bitmap)
852{
853 auto& wm = WindowManager::the();
854 if (wm.dnd_client() || !(wm.last_processed_buttons() & MouseButton::Primary))
855 return false;
856
857 wm.start_dnd_drag(*this, text, drag_bitmap.bitmap(), Core::MimeData::construct(mime_data));
858 return true;
859}
860
861void ConnectionFromClient::set_accepts_drag(bool accepts)
862{
863 auto& wm = WindowManager::the();
864 VERIFY(wm.dnd_client());
865 wm.set_accepts_drag(accepts);
866}
867
868Messages::WindowServer::SetSystemThemeResponse ConnectionFromClient::set_system_theme(DeprecatedString const& theme_path, DeprecatedString const& theme_name, bool keep_desktop_background, Optional<DeprecatedString> const& color_scheme_path)
869{
870 bool success = WindowManager::the().update_theme(theme_path, theme_name, keep_desktop_background, color_scheme_path);
871 return success;
872}
873
874Messages::WindowServer::GetSystemThemeResponse ConnectionFromClient::get_system_theme()
875{
876 return g_config->read_entry("Theme", "Name");
877}
878
879Messages::WindowServer::SetSystemThemeOverrideResponse ConnectionFromClient::set_system_theme_override(Core::AnonymousBuffer const& theme_override)
880{
881 bool success = WindowManager::the().set_theme_override(theme_override);
882 return success;
883}
884
885Messages::WindowServer::GetSystemThemeOverrideResponse ConnectionFromClient::get_system_theme_override()
886{
887 return WindowManager::the().get_theme_override();
888}
889
890void ConnectionFromClient::clear_system_theme_override()
891{
892 WindowManager::the().clear_theme_override();
893}
894
895Messages::WindowServer::IsSystemThemeOverriddenResponse ConnectionFromClient::is_system_theme_overridden()
896{
897 return WindowManager::the().is_theme_overridden();
898}
899
900Messages::WindowServer::GetPreferredColorSchemeResponse ConnectionFromClient::get_preferred_color_scheme()
901{
902 return WindowManager::the().get_preferred_color_scheme();
903}
904
905void ConnectionFromClient::apply_cursor_theme(DeprecatedString const& name)
906{
907 WindowManager::the().apply_cursor_theme(name);
908}
909
910void ConnectionFromClient::set_cursor_highlight_radius(int radius)
911{
912 WindowManager::the().set_cursor_highlight_radius(radius);
913}
914
915Messages::WindowServer::GetCursorHighlightRadiusResponse ConnectionFromClient::get_cursor_highlight_radius()
916{
917 return WindowManager::the().cursor_highlight_radius();
918}
919
920void ConnectionFromClient::set_cursor_highlight_color(Gfx::Color color)
921{
922 WindowManager::the().set_cursor_highlight_color(color);
923}
924
925Messages::WindowServer::GetCursorHighlightColorResponse ConnectionFromClient::get_cursor_highlight_color()
926{
927 return WindowManager::the().cursor_highlight_color();
928}
929
930Messages::WindowServer::GetCursorThemeResponse ConnectionFromClient::get_cursor_theme()
931{
932 return g_config->read_entry("Mouse", "CursorTheme");
933}
934
935Messages::WindowServer::SetSystemFontsResponse ConnectionFromClient::set_system_fonts(DeprecatedString const& default_font_query, DeprecatedString const& fixed_width_font_query, DeprecatedString const& window_title_font_query)
936{
937 if (!Gfx::FontDatabase::the().get_by_name(default_font_query)
938 || !Gfx::FontDatabase::the().get_by_name(fixed_width_font_query)) {
939 dbgln("Received unusable font queries: '{}' and '{}'", default_font_query, fixed_width_font_query);
940 return false;
941 }
942
943 dbgln("Updating fonts: '{}' and '{}'", default_font_query, fixed_width_font_query);
944
945 Gfx::FontDatabase::set_default_font_query(default_font_query);
946 Gfx::FontDatabase::set_fixed_width_font_query(fixed_width_font_query);
947 Gfx::FontDatabase::set_window_title_font_query(window_title_font_query);
948
949 ConnectionFromClient::for_each_client([&](auto& client) {
950 client.async_update_system_fonts(default_font_query, fixed_width_font_query, window_title_font_query);
951 });
952
953 WindowManager::the().invalidate_after_theme_or_font_change();
954
955 g_config->write_entry("Fonts", "Default", default_font_query);
956 g_config->write_entry("Fonts", "FixedWidth", fixed_width_font_query);
957 g_config->write_entry("Fonts", "WindowTitle", window_title_font_query);
958
959 return !g_config->sync().is_error();
960}
961
962void ConnectionFromClient::set_system_effects(Vector<bool> const& effects, u8 geometry)
963{
964 WindowManager::the().apply_system_effects(effects, static_cast<ShowGeometry>(geometry));
965 ConnectionFromClient::for_each_client([&](auto& client) {
966 client.async_update_system_effects(effects);
967 });
968}
969
970void ConnectionFromClient::set_window_base_size_and_size_increment(i32 window_id, Gfx::IntSize base_size, Gfx::IntSize size_increment)
971{
972 auto it = m_windows.find(window_id);
973 if (it == m_windows.end()) {
974 did_misbehave("SetWindowBaseSizeAndSizeIncrementResponse: Bad window ID");
975 return;
976 }
977
978 auto& window = *it->value;
979 window.set_base_size(base_size);
980 window.set_size_increment(size_increment);
981}
982
983void ConnectionFromClient::set_window_resize_aspect_ratio(i32 window_id, Optional<Gfx::IntSize> const& resize_aspect_ratio)
984{
985 auto it = m_windows.find(window_id);
986 if (it == m_windows.end()) {
987 did_misbehave("SetWindowResizeAspectRatioResponse: Bad window ID");
988 return;
989 }
990
991 auto& window = *it->value;
992 window.set_resize_aspect_ratio(resize_aspect_ratio);
993}
994
995void ConnectionFromClient::enable_display_link()
996{
997 if (m_has_display_link)
998 return;
999 m_has_display_link = true;
1000 Compositor::the().increment_display_link_count({});
1001}
1002
1003void ConnectionFromClient::disable_display_link()
1004{
1005 if (!m_has_display_link)
1006 return;
1007 m_has_display_link = false;
1008 Compositor::the().decrement_display_link_count({});
1009}
1010
1011void ConnectionFromClient::notify_display_link(Badge<Compositor>)
1012{
1013 if (!m_has_display_link)
1014 return;
1015
1016 async_display_link_notification();
1017}
1018
1019void ConnectionFromClient::set_window_progress(i32 window_id, Optional<i32> const& progress)
1020{
1021 auto it = m_windows.find(window_id);
1022 if (it == m_windows.end()) {
1023 did_misbehave("SetWindowProgress with bad window ID");
1024 return;
1025 }
1026 it->value->set_progress(progress);
1027}
1028
1029void ConnectionFromClient::refresh_system_theme()
1030{
1031 // Post the client an UpdateSystemTheme message to refresh its theme.
1032 async_update_system_theme(Gfx::current_system_theme_buffer());
1033}
1034
1035void ConnectionFromClient::pong()
1036{
1037 m_ping_timer = nullptr;
1038 set_unresponsive(false);
1039}
1040
1041void ConnectionFromClient::set_global_cursor_position(Gfx::IntPoint position)
1042{
1043 if (!Screen::main().rect().contains(position)) {
1044 did_misbehave("SetGlobalCursorPosition with bad position");
1045 return;
1046 }
1047 if (position != ScreenInput::the().cursor_location()) {
1048 ScreenInput::the().set_cursor_location(position);
1049 Compositor::the().invalidate_cursor();
1050 }
1051}
1052
1053Messages::WindowServer::GetGlobalCursorPositionResponse ConnectionFromClient::get_global_cursor_position()
1054{
1055 return ScreenInput::the().cursor_location();
1056}
1057
1058void ConnectionFromClient::set_mouse_acceleration(float factor)
1059{
1060 double dbl_factor = (double)factor;
1061 if (dbl_factor < mouse_accel_min || dbl_factor > mouse_accel_max) {
1062 did_misbehave("SetMouseAcceleration with bad acceleration factor");
1063 return;
1064 }
1065 WindowManager::the().set_acceleration_factor(dbl_factor);
1066}
1067
1068Messages::WindowServer::GetMouseAccelerationResponse ConnectionFromClient::get_mouse_acceleration()
1069{
1070 return ScreenInput::the().acceleration_factor();
1071}
1072
1073void ConnectionFromClient::set_scroll_step_size(u32 step_size)
1074{
1075 if (step_size < scroll_step_size_min) {
1076 did_misbehave("SetScrollStepSize with bad scroll step size");
1077 return;
1078 }
1079 WindowManager::the().set_scroll_step_size(step_size);
1080}
1081
1082Messages::WindowServer::GetScrollStepSizeResponse ConnectionFromClient::get_scroll_step_size()
1083{
1084 return ScreenInput::the().scroll_step_size();
1085}
1086
1087void ConnectionFromClient::set_double_click_speed(i32 speed)
1088{
1089 if (speed < double_click_speed_min || speed > double_click_speed_max) {
1090 did_misbehave("SetDoubleClickSpeed with bad speed");
1091 return;
1092 }
1093 WindowManager::the().set_double_click_speed(speed);
1094}
1095
1096Messages::WindowServer::GetDoubleClickSpeedResponse ConnectionFromClient::get_double_click_speed()
1097{
1098 return WindowManager::the().double_click_speed();
1099}
1100
1101void ConnectionFromClient::set_mouse_buttons_switched(bool switched)
1102{
1103 WindowManager::the().set_mouse_buttons_switched(switched);
1104}
1105
1106Messages::WindowServer::AreMouseButtonsSwitchedResponse ConnectionFromClient::are_mouse_buttons_switched()
1107{
1108 return WindowManager::the().are_mouse_buttons_switched();
1109}
1110
1111void ConnectionFromClient::set_natural_scroll(bool inverted)
1112{
1113 WindowManager::the().set_natural_scroll(inverted);
1114}
1115
1116Messages::WindowServer::IsNaturalScrollResponse ConnectionFromClient::is_natural_scroll()
1117{
1118 return WindowManager::the().is_natural_scroll();
1119}
1120
1121void ConnectionFromClient::set_unresponsive(bool unresponsive)
1122{
1123 if (m_unresponsive == unresponsive)
1124 return;
1125 m_unresponsive = unresponsive;
1126 for (auto& it : m_windows) {
1127 auto& window = *it.value;
1128 window.invalidate(true, true);
1129 if (unresponsive) {
1130 window.set_cursor_override(WindowManager::the().wait_cursor());
1131 } else {
1132 window.remove_cursor_override();
1133 }
1134 }
1135 Compositor::the().invalidate_cursor();
1136}
1137
1138void ConnectionFromClient::may_have_become_unresponsive()
1139{
1140 async_ping();
1141 m_ping_timer = Core::Timer::create_single_shot(1000, [this] {
1142 set_unresponsive(true);
1143 }).release_value_but_fixme_should_propagate_errors();
1144 m_ping_timer->start();
1145}
1146
1147void ConnectionFromClient::did_become_responsive()
1148{
1149 set_unresponsive(false);
1150}
1151
1152Messages::WindowServer::GetScreenBitmapResponse ConnectionFromClient::get_screen_bitmap(Optional<Gfx::IntRect> const& rect, Optional<u32> const& screen_index)
1153{
1154 if (screen_index.has_value()) {
1155 auto* screen = Screen::find_by_index(screen_index.value());
1156 if (!screen) {
1157 dbgln("get_screen_bitmap: Screen {} does not exist!", screen_index.value());
1158 return { Gfx::ShareableBitmap() };
1159 }
1160 if (rect.has_value()) {
1161 auto bitmap_or_error = Compositor::the().front_bitmap_for_screenshot({}, *screen).cropped(rect.value());
1162 if (bitmap_or_error.is_error()) {
1163 dbgln("get_screen_bitmap: Failed to crop screenshot: {}", bitmap_or_error.error());
1164 return { Gfx::ShareableBitmap() };
1165 }
1166 return bitmap_or_error.release_value()->to_shareable_bitmap();
1167 }
1168 auto& bitmap = Compositor::the().front_bitmap_for_screenshot({}, *screen);
1169 return bitmap.to_shareable_bitmap();
1170 }
1171 // TODO: Mixed scale setups at what scale? Lowest? Highest? Configurable?
1172 auto bitmap_size = rect.value_or(Screen::bounding_rect()).size();
1173 if (auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, bitmap_size, 1); !bitmap_or_error.is_error()) {
1174 auto bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors();
1175 Gfx::Painter painter(*bitmap);
1176 Screen::for_each([&](auto& screen) {
1177 auto screen_rect = screen.rect();
1178 if (rect.has_value() && !rect.value().intersects(screen_rect))
1179 return IterationDecision::Continue;
1180 auto src_rect = rect.has_value() ? rect.value().intersected(screen_rect) : screen_rect;
1181 VERIFY(Screen::bounding_rect().contains(src_rect));
1182 auto& screen_bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen);
1183 // TODO: painter does *not* support down-sampling!!!
1184 painter.blit(screen_rect.location(), screen_bitmap, src_rect.translated(-screen_rect.location()), 1.0f, false);
1185 return IterationDecision::Continue;
1186 });
1187 return bitmap->to_shareable_bitmap();
1188 }
1189 return { Gfx::ShareableBitmap() };
1190}
1191
1192Messages::WindowServer::GetScreenBitmapAroundCursorResponse ConnectionFromClient::get_screen_bitmap_around_cursor(Gfx::IntSize size)
1193{
1194 return get_screen_bitmap_around_location(size, ScreenInput::the().cursor_location()).bitmap();
1195}
1196
1197Messages::WindowServer::GetScreenBitmapAroundLocationResponse ConnectionFromClient::get_screen_bitmap_around_location(Gfx::IntSize size, Gfx::IntPoint location)
1198{
1199 // TODO: Mixed scale setups at what scale? Lowest? Highest? Configurable?
1200 Gfx::Rect rect { location.x() - (size.width() / 2), location.y() - (size.height() / 2), size.width(), size.height() };
1201
1202 // Recompose the screen to make sure the cursor is painted in the location we think it is.
1203 // FIXME: This is rather wasteful. We can probably think of a way to avoid this.
1204 Compositor::the().compose();
1205
1206 // Check if we need to compose from multiple screens. If not we can take a fast path
1207 size_t intersecting_with_screens = 0;
1208 Screen::for_each([&](auto& screen) {
1209 if (rect.intersects(screen.rect()))
1210 intersecting_with_screens++;
1211 return IterationDecision::Continue;
1212 });
1213
1214 if (intersecting_with_screens == 1) {
1215 auto& screen = Screen::closest_to_rect(rect);
1216 auto crop_rect = rect.translated(-screen.rect().location());
1217 auto bitmap_or_error = Compositor::the().front_bitmap_for_screenshot({}, screen).cropped(crop_rect);
1218 if (bitmap_or_error.is_error()) {
1219 dbgln("get_screen_bitmap_around_cursor: Failed to crop screenshot: {}", bitmap_or_error.error());
1220 return { {} };
1221 }
1222 return bitmap_or_error.release_value()->to_shareable_bitmap();
1223 }
1224
1225 if (auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, rect.size(), 1); !bitmap_or_error.is_error()) {
1226 auto bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors();
1227 auto bounding_screen_src_rect = Screen::bounding_rect().intersected(rect);
1228 Gfx::Painter painter(*bitmap);
1229 auto& screen_with_cursor = ScreenInput::the().cursor_location_screen();
1230 auto cursor_rect = Compositor::the().current_cursor_rect();
1231 Screen::for_each([&](auto& screen) {
1232 auto screen_rect = screen.rect();
1233 auto src_rect = screen_rect.intersected(bounding_screen_src_rect);
1234 if (src_rect.is_empty())
1235 return IterationDecision ::Continue;
1236 auto& screen_bitmap = Compositor::the().front_bitmap_for_screenshot({}, screen);
1237 // TODO: Add scaling support for multiple screens
1238 auto from_rect = src_rect.translated(-screen_rect.location());
1239 auto target_location = rect.intersected(screen_rect).location().translated(-rect.location());
1240 // TODO: painter does *not* support down-sampling!!!
1241 painter.blit(target_location, screen_bitmap, from_rect, 1.0f, false);
1242 // Check if we are a screen that doesn't have the cursor but the cursor would
1243 // have normally been cut off (we don't draw portions of the cursor on a screen
1244 // that doesn't actually have the cursor). In that case we need to render the remaining
1245 // portion of the cursor on that screen's capture manually
1246 if (&screen != &screen_with_cursor) {
1247 auto screen_cursor_rect = cursor_rect.intersected(screen_rect);
1248 if (!screen_cursor_rect.is_empty()) {
1249 if (auto const* cursor_bitmap = Compositor::the().cursor_bitmap_for_screenshot({}, screen)) {
1250 auto src_rect = screen_cursor_rect.translated(-cursor_rect.location());
1251 auto cursor_target = cursor_rect.intersected(screen_rect).location().translated(-rect.location());
1252 // TODO: painter does *not* support down-sampling!!!
1253 painter.blit(cursor_target, *cursor_bitmap, src_rect);
1254 }
1255 }
1256 }
1257 return IterationDecision::Continue;
1258 });
1259 return bitmap->to_shareable_bitmap();
1260 }
1261 return { {} };
1262}
1263
1264Messages::WindowServer::GetColorUnderCursorResponse ConnectionFromClient::get_color_under_cursor()
1265{
1266 auto screen_scale_factor = ScreenInput::the().cursor_location_screen().scale_factor();
1267 // FIXME: Add a mechanism to get screen bitmap without cursor, so we don't have to do this
1268 // manual translation to avoid sampling the color on the actual cursor itself.
1269 auto cursor_location = (ScreenInput::the().cursor_location() * screen_scale_factor).translated(-1, -1);
1270 auto& screen_with_cursor = ScreenInput::the().cursor_location_screen();
1271 auto scaled_screen_rect = screen_with_cursor.rect() * screen_scale_factor;
1272
1273 if (!scaled_screen_rect.contains(cursor_location))
1274 return Optional<Color> {};
1275
1276 return { Compositor::the().color_at_position({}, screen_with_cursor, cursor_location) };
1277}
1278
1279Messages::WindowServer::IsWindowModifiedResponse ConnectionFromClient::is_window_modified(i32 window_id)
1280{
1281 auto it = m_windows.find(window_id);
1282 if (it == m_windows.end()) {
1283 did_misbehave("IsWindowModified: Bad window ID");
1284 return nullptr;
1285 }
1286 auto& window = *it->value;
1287 return window.is_modified();
1288}
1289
1290Messages::WindowServer::GetDesktopDisplayScaleResponse ConnectionFromClient::get_desktop_display_scale(u32 screen_index)
1291{
1292 if (auto* screen = Screen::find_by_index(screen_index))
1293 return screen->scale_factor();
1294 dbgln("GetDesktopDisplayScale: Screen {} does not exist", screen_index);
1295 return 0;
1296}
1297
1298void ConnectionFromClient::set_window_modified(i32 window_id, bool modified)
1299{
1300 auto it = m_windows.find(window_id);
1301 if (it == m_windows.end()) {
1302 did_misbehave("SetWindowModified: Bad window ID");
1303 return;
1304 }
1305 auto& window = *it->value;
1306 window.set_modified(modified);
1307}
1308
1309void ConnectionFromClient::set_flash_flush(bool enabled)
1310{
1311 Compositor::the().set_flash_flush(enabled);
1312}
1313
1314void ConnectionFromClient::set_window_parent_from_client(i32 client_id, i32 parent_id, i32 child_id)
1315{
1316 auto* child_window = window_from_id(child_id);
1317 if (!child_window) {
1318 did_misbehave("SetWindowParentFromClient: Bad child window ID");
1319 return;
1320 }
1321
1322 auto* client_connection = from_client_id(client_id);
1323 if (!client_connection) {
1324 did_misbehave("SetWindowParentFromClient: Bad client ID");
1325 return;
1326 }
1327
1328 auto* parent_window = client_connection->window_from_id(parent_id);
1329 if (!parent_window) {
1330 did_misbehave("SetWindowParentFromClient: Bad parent window ID");
1331 return;
1332 }
1333
1334 if (parent_window->is_stealable_by_client(this->client_id())) {
1335 child_window->set_parent_window(*parent_window);
1336 } else {
1337 did_misbehave("SetWindowParentFromClient: Window is not stealable");
1338 }
1339}
1340
1341Messages::WindowServer::GetWindowRectFromClientResponse ConnectionFromClient::get_window_rect_from_client(i32 client_id, i32 window_id)
1342{
1343 auto* client_connection = from_client_id(client_id);
1344 if (!client_connection) {
1345 did_misbehave("GetWindowRectFromClient: Bad client ID");
1346 return { Gfx::IntRect() };
1347 }
1348
1349 auto* window = client_connection->window_from_id(window_id);
1350 if (!window) {
1351 did_misbehave("GetWindowRectFromClient: Bad window ID");
1352 return { Gfx::IntRect() };
1353 }
1354
1355 return window->rect();
1356}
1357
1358void ConnectionFromClient::add_window_stealing_for_client(i32 client_id, i32 window_id)
1359{
1360 auto* window = window_from_id(window_id);
1361 if (!window) {
1362 did_misbehave("AddWindowStealingForClient: Bad window ID");
1363 return;
1364 }
1365
1366 if (!from_client_id(client_id)) {
1367 did_misbehave("AddWindowStealingForClient: Bad client ID");
1368 return;
1369 }
1370
1371 window->add_stealing_for_client(client_id);
1372}
1373
1374void ConnectionFromClient::remove_window_stealing_for_client(i32 client_id, i32 window_id)
1375{
1376 auto* window = window_from_id(window_id);
1377 if (!window) {
1378 did_misbehave("RemoveWindowStealingForClient: Bad window ID");
1379 return;
1380 }
1381
1382 // Don't check if the client exists, it may have died
1383
1384 window->remove_stealing_for_client(client_id);
1385}
1386
1387void ConnectionFromClient::remove_window_stealing(i32 window_id)
1388{
1389 auto* window = window_from_id(window_id);
1390 if (!window) {
1391 did_misbehave("RemoveWindowStealing: Bad window ID");
1392 return;
1393 }
1394
1395 window->remove_all_stealing();
1396}
1397
1398void ConnectionFromClient::set_always_on_top(i32 window_id, bool always_on_top)
1399{
1400 auto* window = window_from_id(window_id);
1401 if (!window) {
1402 did_misbehave("SetAlwaysOnTop: Bad window ID");
1403 return;
1404 }
1405
1406 window->set_always_on_top(always_on_top);
1407}
1408
1409void ConnectionFromClient::notify_about_theme_change()
1410{
1411 // Recalculate minimum size for each window, using the new theme metrics.
1412 // FIXME: We only ever increase the minimum size, which means that if you go from a theme with large buttons
1413 // (eg Basalt) to one with smaller buttons (eg Default) then the minimum size will remain large. This
1414 // only happens with pre-existing windows, and it's unlikely that you will ever have windows that are
1415 // so small, so it's probably fine, but it is technically a bug. :^)
1416 for_each_window([](auto& window) -> IterationDecision {
1417 auto system_window_minimum_size = calculate_minimum_size_for_window(window);
1418
1419 auto old_minimum_size = window.minimum_size();
1420 auto new_rect = window.rect();
1421
1422 window.set_minimum_size({ max(old_minimum_size.width(), system_window_minimum_size.width()),
1423 max(old_minimum_size.height(), system_window_minimum_size.height()) });
1424 if (window.apply_minimum_size(new_rect)) {
1425 window.set_rect(new_rect);
1426 window.refresh_client_size();
1427 }
1428
1429 return IterationDecision::Continue;
1430 });
1431 async_update_system_theme(Gfx::current_system_theme_buffer());
1432}
1433
1434}