// Copyright 2019 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/base/x/x11_window.h" #include #include #include "base/location.h" #include "base/memory/weak_ptr.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "third_party/skia/include/core/SkRegion.h" #include "ui/base/hit_test_x11.h" #include "ui/base/wm_role_names_linux.h" #include "ui/base/x/x11_menu_registrar.h" #include "ui/base/x/x11_pointer_grab.h" #include "ui/base/x/x11_util.h" #include "ui/base/x/x11_util_internal.h" #include "ui/events/devices/x11/device_data_manager_x11.h" #include "ui/events/devices/x11/touch_factory_x11.h" #include "ui/events/event.h" #include "ui/events/event_utils.h" #include "ui/events/platform/x11/x11_event_source.h" #include "ui/events/x/events_x_utils.h" #include "ui/events/x/x11_window_event_manager.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/image/image_skia.h" #include "ui/gfx/image/image_skia_rep.h" #include "ui/gfx/skia_util.h" #include "ui/gfx/x/x11_atom_cache.h" #include "ui/gfx/x/x11_error_tracker.h" #include "ui/gfx/x/x11_path.h" #include "ui/platform_window/common/platform_window_defaults.h" namespace ui { namespace { // Special value of the _NET_WM_DESKTOP property which indicates that the window // should appear on all workspaces/desktops. const int kAllWorkspaces = 0xFFFFFFFF; constexpr char kX11WindowRolePopup[] = "popup"; constexpr char kX11WindowRoleBubble[] = "bubble"; constexpr unsigned char kDarkGtkThemeVariant[] = "dark"; constexpr long kSystemTrayRequestDock = 0; constexpr int kXembedInfoProtocolVersion = 0; constexpr int kXembedFlagMap = 1 << 0; constexpr int kXembedInfoFlags = kXembedFlagMap; // In some situations, views tries to make a zero sized window, and that // makes us crash. Make sure we have valid sizes. gfx::Rect SanitizeBounds(const gfx::Rect& bounds) { gfx::Size sanitized_size(std::max(bounds.width(), 1), std::max(bounds.height(), 1)); gfx::Rect sanitized_bounds(bounds.origin(), sanitized_size); return sanitized_bounds; } void SerializeImageRepresentation(const gfx::ImageSkiaRep& rep, std::vector* data) { int width = rep.GetWidth(); data->push_back(width); int height = rep.GetHeight(); data->push_back(height); const SkBitmap& bitmap = rep.GetBitmap(); for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) data->push_back(bitmap.getColor(x, y)); } int XI2ModeToXMode(int xi2_mode) { switch (xi2_mode) { case XINotifyNormal: return NotifyNormal; case XINotifyGrab: case XINotifyPassiveGrab: return NotifyGrab; case XINotifyUngrab: case XINotifyPassiveUngrab: return NotifyUngrab; case XINotifyWhileGrabbed: return NotifyWhileGrabbed; default: NOTREACHED(); return NotifyNormal; } } bool SyncSetCounter(XDisplay* display, XID counter, int64_t value) { XSyncValue sync_value; XSyncIntsToValue(&sync_value, value & 0xFFFFFFFF, value >> 32); return XSyncSetCounter(display, counter, sync_value) == x11::True; } // Returns the whole path from |window| to the root. std::vector<::Window> GetParentsList(XDisplay* xdisplay, ::Window window) { ::Window parent_win, root_win; Window* child_windows; unsigned int num_child_windows; std::vector<::Window> result; while (window) { result.push_back(window); if (!XQueryTree(xdisplay, window, &root_win, &parent_win, &child_windows, &num_child_windows)) break; if (child_windows) XFree(child_windows); window = parent_win; } return result; } } // namespace XWindow::Configuration::Configuration() : type(WindowType::kWindow), opacity(WindowOpacity::kInferOpacity), icon(nullptr), activatable(true), force_show_in_taskbar(false), keep_on_top(false), visible_on_all_workspaces(false), remove_standard_frame(true), prefer_dark_theme(false) {} XWindow::Configuration::Configuration(const Configuration&) = default; XWindow::Configuration::~Configuration() = default; XWindow::XWindow() : xdisplay_(gfx::GetXDisplay()), x_root_window_(DefaultRootWindow(xdisplay_)) { DCHECK(xdisplay_); DCHECK_NE(x_root_window_, x11::None); } XWindow::~XWindow() { DCHECK_EQ(xwindow_, x11::None) << "XWindow destructed without calling " "Close() to release allocated resources."; } void XWindow::Init(const Configuration& config) { // Ensure that the X11MenuRegistrar exists. The X11MenuRegistrar is // necessary to properly track menu windows. X11MenuRegistrar::Get(); activatable_ = config.activatable; unsigned long attribute_mask = CWBackPixel | CWBitGravity; XSetWindowAttributes swa; memset(&swa, 0, sizeof(swa)); swa.background_pixmap = x11::None; swa.bit_gravity = NorthWestGravity; swa.background_pixel = config.background_color.has_value() ? config.background_color.value() : WhitePixel(xdisplay_, DefaultScreen(xdisplay_)); XAtom window_type; switch (config.type) { case WindowType::kMenu: swa.override_redirect = x11::True; window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_MENU"); break; case WindowType::kTooltip: swa.override_redirect = x11::True; window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_TOOLTIP"); break; case WindowType::kPopup: swa.override_redirect = x11::True; window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_NOTIFICATION"); break; case WindowType::kDrag: swa.override_redirect = x11::True; window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_DND"); break; default: window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_NORMAL"); break; } // An in-activatable window should not interact with the system wm. if (!activatable_ || config.override_redirect) swa.override_redirect = x11::True; #if !defined(USE_X11) // It seems like there is a difference how tests are instantiated in case of // non-Ozone X11 and Ozone. See more details in // EnableTestConfigForPlatformWindows. The reason why this must be here is // that we removed X11WindowBase in favor of the XWindow. The X11WindowBase // was only used with PlatformWindow, which meant non-Ozone X11 did not use it // and set override_redirect based only on |activatable_| variable or // WindowType. But now as XWindow is subclassed by X11Window, which is also a // PlatformWindow, and non-Ozone X11 uses it, we have to add this workaround // here. Otherwise, tests for non-Ozone X11 fail. // TODO(msisov): figure out usage of this for non-Ozone X11. if (UseTestConfigForPlatformWindows()) swa.override_redirect = true; #endif override_redirect_ = swa.override_redirect == x11::True; if (override_redirect_) attribute_mask |= CWOverrideRedirect; bool enable_transparent_visuals; switch (config.opacity) { case WindowOpacity::kOpaqueWindow: enable_transparent_visuals = false; break; case WindowOpacity::kTranslucentWindow: enable_transparent_visuals = true; break; case WindowOpacity::kInferOpacity: enable_transparent_visuals = config.type == WindowType::kDrag; } int visual_id; if (config.wm_role_name == kStatusIconWmRoleName) { std::string atom_name = "_NET_SYSTEM_TRAY_S" + base::NumberToString(DefaultScreen(xdisplay_)); XID manager = XGetSelectionOwner(xdisplay_, gfx::GetAtom(atom_name.c_str())); if (ui::GetIntProperty(manager, "_NET_SYSTEM_TRAY_VISUAL", &visual_id)) visual_id_ = visual_id; } Visual* visual = CopyFromParent; int depth = CopyFromParent; Colormap colormap = CopyFromParent; ui::XVisualManager* visual_manager = ui::XVisualManager::GetInstance(); if (!visual_id_ || !visual_manager->GetVisualInfo(visual_id_, &visual, &depth, &colormap, &visual_has_alpha_)) { visual_manager->ChooseVisualForWindow(enable_transparent_visuals, &visual, &depth, &colormap, &visual_has_alpha_); } if (colormap != CopyFromParent) { attribute_mask |= CWColormap; swa.colormap = colormap; } // x.org will BadMatch if we don't set a border when the depth isn't the // same as the parent depth. attribute_mask |= CWBorderPixel; swa.border_pixel = 0; bounds_in_pixels_ = SanitizeBounds(config.bounds); xwindow_ = XCreateWindow(xdisplay_, x_root_window_, bounds_in_pixels_.x(), bounds_in_pixels_.y(), bounds_in_pixels_.width(), bounds_in_pixels_.height(), 0, // border width depth, InputOutput, visual, attribute_mask, &swa); // It can be a status icon window. If it fails to initialize, don't provide // him with a native window handle, close self and let the client destroy // ourselves. if (config.wm_role_name == kStatusIconWmRoleName && !InitializeAsStatusIcon()) { Close(); return; } OnXWindowCreated(); // TODO(erg): Maybe need to set a ViewProp here like in RWHL::RWHL(). long event_mask = ButtonPressMask | ButtonReleaseMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | EnterWindowMask | LeaveWindowMask | ExposureMask | VisibilityChangeMask | StructureNotifyMask | PropertyChangeMask | PointerMotionMask; xwindow_events_ = std::make_unique(xwindow_, event_mask); XFlush(xdisplay_); if (ui::IsXInput2Available()) ui::TouchFactory::GetInstance()->SetupXI2ForXWindow(xwindow_); // TODO(erg): We currently only request window deletion events. We also // should listen for activation events and anything else that GTK+ listens // for, and do something useful. // Request the _NET_WM_SYNC_REQUEST protocol which is used for synchronizing // between chrome and desktop compositor (or WM) during resizing. // The resizing behavior with _NET_WM_SYNC_REQUEST is: // 1. Desktop compositor (or WM) sends client message _NET_WM_SYNC_REQUEST // with a 64 bits counter to notify about an incoming resize. // 2. Desktop compositor resizes chrome browser window. // 3. Desktop compositor waits on an alert on value change of XSyncCounter on // chrome window. // 4. Chrome handles the ConfigureNotify event, and renders a new frame with // the new size. // 5. Chrome increases the XSyncCounter on chrome window // 6. Desktop compositor gets the alert of counter change, and draws a new // frame with new content from chrome. // 7. Desktop compositor responses user mouse move events, and starts a new // resize process, go to step 1. XAtom protocols[] = { gfx::GetAtom("WM_DELETE_WINDOW"), gfx::GetAtom("_NET_WM_PING"), gfx::GetAtom("_NET_WM_SYNC_REQUEST"), }; XSetWMProtocols(xdisplay_, xwindow_, protocols, base::size(protocols)); // We need a WM_CLIENT_MACHINE and WM_LOCALE_NAME value so we integrate with // the desktop environment. XSetWMProperties(xdisplay_, xwindow_, nullptr, nullptr, nullptr, 0, nullptr, nullptr, nullptr); // Likewise, the X server needs to know this window's pid so it knows which // program to kill if the window hangs. // XChangeProperty() expects "pid" to be long. static_assert(sizeof(long) >= sizeof(pid_t), "pid_t should not be larger than long"); long pid = getpid(); XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, reinterpret_cast(&pid), 1); XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, reinterpret_cast(&window_type), 1); // The changes to |window_properties_| here will be sent to the X server just // before the window is mapped. // Remove popup windows from taskbar unless overridden. if ((config.type == WindowType::kPopup || config.type == WindowType::kBubble) && !config.force_show_in_taskbar) { window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_SKIP_TASKBAR")); } // If the window should stay on top of other windows, add the // _NET_WM_STATE_ABOVE property. is_always_on_top_ = config.keep_on_top; if (is_always_on_top_) window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_ABOVE")); workspace_ = base::nullopt; if (config.visible_on_all_workspaces) { window_properties_.insert(gfx::GetAtom("_NET_WM_STATE_STICKY")); ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", kAllWorkspaces); } else if (!config.workspace.empty()) { int workspace; if (base::StringToInt(config.workspace, &workspace)) ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", workspace); } if (!config.wm_class_name.empty() || !config.wm_class_class.empty()) { ui::SetWindowClassHint(xdisplay_, xwindow_, config.wm_class_name, config.wm_class_class); } const char* wm_role_name = nullptr; // If the widget isn't overriding the role, provide a default value for popup // and bubble types. if (!config.wm_role_name.empty()) { wm_role_name = config.wm_role_name.c_str(); } else { switch (config.type) { case WindowType::kPopup: wm_role_name = kX11WindowRolePopup; break; case WindowType::kBubble: wm_role_name = kX11WindowRoleBubble; break; default: break; } } if (wm_role_name) ui::SetWindowRole(xdisplay_, xwindow_, std::string(wm_role_name)); if (config.remove_standard_frame) { // Setting _GTK_HIDE_TITLEBAR_WHEN_MAXIMIZED tells gnome-shell to not force // fullscreen on the window when it matches the desktop size. ui::SetHideTitlebarWhenMaximizedProperty(xwindow_, ui::HIDE_TITLEBAR_WHEN_MAXIMIZED); } if (config.prefer_dark_theme) { XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_GTK_THEME_VARIANT"), gfx::GetAtom("UTF8_STRING"), 8, PropModeReplace, kDarkGtkThemeVariant, base::size(kDarkGtkThemeVariant) - 1); } if (ui::IsSyncExtensionAvailable()) { XSyncValue value; XSyncIntToValue(&value, 0); update_counter_ = XSyncCreateCounter(xdisplay_, value); extended_update_counter_ = XSyncCreateCounter(xdisplay_, value); XID counters[]{update_counter_, extended_update_counter_}; // Set XSyncCounter as window property _NET_WM_SYNC_REQUEST_COUNTER. the // compositor will listen on them during resizing. XChangeProperty( xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_SYNC_REQUEST_COUNTER"), XA_CARDINAL, 32, PropModeReplace, reinterpret_cast(counters), base::size(counters)); } // Always composite Chromium windows if a compositing WM is used. Sometimes, // WMs will not composite fullscreen windows as an optimization, but this can // lead to tearing of fullscreen videos. ui::SetIntProperty(xwindow_, "_NET_WM_BYPASS_COMPOSITOR", "CARDINAL", 2); if (config.icon) SetXWindowIcons(gfx::ImageSkia(), *config.icon); } void XWindow::Map(bool inactive) { // Before we map the window, set size hints. Otherwise, some window managers // will ignore toplevel XMoveWindow commands. XSizeHints size_hints; size_hints.flags = 0; long supplied_return; XGetWMNormalHints(xdisplay_, xwindow_, &size_hints, &supplied_return); size_hints.flags |= PPosition; size_hints.x = bounds_in_pixels_.x(); size_hints.y = bounds_in_pixels_.y(); XSetWMNormalHints(xdisplay_, xwindow_, &size_hints); ignore_keyboard_input_ = inactive; unsigned long wm_user_time_ms = ignore_keyboard_input_ ? 0 : X11EventSource::GetInstance()->GetTimestamp(); if (inactive || wm_user_time_ms != 0) { XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_USER_TIME"), XA_CARDINAL, 32, PropModeReplace, reinterpret_cast(&wm_user_time_ms), 1); } UpdateMinAndMaxSize(); if (window_properties_.empty()) { XDeleteProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_STATE")); } else { ui::SetAtomArrayProperty(xwindow_, "_NET_WM_STATE", "ATOM", std::vector(std::begin(window_properties_), std::end(window_properties_))); } XMapWindow(xdisplay_, xwindow_); window_mapped_in_client_ = true; // TODO(thomasanderson): Find out why this flush is necessary. XFlush(xdisplay_); } void XWindow::Close() { if (xwindow_ == x11::None) return; CancelResize(); UnconfineCursor(); XDestroyWindow(xdisplay_, xwindow_); xwindow_ = x11::None; if (update_counter_ != x11::None) { XSyncDestroyCounter(xdisplay_, update_counter_); XSyncDestroyCounter(xdisplay_, extended_update_counter_); update_counter_ = x11::None; extended_update_counter_ = x11::None; } } void XWindow::Maximize() { // Some WMs do not respect maximization hints on unmapped windows, so we // save this one for later too. should_maximize_after_map_ = !window_mapped_in_client_; SetWMSpecState(true, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); } void XWindow::Minimize() { if (window_mapped_in_client_) XIconifyWindow(xdisplay_, xwindow_, 0); else SetWMSpecState(true, gfx::GetAtom("_NET_WM_STATE_HIDDEN"), x11::None); } void XWindow::Unmaximize() { should_maximize_after_map_ = false; SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); } bool XWindow::Hide() { if (!window_mapped_in_client_) return false; // Make sure no resize task will run after the window is unmapped. CancelResize(); XWithdrawWindow(xdisplay_, xwindow_, 0); window_mapped_in_client_ = false; return true; } void XWindow::Unhide() { SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_HIDDEN"), x11::None); } void XWindow::SetFullscreen(bool fullscreen) { SetWMSpecState(fullscreen, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"), x11::None); } void XWindow::Activate() { if (!IsXWindowVisible() || !activatable_) return; BeforeActivationStateChanged(); ignore_keyboard_input_ = false; // wmii says that it supports _NET_ACTIVE_WINDOW but does not. // https://code.google.com/p/wmii/issues/detail?id=266 static bool wm_supports_active_window = ui::GuessWindowManager() != ui::WM_WMII && ui::WmSupportsHint(gfx::GetAtom("_NET_ACTIVE_WINDOW")); ::Time timestamp = X11EventSource::GetInstance()->GetTimestamp(); // override_redirect windows ignore _NET_ACTIVE_WINDOW. // https://crbug.com/940924 if (wm_supports_active_window && !override_redirect_) { XEvent xclient; memset(&xclient, 0, sizeof(xclient)); xclient.type = ClientMessage; xclient.xclient.window = xwindow_; xclient.xclient.message_type = gfx::GetAtom("_NET_ACTIVE_WINDOW"); xclient.xclient.format = 32; xclient.xclient.data.l[0] = 1; // Specified we are an app. xclient.xclient.data.l[1] = timestamp; // TODO(thomasanderson): if another chrome window is active, specify that in // data.l[2]. The EWMH spec claims this may make the WM more likely to // service our _NET_ACTIVE_WINDOW request. xclient.xclient.data.l[2] = x11::None; xclient.xclient.data.l[3] = 0; xclient.xclient.data.l[4] = 0; XSendEvent(xdisplay_, x_root_window_, x11::False, SubstructureRedirectMask | SubstructureNotifyMask, &xclient); } else { XRaiseWindow(xdisplay_, xwindow_); // Directly ask the X server to give focus to the window. Note that the call // would have raised an X error if the window is not mapped. auto ignore_errors = [](XDisplay*, XErrorEvent*) -> int { return 0; }; auto old_error_handler = XSetErrorHandler(ignore_errors); XSetInputFocus(xdisplay_, xwindow_, RevertToParent, timestamp); // At this point, we know we will receive focus, and some // webdriver tests depend on a window being IsActive() immediately // after an Activate(), so just set this state now. has_pointer_focus_ = false; has_window_focus_ = true; window_mapped_in_server_ = true; XSetErrorHandler(old_error_handler); } AfterActivationStateChanged(); } void XWindow::Deactivate() { BeforeActivationStateChanged(); // Ignore future input events. ignore_keyboard_input_ = true; XLowerWindow(xdisplay_, xwindow_); AfterActivationStateChanged(); } bool XWindow::IsActive() const { // Focus and stacking order are independent in X11. Since we cannot guarantee // a window is topmost iff it has focus, just use the focus state to determine // if a window is active. Note that Activate() and Deactivate() change the // stacking order in addition to changing the focus state. return (has_window_focus_ || has_pointer_focus_) && !ignore_keyboard_input_; } void XWindow::SetSize(const gfx::Size& size_in_pixels) { XResizeWindow(xdisplay_, xwindow_, size_in_pixels.width(), size_in_pixels.height()); bounds_in_pixels_.set_size(size_in_pixels); } void XWindow::SetBounds(const gfx::Rect& requested_bounds_in_pixels) { gfx::Rect bounds_in_pixels(requested_bounds_in_pixels); bool origin_changed = bounds_in_pixels_.origin() != bounds_in_pixels.origin(); bool size_changed = bounds_in_pixels_.size() != bounds_in_pixels.size(); XWindowChanges changes = {0}; unsigned value_mask = 0; if (size_changed) { // Update the minimum and maximum sizes in case they have changed. UpdateMinAndMaxSize(); if (bounds_in_pixels.width() < min_size_in_pixels_.width() || bounds_in_pixels.height() < min_size_in_pixels_.height() || (!max_size_in_pixels_.IsEmpty() && (bounds_in_pixels.width() > max_size_in_pixels_.width() || bounds_in_pixels.height() > max_size_in_pixels_.height()))) { gfx::Size size_in_pixels = bounds_in_pixels.size(); if (!max_size_in_pixels_.IsEmpty()) size_in_pixels.SetToMin(max_size_in_pixels_); size_in_pixels.SetToMax(min_size_in_pixels_); bounds_in_pixels.set_size(size_in_pixels); } changes.width = bounds_in_pixels.width(); changes.height = bounds_in_pixels.height(); value_mask |= CWHeight | CWWidth; } if (origin_changed) { changes.x = bounds_in_pixels.x(); changes.y = bounds_in_pixels.y(); value_mask |= CWX | CWY; } if (value_mask) XConfigureWindow(xdisplay_, xwindow_, value_mask, &changes); // Assume that the resize will go through as requested, which should be the // case if we're running without a window manager. If there's a window // manager, it can modify or ignore the request, but (per ICCCM) we'll get a // (possibly synthetic) ConfigureNotify about the actual size and correct // |bounds_in_pixels_| later. bounds_in_pixels_ = bounds_in_pixels; ResetWindowRegion(); } bool XWindow::IsXWindowVisible() const { // On Windows, IsVisible() returns true for minimized windows. On X11, a // minimized window is not mapped, so an explicit IsMinimized() check is // necessary. return window_mapped_in_client_ || IsMinimized(); } bool XWindow::IsMinimized() const { return ui::HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_HIDDEN")); } bool XWindow::IsMaximized() const { return (ui::HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT")) && ui::HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"))); } bool XWindow::IsFullscreen() const { return ui::HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN")); } gfx::Rect XWindow::GetOutterBounds() const { gfx::Rect outer_bounds(bounds_in_pixels_); outer_bounds.Inset(-native_window_frame_borders_in_pixels_); return outer_bounds; } void XWindow::GrabPointer() { // If the pointer is already in |xwindow_|, we will not get a crossing event // with a mode of NotifyGrab, so we must record the grab state manually. has_pointer_grab_ |= !ui::GrabPointer(xwindow_, true, x11::None); } void XWindow::ReleasePointerGrab() { ui::UngrabPointer(); has_pointer_grab_ = false; } void XWindow::StackXWindowAbove(::Window window) { DCHECK(window != x11::None); // Find all parent windows up to the root. std::vector<::Window> window_below_parents = GetParentsList(xdisplay_, window); std::vector<::Window> window_above_parents = GetParentsList(xdisplay_, xwindow_); // Find their common ancestor. auto it_below_window = window_below_parents.rbegin(); auto it_above_window = window_above_parents.rbegin(); for (; it_below_window != window_below_parents.rend() && it_above_window != window_above_parents.rend() && *it_below_window == *it_above_window; ++it_below_window, ++it_above_window) { } if (it_below_window != window_below_parents.rend() && it_above_window != window_above_parents.rend()) { // First stack |xwindow| below so Z-order of |window| stays the same. ::Window windows[] = {*it_below_window, *it_above_window}; if (XRestackWindows(xdisplay_, windows, 2) == 0) { // Now stack them properly. std::swap(windows[0], windows[1]); XRestackWindows(xdisplay_, windows, 2); } } } void XWindow::StackXWindowAtTop() { XRaiseWindow(xdisplay_, xwindow_); } void XWindow::SetCursor(::Cursor cursor) { XDefineCursor(xdisplay_, xwindow_, cursor); } bool XWindow::SetTitle(base::string16 title) { if (window_title_ == title) return false; window_title_ = title; std::string utf8str = base::UTF16ToUTF8(title); XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_NAME"), gfx::GetAtom("UTF8_STRING"), 8, PropModeReplace, reinterpret_cast(utf8str.c_str()), utf8str.size()); XTextProperty xtp; char* c_utf8_str = const_cast(utf8str.c_str()); if (Xutf8TextListToTextProperty(xdisplay_, &c_utf8_str, 1, XUTF8StringStyle, &xtp) == x11::Success) { XSetWMName(xdisplay_, xwindow_, &xtp); XFree(xtp.value); } return true; } void XWindow::SetXWindowOpacity(float opacity) { // X server opacity is in terms of 32 bit unsigned int space, and counts from // the opposite direction. // XChangeProperty() expects "cardinality" to be long. // Scale opacity to [0 .. 255] range. unsigned long opacity_8bit = static_cast(opacity * 255.0f) & 0xFF; // Use opacity value for all channels. const unsigned long channel_multiplier = 0x1010101; unsigned long cardinality = opacity_8bit * channel_multiplier; if (cardinality == 0xffffffff) { XDeleteProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_WINDOW_OPACITY")); } else { XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_WINDOW_OPACITY"), XA_CARDINAL, 32, PropModeReplace, reinterpret_cast(&cardinality), 1); } } void XWindow::SetXWindowAspectRatio(const gfx::SizeF& aspect_ratio) { XSizeHints size_hints; size_hints.flags = 0; long supplied_return; XGetWMNormalHints(xdisplay_, xwindow_, &size_hints, &supplied_return); // Unforce aspect ratio is parameter length is 0, otherwise set normally. if (!aspect_ratio.IsEmpty()) { size_hints.flags |= PAspect; size_hints.min_aspect.x = size_hints.max_aspect.x = aspect_ratio.width(); size_hints.min_aspect.y = size_hints.max_aspect.y = aspect_ratio.height(); } XSetWMNormalHints(xdisplay_, xwindow_, &size_hints); } void XWindow::SetXWindowIcons(const gfx::ImageSkia& window_icon, const gfx::ImageSkia& app_icon) { // TODO(erg): The way we handle icons across different versions of chrome // could be substantially improved. The Windows version does its own thing // and only sometimes comes down this code path. The icon stuff in // ChromeViewsDelegate is hard coded to use HICONs. Likewise, we're hard // coded to be given two images instead of an arbitrary collection of images // so that we can pass to the WM. // // All of this could be made much, much better. std::vector data; if (!window_icon.isNull()) SerializeImageRepresentation(window_icon.GetRepresentation(1.0f), &data); if (!app_icon.isNull()) SerializeImageRepresentation(app_icon.GetRepresentation(1.0f), &data); if (!data.empty()) ui::SetAtomArrayProperty(xwindow_, "_NET_WM_ICON", "CARDINAL", data); } void XWindow::SetXWindowVisibleOnAllWorkspaces(bool visible) { SetWMSpecState(visible, gfx::GetAtom("_NET_WM_STATE_STICKY"), x11::None); int new_desktop = 0; if (visible) { new_desktop = kAllWorkspaces; } else { if (!ui::GetCurrentDesktop(&new_desktop)) return; } workspace_ = kAllWorkspaces; XEvent xevent; memset(&xevent, 0, sizeof(xevent)); xevent.type = ClientMessage; xevent.xclient.window = xwindow_; xevent.xclient.message_type = gfx::GetAtom("_NET_WM_DESKTOP"); xevent.xclient.format = 32; xevent.xclient.data.l[0] = new_desktop; xevent.xclient.data.l[1] = 0; xevent.xclient.data.l[2] = 0; xevent.xclient.data.l[3] = 0; xevent.xclient.data.l[4] = 0; XSendEvent(xdisplay_, x_root_window_, x11::False, SubstructureRedirectMask | SubstructureNotifyMask, &xevent); } bool XWindow::IsXWindowVisibleOnAllWorkspaces() const { // We don't need a check for _NET_WM_STATE_STICKY because that would specify // that the window remain in a fixed position even if the viewport scrolls. // This is different from the type of workspace that's associated with // _NET_WM_DESKTOP. return workspace_ == kAllWorkspaces; } void XWindow::MoveCursorTo(const gfx::Point& location_in_pixels) { XWarpPointer(xdisplay_, x11::None, x_root_window_, 0, 0, 0, 0, bounds_in_pixels_.x() + location_in_pixels.x(), bounds_in_pixels_.y() + location_in_pixels.y()); } void XWindow::ResetWindowRegion() { XRegion* xregion = nullptr; if (!use_custom_shape() && !IsMaximized() && !IsFullscreen()) { SkPath window_mask; GetWindowMaskForXWindow(bounds().size(), &window_mask); // Some frame views define a custom (non-rectangular) window mask. If // so, use it to define the window shape. If not, fall through. if (window_mask.countPoints() > 0) xregion = gfx::CreateRegionFromSkPath(window_mask); } UpdateWindowRegion(xregion); } void XWindow::OnWorkspaceUpdated() { auto old_workspace = workspace_; int workspace; if (ui::GetWindowDesktop(xwindow_, &workspace)) workspace_ = workspace; else workspace_ = base::nullopt; if (workspace_ != old_workspace) OnXWindowWorkspaceChanged(); } void XWindow::SetAlwaysOnTop(bool always_on_top) { is_always_on_top_ = always_on_top; SetWMSpecState(always_on_top, gfx::GetAtom("_NET_WM_STATE_ABOVE"), x11::None); } void XWindow::SetFlashFrameHint(bool flash_frame) { if (urgency_hint_set_ == flash_frame) return; gfx::XScopedPtr hints(XGetWMHints(xdisplay_, xwindow_)); if (!hints) { // The window hasn't had its hints set yet. hints.reset(XAllocWMHints()); } if (flash_frame) hints->flags |= XUrgencyHint; else hints->flags &= ~XUrgencyHint; XSetWMHints(xdisplay_, xwindow_, hints.get()); urgency_hint_set_ = flash_frame; } void XWindow::UpdateMinAndMaxSize() { base::Optional minimum_in_pixels = GetMinimumSizeForXWindow(); base::Optional maximum_in_pixels = GetMaximumSizeForXWindow(); if ((!minimum_in_pixels || min_size_in_pixels_ == minimum_in_pixels.value()) && (!maximum_in_pixels || max_size_in_pixels_ == maximum_in_pixels.value())) return; min_size_in_pixels_ = minimum_in_pixels.value(); max_size_in_pixels_ = maximum_in_pixels.value(); XSizeHints hints; hints.flags = 0; long supplied_return; XGetWMNormalHints(xdisplay_, xwindow_, &hints, &supplied_return); if (min_size_in_pixels_.IsEmpty()) { hints.flags &= ~PMinSize; } else { hints.flags |= PMinSize; hints.min_width = min_size_in_pixels_.width(); hints.min_height = min_size_in_pixels_.height(); } if (max_size_in_pixels_.IsEmpty()) { hints.flags &= ~PMaxSize; } else { hints.flags |= PMaxSize; hints.max_width = max_size_in_pixels_.width(); hints.max_height = max_size_in_pixels_.height(); } XSetWMNormalHints(xdisplay_, xwindow_, &hints); } void XWindow::BeforeActivationStateChanged() { was_active_ = IsActive(); had_pointer_ = has_pointer_; had_pointer_grab_ = has_pointer_grab_; had_window_focus_ = has_window_focus_; } void XWindow::AfterActivationStateChanged() { if (had_pointer_grab_ && !has_pointer_grab_) OnXWindowLostPointerGrab(); bool had_pointer_capture = had_pointer_ || had_pointer_grab_; bool has_pointer_capture = has_pointer_ || has_pointer_grab_; if (had_pointer_capture && !has_pointer_capture) OnXWindowLostCapture(); bool is_active = IsActive(); if (!was_active_ && is_active) SetFlashFrameHint(false); if (was_active_ != is_active) OnXWindowIsActiveChanged(is_active); } void XWindow::SetUseNativeFrame(bool use_native_frame) { use_native_frame_ = use_native_frame; ui::SetUseOSWindowFrame(xwindow_, use_native_frame); ResetWindowRegion(); } void XWindow::OnCrossingEvent(bool enter, bool focus_in_window_or_ancestor, int mode, int detail) { // NotifyInferior on a crossing event means the pointer moved into or out of a // child window, but the pointer is still within |xwindow_|. if (detail == NotifyInferior) return; BeforeActivationStateChanged(); if (mode == NotifyGrab) has_pointer_grab_ = enter; else if (mode == NotifyUngrab) has_pointer_grab_ = false; has_pointer_ = enter; if (focus_in_window_or_ancestor && !has_window_focus_) { // If we reach this point, we know the focus is in an ancestor or the // pointer root. The definition of |has_pointer_focus_| is (An ancestor // window or the PointerRoot is focused) && |has_pointer_|. Therefore, we // can just use |has_pointer_| in the assignment. The transitions for when // the focus changes are handled in OnFocusEvent(). has_pointer_focus_ = has_pointer_; } AfterActivationStateChanged(); } void XWindow::OnFocusEvent(bool focus_in, int mode, int detail) { // NotifyInferior on a focus event means the focus moved into or out of a // child window, but the focus is still within |xwindow_|. if (detail == NotifyInferior) return; bool notify_grab = mode == NotifyGrab || mode == NotifyUngrab; BeforeActivationStateChanged(); // For every focus change, the X server sends normal focus events which are // useful for tracking |has_window_focus_|, but supplements these events with // NotifyPointer events which are only useful for tracking pointer focus. // For |has_pointer_focus_| and |has_window_focus_|, we continue tracking // state during a grab, but ignore grab/ungrab events themselves. if (!notify_grab && detail != NotifyPointer) has_window_focus_ = focus_in; if (!notify_grab && has_pointer_) { switch (detail) { case NotifyAncestor: case NotifyVirtual: // If we reach this point, we know |has_pointer_| was true before and // after this event. Since the definition of |has_pointer_focus_| is // (An ancestor window or the PointerRoot is focused) && |has_pointer_|, // we only need to worry about transitions on the first conjunct. // Therefore, |has_pointer_focus_| will become true when: // 1. Focus moves from |xwindow_| to an ancestor // (FocusOut with NotifyAncestor) // 2. Focus moves from a descendant of |xwindow_| to an ancestor // (FocusOut with NotifyVirtual) // |has_pointer_focus_| will become false when: // 1. Focus moves from an ancestor to |xwindow_| // (FocusIn with NotifyAncestor) // 2. Focus moves from an ancestor to a child of |xwindow_| // (FocusIn with NotifyVirtual) has_pointer_focus_ = !focus_in; break; case NotifyPointer: // The remaining cases for |has_pointer_focus_| becoming true are: // 3. Focus moves from |xwindow_| to the PointerRoot // 4. Focus moves from a descendant of |xwindow_| to the PointerRoot // 5. Focus moves from None to the PointerRoot // 6. Focus moves from Other to the PointerRoot // 7. Focus moves from None to an ancestor of |xwindow_| // 8. Focus moves from Other to an ancestor of |xwindow_| // In each case, we will get a FocusIn with a detail of NotifyPointer. // The remaining cases for |has_pointer_focus_| becoming false are: // 3. Focus moves from the PointerRoot to |xwindow_| // 4. Focus moves from the PointerRoot to a descendant of |xwindow| // 5. Focus moves from the PointerRoot to None // 6. Focus moves from an ancestor of |xwindow_| to None // 7. Focus moves from the PointerRoot to Other // 8. Focus moves from an ancestor of |xwindow_| to Other // In each case, we will get a FocusOut with a detail of NotifyPointer. has_pointer_focus_ = focus_in; break; case NotifyNonlinear: case NotifyNonlinearVirtual: // We get Nonlinear(Virtual) events when // 1. Focus moves from Other to |xwindow_| // (FocusIn with NotifyNonlinear) // 2. Focus moves from Other to a descendant of |xwindow_| // (FocusIn with NotifyNonlinearVirtual) // 3. Focus moves from |xwindow_| to Other // (FocusOut with NotifyNonlinear) // 4. Focus moves from a descendant of |xwindow_| to Other // (FocusOut with NotifyNonlinearVirtual) // |has_pointer_focus_| should be false before and after this event. has_pointer_focus_ = false; break; default: break; } } ignore_keyboard_input_ = false; AfterActivationStateChanged(); } bool XWindow::IsTargetedBy(const XEvent& xev) const { ::Window target_window = (xev.type == GenericEvent) ? static_cast(xev.xcookie.data)->event : xev.xany.window; return target_window == xwindow_; } void XWindow::WmMoveResize(int hittest, const gfx::Point& location) const { int direction = HitTestToWmMoveResizeDirection(hittest); if (direction == -1) return; DoWMMoveResize(xdisplay_, x_root_window_, xwindow_, location, direction); } // In Ozone, there are no ui::*Event constructors receiving XEvent* as input, // in this case ui::PlatformEvent is expected. Furthermore, // X11EventSourceLibevent is used in that case, which already translates // Mouse/Key/Touch/Scroll events into ui::Events so they should not be handled // by PlatformWindow, which is supposed to use XWindow in Ozone builds. So // handling these events is disabled for Ozone. void XWindow::ProcessEvent(XEvent* xev) { // We can lose track of the window's position when the window is reparented. // When the parent window is moved, we won't get an event, so the window's // position relative to the root window will get out-of-sync. We can re-sync // when getting pointer events (EnterNotify, LeaveNotify, ButtonPress, // ButtonRelease, MotionNotify) which include the pointer location both // relative to this window and relative to the root window, so we can // calculate this window's position from that information. gfx::Point window_point = ui::EventLocationFromXEvent(*xev); gfx::Point root_point = ui::EventSystemLocationFromXEvent(*xev); if (!window_point.IsOrigin() && !root_point.IsOrigin()) { gfx::Point window_origin = gfx::Point() + (root_point - window_point); if (bounds_in_pixels_.origin() != window_origin) { bounds_in_pixels_.set_origin(window_origin); NotifyBoundsChanged(bounds_in_pixels_); } } // May want to factor CheckXEventForConsistency(xev); into a common location // since it is called here. switch (xev->type) { case EnterNotify: case LeaveNotify: { OnCrossingEvent(xev->type == EnterNotify, xev->xcrossing.focus, xev->xcrossing.mode, xev->xcrossing.detail); break; } case Expose: { gfx::Rect damage_rect_in_pixels(xev->xexpose.x, xev->xexpose.y, xev->xexpose.width, xev->xexpose.height); OnXWindowDamageEvent(damage_rect_in_pixels); break; } case x11::FocusIn: case x11::FocusOut: OnFocusEvent(xev->type == x11::FocusIn, xev->xfocus.mode, xev->xfocus.detail); break; case ConfigureNotify: OnConfigureEvent(xev); break; case GenericEvent: { ui::TouchFactory* factory = ui::TouchFactory::GetInstance(); if (!factory->ShouldProcessXI2Event(xev)) break; XIEnterEvent* enter_event = static_cast(xev->xcookie.data); switch (static_cast(xev->xcookie.data)->evtype) { case XI_Enter: case XI_Leave: { OnCrossingEvent(enter_event->evtype == XI_Enter, enter_event->focus, XI2ModeToXMode(enter_event->mode), enter_event->detail); return; } case XI_FocusIn: case XI_FocusOut: { OnFocusEvent(enter_event->evtype == XI_FocusIn, XI2ModeToXMode(enter_event->mode), enter_event->detail); return; } default: break; } break; } case MapNotify: { OnWindowMapped(); break; } case UnmapNotify: { window_mapped_in_server_ = false; has_pointer_ = false; has_pointer_grab_ = false; has_pointer_focus_ = false; has_window_focus_ = false; break; } case ClientMessage: { Atom message_type = xev->xclient.message_type; if (message_type == gfx::GetAtom("WM_PROTOCOLS")) { Atom protocol = static_cast(xev->xclient.data.l[0]); if (protocol == gfx::GetAtom("WM_DELETE_WINDOW")) { // We have received a close message from the window manager. OnXWindowCloseRequested(); } else if (protocol == gfx::GetAtom("_NET_WM_PING")) { XEvent reply_event = *xev; reply_event.xclient.window = x_root_window_; XSendEvent(xdisplay_, reply_event.xclient.window, x11::False, SubstructureRedirectMask | SubstructureNotifyMask, &reply_event); } else if (protocol == gfx::GetAtom("_NET_WM_SYNC_REQUEST")) { pending_counter_value_ = xev->xclient.data.l[2] + (static_cast(xev->xclient.data.l[3]) << 32); pending_counter_value_is_extended_ = xev->xclient.data.l[4] != 0; } } else { OnXWindowDragDropEvent(xev); } break; } case MappingNotify: { switch (xev->xmapping.request) { case MappingModifier: case MappingKeyboard: XRefreshKeyboardMapping(&xev->xmapping); break; case MappingPointer: ui::DeviceDataManagerX11::GetInstance()->UpdateButtonMap(); break; default: NOTIMPLEMENTED() << " Unknown request: " << xev->xmapping.request; break; } break; } case PropertyNotify: { XAtom changed_atom = xev->xproperty.atom; if (changed_atom == gfx::GetAtom("_NET_WM_STATE")) { OnWMStateUpdated(); } else if (changed_atom == gfx::GetAtom("_NET_FRAME_EXTENTS")) { OnFrameExtentsUpdated(); } else if (changed_atom == gfx::GetAtom("_NET_WM_DESKTOP")) { OnWorkspaceUpdated(); } break; } case SelectionNotify: { OnXWindowSelectionEvent(xev); break; } } } void XWindow::UpdateWMUserTime(ui::Event* event) { if (!IsActive()) return; DCHECK(event); ui::EventType type = event->type(); if (type == ui::ET_MOUSE_PRESSED || type == ui::ET_KEY_PRESSED || type == ui::ET_TOUCH_PRESSED) { unsigned long wm_user_time_ms = (event->time_stamp() - base::TimeTicks()).InMilliseconds(); XChangeProperty(xdisplay_, xwindow_, gfx::GetAtom("_NET_WM_USER_TIME"), XA_CARDINAL, 32, PropModeReplace, reinterpret_cast(&wm_user_time_ms), 1); } } void XWindow::OnWindowMapped() { window_mapped_in_server_ = true; // Some WMs only respect maximize hints after the window has been mapped. // Check whether we need to re-do a maximization. if (should_maximize_after_map_) { Maximize(); should_maximize_after_map_ = false; } } void XWindow::OnConfigureEvent(XEvent* xev) { DCHECK_EQ(xwindow_, xev->xconfigure.window); DCHECK_EQ(xwindow_, xev->xconfigure.event); if (pending_counter_value_) { DCHECK(!configure_counter_value_); configure_counter_value_ = pending_counter_value_; configure_counter_value_is_extended_ = pending_counter_value_is_extended_; pending_counter_value_is_extended_ = 0; pending_counter_value_ = 0; } // It's possible that the X window may be resized by some other means than // from within aura (e.g. the X window manager can change the size). Make // sure the root window size is maintained properly. int translated_x_in_pixels = xev->xconfigure.x; int translated_y_in_pixels = xev->xconfigure.y; if (!xev->xconfigure.send_event && !xev->xconfigure.override_redirect) { Window unused; XTranslateCoordinates(xdisplay_, xwindow_, x_root_window_, 0, 0, &translated_x_in_pixels, &translated_y_in_pixels, &unused); } gfx::Rect bounds_in_pixels(translated_x_in_pixels, translated_y_in_pixels, xev->xconfigure.width, xev->xconfigure.height); bool size_changed = bounds_in_pixels_.size() != bounds_in_pixels.size(); bool origin_changed = bounds_in_pixels_.origin() != bounds_in_pixels.origin(); previous_bounds_in_pixels_ = bounds_in_pixels_; bounds_in_pixels_ = bounds_in_pixels; if (size_changed) DispatchResize(); else if (origin_changed) NotifyBoundsChanged(bounds_in_pixels_); } void XWindow::SetWMSpecState(bool enabled, XAtom state1, XAtom state2) { if (window_mapped_in_client_) { ui::SetWMSpecState(xwindow_, enabled, state1, state2); } else { // The updated state will be set when the window is (re)mapped. base::flat_set new_window_properties = window_properties_; for (XAtom atom : {state1, state2}) { if (enabled) new_window_properties.insert(atom); else new_window_properties.erase(atom); } UpdateWindowProperties(new_window_properties); } } void XWindow::OnWMStateUpdated() { // The EWMH spec requires window managers to remove the _NET_WM_STATE property // when a window is unmapped. However, Chromium code wants the state to // persist across a Hide() and Show(). So if the window is currently // unmapped, leave the state unchanged so it will be restored when the window // is remapped. std::vector atom_list; if (ui::GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &atom_list) || window_mapped_in_client_) { UpdateWindowProperties( base::flat_set(std::begin(atom_list), std::end(atom_list))); } } void XWindow::UpdateWindowProperties( const base::flat_set& new_window_properties) { was_minimized_ = IsMinimized(); window_properties_ = new_window_properties; // Ignore requests by the window manager to enter or exit fullscreen (e.g. as // a result of pressing a window manager accelerator key). Chrome does not // handle window manager initiated fullscreen. In particular, Chrome needs to // do preprocessing before the x window's fullscreen state is toggled. is_always_on_top_ = ui::HasWMSpecProperty( window_properties_, gfx::GetAtom("_NET_WM_STATE_ABOVE")); OnXWindowStateChanged(); ResetWindowRegion(); } void XWindow::OnFrameExtentsUpdated() { std::vector insets; if (ui::GetIntArrayProperty(xwindow_, "_NET_FRAME_EXTENTS", &insets) && insets.size() == 4) { // |insets| are returned in the order: [left, right, top, bottom]. native_window_frame_borders_in_pixels_ = gfx::Insets(insets[2], insets[0], insets[3], insets[1]); } else { native_window_frame_borders_in_pixels_ = gfx::Insets(); } } void XWindow::NotifySwapAfterResize() { if (configure_counter_value_is_extended_) { if ((current_counter_value_ % 2) == 1) { // An increase 3 means that the frame was not drawn as fast as possible. // This can trigger different handling from the compositor. // Setting an even number to |extended_update_counter_| will trigger a // new resize. current_counter_value_ += 3; SyncSetCounter(xdisplay_, extended_update_counter_, current_counter_value_); } return; } if (configure_counter_value_ != 0) { SyncSetCounter(xdisplay_, update_counter_, configure_counter_value_); configure_counter_value_ = 0; } } // Removes |delayed_resize_task_| from the task queue (if it's in // the queue) and adds it back at the end of the queue. void XWindow::DispatchResize() { if (update_counter_ == x11::None || configure_counter_value_ == 0) { // WM doesn't support _NET_WM_SYNC_REQUEST. // Or we are too slow, so _NET_WM_SYNC_REQUEST is disabled by the // compositor. delayed_resize_task_.Reset(base::BindOnce( &XWindow::DelayedResize, base::Unretained(this), bounds_in_pixels_)); base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, delayed_resize_task_.callback()); return; } if (configure_counter_value_is_extended_) { current_counter_value_ = configure_counter_value_; configure_counter_value_ = 0; // Make sure the counter is even number. if ((current_counter_value_ % 2) == 1) ++current_counter_value_; } // If _NET_WM_SYNC_REQUEST is used to synchronize with compositor during // resizing, the compositor will not resize the window, until last resize is // handled, so we don't need accumulate resize events. DelayedResize(bounds_in_pixels_); } void XWindow::DelayedResize(const gfx::Rect& bounds_in_pixels) { base::WeakPtr alive(resize_weak_factory_.GetWeakPtr()); if (configure_counter_value_is_extended_ && (current_counter_value_ % 2) == 0) { // Increase the |extended_update_counter_|, so the compositor will know we // are not frozen and re-enable _NET_WM_SYNC_REQUEST, if it was disabled. // Increase the |extended_update_counter_| to an odd number will not trigger // a new resize. SyncSetCounter(xdisplay_, extended_update_counter_, ++current_counter_value_); } NotifyBoundsChanged(bounds_in_pixels); // Bounds change propagation above may spin a window move loop, which might // end up closing and destroying this instance (e.g: when a chrome window is // snapped into a tab strip). So we must handle this possible scenario before // trying to access any class variable/function. See crbug.com/1068755. if (!alive) return; CancelResize(); } void XWindow::CancelResize() { delayed_resize_task_.Cancel(); } void XWindow::ConfineCursorTo(const gfx::Rect& bounds) { UnconfineCursor(); if (bounds.IsEmpty()) return; gfx::Rect barrier = bounds + bounds_in_pixels_.OffsetFromOrigin(); // Top horizontal barrier. pointer_barriers_[0] = XFixesCreatePointerBarrier( xdisplay_, x_root_window_, barrier.x(), barrier.y(), barrier.right(), barrier.y(), BarrierPositiveY, 0, XIAllDevices); // Bottom horizontal barrier. pointer_barriers_[1] = XFixesCreatePointerBarrier( xdisplay_, x_root_window_, barrier.x(), barrier.bottom(), barrier.right(), barrier.bottom(), BarrierNegativeY, 0, XIAllDevices); // Left vertical barrier. pointer_barriers_[2] = XFixesCreatePointerBarrier( xdisplay_, x_root_window_, barrier.x(), barrier.y(), barrier.x(), barrier.bottom(), BarrierPositiveX, 0, XIAllDevices); // Right vertical barrier. pointer_barriers_[3] = XFixesCreatePointerBarrier( xdisplay_, x_root_window_, barrier.right(), barrier.y(), barrier.right(), barrier.bottom(), BarrierNegativeX, 0, XIAllDevices); has_pointer_barriers_ = true; } void XWindow::LowerWindow() { XLowerWindow(xdisplay_, xwindow_); } void XWindow::SetOverrideRedirect(bool override_redirect) { bool remap = window_mapped_in_client_; if (remap) Hide(); XSetWindowAttributes swa; swa.override_redirect = override_redirect; XChangeWindowAttributes(xdisplay_, xwindow_, CWOverrideRedirect, &swa); if (remap) { Map(); if (has_pointer_grab_) ui::ChangeActivePointerGrabCursor(x11::None); } } bool XWindow::ContainsPointInRegion(const gfx::Point& point) const { if (!shape()) return true; return XPointInRegion(shape(), point.x(), point.y()) == x11::True; } void XWindow::SetXWindowShape(std::unique_ptr native_shape, const gfx::Transform& transform) { XRegion* xregion = nullptr; if (native_shape) { SkRegion native_region; for (const gfx::Rect& rect : *native_shape) native_region.op(gfx::RectToSkIRect(rect), SkRegion::kUnion_Op); if (!transform.IsIdentity() && !native_region.isEmpty()) { SkPath path_in_dip; if (native_region.getBoundaryPath(&path_in_dip)) { SkPath path_in_pixels; path_in_dip.transform(SkMatrix(transform.matrix()), &path_in_pixels); xregion = gfx::CreateRegionFromSkPath(path_in_pixels); } else { xregion = XCreateRegion(); } } else { xregion = gfx::CreateRegionFromSkRegion(native_region); } } custom_window_shape_ = !!xregion; window_shape_.reset(xregion); ResetWindowRegion(); } void XWindow::UnconfineCursor() { if (!has_pointer_barriers_) return; for (XID pointer_barrier : pointer_barriers_) XFixesDestroyPointerBarrier(xdisplay_, pointer_barrier); pointer_barriers_.fill(x11::None); has_pointer_barriers_ = false; } void XWindow::UpdateWindowRegion(XRegion* xregion) { // If a custom window shape was supplied then apply it. if (use_custom_shape()) { XShapeCombineRegion(xdisplay_, xwindow_, ShapeBounding, 0, 0, window_shape_.get(), false); return; } window_shape_.reset(xregion); if (window_shape_) { XShapeCombineRegion(xdisplay_, xwindow_, ShapeBounding, 0, 0, window_shape_.get(), false); return; } // If we didn't set the shape for any reason, reset the shaping information. // How this is done depends on the border style, due to quirks and bugs in // various window managers. if (use_native_frame()) { // If the window has system borders, the mask must be set to null (not a // rectangle), because several window managers (eg, KDE, XFCE, XMonad) will // not put borders on a window with a custom shape. XShapeCombineMask(xdisplay_, xwindow_, ShapeBounding, 0, 0, x11::None, ShapeSet); } else { // Conversely, if the window does not have system borders, the mask must be // manually set to a rectangle that covers the whole window (not null). This // is due to a bug in KWin <= 4.11.5 (KDE bug #330573) where setting a null // shape causes the hint to disable system borders to be ignored (resulting // in a double border). XRectangle r = {0, 0, static_cast(bounds_in_pixels_.width()), static_cast(bounds_in_pixels_.height())}; XShapeCombineRectangles(xdisplay_, xwindow_, ShapeBounding, 0, 0, &r, 1, ShapeSet, YXBanded); } } void XWindow::NotifyBoundsChanged(const gfx::Rect& new_bounds_in_px) { ResetWindowRegion(); OnXWindowBoundsChanged(new_bounds_in_px); } bool XWindow::InitializeAsStatusIcon() { std::string atom_name = "_NET_SYSTEM_TRAY_S" + base::NumberToString(DefaultScreen(xdisplay_)); XID manager = XGetSelectionOwner(xdisplay_, gfx::GetAtom(atom_name.c_str())); if (manager == x11::None) return false; ui::SetIntArrayProperty(xwindow_, "_XEMBED_INFO", "CARDINAL", {kXembedInfoProtocolVersion, kXembedInfoFlags}); XSetWindowAttributes attrs; unsigned long flags = 0; if (has_alpha()) { flags |= CWBackPixel; attrs.background_pixel = 0; } else { ui::SetIntProperty(xwindow_, "CHROMIUM_COMPOSITE_WINDOW", "CARDINAL", 1); flags |= CWBackPixmap; attrs.background_pixmap = ParentRelative; } XChangeWindowAttributes(xdisplay_, xwindow_, flags, &attrs); XEvent ev; memset(&ev, 0, sizeof(ev)); ev.xclient.type = ClientMessage; ev.xclient.window = manager; ev.xclient.message_type = gfx::GetAtom("_NET_SYSTEM_TRAY_OPCODE"); ev.xclient.format = 32; ev.xclient.data.l[0] = ui::X11EventSource::GetInstance()->GetTimestamp(); ev.xclient.data.l[1] = kSystemTrayRequestDock; ev.xclient.data.l[2] = xwindow_; bool error; { gfx::X11ErrorTracker error_tracker; XSendEvent(xdisplay_, manager, false, NoEventMask, &ev); error = error_tracker.FoundNewError(); } return !error; } } // namespace ui