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