Serenity Operating System
1/*
2 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Debug.h>
8#include <AK/StringBuilder.h>
9#include <LibCore/EventLoop.h>
10#include <LibCore/MimeData.h>
11#include <LibGUI/Action.h>
12#include <LibGUI/Application.h>
13#include <LibGUI/CommandPalette.h>
14#include <LibGUI/ConnectionToWindowServer.h>
15#include <LibGUI/Desktop.h>
16#include <LibGUI/DisplayLink.h>
17#include <LibGUI/DragOperation.h>
18#include <LibGUI/EmojiInputDialog.h>
19#include <LibGUI/Event.h>
20#include <LibGUI/Menu.h>
21#include <LibGUI/MouseTracker.h>
22#include <LibGUI/Shortcut.h>
23#include <LibGUI/Window.h>
24#include <LibGfx/Bitmap.h>
25#include <LibGfx/Font/FontDatabase.h>
26#include <LibGfx/Palette.h>
27#include <LibGfx/SystemTheme.h>
28
29namespace GUI {
30
31ConnectionToWindowServer& ConnectionToWindowServer::the()
32{
33 static RefPtr<ConnectionToWindowServer> s_connection = nullptr;
34 if (!s_connection)
35 s_connection = ConnectionToWindowServer::try_create().release_value_but_fixme_should_propagate_errors();
36 return *s_connection;
37}
38
39static void set_system_theme_from_anonymous_buffer(Core::AnonymousBuffer buffer)
40{
41 Gfx::set_system_theme(buffer);
42 Application::the()->set_system_palette(buffer);
43}
44
45ConnectionToWindowServer::ConnectionToWindowServer(NonnullOwnPtr<Core::LocalSocket> socket)
46 : IPC::ConnectionToServer<WindowClientEndpoint, WindowServerEndpoint>(*this, move(socket))
47{
48 // NOTE: WindowServer automatically sends a "fast_greet" message to us when we connect.
49 // All we have to do is wait for it to arrive. This avoids a round-trip during application startup.
50 auto message = wait_for_specific_message<Messages::WindowClient::FastGreet>();
51 set_system_theme_from_anonymous_buffer(message->theme_buffer());
52 Desktop::the().did_receive_screen_rects({}, message->screen_rects(), message->main_screen_index(), message->workspace_rows(), message->workspace_columns());
53 Desktop::the().set_system_effects(message->effects());
54 Gfx::FontDatabase::set_default_font_query(message->default_font_query());
55 Gfx::FontDatabase::set_fixed_width_font_query(message->fixed_width_font_query());
56 Gfx::FontDatabase::set_window_title_font_query(message->window_title_font_query());
57 m_client_id = message->client_id();
58}
59
60void ConnectionToWindowServer::fast_greet(Vector<Gfx::IntRect> const&, u32, u32, u32, Core::AnonymousBuffer const&, DeprecatedString const&, DeprecatedString const&, DeprecatedString const&, Vector<bool> const&, i32)
61{
62 // NOTE: This message is handled in the constructor.
63}
64
65void ConnectionToWindowServer::update_system_theme(Core::AnonymousBuffer const& theme_buffer)
66{
67 set_system_theme_from_anonymous_buffer(theme_buffer);
68 Window::update_all_windows({});
69 Window::for_each_window({}, [](auto& window) {
70 Core::EventLoop::current().post_event(window, make<ThemeChangeEvent>());
71 });
72
73 Application::the()->dispatch_event(*make<ThemeChangeEvent>());
74}
75
76void ConnectionToWindowServer::update_system_fonts(DeprecatedString const& default_font_query, DeprecatedString const& fixed_width_font_query, DeprecatedString const& window_title_font_query)
77{
78 Gfx::FontDatabase::set_default_font_query(default_font_query);
79 Gfx::FontDatabase::set_fixed_width_font_query(fixed_width_font_query);
80 Gfx::FontDatabase::set_window_title_font_query(window_title_font_query);
81 Window::update_all_windows({});
82 Window::for_each_window({}, [](auto& window) {
83 Core::EventLoop::current().post_event(window, make<FontsChangeEvent>());
84 });
85}
86
87void ConnectionToWindowServer::update_system_effects(Vector<bool> const& effects)
88{
89 Desktop::the().set_system_effects(effects);
90}
91
92void ConnectionToWindowServer::paint(i32 window_id, Gfx::IntSize window_size, Vector<Gfx::IntRect> const& rects)
93{
94 if (auto* window = Window::from_window_id(window_id))
95 Core::EventLoop::current().post_event(*window, make<MultiPaintEvent>(rects, window_size));
96}
97
98void ConnectionToWindowServer::window_resized(i32 window_id, Gfx::IntRect const& new_rect)
99{
100 if (auto* window = Window::from_window_id(window_id)) {
101 Core::EventLoop::current().post_event(*window, make<ResizeEvent>(new_rect.size()));
102 }
103}
104
105void ConnectionToWindowServer::window_moved(i32 window_id, Gfx::IntRect const& new_rect)
106{
107 if (auto* window = Window::from_window_id(window_id))
108 Core::EventLoop::current().post_event(*window, make<MoveEvent>(new_rect.location()));
109}
110
111void ConnectionToWindowServer::window_activated(i32 window_id)
112{
113 if (auto* window = Window::from_window_id(window_id))
114 Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowBecameActive));
115}
116
117void ConnectionToWindowServer::window_deactivated(i32 window_id)
118{
119 if (auto* window = Window::from_window_id(window_id))
120 Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowBecameInactive));
121}
122
123void ConnectionToWindowServer::window_input_preempted(i32 window_id)
124{
125 if (auto* window = Window::from_window_id(window_id))
126 Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowInputPreempted));
127}
128
129void ConnectionToWindowServer::window_input_restored(i32 window_id)
130{
131 if (auto* window = Window::from_window_id(window_id))
132 Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowInputRestored));
133}
134
135void ConnectionToWindowServer::window_close_request(i32 window_id)
136{
137 if (auto* window = Window::from_window_id(window_id))
138 Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowCloseRequest));
139}
140
141void ConnectionToWindowServer::window_entered(i32 window_id)
142{
143 if (auto* window = Window::from_window_id(window_id))
144 Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowEntered));
145}
146
147void ConnectionToWindowServer::window_left(i32 window_id)
148{
149 if (auto* window = Window::from_window_id(window_id))
150 Core::EventLoop::current().post_event(*window, make<Event>(Event::WindowLeft));
151}
152
153static Action* action_for_shortcut(Window& window, Shortcut const& shortcut)
154{
155 if (!shortcut.is_valid())
156 return nullptr;
157
158 dbgln_if(KEYBOARD_SHORTCUTS_DEBUG, "Looking up action for {}", shortcut.to_deprecated_string());
159
160 for (auto* widget = window.focused_widget(); widget; widget = widget->parent_widget()) {
161 if (auto* action = widget->action_for_shortcut(shortcut)) {
162 dbgln_if(KEYBOARD_SHORTCUTS_DEBUG, " > Focused widget {} gave action: {} {} (enabled: {}, shortcut: {}, alt-shortcut: {})", *widget, action, action->text(), action->is_enabled(), action->shortcut().to_deprecated_string(), action->alternate_shortcut().to_deprecated_string());
163 return action;
164 }
165 }
166
167 if (auto* action = window.action_for_shortcut(shortcut)) {
168 dbgln_if(KEYBOARD_SHORTCUTS_DEBUG, " > Asked window {}, got action: {} {} (enabled: {}, shortcut: {}, alt-shortcut: {})", window, action, action->text(), action->is_enabled(), action->shortcut().to_deprecated_string(), action->alternate_shortcut().to_deprecated_string());
169 return action;
170 }
171
172 // NOTE: Application-global shortcuts are ignored while a blocking modal window is up.
173 if (!window.is_blocking() && !window.is_popup()) {
174 if (auto* action = Application::the()->action_for_shortcut(shortcut)) {
175 dbgln_if(KEYBOARD_SHORTCUTS_DEBUG, " > Asked application, got action: {} {} (enabled: {}, shortcut: {}, alt-shortcut: {})", action, action->text(), action->is_enabled(), action->shortcut().to_deprecated_string(), action->alternate_shortcut().to_deprecated_string());
176 return action;
177 }
178 }
179
180 return nullptr;
181}
182
183void ConnectionToWindowServer::key_down(i32 window_id, u32 code_point, u32 key, u32 modifiers, u32 scancode)
184{
185 auto* window = Window::from_window_id(window_id);
186 if (!window)
187 return;
188
189 auto key_event = make<KeyEvent>(Event::KeyDown, (KeyCode)key, modifiers, code_point, scancode);
190
191 bool focused_widget_accepts_emoji_input = window->focused_widget() && window->focused_widget()->on_emoji_input;
192 if (!window->blocks_emoji_input() && focused_widget_accepts_emoji_input && (modifiers == (Mod_Ctrl | Mod_Alt)) && key == Key_Space) {
193 auto emoji_input_dialog = EmojiInputDialog::construct(window);
194 if (emoji_input_dialog->exec() != EmojiInputDialog::ExecResult::OK)
195 return;
196
197 window->focused_widget()->on_emoji_input(emoji_input_dialog->selected_emoji_text());
198 return;
199 }
200
201 Core::EventLoop::current().post_event(*window, move(key_event));
202}
203
204void ConnectionToWindowServer::key_up(i32 window_id, u32 code_point, u32 key, u32 modifiers, u32 scancode)
205{
206 auto* window = Window::from_window_id(window_id);
207 if (!window)
208 return;
209
210 auto key_event = make<KeyEvent>(Event::KeyUp, (KeyCode)key, modifiers, code_point, scancode);
211 Core::EventLoop::current().post_event(*window, move(key_event));
212}
213
214static MouseButton to_mouse_button(u32 button)
215{
216 switch (button) {
217 case 0:
218 return MouseButton::None;
219 case 1:
220 return MouseButton::Primary;
221 case 2:
222 return MouseButton::Secondary;
223 case 4:
224 return MouseButton::Middle;
225 case 8:
226 return MouseButton::Backward;
227 case 16:
228 return MouseButton::Forward;
229 default:
230 VERIFY_NOT_REACHED();
231 break;
232 }
233}
234
235void ConnectionToWindowServer::mouse_down(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta_x, i32 wheel_delta_y, i32 wheel_raw_delta_x, i32 wheel_raw_delta_y)
236{
237 auto* window = Window::from_window_id(window_id);
238 if (!window)
239 return;
240
241 auto mouse_event = make<MouseEvent>(Event::MouseDown, mouse_position, buttons, to_mouse_button(button), modifiers, wheel_delta_x, wheel_delta_y, wheel_raw_delta_x, wheel_raw_delta_y);
242
243 if (auto* action = action_for_shortcut(*window, Shortcut(mouse_event->modifiers(), mouse_event->button()))) {
244 if (action->is_enabled()) {
245 action->flash_menubar_menu(*window);
246 action->activate();
247 return;
248 }
249 if (action->swallow_key_event_when_disabled())
250 return;
251 }
252
253 Core::EventLoop::current().post_event(*window, move(mouse_event));
254}
255
256void ConnectionToWindowServer::mouse_up(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta_x, i32 wheel_delta_y, i32 wheel_raw_delta_x, i32 wheel_raw_delta_y)
257{
258 if (auto* window = Window::from_window_id(window_id))
259 Core::EventLoop::current().post_event(*window, make<MouseEvent>(Event::MouseUp, mouse_position, buttons, to_mouse_button(button), modifiers, wheel_delta_x, wheel_delta_y, wheel_raw_delta_x, wheel_raw_delta_y));
260}
261
262void ConnectionToWindowServer::mouse_move(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta_x, i32 wheel_delta_y, i32 wheel_raw_delta_x, i32 wheel_raw_delta_y, bool is_drag, Vector<DeprecatedString> const& mime_types)
263{
264 if (auto* window = Window::from_window_id(window_id)) {
265 if (is_drag)
266 Core::EventLoop::current().post_event(*window, make<DragEvent>(Event::DragMove, mouse_position, mime_types));
267 else
268 Core::EventLoop::current().post_event(*window, make<MouseEvent>(Event::MouseMove, mouse_position, buttons, to_mouse_button(button), modifiers, wheel_delta_x, wheel_delta_y, wheel_raw_delta_x, wheel_raw_delta_y));
269 }
270}
271
272void ConnectionToWindowServer::mouse_double_click(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta_x, i32 wheel_delta_y, i32 wheel_raw_delta_x, i32 wheel_raw_delta_y)
273{
274 if (auto* window = Window::from_window_id(window_id))
275 Core::EventLoop::current().post_event(*window, make<MouseEvent>(Event::MouseDoubleClick, mouse_position, buttons, to_mouse_button(button), modifiers, wheel_delta_x, wheel_delta_y, wheel_raw_delta_x, wheel_raw_delta_y));
276}
277
278void ConnectionToWindowServer::mouse_wheel(i32 window_id, Gfx::IntPoint mouse_position, u32 button, u32 buttons, u32 modifiers, i32 wheel_delta_x, i32 wheel_delta_y, i32 wheel_raw_delta_x, i32 wheel_raw_delta_y)
279{
280 if (auto* window = Window::from_window_id(window_id))
281 Core::EventLoop::current().post_event(*window, make<MouseEvent>(Event::MouseWheel, mouse_position, buttons, to_mouse_button(button), modifiers, wheel_delta_x, wheel_delta_y, wheel_raw_delta_x, wheel_raw_delta_y));
282}
283
284void ConnectionToWindowServer::menu_visibility_did_change(i32 menu_id, bool visible)
285{
286 auto* menu = Menu::from_menu_id(menu_id);
287 if (!menu) {
288 dbgln("EventLoop received visibility change event for invalid menu ID {}", menu_id);
289 return;
290 }
291 menu->visibility_did_change({}, visible);
292}
293
294void ConnectionToWindowServer::menu_item_activated(i32 menu_id, u32 identifier)
295{
296 auto* menu = Menu::from_menu_id(menu_id);
297 if (!menu) {
298 dbgln("EventLoop received event for invalid menu ID {}", menu_id);
299 return;
300 }
301 if (auto* action = menu->action_at(identifier))
302 action->activate(menu);
303}
304
305void ConnectionToWindowServer::menu_item_entered(i32 menu_id, u32 identifier)
306{
307 auto* menu = Menu::from_menu_id(menu_id);
308 if (!menu) {
309 dbgln("ConnectionToWindowServer received MenuItemEntered for invalid menu ID {}", menu_id);
310 return;
311 }
312 auto* action = menu->action_at(identifier);
313 if (!action)
314 return;
315 auto* app = Application::the();
316 if (!app)
317 return;
318 Core::EventLoop::current().post_event(*app, make<ActionEvent>(GUI::Event::ActionEnter, *action));
319}
320
321void ConnectionToWindowServer::menu_item_left(i32 menu_id, u32 identifier)
322{
323 auto* menu = Menu::from_menu_id(menu_id);
324 if (!menu) {
325 dbgln("ConnectionToWindowServer received MenuItemLeft for invalid menu ID {}", menu_id);
326 return;
327 }
328 auto* action = menu->action_at(identifier);
329 if (!action)
330 return;
331 auto* app = Application::the();
332 if (!app)
333 return;
334 Core::EventLoop::current().post_event(*app, make<ActionEvent>(GUI::Event::ActionLeave, *action));
335}
336
337void ConnectionToWindowServer::screen_rects_changed(Vector<Gfx::IntRect> const& rects, u32 main_screen_index, u32 workspace_rows, u32 workspace_columns)
338{
339 Desktop::the().did_receive_screen_rects({}, rects, main_screen_index, workspace_rows, workspace_columns);
340 Window::for_each_window({}, [&](auto& window) {
341 Core::EventLoop::current().post_event(window, make<ScreenRectsChangeEvent>(rects, main_screen_index));
342 });
343}
344
345void ConnectionToWindowServer::applet_area_rect_changed(Gfx::IntRect const& rect)
346{
347 Window::for_each_window({}, [&](auto& window) {
348 Core::EventLoop::current().post_event(window, make<AppletAreaRectChangeEvent>(rect));
349 });
350}
351
352void ConnectionToWindowServer::drag_dropped(i32 window_id, Gfx::IntPoint mouse_position, DeprecatedString const& text, HashMap<DeprecatedString, ByteBuffer> const& mime_data)
353{
354 if (auto* window = Window::from_window_id(window_id)) {
355 auto mime_data_obj = Core::MimeData::construct(mime_data);
356 Core::EventLoop::current().post_event(*window, make<DropEvent>(mouse_position, text, mime_data_obj));
357 }
358}
359
360void ConnectionToWindowServer::drag_accepted()
361{
362 DragOperation::notify_accepted({});
363}
364
365void ConnectionToWindowServer::drag_cancelled()
366{
367 DragOperation::notify_cancelled({});
368 Application::the()->notify_drag_cancelled({});
369}
370
371void ConnectionToWindowServer::window_state_changed(i32 window_id, bool minimized, bool maximized, bool occluded)
372{
373 if (auto* window = Window::from_window_id(window_id))
374 window->notify_state_changed({}, minimized, maximized, occluded);
375}
376
377void ConnectionToWindowServer::display_link_notification()
378{
379 if (m_display_link_notification_pending)
380 return;
381
382 m_display_link_notification_pending = true;
383 deferred_invoke([this] {
384 DisplayLink::notify({});
385 m_display_link_notification_pending = false;
386 });
387}
388
389void ConnectionToWindowServer::track_mouse_move(Gfx::IntPoint mouse_position)
390{
391 MouseTracker::track_mouse_move({}, mouse_position);
392}
393
394void ConnectionToWindowServer::ping()
395{
396 async_pong();
397}
398
399}