| // Copyright (c) 2012 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/views/widget/desktop_aura/desktop_window_tree_host_x11.h" |
| |
| #include <X11/extensions/shape.h> |
| #include <X11/extensions/XInput2.h> |
| #include <X11/Xatom.h> |
| #include <X11/Xregion.h> |
| #include <X11/Xutil.h> |
| |
| #include "base/basictypes.h" |
| #include "base/command_line.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "third_party/skia/include/core/SkPath.h" |
| #include "ui/aura/client/cursor_client.h" |
| #include "ui/aura/client/focus_client.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_event_dispatcher.h" |
| #include "ui/aura/window_property.h" |
| #include "ui/base/dragdrop/os_exchange_data_provider_aurax11.h" |
| #include "ui/base/hit_test.h" |
| #include "ui/base/ime/input_method.h" |
| #include "ui/base/x/x11_util.h" |
| #include "ui/events/devices/x11/device_data_manager_x11.h" |
| #include "ui/events/devices/x11/device_list_cache_x11.h" |
| #include "ui/events/devices/x11/touch_factory_x11.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/null_event_targeter.h" |
| #include "ui/events/platform/platform_event_source.h" |
| #include "ui/events/platform/x11/x11_event_source.h" |
| #include "ui/gfx/display.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_skia_rep.h" |
| #include "ui/gfx/path.h" |
| #include "ui/gfx/path_x11.h" |
| #include "ui/gfx/screen.h" |
| #include "ui/native_theme/native_theme.h" |
| #include "ui/views/corewm/tooltip_aura.h" |
| #include "ui/views/linux_ui/linux_ui.h" |
| #include "ui/views/views_delegate.h" |
| #include "ui/views/views_switches.h" |
| #include "ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h" |
| #include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h" |
| #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" |
| #include "ui/views/widget/desktop_aura/desktop_window_tree_host_observer_x11.h" |
| #include "ui/views/widget/desktop_aura/x11_desktop_handler.h" |
| #include "ui/views/widget/desktop_aura/x11_desktop_window_move_client.h" |
| #include "ui/views/widget/desktop_aura/x11_pointer_grab.h" |
| #include "ui/views/widget/desktop_aura/x11_window_event_filter.h" |
| #include "ui/wm/core/compound_event_filter.h" |
| #include "ui/wm/core/window_util.h" |
| |
| DECLARE_WINDOW_PROPERTY_TYPE(views::DesktopWindowTreeHostX11*); |
| |
| namespace views { |
| |
| DesktopWindowTreeHostX11* DesktopWindowTreeHostX11::g_current_capture = |
| NULL; |
| std::list<XID>* DesktopWindowTreeHostX11::open_windows_ = NULL; |
| |
| DEFINE_WINDOW_PROPERTY_KEY( |
| aura::Window*, kViewsWindowForRootWindow, NULL); |
| |
| DEFINE_WINDOW_PROPERTY_KEY( |
| DesktopWindowTreeHostX11*, kHostForRootWindow, NULL); |
| |
| namespace { |
| |
| // Constants that are part of EWMH. |
| const int k_NET_WM_STATE_ADD = 1; |
| const int k_NET_WM_STATE_REMOVE = 0; |
| |
| // Special value of the _NET_WM_DESKTOP property which indicates that the window |
| // should appear on all desktops. |
| const int kAllDesktops = 0xFFFFFFFF; |
| |
| const char* kAtomsToCache[] = { |
| "UTF8_STRING", |
| "WM_DELETE_WINDOW", |
| "WM_PROTOCOLS", |
| "_NET_FRAME_EXTENTS", |
| "_NET_WM_CM_S0", |
| "_NET_WM_DESKTOP", |
| "_NET_WM_ICON", |
| "_NET_WM_NAME", |
| "_NET_WM_PID", |
| "_NET_WM_PING", |
| "_NET_WM_STATE", |
| "_NET_WM_STATE_ABOVE", |
| "_NET_WM_STATE_FULLSCREEN", |
| "_NET_WM_STATE_HIDDEN", |
| "_NET_WM_STATE_MAXIMIZED_HORZ", |
| "_NET_WM_STATE_MAXIMIZED_VERT", |
| "_NET_WM_STATE_SKIP_TASKBAR", |
| "_NET_WM_STATE_STICKY", |
| "_NET_WM_USER_TIME", |
| "_NET_WM_WINDOW_OPACITY", |
| "_NET_WM_WINDOW_TYPE", |
| "_NET_WM_WINDOW_TYPE_DND", |
| "_NET_WM_WINDOW_TYPE_MENU", |
| "_NET_WM_WINDOW_TYPE_NORMAL", |
| "_NET_WM_WINDOW_TYPE_NOTIFICATION", |
| "_NET_WM_WINDOW_TYPE_TOOLTIP", |
| "XdndActionAsk", |
| "XdndActionCopy" |
| "XdndActionLink", |
| "XdndActionList", |
| "XdndActionMove", |
| "XdndActionPrivate", |
| "XdndAware", |
| "XdndDrop", |
| "XdndEnter", |
| "XdndFinished", |
| "XdndLeave", |
| "XdndPosition", |
| "XdndProxy", // Proxy windows? |
| "XdndSelection", |
| "XdndStatus", |
| "XdndTypeList", |
| NULL |
| }; |
| |
| const char kX11WindowRolePopup[] = "popup"; |
| const char kX11WindowRoleBubble[] = "bubble"; |
| |
| // 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 |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DesktopWindowTreeHostX11, public: |
| |
| DesktopWindowTreeHostX11::DesktopWindowTreeHostX11( |
| internal::NativeWidgetDelegate* native_widget_delegate, |
| DesktopNativeWidgetAura* desktop_native_widget_aura) |
| : xdisplay_(gfx::GetXDisplay()), |
| xwindow_(0), |
| x_root_window_(DefaultRootWindow(xdisplay_)), |
| atom_cache_(xdisplay_, kAtomsToCache), |
| window_mapped_(false), |
| is_fullscreen_(false), |
| is_always_on_top_(false), |
| use_native_frame_(false), |
| should_maximize_after_map_(false), |
| use_argb_visual_(false), |
| drag_drop_client_(NULL), |
| native_widget_delegate_(native_widget_delegate), |
| desktop_native_widget_aura_(desktop_native_widget_aura), |
| content_window_(NULL), |
| window_parent_(NULL), |
| custom_window_shape_(false), |
| urgency_hint_set_(false), |
| activatable_(true), |
| modal_dialog_xid_(0), |
| close_widget_factory_(this) { |
| } |
| |
| DesktopWindowTreeHostX11::~DesktopWindowTreeHostX11() { |
| window()->ClearProperty(kHostForRootWindow); |
| aura::client::SetWindowMoveClient(window(), NULL); |
| desktop_native_widget_aura_->OnDesktopWindowTreeHostDestroyed(this); |
| DestroyDispatcher(); |
| } |
| |
| // static |
| aura::Window* DesktopWindowTreeHostX11::GetContentWindowForXID(XID xid) { |
| aura::WindowTreeHost* host = |
| aura::WindowTreeHost::GetForAcceleratedWidget(xid); |
| return host ? host->window()->GetProperty(kViewsWindowForRootWindow) : NULL; |
| } |
| |
| // static |
| DesktopWindowTreeHostX11* DesktopWindowTreeHostX11::GetHostForXID(XID xid) { |
| aura::WindowTreeHost* host = |
| aura::WindowTreeHost::GetForAcceleratedWidget(xid); |
| return host ? host->window()->GetProperty(kHostForRootWindow) : NULL; |
| } |
| |
| // static |
| std::vector<aura::Window*> DesktopWindowTreeHostX11::GetAllOpenWindows() { |
| std::vector<aura::Window*> windows(open_windows().size()); |
| std::transform(open_windows().begin(), |
| open_windows().end(), |
| windows.begin(), |
| GetContentWindowForXID); |
| return windows; |
| } |
| |
| gfx::Rect DesktopWindowTreeHostX11::GetX11RootWindowBounds() const { |
| return bounds_in_pixels_; |
| } |
| |
| gfx::Rect DesktopWindowTreeHostX11::GetX11RootWindowOuterBounds() const { |
| gfx::Rect outer_bounds(bounds_in_pixels_); |
| outer_bounds.Inset(-native_window_frame_borders_in_pixels_); |
| return outer_bounds; |
| } |
| |
| ::Region DesktopWindowTreeHostX11::GetWindowShape() const { |
| return window_shape_.get(); |
| } |
| |
| void DesktopWindowTreeHostX11::HandleNativeWidgetActivationChanged( |
| bool active) { |
| if (active) { |
| FlashFrame(false); |
| OnHostActivated(); |
| open_windows().remove(xwindow_); |
| open_windows().insert(open_windows().begin(), xwindow_); |
| } else { |
| ReleaseCapture(); |
| } |
| |
| desktop_native_widget_aura_->HandleActivationChanged(active); |
| |
| native_widget_delegate_->AsWidget()->GetRootView()->SchedulePaint(); |
| } |
| |
| void DesktopWindowTreeHostX11::AddObserver( |
| views::DesktopWindowTreeHostObserverX11* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void DesktopWindowTreeHostX11::RemoveObserver( |
| views::DesktopWindowTreeHostObserverX11* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| void DesktopWindowTreeHostX11::SwapNonClientEventHandler( |
| scoped_ptr<ui::EventHandler> handler) { |
| wm::CompoundEventFilter* compound_event_filter = |
| desktop_native_widget_aura_->root_window_event_filter(); |
| if (x11_non_client_event_filter_) |
| compound_event_filter->RemoveHandler(x11_non_client_event_filter_.get()); |
| compound_event_filter->AddHandler(handler.get()); |
| x11_non_client_event_filter_ = handler.Pass(); |
| } |
| |
| void DesktopWindowTreeHostX11::CleanUpWindowList( |
| void (*func)(aura::Window* window)) { |
| if (!open_windows_) |
| return; |
| while (!open_windows_->empty()) { |
| XID xid = open_windows_->front(); |
| func(GetContentWindowForXID(xid)); |
| if (!open_windows_->empty() && open_windows_->front() == xid) |
| open_windows_->erase(open_windows_->begin()); |
| } |
| |
| delete open_windows_; |
| open_windows_ = NULL; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DesktopWindowTreeHostX11, DesktopWindowTreeHost implementation: |
| |
| void DesktopWindowTreeHostX11::Init(aura::Window* content_window, |
| const Widget::InitParams& params) { |
| content_window_ = content_window; |
| activatable_ = (params.activatable == Widget::InitParams::ACTIVATABLE_YES); |
| |
| // TODO(erg): Check whether we *should* be building a WindowTreeHost here, or |
| // whether we should be proxying requests to another DRWHL. |
| |
| // In some situations, views tries to make a zero sized window, and that |
| // makes us crash. Make sure we have valid sizes. |
| Widget::InitParams sanitized_params = params; |
| if (sanitized_params.bounds.width() == 0) |
| sanitized_params.bounds.set_width(100); |
| if (sanitized_params.bounds.height() == 0) |
| sanitized_params.bounds.set_height(100); |
| |
| InitX11Window(sanitized_params); |
| } |
| |
| void DesktopWindowTreeHostX11::OnNativeWidgetCreated( |
| const Widget::InitParams& params) { |
| window()->SetProperty(kViewsWindowForRootWindow, content_window_); |
| window()->SetProperty(kHostForRootWindow, this); |
| |
| // Ensure that the X11DesktopHandler exists so that it dispatches activation |
| // messages to us. |
| X11DesktopHandler::get(); |
| |
| // TODO(erg): Unify this code once the other consumer goes away. |
| SwapNonClientEventHandler( |
| scoped_ptr<ui::EventHandler>(new X11WindowEventFilter(this)).Pass()); |
| SetUseNativeFrame(params.type == Widget::InitParams::TYPE_WINDOW && |
| !params.remove_standard_frame); |
| |
| x11_window_move_client_.reset(new X11DesktopWindowMoveClient); |
| aura::client::SetWindowMoveClient(window(), x11_window_move_client_.get()); |
| |
| SetWindowTransparency(); |
| |
| native_widget_delegate_->OnNativeWidgetCreated(true); |
| } |
| |
| scoped_ptr<corewm::Tooltip> DesktopWindowTreeHostX11::CreateTooltip() { |
| return make_scoped_ptr(new corewm::TooltipAura); |
| } |
| |
| scoped_ptr<aura::client::DragDropClient> |
| DesktopWindowTreeHostX11::CreateDragDropClient( |
| DesktopNativeCursorManager* cursor_manager) { |
| drag_drop_client_ = new DesktopDragDropClientAuraX11( |
| window(), cursor_manager, xdisplay_, xwindow_); |
| drag_drop_client_->Init(); |
| return make_scoped_ptr(drag_drop_client_); |
| } |
| |
| void DesktopWindowTreeHostX11::Close() { |
| // TODO(erg): Might need to do additional hiding tasks here. |
| delayed_resize_task_.Cancel(); |
| |
| if (!close_widget_factory_.HasWeakPtrs()) { |
| // And we delay the close so that if we are called from an ATL callback, |
| // we don't destroy the window before the callback returned (as the caller |
| // may delete ourselves on destroy and the ATL callback would still |
| // dereference us when the callback returns). |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&DesktopWindowTreeHostX11::CloseNow, |
| close_widget_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void DesktopWindowTreeHostX11::CloseNow() { |
| if (xwindow_ == None) |
| return; |
| |
| ReleaseCapture(); |
| native_widget_delegate_->OnNativeWidgetDestroying(); |
| |
| // If we have children, close them. Use a copy for iteration because they'll |
| // remove themselves. |
| std::set<DesktopWindowTreeHostX11*> window_children_copy = window_children_; |
| for (std::set<DesktopWindowTreeHostX11*>::iterator it = |
| window_children_copy.begin(); it != window_children_copy.end(); |
| ++it) { |
| (*it)->CloseNow(); |
| } |
| DCHECK(window_children_.empty()); |
| |
| // If we have a parent, remove ourselves from its children list. |
| if (window_parent_) { |
| window_parent_->window_children_.erase(this); |
| window_parent_ = NULL; |
| } |
| |
| // Remove the event listeners we've installed. We need to remove these |
| // because otherwise we get assert during ~WindowEventDispatcher(). |
| desktop_native_widget_aura_->root_window_event_filter()->RemoveHandler( |
| x11_non_client_event_filter_.get()); |
| x11_non_client_event_filter_.reset(); |
| |
| // Destroy the compositor before destroying the |xwindow_| since shutdown |
| // may try to swap, and the swap without a window causes an X error, which |
| // causes a crash with in-process renderer. |
| DestroyCompositor(); |
| |
| open_windows().remove(xwindow_); |
| // Actually free our native resources. |
| if (ui::PlatformEventSource::GetInstance()) |
| ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this); |
| XDestroyWindow(xdisplay_, xwindow_); |
| xwindow_ = None; |
| |
| desktop_native_widget_aura_->OnHostClosed(); |
| } |
| |
| aura::WindowTreeHost* DesktopWindowTreeHostX11::AsWindowTreeHost() { |
| return this; |
| } |
| |
| void DesktopWindowTreeHostX11::ShowWindowWithState( |
| ui::WindowShowState show_state) { |
| if (compositor()) |
| compositor()->SetVisible(true); |
| if (!window_mapped_) |
| MapWindow(show_state); |
| |
| switch (show_state) { |
| case ui::SHOW_STATE_MAXIMIZED: |
| Maximize(); |
| break; |
| case ui::SHOW_STATE_MINIMIZED: |
| Minimize(); |
| break; |
| case ui::SHOW_STATE_FULLSCREEN: |
| SetFullscreen(true); |
| break; |
| default: |
| break; |
| } |
| |
| // Makes the window activated by default if the state is not INACTIVE or |
| // MINIMIZED. |
| if (show_state != ui::SHOW_STATE_INACTIVE && |
| show_state != ui::SHOW_STATE_MINIMIZED && |
| activatable_) { |
| Activate(); |
| } |
| |
| native_widget_delegate_->AsWidget()->SetInitialFocus(show_state); |
| } |
| |
| void DesktopWindowTreeHostX11::ShowMaximizedWithBounds( |
| const gfx::Rect& restored_bounds) { |
| ShowWindowWithState(ui::SHOW_STATE_MAXIMIZED); |
| // Enforce |restored_bounds_in_pixels_| since calling Maximize() could have |
| // reset it. |
| restored_bounds_in_pixels_ = ToPixelRect(restored_bounds); |
| } |
| |
| bool DesktopWindowTreeHostX11::IsVisible() const { |
| return window_mapped_; |
| } |
| |
| void DesktopWindowTreeHostX11::SetSize(const gfx::Size& requested_size) { |
| gfx::Size size_in_pixels = ToPixelRect(gfx::Rect(requested_size)).size(); |
| size_in_pixels = AdjustSize(size_in_pixels); |
| bool size_changed = bounds_in_pixels_.size() != size_in_pixels; |
| XResizeWindow(xdisplay_, xwindow_, size_in_pixels.width(), |
| size_in_pixels.height()); |
| bounds_in_pixels_.set_size(size_in_pixels); |
| if (size_changed) { |
| OnHostResized(size_in_pixels); |
| ResetWindowRegion(); |
| } |
| } |
| |
| void DesktopWindowTreeHostX11::StackAbove(aura::Window* window) { |
| if (window && window->GetRootWindow()) { |
| ::Window window_below = window->GetHost()->GetAcceleratedWidget(); |
| // Find all parent windows up to the root. |
| std::vector<::Window> window_below_parents = |
| GetParentsList(xdisplay_, window_below); |
| 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 DesktopWindowTreeHostX11::StackAtTop() { |
| XRaiseWindow(xdisplay_, xwindow_); |
| } |
| |
| void DesktopWindowTreeHostX11::CenterWindow(const gfx::Size& size) { |
| gfx::Size size_in_pixels = ToPixelRect(gfx::Rect(size)).size(); |
| gfx::Rect parent_bounds_in_pixels = GetWorkAreaBoundsInPixels(); |
| |
| // If |window_|'s transient parent bounds are big enough to contain |size|, |
| // use them instead. |
| if (wm::GetTransientParent(content_window_)) { |
| gfx::Rect transient_parent_rect = |
| wm::GetTransientParent(content_window_)->GetBoundsInScreen(); |
| if (transient_parent_rect.height() >= size.height() && |
| transient_parent_rect.width() >= size.width()) { |
| parent_bounds_in_pixels = ToPixelRect(transient_parent_rect); |
| } |
| } |
| |
| gfx::Rect window_bounds_in_pixels( |
| parent_bounds_in_pixels.x() + |
| (parent_bounds_in_pixels.width() - size_in_pixels.width()) / 2, |
| parent_bounds_in_pixels.y() + |
| (parent_bounds_in_pixels.height() - size_in_pixels.height()) / 2, |
| size_in_pixels.width(), size_in_pixels.height()); |
| // Don't size the window bigger than the parent, otherwise the user may not be |
| // able to close or move it. |
| window_bounds_in_pixels.AdjustToFit(parent_bounds_in_pixels); |
| |
| SetBounds(window_bounds_in_pixels); |
| } |
| |
| void DesktopWindowTreeHostX11::GetWindowPlacement( |
| gfx::Rect* bounds, |
| ui::WindowShowState* show_state) const { |
| *bounds = GetRestoredBounds(); |
| |
| if (IsFullscreen()) { |
| *show_state = ui::SHOW_STATE_FULLSCREEN; |
| } else if (IsMinimized()) { |
| *show_state = ui::SHOW_STATE_MINIMIZED; |
| } else if (IsMaximized()) { |
| *show_state = ui::SHOW_STATE_MAXIMIZED; |
| } else if (!IsActive()) { |
| *show_state = ui::SHOW_STATE_INACTIVE; |
| } else { |
| *show_state = ui::SHOW_STATE_NORMAL; |
| } |
| } |
| |
| gfx::Rect DesktopWindowTreeHostX11::GetWindowBoundsInScreen() const { |
| return ToDIPRect(bounds_in_pixels_); |
| } |
| |
| gfx::Rect DesktopWindowTreeHostX11::GetClientAreaBoundsInScreen() const { |
| // TODO(erg): The NativeWidgetAura version returns |bounds_in_pixels_|, |
| // claiming it's needed for View::ConvertPointToScreen() to work correctly. |
| // DesktopWindowTreeHostWin::GetClientAreaBoundsInScreen() just asks windows |
| // what it thinks the client rect is. |
| // |
| // Attempts to calculate the rect by asking the NonClientFrameView what it |
| // thought its GetBoundsForClientView() were broke combobox drop down |
| // placement. |
| return GetWindowBoundsInScreen(); |
| } |
| |
| gfx::Rect DesktopWindowTreeHostX11::GetRestoredBounds() const { |
| // We can't reliably track the restored bounds of a window, but we can get |
| // the 90% case down. When *chrome* is the process that requests maximizing |
| // or restoring bounds, we can record the current bounds before we request |
| // maximization, and clear it when we detect a state change. |
| if (!restored_bounds_in_pixels_.IsEmpty()) |
| return ToDIPRect(restored_bounds_in_pixels_); |
| |
| return GetWindowBoundsInScreen(); |
| } |
| |
| gfx::Rect DesktopWindowTreeHostX11::GetWorkAreaBoundsInScreen() const { |
| return ToDIPRect(GetWorkAreaBoundsInPixels()); |
| } |
| |
| void DesktopWindowTreeHostX11::SetShape(SkRegion* native_region) { |
| custom_window_shape_ = false; |
| window_shape_.reset(); |
| |
| if (native_region) { |
| gfx::Transform transform = GetRootTransform(); |
| 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(transform.matrix(), &path_in_pixels); |
| window_shape_.reset(gfx::CreateRegionFromSkPath(path_in_pixels)); |
| } else { |
| window_shape_.reset(XCreateRegion()); |
| } |
| } else { |
| window_shape_.reset(gfx::CreateRegionFromSkRegion(*native_region)); |
| } |
| |
| custom_window_shape_ = true; |
| delete native_region; |
| } |
| ResetWindowRegion(); |
| } |
| |
| void DesktopWindowTreeHostX11::Activate() { |
| if (!window_mapped_) |
| return; |
| |
| X11DesktopHandler::get()->ActivateWindow(xwindow_); |
| } |
| |
| void DesktopWindowTreeHostX11::Deactivate() { |
| if (!IsActive()) |
| return; |
| |
| ReleaseCapture(); |
| X11DesktopHandler::get()->DeactivateWindow(xwindow_); |
| } |
| |
| bool DesktopWindowTreeHostX11::IsActive() const { |
| return X11DesktopHandler::get()->IsActiveWindow(xwindow_); |
| } |
| |
| void DesktopWindowTreeHostX11::Maximize() { |
| if (HasWMSpecProperty("_NET_WM_STATE_FULLSCREEN")) { |
| // Unfullscreen the window if it is fullscreen. |
| SetWMSpecState(false, |
| atom_cache_.GetAtom("_NET_WM_STATE_FULLSCREEN"), |
| None); |
| |
| // Resize the window so that it does not have the same size as a monitor. |
| // (Otherwise, some window managers immediately put the window back in |
| // fullscreen mode). |
| gfx::Rect adjusted_bounds_in_pixels(bounds_in_pixels_.origin(), |
| AdjustSize(bounds_in_pixels_.size())); |
| if (adjusted_bounds_in_pixels != bounds_in_pixels_) |
| SetBounds(adjusted_bounds_in_pixels); |
| } |
| |
| // Some WMs do not respect maximization hints on unmapped windows, so we |
| // save this one for later too. |
| should_maximize_after_map_ = !window_mapped_; |
| |
| // When we are in the process of requesting to maximize a window, we can |
| // accurately keep track of our restored bounds instead of relying on the |
| // heuristics that are in the PropertyNotify and ConfigureNotify handlers. |
| restored_bounds_in_pixels_ = bounds_in_pixels_; |
| |
| SetWMSpecState(true, |
| atom_cache_.GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), |
| atom_cache_.GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); |
| if (IsMinimized()) |
| ShowWindowWithState(ui::SHOW_STATE_NORMAL); |
| } |
| |
| void DesktopWindowTreeHostX11::Minimize() { |
| ReleaseCapture(); |
| XIconifyWindow(xdisplay_, xwindow_, 0); |
| } |
| |
| void DesktopWindowTreeHostX11::Restore() { |
| should_maximize_after_map_ = false; |
| SetWMSpecState(false, |
| atom_cache_.GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), |
| atom_cache_.GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); |
| if (IsMinimized()) |
| ShowWindowWithState(ui::SHOW_STATE_NORMAL); |
| } |
| |
| bool DesktopWindowTreeHostX11::IsMaximized() const { |
| return (HasWMSpecProperty("_NET_WM_STATE_MAXIMIZED_VERT") && |
| HasWMSpecProperty("_NET_WM_STATE_MAXIMIZED_HORZ")); |
| } |
| |
| bool DesktopWindowTreeHostX11::IsMinimized() const { |
| return HasWMSpecProperty("_NET_WM_STATE_HIDDEN"); |
| } |
| |
| bool DesktopWindowTreeHostX11::HasCapture() const { |
| return g_current_capture == this; |
| } |
| |
| void DesktopWindowTreeHostX11::SetAlwaysOnTop(bool always_on_top) { |
| is_always_on_top_ = always_on_top; |
| SetWMSpecState(always_on_top, |
| atom_cache_.GetAtom("_NET_WM_STATE_ABOVE"), |
| None); |
| } |
| |
| bool DesktopWindowTreeHostX11::IsAlwaysOnTop() const { |
| return is_always_on_top_; |
| } |
| |
| void DesktopWindowTreeHostX11::SetVisibleOnAllWorkspaces(bool always_visible) { |
| SetWMSpecState(always_visible, |
| atom_cache_.GetAtom("_NET_WM_STATE_STICKY"), |
| None); |
| |
| int new_desktop = 0; |
| if (always_visible) { |
| new_desktop = kAllDesktops; |
| } else { |
| if (!ui::GetCurrentDesktop(&new_desktop)) |
| return; |
| } |
| |
| XEvent xevent; |
| memset (&xevent, 0, sizeof (xevent)); |
| xevent.type = ClientMessage; |
| xevent.xclient.window = xwindow_; |
| xevent.xclient.message_type = atom_cache_.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_, False, |
| SubstructureRedirectMask | SubstructureNotifyMask, |
| &xevent); |
| } |
| |
| bool DesktopWindowTreeHostX11::SetWindowTitle(const base::string16& title) { |
| if (window_title_ == title) |
| return false; |
| window_title_ = title; |
| std::string utf8str = base::UTF16ToUTF8(title); |
| XChangeProperty(xdisplay_, |
| xwindow_, |
| atom_cache_.GetAtom("_NET_WM_NAME"), |
| atom_cache_.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) == Success) { |
| XSetWMName(xdisplay_, xwindow_, &xtp); |
| XFree(xtp.value); |
| } |
| return true; |
| } |
| |
| void DesktopWindowTreeHostX11::ClearNativeFocus() { |
| // This method is weird and misnamed. Instead of clearing the native focus, |
| // it sets the focus to our |content_window_|, which will trigger a cascade |
| // of focus changes into views. |
| if (content_window_ && aura::client::GetFocusClient(content_window_) && |
| content_window_->Contains( |
| aura::client::GetFocusClient(content_window_)->GetFocusedWindow())) { |
| aura::client::GetFocusClient(content_window_)->FocusWindow(content_window_); |
| } |
| } |
| |
| Widget::MoveLoopResult DesktopWindowTreeHostX11::RunMoveLoop( |
| const gfx::Vector2d& drag_offset, |
| Widget::MoveLoopSource source, |
| Widget::MoveLoopEscapeBehavior escape_behavior) { |
| aura::client::WindowMoveSource window_move_source = |
| source == Widget::MOVE_LOOP_SOURCE_MOUSE ? |
| aura::client::WINDOW_MOVE_SOURCE_MOUSE : |
| aura::client::WINDOW_MOVE_SOURCE_TOUCH; |
| if (x11_window_move_client_->RunMoveLoop(content_window_, drag_offset, |
| window_move_source) == aura::client::MOVE_SUCCESSFUL) |
| return Widget::MOVE_LOOP_SUCCESSFUL; |
| |
| return Widget::MOVE_LOOP_CANCELED; |
| } |
| |
| void DesktopWindowTreeHostX11::EndMoveLoop() { |
| x11_window_move_client_->EndMoveLoop(); |
| } |
| |
| void DesktopWindowTreeHostX11::SetVisibilityChangedAnimationsEnabled( |
| bool value) { |
| // Much like the previous NativeWidgetGtk, we don't have anything to do here. |
| } |
| |
| bool DesktopWindowTreeHostX11::ShouldUseNativeFrame() const { |
| return use_native_frame_; |
| } |
| |
| bool DesktopWindowTreeHostX11::ShouldWindowContentsBeTransparent() const { |
| return false; |
| } |
| |
| void DesktopWindowTreeHostX11::FrameTypeChanged() { |
| Widget::FrameType new_type = |
| native_widget_delegate_->AsWidget()->frame_type(); |
| if (new_type == Widget::FRAME_TYPE_DEFAULT) { |
| // The default is determined by Widget::InitParams::remove_standard_frame |
| // and does not change. |
| return; |
| } |
| |
| SetUseNativeFrame(new_type == Widget::FRAME_TYPE_FORCE_NATIVE); |
| // Replace the frame and layout the contents. Even though we don't have a |
| // swapable glass frame like on Windows, we still replace the frame because |
| // the button assets don't update otherwise. |
| native_widget_delegate_->AsWidget()->non_client_view()->UpdateFrame(); |
| } |
| |
| void DesktopWindowTreeHostX11::SetFullscreen(bool fullscreen) { |
| if (is_fullscreen_ == fullscreen) |
| return; |
| is_fullscreen_ = fullscreen; |
| if (is_fullscreen_) |
| delayed_resize_task_.Cancel(); |
| |
| // Work around a bug where if we try to unfullscreen, metacity immediately |
| // fullscreens us again. This is a little flickery and not necessary if |
| // there's a gnome-panel, but it's not easy to detect whether there's a |
| // panel or not. |
| bool unmaximize_and_remaximize = !fullscreen && IsMaximized() && |
| ui::GuessWindowManager() == ui::WM_METACITY; |
| |
| if (unmaximize_and_remaximize) |
| Restore(); |
| SetWMSpecState(fullscreen, |
| atom_cache_.GetAtom("_NET_WM_STATE_FULLSCREEN"), |
| None); |
| if (unmaximize_and_remaximize) |
| Maximize(); |
| |
| // Try to guess the size we will have after the switch to/from fullscreen: |
| // - (may) avoid transient states |
| // - works around Flash content which expects to have the size updated |
| // synchronously. |
| // See https://crbug.com/361408 |
| if (fullscreen) { |
| restored_bounds_in_pixels_ = bounds_in_pixels_; |
| const gfx::Display display = |
| gfx::Screen::GetScreenFor(NULL)->GetDisplayNearestWindow(window()); |
| bounds_in_pixels_ = ToPixelRect(display.bounds()); |
| } else { |
| bounds_in_pixels_ = restored_bounds_in_pixels_; |
| } |
| OnHostMoved(bounds_in_pixels_.origin()); |
| OnHostResized(bounds_in_pixels_.size()); |
| |
| if (HasWMSpecProperty("_NET_WM_STATE_FULLSCREEN") == fullscreen) { |
| Relayout(); |
| ResetWindowRegion(); |
| } |
| // Else: the widget will be relaid out either when the window bounds change or |
| // when |xwindow_|'s fullscreen state changes. |
| } |
| |
| bool DesktopWindowTreeHostX11::IsFullscreen() const { |
| return is_fullscreen_; |
| } |
| |
| void DesktopWindowTreeHostX11::SetOpacity(unsigned char 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. |
| unsigned long cardinality = opacity * 0x1010101; |
| |
| if (cardinality == 0xffffffff) { |
| XDeleteProperty(xdisplay_, xwindow_, |
| atom_cache_.GetAtom("_NET_WM_WINDOW_OPACITY")); |
| } else { |
| XChangeProperty(xdisplay_, xwindow_, |
| atom_cache_.GetAtom("_NET_WM_WINDOW_OPACITY"), |
| XA_CARDINAL, 32, |
| PropModeReplace, |
| reinterpret_cast<unsigned char*>(&cardinality), 1); |
| } |
| } |
| |
| void DesktopWindowTreeHostX11::SetWindowIcons( |
| 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.HasRepresentation(1.0f)) |
| SerializeImageRepresentation(window_icon.GetRepresentation(1.0f), &data); |
| |
| if (app_icon.HasRepresentation(1.0f)) |
| SerializeImageRepresentation(app_icon.GetRepresentation(1.0f), &data); |
| |
| if (!data.empty()) |
| ui::SetAtomArrayProperty(xwindow_, "_NET_WM_ICON", "CARDINAL", data); |
| } |
| |
| void DesktopWindowTreeHostX11::InitModalType(ui::ModalType modal_type) { |
| switch (modal_type) { |
| case ui::MODAL_TYPE_NONE: |
| break; |
| default: |
| // TODO(erg): Figure out under what situations |modal_type| isn't |
| // none. The comment in desktop_native_widget_aura.cc suggests that this |
| // is rare. |
| NOTIMPLEMENTED(); |
| } |
| } |
| |
| void DesktopWindowTreeHostX11::FlashFrame(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 DesktopWindowTreeHostX11::OnRootViewLayout() { |
| UpdateMinAndMaxSize(); |
| } |
| |
| void DesktopWindowTreeHostX11::OnNativeWidgetFocus() { |
| } |
| |
| void DesktopWindowTreeHostX11::OnNativeWidgetBlur() { |
| } |
| |
| bool DesktopWindowTreeHostX11::IsAnimatingClosed() const { |
| return false; |
| } |
| |
| bool DesktopWindowTreeHostX11::IsTranslucentWindowOpacitySupported() const { |
| return false; |
| } |
| |
| void DesktopWindowTreeHostX11::SizeConstraintsChanged() { |
| UpdateMinAndMaxSize(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DesktopWindowTreeHostX11, aura::WindowTreeHost implementation: |
| |
| gfx::Transform DesktopWindowTreeHostX11::GetRootTransform() const { |
| gfx::Display display = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay(); |
| if (window_mapped_) { |
| aura::Window* win = const_cast<aura::Window*>(window()); |
| display = gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(win); |
| } |
| |
| float scale = display.device_scale_factor(); |
| gfx::Transform transform; |
| transform.Scale(scale, scale); |
| return transform; |
| } |
| |
| ui::EventSource* DesktopWindowTreeHostX11::GetEventSource() { |
| return this; |
| } |
| |
| gfx::AcceleratedWidget DesktopWindowTreeHostX11::GetAcceleratedWidget() { |
| return xwindow_; |
| } |
| |
| void DesktopWindowTreeHostX11::ShowImpl() { |
| ShowWindowWithState(ui::SHOW_STATE_NORMAL); |
| native_widget_delegate_->OnNativeWidgetVisibilityChanged(true); |
| } |
| |
| void DesktopWindowTreeHostX11::HideImpl() { |
| if (window_mapped_) { |
| XWithdrawWindow(xdisplay_, xwindow_, 0); |
| window_mapped_ = false; |
| } |
| native_widget_delegate_->OnNativeWidgetVisibilityChanged(false); |
| } |
| |
| gfx::Rect DesktopWindowTreeHostX11::GetBounds() const { |
| return bounds_in_pixels_; |
| } |
| |
| void DesktopWindowTreeHostX11::SetBounds( |
| const gfx::Rect& requested_bounds_in_pixel) { |
| gfx::Rect bounds_in_pixels(requested_bounds_in_pixel.origin(), |
| AdjustSize(requested_bounds_in_pixel.size())); |
| 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; |
| |
| if (origin_changed) |
| native_widget_delegate_->AsWidget()->OnNativeWidgetMove(); |
| if (size_changed) { |
| OnHostResized(bounds_in_pixels.size()); |
| ResetWindowRegion(); |
| } |
| } |
| |
| gfx::Point DesktopWindowTreeHostX11::GetLocationOnNativeScreen() const { |
| return bounds_in_pixels_.origin(); |
| } |
| |
| void DesktopWindowTreeHostX11::SetCapture() { |
| if (HasCapture()) |
| return; |
| |
| // Grabbing the mouse is asynchronous. However, we synchronously start |
| // forwarding all mouse events received by Chrome to the |
| // aura::WindowEventDispatcher which has capture. This makes capture |
| // synchronous for all intents and purposes if either: |
| // - |g_current_capture|'s X window has capture. |
| // OR |
| // - The topmost window underneath the mouse is managed by Chrome. |
| DesktopWindowTreeHostX11* old_capturer = g_current_capture; |
| |
| // Update |g_current_capture| prior to calling OnHostLostWindowCapture() to |
| // avoid releasing pointer grab. |
| g_current_capture = this; |
| if (old_capturer) |
| old_capturer->OnHostLostWindowCapture(); |
| |
| GrabPointer(xwindow_, true, None); |
| } |
| |
| void DesktopWindowTreeHostX11::ReleaseCapture() { |
| if (g_current_capture == this) { |
| // Release mouse grab asynchronously. A window managed by Chrome is likely |
| // the topmost window underneath the mouse so the capture release being |
| // asynchronous is likely inconsequential. |
| g_current_capture = NULL; |
| UngrabPointer(); |
| |
| OnHostLostWindowCapture(); |
| } |
| } |
| |
| void DesktopWindowTreeHostX11::SetCursorNative(gfx::NativeCursor cursor) { |
| XDefineCursor(xdisplay_, xwindow_, cursor.platform()); |
| } |
| |
| void DesktopWindowTreeHostX11::MoveCursorToNative(const gfx::Point& location) { |
| XWarpPointer(xdisplay_, None, x_root_window_, 0, 0, 0, 0, |
| bounds_in_pixels_.x() + location.x(), |
| bounds_in_pixels_.y() + location.y()); |
| } |
| |
| void DesktopWindowTreeHostX11::OnCursorVisibilityChangedNative(bool show) { |
| // TODO(erg): Conditional on us enabling touch on desktop linux builds, do |
| // the same tap-to-click disabling here that chromeos does. |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DesktopWindowTreeHostX11, private: |
| |
| void DesktopWindowTreeHostX11::InitX11Window( |
| const Widget::InitParams& params) { |
| unsigned long attribute_mask = CWBackPixmap | CWBitGravity; |
| XSetWindowAttributes swa; |
| memset(&swa, 0, sizeof(swa)); |
| swa.background_pixmap = None; |
| swa.bit_gravity = NorthWestGravity; |
| |
| ::Atom window_type; |
| switch (params.type) { |
| case Widget::InitParams::TYPE_MENU: |
| swa.override_redirect = True; |
| window_type = atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE_MENU"); |
| break; |
| case Widget::InitParams::TYPE_TOOLTIP: |
| swa.override_redirect = True; |
| window_type = atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE_TOOLTIP"); |
| break; |
| case Widget::InitParams::TYPE_POPUP: |
| swa.override_redirect = True; |
| window_type = atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE_NOTIFICATION"); |
| break; |
| case Widget::InitParams::TYPE_DRAG: |
| swa.override_redirect = True; |
| window_type = atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE_DND"); |
| break; |
| default: |
| window_type = atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE_NORMAL"); |
| break; |
| } |
| if (swa.override_redirect) |
| attribute_mask |= CWOverrideRedirect; |
| |
| // Detect whether we're running inside a compositing manager. If so, try to |
| // use the ARGB visual. Otherwise, just use our parent's visual. |
| Visual* visual = CopyFromParent; |
| int depth = CopyFromParent; |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableTransparentVisuals) && |
| XGetSelectionOwner(xdisplay_, atom_cache_.GetAtom("_NET_WM_CM_S0")) != |
| None) { |
| Visual* rgba_visual = GetARGBVisual(); |
| if (rgba_visual) { |
| visual = rgba_visual; |
| depth = 32; |
| |
| attribute_mask |= CWColormap; |
| swa.colormap = XCreateColormap(xdisplay_, x_root_window_, visual, |
| AllocNone); |
| |
| // 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; |
| |
| use_argb_visual_ = true; |
| } |
| } |
| |
| bounds_in_pixels_ = ToPixelRect(params.bounds); |
| bounds_in_pixels_.set_size(AdjustSize(bounds_in_pixels_.size())); |
| 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); |
| if (ui::PlatformEventSource::GetInstance()) |
| ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); |
| open_windows().push_back(xwindow_); |
| |
| // 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; |
| XSelectInput(xdisplay_, 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. |
| ::Atom protocols[2]; |
| protocols[0] = atom_cache_.GetAtom("WM_DELETE_WINDOW"); |
| protocols[1] = atom_cache_.GetAtom("_NET_WM_PING"); |
| XSetWMProtocols(xdisplay_, xwindow_, protocols, 2); |
| |
| // We need a WM_CLIENT_MACHINE and WM_LOCALE_NAME value so we integrate with |
| // the desktop environment. |
| XSetWMProperties(xdisplay_, xwindow_, NULL, NULL, NULL, 0, NULL, NULL, NULL); |
| |
| // 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_, |
| atom_cache_.GetAtom("_NET_WM_PID"), |
| XA_CARDINAL, |
| 32, |
| PropModeReplace, |
| reinterpret_cast<unsigned char*>(&pid), 1); |
| |
| XChangeProperty(xdisplay_, |
| xwindow_, |
| atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE"), |
| XA_ATOM, |
| 32, |
| PropModeReplace, |
| reinterpret_cast<unsigned char*>(&window_type), 1); |
| |
| // List of window state properties (_NET_WM_STATE) to set, if any. |
| std::vector< ::Atom> state_atom_list; |
| |
| // Remove popup windows from taskbar unless overridden. |
| if ((params.type == Widget::InitParams::TYPE_POPUP || |
| params.type == Widget::InitParams::TYPE_BUBBLE) && |
| !params.force_show_in_taskbar) { |
| state_atom_list.push_back( |
| atom_cache_.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_ = params.keep_on_top; |
| if (is_always_on_top_) |
| state_atom_list.push_back(atom_cache_.GetAtom("_NET_WM_STATE_ABOVE")); |
| |
| if (params.visible_on_all_workspaces) { |
| state_atom_list.push_back(atom_cache_.GetAtom("_NET_WM_STATE_STICKY")); |
| ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", kAllDesktops); |
| } |
| |
| // Setting _NET_WM_STATE by sending a message to the root_window (with |
| // SetWMSpecState) has no effect here since the window has not yet been |
| // mapped. So we manually change the state. |
| if (!state_atom_list.empty()) { |
| ui::SetAtomArrayProperty(xwindow_, |
| "_NET_WM_STATE", |
| "ATOM", |
| state_atom_list); |
| } |
| |
| if (!params.wm_class_name.empty() || !params.wm_class_class.empty()) { |
| ui::SetWindowClassHint( |
| xdisplay_, xwindow_, params.wm_class_name, params.wm_class_class); |
| } |
| |
| const char* wm_role_name = NULL; |
| // If the widget isn't overriding the role, provide a default value for popup |
| // and bubble types. |
| if (!params.wm_role_name.empty()) { |
| wm_role_name = params.wm_role_name.c_str(); |
| } else { |
| switch (params.type) { |
| case Widget::InitParams::TYPE_POPUP: |
| wm_role_name = kX11WindowRolePopup; |
| break; |
| case Widget::InitParams::TYPE_BUBBLE: |
| wm_role_name = kX11WindowRoleBubble; |
| break; |
| default: |
| break; |
| } |
| } |
| if (wm_role_name) |
| ui::SetWindowRole(xdisplay_, xwindow_, std::string(wm_role_name)); |
| |
| if (params.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 we have a parent, record the parent/child relationship. We use this |
| // data during destruction to make sure that when we try to close a parent |
| // window, we also destroy all child windows. |
| if (params.parent && params.parent->GetHost()) { |
| XID parent_xid = |
| params.parent->GetHost()->GetAcceleratedWidget(); |
| window_parent_ = GetHostForXID(parent_xid); |
| DCHECK(window_parent_); |
| window_parent_->window_children_.insert(this); |
| } |
| |
| // If we have a delegate which is providing a default window icon, use that |
| // icon. |
| gfx::ImageSkia* window_icon = |
| ViewsDelegate::GetInstance() |
| ? ViewsDelegate::GetInstance()->GetDefaultWindowIcon() |
| : NULL; |
| if (window_icon) { |
| SetWindowIcons(gfx::ImageSkia(), *window_icon); |
| } |
| CreateCompositor(); |
| OnAcceleratedWidgetAvailable(); |
| } |
| |
| gfx::Size DesktopWindowTreeHostX11::AdjustSize( |
| const gfx::Size& requested_size_in_pixels) { |
| std::vector<gfx::Display> displays = |
| gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_NATIVE)->GetAllDisplays(); |
| // Compare against all monitor sizes. The window manager can move the window |
| // to whichever monitor it wants. |
| for (size_t i = 0; i < displays.size(); ++i) { |
| if (requested_size_in_pixels == displays[i].GetSizeInPixel()) { |
| return gfx::Size(requested_size_in_pixels.width() - 1, |
| requested_size_in_pixels.height() - 1); |
| } |
| } |
| |
| // Do not request a 0x0 window size. It causes an XError. |
| gfx::Size size_in_pixels = requested_size_in_pixels; |
| size_in_pixels.SetToMax(gfx::Size(1, 1)); |
| return size_in_pixels; |
| } |
| |
| void DesktopWindowTreeHostX11::OnWMStateUpdated() { |
| std::vector< ::Atom> atom_list; |
| // Ignore the return value of ui::GetAtomArrayProperty(). Fluxbox removes the |
| // _NET_WM_STATE property when no _NET_WM_STATE atoms are set. |
| ui::GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &atom_list); |
| |
| bool was_minimized = IsMinimized(); |
| |
| window_properties_.clear(); |
| std::copy(atom_list.begin(), atom_list.end(), |
| inserter(window_properties_, window_properties_.begin())); |
| |
| // Propagate the window minimization information to the content window, so |
| // the render side can update its visibility properly. OnWMStateUpdated() is |
| // called by PropertyNofify event from DispatchEvent() when the browser is |
| // minimized or shown from minimized state. On Windows, this is realized by |
| // calling OnHostResized() with an empty size. In particular, |
| // HWNDMessageHandler::GetClientAreaBounds() returns an empty size when the |
| // window is minimized. On Linux, returning empty size in GetBounds() or |
| // SetBounds() does not work. |
| // We also propagate the minimization to the compositor, to makes sure that we |
| // don't draw any 'blank' frames that could be noticed in applications such as |
| // window manager previews, which show content even when a window is |
| // minimized. |
| bool is_minimized = IsMinimized(); |
| if (is_minimized != was_minimized) { |
| if (is_minimized) { |
| compositor()->SetVisible(false); |
| content_window_->Hide(); |
| } else { |
| content_window_->Show(); |
| compositor()->SetVisible(true); |
| } |
| } |
| |
| if (restored_bounds_in_pixels_.IsEmpty()) { |
| DCHECK(!IsFullscreen()); |
| if (IsMaximized()) { |
| // The request that we become maximized originated from a different |
| // process. |bounds_in_pixels_| already contains our maximized bounds. Do |
| // a best effort attempt to get restored bounds by setting it to our |
| // previously set bounds (and if we get this wrong, we aren't any worse |
| // off since we'd otherwise be returning our maximized bounds). |
| restored_bounds_in_pixels_ = previous_bounds_in_pixels_; |
| } |
| } else if (!IsMaximized() && !IsFullscreen()) { |
| // If we have restored bounds, but WM_STATE no longer claims to be |
| // maximized or fullscreen, we should clear our restored bounds. |
| restored_bounds_in_pixels_ = gfx::Rect(); |
| } |
| |
| // 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("_NET_WM_STATE_ABOVE"); |
| |
| // Now that we have different window properties, we may need to relayout the |
| // window. (The windows code doesn't need this because their window change is |
| // synchronous.) |
| Relayout(); |
| ResetWindowRegion(); |
| } |
| |
| void DesktopWindowTreeHostX11::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 DesktopWindowTreeHostX11::UpdateMinAndMaxSize() { |
| if (!window_mapped_) |
| return; |
| |
| gfx::Size minimum_in_pixels = |
| ToPixelRect(gfx::Rect(native_widget_delegate_->GetMinimumSize())).size(); |
| gfx::Size maximum_in_pixels = |
| ToPixelRect(gfx::Rect(native_widget_delegate_->GetMaximumSize())).size(); |
| if (min_size_in_pixels_ == minimum_in_pixels && |
| max_size_in_pixels_ == maximum_in_pixels) |
| return; |
| |
| min_size_in_pixels_ = minimum_in_pixels; |
| max_size_in_pixels_ = maximum_in_pixels; |
| |
| XSizeHints hints; |
| long supplied_return; |
| XGetWMNormalHints(xdisplay_, xwindow_, &hints, &supplied_return); |
| |
| if (minimum_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 (maximum_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 DesktopWindowTreeHostX11::UpdateWMUserTime( |
| const ui::PlatformEvent& event) { |
| if (!IsActive()) |
| return; |
| |
| ui::EventType type = ui::EventTypeFromNative(event); |
| if (type == ui::ET_MOUSE_PRESSED || |
| type == ui::ET_KEY_PRESSED || |
| type == ui::ET_TOUCH_PRESSED) { |
| unsigned long wm_user_time_ms = static_cast<unsigned long>( |
| ui::EventTimeFromNative(event).InMilliseconds()); |
| XChangeProperty(xdisplay_, |
| xwindow_, |
| atom_cache_.GetAtom("_NET_WM_USER_TIME"), |
| XA_CARDINAL, |
| 32, |
| PropModeReplace, |
| reinterpret_cast<const unsigned char *>(&wm_user_time_ms), |
| 1); |
| X11DesktopHandler::get()->set_wm_user_time_ms(wm_user_time_ms); |
| } |
| } |
| |
| void DesktopWindowTreeHostX11::SetWMSpecState(bool enabled, |
| ::Atom state1, |
| ::Atom state2) { |
| XEvent xclient; |
| memset(&xclient, 0, sizeof(xclient)); |
| xclient.type = ClientMessage; |
| xclient.xclient.window = xwindow_; |
| xclient.xclient.message_type = atom_cache_.GetAtom("_NET_WM_STATE"); |
| xclient.xclient.format = 32; |
| xclient.xclient.data.l[0] = |
| enabled ? k_NET_WM_STATE_ADD : k_NET_WM_STATE_REMOVE; |
| xclient.xclient.data.l[1] = state1; |
| xclient.xclient.data.l[2] = state2; |
| xclient.xclient.data.l[3] = 1; |
| xclient.xclient.data.l[4] = 0; |
| |
| XSendEvent(xdisplay_, x_root_window_, False, |
| SubstructureRedirectMask | SubstructureNotifyMask, |
| &xclient); |
| } |
| |
| bool DesktopWindowTreeHostX11::HasWMSpecProperty(const char* property) const { |
| return window_properties_.find(atom_cache_.GetAtom(property)) != |
| window_properties_.end(); |
| } |
| |
| void DesktopWindowTreeHostX11::SetUseNativeFrame(bool use_native_frame) { |
| use_native_frame_ = use_native_frame; |
| ui::SetUseOSWindowFrame(xwindow_, use_native_frame); |
| ResetWindowRegion(); |
| } |
| |
| void DesktopWindowTreeHostX11::DispatchMouseEvent(ui::MouseEvent* event) { |
| // In Windows, the native events sent to chrome are separated into client |
| // and non-client versions of events, which we record on our LocatedEvent |
| // structures. On X11, we emulate the concept of non-client. Before we pass |
| // this event to the cross platform event handling framework, we need to |
| // make sure it is appropriately marked as non-client if it's in the non |
| // client area, or otherwise, we can get into a state where the a window is |
| // set as the |mouse_pressed_handler_| in window_event_dispatcher.cc |
| // despite the mouse button being released. |
| // |
| // We can't do this later in the dispatch process because we share that |
| // with ash, and ash gets confused about event IS_NON_CLIENT-ness on |
| // events, since ash doesn't expect this bit to be set, because it's never |
| // been set before. (This works on ash on Windows because none of the mouse |
| // events on the ash desktop are clicking in what Windows considers to be a |
| // non client area.) Likewise, we won't want to do the following in any |
| // WindowTreeHost that hosts ash. |
| if (content_window_ && content_window_->delegate()) { |
| int flags = event->flags(); |
| int hit_test_code = |
| content_window_->delegate()->GetNonClientComponent(event->location()); |
| if (hit_test_code != HTCLIENT && hit_test_code != HTNOWHERE) |
| flags |= ui::EF_IS_NON_CLIENT; |
| event->set_flags(flags); |
| } |
| |
| // While we unset the urgency hint when we gain focus, we also must remove it |
| // on mouse clicks because we can call FlashFrame() on an active window. |
| if (event->IsAnyButton() || event->IsMouseWheelEvent()) |
| FlashFrame(false); |
| |
| if (!g_current_capture || g_current_capture == this) { |
| SendEventToProcessor(event); |
| } else { |
| // Another DesktopWindowTreeHostX11 has installed itself as |
| // capture. Translate the event's location and dispatch to the other. |
| ConvertEventToDifferentHost(event, g_current_capture); |
| g_current_capture->SendEventToProcessor(event); |
| } |
| } |
| |
| void DesktopWindowTreeHostX11::DispatchTouchEvent(ui::TouchEvent* event) { |
| if (g_current_capture && g_current_capture != this && |
| event->type() == ui::ET_TOUCH_PRESSED) { |
| ConvertEventToDifferentHost(event, g_current_capture); |
| g_current_capture->SendEventToProcessor(event); |
| } else { |
| SendEventToProcessor(event); |
| } |
| } |
| |
| void DesktopWindowTreeHostX11::DispatchKeyEvent(ui::KeyEvent* event) { |
| if (native_widget_delegate_->AsWidget()->IsActive()) |
| GetInputMethod()->DispatchKeyEvent(event); |
| } |
| |
| void DesktopWindowTreeHostX11::ConvertEventToDifferentHost( |
| ui::LocatedEvent* located_event, |
| DesktopWindowTreeHostX11* host) { |
| DCHECK_NE(this, host); |
| const gfx::Display display_src = |
| gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(window()); |
| const gfx::Display display_dest = |
| gfx::Screen::GetNativeScreen()->GetDisplayNearestWindow(host->window()); |
| DCHECK_EQ(display_src.device_scale_factor(), |
| display_dest.device_scale_factor()); |
| gfx::Vector2d offset = GetLocationOnNativeScreen() - |
| host->GetLocationOnNativeScreen(); |
| gfx::PointF location_in_pixel_in_host = |
| located_event->location_f() + gfx::Vector2dF(offset); |
| located_event->set_location_f(location_in_pixel_in_host); |
| } |
| |
| void DesktopWindowTreeHostX11::ResetWindowRegion() { |
| // If a custom window shape was supplied then apply it. |
| if (custom_window_shape_) { |
| XShapeCombineRegion(xdisplay_, xwindow_, ShapeBounding, 0, 0, |
| window_shape_.get(), false); |
| return; |
| } |
| |
| window_shape_.reset(); |
| |
| if (!IsMaximized() && !IsFullscreen()) { |
| gfx::Path window_mask; |
| views::Widget* widget = native_widget_delegate_->AsWidget(); |
| if (widget->non_client_view()) { |
| // Some frame views define a custom (non-rectangular) window mask. If |
| // so, use it to define the window shape. If not, fall through. |
| widget->non_client_view()->GetWindowMask(bounds_in_pixels_.size(), |
| &window_mask); |
| if (window_mask.countPoints() > 0) { |
| window_shape_.reset(gfx::CreateRegionFromSkPath(window_mask)); |
| 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 (ShouldUseNativeFrame()) { |
| // 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, 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 DesktopWindowTreeHostX11::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.sk_bitmap(); |
| SkAutoLockPixels locker(bitmap); |
| |
| for (int y = 0; y < height; ++y) |
| for (int x = 0; x < width; ++x) |
| data->push_back(bitmap.getColor(x, y)); |
| } |
| |
| Visual* DesktopWindowTreeHostX11::GetARGBVisual() { |
| XVisualInfo visual_template; |
| visual_template.screen = 0; |
| |
| int visuals_len; |
| gfx::XScopedPtr<XVisualInfo[]> visual_list(XGetVisualInfo( |
| xdisplay_, VisualScreenMask, &visual_template, &visuals_len)); |
| for (int i = 0; i < visuals_len; ++i) { |
| // Why support only 8888 ARGB? Because it's all that GTK+ supports. In |
| // gdkvisual-x11.cc, they look for this specific visual and use it for all |
| // their alpha channel using needs. |
| // |
| // TODO(erg): While the following does find a valid visual, some GL drivers |
| // don't believe that this has an alpha channel. According to marcheu@, |
| // this should work on open source driver though. (It doesn't work with |
| // NVidia's binaries currently.) http://crbug.com/369209 |
| const XVisualInfo& info = visual_list[i]; |
| if (info.depth == 32 && info.visual->red_mask == 0xff0000 && |
| info.visual->green_mask == 0x00ff00 && |
| info.visual->blue_mask == 0x0000ff) { |
| return info.visual; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| std::list<XID>& DesktopWindowTreeHostX11::open_windows() { |
| if (!open_windows_) |
| open_windows_ = new std::list<XID>(); |
| return *open_windows_; |
| } |
| |
| void DesktopWindowTreeHostX11::MapWindow(ui::WindowShowState show_state) { |
| if (show_state != ui::SHOW_STATE_DEFAULT && |
| show_state != ui::SHOW_STATE_NORMAL && |
| show_state != ui::SHOW_STATE_INACTIVE && |
| show_state != ui::SHOW_STATE_MAXIMIZED) { |
| // It will behave like SHOW_STATE_NORMAL. |
| NOTIMPLEMENTED(); |
| } |
| |
| // Before we map the window, set size hints. Otherwise, some window managers |
| // will ignore toplevel XMoveWindow commands. |
| XSizeHints size_hints; |
| size_hints.flags = PPosition; |
| size_hints.x = bounds_in_pixels_.x(); |
| size_hints.y = bounds_in_pixels_.y(); |
| XSetWMNormalHints(xdisplay_, xwindow_, &size_hints); |
| |
| // If SHOW_STATE_INACTIVE, tell the window manager not to focus the window |
| // when mapping. This is done by setting the _NET_WM_USER_TIME to 0. See e.g. |
| // http://standards.freedesktop.org/wm-spec/latest/ar01s05.html |
| unsigned long wm_user_time_ms = (show_state == ui::SHOW_STATE_INACTIVE) ? |
| 0 : X11DesktopHandler::get()->wm_user_time_ms(); |
| if (show_state == ui::SHOW_STATE_INACTIVE || wm_user_time_ms != 0) { |
| XChangeProperty(xdisplay_, |
| xwindow_, |
| atom_cache_.GetAtom("_NET_WM_USER_TIME"), |
| XA_CARDINAL, |
| 32, |
| PropModeReplace, |
| reinterpret_cast<const unsigned char *>(&wm_user_time_ms), |
| 1); |
| } |
| |
| XMapWindow(xdisplay_, xwindow_); |
| |
| // We now block until our window is mapped. Some X11 APIs will crash and |
| // burn if passed |xwindow_| before the window is mapped, and XMapWindow is |
| // asynchronous. |
| if (ui::X11EventSource::GetInstance()) |
| ui::X11EventSource::GetInstance()->BlockUntilWindowMapped(xwindow_); |
| window_mapped_ = true; |
| |
| UpdateMinAndMaxSize(); |
| |
| // 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 DesktopWindowTreeHostX11::SetWindowTransparency() { |
| compositor()->SetHostHasTransparentBackground(use_argb_visual_); |
| window()->SetTransparent(use_argb_visual_); |
| content_window_->SetTransparent(use_argb_visual_); |
| } |
| |
| void DesktopWindowTreeHostX11::Relayout() { |
| Widget* widget = native_widget_delegate_->AsWidget(); |
| NonClientView* non_client_view = widget->non_client_view(); |
| // non_client_view may be NULL, especially during creation. |
| if (non_client_view) { |
| non_client_view->client_view()->InvalidateLayout(); |
| non_client_view->InvalidateLayout(); |
| } |
| widget->GetRootView()->Layout(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DesktopWindowTreeHostX11, ui::PlatformEventDispatcher implementation: |
| |
| bool DesktopWindowTreeHostX11::CanDispatchEvent( |
| const ui::PlatformEvent& event) { |
| return event->xany.window == xwindow_ || |
| (event->type == GenericEvent && |
| static_cast<XIDeviceEvent*>(event->xcookie.data)->event == xwindow_); |
| } |
| |
| uint32_t DesktopWindowTreeHostX11::DispatchEvent( |
| const ui::PlatformEvent& event) { |
| XEvent* xev = event; |
| |
| TRACE_EVENT1("views", "DesktopWindowTreeHostX11::Dispatch", |
| "event->type", event->type); |
| |
| UpdateWMUserTime(event); |
| |
| // May want to factor CheckXEventForConsistency(xev); into a common location |
| // since it is called here. |
| switch (xev->type) { |
| case EnterNotify: |
| case LeaveNotify: { |
| // Ignore EventNotify and LeaveNotify events from children of |xwindow_|. |
| // NativeViewGLSurfaceGLX adds a child to |xwindow_|. |
| // TODO(pkotwicz|tdanderson): Figure out whether the suppression is |
| // necessary. crbug.com/385716 |
| if (xev->xcrossing.detail == NotifyInferior) |
| break; |
| |
| ui::MouseEvent mouse_event(xev); |
| DispatchMouseEvent(&mouse_event); |
| break; |
| } |
| case Expose: { |
| gfx::Rect damage_rect_in_pixels(xev->xexpose.x, xev->xexpose.y, |
| xev->xexpose.width, xev->xexpose.height); |
| compositor()->ScheduleRedrawRect(damage_rect_in_pixels); |
| break; |
| } |
| case KeyPress: { |
| ui::KeyEvent keydown_event(xev); |
| DispatchKeyEvent(&keydown_event); |
| break; |
| } |
| case KeyRelease: { |
| // There is no way to deactivate a window in X11 so ignore input if |
| // window is supposed to be 'inactive'. See comments in |
| // X11DesktopHandler::DeactivateWindow() for more details. |
| if (!IsActive() && !HasCapture()) |
| break; |
| |
| ui::KeyEvent key_event(xev); |
| DispatchKeyEvent(&key_event); |
| break; |
| } |
| case ButtonPress: |
| case ButtonRelease: { |
| ui::EventType event_type = ui::EventTypeFromNative(xev); |
| switch (event_type) { |
| case ui::ET_MOUSEWHEEL: { |
| ui::MouseWheelEvent mouseev(xev); |
| DispatchMouseEvent(&mouseev); |
| break; |
| } |
| case ui::ET_MOUSE_PRESSED: |
| case ui::ET_MOUSE_RELEASED: { |
| ui::MouseEvent mouseev(xev); |
| DispatchMouseEvent(&mouseev); |
| break; |
| } |
| case ui::ET_UNKNOWN: |
| // No event is created for X11-release events for mouse-wheel buttons. |
| break; |
| default: |
| NOTREACHED() << event_type; |
| } |
| break; |
| } |
| case FocusOut: |
| if (xev->xfocus.mode != NotifyGrab) { |
| ReleaseCapture(); |
| OnHostLostWindowCapture(); |
| X11DesktopHandler::get()->ProcessXEvent(xev); |
| } else { |
| dispatcher()->OnHostLostMouseGrab(); |
| } |
| break; |
| case FocusIn: |
| X11DesktopHandler::get()->ProcessXEvent(xev); |
| break; |
| case ConfigureNotify: { |
| DCHECK_EQ(xwindow_, xev->xconfigure.window); |
| DCHECK_EQ(xwindow_, xev->xconfigure.event); |
| // 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 (origin_changed) |
| OnHostMoved(bounds_in_pixels_.origin()); |
| |
| if (size_changed) { |
| delayed_resize_task_.Reset(base::Bind( |
| &DesktopWindowTreeHostX11::DelayedResize, |
| close_widget_factory_.GetWeakPtr(), bounds_in_pixels.size())); |
| base::MessageLoop::current()->PostTask( |
| FROM_HERE, delayed_resize_task_.callback()); |
| } |
| break; |
| } |
| case GenericEvent: { |
| ui::TouchFactory* factory = ui::TouchFactory::GetInstance(); |
| if (!factory->ShouldProcessXI2Event(xev)) |
| break; |
| |
| ui::EventType type = ui::EventTypeFromNative(xev); |
| XEvent last_event; |
| int num_coalesced = 0; |
| |
| switch (type) { |
| case ui::ET_TOUCH_MOVED: |
| num_coalesced = ui::CoalescePendingMotionEvents(xev, &last_event); |
| if (num_coalesced > 0) |
| xev = &last_event; |
| // fallthrough |
| case ui::ET_TOUCH_PRESSED: |
| case ui::ET_TOUCH_RELEASED: { |
| ui::TouchEvent touchev(xev); |
| DispatchTouchEvent(&touchev); |
| break; |
| } |
| case ui::ET_MOUSE_MOVED: |
| case ui::ET_MOUSE_DRAGGED: |
| case ui::ET_MOUSE_PRESSED: |
| case ui::ET_MOUSE_RELEASED: |
| case ui::ET_MOUSE_ENTERED: |
| case ui::ET_MOUSE_EXITED: { |
| if (type == ui::ET_MOUSE_MOVED || type == ui::ET_MOUSE_DRAGGED) { |
| // If this is a motion event, we want to coalesce all pending motion |
| // events that are at the top of the queue. |
| num_coalesced = ui::CoalescePendingMotionEvents(xev, &last_event); |
| if (num_coalesced > 0) |
| xev = &last_event; |
| } |
| ui::MouseEvent mouseev(xev); |
| DispatchMouseEvent(&mouseev); |
| break; |
| } |
| case ui::ET_MOUSEWHEEL: { |
| ui::MouseWheelEvent mouseev(xev); |
| DispatchMouseEvent(&mouseev); |
| break; |
| } |
| case ui::ET_SCROLL_FLING_START: |
| case ui::ET_SCROLL_FLING_CANCEL: |
| case ui::ET_SCROLL: { |
| ui::ScrollEvent scrollev(xev); |
| SendEventToProcessor(&scrollev); |
| break; |
| } |
| case ui::ET_KEY_PRESSED: |
| case ui::ET_KEY_RELEASED: { |
| ui::KeyEvent key_event(xev); |
| DispatchKeyEvent(&key_event); |
| break; |
| } |
| case ui::ET_UNKNOWN: |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| // If we coalesced an event we need to free its cookie. |
| if (num_coalesced > 0) |
| XFreeEventData(xev->xgeneric.display, &last_event.xcookie); |
| break; |
| } |
| case MapNotify: { |
| FOR_EACH_OBSERVER(DesktopWindowTreeHostObserverX11, |
| observer_list_, |
| OnWindowMapped(xwindow_)); |
| break; |
| } |
| case UnmapNotify: { |
| FOR_EACH_OBSERVER(DesktopWindowTreeHostObserverX11, |
| observer_list_, |
| OnWindowUnmapped(xwindow_)); |
| break; |
| } |
| case ClientMessage: { |
| Atom message_type = xev->xclient.message_type; |
| if (message_type == atom_cache_.GetAtom("WM_PROTOCOLS")) { |
| Atom protocol = static_cast<Atom>(xev->xclient.data.l[0]); |
| if (protocol == atom_cache_.GetAtom("WM_DELETE_WINDOW")) { |
| // We have received a close message from the window manager. |
| OnHostCloseRequested(); |
| } else if (protocol == atom_cache_.GetAtom("_NET_WM_PING")) { |
| XEvent reply_event = *xev; |
| reply_event.xclient.window = x_root_window_; |
| |
| XSendEvent(xdisplay_, |
| reply_event.xclient.window, |
| False, |
| SubstructureRedirectMask | SubstructureNotifyMask, |
| &reply_event); |
| } |
| } else if (message_type == atom_cache_.GetAtom("XdndEnter")) { |
| drag_drop_client_->OnXdndEnter(xev->xclient); |
| } else if (message_type == atom_cache_.GetAtom("XdndLeave")) { |
| drag_drop_client_->OnXdndLeave(xev->xclient); |
| } else if (message_type == atom_cache_.GetAtom("XdndPosition")) { |
| drag_drop_client_->OnXdndPosition(xev->xclient); |
| } else if (message_type == atom_cache_.GetAtom("XdndStatus")) { |
| drag_drop_client_->OnXdndStatus(xev->xclient); |
| } else if (message_type == atom_cache_.GetAtom("XdndFinished")) { |
| drag_drop_client_->OnXdndFinished(xev->xclient); |
| } else if (message_type == atom_cache_.GetAtom("XdndDrop")) { |
| drag_drop_client_->OnXdndDrop(xev->xclient); |
| } |
| 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 MotionNotify: { |
| // Discard all but the most recent motion event that targets the same |
| // window with unchanged state. |
| XEvent last_event; |
| while (XPending(xev->xany.display)) { |
| XEvent next_event; |
| XPeekEvent(xev->xany.display, &next_event); |
| if (next_event.type == MotionNotify && |
| next_event.xmotion.window == xev->xmotion.window && |
| next_event.xmotion.subwindow == xev->xmotion.subwindow && |
| next_event.xmotion.state == xev->xmotion.state) { |
| XNextEvent(xev->xany.display, &last_event); |
| xev = &last_event; |
| } else { |
| break; |
| } |
| } |
| |
| ui::MouseEvent mouseev(xev); |
| DispatchMouseEvent(&mouseev); |
| break; |
| } |
| case PropertyNotify: { |
| ::Atom changed_atom = xev->xproperty.atom; |
| if (changed_atom == atom_cache_.GetAtom("_NET_WM_STATE")) |
| OnWMStateUpdated(); |
| else if (changed_atom == atom_cache_.GetAtom("_NET_FRAME_EXTENTS")) |
| OnFrameExtentsUpdated(); |
| break; |
| } |
| case SelectionNotify: { |
| drag_drop_client_->OnSelectionNotify(xev->xselection); |
| break; |
| } |
| } |
| return ui::POST_DISPATCH_STOP_PROPAGATION; |
| } |
| |
| void DesktopWindowTreeHostX11::DelayedResize(const gfx::Size& size_in_pixels) { |
| OnHostResized(size_in_pixels); |
| ResetWindowRegion(); |
| delayed_resize_task_.Cancel(); |
| } |
| |
| gfx::Rect DesktopWindowTreeHostX11::GetWorkAreaBoundsInPixels() const { |
| std::vector<int> value; |
| if (ui::GetIntArrayProperty(x_root_window_, "_NET_WORKAREA", &value) && |
| value.size() >= 4) { |
| return gfx::Rect(value[0], value[1], value[2], value[3]); |
| } |
| |
| // Fetch the geometry of the root window. |
| Window root; |
| int x, y; |
| unsigned int width, height; |
| unsigned int border_width, depth; |
| if (!XGetGeometry(xdisplay_, x_root_window_, &root, &x, &y, &width, &height, |
| &border_width, &depth)) { |
| NOTIMPLEMENTED(); |
| return gfx::Rect(0, 0, 10, 10); |
| } |
| |
| return gfx::Rect(x, y, width, height); |
| } |
| |
| gfx::Rect DesktopWindowTreeHostX11::ToDIPRect( |
| const gfx::Rect& rect_in_pixels) const { |
| gfx::RectF rect_in_dip = gfx::RectF(rect_in_pixels); |
| GetRootTransform().TransformRectReverse(&rect_in_dip); |
| return gfx::ToEnclosingRect(rect_in_dip); |
| } |
| |
| gfx::Rect DesktopWindowTreeHostX11::ToPixelRect( |
| const gfx::Rect& rect_in_dip) const { |
| gfx::RectF rect_in_pixels = gfx::RectF(rect_in_dip); |
| GetRootTransform().TransformRect(&rect_in_pixels); |
| return gfx::ToEnclosingRect(rect_in_pixels); |
| } |
| |
| const XID DesktopWindowTreeHostX11::GetModalDialog() { |
| return modal_dialog_xid_; |
| } |
| |
| void DesktopWindowTreeHostX11::DisableEventListening(XID dialog) { |
| DCHECK(dialog); |
| DCHECK(!modal_dialog_xid_); |
| modal_dialog_xid_ = dialog; |
| // ScopedWindowTargeter is used to temporarily replace the event-targeter |
| // with NullEventTargeter to make |dialog| modal. |
| targeter_for_modal_.reset(new aura::ScopedWindowTargeter(window(), |
| scoped_ptr<ui::EventTargeter>(new ui::NullEventTargeter))); |
| } |
| |
| void DesktopWindowTreeHostX11::EnableEventListening() { |
| DCHECK(modal_dialog_xid_); |
| modal_dialog_xid_ = 0; |
| targeter_for_modal_.reset(); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // DesktopWindowTreeHost, public: |
| |
| // static |
| DesktopWindowTreeHost* DesktopWindowTreeHost::Create( |
| internal::NativeWidgetDelegate* native_widget_delegate, |
| DesktopNativeWidgetAura* desktop_native_widget_aura) { |
| return new DesktopWindowTreeHostX11(native_widget_delegate, |
| desktop_native_widget_aura); |
| } |
| |
| // static |
| ui::NativeTheme* DesktopWindowTreeHost::GetNativeTheme(aura::Window* window) { |
| const views::LinuxUI* linux_ui = views::LinuxUI::instance(); |
| if (linux_ui) { |
| ui::NativeTheme* native_theme = linux_ui->GetNativeTheme(window); |
| if (native_theme) |
| return native_theme; |
| } |
| |
| return ui::NativeTheme::instance(); |
| } |
| |
| } // namespace views |