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
59namespace {
60
61// The maximum duration for which locker should try to grab the keyboard and
62// mouse and its interval for regrabbing on failure.
63const int kMaxGrabFailureSec = 30;
64const 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.
68const int kMaxGrabFailures = kMaxGrabFailureSec * 1000 / kRetryGrabIntervalMs;
69
70// A idle time to show the screen saver in seconds.
71const int kScreenSaverIdleTimeout = 15;
72
73// Observer to start ScreenLocker when the screen lock
74class 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
194static 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.
199class 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.
244class 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.
284class 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
402void 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
463void 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.
513class 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
559namespace chromeos {
560
561// static
562ScreenLocker* 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.
566class 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.
628class 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.
664class 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
699ScreenLocker::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
719void 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
806void 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
835void 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
863void 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
872void 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
895void 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
920void ScreenLocker::ClearErrors() {
921  if (error_info_) {
922    error_info_->Close();
923    error_info_ = NULL;
924  }
925}
926
927void 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
935void 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
948void 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
969void 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
982void ScreenLocker::OnGrabInputs() {
983  DVLOG(1) << "OnGrabInputs";
984  input_grabbed_ = true;
985  if (drawn_)
986    ScreenLockReady();
987}
988
989views::View* ScreenLocker::GetViewByID(int id) {
990  return lock_widget_->GetRootView()->GetViewByID(id);
991}
992
993// static
994void 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
1023void 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
1031void 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
1049void ScreenLocker::InitClass() {
1050  g_screen_lock_observer.Get();
1051}
1052
1053////////////////////////////////////////////////////////////////////////////////
1054// ScreenLocker, private:
1055
1056ScreenLocker::~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
1085void ScreenLocker::SetAuthenticator(Authenticator* authenticator) {
1086  authenticator_ = authenticator;
1087}
1088
1089void 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
1116void 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
1124void ScreenLocker::OnWindowManagerReady() {
1125  DVLOG(1) << "OnClientEvent: drawn for lock";
1126  drawn_ = true;
1127  if (input_grabbed_)
1128    ScreenLockReady();
1129}
1130
1131void 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
1157void 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
1169void 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
1183bool 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