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:
ScreenLockObserver()77 ScreenLockObserver() {
78 registrar_.Add(this, NotificationType::LOGIN_USER_CHANGED,
79 NotificationService::AllSources());
80 }
81
82 // NotificationObserver overrides:
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
LockScreen(chromeos::ScreenLockLibrary * obj)94 virtual void LockScreen(chromeos::ScreenLockLibrary* obj) {
95 VLOG(1) << "In: ScreenLockObserver::LockScreen";
96 SetupInputMethodsForScreenLocker();
97 chromeos::ScreenLocker::Show();
98 }
99
UnlockScreen(chromeos::ScreenLockLibrary * obj)100 virtual void UnlockScreen(chromeos::ScreenLockLibrary* obj) {
101 RestoreInputMethods();
102 chromeos::ScreenLocker::Hide();
103 }
104
UnlockScreenFailed(chromeos::ScreenLockLibrary * obj)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).
SetupInputMethodsForScreenLocker()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
RestoreInputMethods()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:
LockWindow()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.
OnKeyEvent(GtkWidget * widget,GdkEventKey * event)209 virtual gboolean OnKeyEvent(GtkWidget* widget, GdkEventKey* event) {
210 // Don't handle key event in the lock window.
211 return false;
212 }
213
OnDestroy(GtkWidget * object)214 virtual void OnDestroy(GtkWidget* object) {
215 VLOG(1) << "OnDestroy: LockWindow destroyed";
216 views::WidgetGtk::OnDestroy(object);
217 }
218
ClearNativeFocus()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.
set_toplevel_focus_widget(GtkWidget * widget)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:
GrabWidgetRootView(chromeos::ScreenLockView * screen_lock_view)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.
Layout()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:
SetScreenLockView(views::View * screen_lock_view)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:
GrabWidget(chromeos::ScreenLocker * screen_locker)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
Show()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
ClearGtkGrab()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
OnKeyEvent(GtkWidget * widget,GdkEventKey * event)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
OnButtonPress(GtkWidget * widget,GdkEventButton * event)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:
HandleGtkGrabBroke()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.
FailedWithGrabAlreadyGrabbed()371 void FailedWithGrabAlreadyGrabbed() {
372 LOG(FATAL) << "Grab already grabbed";
373 }
FailedWithGrabInvalidTime()374 void FailedWithGrabInvalidTime() {
375 LOG(FATAL) << "Grab invalid time";
376 }
FailedWithGrabNotViewable()377 void FailedWithGrabNotViewable() {
378 LOG(FATAL) << "Grab not viewable";
379 }
FailedWithGrabFrozen()380 void FailedWithGrabFrozen() {
381 LOG(FATAL) << "Grab frozen";
382 }
FailedWithUnknownError()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
TryGrabAllInputs()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
TryUngrabOtherClients()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:
ScreenLockerBackgroundView(views::WidgetGtk * lock_widget,views::View * screen_lock_view)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
GetScreenMode() const523 virtual ScreenMode GetScreenMode() const {
524 return kScreenLockerMode;
525 }
526
Layout()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:
SetScreenLockView(views::View * screen_lock_view)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:
MouseEventRelay(GdkWindow * src,GdkWindow * dest)568 MouseEventRelay(GdkWindow* src, GdkWindow* dest)
569 : src_(src),
570 dest_(dest),
571 initialized_(false) {
572 DCHECK(src_);
573 DCHECK(dest_);
574 }
575
WillProcessEvent(GdkEvent * event)576 virtual void WillProcessEvent(GdkEvent* event) {}
577
DidProcessEvent(GdkEvent * event)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:
InputEventObserver(ScreenLocker * screen_locker)630 explicit InputEventObserver(ScreenLocker* screen_locker)
631 : screen_locker_(screen_locker),
632 activated_(false) {
633 }
634
WillProcessEvent(GdkEvent * event)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
DidProcessEvent(GdkEvent * event)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:
LockerInputEventObserver(ScreenLocker * screen_locker)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
WillProcessEvent(GdkEvent * event)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
DidProcessEvent(GdkEvent * event)682 virtual void DidProcessEvent(GdkEvent* event) {
683 }
684
685 private:
StartScreenSaver()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
ScreenLocker(const UserManager::User & user)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
Init()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
OnLoginFailure(const LoginFailure & error)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
OnLoginSuccess(const std::string & username,const std::string & password,const GaiaAuthConsumer::ClientLoginResult & unused,bool pending_requests)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
BubbleClosing(Bubble * bubble,bool closed_by_escape)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
OnCaptchaEntered(const std::string & captcha)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
Authenticate(const string16 & password)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
ClearErrors()920 void ScreenLocker::ClearErrors() {
921 if (error_info_) {
922 error_info_->Close();
923 error_info_ = NULL;
924 }
925 }
926
EnableInput()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
Signout()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
ShowCaptchaAndErrorMessage(const GURL & captcha_url,const std::wstring & message)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
ShowErrorMessage(const std::wstring & message,bool sign_out_only)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
OnGrabInputs()982 void ScreenLocker::OnGrabInputs() {
983 DVLOG(1) << "OnGrabInputs";
984 input_grabbed_ = true;
985 if (drawn_)
986 ScreenLockReady();
987 }
988
GetViewByID(int id)989 views::View* ScreenLocker::GetViewByID(int id) {
990 return lock_widget_->GetRootView()->GetViewByID(id);
991 }
992
993 // static
Show()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
Hide()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
UnlockScreenFailed()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
InitClass()1049 void ScreenLocker::InitClass() {
1050 g_screen_lock_observer.Get();
1051 }
1052
1053 ////////////////////////////////////////////////////////////////////////////////
1054 // ScreenLocker, private:
1055
~ScreenLocker()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
SetAuthenticator(Authenticator * authenticator)1085 void ScreenLocker::SetAuthenticator(Authenticator* authenticator) {
1086 authenticator_ = authenticator;
1087 }
1088
ScreenLockReady()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
OnClientEvent(GtkWidget * widge,GdkEventClient * event)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
OnWindowManagerReady()1124 void ScreenLocker::OnWindowManagerReady() {
1125 DVLOG(1) << "OnClientEvent: drawn for lock";
1126 drawn_ = true;
1127 if (input_grabbed_)
1128 ScreenLockReady();
1129 }
1130
ShowErrorBubble(const std::wstring & message,BubbleBorder::ArrowLocation arrow_location)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
StopScreenSaver()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
StartScreenSaver()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
AcceleratorPressed(const views::Accelerator & accelerator)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