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_foreign_window_manager.h" 14 #include "ui/base/x/x11_menu_list.h" 15 #include "ui/base/x/x11_util.h" 16 #include "ui/events/platform/platform_event_source.h" 17 #include "ui/gfx/x/x11_error_tracker.h" 18 #include "ui/views/ime/input_method.h" 19 #include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h" 20 21 namespace { 22 23 const char* kAtomsToCache[] = { 24 "_NET_ACTIVE_WINDOW", 25 NULL 26 }; 27 28 // Our global instance. Deleted when our Env() is deleted. 29 views::X11DesktopHandler* g_handler = NULL; 30 31 } // namespace 32 33 namespace views { 34 35 // static 36 X11DesktopHandler* X11DesktopHandler::get() { 37 if (!g_handler) 38 g_handler = new X11DesktopHandler; 39 40 return g_handler; 41 } 42 43 X11DesktopHandler::X11DesktopHandler() 44 : xdisplay_(gfx::GetXDisplay()), 45 x_root_window_(DefaultRootWindow(xdisplay_)), 46 wm_user_time_ms_(0), 47 current_window_(None), 48 current_window_active_state_(NOT_ACTIVE), 49 atom_cache_(xdisplay_, kAtomsToCache), 50 wm_supports_active_window_(false) { 51 if (ui::PlatformEventSource::GetInstance()) 52 ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); 53 aura::Env::GetInstance()->AddObserver(this); 54 55 XWindowAttributes attr; 56 XGetWindowAttributes(xdisplay_, x_root_window_, &attr); 57 XSelectInput(xdisplay_, x_root_window_, 58 attr.your_event_mask | PropertyChangeMask | 59 StructureNotifyMask | SubstructureNotifyMask); 60 61 wm_supports_active_window_ = 62 ui::WmSupportsHint(atom_cache_.GetAtom("_NET_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 (current_window_ == window && 73 current_window_active_state_ == NOT_ACTIVE) { 74 // |window| is most likely still active wrt to the X server. Undo the 75 // changes made in DeactivateWindow(). 76 OnActiveWindowChanged(window, ACTIVE); 77 78 // Go through the regular activation path such that calling 79 // DeactivateWindow() and ActivateWindow() immediately afterwards results 80 // in an active X window. 81 } 82 83 XRaiseWindow(xdisplay_, window); 84 85 if (wm_supports_active_window_) { 86 DCHECK_EQ(gfx::GetXDisplay(), xdisplay_); 87 88 XEvent xclient; 89 memset(&xclient, 0, sizeof(xclient)); 90 xclient.type = ClientMessage; 91 xclient.xclient.window = window; 92 xclient.xclient.message_type = atom_cache_.GetAtom("_NET_ACTIVE_WINDOW"); 93 xclient.xclient.format = 32; 94 xclient.xclient.data.l[0] = 1; // Specified we are an app. 95 xclient.xclient.data.l[1] = wm_user_time_ms_; 96 xclient.xclient.data.l[2] = None; 97 xclient.xclient.data.l[3] = 0; 98 xclient.xclient.data.l[4] = 0; 99 100 XSendEvent(xdisplay_, x_root_window_, False, 101 SubstructureRedirectMask | SubstructureNotifyMask, 102 &xclient); 103 } else { 104 // XRaiseWindow will not give input focus to the window. We now need to ask 105 // the X server to do that. Note that the call will raise an X error if the 106 // window is not mapped. 107 XSetInputFocus(xdisplay_, window, RevertToParent, CurrentTime); 108 109 OnActiveWindowChanged(window, ACTIVE); 110 } 111 } 112 113 void X11DesktopHandler::DeactivateWindow(::Window window) { 114 if (!IsActiveWindow(window)) 115 return; 116 117 XLowerWindow(xdisplay_, window); 118 119 // Per ICCCM: http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7 120 // "Clients should not give up the input focus of their own volition. 121 // They should ignore input that they receive instead." 122 // 123 // There is nothing else that we can do. Pretend that we have been 124 // deactivated and ignore keyboard input in DesktopWindowTreeHostX11. 125 OnActiveWindowChanged(window, NOT_ACTIVE); 126 } 127 128 bool X11DesktopHandler::IsActiveWindow(::Window window) const { 129 return window == current_window_ && current_window_active_state_ == ACTIVE; 130 } 131 132 void X11DesktopHandler::ProcessXEvent(XEvent* event) { 133 switch (event->type) { 134 case FocusIn: 135 if (current_window_ != event->xfocus.window) 136 OnActiveWindowChanged(event->xfocus.window, ACTIVE); 137 break; 138 case FocusOut: 139 if (current_window_ == event->xfocus.window) 140 OnActiveWindowChanged(None, NOT_ACTIVE); 141 break; 142 default: 143 NOTREACHED(); 144 } 145 } 146 147 bool X11DesktopHandler::CanDispatchEvent(const ui::PlatformEvent& event) { 148 return event->type == CreateNotify || event->type == DestroyNotify || 149 (event->type == PropertyNotify && 150 event->xproperty.window == x_root_window_); 151 } 152 153 uint32_t X11DesktopHandler::DispatchEvent(const ui::PlatformEvent& event) { 154 switch (event->type) { 155 case PropertyNotify: { 156 // Check for a change to the active window. 157 CHECK_EQ(x_root_window_, event->xproperty.window); 158 ::Atom active_window_atom = atom_cache_.GetAtom("_NET_ACTIVE_WINDOW"); 159 if (event->xproperty.atom == active_window_atom) { 160 ::Window window; 161 if (ui::GetXIDProperty(x_root_window_, "_NET_ACTIVE_WINDOW", &window) && 162 window) { 163 OnActiveWindowChanged(window, ACTIVE); 164 } 165 } 166 break; 167 } 168 169 case CreateNotify: 170 OnWindowCreatedOrDestroyed(event->type, event->xcreatewindow.window); 171 break; 172 case DestroyNotify: 173 OnWindowCreatedOrDestroyed(event->type, event->xdestroywindow.window); 174 break; 175 default: 176 NOTREACHED(); 177 } 178 179 return ui::POST_DISPATCH_NONE; 180 } 181 182 void X11DesktopHandler::OnWindowInitialized(aura::Window* window) { 183 } 184 185 void X11DesktopHandler::OnWillDestroyEnv() { 186 g_handler = NULL; 187 delete this; 188 } 189 190 void X11DesktopHandler::OnActiveWindowChanged(::Window xid, 191 ActiveState active_state) { 192 if (current_window_ == xid && current_window_active_state_ == active_state) 193 return; 194 195 if (current_window_active_state_ == ACTIVE) { 196 DesktopWindowTreeHostX11* old_host = 197 views::DesktopWindowTreeHostX11::GetHostForXID(current_window_); 198 if (old_host) 199 old_host->HandleNativeWidgetActivationChanged(false); 200 } 201 202 // Update the current window ID to effectively change the active widget. 203 current_window_ = xid; 204 current_window_active_state_ = active_state; 205 206 if (active_state == ACTIVE) { 207 DesktopWindowTreeHostX11* new_host = 208 views::DesktopWindowTreeHostX11::GetHostForXID(xid); 209 if (new_host) 210 new_host->HandleNativeWidgetActivationChanged(true); 211 } 212 } 213 214 void X11DesktopHandler::OnWindowCreatedOrDestroyed(int event_type, 215 XID window) { 216 // Menus created by Chrome can be drag and drop targets. Since they are 217 // direct children of the screen root window and have override_redirect 218 // we cannot use regular _NET_CLIENT_LIST_STACKING property to find them 219 // and use a separate cache to keep track of them. 220 // TODO(varkha): Implement caching of all top level X windows and their 221 // coordinates and stacking order to eliminate repeated calls to the X server 222 // during mouse movement, drag and shaping events. 223 if (event_type == CreateNotify) { 224 // The window might be destroyed if the message pump did not get a chance to 225 // run but we can safely ignore the X error. 226 gfx::X11ErrorTracker error_tracker; 227 ui::XMenuList::GetInstance()->MaybeRegisterMenu(window); 228 } else { 229 ui::XMenuList::GetInstance()->MaybeUnregisterMenu(window); 230 } 231 232 if (event_type == DestroyNotify) { 233 // Notify the XForeignWindowManager that |window| has been destroyed. 234 ui::XForeignWindowManager::GetInstance()->OnWindowDestroyed(window); 235 } 236 } 237 238 } // namespace views 239