Serenity Operating System
1/*
2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#pragma once
8
9#include "WindowStack.h"
10#include <AK/HashMap.h>
11#include <AK/HashTable.h>
12#include <AK/WeakPtr.h>
13#include <LibCore/ConfigFile.h>
14#include <LibCore/ElapsedTimer.h>
15#include <LibGfx/Color.h>
16#include <LibGfx/DisjointRectSet.h>
17#include <LibGfx/Painter.h>
18#include <LibGfx/Palette.h>
19#include <LibGfx/Rect.h>
20#include <WindowServer/Cursor.h>
21#include <WindowServer/Event.h>
22#include <WindowServer/KeymapSwitcher.h>
23#include <WindowServer/MenuManager.h>
24#include <WindowServer/ResizeDirection.h>
25#include <WindowServer/ScreenLayout.h>
26#include <WindowServer/SystemEffects.h>
27#include <WindowServer/WMConnectionFromClient.h>
28#include <WindowServer/WindowSwitcher.h>
29#include <WindowServer/WindowType.h>
30
31namespace WindowServer {
32
33int const double_click_speed_max = 900;
34int const double_click_speed_min = 100;
35
36extern RefPtr<Core::ConfigFile> g_config;
37
38class Screen;
39class MouseEvent;
40class Window;
41class ConnectionFromClient;
42class WindowSwitcher;
43class Button;
44class DndOverlay;
45class WindowGeometryOverlay;
46
47class WindowManager : public Core::Object {
48 C_OBJECT(WindowManager)
49
50 friend class Compositor;
51 friend class WindowFrame;
52 friend class WindowSwitcher;
53
54public:
55 static constexpr size_t default_window_stack_rows = 2;
56 static constexpr size_t default_window_stack_columns = 2;
57 static_assert(default_window_stack_rows >= 1);
58 static_assert(default_window_stack_columns >= 1);
59 static constexpr unsigned max_window_stack_rows = 16;
60 static constexpr unsigned max_window_stack_columns = 16;
61
62 static WindowManager& the();
63
64 virtual ~WindowManager() override = default;
65
66 Palette palette() const { return Palette(*m_palette); }
67
68 RefPtr<Core::ConfigFile> config() const { return m_config; }
69 void reload_config();
70
71 void add_window(Window&);
72 void remove_window(Window&);
73
74 void notify_title_changed(Window&);
75 void notify_rect_changed(Window&, Gfx::IntRect const& oldRect, Gfx::IntRect const& newRect);
76 void notify_minimization_state_changed(Window&);
77 void notify_opacity_changed(Window&);
78 void notify_occlusion_state_changed(Window&);
79 void notify_progress_changed(Window&);
80 void notify_modified_changed(Window&);
81
82 Gfx::IntRect tiled_window_rect(Window const&, WindowTileType tile_type = WindowTileType::Maximized, bool relative_to_window_screen = false) const;
83
84 ConnectionFromClient const* dnd_client() const { return m_dnd_client.ptr(); }
85 Core::MimeData const& dnd_mime_data() const { return *m_dnd_mime_data; }
86
87 void start_dnd_drag(ConnectionFromClient&, DeprecatedString const& text, Gfx::Bitmap const*, Core::MimeData const&);
88 void end_dnd_drag();
89
90 void set_accepts_drag(bool);
91
92 Window* active_window()
93 {
94 VERIFY(m_current_window_stack);
95 return m_current_window_stack->active_window();
96 }
97 Window const* active_window() const
98 {
99 VERIFY(m_current_window_stack);
100 return m_current_window_stack->active_window();
101 }
102
103 Window* foremost_popup_window(WindowStack& stack = WindowManager::the().current_window_stack());
104 void request_close_fragile_windows(WindowStack& stack = WindowManager::the().current_window_stack());
105
106 ConnectionFromClient const* active_client() const;
107
108 Window* window_with_active_menu() { return m_window_with_active_menu; }
109 Window const* window_with_active_menu() const { return m_window_with_active_menu; }
110 void set_window_with_active_menu(Window*);
111
112 Window* highlight_window() { return m_highlight_window; }
113 Window const* highlight_window() const { return m_highlight_window; }
114 void set_highlight_window(Window*);
115
116 void move_to_front_and_make_active(Window&);
117
118 Gfx::IntRect desktop_rect(Screen&) const;
119 Gfx::IntRect arena_rect_for_type(Screen&, WindowType) const;
120
121 Cursor const& active_cursor() const;
122 Cursor const& hidden_cursor() const { return *m_hidden_cursor; }
123 Cursor const& arrow_cursor() const { return *m_arrow_cursor; }
124 Cursor const& crosshair_cursor() const { return *m_crosshair_cursor; }
125 Cursor const& hand_cursor() const { return *m_hand_cursor; }
126 Cursor const& help_cursor() const { return *m_help_cursor; }
127 Cursor const& resize_horizontally_cursor() const { return *m_resize_horizontally_cursor; }
128 Cursor const& resize_vertically_cursor() const { return *m_resize_vertically_cursor; }
129 Cursor const& resize_diagonally_tlbr_cursor() const { return *m_resize_diagonally_tlbr_cursor; }
130 Cursor const& resize_diagonally_bltr_cursor() const { return *m_resize_diagonally_bltr_cursor; }
131 Cursor const& resize_column_cursor() const { return *m_resize_column_cursor; }
132 Cursor const& resize_row_cursor() const { return *m_resize_row_cursor; }
133 Cursor const& i_beam_cursor() const { return *m_i_beam_cursor; }
134 Cursor const& disallowed_cursor() const { return *m_disallowed_cursor; }
135 Cursor const& move_cursor() const { return *m_move_cursor; }
136 Cursor const& drag_cursor() const { return *m_drag_cursor; }
137 Cursor const& drag_copy_cursor() const { return *m_drag_copy_cursor; }
138 Cursor const& wait_cursor() const { return *m_wait_cursor; }
139 Cursor const& eyedropper_cursor() const { return *m_eyedropper_cursor; }
140 Cursor const& zoom_cursor() const { return *m_zoom_cursor; }
141
142 int cursor_highlight_radius() const { return m_cursor_highlight_radius; }
143 Gfx::Color cursor_highlight_color() const { return m_cursor_highlight_color; }
144
145 Gfx::Font const& font() const;
146 Gfx::Font const& window_title_font() const;
147
148 bool set_screen_layout(ScreenLayout&&, bool, DeprecatedString&);
149 ScreenLayout get_screen_layout() const;
150 bool save_screen_layout(DeprecatedString&);
151
152 void set_acceleration_factor(double);
153 void set_scroll_step_size(unsigned);
154 void set_double_click_speed(int);
155 int double_click_speed() const;
156 void set_mouse_buttons_switched(bool);
157 bool are_mouse_buttons_switched() const;
158 void set_natural_scroll(bool);
159 bool is_natural_scroll() const;
160
161 void set_active_window(Window*);
162 void set_hovered_button(Button*);
163
164 Button const* cursor_tracking_button() const { return m_cursor_tracking_button.ptr(); }
165 void set_cursor_tracking_button(Button*);
166
167 void set_resize_candidate(Window&, ResizeDirection);
168 void clear_resize_candidate();
169 ResizeDirection resize_direction_of_window(Window const&);
170
171 void greet_window_manager(WMConnectionFromClient&);
172 void tell_wms_window_state_changed(Window&);
173 void tell_wms_window_icon_changed(Window&);
174 void tell_wms_window_rect_changed(Window&);
175 void tell_wms_screen_rects_changed();
176 void tell_wms_applet_area_size_changed(Gfx::IntSize);
177 void tell_wms_super_key_pressed();
178 void tell_wms_super_space_key_pressed();
179 void tell_wms_super_d_key_pressed();
180 void tell_wms_super_digit_key_pressed(u8);
181 void tell_wms_current_window_stack_changed();
182
183 void check_hide_geometry_overlay(Window&);
184
185 void start_window_resize(Window&, Gfx::IntPoint, MouseButton, ResizeDirection);
186 void start_window_resize(Window&, MouseEvent const&, ResizeDirection);
187 void start_window_move(Window&, MouseEvent const&);
188 void start_window_move(Window&, Gfx::IntPoint);
189
190 Window const* active_fullscreen_window() const
191 {
192 if (active_window() && active_window()->is_fullscreen())
193 return active_window();
194 return nullptr;
195 };
196
197 Window* active_fullscreen_window()
198 {
199 if (active_window() && active_window()->is_fullscreen())
200 return active_window();
201 return nullptr;
202 }
203
204 bool update_theme(DeprecatedString theme_path, DeprecatedString theme_name, bool keep_desktop_background, Optional<DeprecatedString> const& color_scheme_path);
205 void invalidate_after_theme_or_font_change();
206
207 bool set_theme_override(Core::AnonymousBuffer const& theme_override);
208 Optional<Core::AnonymousBuffer> get_theme_override() const;
209 void clear_theme_override();
210 bool is_theme_overridden() { return m_theme_overridden; }
211 Optional<DeprecatedString> get_preferred_color_scheme() { return m_preferred_color_scheme; }
212
213 bool set_hovered_window(Window*);
214 void deliver_mouse_event(Window&, MouseEvent const&);
215
216 void did_popup_a_menu(Badge<Menu>);
217
218 void system_menu_doubleclick(Window& window, MouseEvent const& event);
219 bool is_menu_doubleclick(Window& window, MouseEvent const& event) const;
220
221 void minimize_windows(Window&, bool);
222 void hide_windows(Window&, bool);
223 void maximize_windows(Window&, bool);
224 void set_always_on_top(Window&, bool);
225
226 template<typename Callback>
227 Window* for_each_window_in_modal_chain(Window& window, Callback callback)
228 {
229 Function<Window*(Window&)> recurse = [&](Window& w) -> Window* {
230 if (!w.is_modal()) {
231 auto decision = callback(w);
232 if (decision == IterationDecision::Break)
233 return &w;
234 }
235 for (auto& child : w.child_windows()) {
236 if (!child || child->is_destroyed() || !child->is_modal())
237 continue;
238 auto decision = callback(*child);
239 if (auto* result = recurse(*child))
240 return result;
241 if (decision == IterationDecision::Break)
242 return child;
243 }
244 return nullptr;
245 };
246 if (auto* modeless = window.modeless_ancestor())
247 return recurse(*modeless);
248 return nullptr;
249 }
250 bool is_window_in_modal_chain(Window& chain_window, Window& other_window);
251
252 Gfx::IntPoint get_recommended_window_position(Gfx::IntPoint desired);
253
254 void reload_icon_bitmaps_after_scale_change();
255
256 void reevaluate_hover_state_for_window(Window* = nullptr);
257 Window* hovered_window() const { return m_hovered_window.ptr(); }
258
259 void switch_to_window_stack(WindowStack&, Window* = nullptr, bool show_overlay = true);
260 void switch_to_window_stack(u32 row, u32 col, Window* carry = nullptr, bool show_overlay = true)
261 {
262 if (row < window_stack_rows() && col < window_stack_columns())
263 switch_to_window_stack(*(*m_window_stacks[row])[col], carry, show_overlay);
264 }
265
266 size_t window_stack_rows() const { return m_window_stacks.size(); }
267 size_t window_stack_columns() const { return m_window_stacks[0]->size(); }
268
269 bool apply_workspace_settings(unsigned rows, unsigned columns, bool save);
270
271 WindowStack& current_window_stack()
272 {
273 VERIFY(m_current_window_stack);
274 return *m_current_window_stack;
275 }
276
277 template<typename F>
278 IterationDecision for_each_window_stack(F f)
279 {
280 for (auto& row : m_window_stacks) {
281 for (auto& stack : *row) {
282 IterationDecision decision = f(*stack);
283 if (decision != IterationDecision::Continue)
284 return decision;
285 }
286 }
287 return IterationDecision::Continue;
288 }
289
290 WindowStack& window_stack_for_window(Window&);
291
292 static constexpr bool is_stationary_window_type(WindowType window_type)
293 {
294 switch (window_type) {
295 case WindowType::Normal:
296 return false;
297 default:
298 return true;
299 }
300 }
301
302 static constexpr bool is_fragile_window_type(WindowType window_type)
303 {
304 switch (window_type) {
305 case WindowType::Autocomplete:
306 case WindowType::Popup:
307 case WindowType::Tooltip:
308 return true;
309 default:
310 return false;
311 }
312 }
313
314 void did_switch_window_stack(Badge<Compositor>, WindowStack&, WindowStack&);
315
316 template<typename Callback>
317 IterationDecision for_each_visible_window_from_back_to_front(Callback, WindowStack* = nullptr);
318 template<typename Callback>
319 IterationDecision for_each_visible_window_from_front_to_back(Callback, WindowStack* = nullptr);
320
321 MultiScaleBitmaps const* overlay_rect_shadow() const { return m_overlay_rect_shadow.ptr(); }
322
323 void apply_cursor_theme(DeprecatedString const& name);
324
325 void set_cursor_highlight_radius(int radius);
326 void set_cursor_highlight_color(Gfx::Color color);
327
328 bool is_cursor_highlight_enabled() const { return m_cursor_highlight_radius > 0 && m_cursor_highlight_enabled; }
329
330 void load_system_effects();
331 void apply_system_effects(Vector<bool>, ShowGeometry);
332 SystemEffects& system_effects() { return m_system_effects; }
333
334 RefPtr<KeymapSwitcher> keymap_switcher() { return m_keymap_switcher; }
335
336 Window* automatic_cursor_tracking_window() { return m_automatic_cursor_tracking_window; }
337 Window const* automatic_cursor_tracking_window() const { return m_automatic_cursor_tracking_window; }
338 void set_automatic_cursor_tracking_window(Window* window) { m_automatic_cursor_tracking_window = window; }
339
340 u8 last_processed_buttons() { return m_last_processed_buttons; }
341
342private:
343 explicit WindowManager(Gfx::PaletteImpl&);
344
345 void notify_new_active_window(Window&);
346 void notify_previous_active_window(Window&);
347 void notify_active_window_input_preempted();
348 void notify_active_window_input_restored();
349
350 void process_mouse_event(MouseEvent&);
351 void process_event_for_doubleclick(Window& window, MouseEvent& event);
352 bool process_ongoing_window_resize(MouseEvent const&);
353 bool process_ongoing_window_move(MouseEvent&);
354 bool process_ongoing_drag(MouseEvent&);
355 bool process_ongoing_active_input_mouse_event(MouseEvent const&);
356 bool process_mouse_event_for_titlebar_buttons(MouseEvent const&);
357 void process_mouse_event_for_window(HitTestResult&, MouseEvent const&);
358
359 void process_key_event(KeyEvent&);
360
361 template<typename Callback>
362 void for_each_window_manager(Callback);
363
364 virtual void event(Core::Event&) override;
365 void tell_wm_about_window(WMConnectionFromClient& conn, Window&);
366 void tell_wm_about_window_icon(WMConnectionFromClient& conn, Window&);
367 void tell_wm_about_window_rect(WMConnectionFromClient& conn, Window&);
368 void tell_wm_about_current_window_stack(WMConnectionFromClient&);
369 void pick_new_active_window(Window*);
370
371 bool sync_config_to_disk();
372
373 [[nodiscard]] static WindowStack& get_rendering_window_stacks(WindowStack*&);
374
375 RefPtr<Cursor const> m_hidden_cursor;
376 RefPtr<Cursor const> m_arrow_cursor;
377 RefPtr<Cursor const> m_hand_cursor;
378 RefPtr<Cursor const> m_help_cursor;
379 RefPtr<Cursor const> m_resize_horizontally_cursor;
380 RefPtr<Cursor const> m_resize_vertically_cursor;
381 RefPtr<Cursor const> m_resize_diagonally_tlbr_cursor;
382 RefPtr<Cursor const> m_resize_diagonally_bltr_cursor;
383 RefPtr<Cursor const> m_resize_column_cursor;
384 RefPtr<Cursor const> m_resize_row_cursor;
385 RefPtr<Cursor const> m_i_beam_cursor;
386 RefPtr<Cursor const> m_disallowed_cursor;
387 RefPtr<Cursor const> m_move_cursor;
388 RefPtr<Cursor const> m_drag_cursor;
389 RefPtr<Cursor const> m_drag_copy_cursor;
390 RefPtr<Cursor const> m_wait_cursor;
391 RefPtr<Cursor const> m_crosshair_cursor;
392 RefPtr<Cursor const> m_eyedropper_cursor;
393 RefPtr<Cursor const> m_zoom_cursor;
394 int m_cursor_highlight_radius { 0 };
395 Gfx::Color m_cursor_highlight_color;
396 bool m_cursor_highlight_enabled { false };
397
398 RefPtr<MultiScaleBitmaps> m_overlay_rect_shadow;
399
400 // Setup 2 rows 1 column by default
401 Vector<NonnullOwnPtr<Vector<NonnullOwnPtr<WindowStack>, default_window_stack_columns>>, default_window_stack_rows> m_window_stacks;
402 WindowStack* m_current_window_stack { nullptr };
403
404 struct DoubleClickInfo {
405 struct ClickMetadata {
406 Core::ElapsedTimer clock;
407 Gfx::IntPoint last_position;
408 };
409
410 ClickMetadata const& metadata_for_button(MouseButton) const;
411 ClickMetadata& metadata_for_button(MouseButton);
412
413 void reset()
414 {
415 m_primary = {};
416 m_secondary = {};
417 m_middle = {};
418 m_backward = {};
419 m_forward = {};
420 }
421
422 WeakPtr<Window> m_clicked_window;
423
424 private:
425 ClickMetadata m_primary;
426 ClickMetadata m_secondary;
427 ClickMetadata m_middle;
428 ClickMetadata m_backward;
429 ClickMetadata m_forward;
430 };
431
432 bool is_considered_doubleclick(MouseEvent const&, DoubleClickInfo::ClickMetadata const&) const;
433
434 Gfx::IntPoint to_floating_cursor_position(Gfx::IntPoint) const;
435
436 DoubleClickInfo m_double_click_info;
437 int m_double_click_speed { 0 };
438 int m_max_distance_for_double_click { 4 };
439 bool m_previous_event_was_super_keydown { false };
440 bool m_mouse_buttons_switched { false };
441 bool m_natural_scroll { false };
442 bool m_theme_overridden { false };
443 Optional<DeprecatedString> m_preferred_color_scheme { OptionalNone() };
444
445 WeakPtr<Window> m_hovered_window;
446 WeakPtr<Window> m_highlight_window;
447 WeakPtr<Window> m_window_with_active_menu;
448 WeakPtr<Window> m_automatic_cursor_tracking_window;
449
450 OwnPtr<WindowGeometryOverlay> m_geometry_overlay;
451 WeakPtr<Window> m_move_window;
452 Gfx::IntPoint m_move_origin;
453 Gfx::IntPoint m_move_window_origin;
454 Gfx::IntPoint m_move_window_cursor_position;
455 Gfx::IntPoint m_mouse_down_origin;
456
457 WeakPtr<Window> m_resize_window;
458 WeakPtr<Window> m_resize_candidate;
459 MouseButton m_resizing_mouse_button { MouseButton::None };
460 Gfx::IntRect m_resize_window_original_rect;
461 Gfx::IntPoint m_resize_origin;
462 ResizeDirection m_resize_direction { ResizeDirection::None };
463
464 u8 m_keyboard_modifiers { 0 };
465 u8 m_last_processed_buttons { MouseButton::None };
466
467 NonnullRefPtr<WindowSwitcher> m_switcher;
468 NonnullRefPtr<KeymapSwitcher> m_keymap_switcher;
469
470 WeakPtr<Button> m_cursor_tracking_button;
471 WeakPtr<Button> m_hovered_button;
472
473 NonnullRefPtr<Gfx::PaletteImpl> m_palette;
474
475 RefPtr<Core::ConfigFile> m_config;
476
477 OwnPtr<DndOverlay> m_dnd_overlay;
478 WeakPtr<ConnectionFromClient> m_dnd_client;
479 DeprecatedString m_dnd_text;
480 bool m_dnd_accepts_drag { false };
481
482 RefPtr<Core::MimeData const> m_dnd_mime_data;
483
484 WindowStack* m_switching_to_window_stack { nullptr };
485 Vector<WeakPtr<Window>, 4> m_carry_window_to_new_stack;
486
487 SystemEffects m_system_effects;
488};
489
490template<typename Callback>
491inline IterationDecision WindowManager::for_each_visible_window_from_back_to_front(Callback callback, WindowStack* specific_stack)
492{
493 auto* window_stack = specific_stack;
494 WindowStack* transitioning_to_window_stack = nullptr;
495 if (!window_stack)
496 window_stack = &get_rendering_window_stacks(transitioning_to_window_stack);
497 auto for_each_window = [&]<WindowType window_type>() {
498 if constexpr (is_stationary_window_type(window_type)) {
499 auto& stationary_stack = window_stack->stationary_window_stack();
500 return stationary_stack.for_each_visible_window_of_type_from_back_to_front(window_type, callback);
501 } else {
502 auto decision = window_stack->for_each_visible_window_of_type_from_back_to_front(window_type, callback);
503 if (decision == IterationDecision::Continue && transitioning_to_window_stack)
504 decision = transitioning_to_window_stack->for_each_visible_window_of_type_from_back_to_front(window_type, callback);
505 return decision;
506 }
507 };
508 if (for_each_window.template operator()<WindowType::Desktop>() == IterationDecision::Break)
509 return IterationDecision::Break;
510 if (for_each_window.template operator()<WindowType::Normal>() == IterationDecision::Break)
511 return IterationDecision::Break;
512 if (for_each_window.template operator()<WindowType::Taskbar>() == IterationDecision::Break)
513 return IterationDecision::Break;
514 if (for_each_window.template operator()<WindowType::AppletArea>() == IterationDecision::Break)
515 return IterationDecision::Break;
516 if (for_each_window.template operator()<WindowType::Notification>() == IterationDecision::Break)
517 return IterationDecision::Break;
518 if (for_each_window.template operator()<WindowType::Autocomplete>() == IterationDecision::Break)
519 return IterationDecision::Break;
520 if (for_each_window.template operator()<WindowType::Popup>() == IterationDecision::Break)
521 return IterationDecision::Break;
522 if (for_each_window.template operator()<WindowType::Tooltip>() == IterationDecision::Break)
523 return IterationDecision::Break;
524 if (for_each_window.template operator()<WindowType::Menu>() == IterationDecision::Break)
525 return IterationDecision::Break;
526 return for_each_window.template operator()<WindowType::WindowSwitcher>();
527}
528
529template<typename Callback>
530inline IterationDecision WindowManager::for_each_visible_window_from_front_to_back(Callback callback, WindowStack* specific_stack)
531{
532 auto* window_stack = specific_stack;
533 WindowStack* transitioning_to_window_stack = nullptr;
534 if (!window_stack)
535 window_stack = &get_rendering_window_stacks(transitioning_to_window_stack);
536 auto for_each_window = [&]<WindowType window_type>() {
537 if constexpr (is_stationary_window_type(window_type)) {
538 auto& stationary_stack = window_stack->stationary_window_stack();
539 return stationary_stack.for_each_visible_window_of_type_from_front_to_back(window_type, callback);
540 } else {
541 auto decision = window_stack->for_each_visible_window_of_type_from_front_to_back(window_type, callback);
542 if (decision == IterationDecision::Continue && transitioning_to_window_stack)
543 decision = transitioning_to_window_stack->for_each_visible_window_of_type_from_front_to_back(window_type, callback);
544 return decision;
545 }
546 };
547 if (for_each_window.template operator()<WindowType::WindowSwitcher>() == IterationDecision::Break)
548 return IterationDecision::Break;
549 if (for_each_window.template operator()<WindowType::Menu>() == IterationDecision::Break)
550 return IterationDecision::Break;
551 if (for_each_window.template operator()<WindowType::Tooltip>() == IterationDecision::Break)
552 return IterationDecision::Break;
553 if (for_each_window.template operator()<WindowType::Popup>() == IterationDecision::Break)
554 return IterationDecision::Break;
555 if (for_each_window.template operator()<WindowType::Autocomplete>() == IterationDecision::Break)
556 return IterationDecision::Break;
557 if (for_each_window.template operator()<WindowType::Notification>() == IterationDecision::Break)
558 return IterationDecision::Break;
559 if (for_each_window.template operator()<WindowType::AppletArea>() == IterationDecision::Break)
560 return IterationDecision::Break;
561 if (for_each_window.template operator()<WindowType::Taskbar>() == IterationDecision::Break)
562 return IterationDecision::Break;
563 if (for_each_window.template operator()<WindowType::Normal>() == IterationDecision::Break)
564 return IterationDecision::Break;
565 return for_each_window.template operator()<WindowType::Desktop>();
566}
567
568template<typename Callback>
569void WindowManager::for_each_window_manager(Callback callback)
570{
571 auto& connections = WMConnectionFromClient::s_connections;
572
573 // FIXME: this isn't really ordered... does it need to be?
574 for (auto it = connections.begin(); it != connections.end(); ++it) {
575 if (callback(*it->value) == IterationDecision::Break)
576 return;
577 }
578}
579
580}