// 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/bind.h" #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 "net/base/network_interfaces.h" #include "third_party/skia/include/core/SkRegion.h" #include "ui/base/hit_test_x11.h" #include "ui/base/ui_base_features.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/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/connection.h" #include "ui/gfx/x/x11_atom_cache.h" #include "ui/gfx/x/x11_path.h" #include "ui/gfx/x/xfixes.h" #include "ui/gfx/x/xinput.h" #include "ui/gfx/x/xproto.h" #include "ui/gfx/x/xproto_util.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 char kDarkGtkThemeVariant[] = "dark"; constexpr long kSystemTrayRequestDock = 0; constexpr int kXembedInfoProtocolVersion = 0; constexpr int kXembedFlagMap = 1 << 0; constexpr int kXembedInfoFlags = kXembedFlagMap; enum CrossingFlags : uint8_t { CROSSING_FLAG_FOCUS = 1 << 0, CROSSING_FLAG_SAME_SCREEN = 1 << 1, }; // 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) { uint32_t width = rep.GetWidth(); data->push_back(width); uint32_t height = rep.GetHeight(); data->push_back(height); const SkBitmap& bitmap = rep.GetBitmap(); for (uint32_t y = 0; y < height; ++y) for (uint32_t x = 0; x < width; ++x) data->push_back(bitmap.getColor(x, y)); } x11::NotifyMode XI2ModeToXMode(x11::Input::NotifyMode xi2_mode) { switch (xi2_mode) { case x11::Input::NotifyMode::Normal: return x11::NotifyMode::Normal; case x11::Input::NotifyMode::Grab: case x11::Input::NotifyMode::PassiveGrab: return x11::NotifyMode::Grab; case x11::Input::NotifyMode::Ungrab: case x11::Input::NotifyMode::PassiveUngrab: return x11::NotifyMode::Ungrab; case x11::Input::NotifyMode::WhileGrabbed: return x11::NotifyMode::WhileGrabbed; default: NOTREACHED(); return x11::NotifyMode::Normal; } } x11::NotifyDetail XI2DetailToXDetail(x11::Input::NotifyDetail xi2_detail) { switch (xi2_detail) { case x11::Input::NotifyDetail::Ancestor: return x11::NotifyDetail::Ancestor; case x11::Input::NotifyDetail::Virtual: return x11::NotifyDetail::Virtual; case x11::Input::NotifyDetail::Inferior: return x11::NotifyDetail::Inferior; case x11::Input::NotifyDetail::Nonlinear: return x11::NotifyDetail::Nonlinear; case x11::Input::NotifyDetail::NonlinearVirtual: return x11::NotifyDetail::NonlinearVirtual; case x11::Input::NotifyDetail::Pointer: return x11::NotifyDetail::Pointer; case x11::Input::NotifyDetail::PointerRoot: return x11::NotifyDetail::PointerRoot; case x11::Input::NotifyDetail::None: return x11::NotifyDetail::None; } } void SyncSetCounter(x11::Connection* connection, x11::Sync::Counter counter, int64_t value) { x11::Sync::Int64 sync_value{.hi = value >> 32, .lo = value & 0xFFFFFFFF}; connection->sync().SetCounter({counter, sync_value}); } // Returns the whole path from |window| to the root. std::vector GetParentsList(x11::Connection* connection, x11::Window window) { std::vector result; while (window != x11::Window::None) { result.push_back(window); if (auto reply = connection->QueryTree({window}).Sync()) window = reply->parent; else break; } 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() : connection_(x11::Connection::Get()), x_root_window_(GetX11RootWindow()) { DCHECK(connection_); DCHECK_NE(x_root_window_, x11::Window::None); } XWindow::~XWindow() { DCHECK_EQ(xwindow_, x11::Window::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; x11::CreateWindowRequest req; req.bit_gravity = x11::Gravity::NorthWest; req.background_pixel = config.background_color.has_value() ? config.background_color.value() : connection_->default_screen().white_pixel; x11::Atom window_type; switch (config.type) { case WindowType::kMenu: req.override_redirect = x11::Bool32(true); window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_MENU"); break; case WindowType::kTooltip: req.override_redirect = x11::Bool32(true); window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_TOOLTIP"); break; case WindowType::kPopup: req.override_redirect = x11::Bool32(true); window_type = gfx::GetAtom("_NET_WM_WINDOW_TYPE_NOTIFICATION"); break; case WindowType::kDrag: req.override_redirect = x11::Bool32(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) req.override_redirect = x11::Bool32(true); #if defined(OS_CHROMEOS) req.override_redirect = x11::Bool32(UseTestConfigForPlatformWindows()); #endif override_redirect_ = req.override_redirect.has_value(); 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; } if (config.wm_role_name == kStatusIconWmRoleName) { std::string atom_name = "_NET_SYSTEM_TRAY_S" + base::NumberToString(connection_->DefaultScreenId()); auto selection = connection_->GetSelectionOwner({gfx::GetAtom(atom_name)}); if (auto reply = selection.Sync()) { GetProperty(reply->owner, gfx::GetAtom("_NET_SYSTEM_TRAY_VISUAL"), &visual_id_); } } x11::VisualId visual_id = visual_id_; uint8_t depth = 0; x11::ColorMap colormap{}; XVisualManager* visual_manager = XVisualManager::GetInstance(); if (visual_id_ == x11::VisualId{} || !visual_manager->GetVisualInfo(visual_id_, &depth, &colormap, &visual_has_alpha_)) { visual_manager->ChooseVisualForWindow(enable_transparent_visuals, &visual_id, &depth, &colormap, &visual_has_alpha_); } // x.org will BadMatch if we don't set a border when the depth isn't the // same as the parent depth. req.border_pixel = 0; bounds_in_pixels_ = SanitizeBounds(config.bounds); req.parent = x_root_window_; req.x = bounds_in_pixels_.x(); req.y = bounds_in_pixels_.y(); req.width = bounds_in_pixels_.width(); req.height = bounds_in_pixels_.height(); req.depth = depth; req.c_class = x11::WindowClass::InputOutput; req.visual = visual_id; req.colormap = colormap; xwindow_ = connection_->GenerateId(); req.wid = xwindow_; connection_->CreateWindow(req); // 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(). auto event_mask = x11::EventMask::ButtonPress | x11::EventMask::ButtonRelease | x11::EventMask::FocusChange | x11::EventMask::KeyPress | x11::EventMask::KeyRelease | x11::EventMask::EnterWindow | x11::EventMask::LeaveWindow | x11::EventMask::Exposure | x11::EventMask::VisibilityChange | x11::EventMask::StructureNotify | x11::EventMask::PropertyChange | x11::EventMask::PointerMotion; xwindow_events_ = std::make_unique(xwindow_, event_mask); connection_->Flush(); if (IsXInput2Available()) TouchFactory::GetInstance()->SetupXI2ForXWindow(xwindow_); // 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. std::vector protocols = { gfx::GetAtom("WM_DELETE_WINDOW"), gfx::GetAtom("_NET_WM_PING"), gfx::GetAtom("_NET_WM_SYNC_REQUEST"), }; SetArrayProperty(xwindow_, gfx::GetAtom("WM_PROTOCOLS"), x11::Atom::ATOM, protocols); // We need a WM_CLIENT_MACHINE value so we integrate with the desktop // environment. SetStringProperty(xwindow_, gfx::GetAtom("WM_CLIENT_MACHINE"), gfx::GetAtom("STRING"), net::GetHostName()); // 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(uint32_t) >= sizeof(pid_t), "pid_t should not be larger than uint32_t"); uint32_t pid = getpid(); SetProperty(xwindow_, gfx::GetAtom("_NET_WM_PID"), x11::Atom::CARDINAL, pid); SetProperty(xwindow_, gfx::GetAtom("_NET_WM_WINDOW_TYPE"), x11::Atom::ATOM, window_type); // 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")); SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", kAllWorkspaces); } else if (!config.workspace.empty()) { int workspace; if (base::StringToInt(config.workspace, &workspace)) SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", workspace); } if (!config.wm_class_name.empty() || !config.wm_class_class.empty()) { SetWindowClassHint(connection_, 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) SetWindowRole(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. SetHideTitlebarWhenMaximizedProperty(xwindow_, HIDE_TITLEBAR_WHEN_MAXIMIZED); } if (config.prefer_dark_theme) { SetStringProperty(xwindow_, gfx::GetAtom("_GTK_THEME_VARIANT"), gfx::GetAtom("UTF8_STRING"), kDarkGtkThemeVariant); } if (IsSyncExtensionAvailable()) { x11::Sync::Int64 value{}; update_counter_ = connection_->GenerateId(); connection_->sync().CreateCounter({update_counter_, value}); extended_update_counter_ = connection_->GenerateId(); connection_->sync().CreateCounter({extended_update_counter_, value}); std::vector counters{update_counter_, extended_update_counter_}; // Set XSyncCounter as window property _NET_WM_SYNC_REQUEST_COUNTER. the // compositor will listen on them during resizing. SetArrayProperty(xwindow_, gfx::GetAtom("_NET_WM_SYNC_REQUEST_COUNTER"), x11::Atom::CARDINAL, 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. 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. SizeHints size_hints; memset(&size_hints, 0, sizeof(size_hints)); GetWmNormalHints(xwindow_, &size_hints); size_hints.flags |= SIZE_HINT_P_POSITION; size_hints.x = bounds_in_pixels_.x(); size_hints.y = bounds_in_pixels_.y(); SetWmNormalHints(xwindow_, size_hints); ignore_keyboard_input_ = inactive; auto wm_user_time_ms = ignore_keyboard_input_ ? x11::Time::CurrentTime : X11EventSource::GetInstance()->GetTimestamp(); if (inactive || wm_user_time_ms != x11::Time::CurrentTime) { SetProperty(xwindow_, gfx::GetAtom("_NET_WM_USER_TIME"), x11::Atom::CARDINAL, wm_user_time_ms); } UpdateMinAndMaxSize(); if (window_properties_.empty()) { DeleteProperty(xwindow_, gfx::GetAtom("_NET_WM_STATE")); } else { SetAtomArrayProperty(xwindow_, "_NET_WM_STATE", "ATOM", std::vector(std::begin(window_properties_), std::end(window_properties_))); } connection_->MapWindow({xwindow_}); window_mapped_in_client_ = true; // TODO(thomasanderson): Find out why this flush is necessary. connection_->Flush(); } void XWindow::Close() { if (xwindow_ == x11::Window::None) return; CancelResize(); UnconfineCursor(); connection_->DestroyWindow({xwindow_}); xwindow_ = x11::Window::None; if (update_counter_ != x11::Sync::Counter{}) { connection_->sync().DestroyCounter({update_counter_}); connection_->sync().DestroyCounter({extended_update_counter_}); update_counter_ = {}; extended_update_counter_ = {}; } } 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_) { SendClientMessage(xwindow_, x_root_window_, gfx::GetAtom("WM_CHANGE_STATE"), {WM_STATE_ICONIC, 0, 0, 0, 0}); } else { SetWMSpecState(true, gfx::GetAtom("_NET_WM_STATE_HIDDEN"), x11::Atom::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(); WithdrawWindow(xwindow_); window_mapped_in_client_ = false; return true; } void XWindow::Unhide() { SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_HIDDEN"), x11::Atom::None); } void XWindow::SetFullscreen(bool fullscreen) { SetWMSpecState(fullscreen, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"), x11::Atom::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 = GuessWindowManager() != WM_WMII && WmSupportsHint(gfx::GetAtom("_NET_ACTIVE_WINDOW")); x11::Time timestamp = X11EventSource::GetInstance()->GetTimestamp(); // override_redirect windows ignore _NET_ACTIVE_WINDOW. // https://crbug.com/940924 if (wm_supports_active_window && !override_redirect_) { std::array data = { // We're an app. 1, static_cast(timestamp), // TODO(thomasanderson): if another chrome window is active, specify // that here. The EWMH spec claims this may make the WM more likely to // service our _NET_ACTIVE_WINDOW request. 0, 0, 0, }; SendClientMessage(xwindow_, x_root_window_, gfx::GetAtom("_NET_ACTIVE_WINDOW"), data); } else { RaiseWindow(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. connection_ ->SetInputFocus({x11::InputFocus::Parent, xwindow_, static_cast(timestamp)}) .IgnoreError(); // 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; } AfterActivationStateChanged(); } void XWindow::Deactivate() { BeforeActivationStateChanged(); // Ignore future input events. ignore_keyboard_input_ = true; ui::LowerWindow(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) { connection_->ConfigureWindow({.window = xwindow_, .width = size_in_pixels.width(), .height = 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(); x11::ConfigureWindowRequest req{.window = xwindow_}; 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); } req.width = bounds_in_pixels.width(); req.height = bounds_in_pixels.height(); } if (origin_changed) { req.x = bounds_in_pixels.x(); req.y = bounds_in_pixels.y(); } if (origin_changed || size_changed) connection_->ConfigureWindow(req); // 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(); // Even if the pixel bounds didn't change this call to the delegate should // still happen. The device scale factor may have changed which effectively // changes the bounds. OnXWindowBoundsChanged(bounds_in_pixels); } 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 HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_HIDDEN")); } bool XWindow::IsMaximized() const { return (HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT")) && HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"))); } bool XWindow::IsFullscreen() const { return HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN")); } gfx::Rect XWindow::GetOuterBounds() 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, nullptr) == x11::GrabStatus::Success); } void XWindow::ReleasePointerGrab() { UngrabPointer(); has_pointer_grab_ = false; } void XWindow::StackXWindowAbove(x11::Window window) { DCHECK(window != x11::Window::None); // Find all parent windows up to the root. std::vector window_below_parents = GetParentsList(connection_, window); std::vector window_above_parents = GetParentsList(connection_, 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()) { connection_->ConfigureWindow(x11::ConfigureWindowRequest{ .window = *it_above_window, .sibling = *it_below_window, .stack_mode = x11::StackMode::Above, }); } } void XWindow::StackXWindowAtTop() { RaiseWindow(xwindow_); } void XWindow::SetCursor(scoped_refptr cursor) { last_cursor_ = cursor; on_cursor_loaded_.Reset(base::BindOnce(DefineCursor, xwindow_)); if (cursor) cursor->OnCursorLoaded(on_cursor_loaded_.callback()); } bool XWindow::SetTitle(base::string16 title) { if (window_title_ == title) return false; window_title_ = title; std::string utf8str = base::UTF16ToUTF8(title); SetStringProperty(xwindow_, gfx::GetAtom("_NET_WM_NAME"), gfx::GetAtom("UTF8_STRING"), utf8str); SetStringProperty(xwindow_, x11::Atom::WM_NAME, gfx::GetAtom("UTF8_STRING"), utf8str); 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. uint32_t opacity_8bit = static_cast(opacity * 255.0f) & 0xFF; // Use opacity value for all channels. uint32_t channel_multiplier = 0x1010101; uint32_t cardinality = opacity_8bit * channel_multiplier; if (cardinality == 0xffffffff) { DeleteProperty(xwindow_, gfx::GetAtom("_NET_WM_WINDOW_OPACITY")); } else { SetProperty(xwindow_, gfx::GetAtom("_NET_WM_WINDOW_OPACITY"), x11::Atom::CARDINAL, cardinality); } } void XWindow::SetXWindowAspectRatio(const gfx::SizeF& aspect_ratio) { SizeHints size_hints; memset(&size_hints, 0, sizeof(size_hints)); GetWmNormalHints(xwindow_, &size_hints); // Unforce aspect ratio is parameter length is 0, otherwise set normally. if (aspect_ratio.IsEmpty()) { size_hints.flags &= ~SIZE_HINT_P_ASPECT; } else { size_hints.flags |= SIZE_HINT_P_ASPECT; size_hints.min_aspect_num = size_hints.max_aspect_num = aspect_ratio.width(); size_hints.min_aspect_den = size_hints.max_aspect_den = aspect_ratio.height(); } SetWmNormalHints(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()) { SetArrayProperty(xwindow_, gfx::GetAtom("_NET_WM_ICON"), x11::Atom::CARDINAL, data); } } void XWindow::SetXWindowVisibleOnAllWorkspaces(bool visible) { SetWMSpecState(visible, gfx::GetAtom("_NET_WM_STATE_STICKY"), x11::Atom::None); int new_desktop = 0; if (visible) { new_desktop = kAllWorkspaces; } else { if (!GetCurrentDesktop(&new_desktop)) return; } workspace_ = kAllWorkspaces; SendClientMessage(xwindow_, x_root_window_, gfx::GetAtom("_NET_WM_DESKTOP"), {new_desktop, 0, 0, 0, 0}); } 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) { connection_->WarpPointer(x11::WarpPointerRequest{ .dst_window = x_root_window_, .dst_x = bounds_in_pixels_.x() + location_in_pixels.x(), .dst_y = bounds_in_pixels_.y() + location_in_pixels.y(), }); } void XWindow::ResetWindowRegion() { std::unique_ptr> xregion; 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(std::move(xregion)); } void XWindow::OnWorkspaceUpdated() { auto old_workspace = workspace_; int workspace; if (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::Atom::None); } void XWindow::SetFlashFrameHint(bool flash_frame) { if (urgency_hint_set_ == flash_frame) return; WmHints hints; memset(&hints, 0, sizeof(hints)); GetWmHints(xwindow_, &hints); if (flash_frame) hints.flags |= WM_HINT_X_URGENCY; else hints.flags &= ~WM_HINT_X_URGENCY; SetWmHints(xwindow_, hints); 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(); SizeHints hints; memset(&hints, 0, sizeof(hints)); GetWmNormalHints(xwindow_, &hints); if (min_size_in_pixels_.IsEmpty()) { hints.flags &= ~SIZE_HINT_P_MIN_SIZE; } else { hints.flags |= SIZE_HINT_P_MIN_SIZE; hints.min_width = min_size_in_pixels_.width(); hints.min_height = min_size_in_pixels_.height(); } if (max_size_in_pixels_.IsEmpty()) { hints.flags &= ~SIZE_HINT_P_MAX_SIZE; } else { hints.flags |= SIZE_HINT_P_MAX_SIZE; hints.max_width = max_size_in_pixels_.width(); hints.max_height = max_size_in_pixels_.height(); } SetWmNormalHints(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; SetUseOSWindowFrame(xwindow_, use_native_frame); ResetWindowRegion(); } void XWindow::OnCrossingEvent(bool enter, bool focus_in_window_or_ancestor, x11::NotifyMode mode, x11::NotifyDetail 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 == x11::NotifyDetail::Inferior) return; BeforeActivationStateChanged(); if (mode == x11::NotifyMode::Grab) has_pointer_grab_ = enter; else if (mode == x11::NotifyMode::Ungrab) 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, x11::NotifyMode mode, x11::NotifyDetail 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 == x11::NotifyDetail::Inferior) return; bool notify_grab = mode == x11::NotifyMode::Grab || mode == x11::NotifyMode::Ungrab; 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 != x11::NotifyDetail::Pointer) has_window_focus_ = focus_in; if (!notify_grab && has_pointer_) { switch (detail) { case x11::NotifyDetail::Ancestor: case x11::NotifyDetail::Virtual: // 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 x11::NotifyDetail::Pointer: // 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 x11::NotifyDetail::Nonlinear: case x11::NotifyDetail::NonlinearVirtual: // 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 x11::Event& x11_event) const { return x11_event.window() == xwindow_; } bool XWindow::IsTransientWindowTargetedBy(const x11::Event& x11_event) const { return x11_event.window() == transient_window_; } void XWindow::SetTransientWindow(x11::Window window) { transient_window_ = window; } void XWindow::WmMoveResize(int hittest, const gfx::Point& location) const { int direction = HitTestToWmMoveResizeDirection(hittest); if (direction == -1) return; DoWMMoveResize(connection_, x_root_window_, xwindow_, location, direction); } void XWindow::ProcessEvent(x11::Event* 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 = EventLocationFromXEvent(*xev); gfx::Point root_point = 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. if (auto* crossing = xev->As()) { bool focus = crossing->same_screen_focus & CROSSING_FLAG_FOCUS; OnCrossingEvent(crossing->opcode == x11::CrossingEvent::EnterNotify, focus, crossing->mode, crossing->detail); } else if (auto* expose = xev->As()) { gfx::Rect damage_rect_in_pixels(expose->x, expose->y, expose->width, expose->height); OnXWindowDamageEvent(damage_rect_in_pixels); } else if (auto* focus = xev->As()) { OnFocusEvent(focus->opcode == x11::FocusEvent::In, focus->mode, focus->detail); } else if (auto* configure = xev->As()) { OnConfigureEvent(*configure); } else if (auto* crossing = xev->As()) { TouchFactory* factory = TouchFactory::GetInstance(); if (factory->ShouldProcessCrossingEvent(*crossing)) { auto mode = XI2ModeToXMode(crossing->mode); auto detail = XI2DetailToXDetail(crossing->detail); switch (crossing->opcode) { case x11::Input::CrossingEvent::Enter: OnCrossingEvent(true, crossing->focus, mode, detail); break; case x11::Input::CrossingEvent::Leave: OnCrossingEvent(false, crossing->focus, mode, detail); break; case x11::Input::CrossingEvent::FocusIn: OnFocusEvent(true, mode, detail); break; case x11::Input::CrossingEvent::FocusOut: OnFocusEvent(false, mode, detail); break; } } } else if (xev->As()) { OnWindowMapped(); } else if (xev->As()) { window_mapped_in_server_ = false; has_pointer_ = false; has_pointer_grab_ = false; has_pointer_focus_ = false; has_window_focus_ = false; } else if (auto* client = xev->As()) { x11::Atom message_type = client->type; if (message_type == gfx::GetAtom("WM_PROTOCOLS")) { x11::Atom protocol = static_cast(client->data.data32[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")) { x11::ClientMessageEvent reply_event = *client; reply_event.window = x_root_window_; x11::SendEvent(reply_event, x_root_window_, x11::EventMask::SubstructureNotify | x11::EventMask::SubstructureRedirect); } else if (protocol == gfx::GetAtom("_NET_WM_SYNC_REQUEST")) { pending_counter_value_ = client->data.data32[2] + (static_cast(client->data.data32[3]) << 32); pending_counter_value_is_extended_ = client->data.data32[4] != 0; } } else { OnXWindowDragDropEvent(xev); } } else if (auto* property = xev->As()) { x11::Atom changed_atom = property->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(); } else if (auto* selection = xev->As()) { OnXWindowSelectionEvent(xev); } } void XWindow::UpdateWMUserTime(Event* event) { if (!IsActive()) return; DCHECK(event); EventType type = event->type(); if (type == ET_MOUSE_PRESSED || type == ET_KEY_PRESSED || type == ET_TOUCH_PRESSED) { uint32_t wm_user_time_ms = (event->time_stamp() - base::TimeTicks()).InMilliseconds(); SetProperty(xwindow_, gfx::GetAtom("_NET_WM_USER_TIME"), x11::Atom::CARDINAL, wm_user_time_ms); } } 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(const x11::ConfigureNotifyEvent& configure) { DCHECK_EQ(xwindow_, configure.window); DCHECK_EQ(xwindow_, configure.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_ = false; 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 = configure.x; int translated_y_in_pixels = configure.y; if (!configure.send_event && !configure.override_redirect) { auto future = connection_->TranslateCoordinates({xwindow_, x_root_window_, 0, 0}); if (auto coords = future.Sync()) { translated_x_in_pixels = coords->dst_x; translated_y_in_pixels = coords->dst_y; } } gfx::Rect bounds_in_pixels(translated_x_in_pixels, translated_y_in_pixels, configure.width, configure.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, x11::Atom state1, x11::Atom 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 (x11::Atom 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 (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_ = HasWMSpecProperty(window_properties_, gfx::GetAtom("_NET_WM_STATE_ABOVE")); OnXWindowStateChanged(); ResetWindowRegion(); } void XWindow::OnFrameExtentsUpdated() { std::vector insets; if (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(connection_, extended_update_counter_, current_counter_value_); } return; } if (configure_counter_value_ != 0) { SyncSetCounter(connection_, 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::Sync::Counter{} || 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) { 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(connection_, extended_update_counter_, ++current_counter_value_); } CancelResize(); NotifyBoundsChanged(bounds_in_pixels); // No more member accesses here: bounds change propagation may have deleted // |this| (e.g. when a chrome window is snapped into a tab strip. Further // details at crbug.com/1068755). } 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(); auto make_barrier = [&](uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, x11::XFixes::BarrierDirections directions) { x11::XFixes::Barrier barrier = connection_->GenerateId(); connection_->xfixes().CreatePointerBarrier( {barrier, x_root_window_, x1, y1, x2, y2, directions}); return barrier; }; // Top horizontal barrier. pointer_barriers_[0] = make_barrier(barrier.x(), barrier.y(), barrier.right(), barrier.y(), x11::XFixes::BarrierDirections::PositiveY); // Bottom horizontal barrier. pointer_barriers_[1] = make_barrier(barrier.x(), barrier.bottom(), barrier.right(), barrier.bottom(), x11::XFixes::BarrierDirections::NegativeY); // Left vertical barrier. pointer_barriers_[2] = make_barrier(barrier.x(), barrier.y(), barrier.x(), barrier.bottom(), x11::XFixes::BarrierDirections::PositiveX); // Right vertical barrier. pointer_barriers_[3] = make_barrier(barrier.right(), barrier.y(), barrier.right(), barrier.bottom(), x11::XFixes::BarrierDirections::NegativeX); has_pointer_barriers_ = true; } void XWindow::LowerWindow() { ui::LowerWindow(xwindow_); } void XWindow::SetOverrideRedirect(bool override_redirect) { bool remap = window_mapped_in_client_; if (remap) Hide(); connection_->ChangeWindowAttributes(x11::ChangeWindowAttributesRequest{ .window = xwindow_, .override_redirect = x11::Bool32(override_redirect), }); if (remap) { Map(); if (has_pointer_grab_) ChangeActivePointerGrabCursor(nullptr); } } bool XWindow::ContainsPointInRegion(const gfx::Point& point) const { if (!shape()) return true; for (const auto& rect : *shape()) { if (gfx::Rect(rect.x, rect.y, rect.width, rect.height).Contains(point)) return true; } return false; } void XWindow::SetXWindowShape(std::unique_ptr native_shape, const gfx::Transform& transform) { std::unique_ptr> xregion; 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 = std::make_unique>(); } } else { xregion = gfx::CreateRegionFromSkRegion(native_region); } } custom_window_shape_ = !!xregion; window_shape_ = std::move(xregion); ResetWindowRegion(); } void XWindow::UnconfineCursor() { if (!has_pointer_barriers_) return; for (auto pointer_barrier : pointer_barriers_) connection_->xfixes().DeletePointerBarrier({pointer_barrier}); pointer_barriers_.fill({}); has_pointer_barriers_ = false; } void XWindow::UpdateWindowRegion( std::unique_ptr> region) { auto set_shape = [&](const std::vector& rectangles) { connection_->shape().Rectangles(x11::Shape::RectanglesRequest{ .operation = x11::Shape::So::Set, .destination_kind = x11::Shape::Sk::Bounding, .ordering = x11::ClipOrdering::YXBanded, .destination_window = xwindow_, .rectangles = rectangles, }); }; // If a custom window shape was supplied then apply it. if (use_custom_shape()) { set_shape(*window_shape_); return; } window_shape_ = std::move(region); if (window_shape_) { set_shape(*window_shape_); 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. connection_->shape().Mask(x11::Shape::MaskRequest{ .operation = x11::Shape::So::Set, .destination_kind = x11::Shape::Sk::Bounding, .destination_window = xwindow_, .source_bitmap = x11::Pixmap::None, }); } 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). x11::Rectangle r{0, 0, bounds_in_pixels_.width(), bounds_in_pixels_.height()}; set_shape({r}); } } 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(connection_->DefaultScreenId()); auto reply = connection_->GetSelectionOwner({gfx::GetAtom(atom_name)}).Sync(); if (!reply || reply->owner == x11::Window::None) return false; auto manager = reply->owner; SetIntArrayProperty(xwindow_, "_XEMBED_INFO", "CARDINAL", {kXembedInfoProtocolVersion, kXembedInfoFlags}); x11::ChangeWindowAttributesRequest req{xwindow_}; if (has_alpha()) { req.background_pixel = 0; } else { SetIntProperty(xwindow_, "CHROMIUM_COMPOSITE_WINDOW", "CARDINAL", 1); req.background_pixmap = static_cast(x11::BackPixmap::ParentRelative); } connection_->ChangeWindowAttributes(req); auto future = SendClientMessage( manager, manager, gfx::GetAtom("_NET_SYSTEM_TRAY_OPCODE"), {static_cast(X11EventSource::GetInstance()->GetTimestamp()), kSystemTrayRequestDock, static_cast(xwindow_), 0, 0}, x11::EventMask::NoEvent); return !future.Sync().error; } } // namespace ui