1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ui/views/widget/desktop_aura/x11_desktop_handler.h" 6 7 #include <X11/Xatom.h> 8 #include <X11/Xlib.h> 9 10 #include "base/message_loop/message_loop.h" 11 #include "ui/aura/env.h" 12 #include "ui/aura/window_event_dispatcher.h" 13 #include "ui/base/x/x11_menu_list.h" 14 #include "ui/base/x/x11_util.h" 15 #include "ui/events/platform/platform_event_source.h" 16 #include "ui/gfx/x/x11_error_tracker.h" 17 #include "ui/views/ime/input_method.h" 18 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h" 19 20 namespace { 21 22 const char* kAtomsToCache[] = { 23 "_NET_ACTIVE_WINDOW", 24 NULL 25 }; 26 27 // Our global instance. Deleted when our Env() is deleted. 28 views::X11DesktopHandler* g_handler = NULL; 29 30 } // namespace 31 32 namespace views { 33 34 // static 35 X11DesktopHandler* X11DesktopHandler::get() { 36 if (!g_handler) 37 g_handler = new X11DesktopHandler; 38 39 return g_handler; 40 } 41 42 X11DesktopHandler::X11DesktopHandler() 43 : xdisplay_(gfx::GetXDisplay()), 44 x_root_window_(DefaultRootWindow(xdisplay_)), 45 wm_user_time_ms_(0), 46 current_window_(None), 47 atom_cache_(xdisplay_, kAtomsToCache), 48 wm_supports_active_window_(false) { 49 if (ui::PlatformEventSource::GetInstance()) 50 ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); 51 aura::Env::GetInstance()->AddObserver(this); 52 53 XWindowAttributes attr; 54 XGetWindowAttributes(xdisplay_, x_root_window_, &attr); 55 XSelectInput(xdisplay_, x_root_window_, 56 attr.your_event_mask | PropertyChangeMask | 57 StructureNotifyMask | SubstructureNotifyMask); 58 59 ::Window active_window; 60 wm_supports_active_window_ = 61 ui::GetXIDProperty(x_root_window_, "_NET_ACTIVE_WINDOW", &active_window) && 62 active_window; 63 } 64 65 X11DesktopHandler::~X11DesktopHandler() { 66 aura::Env::GetInstance()->RemoveObserver(this); 67 if (ui::PlatformEventSource::GetInstance()) 68 ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this); 69 } 70 71 void X11DesktopHandler::ActivateWindow(::Window window) { 72 if (wm_supports_active_window_) { 73 DCHECK_EQ(gfx::GetXDisplay(), xdisplay_); 74 75 XEvent xclient; 76 memset(&xclient, 0, sizeof(xclient)); 77 xclient.type = ClientMessage; 78 xclient.xclient.window = window; 79 xclient.xclient.message_type = atom_cache_.GetAtom("_NET_ACTIVE_WINDOW"); 80 xclient.xclient.format = 32; 81 xclient.xclient.data.l[0] = 1; // Specified we are an app. 82 xclient.xclient.data.l[1] = wm_user_time_ms_; 83 xclient.xclient.data.l[2] = None; 84 xclient.xclient.data.l[3] = 0; 85 xclient.xclient.data.l[4] = 0; 86 87 XSendEvent(xdisplay_, x_root_window_, False, 88 SubstructureRedirectMask | SubstructureNotifyMask, 89 &xclient); 90 } else { 91 XRaiseWindow(xdisplay_, window); 92 93 // XRaiseWindow will not give input focus to the window. We now need to ask 94 // the X server to do that. Note that the call will raise an X error if the 95 // window is not mapped. 96 XSetInputFocus(xdisplay_, window, RevertToParent, CurrentTime); 97 98 OnActiveWindowChanged(window); 99 } 100 } 101 102 bool X11DesktopHandler::IsActiveWindow(::Window window) const { 103 return window == current_window_; 104 } 105 106 void X11DesktopHandler::ProcessXEvent(XEvent* event) { 107 switch (event->type) { 108 case FocusIn: 109 if (current_window_ != event->xfocus.window) 110 OnActiveWindowChanged(event->xfocus.window); 111 break; 112 case FocusOut: 113 if (current_window_ == event->xfocus.window) 114 OnActiveWindowChanged(None); 115 break; 116 default: 117 NOTREACHED(); 118 } 119 } 120 121 bool X11DesktopHandler::CanDispatchEvent(const ui::PlatformEvent& event) { 122 return event->type == CreateNotify || event->type == DestroyNotify || 123 (event->type == PropertyNotify && 124 event->xproperty.window == x_root_window_); 125 } 126 127 uint32_t X11DesktopHandler::DispatchEvent(const ui::PlatformEvent& event) { 128 switch (event->type) { 129 case PropertyNotify: { 130 // Check for a change to the active window. 131 CHECK_EQ(x_root_window_, event->xproperty.window); 132 ::Atom active_window_atom = atom_cache_.GetAtom("_NET_ACTIVE_WINDOW"); 133 if (event->xproperty.atom == active_window_atom) { 134 ::Window window; 135 if (ui::GetXIDProperty(x_root_window_, "_NET_ACTIVE_WINDOW", &window) && 136 window) { 137 OnActiveWindowChanged(window); 138 } 139 } 140 break; 141 } 142 143 // Menus created by Chrome can be drag and drop targets. Since they are 144 // direct children of the screen root window and have override_redirect 145 // we cannot use regular _NET_CLIENT_LIST_STACKING property to find them 146 // and use a separate cache to keep track of them. 147 // TODO(varkha): Implement caching of all top level X windows and their 148 // coordinates and stacking order to eliminate repeated calls to X server 149 // during mouse movement, drag and shaping events. 150 case CreateNotify: { 151 // The window might be destroyed if the message pump haven't gotten a 152 // chance to run but we can safely ignore the X error. 153 gfx::X11ErrorTracker error_tracker; 154 XCreateWindowEvent *xcwe = &event->xcreatewindow; 155 ui::XMenuList::GetInstance()->MaybeRegisterMenu(xcwe->window); 156 break; 157 } 158 case DestroyNotify: { 159 XDestroyWindowEvent *xdwe = &event->xdestroywindow; 160 ui::XMenuList::GetInstance()->MaybeUnregisterMenu(xdwe->window); 161 break; 162 } 163 default: 164 NOTREACHED(); 165 } 166 167 return ui::POST_DISPATCH_NONE; 168 } 169 170 void X11DesktopHandler::OnWindowInitialized(aura::Window* window) { 171 } 172 173 void X11DesktopHandler::OnWillDestroyEnv() { 174 g_handler = NULL; 175 delete this; 176 } 177 178 void X11DesktopHandler::OnActiveWindowChanged(::Window xid) { 179 if (current_window_ == xid) 180 return; 181 DesktopWindowTreeHostX11* old_host = 182 views::DesktopWindowTreeHostX11::GetHostForXID(current_window_); 183 if (old_host) 184 old_host->HandleNativeWidgetActivationChanged(false); 185 186 // Update the current window ID to effectively change the active widget. 187 current_window_ = xid; 188 189 DesktopWindowTreeHostX11* new_host = 190 views::DesktopWindowTreeHostX11::GetHostForXID(xid); 191 if (new_host) 192 new_host->HandleNativeWidgetActivationChanged(true); 193 } 194 195 } // namespace views 196