| OLD | NEW |
| (Empty) |
| 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 "chrome/browser/chromeos/login/lock_window_gtk.h" | |
| 6 | |
| 7 #include <X11/extensions/XTest.h> | |
| 8 #include <X11/keysym.h> | |
| 9 #include <gdk/gdkkeysyms.h> | |
| 10 #include <gdk/gdkx.h> | |
| 11 | |
| 12 // Evil hack to undo X11 evil #define. | |
| 13 #undef None | |
| 14 #undef Status | |
| 15 | |
| 16 #include "base/command_line.h" | |
| 17 #include "base/utf_string_conversions.h" | |
| 18 #include "base/values.h" | |
| 19 #include "chrome/browser/chromeos/cros/cros_library.h" | |
| 20 #include "chrome/browser/chromeos/cros/network_library.h" | |
| 21 #include "chrome/browser/chromeos/legacy_window_manager/wm_ipc.h" | |
| 22 #include "chrome/browser/chromeos/login/helper.h" | |
| 23 #include "chrome/browser/chromeos/login/screen_locker.h" | |
| 24 #include "chrome/browser/chromeos/login/user_manager.h" | |
| 25 #include "chrome/browser/chromeos/login/webui_login_display.h" | |
| 26 #include "chrome/browser/ui/views/dom_view.h" | |
| 27 #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h" | |
| 28 #include "chrome/common/chrome_notification_types.h" | |
| 29 #include "chrome/common/url_constants.h" | |
| 30 #include "content/public/browser/notification_service.h" | |
| 31 #include "content/public/browser/notification_types.h" | |
| 32 #include "content/public/browser/render_widget_host_view.h" | |
| 33 #include "content/public/browser/web_contents.h" | |
| 34 #include "ui/base/l10n/l10n_util.h" | |
| 35 #include "ui/base/x/x11_util.h" | |
| 36 #include "ui/gfx/screen.h" | |
| 37 #include "ui/views/widget/native_widget_gtk.h" | |
| 38 #include "ui/views/widget/widget.h" | |
| 39 | |
| 40 namespace { | |
| 41 | |
| 42 // The maximum duration for which locker should try to grab the keyboard and | |
| 43 // mouse and its interval for regrabbing on failure. | |
| 44 const int kMaxGrabFailureSec = 30; | |
| 45 const int64 kRetryGrabIntervalMs = 500; | |
| 46 | |
| 47 // Maximum number of times we'll try to grab the keyboard and mouse before | |
| 48 // giving up. If we hit the limit, Chrome exits and the session is terminated. | |
| 49 const int kMaxGrabFailures = kMaxGrabFailureSec * 1000 / kRetryGrabIntervalMs; | |
| 50 | |
| 51 // Define separate methods for each error code so that stack trace | |
| 52 // will tell which error the grab failed with. | |
| 53 void FailedWithGrabAlreadyGrabbed() { | |
| 54 LOG(FATAL) << "Grab already grabbed"; | |
| 55 } | |
| 56 void FailedWithGrabInvalidTime() { | |
| 57 LOG(FATAL) << "Grab invalid time"; | |
| 58 } | |
| 59 void FailedWithGrabNotViewable() { | |
| 60 LOG(FATAL) << "Grab not viewable"; | |
| 61 } | |
| 62 void FailedWithGrabFrozen() { | |
| 63 LOG(FATAL) << "Grab frozen"; | |
| 64 } | |
| 65 void FailedWithUnknownError() { | |
| 66 LOG(FATAL) << "Grab uknown"; | |
| 67 } | |
| 68 | |
| 69 } // namespace | |
| 70 | |
| 71 namespace chromeos { | |
| 72 | |
| 73 LockWindow* LockWindow::Create() { | |
| 74 return new LockWindowGtk(); | |
| 75 } | |
| 76 | |
| 77 //////////////////////////////////////////////////////////////////////////////// | |
| 78 // LockWindowGtk implementation. | |
| 79 | |
| 80 void LockWindowGtk::Grab(DOMView* dom_view) { | |
| 81 // Grab on the RenderWidgetHostView hosting the WebUI login screen. | |
| 82 grab_widget_ = dom_view->dom_contents()->web_contents()-> | |
| 83 GetRenderWidgetHostView()->GetNativeView(); | |
| 84 ClearGtkGrab(); | |
| 85 | |
| 86 // Call this after lock_window_->Show(); otherwise the 1st invocation | |
| 87 // of gdk_xxx_grab() will always fail. | |
| 88 TryGrabAllInputs(); | |
| 89 | |
| 90 // Add the window to its own group so that its grab won't be stolen if | |
| 91 // gtk_grab_add() gets called on behalf on a non-screen-locker widget (e.g. | |
| 92 // a modal dialog) -- see http://crosbug.com/8999. We intentionally do this | |
| 93 // after calling ClearGtkGrab(), as want to be in the default window group | |
| 94 // then so we can break any existing GTK grabs. | |
| 95 GtkWindowGroup* window_group = gtk_window_group_new(); | |
| 96 gtk_window_group_add_window(window_group, | |
| 97 GTK_WINDOW(lock_window_->GetNativeView())); | |
| 98 g_object_unref(window_group); | |
| 99 } | |
| 100 | |
| 101 views::Widget* LockWindowGtk::GetWidget() { | |
| 102 return views::NativeWidgetGtk::GetWidget(); | |
| 103 } | |
| 104 | |
| 105 gboolean LockWindowGtk::OnButtonPress(GtkWidget* widget, | |
| 106 GdkEventButton* event) { | |
| 107 // Never propagate mouse events to parent. | |
| 108 return true; | |
| 109 } | |
| 110 | |
| 111 void LockWindowGtk::OnDestroy(GtkWidget* object) { | |
| 112 VLOG(1) << "OnDestroy: LockWindow destroyed"; | |
| 113 views::NativeWidgetGtk::OnDestroy(object); | |
| 114 } | |
| 115 | |
| 116 void LockWindowGtk::ClearNativeFocus() { | |
| 117 gtk_widget_grab_focus(window_contents()); | |
| 118 } | |
| 119 | |
| 120 //////////////////////////////////////////////////////////////////////////////// | |
| 121 // LockWindowGtk private: | |
| 122 | |
| 123 LockWindowGtk::LockWindowGtk() | |
| 124 : views::NativeWidgetGtk(new views::Widget), | |
| 125 lock_window_(NULL), | |
| 126 grab_widget_(NULL), | |
| 127 drawn_(false), | |
| 128 input_grabbed_(false), | |
| 129 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), | |
| 130 grab_failure_count_(0), | |
| 131 kbd_grab_status_(GDK_GRAB_INVALID_TIME), | |
| 132 mouse_grab_status_(GDK_GRAB_INVALID_TIME) { | |
| 133 Init(); | |
| 134 } | |
| 135 | |
| 136 LockWindowGtk::~LockWindowGtk() { | |
| 137 } | |
| 138 | |
| 139 void LockWindowGtk::Init() { | |
| 140 static const GdkColor kGdkBlack = {0, 0, 0, 0}; | |
| 141 EnableDoubleBuffer(true); | |
| 142 | |
| 143 gfx::Rect bounds(gfx::Screen::GetMonitorAreaNearestWindow(NULL)); | |
| 144 | |
| 145 lock_window_ = GetWidget(); | |
| 146 views::Widget::InitParams params( | |
| 147 views::Widget::InitParams::TYPE_WINDOW_FRAMELESS); | |
| 148 params.bounds = bounds; | |
| 149 params.native_widget = this; | |
| 150 lock_window_->Init(params); | |
| 151 gtk_widget_modify_bg( | |
| 152 lock_window_->GetNativeView(), GTK_STATE_NORMAL, &kGdkBlack); | |
| 153 | |
| 154 g_signal_connect(lock_window_->GetNativeView(), "client-event", | |
| 155 G_CALLBACK(OnClientEventThunk), this); | |
| 156 | |
| 157 DCHECK(GTK_WIDGET_REALIZED(lock_window_->GetNativeView())); | |
| 158 WmIpc::instance()->SetWindowType( | |
| 159 lock_window_->GetNativeView(), | |
| 160 WM_IPC_WINDOW_CHROME_SCREEN_LOCKER, | |
| 161 NULL); | |
| 162 } | |
| 163 | |
| 164 void LockWindowGtk::OnGrabInputs() { | |
| 165 DVLOG(1) << "OnGrabInputs"; | |
| 166 input_grabbed_ = true; | |
| 167 if (drawn_ && observer_) | |
| 168 observer_->OnLockWindowReady(); | |
| 169 } | |
| 170 | |
| 171 void LockWindowGtk::OnWindowManagerReady() { | |
| 172 DVLOG(1) << "OnClientEvent: drawn for lock"; | |
| 173 drawn_ = true; | |
| 174 if (input_grabbed_ && observer_) | |
| 175 observer_->OnLockWindowReady(); | |
| 176 } | |
| 177 | |
| 178 void LockWindowGtk::ClearGtkGrab() { | |
| 179 GtkWidget* current_grab_window; | |
| 180 // Grab gtk input first so that the menu holding gtk grab will | |
| 181 // close itself. | |
| 182 gtk_grab_add(grab_widget_); | |
| 183 | |
| 184 // Make sure there is no gtk grab widget so that gtk simply propagates | |
| 185 // an event. GTK maintains grab widgets in a linked-list, so we need to | |
| 186 // remove until it's empty. | |
| 187 while ((current_grab_window = gtk_grab_get_current()) != NULL) | |
| 188 gtk_grab_remove(current_grab_window); | |
| 189 } | |
| 190 | |
| 191 void LockWindowGtk::TryGrabAllInputs() { | |
| 192 // Grab x server so that we can atomically grab and take | |
| 193 // action when grab fails. | |
| 194 gdk_x11_grab_server(); | |
| 195 gtk_grab_add(grab_widget_); | |
| 196 if (kbd_grab_status_ != GDK_GRAB_SUCCESS) | |
| 197 kbd_grab_status_ = gdk_keyboard_grab(grab_widget_->window, | |
| 198 FALSE, | |
| 199 GDK_CURRENT_TIME); | |
| 200 if (mouse_grab_status_ != GDK_GRAB_SUCCESS) { | |
| 201 mouse_grab_status_ = | |
| 202 gdk_pointer_grab(grab_widget_->window, | |
| 203 FALSE, | |
| 204 static_cast<GdkEventMask>( | |
| 205 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | | |
| 206 GDK_POINTER_MOTION_MASK), | |
| 207 NULL, | |
| 208 NULL, | |
| 209 GDK_CURRENT_TIME); | |
| 210 } | |
| 211 if ((kbd_grab_status_ != GDK_GRAB_SUCCESS || | |
| 212 mouse_grab_status_ != GDK_GRAB_SUCCESS) && | |
| 213 grab_failure_count_++ < kMaxGrabFailures) { | |
| 214 LOG(WARNING) << "Failed to grab inputs. Trying again in " | |
| 215 << kRetryGrabIntervalMs << " ms: kbd=" | |
| 216 << kbd_grab_status_ << ", mouse=" << mouse_grab_status_; | |
| 217 TryUngrabOtherClients(); | |
| 218 gdk_x11_ungrab_server(); | |
| 219 MessageLoop::current()->PostDelayedTask( | |
| 220 FROM_HERE, | |
| 221 base::Bind(&LockWindowGtk::TryGrabAllInputs, | |
| 222 weak_factory_.GetWeakPtr()), | |
| 223 kRetryGrabIntervalMs); | |
| 224 } else { | |
| 225 gdk_x11_ungrab_server(); | |
| 226 GdkGrabStatus status = kbd_grab_status_; | |
| 227 if (status == GDK_GRAB_SUCCESS) { | |
| 228 status = mouse_grab_status_; | |
| 229 } | |
| 230 switch (status) { | |
| 231 case GDK_GRAB_SUCCESS: | |
| 232 break; | |
| 233 case GDK_GRAB_ALREADY_GRABBED: | |
| 234 FailedWithGrabAlreadyGrabbed(); | |
| 235 break; | |
| 236 case GDK_GRAB_INVALID_TIME: | |
| 237 FailedWithGrabInvalidTime(); | |
| 238 break; | |
| 239 case GDK_GRAB_NOT_VIEWABLE: | |
| 240 FailedWithGrabNotViewable(); | |
| 241 break; | |
| 242 case GDK_GRAB_FROZEN: | |
| 243 FailedWithGrabFrozen(); | |
| 244 break; | |
| 245 default: | |
| 246 FailedWithUnknownError(); | |
| 247 break; | |
| 248 } | |
| 249 DVLOG(1) << "Grab Success"; | |
| 250 OnGrabInputs(); | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 void LockWindowGtk::TryUngrabOtherClients() { | |
| 255 #if !defined(NDEBUG) | |
| 256 { | |
| 257 int event_base, error_base; | |
| 258 int major, minor; | |
| 259 // Make sure we have XTest extension. | |
| 260 DCHECK(XTestQueryExtension(ui::GetXDisplay(), | |
| 261 &event_base, &error_base, | |
| 262 &major, &minor)); | |
| 263 } | |
| 264 #endif | |
| 265 | |
| 266 // The following code is an attempt to grab inputs by closing | |
| 267 // supposedly opened menu. This happens when a plugin has a menu | |
| 268 // opened. | |
| 269 if (mouse_grab_status_ == GDK_GRAB_ALREADY_GRABBED || | |
| 270 mouse_grab_status_ == GDK_GRAB_FROZEN) { | |
| 271 // Successfully grabbed the keyboard, but pointer is still | |
| 272 // grabbed by other client. Another attempt to close supposedly | |
| 273 // opened menu by emulating keypress at the left top corner. | |
| 274 Display* display = ui::GetXDisplay(); | |
| 275 Window root, child; | |
| 276 int root_x, root_y, win_x, winy; | |
| 277 unsigned int mask; | |
| 278 XQueryPointer(display, | |
| 279 ui::GetX11WindowFromGtkWidget( | |
| 280 window_contents()), | |
| 281 &root, &child, &root_x, &root_y, | |
| 282 &win_x, &winy, &mask); | |
| 283 XTestFakeMotionEvent(display, -1, -10000, -10000, CurrentTime); | |
| 284 XTestFakeButtonEvent(display, 1, True, CurrentTime); | |
| 285 XTestFakeButtonEvent(display, 1, False, CurrentTime); | |
| 286 // Move the pointer back. | |
| 287 XTestFakeMotionEvent(display, -1, root_x, root_y, CurrentTime); | |
| 288 XFlush(display); | |
| 289 } else if (kbd_grab_status_ == GDK_GRAB_ALREADY_GRABBED || | |
| 290 kbd_grab_status_ == GDK_GRAB_FROZEN) { | |
| 291 // Successfully grabbed the pointer, but keyboard is still grabbed | |
| 292 // by other client. Another attempt to close supposedly opened | |
| 293 // menu by emulating escape key. Such situation must be very | |
| 294 // rare, but handling this just in case | |
| 295 Display* display = ui::GetXDisplay(); | |
| 296 KeyCode escape = XKeysymToKeycode(display, XK_Escape); | |
| 297 XTestFakeKeyEvent(display, escape, True, CurrentTime); | |
| 298 XTestFakeKeyEvent(display, escape, False, CurrentTime); | |
| 299 XFlush(display); | |
| 300 } | |
| 301 } | |
| 302 | |
| 303 void LockWindowGtk::HandleGtkGrabBroke() { | |
| 304 // Input should never be stolen from ScreenLocker once it's | |
| 305 // grabbed. If this happens, it's a bug and has to be fixed. We | |
| 306 // let chrome crash to get a crash report and dump, and | |
| 307 // SessionManager will terminate the session to logout. | |
| 308 CHECK_NE(GDK_GRAB_SUCCESS, kbd_grab_status_); | |
| 309 CHECK_NE(GDK_GRAB_SUCCESS, mouse_grab_status_); | |
| 310 } | |
| 311 | |
| 312 void LockWindowGtk::OnClientEvent(GtkWidget* widge, GdkEventClient* event) { | |
| 313 WmIpc::Message msg; | |
| 314 WmIpc::instance()->DecodeMessage(*event, &msg); | |
| 315 if (msg.type() == WM_IPC_MESSAGE_CHROME_NOTIFY_SCREEN_REDRAWN_FOR_LOCK) | |
| 316 OnWindowManagerReady(); | |
| 317 } | |
| 318 | |
| 319 } // namespace chromeos | |
| OLD | NEW |