1 // Copyright (c) 2011 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/screen_locker.h" 6 7 #include <X11/extensions/XTest.h> 8 #include <X11/keysym.h> 9 #include <gdk/gdkkeysyms.h> 10 #include <gdk/gdkx.h> 11 #include <string> 12 #include <vector> 13 // Evil hack to undo X11 evil #define. See crosbug.com/ 14 #undef Status 15 16 #include "base/command_line.h" 17 #include "base/lazy_instance.h" 18 #include "base/message_loop.h" 19 #include "base/metrics/histogram.h" 20 #include "base/string_util.h" 21 #include "base/timer.h" 22 #include "base/utf_string_conversions.h" 23 #include "chrome/browser/chromeos/cros/input_method_library.h" 24 #include "chrome/browser/chromeos/cros/login_library.h" 25 #include "chrome/browser/chromeos/cros/screen_lock_library.h" 26 #include "chrome/browser/chromeos/input_method/input_method_util.h" 27 #include "chrome/browser/chromeos/language_preferences.h" 28 #include "chrome/browser/chromeos/login/authenticator.h" 29 #include "chrome/browser/chromeos/login/background_view.h" 30 #include "chrome/browser/chromeos/login/login_performer.h" 31 #include "chrome/browser/chromeos/login/login_utils.h" 32 #include "chrome/browser/chromeos/login/message_bubble.h" 33 #include "chrome/browser/chromeos/login/screen_lock_view.h" 34 #include "chrome/browser/chromeos/login/shutdown_button.h" 35 #include "chrome/browser/chromeos/system_key_event_listener.h" 36 #include "chrome/browser/chromeos/view_ids.h" 37 #include "chrome/browser/chromeos/wm_ipc.h" 38 #include "chrome/browser/metrics/user_metrics.h" 39 #include "chrome/browser/profiles/profile.h" 40 #include "chrome/browser/profiles/profile_manager.h" 41 #include "chrome/browser/sync/profile_sync_service.h" 42 #include "chrome/browser/ui/browser.h" 43 #include "chrome/browser/ui/browser_list.h" 44 #include "chrome/browser/ui/browser_window.h" 45 #include "chrome/common/chrome_switches.h" 46 #include "content/browser/browser_thread.h" 47 #include "content/common/notification_service.h" 48 #include "googleurl/src/gurl.h" 49 #include "grit/generated_resources.h" 50 #include "grit/theme_resources.h" 51 #include "third_party/cros/chromeos_wm_ipc_enums.h" 52 #include "ui/base/l10n/l10n_util.h" 53 #include "ui/base/resource/resource_bundle.h" 54 #include "ui/base/x/x11_util.h" 55 #include "views/screen.h" 56 #include "views/widget/root_view.h" 57 #include "views/widget/widget_gtk.h" 58 59 namespace { 60 61 // The maximum duration for which locker should try to grab the keyboard and 62 // mouse and its interval for regrabbing on failure. 63 const int kMaxGrabFailureSec = 30; 64 const int64 kRetryGrabIntervalMs = 500; 65 66 // Maximum number of times we'll try to grab the keyboard and mouse before 67 // giving up. If we hit the limit, Chrome exits and the session is terminated. 68 const int kMaxGrabFailures = kMaxGrabFailureSec * 1000 / kRetryGrabIntervalMs; 69 70 // A idle time to show the screen saver in seconds. 71 const int kScreenSaverIdleTimeout = 15; 72 73 // Observer to start ScreenLocker when the screen lock 74 class ScreenLockObserver : public chromeos::ScreenLockLibrary::Observer, 75 public NotificationObserver { 76 public: 77 ScreenLockObserver() { 78 registrar_.Add(this, NotificationType::LOGIN_USER_CHANGED, 79 NotificationService::AllSources()); 80 } 81 82 // NotificationObserver overrides: 83 virtual void Observe(NotificationType type, 84 const NotificationSource& source, 85 const NotificationDetails& details) { 86 if (type == NotificationType::LOGIN_USER_CHANGED) { 87 // Register Screen Lock after login screen to make sure 88 // we don't show the screen lock on top of the login screen by accident. 89 if (chromeos::CrosLibrary::Get()->EnsureLoaded()) 90 chromeos::CrosLibrary::Get()->GetScreenLockLibrary()->AddObserver(this); 91 } 92 } 93 94 virtual void LockScreen(chromeos::ScreenLockLibrary* obj) { 95 VLOG(1) << "In: ScreenLockObserver::LockScreen"; 96 SetupInputMethodsForScreenLocker(); 97 chromeos::ScreenLocker::Show(); 98 } 99 100 virtual void UnlockScreen(chromeos::ScreenLockLibrary* obj) { 101 RestoreInputMethods(); 102 chromeos::ScreenLocker::Hide(); 103 } 104 105 virtual void UnlockScreenFailed(chromeos::ScreenLockLibrary* obj) { 106 chromeos::ScreenLocker::UnlockScreenFailed(); 107 } 108 109 private: 110 // Temporarily deactivates all input methods (e.g. Chinese, Japanese, Arabic) 111 // since they are not necessary to input a login password. Users are still 112 // able to use/switch active keyboard layouts (e.g. US qwerty, US dvorak, 113 // French). 114 void SetupInputMethodsForScreenLocker() { 115 if (chromeos::CrosLibrary::Get()->EnsureLoaded() && 116 // The LockScreen function is also called when the OS is suspended, and 117 // in that case |saved_active_input_method_list_| might be non-empty. 118 saved_active_input_method_list_.empty()) { 119 chromeos::InputMethodLibrary* library = 120 chromeos::CrosLibrary::Get()->GetInputMethodLibrary(); 121 122 saved_previous_input_method_id_ = library->previous_input_method().id; 123 saved_current_input_method_id_ = library->current_input_method().id; 124 scoped_ptr<chromeos::InputMethodDescriptors> active_input_method_list( 125 library->GetActiveInputMethods()); 126 127 const std::string hardware_keyboard_id = 128 chromeos::input_method::GetHardwareInputMethodId(); 129 // We'll add the hardware keyboard if it's not included in 130 // |active_input_method_list| so that the user can always use the hardware 131 // keyboard on the screen locker. 132 bool should_add_hardware_keyboard = true; 133 134 chromeos::ImeConfigValue value; 135 value.type = chromeos::ImeConfigValue::kValueTypeStringList; 136 for (size_t i = 0; i < active_input_method_list->size(); ++i) { 137 const std::string& input_method_id = active_input_method_list->at(i).id; 138 saved_active_input_method_list_.push_back(input_method_id); 139 // Skip if it's not a keyboard layout. 140 if (!chromeos::input_method::IsKeyboardLayout(input_method_id)) 141 continue; 142 value.string_list_value.push_back(input_method_id); 143 if (input_method_id == hardware_keyboard_id) { 144 should_add_hardware_keyboard = false; 145 } 146 } 147 if (should_add_hardware_keyboard) { 148 value.string_list_value.push_back(hardware_keyboard_id); 149 } 150 // We don't want to shut down the IME, even if the hardware layout is the 151 // only IME left. 152 library->SetEnableAutoImeShutdown(false); 153 library->SetImeConfig( 154 chromeos::language_prefs::kGeneralSectionName, 155 chromeos::language_prefs::kPreloadEnginesConfigName, 156 value); 157 } 158 } 159 160 void RestoreInputMethods() { 161 if (chromeos::CrosLibrary::Get()->EnsureLoaded() && 162 !saved_active_input_method_list_.empty()) { 163 chromeos::InputMethodLibrary* library = 164 chromeos::CrosLibrary::Get()->GetInputMethodLibrary(); 165 166 chromeos::ImeConfigValue value; 167 value.type = chromeos::ImeConfigValue::kValueTypeStringList; 168 value.string_list_value = saved_active_input_method_list_; 169 library->SetEnableAutoImeShutdown(true); 170 library->SetImeConfig( 171 chromeos::language_prefs::kGeneralSectionName, 172 chromeos::language_prefs::kPreloadEnginesConfigName, 173 value); 174 // Send previous input method id first so Ctrl+space would work fine. 175 if (!saved_previous_input_method_id_.empty()) 176 library->ChangeInputMethod(saved_previous_input_method_id_); 177 if (!saved_current_input_method_id_.empty()) 178 library->ChangeInputMethod(saved_current_input_method_id_); 179 180 saved_previous_input_method_id_.clear(); 181 saved_current_input_method_id_.clear(); 182 saved_active_input_method_list_.clear(); 183 } 184 } 185 186 NotificationRegistrar registrar_; 187 std::string saved_previous_input_method_id_; 188 std::string saved_current_input_method_id_; 189 std::vector<std::string> saved_active_input_method_list_; 190 191 DISALLOW_COPY_AND_ASSIGN(ScreenLockObserver); 192 }; 193 194 static base::LazyInstance<ScreenLockObserver> g_screen_lock_observer( 195 base::LINKER_INITIALIZED); 196 197 // A ScreenLock window that covers entire screen to keep the keyboard 198 // focus/events inside the grab widget. 199 class LockWindow : public views::WidgetGtk { 200 public: 201 LockWindow() 202 : views::WidgetGtk(views::WidgetGtk::TYPE_WINDOW), 203 toplevel_focus_widget_(NULL) { 204 EnableDoubleBuffer(true); 205 } 206 207 // GTK propagates key events from parents to children. 208 // Make sure LockWindow will never handle key events. 209 virtual gboolean OnKeyEvent(GtkWidget* widget, GdkEventKey* event) { 210 // Don't handle key event in the lock window. 211 return false; 212 } 213 214 virtual void OnDestroy(GtkWidget* object) { 215 VLOG(1) << "OnDestroy: LockWindow destroyed"; 216 views::WidgetGtk::OnDestroy(object); 217 } 218 219 virtual void ClearNativeFocus() { 220 DCHECK(toplevel_focus_widget_); 221 gtk_widget_grab_focus(toplevel_focus_widget_); 222 } 223 224 // Sets the widget to move the focus to when clearning the native 225 // widget's focus. 226 void set_toplevel_focus_widget(GtkWidget* widget) { 227 GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_FOCUS); 228 toplevel_focus_widget_ = widget; 229 } 230 231 private: 232 // The widget we set focus to when clearning the focus on native 233 // widget. In screen locker, gdk input is grabbed in GrabWidget, 234 // and resetting the focus by using gtk_window_set_focus seems to 235 // confuse gtk and doesn't let focus move to native widget under 236 // GrabWidget. 237 GtkWidget* toplevel_focus_widget_; 238 239 DISALLOW_COPY_AND_ASSIGN(LockWindow); 240 }; 241 242 // GrabWidget's root view to layout the ScreenLockView at the center 243 // and the Shutdown button at the right bottom. 244 class GrabWidgetRootView 245 : public views::View, 246 public chromeos::ScreenLocker::ScreenLockViewContainer { 247 public: 248 explicit GrabWidgetRootView(chromeos::ScreenLockView* screen_lock_view) 249 : screen_lock_view_(screen_lock_view), 250 shutdown_button_(new chromeos::ShutdownButton()) { 251 shutdown_button_->Init(); 252 AddChildView(screen_lock_view_); 253 AddChildView(shutdown_button_); 254 } 255 256 // views::View implementation. 257 virtual void Layout() { 258 gfx::Size size = screen_lock_view_->GetPreferredSize(); 259 screen_lock_view_->SetBounds(0, 0, size.width(), size.height()); 260 shutdown_button_->LayoutIn(this); 261 } 262 263 // ScreenLocker::ScreenLockViewContainer implementation: 264 void SetScreenLockView(views::View* screen_lock_view) { 265 if (screen_lock_view_) { 266 RemoveChildView(screen_lock_view_); 267 } 268 screen_lock_view_ = screen_lock_view; 269 if (screen_lock_view_) { 270 AddChildViewAt(screen_lock_view_, 0); 271 } 272 Layout(); 273 } 274 275 private: 276 views::View* screen_lock_view_; 277 278 chromeos::ShutdownButton* shutdown_button_; 279 280 DISALLOW_COPY_AND_ASSIGN(GrabWidgetRootView); 281 }; 282 283 // A child widget that grabs both keyboard and pointer input. 284 class GrabWidget : public views::WidgetGtk { 285 public: 286 explicit GrabWidget(chromeos::ScreenLocker* screen_locker) 287 : views::WidgetGtk(views::WidgetGtk::TYPE_CHILD), 288 screen_locker_(screen_locker), 289 ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), 290 grab_failure_count_(0), 291 kbd_grab_status_(GDK_GRAB_INVALID_TIME), 292 mouse_grab_status_(GDK_GRAB_INVALID_TIME), 293 signout_link_(NULL), 294 shutdown_(NULL) { 295 } 296 297 virtual void Show() { 298 views::WidgetGtk::Show(); 299 signout_link_ = 300 screen_locker_->GetViewByID(VIEW_ID_SCREEN_LOCKER_SIGNOUT_LINK); 301 shutdown_ = screen_locker_->GetViewByID(VIEW_ID_SCREEN_LOCKER_SHUTDOWN); 302 // These can be null in guest mode. 303 } 304 305 void ClearGtkGrab() { 306 GtkWidget* current_grab_window; 307 // Grab gtk input first so that the menu holding gtk grab will 308 // close itself. 309 gtk_grab_add(window_contents()); 310 311 // Make sure there is no gtk grab widget so that gtk simply propagates 312 // an event. This is necessary to allow message bubble and password 313 // field, button to process events simultaneously. GTK 314 // maintains grab widgets in a linked-list, so we need to remove 315 // until it's empty. 316 while ((current_grab_window = gtk_grab_get_current()) != NULL) 317 gtk_grab_remove(current_grab_window); 318 } 319 320 virtual gboolean OnKeyEvent(GtkWidget* widget, GdkEventKey* event) { 321 views::KeyEvent key_event(reinterpret_cast<GdkEvent*>(event)); 322 // This is a hack to workaround the issue crosbug.com/10655 due to 323 // the limitation that a focus manager cannot handle views in 324 // TYPE_CHILD WidgetGtk correctly. 325 if (signout_link_ && 326 event->type == GDK_KEY_PRESS && 327 (event->keyval == GDK_Tab || 328 event->keyval == GDK_ISO_Left_Tab || 329 event->keyval == GDK_KP_Tab)) { 330 DCHECK(shutdown_); 331 bool reverse = event->state & GDK_SHIFT_MASK; 332 if (reverse && signout_link_->HasFocus()) { 333 shutdown_->RequestFocus(); 334 return true; 335 } 336 if (!reverse && shutdown_->HasFocus()) { 337 signout_link_->RequestFocus(); 338 return true; 339 } 340 } 341 return views::WidgetGtk::OnKeyEvent(widget, event); 342 } 343 344 virtual gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event) { 345 WidgetGtk::OnButtonPress(widget, event); 346 // Never propagate event to parent. 347 return true; 348 } 349 350 // Try to grab all inputs. It initiates another try if it fails to 351 // grab and the retry count is within a limit, or fails with CHECK. 352 void TryGrabAllInputs(); 353 354 // This method tries to steal pointer/keyboard grab from other 355 // client by sending events that will hopefully close menus or windows 356 // that have the grab. 357 void TryUngrabOtherClients(); 358 359 private: 360 virtual void HandleGtkGrabBroke() { 361 // Input should never be stolen from ScreenLocker once it's 362 // grabbed. If this happens, it's a bug and has to be fixed. We 363 // let chrome crash to get a crash report and dump, and 364 // SessionManager will terminate the session to logout. 365 CHECK_NE(GDK_GRAB_SUCCESS, kbd_grab_status_); 366 CHECK_NE(GDK_GRAB_SUCCESS, mouse_grab_status_); 367 } 368 369 // Define separate methods for each error code so that stack trace 370 // will tell which error the grab failed with. 371 void FailedWithGrabAlreadyGrabbed() { 372 LOG(FATAL) << "Grab already grabbed"; 373 } 374 void FailedWithGrabInvalidTime() { 375 LOG(FATAL) << "Grab invalid time"; 376 } 377 void FailedWithGrabNotViewable() { 378 LOG(FATAL) << "Grab not viewable"; 379 } 380 void FailedWithGrabFrozen() { 381 LOG(FATAL) << "Grab frozen"; 382 } 383 void FailedWithUnknownError() { 384 LOG(FATAL) << "Grab uknown"; 385 } 386 387 chromeos::ScreenLocker* screen_locker_; 388 ScopedRunnableMethodFactory<GrabWidget> task_factory_; 389 390 // The number times the widget tried to grab all focus. 391 int grab_failure_count_; 392 // Status of keyboard and mouse grab. 393 GdkGrabStatus kbd_grab_status_; 394 GdkGrabStatus mouse_grab_status_; 395 396 views::View* signout_link_; 397 views::View* shutdown_; 398 399 DISALLOW_COPY_AND_ASSIGN(GrabWidget); 400 }; 401 402 void GrabWidget::TryGrabAllInputs() { 403 // Grab x server so that we can atomically grab and take 404 // action when grab fails. 405 gdk_x11_grab_server(); 406 if (kbd_grab_status_ != GDK_GRAB_SUCCESS) { 407 kbd_grab_status_ = gdk_keyboard_grab(window_contents()->window, FALSE, 408 GDK_CURRENT_TIME); 409 } 410 if (mouse_grab_status_ != GDK_GRAB_SUCCESS) { 411 mouse_grab_status_ = 412 gdk_pointer_grab(window_contents()->window, 413 FALSE, 414 static_cast<GdkEventMask>( 415 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | 416 GDK_POINTER_MOTION_MASK), 417 NULL, 418 NULL, 419 GDK_CURRENT_TIME); 420 } 421 if ((kbd_grab_status_ != GDK_GRAB_SUCCESS || 422 mouse_grab_status_ != GDK_GRAB_SUCCESS) && 423 grab_failure_count_++ < kMaxGrabFailures) { 424 LOG(WARNING) << "Failed to grab inputs. Trying again in " 425 << kRetryGrabIntervalMs << " ms: kbd=" 426 << kbd_grab_status_ << ", mouse=" << mouse_grab_status_; 427 TryUngrabOtherClients(); 428 gdk_x11_ungrab_server(); 429 MessageLoop::current()->PostDelayedTask( 430 FROM_HERE, 431 task_factory_.NewRunnableMethod(&GrabWidget::TryGrabAllInputs), 432 kRetryGrabIntervalMs); 433 } else { 434 gdk_x11_ungrab_server(); 435 GdkGrabStatus status = kbd_grab_status_; 436 if (status == GDK_GRAB_SUCCESS) { 437 status = mouse_grab_status_; 438 } 439 switch (status) { 440 case GDK_GRAB_SUCCESS: 441 break; 442 case GDK_GRAB_ALREADY_GRABBED: 443 FailedWithGrabAlreadyGrabbed(); 444 break; 445 case GDK_GRAB_INVALID_TIME: 446 FailedWithGrabInvalidTime(); 447 break; 448 case GDK_GRAB_NOT_VIEWABLE: 449 FailedWithGrabNotViewable(); 450 break; 451 case GDK_GRAB_FROZEN: 452 FailedWithGrabFrozen(); 453 break; 454 default: 455 FailedWithUnknownError(); 456 break; 457 } 458 DVLOG(1) << "Grab Success"; 459 screen_locker_->OnGrabInputs(); 460 } 461 } 462 463 void GrabWidget::TryUngrabOtherClients() { 464 #if !defined(NDEBUG) 465 { 466 int event_base, error_base; 467 int major, minor; 468 // Make sure we have XTest extension. 469 DCHECK(XTestQueryExtension(ui::GetXDisplay(), 470 &event_base, &error_base, 471 &major, &minor)); 472 } 473 #endif 474 475 // The following code is an attempt to grab inputs by closing 476 // supposedly opened menu. This happens when a plugin has a menu 477 // opened. 478 if (mouse_grab_status_ == GDK_GRAB_ALREADY_GRABBED || 479 mouse_grab_status_ == GDK_GRAB_FROZEN) { 480 // Successfully grabbed the keyboard, but pointer is still 481 // grabbed by other client. Another attempt to close supposedly 482 // opened menu by emulating keypress at the left top corner. 483 Display* display = ui::GetXDisplay(); 484 Window root, child; 485 int root_x, root_y, win_x, winy; 486 unsigned int mask; 487 XQueryPointer(display, 488 ui::GetX11WindowFromGtkWidget(window_contents()), 489 &root, &child, &root_x, &root_y, 490 &win_x, &winy, &mask); 491 XTestFakeMotionEvent(display, -1, -10000, -10000, CurrentTime); 492 XTestFakeButtonEvent(display, 1, True, CurrentTime); 493 XTestFakeButtonEvent(display, 1, False, CurrentTime); 494 // Move the pointer back. 495 XTestFakeMotionEvent(display, -1, root_x, root_y, CurrentTime); 496 XFlush(display); 497 } else if (kbd_grab_status_ == GDK_GRAB_ALREADY_GRABBED || 498 kbd_grab_status_ == GDK_GRAB_FROZEN) { 499 // Successfully grabbed the pointer, but keyboard is still grabbed 500 // by other client. Another attempt to close supposedly opened 501 // menu by emulating escape key. Such situation must be very 502 // rare, but handling this just in case 503 Display* display = ui::GetXDisplay(); 504 KeyCode escape = XKeysymToKeycode(display, XK_Escape); 505 XTestFakeKeyEvent(display, escape, True, CurrentTime); 506 XTestFakeKeyEvent(display, escape, False, CurrentTime); 507 XFlush(display); 508 } 509 } 510 511 // BackgroundView for ScreenLocker, which layouts a lock widget in 512 // addition to other background components. 513 class ScreenLockerBackgroundView 514 : public chromeos::BackgroundView, 515 public chromeos::ScreenLocker::ScreenLockViewContainer { 516 public: 517 ScreenLockerBackgroundView(views::WidgetGtk* lock_widget, 518 views::View* screen_lock_view) 519 : lock_widget_(lock_widget), 520 screen_lock_view_(screen_lock_view) { 521 } 522 523 virtual ScreenMode GetScreenMode() const { 524 return kScreenLockerMode; 525 } 526 527 virtual void Layout() { 528 chromeos::BackgroundView::Layout(); 529 gfx::Rect screen = bounds(); 530 if (screen_lock_view_) { 531 gfx::Size size = screen_lock_view_->GetPreferredSize(); 532 gfx::Point origin((screen.width() - size.width()) / 2, 533 (screen.height() - size.height()) / 2); 534 gfx::Size widget_size(screen.size()); 535 widget_size.Enlarge(-origin.x(), -origin.y()); 536 lock_widget_->SetBounds(gfx::Rect(origin, widget_size)); 537 } else { 538 // No password entry. Move the lock widget to off screen. 539 lock_widget_->SetBounds(gfx::Rect(-100, -100, 1, 1)); 540 } 541 } 542 543 // ScreenLocker::ScreenLockViewContainer implementation: 544 void SetScreenLockView(views::View* screen_lock_view) { 545 screen_lock_view_ = screen_lock_view; 546 Layout(); 547 } 548 549 private: 550 views::WidgetGtk* lock_widget_; 551 552 views::View* screen_lock_view_; 553 554 DISALLOW_COPY_AND_ASSIGN(ScreenLockerBackgroundView); 555 }; 556 557 } // namespace 558 559 namespace chromeos { 560 561 // static 562 ScreenLocker* ScreenLocker::screen_locker_ = NULL; 563 564 // A event observer that forwards gtk events from one window to another. 565 // See screen_locker.h for more details. 566 class MouseEventRelay : public MessageLoopForUI::Observer { 567 public: 568 MouseEventRelay(GdkWindow* src, GdkWindow* dest) 569 : src_(src), 570 dest_(dest), 571 initialized_(false) { 572 DCHECK(src_); 573 DCHECK(dest_); 574 } 575 576 virtual void WillProcessEvent(GdkEvent* event) {} 577 578 virtual void DidProcessEvent(GdkEvent* event) { 579 if (event->any.window != src_) 580 return; 581 if (!initialized_) { 582 gint src_x, src_y, dest_x, dest_y, width, height, depth; 583 gdk_window_get_geometry(dest_, &dest_x, &dest_y, &width, &height, &depth); 584 // wait to compute offset until the info bubble widget's location 585 // is available. 586 if (dest_x < 0 || dest_y < 0) 587 return; 588 gdk_window_get_geometry(src_, &src_x, &src_y, &width, &height, &depth); 589 offset_.SetPoint(dest_x - src_x, dest_y - src_y); 590 initialized_ = true; 591 } 592 if (event->type == GDK_BUTTON_PRESS || 593 event->type == GDK_BUTTON_RELEASE) { 594 GdkEvent* copy = gdk_event_copy(event); 595 copy->button.window = dest_; 596 g_object_ref(copy->button.window); 597 copy->button.x -= offset_.x(); 598 copy->button.y -= offset_.y(); 599 600 gdk_event_put(copy); 601 gdk_event_free(copy); 602 } else if (event->type == GDK_MOTION_NOTIFY) { 603 GdkEvent* copy = gdk_event_copy(event); 604 copy->motion.window = dest_; 605 g_object_ref(copy->motion.window); 606 copy->motion.x -= offset_.x(); 607 copy->motion.y -= offset_.y(); 608 609 gdk_event_put(copy); 610 gdk_event_free(copy); 611 } 612 } 613 614 private: 615 GdkWindow* src_; 616 GdkWindow* dest_; 617 bool initialized_; 618 619 // Offset from src_'s origin to dest_'s origin. 620 gfx::Point offset_; 621 622 DISALLOW_COPY_AND_ASSIGN(MouseEventRelay); 623 }; 624 625 // A event observer used to unlock the screen upon user's action 626 // without asking password. Used in BWSI and auto login mode. 627 // TODO(oshima): consolidate InputEventObserver and LockerInputEventObserver. 628 class InputEventObserver : public MessageLoopForUI::Observer { 629 public: 630 explicit InputEventObserver(ScreenLocker* screen_locker) 631 : screen_locker_(screen_locker), 632 activated_(false) { 633 } 634 635 virtual void WillProcessEvent(GdkEvent* event) { 636 if ((event->type == GDK_KEY_PRESS || 637 event->type == GDK_BUTTON_PRESS || 638 event->type == GDK_MOTION_NOTIFY) && 639 !activated_) { 640 activated_ = true; 641 std::string not_used_string; 642 GaiaAuthConsumer::ClientLoginResult not_used; 643 screen_locker_->OnLoginSuccess(not_used_string, 644 not_used_string, 645 not_used, 646 false); 647 } 648 } 649 650 virtual void DidProcessEvent(GdkEvent* event) { 651 } 652 653 private: 654 chromeos::ScreenLocker* screen_locker_; 655 656 bool activated_; 657 658 DISALLOW_COPY_AND_ASSIGN(InputEventObserver); 659 }; 660 661 // A event observer used to show the screen locker upon 662 // user action: mouse or keyboard interactions. 663 // TODO(oshima): this has to be disabled while authenticating. 664 class LockerInputEventObserver : public MessageLoopForUI::Observer { 665 public: 666 explicit LockerInputEventObserver(ScreenLocker* screen_locker) 667 : screen_locker_(screen_locker), 668 ALLOW_THIS_IN_INITIALIZER_LIST( 669 timer_(base::TimeDelta::FromSeconds(kScreenSaverIdleTimeout), this, 670 &LockerInputEventObserver::StartScreenSaver)) { 671 } 672 673 virtual void WillProcessEvent(GdkEvent* event) { 674 if ((event->type == GDK_KEY_PRESS || 675 event->type == GDK_BUTTON_PRESS || 676 event->type == GDK_MOTION_NOTIFY)) { 677 timer_.Reset(); 678 screen_locker_->StopScreenSaver(); 679 } 680 } 681 682 virtual void DidProcessEvent(GdkEvent* event) { 683 } 684 685 private: 686 void StartScreenSaver() { 687 screen_locker_->StartScreenSaver(); 688 } 689 690 chromeos::ScreenLocker* screen_locker_; 691 base::DelayTimer<LockerInputEventObserver> timer_; 692 693 DISALLOW_COPY_AND_ASSIGN(LockerInputEventObserver); 694 }; 695 696 ////////////////////////////////////////////////////////////////////////////// 697 // ScreenLocker, public: 698 699 ScreenLocker::ScreenLocker(const UserManager::User& user) 700 : lock_window_(NULL), 701 lock_widget_(NULL), 702 screen_lock_view_(NULL), 703 captcha_view_(NULL), 704 grab_container_(NULL), 705 background_container_(NULL), 706 user_(user), 707 error_info_(NULL), 708 drawn_(false), 709 input_grabbed_(false), 710 // TODO(oshima): support auto login mode (this is not implemented yet) 711 // http://crosbug.com/1881 712 unlock_on_input_(user_.email().empty()), 713 locked_(false), 714 start_time_(base::Time::Now()) { 715 DCHECK(!screen_locker_); 716 screen_locker_ = this; 717 } 718 719 void ScreenLocker::Init() { 720 static const GdkColor kGdkBlack = {0, 0, 0, 0}; 721 722 authenticator_ = LoginUtils::Get()->CreateAuthenticator(this); 723 724 gfx::Point left_top(1, 1); 725 gfx::Rect init_bounds(views::Screen::GetMonitorAreaNearestPoint(left_top)); 726 727 LockWindow* lock_window = new LockWindow(); 728 lock_window_ = lock_window; 729 lock_window_->Init(NULL, init_bounds); 730 gtk_widget_modify_bg( 731 lock_window_->GetNativeView(), GTK_STATE_NORMAL, &kGdkBlack); 732 733 g_signal_connect(lock_window_->GetNativeView(), "client-event", 734 G_CALLBACK(OnClientEventThunk), this); 735 736 // GTK does not like zero width/height. 737 if (!unlock_on_input_) { 738 screen_lock_view_ = new ScreenLockView(this); 739 screen_lock_view_->Init(); 740 screen_lock_view_->SetEnabled(false); 741 screen_lock_view_->StartThrobber(); 742 } else { 743 input_event_observer_.reset(new InputEventObserver(this)); 744 MessageLoopForUI::current()->AddObserver(input_event_observer_.get()); 745 } 746 747 // Hang on to a cast version of the grab widget so we can call its 748 // TryGrabAllInputs() method later. (Nobody else needs to use it, so moving 749 // its declaration to the header instead of keeping it in an anonymous 750 // namespace feels a bit ugly.) 751 GrabWidget* cast_lock_widget = new GrabWidget(this); 752 lock_widget_ = cast_lock_widget; 753 lock_widget_->MakeTransparent(); 754 lock_widget_->InitWithWidget(lock_window_, gfx::Rect()); 755 if (screen_lock_view_) { 756 GrabWidgetRootView* root_view = new GrabWidgetRootView(screen_lock_view_); 757 grab_container_ = root_view; 758 lock_widget_->SetContentsView(root_view); 759 } 760 lock_widget_->Show(); 761 762 // Configuring the background url. 763 std::string url_string = 764 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 765 switches::kScreenSaverUrl); 766 ScreenLockerBackgroundView* screen_lock_background_view_ = 767 new ScreenLockerBackgroundView(lock_widget_, screen_lock_view_); 768 background_container_ = screen_lock_background_view_; 769 background_view_ = screen_lock_background_view_; 770 background_view_->Init(GURL(url_string)); 771 if (background_view_->ScreenSaverEnabled()) 772 StartScreenSaver(); 773 774 DCHECK(GTK_WIDGET_REALIZED(lock_window_->GetNativeView())); 775 WmIpc::instance()->SetWindowType( 776 lock_window_->GetNativeView(), 777 WM_IPC_WINDOW_CHROME_SCREEN_LOCKER, 778 NULL); 779 780 lock_window_->SetContentsView(background_view_); 781 lock_window_->Show(); 782 783 cast_lock_widget->ClearGtkGrab(); 784 785 // Call this after lock_window_->Show(); otherwise the 1st invocation 786 // of gdk_xxx_grab() will always fail. 787 cast_lock_widget->TryGrabAllInputs(); 788 789 // Add the window to its own group so that its grab won't be stolen if 790 // gtk_grab_add() gets called on behalf on a non-screen-locker widget (e.g. 791 // a modal dialog) -- see http://crosbug.com/8999. We intentionally do this 792 // after calling ClearGtkGrab(), as want to be in the default window group 793 // then so we can break any existing GTK grabs. 794 GtkWindowGroup* window_group = gtk_window_group_new(); 795 gtk_window_group_add_window(window_group, 796 GTK_WINDOW(lock_window_->GetNativeView())); 797 g_object_unref(window_group); 798 799 lock_window->set_toplevel_focus_widget(lock_widget_->window_contents()); 800 801 // Create the SystemKeyEventListener so it can listen for system keyboard 802 // messages regardless of focus while screen locked. 803 SystemKeyEventListener::GetInstance(); 804 } 805 806 void ScreenLocker::OnLoginFailure(const LoginFailure& error) { 807 DVLOG(1) << "OnLoginFailure"; 808 UserMetrics::RecordAction(UserMetricsAction("ScreenLocker_OnLoginFailure")); 809 if (authentication_start_time_.is_null()) { 810 LOG(ERROR) << "authentication_start_time_ is not set"; 811 } else { 812 base::TimeDelta delta = base::Time::Now() - authentication_start_time_; 813 VLOG(1) << "Authentication failure time: " << delta.InSecondsF(); 814 UMA_HISTOGRAM_TIMES("ScreenLocker.AuthenticationFailureTime", delta); 815 } 816 817 EnableInput(); 818 // Don't enable signout button here as we're showing 819 // MessageBubble. 820 821 string16 msg = l10n_util::GetStringUTF16(IDS_LOGIN_ERROR_AUTHENTICATING); 822 const std::string error_text = error.GetErrorString(); 823 if (!error_text.empty()) 824 msg += ASCIIToUTF16("\n") + ASCIIToUTF16(error_text); 825 826 InputMethodLibrary* input_method_library = 827 CrosLibrary::Get()->GetInputMethodLibrary(); 828 if (input_method_library->GetNumActiveInputMethods() > 1) 829 msg += ASCIIToUTF16("\n") + 830 l10n_util::GetStringUTF16(IDS_LOGIN_ERROR_KEYBOARD_SWITCH_HINT); 831 832 ShowErrorBubble(UTF16ToWide(msg), BubbleBorder::BOTTOM_LEFT); 833 } 834 835 void ScreenLocker::OnLoginSuccess( 836 const std::string& username, 837 const std::string& password, 838 const GaiaAuthConsumer::ClientLoginResult& unused, 839 bool pending_requests) { 840 VLOG(1) << "OnLoginSuccess: Sending Unlock request."; 841 if (authentication_start_time_.is_null()) { 842 if (!username.empty()) 843 LOG(WARNING) << "authentication_start_time_ is not set"; 844 } else { 845 base::TimeDelta delta = base::Time::Now() - authentication_start_time_; 846 VLOG(1) << "Authentication success time: " << delta.InSecondsF(); 847 UMA_HISTOGRAM_TIMES("ScreenLocker.AuthenticationSuccessTime", delta); 848 } 849 850 Profile* profile = ProfileManager::GetDefaultProfile(); 851 if (profile) { 852 ProfileSyncService* service = profile->GetProfileSyncService(username); 853 if (service && !service->HasSyncSetupCompleted()) { 854 // If sync has failed somehow, try setting the sync passphrase here. 855 service->SetPassphrase(password, false, true); 856 } 857 } 858 859 if (CrosLibrary::Get()->EnsureLoaded()) 860 CrosLibrary::Get()->GetScreenLockLibrary()->NotifyScreenUnlockRequested(); 861 } 862 863 void ScreenLocker::BubbleClosing(Bubble* bubble, bool closed_by_escape) { 864 error_info_ = NULL; 865 screen_lock_view_->SetSignoutEnabled(true); 866 if (mouse_event_relay_.get()) { 867 MessageLoopForUI::current()->RemoveObserver(mouse_event_relay_.get()); 868 mouse_event_relay_.reset(); 869 } 870 } 871 872 void ScreenLocker::OnCaptchaEntered(const std::string& captcha) { 873 // Captcha dialog is only shown when LoginPerformer instance exists, 874 // i.e. blocking UI after password change is in place. 875 DCHECK(LoginPerformer::default_performer()); 876 LoginPerformer::default_performer()->set_captcha(captcha); 877 878 // ScreenLockView ownership is passed to grab_container_. 879 // Need to save return value here so that compile 880 // doesn't fail with "unused result" warning. 881 views::View* view = secondary_view_.release(); 882 view = NULL; 883 captcha_view_->SetVisible(false); 884 grab_container_->SetScreenLockView(screen_lock_view_); 885 background_container_->SetScreenLockView(screen_lock_view_); 886 screen_lock_view_->SetVisible(true); 887 screen_lock_view_->ClearAndSetFocusToPassword(); 888 889 // Take CaptchaView ownership now that it's removed from grab_container_. 890 secondary_view_.reset(captcha_view_); 891 ShowErrorMessage(postponed_error_message_, false); 892 postponed_error_message_.clear(); 893 } 894 895 void ScreenLocker::Authenticate(const string16& password) { 896 if (password.empty()) 897 return; 898 899 authentication_start_time_ = base::Time::Now(); 900 screen_lock_view_->SetEnabled(false); 901 screen_lock_view_->SetSignoutEnabled(false); 902 screen_lock_view_->StartThrobber(); 903 904 // If LoginPerformer instance exists, 905 // initial online login phase is still active. 906 if (LoginPerformer::default_performer()) { 907 DVLOG(1) << "Delegating authentication to LoginPerformer."; 908 LoginPerformer::default_performer()->Login(user_.email(), 909 UTF16ToUTF8(password)); 910 } else { 911 BrowserThread::PostTask( 912 BrowserThread::UI, FROM_HERE, 913 NewRunnableMethod(authenticator_.get(), 914 &Authenticator::AuthenticateToUnlock, 915 user_.email(), 916 UTF16ToUTF8(password))); 917 } 918 } 919 920 void ScreenLocker::ClearErrors() { 921 if (error_info_) { 922 error_info_->Close(); 923 error_info_ = NULL; 924 } 925 } 926 927 void ScreenLocker::EnableInput() { 928 if (screen_lock_view_) { 929 screen_lock_view_->SetEnabled(true); 930 screen_lock_view_->ClearAndSetFocusToPassword(); 931 screen_lock_view_->StopThrobber(); 932 } 933 } 934 935 void ScreenLocker::Signout() { 936 if (!error_info_) { 937 UserMetrics::RecordAction(UserMetricsAction("ScreenLocker_Signout")); 938 WmIpc::instance()->NotifyAboutSignout(); 939 if (CrosLibrary::Get()->EnsureLoaded()) { 940 CrosLibrary::Get()->GetLoginLibrary()->StopSession(""); 941 } 942 943 // Don't hide yet the locker because the chrome screen may become visible 944 // briefly. 945 } 946 } 947 948 void ScreenLocker::ShowCaptchaAndErrorMessage(const GURL& captcha_url, 949 const std::wstring& message) { 950 postponed_error_message_ = message; 951 if (captcha_view_) { 952 captcha_view_->SetCaptchaURL(captcha_url); 953 } else { 954 captcha_view_ = new CaptchaView(captcha_url, true); 955 captcha_view_->Init(); 956 captcha_view_->set_delegate(this); 957 } 958 // CaptchaView ownership is passed to grab_container_. 959 views::View* view = secondary_view_.release(); 960 view = NULL; 961 screen_lock_view_->SetVisible(false); 962 grab_container_->SetScreenLockView(captcha_view_); 963 background_container_->SetScreenLockView(captcha_view_); 964 captcha_view_->SetVisible(true); 965 // Take ScreenLockView ownership now that it's removed from grab_container_. 966 secondary_view_.reset(screen_lock_view_); 967 } 968 969 void ScreenLocker::ShowErrorMessage(const std::wstring& message, 970 bool sign_out_only) { 971 if (sign_out_only) { 972 screen_lock_view_->SetEnabled(false); 973 } else { 974 EnableInput(); 975 } 976 screen_lock_view_->SetSignoutEnabled(sign_out_only); 977 // Make sure that active Sign Out button is not hidden behind the bubble. 978 ShowErrorBubble(message, sign_out_only ? 979 BubbleBorder::BOTTOM_RIGHT : BubbleBorder::BOTTOM_LEFT); 980 } 981 982 void ScreenLocker::OnGrabInputs() { 983 DVLOG(1) << "OnGrabInputs"; 984 input_grabbed_ = true; 985 if (drawn_) 986 ScreenLockReady(); 987 } 988 989 views::View* ScreenLocker::GetViewByID(int id) { 990 return lock_widget_->GetRootView()->GetViewByID(id); 991 } 992 993 // static 994 void ScreenLocker::Show() { 995 VLOG(1) << "In ScreenLocker::Show"; 996 UserMetrics::RecordAction(UserMetricsAction("ScreenLocker_Show")); 997 DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI); 998 999 // Exit fullscreen. 1000 Browser* browser = BrowserList::GetLastActive(); 1001 // browser can be NULL if we receive a lock request before the first browser 1002 // window is shown. 1003 if (browser && browser->window()->IsFullscreen()) { 1004 browser->ToggleFullscreenMode(); 1005 } 1006 1007 if (!screen_locker_) { 1008 VLOG(1) << "Show: Locking screen"; 1009 ScreenLocker* locker = 1010 new ScreenLocker(UserManager::Get()->logged_in_user()); 1011 locker->Init(); 1012 } else { 1013 // PowerManager re-sends lock screen signal if it doesn't 1014 // receive the response within timeout. Just send complete 1015 // signal. 1016 VLOG(1) << "Show: locker already exists. Just sending completion event."; 1017 if (CrosLibrary::Get()->EnsureLoaded()) 1018 CrosLibrary::Get()->GetScreenLockLibrary()->NotifyScreenLockCompleted(); 1019 } 1020 } 1021 1022 // static 1023 void ScreenLocker::Hide() { 1024 DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI); 1025 DCHECK(screen_locker_); 1026 VLOG(1) << "Hide: Deleting ScreenLocker: " << screen_locker_; 1027 MessageLoopForUI::current()->DeleteSoon(FROM_HERE, screen_locker_); 1028 } 1029 1030 // static 1031 void ScreenLocker::UnlockScreenFailed() { 1032 DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI); 1033 if (screen_locker_) { 1034 // Power manager decided no to unlock the screen even if a user 1035 // typed in password, for example, when a user closed the lid 1036 // immediately after typing in the password. 1037 VLOG(1) << "UnlockScreenFailed: re-enabling screen locker."; 1038 screen_locker_->EnableInput(); 1039 } else { 1040 // This can happen when a user requested unlock, but PowerManager 1041 // rejected because the computer is closed, then PowerManager unlocked 1042 // because it's open again and the above failure message arrives. 1043 // This'd be extremely rare, but may still happen. 1044 VLOG(1) << "UnlockScreenFailed: screen is already unlocked."; 1045 } 1046 } 1047 1048 // static 1049 void ScreenLocker::InitClass() { 1050 g_screen_lock_observer.Get(); 1051 } 1052 1053 //////////////////////////////////////////////////////////////////////////////// 1054 // ScreenLocker, private: 1055 1056 ScreenLocker::~ScreenLocker() { 1057 DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI); 1058 ClearErrors(); 1059 if (input_event_observer_.get()) 1060 MessageLoopForUI::current()->RemoveObserver(input_event_observer_.get()); 1061 if (locker_input_event_observer_.get()) { 1062 lock_widget_->GetFocusManager()->UnregisterAccelerator( 1063 views::Accelerator(ui::VKEY_ESCAPE, false, false, false), this); 1064 MessageLoopForUI::current()->RemoveObserver( 1065 locker_input_event_observer_.get()); 1066 } 1067 1068 gdk_keyboard_ungrab(GDK_CURRENT_TIME); 1069 gdk_pointer_ungrab(GDK_CURRENT_TIME); 1070 1071 DCHECK(lock_window_); 1072 VLOG(1) << "~ScreenLocker(): Closing ScreenLocker window."; 1073 lock_window_->Close(); 1074 // lock_widget_ will be deleted by gtk's destroy signal. 1075 screen_locker_ = NULL; 1076 bool state = false; 1077 NotificationService::current()->Notify( 1078 NotificationType::SCREEN_LOCK_STATE_CHANGED, 1079 Source<ScreenLocker>(this), 1080 Details<bool>(&state)); 1081 if (CrosLibrary::Get()->EnsureLoaded()) 1082 CrosLibrary::Get()->GetScreenLockLibrary()->NotifyScreenUnlockCompleted(); 1083 } 1084 1085 void ScreenLocker::SetAuthenticator(Authenticator* authenticator) { 1086 authenticator_ = authenticator; 1087 } 1088 1089 void ScreenLocker::ScreenLockReady() { 1090 VLOG(1) << "ScreenLockReady: sending completed signal to power manager."; 1091 locked_ = true; 1092 base::TimeDelta delta = base::Time::Now() - start_time_; 1093 VLOG(1) << "Screen lock time: " << delta.InSecondsF(); 1094 UMA_HISTOGRAM_TIMES("ScreenLocker.ScreenLockTime", delta); 1095 1096 if (background_view_->ScreenSaverEnabled()) { 1097 lock_widget_->GetFocusManager()->RegisterAccelerator( 1098 views::Accelerator(ui::VKEY_ESCAPE, false, false, false), this); 1099 locker_input_event_observer_.reset(new LockerInputEventObserver(this)); 1100 MessageLoopForUI::current()->AddObserver( 1101 locker_input_event_observer_.get()); 1102 } else { 1103 // Don't enable the password field until we grab all inputs. 1104 EnableInput(); 1105 } 1106 1107 bool state = true; 1108 NotificationService::current()->Notify( 1109 NotificationType::SCREEN_LOCK_STATE_CHANGED, 1110 Source<ScreenLocker>(this), 1111 Details<bool>(&state)); 1112 if (CrosLibrary::Get()->EnsureLoaded()) 1113 CrosLibrary::Get()->GetScreenLockLibrary()->NotifyScreenLockCompleted(); 1114 } 1115 1116 void ScreenLocker::OnClientEvent(GtkWidget* widge, GdkEventClient* event) { 1117 WmIpc::Message msg; 1118 WmIpc::instance()->DecodeMessage(*event, &msg); 1119 if (msg.type() == WM_IPC_MESSAGE_CHROME_NOTIFY_SCREEN_REDRAWN_FOR_LOCK) { 1120 OnWindowManagerReady(); 1121 } 1122 } 1123 1124 void ScreenLocker::OnWindowManagerReady() { 1125 DVLOG(1) << "OnClientEvent: drawn for lock"; 1126 drawn_ = true; 1127 if (input_grabbed_) 1128 ScreenLockReady(); 1129 } 1130 1131 void ScreenLocker::ShowErrorBubble(const std::wstring& message, 1132 BubbleBorder::ArrowLocation arrow_location) { 1133 if (error_info_) 1134 error_info_->Close(); 1135 1136 gfx::Rect rect = screen_lock_view_->GetPasswordBoundsRelativeTo( 1137 lock_widget_->GetRootView()); 1138 gfx::Rect lock_widget_bounds = lock_widget_->GetClientAreaScreenBounds(); 1139 rect.Offset(lock_widget_bounds.x(), lock_widget_bounds.y()); 1140 error_info_ = MessageBubble::ShowNoGrab( 1141 lock_window_, 1142 rect, 1143 arrow_location, 1144 ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_WARNING), 1145 message, 1146 std::wstring(), // TODO(nkostylev): Add help link. 1147 this); 1148 1149 if (mouse_event_relay_.get()) 1150 MessageLoopForUI::current()->RemoveObserver(mouse_event_relay_.get()); 1151 mouse_event_relay_.reset( 1152 new MouseEventRelay(lock_widget_->GetNativeView()->window, 1153 error_info_->GetNativeView()->window)); 1154 MessageLoopForUI::current()->AddObserver(mouse_event_relay_.get()); 1155 } 1156 1157 void ScreenLocker::StopScreenSaver() { 1158 if (background_view_->IsScreenSaverVisible()) { 1159 VLOG(1) << "StopScreenSaver"; 1160 background_view_->HideScreenSaver(); 1161 if (screen_lock_view_) { 1162 screen_lock_view_->SetVisible(true); 1163 screen_lock_view_->RequestFocus(); 1164 } 1165 EnableInput(); 1166 } 1167 } 1168 1169 void ScreenLocker::StartScreenSaver() { 1170 if (!background_view_->IsScreenSaverVisible()) { 1171 VLOG(1) << "StartScreenSaver"; 1172 UserMetrics::RecordAction( 1173 UserMetricsAction("ScreenLocker_StartScreenSaver")); 1174 background_view_->ShowScreenSaver(); 1175 if (screen_lock_view_) { 1176 screen_lock_view_->SetEnabled(false); 1177 screen_lock_view_->SetVisible(false); 1178 } 1179 ClearErrors(); 1180 } 1181 } 1182 1183 bool ScreenLocker::AcceleratorPressed(const views::Accelerator& accelerator) { 1184 if (!background_view_->IsScreenSaverVisible()) { 1185 StartScreenSaver(); 1186 return true; 1187 } 1188 return false; 1189 } 1190 1191 } // namespace chromeos 1192