blob: 7fbc130d78df73e5e3e329c3f267d560a98c3b07 [file] [log] [blame]
// 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 <algorithm>
#include <vector>
#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 "build/chromeos_buildflags.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/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/x11_window_event_manager.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 int32_t kAllWorkspaces = -1;
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<uint32_t>* 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<x11::Window> GetParentsList(x11::Connection* connection,
x11::Window window) {
std::vector<x11::Window> 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 = x11::GetAtom("_NET_WM_WINDOW_TYPE_MENU");
break;
case WindowType::kTooltip:
req.override_redirect = x11::Bool32(true);
window_type = x11::GetAtom("_NET_WM_WINDOW_TYPE_TOOLTIP");
break;
case WindowType::kPopup:
req.override_redirect = x11::Bool32(true);
window_type = x11::GetAtom("_NET_WM_WINDOW_TYPE_NOTIFICATION");
break;
case WindowType::kDrag:
req.override_redirect = x11::Bool32(true);
window_type = x11::GetAtom("_NET_WM_WINDOW_TYPE_DND");
break;
default:
window_type = x11::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 BUILDFLAG(IS_CHROMEOS_ASH)
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({x11::GetAtom(atom_name)});
if (auto reply = selection.Sync()) {
GetProperty(reply->owner, x11::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<x11::Window>();
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<x11::XScopedEventSelector>(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<x11::Atom> protocols = {
x11::GetAtom("WM_DELETE_WINDOW"),
x11::GetAtom("_NET_WM_PING"),
x11::GetAtom("_NET_WM_SYNC_REQUEST"),
};
SetArrayProperty(xwindow_, x11::GetAtom("WM_PROTOCOLS"), x11::Atom::ATOM,
protocols);
// We need a WM_CLIENT_MACHINE value so we integrate with the desktop
// environment.
SetStringProperty(xwindow_, x11::Atom::WM_CLIENT_MACHINE, x11::Atom::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_, x11::GetAtom("_NET_WM_PID"), x11::Atom::CARDINAL, pid);
SetProperty(xwindow_, x11::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(x11::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(x11::GetAtom("_NET_WM_STATE_ABOVE"));
workspace_ = base::nullopt;
if (config.visible_on_all_workspaces) {
window_properties_.insert(x11::GetAtom("_NET_WM_STATE_STICKY"));
SetProperty(xwindow_, x11::GetAtom("_NET_WM_DESKTOP"), x11::Atom::CARDINAL,
kAllWorkspaces);
} else if (!config.workspace.empty()) {
int32_t workspace;
if (base::StringToInt(config.workspace, &workspace))
SetProperty(xwindow_, x11::GetAtom("_NET_WM_DESKTOP"),
x11::Atom::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_, x11::GetAtom("_GTK_THEME_VARIANT"),
x11::GetAtom("UTF8_STRING"), kDarkGtkThemeVariant);
}
if (IsSyncExtensionAvailable()) {
x11::Sync::Int64 value{};
update_counter_ = connection_->GenerateId<x11::Sync::Counter>();
connection_->sync().CreateCounter({update_counter_, value});
extended_update_counter_ = connection_->GenerateId<x11::Sync::Counter>();
connection_->sync().CreateCounter({extended_update_counter_, value});
std::vector<x11::Sync::Counter> 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_, x11::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.
x11::SetProperty<uint32_t>(xwindow_,
x11::GetAtom("_NET_WM_BYPASS_COMPOSITOR"),
x11::Atom::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_, x11::GetAtom("_NET_WM_USER_TIME"),
x11::Atom::CARDINAL, wm_user_time_ms);
}
UpdateMinAndMaxSize();
if (window_properties_.empty()) {
x11::DeleteProperty(xwindow_, x11::GetAtom("_NET_WM_STATE"));
} else {
SetArrayProperty(xwindow_, x11::GetAtom("_NET_WM_STATE"), x11::Atom::ATOM,
std::vector<x11::Atom>(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, x11::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"),
x11::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"));
}
void XWindow::Minimize() {
if (window_mapped_in_client_) {
SendClientMessage(xwindow_, x_root_window_, x11::GetAtom("WM_CHANGE_STATE"),
{WM_STATE_ICONIC, 0, 0, 0, 0});
} else {
SetWMSpecState(true, x11::GetAtom("_NET_WM_STATE_HIDDEN"), x11::Atom::None);
}
}
void XWindow::Unmaximize() {
should_maximize_after_map_ = false;
SetWMSpecState(false, x11::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"),
x11::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, x11::GetAtom("_NET_WM_STATE_HIDDEN"), x11::Atom::None);
}
void XWindow::SetFullscreen(bool fullscreen) {
SetWMSpecState(fullscreen, x11::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(x11::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<uint32_t, 5> data = {
// We're an app.
1,
static_cast<uint32_t>(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_,
x11::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<x11::Time>(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_,
x11::GetAtom("_NET_WM_STATE_HIDDEN"));
}
bool XWindow::IsMaximized() const {
return (HasWMSpecProperty(window_properties_,
x11::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT")) &&
HasWMSpecProperty(window_properties_,
x11::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")));
}
bool XWindow::IsFullscreen() const {
return HasWMSpecProperty(window_properties_,
x11::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<x11::Window> window_below_parents =
GetParentsList(connection_, window);
std::vector<x11::Window> 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<X11Cursor> 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_, x11::GetAtom("_NET_WM_NAME"),
x11::GetAtom("UTF8_STRING"), utf8str);
SetStringProperty(xwindow_, x11::Atom::WM_NAME, x11::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<uint32_t>(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) {
x11::DeleteProperty(xwindow_, x11::GetAtom("_NET_WM_WINDOW_OPACITY"));
} else {
SetProperty(xwindow_, x11::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<uint32_t> 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_, x11::GetAtom("_NET_WM_ICON"),
x11::Atom::CARDINAL, data);
}
}
void XWindow::SetXWindowVisibleOnAllWorkspaces(bool visible) {
SetWMSpecState(visible, x11::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_, x11::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<std::vector<x11::Rectangle>> 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 = x11::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, x11::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<gfx::Size> minimum_in_pixels = GetMinimumSizeForXWindow();
base::Optional<gfx::Size> 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::OnEvent(const 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<x11::CrossingEvent>()) {
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<x11::ExposeEvent>()) {
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<x11::FocusEvent>()) {
OnFocusEvent(focus->opcode == x11::FocusEvent::In, focus->mode,
focus->detail);
} else if (auto* configure = xev.As<x11::ConfigureNotifyEvent>()) {
OnConfigureEvent(*configure);
} else if (auto* crossing = xev.As<x11::Input::CrossingEvent>()) {
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<x11::MapNotifyEvent>()) {
OnWindowMapped();
} else if (xev.As<x11::UnmapNotifyEvent>()) {
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::ClientMessageEvent>()) {
x11::Atom message_type = client->type;
if (message_type == x11::GetAtom("WM_PROTOCOLS")) {
x11::Atom protocol = static_cast<x11::Atom>(client->data.data32[0]);
if (protocol == x11::GetAtom("WM_DELETE_WINDOW")) {
// We have received a close message from the window manager.
OnXWindowCloseRequested();
} else if (protocol == x11::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 == x11::GetAtom("_NET_WM_SYNC_REQUEST")) {
pending_counter_value_ =
client->data.data32[2] +
(static_cast<int64_t>(client->data.data32[3]) << 32);
pending_counter_value_is_extended_ = client->data.data32[4] != 0;
}
} else {
OnXWindowDragDropEvent(*client);
}
} else if (auto* property = xev.As<x11::PropertyNotifyEvent>()) {
x11::Atom changed_atom = property->atom;
if (changed_atom == x11::GetAtom("_NET_WM_STATE"))
OnWMStateUpdated();
else if (changed_atom == x11::GetAtom("_NET_FRAME_EXTENTS"))
OnFrameExtentsUpdated();
else if (changed_atom == x11::GetAtom("_NET_WM_DESKTOP"))
OnWorkspaceUpdated();
} else if (auto* selection = xev.As<x11::SelectionNotifyEvent>()) {
OnXWindowSelectionEvent(*selection);
}
}
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_, x11::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<x11::Atom> 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<x11::Atom> atom_list;
if (GetArrayProperty(xwindow_, x11::GetAtom("_NET_WM_STATE"), &atom_list) ||
window_mapped_in_client_) {
UpdateWindowProperties(
base::flat_set<x11::Atom>(std::begin(atom_list), std::end(atom_list)));
}
}
void XWindow::UpdateWindowProperties(
const base::flat_set<x11::Atom>& 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_,
x11::GetAtom("_NET_WM_STATE_ABOVE"));
OnXWindowStateChanged();
ResetWindowRegion();
}
void XWindow::OnFrameExtentsUpdated() {
std::vector<int32_t> insets;
if (GetArrayProperty(xwindow_, x11::GetAtom("_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<x11::XFixes::Barrier>();
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<NativeShapeRects> native_shape,
const gfx::Transform& transform) {
std::unique_ptr<std::vector<x11::Rectangle>> 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 = x11::CreateRegionFromSkPath(path_in_pixels);
} else {
xregion = std::make_unique<std::vector<x11::Rectangle>>();
}
} else {
xregion = x11::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<std::vector<x11::Rectangle>> region) {
auto set_shape = [&](const std::vector<x11::Rectangle>& 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({x11::GetAtom(atom_name)}).Sync();
if (!reply || reply->owner == x11::Window::None)
return false;
auto manager = reply->owner;
SetArrayProperty(
xwindow_, x11::GetAtom("_XEMBED_INFO"), x11::Atom::CARDINAL,
std::vector<uint32_t>{kXembedInfoProtocolVersion, kXembedInfoFlags});
x11::ChangeWindowAttributesRequest req{xwindow_};
if (has_alpha()) {
req.background_pixel = 0;
} else {
SetProperty(xwindow_, x11::GetAtom("CHROMIUM_COMPOSITE_WINDOW"),
x11::Atom::CARDINAL, static_cast<uint32_t>(1));
req.background_pixmap =
static_cast<x11::Pixmap>(x11::BackPixmap::ParentRelative);
}
connection_->ChangeWindowAttributes(req);
auto future = SendClientMessage(
manager, manager, x11::GetAtom("_NET_SYSTEM_TRAY_OPCODE"),
{static_cast<uint32_t>(X11EventSource::GetInstance()->GetTimestamp()),
kSystemTrayRequestDock, static_cast<uint32_t>(xwindow_), 0, 0},
x11::EventMask::NoEvent);
return !future.Sync().error;
}
} // namespace ui