1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/base/x/x11_window.h"
6
7 #include <algorithm>
8 #include <vector>
9
10 #include "base/bind.h"
11 #include "base/location.h"
12 #include "base/memory/weak_ptr.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/threading/thread_task_runner_handle.h"
16 #include "base/time/time.h"
17 #include "net/base/network_interfaces.h"
18 #include "third_party/skia/include/core/SkRegion.h"
19 #include "ui/base/hit_test_x11.h"
20 #include "ui/base/ui_base_features.h"
21 #include "ui/base/wm_role_names_linux.h"
22 #include "ui/base/x/x11_menu_registrar.h"
23 #include "ui/base/x/x11_pointer_grab.h"
24 #include "ui/base/x/x11_util.h"
25 #include "ui/events/devices/x11/device_data_manager_x11.h"
26 #include "ui/events/devices/x11/touch_factory_x11.h"
27 #include "ui/events/event.h"
28 #include "ui/events/event_utils.h"
29 #include "ui/events/platform/x11/x11_event_source.h"
30 #include "ui/events/x/events_x_utils.h"
31 #include "ui/events/x/x11_window_event_manager.h"
32 #include "ui/gfx/geometry/insets.h"
33 #include "ui/gfx/image/image_skia.h"
34 #include "ui/gfx/image/image_skia_rep.h"
35 #include "ui/gfx/skia_util.h"
36 #include "ui/gfx/x/connection.h"
37 #include "ui/gfx/x/x11_atom_cache.h"
38 #include "ui/gfx/x/x11_path.h"
39 #include "ui/gfx/x/xfixes.h"
40 #include "ui/gfx/x/xinput.h"
41 #include "ui/gfx/x/xproto.h"
42 #include "ui/gfx/x/xproto_util.h"
43 #include "ui/platform_window/common/platform_window_defaults.h"
44
45 namespace ui {
46
47 namespace {
48
49 // Special value of the _NET_WM_DESKTOP property which indicates that the window
50 // should appear on all workspaces/desktops.
51 const int kAllWorkspaces = 0xFFFFFFFF;
52
53 constexpr char kX11WindowRolePopup[] = "popup";
54 constexpr char kX11WindowRoleBubble[] = "bubble";
55 constexpr char kDarkGtkThemeVariant[] = "dark";
56
57 constexpr long kSystemTrayRequestDock = 0;
58
59 constexpr int kXembedInfoProtocolVersion = 0;
60 constexpr int kXembedFlagMap = 1 << 0;
61 constexpr int kXembedInfoFlags = kXembedFlagMap;
62
63 enum CrossingFlags : uint8_t {
64 CROSSING_FLAG_FOCUS = 1 << 0,
65 CROSSING_FLAG_SAME_SCREEN = 1 << 1,
66 };
67
68 // In some situations, views tries to make a zero sized window, and that
69 // makes us crash. Make sure we have valid sizes.
SanitizeBounds(const gfx::Rect & bounds)70 gfx::Rect SanitizeBounds(const gfx::Rect& bounds) {
71 gfx::Size sanitized_size(std::max(bounds.width(), 1),
72 std::max(bounds.height(), 1));
73 gfx::Rect sanitized_bounds(bounds.origin(), sanitized_size);
74 return sanitized_bounds;
75 }
76
SerializeImageRepresentation(const gfx::ImageSkiaRep & rep,std::vector<uint32_t> * data)77 void SerializeImageRepresentation(const gfx::ImageSkiaRep& rep,
78 std::vector<uint32_t>* data) {
79 uint32_t width = rep.GetWidth();
80 data->push_back(width);
81
82 uint32_t height = rep.GetHeight();
83 data->push_back(height);
84
85 const SkBitmap& bitmap = rep.GetBitmap();
86
87 for (uint32_t y = 0; y < height; ++y)
88 for (uint32_t x = 0; x < width; ++x)
89 data->push_back(bitmap.getColor(x, y));
90 }
91
XI2ModeToXMode(x11::Input::NotifyMode xi2_mode)92 x11::NotifyMode XI2ModeToXMode(x11::Input::NotifyMode xi2_mode) {
93 switch (xi2_mode) {
94 case x11::Input::NotifyMode::Normal:
95 return x11::NotifyMode::Normal;
96 case x11::Input::NotifyMode::Grab:
97 case x11::Input::NotifyMode::PassiveGrab:
98 return x11::NotifyMode::Grab;
99 case x11::Input::NotifyMode::Ungrab:
100 case x11::Input::NotifyMode::PassiveUngrab:
101 return x11::NotifyMode::Ungrab;
102 case x11::Input::NotifyMode::WhileGrabbed:
103 return x11::NotifyMode::WhileGrabbed;
104 default:
105 NOTREACHED();
106 return x11::NotifyMode::Normal;
107 }
108 }
109
XI2DetailToXDetail(x11::Input::NotifyDetail xi2_detail)110 x11::NotifyDetail XI2DetailToXDetail(x11::Input::NotifyDetail xi2_detail) {
111 switch (xi2_detail) {
112 case x11::Input::NotifyDetail::Ancestor:
113 return x11::NotifyDetail::Ancestor;
114 case x11::Input::NotifyDetail::Virtual:
115 return x11::NotifyDetail::Virtual;
116 case x11::Input::NotifyDetail::Inferior:
117 return x11::NotifyDetail::Inferior;
118 case x11::Input::NotifyDetail::Nonlinear:
119 return x11::NotifyDetail::Nonlinear;
120 case x11::Input::NotifyDetail::NonlinearVirtual:
121 return x11::NotifyDetail::NonlinearVirtual;
122 case x11::Input::NotifyDetail::Pointer:
123 return x11::NotifyDetail::Pointer;
124 case x11::Input::NotifyDetail::PointerRoot:
125 return x11::NotifyDetail::PointerRoot;
126 case x11::Input::NotifyDetail::None:
127 return x11::NotifyDetail::None;
128 }
129 }
130
SyncSetCounter(x11::Connection * connection,x11::Sync::Counter counter,int64_t value)131 void SyncSetCounter(x11::Connection* connection,
132 x11::Sync::Counter counter,
133 int64_t value) {
134 x11::Sync::Int64 sync_value{.hi = value >> 32, .lo = value & 0xFFFFFFFF};
135 connection->sync().SetCounter({counter, sync_value});
136 }
137
138 // Returns the whole path from |window| to the root.
GetParentsList(x11::Connection * connection,x11::Window window)139 std::vector<x11::Window> GetParentsList(x11::Connection* connection,
140 x11::Window window) {
141 std::vector<x11::Window> result;
142 while (window != x11::Window::None) {
143 result.push_back(window);
144 if (auto reply = connection->QueryTree({window}).Sync())
145 window = reply->parent;
146 else
147 break;
148 }
149 return result;
150 }
151
152 } // namespace
153
Configuration()154 XWindow::Configuration::Configuration()
155 : type(WindowType::kWindow),
156 opacity(WindowOpacity::kInferOpacity),
157 icon(nullptr),
158 activatable(true),
159 force_show_in_taskbar(false),
160 keep_on_top(false),
161 visible_on_all_workspaces(false),
162 remove_standard_frame(true),
163 prefer_dark_theme(false) {}
164
165 XWindow::Configuration::Configuration(const Configuration&) = default;
166
167 XWindow::Configuration::~Configuration() = default;
168
XWindow()169 XWindow::XWindow()
170 : connection_(x11::Connection::Get()), x_root_window_(GetX11RootWindow()) {
171 DCHECK(connection_);
172 DCHECK_NE(x_root_window_, x11::Window::None);
173 }
174
~XWindow()175 XWindow::~XWindow() {
176 DCHECK_EQ(xwindow_, x11::Window::None)
177 << "XWindow destructed without calling "
178 "Close() to release allocated resources.";
179 }
180
Init(const Configuration & config)181 void XWindow::Init(const Configuration& config) {
182 // Ensure that the X11MenuRegistrar exists. The X11MenuRegistrar is
183 // necessary to properly track menu windows.
184 X11MenuRegistrar::Get();
185
186 activatable_ = config.activatable;
187
188 x11::CreateWindowRequest req;
189 req.bit_gravity = x11::Gravity::NorthWest;
190 req.background_pixel = config.background_color.has_value()
191 ? config.background_color.value()
192 : connection_->default_screen().white_pixel;
193
194 x11::Atom window_type;
195 switch (config.type) {
196 case WindowType::kMenu:
197 req.override_redirect = x11::Bool32(true);
198 window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_MENU");
199 break;
200 case WindowType::kTooltip:
201 req.override_redirect = x11::Bool32(true);
202 window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_TOOLTIP");
203 break;
204 case WindowType::kPopup:
205 req.override_redirect = x11::Bool32(true);
206 window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_NOTIFICATION");
207 break;
208 case WindowType::kDrag:
209 req.override_redirect = x11::Bool32(true);
210 window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_DND");
211 break;
212 default:
213 window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_NORMAL");
214 break;
215 }
216 // An in-activatable window should not interact with the system wm.
217 if (!activatable_ || config.override_redirect)
218 req.override_redirect = x11::Bool32(true);
219
220 #if defined(OS_CHROMEOS)
221 req.override_redirect = x11::Bool32(UseTestConfigForPlatformWindows());
222 #endif
223
224 override_redirect_ = req.override_redirect.has_value();
225
226 bool enable_transparent_visuals;
227 switch (config.opacity) {
228 case WindowOpacity::kOpaqueWindow:
229 enable_transparent_visuals = false;
230 break;
231 case WindowOpacity::kTranslucentWindow:
232 enable_transparent_visuals = true;
233 break;
234 case WindowOpacity::kInferOpacity:
235 enable_transparent_visuals = config.type == WindowType::kDrag;
236 }
237
238 if (config.wm_role_name == kStatusIconWmRoleName) {
239 std::string atom_name =
240 "_NET_SYSTEM_TRAY_S" +
241 base::NumberToString(connection_->DefaultScreenId());
242 auto selection = connection_->GetSelectionOwner({gfx::GetAtom(atom_name)});
243 if (auto reply = selection.Sync()) {
244 GetProperty(reply->owner, gfx::GetAtom("_NET_SYSTEM_TRAY_VISUAL"),
245 &visual_id_);
246 }
247 }
248
249 x11::VisualId visual_id = visual_id_;
250 uint8_t depth = 0;
251 x11::ColorMap colormap{};
252 XVisualManager* visual_manager = XVisualManager::GetInstance();
253 if (visual_id_ == x11::VisualId{} ||
254 !visual_manager->GetVisualInfo(visual_id_, &depth, &colormap,
255 &visual_has_alpha_)) {
256 visual_manager->ChooseVisualForWindow(enable_transparent_visuals,
257 &visual_id, &depth, &colormap,
258 &visual_has_alpha_);
259 }
260
261 // x.org will BadMatch if we don't set a border when the depth isn't the
262 // same as the parent depth.
263 req.border_pixel = 0;
264
265 bounds_in_pixels_ = SanitizeBounds(config.bounds);
266 req.parent = x_root_window_;
267 req.x = bounds_in_pixels_.x();
268 req.y = bounds_in_pixels_.y();
269 req.width = bounds_in_pixels_.width();
270 req.height = bounds_in_pixels_.height();
271 req.depth = depth;
272 req.c_class = x11::WindowClass::InputOutput;
273 req.visual = visual_id;
274 req.colormap = colormap;
275 xwindow_ = connection_->GenerateId<x11::Window>();
276 req.wid = xwindow_;
277 connection_->CreateWindow(req);
278
279 // It can be a status icon window. If it fails to initialize, don't provide
280 // him with a native window handle, close self and let the client destroy
281 // ourselves.
282 if (config.wm_role_name == kStatusIconWmRoleName &&
283 !InitializeAsStatusIcon()) {
284 Close();
285 return;
286 }
287
288 OnXWindowCreated();
289
290 // TODO(erg): Maybe need to set a ViewProp here like in RWHL::RWHL().
291
292 auto event_mask =
293 x11::EventMask::ButtonPress | x11::EventMask::ButtonRelease |
294 x11::EventMask::FocusChange | x11::EventMask::KeyPress |
295 x11::EventMask::KeyRelease | x11::EventMask::EnterWindow |
296 x11::EventMask::LeaveWindow | x11::EventMask::Exposure |
297 x11::EventMask::VisibilityChange | x11::EventMask::StructureNotify |
298 x11::EventMask::PropertyChange | x11::EventMask::PointerMotion;
299 xwindow_events_ =
300 std::make_unique<XScopedEventSelector>(xwindow_, event_mask);
301 connection_->Flush();
302
303 if (IsXInput2Available())
304 TouchFactory::GetInstance()->SetupXI2ForXWindow(xwindow_);
305
306 // Request the _NET_WM_SYNC_REQUEST protocol which is used for synchronizing
307 // between chrome and desktop compositor (or WM) during resizing.
308 // The resizing behavior with _NET_WM_SYNC_REQUEST is:
309 // 1. Desktop compositor (or WM) sends client message _NET_WM_SYNC_REQUEST
310 // with a 64 bits counter to notify about an incoming resize.
311 // 2. Desktop compositor resizes chrome browser window.
312 // 3. Desktop compositor waits on an alert on value change of XSyncCounter on
313 // chrome window.
314 // 4. Chrome handles the ConfigureNotify event, and renders a new frame with
315 // the new size.
316 // 5. Chrome increases the XSyncCounter on chrome window
317 // 6. Desktop compositor gets the alert of counter change, and draws a new
318 // frame with new content from chrome.
319 // 7. Desktop compositor responses user mouse move events, and starts a new
320 // resize process, go to step 1.
321 std::vector<x11::Atom> protocols = {
322 gfx::GetAtom("WM_DELETE_WINDOW"),
323 gfx::GetAtom("_NET_WM_PING"),
324 gfx::GetAtom("_NET_WM_SYNC_REQUEST"),
325 };
326 SetArrayProperty(xwindow_, gfx::GetAtom("WM_PROTOCOLS"), x11::Atom::ATOM,
327 protocols);
328
329 // We need a WM_CLIENT_MACHINE value so we integrate with the desktop
330 // environment.
331 SetStringProperty(xwindow_, gfx::GetAtom("WM_CLIENT_MACHINE"),
332 gfx::GetAtom("STRING"), net::GetHostName());
333
334 // Likewise, the X server needs to know this window's pid so it knows which
335 // program to kill if the window hangs.
336 // XChangeProperty() expects "pid" to be long.
337 static_assert(sizeof(uint32_t) >= sizeof(pid_t),
338 "pid_t should not be larger than uint32_t");
339 uint32_t pid = getpid();
340 SetProperty(xwindow_, gfx::GetAtom("_NET_WM_PID"), x11::Atom::CARDINAL, pid);
341
342 SetProperty(xwindow_, gfx::GetAtom("_NET_WM_WINDOW_TYPE"), x11::Atom::ATOM,
343 window_type);
344
345 // The changes to |window_properties_| here will be sent to the X server just
346 // before the window is mapped.
347
348 // Remove popup windows from taskbar unless overridden.
349 if ((config.type == WindowType::kPopup ||
350 config.type == WindowType::kBubble) &&
351 !config.force_show_in_taskbar) {
352 window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_SKIP_TASKBAR"));
353 }
354
355 // If the window should stay on top of other windows, add the
356 // _NET_WM_STATE_ABOVE property.
357 is_always_on_top_ = config.keep_on_top;
358 if (is_always_on_top_)
359 window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_ABOVE"));
360
361 workspace_ = base::nullopt;
362 if (config.visible_on_all_workspaces) {
363 window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_STICKY"));
364 SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", kAllWorkspaces);
365 } else if (!config.workspace.empty()) {
366 int workspace;
367 if (base::StringToInt(config.workspace, &workspace))
368 SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", workspace);
369 }
370
371 if (!config.wm_class_name.empty() || !config.wm_class_class.empty()) {
372 SetWindowClassHint(connection_, xwindow_, config.wm_class_name,
373 config.wm_class_class);
374 }
375
376 const char* wm_role_name = nullptr;
377 // If the widget isn't overriding the role, provide a default value for popup
378 // and bubble types.
379 if (!config.wm_role_name.empty()) {
380 wm_role_name = config.wm_role_name.c_str();
381 } else {
382 switch (config.type) {
383 case WindowType::kPopup:
384 wm_role_name = kX11WindowRolePopup;
385 break;
386 case WindowType::kBubble:
387 wm_role_name = kX11WindowRoleBubble;
388 break;
389 default:
390 break;
391 }
392 }
393 if (wm_role_name)
394 SetWindowRole(xwindow_, std::string(wm_role_name));
395
396 if (config.remove_standard_frame) {
397 // Setting _GTK_HIDE_TITLEBAR_WHEN_MAXIMIZED tells gnome-shell to not force
398 // fullscreen on the window when it matches the desktop size.
399 SetHideTitlebarWhenMaximizedProperty(xwindow_,
400 HIDE_TITLEBAR_WHEN_MAXIMIZED);
401 }
402
403 if (config.prefer_dark_theme) {
404 SetStringProperty(xwindow_, gfx::GetAtom("_GTK_THEME_VARIANT"),
405 gfx::GetAtom("UTF8_STRING"), kDarkGtkThemeVariant);
406 }
407
408 if (IsSyncExtensionAvailable()) {
409 x11::Sync::Int64 value{};
410 update_counter_ = connection_->GenerateId<x11::Sync::Counter>();
411 connection_->sync().CreateCounter({update_counter_, value});
412 extended_update_counter_ = connection_->GenerateId<x11::Sync::Counter>();
413 connection_->sync().CreateCounter({extended_update_counter_, value});
414
415 std::vector<x11::Sync::Counter> counters{update_counter_,
416 extended_update_counter_};
417
418 // Set XSyncCounter as window property _NET_WM_SYNC_REQUEST_COUNTER. the
419 // compositor will listen on them during resizing.
420 SetArrayProperty(xwindow_, gfx::GetAtom("_NET_WM_SYNC_REQUEST_COUNTER"),
421 x11::Atom::CARDINAL, counters);
422 }
423
424 // Always composite Chromium windows if a compositing WM is used. Sometimes,
425 // WMs will not composite fullscreen windows as an optimization, but this can
426 // lead to tearing of fullscreen videos.
427 SetIntProperty(xwindow_, "_NET_WM_BYPASS_COMPOSITOR", "CARDINAL", 2);
428
429 if (config.icon)
430 SetXWindowIcons(gfx::ImageSkia(), *config.icon);
431 }
432
Map(bool inactive)433 void XWindow::Map(bool inactive) {
434 // Before we map the window, set size hints. Otherwise, some window managers
435 // will ignore toplevel XMoveWindow commands.
436 SizeHints size_hints;
437 memset(&size_hints, 0, sizeof(size_hints));
438 GetWmNormalHints(xwindow_, &size_hints);
439 size_hints.flags |= SIZE_HINT_P_POSITION;
440 size_hints.x = bounds_in_pixels_.x();
441 size_hints.y = bounds_in_pixels_.y();
442 SetWmNormalHints(xwindow_, size_hints);
443
444 ignore_keyboard_input_ = inactive;
445 auto wm_user_time_ms = ignore_keyboard_input_
446 ? x11::Time::CurrentTime
447 : X11EventSource::GetInstance()->GetTimestamp();
448 if (inactive || wm_user_time_ms != x11::Time::CurrentTime) {
449 SetProperty(xwindow_, gfx::GetAtom("_NET_WM_USER_TIME"),
450 x11::Atom::CARDINAL, wm_user_time_ms);
451 }
452
453 UpdateMinAndMaxSize();
454
455 if (window_properties_.empty()) {
456 DeleteProperty(xwindow_, gfx::GetAtom("_NET_WM_STATE"));
457 } else {
458 SetAtomArrayProperty(xwindow_, "_NET_WM_STATE", "ATOM",
459 std::vector<x11::Atom>(std::begin(window_properties_),
460 std::end(window_properties_)));
461 }
462
463 connection_->MapWindow({xwindow_});
464 window_mapped_in_client_ = true;
465
466 // TODO(thomasanderson): Find out why this flush is necessary.
467 connection_->Flush();
468 }
469
Close()470 void XWindow::Close() {
471 if (xwindow_ == x11::Window::None)
472 return;
473
474 CancelResize();
475 UnconfineCursor();
476
477 connection_->DestroyWindow({xwindow_});
478 xwindow_ = x11::Window::None;
479
480 if (update_counter_ != x11::Sync::Counter{}) {
481 connection_->sync().DestroyCounter({update_counter_});
482 connection_->sync().DestroyCounter({extended_update_counter_});
483 update_counter_ = {};
484 extended_update_counter_ = {};
485 }
486 }
487
Maximize()488 void XWindow::Maximize() {
489 // Some WMs do not respect maximization hints on unmapped windows, so we
490 // save this one for later too.
491 should_maximize_after_map_ = !window_mapped_in_client_;
492
493 SetWMSpecState(true, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"),
494 gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"));
495 }
496
Minimize()497 void XWindow::Minimize() {
498 if (window_mapped_in_client_) {
499 SendClientMessage(xwindow_, x_root_window_, gfx::GetAtom("WM_CHANGE_STATE"),
500 {WM_STATE_ICONIC, 0, 0, 0, 0});
501 } else {
502 SetWMSpecState(true, gfx::GetAtom("_NET_WM_STATE_HIDDEN"), x11::Atom::None);
503 }
504 }
505
Unmaximize()506 void XWindow::Unmaximize() {
507 should_maximize_after_map_ = false;
508 SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"),
509 gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"));
510 }
511
Hide()512 bool XWindow::Hide() {
513 if (!window_mapped_in_client_)
514 return false;
515
516 // Make sure no resize task will run after the window is unmapped.
517 CancelResize();
518
519 WithdrawWindow(xwindow_);
520 window_mapped_in_client_ = false;
521 return true;
522 }
523
Unhide()524 void XWindow::Unhide() {
525 SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_HIDDEN"), x11::Atom::None);
526 }
527
SetFullscreen(bool fullscreen)528 void XWindow::SetFullscreen(bool fullscreen) {
529 SetWMSpecState(fullscreen, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"),
530 x11::Atom::None);
531 }
532
Activate()533 void XWindow::Activate() {
534 if (!IsXWindowVisible() || !activatable_)
535 return;
536
537 BeforeActivationStateChanged();
538
539 ignore_keyboard_input_ = false;
540
541 // wmii says that it supports _NET_ACTIVE_WINDOW but does not.
542 // https://code.google.com/p/wmii/issues/detail?id=266
543 static bool wm_supports_active_window =
544 GuessWindowManager() != WM_WMII &&
545 WmSupportsHint(gfx::GetAtom("_NET_ACTIVE_WINDOW"));
546
547 x11::Time timestamp = X11EventSource::GetInstance()->GetTimestamp();
548
549 // override_redirect windows ignore _NET_ACTIVE_WINDOW.
550 // https://crbug.com/940924
551 if (wm_supports_active_window && !override_redirect_) {
552 std::array<uint32_t, 5> data = {
553 // We're an app.
554 1,
555 static_cast<uint32_t>(timestamp),
556 // TODO(thomasanderson): if another chrome window is active, specify
557 // that here. The EWMH spec claims this may make the WM more likely to
558 // service our _NET_ACTIVE_WINDOW request.
559 0,
560 0,
561 0,
562 };
563 SendClientMessage(xwindow_, x_root_window_,
564 gfx::GetAtom("_NET_ACTIVE_WINDOW"), data);
565 } else {
566 RaiseWindow(xwindow_);
567 // Directly ask the X server to give focus to the window. Note that the call
568 // would have raised an X error if the window is not mapped.
569 connection_
570 ->SetInputFocus({x11::InputFocus::Parent, xwindow_,
571 static_cast<x11::Time>(timestamp)})
572 .IgnoreError();
573 // At this point, we know we will receive focus, and some webdriver tests
574 // depend on a window being IsActive() immediately after an Activate(), so
575 // just set this state now.
576 has_pointer_focus_ = false;
577 has_window_focus_ = true;
578 window_mapped_in_server_ = true;
579 }
580
581 AfterActivationStateChanged();
582 }
583
Deactivate()584 void XWindow::Deactivate() {
585 BeforeActivationStateChanged();
586
587 // Ignore future input events.
588 ignore_keyboard_input_ = true;
589
590 ui::LowerWindow(xwindow_);
591
592 AfterActivationStateChanged();
593 }
594
IsActive() const595 bool XWindow::IsActive() const {
596 // Focus and stacking order are independent in X11. Since we cannot guarantee
597 // a window is topmost iff it has focus, just use the focus state to determine
598 // if a window is active. Note that Activate() and Deactivate() change the
599 // stacking order in addition to changing the focus state.
600 return (has_window_focus_ || has_pointer_focus_) && !ignore_keyboard_input_;
601 }
602
SetSize(const gfx::Size & size_in_pixels)603 void XWindow::SetSize(const gfx::Size& size_in_pixels) {
604 connection_->ConfigureWindow({.window = xwindow_,
605 .width = size_in_pixels.width(),
606 .height = size_in_pixels.height()});
607 bounds_in_pixels_.set_size(size_in_pixels);
608 }
609
SetBounds(const gfx::Rect & requested_bounds_in_pixels)610 void XWindow::SetBounds(const gfx::Rect& requested_bounds_in_pixels) {
611 gfx::Rect bounds_in_pixels(requested_bounds_in_pixels);
612 bool origin_changed = bounds_in_pixels_.origin() != bounds_in_pixels.origin();
613 bool size_changed = bounds_in_pixels_.size() != bounds_in_pixels.size();
614
615 x11::ConfigureWindowRequest req{.window = xwindow_};
616
617 if (size_changed) {
618 // Update the minimum and maximum sizes in case they have changed.
619 UpdateMinAndMaxSize();
620
621 if (bounds_in_pixels.width() < min_size_in_pixels_.width() ||
622 bounds_in_pixels.height() < min_size_in_pixels_.height() ||
623 (!max_size_in_pixels_.IsEmpty() &&
624 (bounds_in_pixels.width() > max_size_in_pixels_.width() ||
625 bounds_in_pixels.height() > max_size_in_pixels_.height()))) {
626 gfx::Size size_in_pixels = bounds_in_pixels.size();
627 if (!max_size_in_pixels_.IsEmpty())
628 size_in_pixels.SetToMin(max_size_in_pixels_);
629 size_in_pixels.SetToMax(min_size_in_pixels_);
630 bounds_in_pixels.set_size(size_in_pixels);
631 }
632
633 req.width = bounds_in_pixels.width();
634 req.height = bounds_in_pixels.height();
635 }
636
637 if (origin_changed) {
638 req.x = bounds_in_pixels.x();
639 req.y = bounds_in_pixels.y();
640 }
641
642 if (origin_changed || size_changed)
643 connection_->ConfigureWindow(req);
644
645 // Assume that the resize will go through as requested, which should be the
646 // case if we're running without a window manager. If there's a window
647 // manager, it can modify or ignore the request, but (per ICCCM) we'll get a
648 // (possibly synthetic) ConfigureNotify about the actual size and correct
649 // |bounds_in_pixels_| later.
650 bounds_in_pixels_ = bounds_in_pixels;
651 ResetWindowRegion();
652
653 // Even if the pixel bounds didn't change this call to the delegate should
654 // still happen. The device scale factor may have changed which effectively
655 // changes the bounds.
656 OnXWindowBoundsChanged(bounds_in_pixels);
657 }
658
IsXWindowVisible() const659 bool XWindow::IsXWindowVisible() const {
660 // On Windows, IsVisible() returns true for minimized windows. On X11, a
661 // minimized window is not mapped, so an explicit IsMinimized() check is
662 // necessary.
663 return window_mapped_in_client_ || IsMinimized();
664 }
665
IsMinimized() const666 bool XWindow::IsMinimized() const {
667 return HasWMSpecProperty(window_properties_,
668 gfx::GetAtom("_NET_WM_STATE_HIDDEN"));
669 }
670
IsMaximized() const671 bool XWindow::IsMaximized() const {
672 return (HasWMSpecProperty(window_properties_,
673 gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT")) &&
674 HasWMSpecProperty(window_properties_,
675 gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")));
676 }
677
IsFullscreen() const678 bool XWindow::IsFullscreen() const {
679 return HasWMSpecProperty(window_properties_,
680 gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"));
681 }
682
GetOuterBounds() const683 gfx::Rect XWindow::GetOuterBounds() const {
684 gfx::Rect outer_bounds(bounds_in_pixels_);
685 outer_bounds.Inset(-native_window_frame_borders_in_pixels_);
686 return outer_bounds;
687 }
688
GrabPointer()689 void XWindow::GrabPointer() {
690 // If the pointer is already in |xwindow_|, we will not get a crossing event
691 // with a mode of NotifyGrab, so we must record the grab state manually.
692 has_pointer_grab_ |=
693 (ui::GrabPointer(xwindow_, true, nullptr) == x11::GrabStatus::Success);
694 }
695
ReleasePointerGrab()696 void XWindow::ReleasePointerGrab() {
697 UngrabPointer();
698 has_pointer_grab_ = false;
699 }
700
StackXWindowAbove(x11::Window window)701 void XWindow::StackXWindowAbove(x11::Window window) {
702 DCHECK(window != x11::Window::None);
703
704 // Find all parent windows up to the root.
705 std::vector<x11::Window> window_below_parents =
706 GetParentsList(connection_, window);
707 std::vector<x11::Window> window_above_parents =
708 GetParentsList(connection_, xwindow_);
709
710 // Find their common ancestor.
711 auto it_below_window = window_below_parents.rbegin();
712 auto it_above_window = window_above_parents.rbegin();
713 for (; it_below_window != window_below_parents.rend() &&
714 it_above_window != window_above_parents.rend() &&
715 *it_below_window == *it_above_window;
716 ++it_below_window, ++it_above_window) {
717 }
718
719 if (it_below_window != window_below_parents.rend() &&
720 it_above_window != window_above_parents.rend()) {
721 connection_->ConfigureWindow(x11::ConfigureWindowRequest{
722 .window = *it_above_window,
723 .sibling = *it_below_window,
724 .stack_mode = x11::StackMode::Above,
725 });
726 }
727 }
728
StackXWindowAtTop()729 void XWindow::StackXWindowAtTop() {
730 RaiseWindow(xwindow_);
731 }
732
SetCursor(scoped_refptr<X11Cursor> cursor)733 void XWindow::SetCursor(scoped_refptr<X11Cursor> cursor) {
734 last_cursor_ = cursor;
735 on_cursor_loaded_.Reset(base::BindOnce(DefineCursor, xwindow_));
736 if (cursor)
737 cursor->OnCursorLoaded(on_cursor_loaded_.callback());
738 }
739
SetTitle(base::string16 title)740 bool XWindow::SetTitle(base::string16 title) {
741 if (window_title_ == title)
742 return false;
743
744 window_title_ = title;
745 std::string utf8str = base::UTF16ToUTF8(title);
746 SetStringProperty(xwindow_, gfx::GetAtom("_NET_WM_NAME"),
747 gfx::GetAtom("UTF8_STRING"), utf8str);
748 SetStringProperty(xwindow_, x11::Atom::WM_NAME, gfx::GetAtom("UTF8_STRING"),
749 utf8str);
750 return true;
751 }
752
SetXWindowOpacity(float opacity)753 void XWindow::SetXWindowOpacity(float opacity) {
754 // X server opacity is in terms of 32 bit unsigned int space, and counts from
755 // the opposite direction.
756 // XChangeProperty() expects "cardinality" to be long.
757
758 // Scale opacity to [0 .. 255] range.
759 uint32_t opacity_8bit = static_cast<uint32_t>(opacity * 255.0f) & 0xFF;
760 // Use opacity value for all channels.
761 uint32_t channel_multiplier = 0x1010101;
762 uint32_t cardinality = opacity_8bit * channel_multiplier;
763
764 if (cardinality == 0xffffffff) {
765 DeleteProperty(xwindow_, gfx::GetAtom("_NET_WM_WINDOW_OPACITY"));
766 } else {
767 SetProperty(xwindow_, gfx::GetAtom("_NET_WM_WINDOW_OPACITY"),
768 x11::Atom::CARDINAL, cardinality);
769 }
770 }
771
SetXWindowAspectRatio(const gfx::SizeF & aspect_ratio)772 void XWindow::SetXWindowAspectRatio(const gfx::SizeF& aspect_ratio) {
773 SizeHints size_hints;
774 memset(&size_hints, 0, sizeof(size_hints));
775
776 GetWmNormalHints(xwindow_, &size_hints);
777 // Unforce aspect ratio is parameter length is 0, otherwise set normally.
778 if (aspect_ratio.IsEmpty()) {
779 size_hints.flags &= ~SIZE_HINT_P_ASPECT;
780 } else {
781 size_hints.flags |= SIZE_HINT_P_ASPECT;
782 size_hints.min_aspect_num = size_hints.max_aspect_num =
783 aspect_ratio.width();
784 size_hints.min_aspect_den = size_hints.max_aspect_den =
785 aspect_ratio.height();
786 }
787 SetWmNormalHints(xwindow_, size_hints);
788 }
789
SetXWindowIcons(const gfx::ImageSkia & window_icon,const gfx::ImageSkia & app_icon)790 void XWindow::SetXWindowIcons(const gfx::ImageSkia& window_icon,
791 const gfx::ImageSkia& app_icon) {
792 // TODO(erg): The way we handle icons across different versions of chrome
793 // could be substantially improved. The Windows version does its own thing
794 // and only sometimes comes down this code path. The icon stuff in
795 // ChromeViewsDelegate is hard coded to use HICONs. Likewise, we're hard
796 // coded to be given two images instead of an arbitrary collection of images
797 // so that we can pass to the WM.
798 //
799 // All of this could be made much, much better.
800 std::vector<uint32_t> data;
801
802 if (!window_icon.isNull())
803 SerializeImageRepresentation(window_icon.GetRepresentation(1.0f), &data);
804
805 if (!app_icon.isNull())
806 SerializeImageRepresentation(app_icon.GetRepresentation(1.0f), &data);
807
808 if (!data.empty()) {
809 SetArrayProperty(xwindow_, gfx::GetAtom("_NET_WM_ICON"),
810 x11::Atom::CARDINAL, data);
811 }
812 }
813
SetXWindowVisibleOnAllWorkspaces(bool visible)814 void XWindow::SetXWindowVisibleOnAllWorkspaces(bool visible) {
815 SetWMSpecState(visible, gfx::GetAtom("_NET_WM_STATE_STICKY"),
816 x11::Atom::None);
817
818 int new_desktop = 0;
819 if (visible) {
820 new_desktop = kAllWorkspaces;
821 } else {
822 if (!GetCurrentDesktop(&new_desktop))
823 return;
824 }
825
826 workspace_ = kAllWorkspaces;
827 SendClientMessage(xwindow_, x_root_window_, gfx::GetAtom("_NET_WM_DESKTOP"),
828 {new_desktop, 0, 0, 0, 0});
829 }
830
IsXWindowVisibleOnAllWorkspaces() const831 bool XWindow::IsXWindowVisibleOnAllWorkspaces() const {
832 // We don't need a check for _NET_WM_STATE_STICKY because that would specify
833 // that the window remain in a fixed position even if the viewport scrolls.
834 // This is different from the type of workspace that's associated with
835 // _NET_WM_DESKTOP.
836 return workspace_ == kAllWorkspaces;
837 }
838
MoveCursorTo(const gfx::Point & location_in_pixels)839 void XWindow::MoveCursorTo(const gfx::Point& location_in_pixels) {
840 connection_->WarpPointer(x11::WarpPointerRequest{
841 .dst_window = x_root_window_,
842 .dst_x = bounds_in_pixels_.x() + location_in_pixels.x(),
843 .dst_y = bounds_in_pixels_.y() + location_in_pixels.y(),
844 });
845 }
846
ResetWindowRegion()847 void XWindow::ResetWindowRegion() {
848 std::unique_ptr<std::vector<x11::Rectangle>> xregion;
849 if (!use_custom_shape() && !IsMaximized() && !IsFullscreen()) {
850 SkPath window_mask;
851 GetWindowMaskForXWindow(bounds().size(), &window_mask);
852 // Some frame views define a custom (non-rectangular) window mask. If
853 // so, use it to define the window shape. If not, fall through.
854 if (window_mask.countPoints() > 0)
855 xregion = gfx::CreateRegionFromSkPath(window_mask);
856 }
857 UpdateWindowRegion(std::move(xregion));
858 }
859
OnWorkspaceUpdated()860 void XWindow::OnWorkspaceUpdated() {
861 auto old_workspace = workspace_;
862 int workspace;
863 if (GetWindowDesktop(xwindow_, &workspace))
864 workspace_ = workspace;
865 else
866 workspace_ = base::nullopt;
867
868 if (workspace_ != old_workspace)
869 OnXWindowWorkspaceChanged();
870 }
871
SetAlwaysOnTop(bool always_on_top)872 void XWindow::SetAlwaysOnTop(bool always_on_top) {
873 is_always_on_top_ = always_on_top;
874 SetWMSpecState(always_on_top, gfx::GetAtom("_NET_WM_STATE_ABOVE"),
875 x11::Atom::None);
876 }
877
SetFlashFrameHint(bool flash_frame)878 void XWindow::SetFlashFrameHint(bool flash_frame) {
879 if (urgency_hint_set_ == flash_frame)
880 return;
881
882 WmHints hints;
883 memset(&hints, 0, sizeof(hints));
884 GetWmHints(xwindow_, &hints);
885
886 if (flash_frame)
887 hints.flags |= WM_HINT_X_URGENCY;
888 else
889 hints.flags &= ~WM_HINT_X_URGENCY;
890
891 SetWmHints(xwindow_, hints);
892
893 urgency_hint_set_ = flash_frame;
894 }
895
UpdateMinAndMaxSize()896 void XWindow::UpdateMinAndMaxSize() {
897 base::Optional<gfx::Size> minimum_in_pixels = GetMinimumSizeForXWindow();
898 base::Optional<gfx::Size> maximum_in_pixels = GetMaximumSizeForXWindow();
899 if ((!minimum_in_pixels ||
900 min_size_in_pixels_ == minimum_in_pixels.value()) &&
901 (!maximum_in_pixels || max_size_in_pixels_ == maximum_in_pixels.value()))
902 return;
903
904 min_size_in_pixels_ = minimum_in_pixels.value();
905 max_size_in_pixels_ = maximum_in_pixels.value();
906
907 SizeHints hints;
908 memset(&hints, 0, sizeof(hints));
909 GetWmNormalHints(xwindow_, &hints);
910
911 if (min_size_in_pixels_.IsEmpty()) {
912 hints.flags &= ~SIZE_HINT_P_MIN_SIZE;
913 } else {
914 hints.flags |= SIZE_HINT_P_MIN_SIZE;
915 hints.min_width = min_size_in_pixels_.width();
916 hints.min_height = min_size_in_pixels_.height();
917 }
918
919 if (max_size_in_pixels_.IsEmpty()) {
920 hints.flags &= ~SIZE_HINT_P_MAX_SIZE;
921 } else {
922 hints.flags |= SIZE_HINT_P_MAX_SIZE;
923 hints.max_width = max_size_in_pixels_.width();
924 hints.max_height = max_size_in_pixels_.height();
925 }
926
927 SetWmNormalHints(xwindow_, hints);
928 }
929
BeforeActivationStateChanged()930 void XWindow::BeforeActivationStateChanged() {
931 was_active_ = IsActive();
932 had_pointer_ = has_pointer_;
933 had_pointer_grab_ = has_pointer_grab_;
934 had_window_focus_ = has_window_focus_;
935 }
936
AfterActivationStateChanged()937 void XWindow::AfterActivationStateChanged() {
938 if (had_pointer_grab_ && !has_pointer_grab_)
939 OnXWindowLostPointerGrab();
940
941 bool had_pointer_capture = had_pointer_ || had_pointer_grab_;
942 bool has_pointer_capture = has_pointer_ || has_pointer_grab_;
943 if (had_pointer_capture && !has_pointer_capture)
944 OnXWindowLostCapture();
945
946 bool is_active = IsActive();
947 if (!was_active_ && is_active)
948 SetFlashFrameHint(false);
949
950 if (was_active_ != is_active)
951 OnXWindowIsActiveChanged(is_active);
952 }
953
SetUseNativeFrame(bool use_native_frame)954 void XWindow::SetUseNativeFrame(bool use_native_frame) {
955 use_native_frame_ = use_native_frame;
956 SetUseOSWindowFrame(xwindow_, use_native_frame);
957 ResetWindowRegion();
958 }
959
OnCrossingEvent(bool enter,bool focus_in_window_or_ancestor,x11::NotifyMode mode,x11::NotifyDetail detail)960 void XWindow::OnCrossingEvent(bool enter,
961 bool focus_in_window_or_ancestor,
962 x11::NotifyMode mode,
963 x11::NotifyDetail detail) {
964 // NotifyInferior on a crossing event means the pointer moved into or out of a
965 // child window, but the pointer is still within |xwindow_|.
966 if (detail == x11::NotifyDetail::Inferior)
967 return;
968
969 BeforeActivationStateChanged();
970
971 if (mode == x11::NotifyMode::Grab)
972 has_pointer_grab_ = enter;
973 else if (mode == x11::NotifyMode::Ungrab)
974 has_pointer_grab_ = false;
975
976 has_pointer_ = enter;
977 if (focus_in_window_or_ancestor && !has_window_focus_) {
978 // If we reach this point, we know the focus is in an ancestor or the
979 // pointer root. The definition of |has_pointer_focus_| is (An ancestor
980 // window or the PointerRoot is focused) && |has_pointer_|. Therefore, we
981 // can just use |has_pointer_| in the assignment. The transitions for when
982 // the focus changes are handled in OnFocusEvent().
983 has_pointer_focus_ = has_pointer_;
984 }
985
986 AfterActivationStateChanged();
987 }
988
OnFocusEvent(bool focus_in,x11::NotifyMode mode,x11::NotifyDetail detail)989 void XWindow::OnFocusEvent(bool focus_in,
990 x11::NotifyMode mode,
991 x11::NotifyDetail detail) {
992 // NotifyInferior on a focus event means the focus moved into or out of a
993 // child window, but the focus is still within |xwindow_|.
994 if (detail == x11::NotifyDetail::Inferior)
995 return;
996
997 bool notify_grab =
998 mode == x11::NotifyMode::Grab || mode == x11::NotifyMode::Ungrab;
999
1000 BeforeActivationStateChanged();
1001
1002 // For every focus change, the X server sends normal focus events which are
1003 // useful for tracking |has_window_focus_|, but supplements these events with
1004 // NotifyPointer events which are only useful for tracking pointer focus.
1005
1006 // For |has_pointer_focus_| and |has_window_focus_|, we continue tracking
1007 // state during a grab, but ignore grab/ungrab events themselves.
1008 if (!notify_grab && detail != x11::NotifyDetail::Pointer)
1009 has_window_focus_ = focus_in;
1010
1011 if (!notify_grab && has_pointer_) {
1012 switch (detail) {
1013 case x11::NotifyDetail::Ancestor:
1014 case x11::NotifyDetail::Virtual:
1015 // If we reach this point, we know |has_pointer_| was true before and
1016 // after this event. Since the definition of |has_pointer_focus_| is
1017 // (An ancestor window or the PointerRoot is focused) && |has_pointer_|,
1018 // we only need to worry about transitions on the first conjunct.
1019 // Therefore, |has_pointer_focus_| will become true when:
1020 // 1. Focus moves from |xwindow_| to an ancestor
1021 // (FocusOut with NotifyAncestor)
1022 // 2. Focus moves from a descendant of |xwindow_| to an ancestor
1023 // (FocusOut with NotifyVirtual)
1024 // |has_pointer_focus_| will become false when:
1025 // 1. Focus moves from an ancestor to |xwindow_|
1026 // (FocusIn with NotifyAncestor)
1027 // 2. Focus moves from an ancestor to a child of |xwindow_|
1028 // (FocusIn with NotifyVirtual)
1029 has_pointer_focus_ = !focus_in;
1030 break;
1031 case x11::NotifyDetail::Pointer:
1032 // The remaining cases for |has_pointer_focus_| becoming true are:
1033 // 3. Focus moves from |xwindow_| to the PointerRoot
1034 // 4. Focus moves from a descendant of |xwindow_| to the PointerRoot
1035 // 5. Focus moves from None to the PointerRoot
1036 // 6. Focus moves from Other to the PointerRoot
1037 // 7. Focus moves from None to an ancestor of |xwindow_|
1038 // 8. Focus moves from Other to an ancestor of |xwindow_|
1039 // In each case, we will get a FocusIn with a detail of NotifyPointer.
1040 // The remaining cases for |has_pointer_focus_| becoming false are:
1041 // 3. Focus moves from the PointerRoot to |xwindow_|
1042 // 4. Focus moves from the PointerRoot to a descendant of |xwindow|
1043 // 5. Focus moves from the PointerRoot to None
1044 // 6. Focus moves from an ancestor of |xwindow_| to None
1045 // 7. Focus moves from the PointerRoot to Other
1046 // 8. Focus moves from an ancestor of |xwindow_| to Other
1047 // In each case, we will get a FocusOut with a detail of NotifyPointer.
1048 has_pointer_focus_ = focus_in;
1049 break;
1050 case x11::NotifyDetail::Nonlinear:
1051 case x11::NotifyDetail::NonlinearVirtual:
1052 // We get Nonlinear(Virtual) events when
1053 // 1. Focus moves from Other to |xwindow_|
1054 // (FocusIn with NotifyNonlinear)
1055 // 2. Focus moves from Other to a descendant of |xwindow_|
1056 // (FocusIn with NotifyNonlinearVirtual)
1057 // 3. Focus moves from |xwindow_| to Other
1058 // (FocusOut with NotifyNonlinear)
1059 // 4. Focus moves from a descendant of |xwindow_| to Other
1060 // (FocusOut with NotifyNonlinearVirtual)
1061 // |has_pointer_focus_| should be false before and after this event.
1062 has_pointer_focus_ = false;
1063 break;
1064 default:
1065 break;
1066 }
1067 }
1068
1069 ignore_keyboard_input_ = false;
1070
1071 AfterActivationStateChanged();
1072 }
1073
IsTargetedBy(const x11::Event & x11_event) const1074 bool XWindow::IsTargetedBy(const x11::Event& x11_event) const {
1075 return x11_event.window() == xwindow_;
1076 }
1077
IsTransientWindowTargetedBy(const x11::Event & x11_event) const1078 bool XWindow::IsTransientWindowTargetedBy(const x11::Event& x11_event) const {
1079 return x11_event.window() == transient_window_;
1080 }
1081
SetTransientWindow(x11::Window window)1082 void XWindow::SetTransientWindow(x11::Window window) {
1083 transient_window_ = window;
1084 }
1085
WmMoveResize(int hittest,const gfx::Point & location) const1086 void XWindow::WmMoveResize(int hittest, const gfx::Point& location) const {
1087 int direction = HitTestToWmMoveResizeDirection(hittest);
1088 if (direction == -1)
1089 return;
1090
1091 DoWMMoveResize(connection_, x_root_window_, xwindow_, location, direction);
1092 }
1093
ProcessEvent(x11::Event * xev)1094 void XWindow::ProcessEvent(x11::Event* xev) {
1095 // We can lose track of the window's position when the window is reparented.
1096 // When the parent window is moved, we won't get an event, so the window's
1097 // position relative to the root window will get out-of-sync. We can re-sync
1098 // when getting pointer events (EnterNotify, LeaveNotify, ButtonPress,
1099 // ButtonRelease, MotionNotify) which include the pointer location both
1100 // relative to this window and relative to the root window, so we can
1101 // calculate this window's position from that information.
1102 gfx::Point window_point = EventLocationFromXEvent(*xev);
1103 gfx::Point root_point = EventSystemLocationFromXEvent(*xev);
1104 if (!window_point.IsOrigin() && !root_point.IsOrigin()) {
1105 gfx::Point window_origin = gfx::Point() + (root_point - window_point);
1106 if (bounds_in_pixels_.origin() != window_origin) {
1107 bounds_in_pixels_.set_origin(window_origin);
1108 NotifyBoundsChanged(bounds_in_pixels_);
1109 }
1110 }
1111
1112 // May want to factor CheckXEventForConsistency(xev); into a common location
1113 // since it is called here.
1114 if (auto* crossing = xev->As<x11::CrossingEvent>()) {
1115 bool focus = crossing->same_screen_focus & CROSSING_FLAG_FOCUS;
1116 OnCrossingEvent(crossing->opcode == x11::CrossingEvent::EnterNotify, focus,
1117 crossing->mode, crossing->detail);
1118 } else if (auto* expose = xev->As<x11::ExposeEvent>()) {
1119 gfx::Rect damage_rect_in_pixels(expose->x, expose->y, expose->width,
1120 expose->height);
1121 OnXWindowDamageEvent(damage_rect_in_pixels);
1122 } else if (auto* focus = xev->As<x11::FocusEvent>()) {
1123 OnFocusEvent(focus->opcode == x11::FocusEvent::In, focus->mode,
1124 focus->detail);
1125 } else if (auto* configure = xev->As<x11::ConfigureNotifyEvent>()) {
1126 OnConfigureEvent(*configure);
1127 } else if (auto* crossing = xev->As<x11::Input::CrossingEvent>()) {
1128 TouchFactory* factory = TouchFactory::GetInstance();
1129 if (factory->ShouldProcessCrossingEvent(*crossing)) {
1130 auto mode = XI2ModeToXMode(crossing->mode);
1131 auto detail = XI2DetailToXDetail(crossing->detail);
1132 switch (crossing->opcode) {
1133 case x11::Input::CrossingEvent::Enter:
1134 OnCrossingEvent(true, crossing->focus, mode, detail);
1135 break;
1136 case x11::Input::CrossingEvent::Leave:
1137 OnCrossingEvent(false, crossing->focus, mode, detail);
1138 break;
1139 case x11::Input::CrossingEvent::FocusIn:
1140 OnFocusEvent(true, mode, detail);
1141 break;
1142 case x11::Input::CrossingEvent::FocusOut:
1143 OnFocusEvent(false, mode, detail);
1144 break;
1145 }
1146 }
1147 } else if (xev->As<x11::MapNotifyEvent>()) {
1148 OnWindowMapped();
1149 } else if (xev->As<x11::UnmapNotifyEvent>()) {
1150 window_mapped_in_server_ = false;
1151 has_pointer_ = false;
1152 has_pointer_grab_ = false;
1153 has_pointer_focus_ = false;
1154 has_window_focus_ = false;
1155 } else if (auto* client = xev->As<x11::ClientMessageEvent>()) {
1156 x11::Atom message_type = client->type;
1157 if (message_type == gfx::GetAtom("WM_PROTOCOLS")) {
1158 x11::Atom protocol = static_cast<x11::Atom>(client->data.data32[0]);
1159 if (protocol == gfx::GetAtom("WM_DELETE_WINDOW")) {
1160 // We have received a close message from the window manager.
1161 OnXWindowCloseRequested();
1162 } else if (protocol == gfx::GetAtom("_NET_WM_PING")) {
1163 x11::ClientMessageEvent reply_event = *client;
1164 reply_event.window = x_root_window_;
1165 x11::SendEvent(reply_event, x_root_window_,
1166 x11::EventMask::SubstructureNotify |
1167 x11::EventMask::SubstructureRedirect);
1168 } else if (protocol == gfx::GetAtom("_NET_WM_SYNC_REQUEST")) {
1169 pending_counter_value_ =
1170 client->data.data32[2] +
1171 (static_cast<int64_t>(client->data.data32[3]) << 32);
1172 pending_counter_value_is_extended_ = client->data.data32[4] != 0;
1173 }
1174 } else {
1175 OnXWindowDragDropEvent(xev);
1176 }
1177 } else if (auto* property = xev->As<x11::PropertyNotifyEvent>()) {
1178 x11::Atom changed_atom = property->atom;
1179 if (changed_atom == gfx::GetAtom("_NET_WM_STATE"))
1180 OnWMStateUpdated();
1181 else if (changed_atom == gfx::GetAtom("_NET_FRAME_EXTENTS"))
1182 OnFrameExtentsUpdated();
1183 else if (changed_atom == gfx::GetAtom("_NET_WM_DESKTOP"))
1184 OnWorkspaceUpdated();
1185 } else if (auto* selection = xev->As<x11::SelectionNotifyEvent>()) {
1186 OnXWindowSelectionEvent(xev);
1187 }
1188 }
1189
UpdateWMUserTime(Event * event)1190 void XWindow::UpdateWMUserTime(Event* event) {
1191 if (!IsActive())
1192 return;
1193 DCHECK(event);
1194 EventType type = event->type();
1195 if (type == ET_MOUSE_PRESSED || type == ET_KEY_PRESSED ||
1196 type == ET_TOUCH_PRESSED) {
1197 uint32_t wm_user_time_ms =
1198 (event->time_stamp() - base::TimeTicks()).InMilliseconds();
1199 SetProperty(xwindow_, gfx::GetAtom("_NET_WM_USER_TIME"),
1200 x11::Atom::CARDINAL, wm_user_time_ms);
1201 }
1202 }
1203
OnWindowMapped()1204 void XWindow::OnWindowMapped() {
1205 window_mapped_in_server_ = true;
1206 // Some WMs only respect maximize hints after the window has been mapped.
1207 // Check whether we need to re-do a maximization.
1208 if (should_maximize_after_map_) {
1209 Maximize();
1210 should_maximize_after_map_ = false;
1211 }
1212 }
1213
OnConfigureEvent(const x11::ConfigureNotifyEvent & configure)1214 void XWindow::OnConfigureEvent(const x11::ConfigureNotifyEvent& configure) {
1215 DCHECK_EQ(xwindow_, configure.window);
1216 DCHECK_EQ(xwindow_, configure.event);
1217
1218 if (pending_counter_value_) {
1219 DCHECK(!configure_counter_value_);
1220 configure_counter_value_ = pending_counter_value_;
1221 configure_counter_value_is_extended_ = pending_counter_value_is_extended_;
1222 pending_counter_value_is_extended_ = false;
1223 pending_counter_value_ = 0;
1224 }
1225
1226 // It's possible that the X window may be resized by some other means than
1227 // from within aura (e.g. the X window manager can change the size). Make
1228 // sure the root window size is maintained properly.
1229 int translated_x_in_pixels = configure.x;
1230 int translated_y_in_pixels = configure.y;
1231 if (!configure.send_event && !configure.override_redirect) {
1232 auto future =
1233 connection_->TranslateCoordinates({xwindow_, x_root_window_, 0, 0});
1234 if (auto coords = future.Sync()) {
1235 translated_x_in_pixels = coords->dst_x;
1236 translated_y_in_pixels = coords->dst_y;
1237 }
1238 }
1239 gfx::Rect bounds_in_pixels(translated_x_in_pixels, translated_y_in_pixels,
1240 configure.width, configure.height);
1241 bool size_changed = bounds_in_pixels_.size() != bounds_in_pixels.size();
1242 bool origin_changed = bounds_in_pixels_.origin() != bounds_in_pixels.origin();
1243 previous_bounds_in_pixels_ = bounds_in_pixels_;
1244 bounds_in_pixels_ = bounds_in_pixels;
1245
1246 if (size_changed)
1247 DispatchResize();
1248 else if (origin_changed)
1249 NotifyBoundsChanged(bounds_in_pixels_);
1250 }
1251
SetWMSpecState(bool enabled,x11::Atom state1,x11::Atom state2)1252 void XWindow::SetWMSpecState(bool enabled, x11::Atom state1, x11::Atom state2) {
1253 if (window_mapped_in_client_) {
1254 ui::SetWMSpecState(xwindow_, enabled, state1, state2);
1255 } else {
1256 // The updated state will be set when the window is (re)mapped.
1257 base::flat_set<x11::Atom> new_window_properties = window_properties_;
1258 for (x11::Atom atom : {state1, state2}) {
1259 if (enabled)
1260 new_window_properties.insert(atom);
1261 else
1262 new_window_properties.erase(atom);
1263 }
1264 UpdateWindowProperties(new_window_properties);
1265 }
1266 }
1267
OnWMStateUpdated()1268 void XWindow::OnWMStateUpdated() {
1269 // The EWMH spec requires window managers to remove the _NET_WM_STATE property
1270 // when a window is unmapped. However, Chromium code wants the state to
1271 // persist across a Hide() and Show(). So if the window is currently
1272 // unmapped, leave the state unchanged so it will be restored when the window
1273 // is remapped.
1274 std::vector<x11::Atom> atom_list;
1275 if (GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &atom_list) ||
1276 window_mapped_in_client_) {
1277 UpdateWindowProperties(
1278 base::flat_set<x11::Atom>(std::begin(atom_list), std::end(atom_list)));
1279 }
1280 }
1281
UpdateWindowProperties(const base::flat_set<x11::Atom> & new_window_properties)1282 void XWindow::UpdateWindowProperties(
1283 const base::flat_set<x11::Atom>& new_window_properties) {
1284 was_minimized_ = IsMinimized();
1285
1286 window_properties_ = new_window_properties;
1287
1288 // Ignore requests by the window manager to enter or exit fullscreen (e.g. as
1289 // a result of pressing a window manager accelerator key). Chrome does not
1290 // handle window manager initiated fullscreen. In particular, Chrome needs to
1291 // do preprocessing before the x window's fullscreen state is toggled.
1292
1293 is_always_on_top_ = HasWMSpecProperty(window_properties_,
1294 gfx::GetAtom("_NET_WM_STATE_ABOVE"));
1295 OnXWindowStateChanged();
1296 ResetWindowRegion();
1297 }
1298
OnFrameExtentsUpdated()1299 void XWindow::OnFrameExtentsUpdated() {
1300 std::vector<int> insets;
1301 if (GetIntArrayProperty(xwindow_, "_NET_FRAME_EXTENTS", &insets) &&
1302 insets.size() == 4) {
1303 // |insets| are returned in the order: [left, right, top, bottom].
1304 native_window_frame_borders_in_pixels_ =
1305 gfx::Insets(insets[2], insets[0], insets[3], insets[1]);
1306 } else {
1307 native_window_frame_borders_in_pixels_ = gfx::Insets();
1308 }
1309 }
1310
NotifySwapAfterResize()1311 void XWindow::NotifySwapAfterResize() {
1312 if (configure_counter_value_is_extended_) {
1313 if ((current_counter_value_ % 2) == 1) {
1314 // An increase 3 means that the frame was not drawn as fast as possible.
1315 // This can trigger different handling from the compositor.
1316 // Setting an even number to |extended_update_counter_| will trigger a
1317 // new resize.
1318 current_counter_value_ += 3;
1319 SyncSetCounter(connection_, extended_update_counter_,
1320 current_counter_value_);
1321 }
1322 return;
1323 }
1324
1325 if (configure_counter_value_ != 0) {
1326 SyncSetCounter(connection_, update_counter_, configure_counter_value_);
1327 configure_counter_value_ = 0;
1328 }
1329 }
1330
1331 // Removes |delayed_resize_task_| from the task queue (if it's in the queue) and
1332 // adds it back at the end of the queue.
DispatchResize()1333 void XWindow::DispatchResize() {
1334 if (update_counter_ == x11::Sync::Counter{} ||
1335 configure_counter_value_ == 0) {
1336 // WM doesn't support _NET_WM_SYNC_REQUEST. Or we are too slow, so
1337 // _NET_WM_SYNC_REQUEST is disabled by the compositor.
1338 delayed_resize_task_.Reset(base::BindOnce(
1339 &XWindow::DelayedResize, base::Unretained(this), bounds_in_pixels_));
1340 base::ThreadTaskRunnerHandle::Get()->PostTask(
1341 FROM_HERE, delayed_resize_task_.callback());
1342 return;
1343 }
1344
1345 if (configure_counter_value_is_extended_) {
1346 current_counter_value_ = configure_counter_value_;
1347 configure_counter_value_ = 0;
1348 // Make sure the counter is even number.
1349 if ((current_counter_value_ % 2) == 1)
1350 ++current_counter_value_;
1351 }
1352
1353 // If _NET_WM_SYNC_REQUEST is used to synchronize with compositor during
1354 // resizing, the compositor will not resize the window, until last resize is
1355 // handled, so we don't need accumulate resize events.
1356 DelayedResize(bounds_in_pixels_);
1357 }
1358
DelayedResize(const gfx::Rect & bounds_in_pixels)1359 void XWindow::DelayedResize(const gfx::Rect& bounds_in_pixels) {
1360 if (configure_counter_value_is_extended_ &&
1361 (current_counter_value_ % 2) == 0) {
1362 // Increase the |extended_update_counter_|, so the compositor will know we
1363 // are not frozen and re-enable _NET_WM_SYNC_REQUEST, if it was disabled.
1364 // Increase the |extended_update_counter_| to an odd number will not trigger
1365 // a new resize.
1366 SyncSetCounter(connection_, extended_update_counter_,
1367 ++current_counter_value_);
1368 }
1369
1370 CancelResize();
1371 NotifyBoundsChanged(bounds_in_pixels);
1372
1373 // No more member accesses here: bounds change propagation may have deleted
1374 // |this| (e.g. when a chrome window is snapped into a tab strip. Further
1375 // details at crbug.com/1068755).
1376 }
1377
CancelResize()1378 void XWindow::CancelResize() {
1379 delayed_resize_task_.Cancel();
1380 }
1381
ConfineCursorTo(const gfx::Rect & bounds)1382 void XWindow::ConfineCursorTo(const gfx::Rect& bounds) {
1383 UnconfineCursor();
1384
1385 if (bounds.IsEmpty())
1386 return;
1387
1388 gfx::Rect barrier = bounds + bounds_in_pixels_.OffsetFromOrigin();
1389
1390 auto make_barrier = [&](uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
1391 x11::XFixes::BarrierDirections directions) {
1392 x11::XFixes::Barrier barrier =
1393 connection_->GenerateId<x11::XFixes::Barrier>();
1394 connection_->xfixes().CreatePointerBarrier(
1395 {barrier, x_root_window_, x1, y1, x2, y2, directions});
1396 return barrier;
1397 };
1398
1399 // Top horizontal barrier.
1400 pointer_barriers_[0] =
1401 make_barrier(barrier.x(), barrier.y(), barrier.right(), barrier.y(),
1402 x11::XFixes::BarrierDirections::PositiveY);
1403 // Bottom horizontal barrier.
1404 pointer_barriers_[1] =
1405 make_barrier(barrier.x(), barrier.bottom(), barrier.right(),
1406 barrier.bottom(), x11::XFixes::BarrierDirections::NegativeY);
1407 // Left vertical barrier.
1408 pointer_barriers_[2] =
1409 make_barrier(barrier.x(), barrier.y(), barrier.x(), barrier.bottom(),
1410 x11::XFixes::BarrierDirections::PositiveX);
1411 // Right vertical barrier.
1412 pointer_barriers_[3] =
1413 make_barrier(barrier.right(), barrier.y(), barrier.right(),
1414 barrier.bottom(), x11::XFixes::BarrierDirections::NegativeX);
1415
1416 has_pointer_barriers_ = true;
1417 }
1418
LowerWindow()1419 void XWindow::LowerWindow() {
1420 ui::LowerWindow(xwindow_);
1421 }
1422
SetOverrideRedirect(bool override_redirect)1423 void XWindow::SetOverrideRedirect(bool override_redirect) {
1424 bool remap = window_mapped_in_client_;
1425 if (remap)
1426 Hide();
1427 connection_->ChangeWindowAttributes(x11::ChangeWindowAttributesRequest{
1428 .window = xwindow_,
1429 .override_redirect = x11::Bool32(override_redirect),
1430 });
1431 if (remap) {
1432 Map();
1433 if (has_pointer_grab_)
1434 ChangeActivePointerGrabCursor(nullptr);
1435 }
1436 }
1437
ContainsPointInRegion(const gfx::Point & point) const1438 bool XWindow::ContainsPointInRegion(const gfx::Point& point) const {
1439 if (!shape())
1440 return true;
1441
1442 for (const auto& rect : *shape()) {
1443 if (gfx::Rect(rect.x, rect.y, rect.width, rect.height).Contains(point))
1444 return true;
1445 }
1446 return false;
1447 }
1448
SetXWindowShape(std::unique_ptr<NativeShapeRects> native_shape,const gfx::Transform & transform)1449 void XWindow::SetXWindowShape(std::unique_ptr<NativeShapeRects> native_shape,
1450 const gfx::Transform& transform) {
1451 std::unique_ptr<std::vector<x11::Rectangle>> xregion;
1452 if (native_shape) {
1453 SkRegion native_region;
1454 for (const gfx::Rect& rect : *native_shape)
1455 native_region.op(gfx::RectToSkIRect(rect), SkRegion::kUnion_Op);
1456 if (!transform.IsIdentity() && !native_region.isEmpty()) {
1457 SkPath path_in_dip;
1458 if (native_region.getBoundaryPath(&path_in_dip)) {
1459 SkPath path_in_pixels;
1460 path_in_dip.transform(SkMatrix(transform.matrix()), &path_in_pixels);
1461 xregion = gfx::CreateRegionFromSkPath(path_in_pixels);
1462 } else {
1463 xregion = std::make_unique<std::vector<x11::Rectangle>>();
1464 }
1465 } else {
1466 xregion = gfx::CreateRegionFromSkRegion(native_region);
1467 }
1468 }
1469
1470 custom_window_shape_ = !!xregion;
1471 window_shape_ = std::move(xregion);
1472 ResetWindowRegion();
1473 }
1474
UnconfineCursor()1475 void XWindow::UnconfineCursor() {
1476 if (!has_pointer_barriers_)
1477 return;
1478
1479 for (auto pointer_barrier : pointer_barriers_)
1480 connection_->xfixes().DeletePointerBarrier({pointer_barrier});
1481
1482 pointer_barriers_.fill({});
1483
1484 has_pointer_barriers_ = false;
1485 }
1486
UpdateWindowRegion(std::unique_ptr<std::vector<x11::Rectangle>> region)1487 void XWindow::UpdateWindowRegion(
1488 std::unique_ptr<std::vector<x11::Rectangle>> region) {
1489 auto set_shape = [&](const std::vector<x11::Rectangle>& rectangles) {
1490 connection_->shape().Rectangles(x11::Shape::RectanglesRequest{
1491 .operation = x11::Shape::So::Set,
1492 .destination_kind = x11::Shape::Sk::Bounding,
1493 .ordering = x11::ClipOrdering::YXBanded,
1494 .destination_window = xwindow_,
1495 .rectangles = rectangles,
1496 });
1497 };
1498
1499 // If a custom window shape was supplied then apply it.
1500 if (use_custom_shape()) {
1501 set_shape(*window_shape_);
1502 return;
1503 }
1504
1505 window_shape_ = std::move(region);
1506 if (window_shape_) {
1507 set_shape(*window_shape_);
1508 return;
1509 }
1510
1511 // If we didn't set the shape for any reason, reset the shaping information.
1512 // How this is done depends on the border style, due to quirks and bugs in
1513 // various window managers.
1514 if (use_native_frame()) {
1515 // If the window has system borders, the mask must be set to null (not a
1516 // rectangle), because several window managers (eg, KDE, XFCE, XMonad) will
1517 // not put borders on a window with a custom shape.
1518 connection_->shape().Mask(x11::Shape::MaskRequest{
1519 .operation = x11::Shape::So::Set,
1520 .destination_kind = x11::Shape::Sk::Bounding,
1521 .destination_window = xwindow_,
1522 .source_bitmap = x11::Pixmap::None,
1523 });
1524 } else {
1525 // Conversely, if the window does not have system borders, the mask must be
1526 // manually set to a rectangle that covers the whole window (not null). This
1527 // is due to a bug in KWin <= 4.11.5 (KDE bug #330573) where setting a null
1528 // shape causes the hint to disable system borders to be ignored (resulting
1529 // in a double border).
1530 x11::Rectangle r{0, 0, bounds_in_pixels_.width(),
1531 bounds_in_pixels_.height()};
1532 set_shape({r});
1533 }
1534 }
1535
NotifyBoundsChanged(const gfx::Rect & new_bounds_in_px)1536 void XWindow::NotifyBoundsChanged(const gfx::Rect& new_bounds_in_px) {
1537 ResetWindowRegion();
1538 OnXWindowBoundsChanged(new_bounds_in_px);
1539 }
1540
InitializeAsStatusIcon()1541 bool XWindow::InitializeAsStatusIcon() {
1542 std::string atom_name = "_NET_SYSTEM_TRAY_S" +
1543 base::NumberToString(connection_->DefaultScreenId());
1544 auto reply = connection_->GetSelectionOwner({gfx::GetAtom(atom_name)}).Sync();
1545 if (!reply || reply->owner == x11::Window::None)
1546 return false;
1547 auto manager = reply->owner;
1548
1549 SetIntArrayProperty(xwindow_, "_XEMBED_INFO", "CARDINAL",
1550 {kXembedInfoProtocolVersion, kXembedInfoFlags});
1551
1552 x11::ChangeWindowAttributesRequest req{xwindow_};
1553 if (has_alpha()) {
1554 req.background_pixel = 0;
1555 } else {
1556 SetIntProperty(xwindow_, "CHROMIUM_COMPOSITE_WINDOW", "CARDINAL", 1);
1557 req.background_pixmap =
1558 static_cast<x11::Pixmap>(x11::BackPixmap::ParentRelative);
1559 }
1560 connection_->ChangeWindowAttributes(req);
1561
1562 auto future = SendClientMessage(
1563 manager, manager, gfx::GetAtom("_NET_SYSTEM_TRAY_OPCODE"),
1564 {static_cast<uint32_t>(X11EventSource::GetInstance()->GetTimestamp()),
1565 kSystemTrayRequestDock, static_cast<uint32_t>(xwindow_), 0, 0},
1566 x11::EventMask::NoEvent);
1567 return !future.Sync().error;
1568 }
1569
1570 } // namespace ui
1571