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