// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/login/screen_locker.h"
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
#include <string>
#include <vector>
// Evil hack to undo X11 evil #define. See crosbug.com/
#undef Status
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/string_util.h"
#include "base/timer.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/chromeos/cros/input_method_library.h"
#include "chrome/browser/chromeos/cros/login_library.h"
#include "chrome/browser/chromeos/cros/screen_lock_library.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "chrome/browser/chromeos/language_preferences.h"
#include "chrome/browser/chromeos/login/authenticator.h"
#include "chrome/browser/chromeos/login/background_view.h"
#include "chrome/browser/chromeos/login/login_performer.h"
#include "chrome/browser/chromeos/login/login_utils.h"
#include "chrome/browser/chromeos/login/message_bubble.h"
#include "chrome/browser/chromeos/login/screen_lock_view.h"
#include "chrome/browser/chromeos/login/shutdown_button.h"
#include "chrome/browser/chromeos/system_key_event_listener.h"
#include "chrome/browser/chromeos/view_ids.h"
#include "chrome/browser/chromeos/wm_ipc.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/chrome_switches.h"
#include "content/browser/browser_thread.h"
#include "content/common/notification_service.h"
#include "googleurl/src/gurl.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "third_party/cros/chromeos_wm_ipc_enums.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/x/x11_util.h"
#include "views/screen.h"
#include "views/widget/root_view.h"
#include "views/widget/widget_gtk.h"
namespace {
// The maximum duration for which locker should try to grab the keyboard and
// mouse and its interval for regrabbing on failure.
const int kMaxGrabFailureSec = 30;
const int64 kRetryGrabIntervalMs = 500;
// Maximum number of times we'll try to grab the keyboard and mouse before
// giving up. If we hit the limit, Chrome exits and the session is terminated.
const int kMaxGrabFailures = kMaxGrabFailureSec * 1000 / kRetryGrabIntervalMs;
// A idle time to show the screen saver in seconds.
const int kScreenSaverIdleTimeout = 15;
// Observer to start ScreenLocker when the screen lock
class ScreenLockObserver : public chromeos::ScreenLockLibrary::Observer,
public NotificationObserver {
public:
ScreenLockObserver() {
registrar_.Add(this, NotificationType::LOGIN_USER_CHANGED,
NotificationService::AllSources());
}
// NotificationObserver overrides:
virtual void Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
if (type == NotificationType::LOGIN_USER_CHANGED) {
// Register Screen Lock after login screen to make sure
// we don't show the screen lock on top of the login screen by accident.
if (chromeos::CrosLibrary::Get()->EnsureLoaded())
chromeos::CrosLibrary::Get()->GetScreenLockLibrary()->AddObserver(this);
}
}
virtual void LockScreen(chromeos::ScreenLockLibrary* obj) {
VLOG(1) << "In: ScreenLockObserver::LockScreen";
SetupInputMethodsForScreenLocker();
chromeos::ScreenLocker::Show();
}
virtual void UnlockScreen(chromeos::ScreenLockLibrary* obj) {
RestoreInputMethods();
chromeos::ScreenLocker::Hide();
}
virtual void UnlockScreenFailed(chromeos::ScreenLockLibrary* obj) {
chromeos::ScreenLocker::UnlockScreenFailed();
}
private:
// Temporarily deactivates all input methods (e.g. Chinese, Japanese, Arabic)
// since they are not necessary to input a login password. Users are still
// able to use/switch active keyboard layouts (e.g. US qwerty, US dvorak,
// French).
void SetupInputMethodsForScreenLocker() {
if (chromeos::CrosLibrary::Get()->EnsureLoaded() &&
// The LockScreen function is also called when the OS is suspended, and
// in that case |saved_active_input_method_list_| might be non-empty.
saved_active_input_method_list_.empty()) {
chromeos::InputMethodLibrary* library =
chromeos::CrosLibrary::Get()->GetInputMethodLibrary();
saved_previous_input_method_id_ = library->previous_input_method().id;
saved_current_input_method_id_ = library->current_input_method().id;
scoped_ptr<chromeos::InputMethodDescriptors> active_input_method_list(
library->GetActiveInputMethods());
const std::string hardware_keyboard_id =
chromeos::input_method::GetHardwareInputMethodId();
// We'll add the hardware keyboard if it's not included in
// |active_input_method_list| so that the user can always use the hardware
// keyboard on the screen locker.
bool should_add_hardware_keyboard = true;
chromeos::ImeConfigValue value;
value.type = chromeos::ImeConfigValue::kValueTypeStringList;
for (size_t i = 0; i < active_input_method_list->size(); ++i) {
const std::string& input_method_id = active_input_method_list->at(i).id;
saved_active_input_method_list_.push_back(input_method_id);
// Skip if it's not a keyboard layout.
if (!chromeos::input_method::IsKeyboardLayout(input_method_id))
continue;
value.string_list_value.push_back(input_method_id);
if (input_method_id == hardware_keyboard_id) {
should_add_hardware_keyboard = false;
}
}
if (should_add_hardware_keyboard) {
value.string_list_value.push_back(hardware_keyboard_id);
}
// We don't want to shut down the IME, even if the hardware layout is the
// only IME left.
library->SetEnableAutoImeShutdown(false);
library->SetImeConfig(
chromeos::language_prefs::kGeneralSectionName,
chromeos::language_prefs::kPreloadEnginesConfigName,
value);
}
}
void RestoreInputMethods() {
if (chromeos::CrosLibrary::Get()->EnsureLoaded() &&
!saved_active_input_method_list_.empty()) {
chromeos::InputMethodLibrary* library =
chromeos::CrosLibrary::Get()->GetInputMethodLibrary();
chromeos::ImeConfigValue value;
value.type = chromeos::ImeConfigValue::kValueTypeStringList;
value.string_list_value = saved_active_input_method_list_;
library->SetEnableAutoImeShutdown(true);
library->SetImeConfig(
chromeos::language_prefs::kGeneralSectionName,
chromeos::language_prefs::kPreloadEnginesConfigName,
value);
// Send previous input method id first so Ctrl+space would work fine.
if (!saved_previous_input_method_id_.empty())
library->ChangeInputMethod(saved_previous_input_method_id_);
if (!saved_current_input_method_id_.empty())
library->ChangeInputMethod(saved_current_input_method_id_);
saved_previous_input_method_id_.clear();
saved_current_input_method_id_.clear();
saved_active_input_method_list_.clear();
}
}
NotificationRegistrar registrar_;
std::string saved_previous_input_method_id_;
std::string saved_current_input_method_id_;
std::vector<std::string> saved_active_input_method_list_;
DISALLOW_COPY_AND_ASSIGN(ScreenLockObserver);
};
static base::LazyInstance<ScreenLockObserver> g_screen_lock_observer(
base::LINKER_INITIALIZED);
// A ScreenLock window that covers entire screen to keep the keyboard
// focus/events inside the grab widget.
class LockWindow : public views::WidgetGtk {
public:
LockWindow()
: views::WidgetGtk(views::WidgetGtk::TYPE_WINDOW),
toplevel_focus_widget_(NULL) {
EnableDoubleBuffer(true);
}
// GTK propagates key events from parents to children.
// Make sure LockWindow will never handle key events.
virtual gboolean OnKeyEvent(GtkWidget* widget, GdkEventKey* event) {
// Don't handle key event in the lock window.
return false;
}
virtual void OnDestroy(GtkWidget* object) {
VLOG(1) << "OnDestroy: LockWindow destroyed";
views::WidgetGtk::OnDestroy(object);
}
virtual void ClearNativeFocus() {
DCHECK(toplevel_focus_widget_);
gtk_widget_grab_focus(toplevel_focus_widget_);
}
// Sets the widget to move the focus to when clearning the native
// widget's focus.
void set_toplevel_focus_widget(GtkWidget* widget) {
GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_FOCUS);
toplevel_focus_widget_ = widget;
}
private:
// The widget we set focus to when clearning the focus on native
// widget. In screen locker, gdk input is grabbed in GrabWidget,
// and resetting the focus by using gtk_window_set_focus seems to
// confuse gtk and doesn't let focus move to native widget under
// GrabWidget.
GtkWidget* toplevel_focus_widget_;
DISALLOW_COPY_AND_ASSIGN(LockWindow);
};
// GrabWidget's root view to layout the ScreenLockView at the center
// and the Shutdown button at the right bottom.
class GrabWidgetRootView
: public views::View,
public chromeos::ScreenLocker::ScreenLockViewContainer {
public:
explicit GrabWidgetRootView(chromeos::ScreenLockView* screen_lock_view)
: screen_lock_view_(screen_lock_view),
shutdown_button_(new chromeos::ShutdownButton()) {
shutdown_button_->Init();
AddChildView(screen_lock_view_);
AddChildView(shutdown_button_);
}
// views::View implementation.
virtual void Layout() {
gfx::Size size = screen_lock_view_->GetPreferredSize();
screen_lock_view_->SetBounds(0, 0, size.width(), size.height());
shutdown_button_->LayoutIn(this);
}
// ScreenLocker::ScreenLockViewContainer implementation:
void SetScreenLockView(views::View* screen_lock_view) {
if (screen_lock_view_) {
RemoveChildView(screen_lock_view_);
}
screen_lock_view_ = screen_lock_view;
if (screen_lock_view_) {
AddChildViewAt(screen_lock_view_, 0);
}
Layout();
}
private:
views::View* screen_lock_view_;
chromeos::ShutdownButton* shutdown_button_;
DISALLOW_COPY_AND_ASSIGN(GrabWidgetRootView);
};
// A child widget that grabs both keyboard and pointer input.
class GrabWidget : public views::WidgetGtk {
public:
explicit GrabWidget(chromeos::ScreenLocker* screen_locker)
: views::WidgetGtk(views::WidgetGtk::TYPE_CHILD),
screen_locker_(screen_locker),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
grab_failure_count_(0),
kbd_grab_status_(GDK_GRAB_INVALID_TIME),
mouse_grab_status_(GDK_GRAB_INVALID_TIME),
signout_link_(NULL),
shutdown_(NULL) {
}
virtual void Show() {
views::WidgetGtk::Show();
signout_link_ =
screen_locker_->GetViewByID(VIEW_ID_SCREEN_LOCKER_SIGNOUT_LINK);
shutdown_ = screen_locker_->GetViewByID(VIEW_ID_SCREEN_LOCKER_SHUTDOWN);
// These can be null in guest mode.
}
void ClearGtkGrab() {
GtkWidget* current_grab_window;
// Grab gtk input first so that the menu holding gtk grab will
// close itself.
gtk_grab_add(window_contents());
// Make sure there is no gtk grab widget so that gtk simply propagates
// an event. This is necessary to allow message bubble and password
// field, button to process events simultaneously. GTK
// maintains grab widgets in a linked-list, so we need to remove
// until it's empty.
while ((current_grab_window = gtk_grab_get_current()) != NULL)
gtk_grab_remove(current_grab_window);
}
virtual gboolean OnKeyEvent(GtkWidget* widget, GdkEventKey* event) {
views::KeyEvent key_event(reinterpret_cast<GdkEvent*>(event));
// This is a hack to workaround the issue crosbug.com/10655 due to
// the limitation that a focus manager cannot handle views in
// TYPE_CHILD WidgetGtk correctly.
if (signout_link_ &&
event->type == GDK_KEY_PRESS &&
(event->keyval == GDK_Tab ||
event->keyval == GDK_ISO_Left_Tab ||
event->keyval == GDK_KP_Tab)) {
DCHECK(shutdown_);
bool reverse = event->state & GDK_SHIFT_MASK;
if (reverse && signout_link_->HasFocus()) {
shutdown_->RequestFocus();
return true;
}
if (!reverse && shutdown_->HasFocus()) {
signout_link_->RequestFocus();
return true;
}
}
return views::WidgetGtk::OnKeyEvent(widget, event);
}
virtual gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event) {
WidgetGtk::OnButtonPress(widget, event);
// Never propagate event to parent.
return true;
}
// Try to grab all inputs. It initiates another try if it fails to
// grab and the retry count is within a limit, or fails with CHECK.
void TryGrabAllInputs();
// This method tries to steal pointer/keyboard grab from other
// client by sending events that will hopefully close menus or windows
// that have the grab.
void TryUngrabOtherClients();
private:
virtual void HandleGtkGrabBroke() {
// Input should never be stolen from ScreenLocker once it's
// grabbed. If this happens, it's a bug and has to be fixed. We
// let chrome crash to get a crash report and dump, and
// SessionManager will terminate the session to logout.
CHECK_NE(GDK_GRAB_SUCCESS, kbd_grab_status_);
CHECK_NE(GDK_GRAB_SUCCESS, mouse_grab_status_);
}
// Define separate methods for each error code so that stack trace
// will tell which error the grab failed with.
void FailedWithGrabAlreadyGrabbed() {
LOG(FATAL) << "Grab already grabbed";
}
void FailedWithGrabInvalidTime() {
LOG(FATAL) << "Grab invalid time";
}
void FailedWithGrabNotViewable() {
LOG(FATAL) << "Grab not viewable";
}
void FailedWithGrabFrozen() {
LOG(FATAL) << "Grab frozen";
}
void FailedWithUnknownError() {
LOG(FATAL) << "Grab uknown";
}
chromeos::ScreenLocker* screen_locker_;
ScopedRunnableMethodFactory<GrabWidget> task_factory_;
// The number times the widget tried to grab all focus.
int grab_failure_count_;
// Status of keyboard and mouse grab.
GdkGrabStatus kbd_grab_status_;
GdkGrabStatus mouse_grab_status_;
views::View* signout_link_;
views::View* shutdown_;
DISALLOW_COPY_AND_ASSIGN(GrabWidget);
};
void GrabWidget::TryGrabAllInputs() {
// Grab x server so that we can atomically grab and take
// action when grab fails.
gdk_x11_grab_server();
if (kbd_grab_status_ != GDK_GRAB_SUCCESS) {
kbd_grab_status_ = gdk_keyboard_grab(window_contents()->window, FALSE,
GDK_CURRENT_TIME);
}
if (mouse_grab_status_ != GDK_GRAB_SUCCESS) {
mouse_grab_status_ =
gdk_pointer_grab(window_contents()->window,
FALSE,
static_cast<GdkEventMask>(
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
GDK_POINTER_MOTION_MASK),
NULL,
NULL,
GDK_CURRENT_TIME);
}
if ((kbd_grab_status_ != GDK_GRAB_SUCCESS ||
mouse_grab_status_ != GDK_GRAB_SUCCESS) &&
grab_failure_count_++ < kMaxGrabFailures) {
LOG(WARNING) << "Failed to grab inputs. Trying again in "
<< kRetryGrabIntervalMs << " ms: kbd="
<< kbd_grab_status_ << ", mouse=" << mouse_grab_status_;
TryUngrabOtherClients();
gdk_x11_ungrab_server();
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
task_factory_.NewRunnableMethod(&GrabWidget::TryGrabAllInputs),
kRetryGrabIntervalMs);
} else {
gdk_x11_ungrab_server();
GdkGrabStatus status = kbd_grab_status_;
if (status == GDK_GRAB_SUCCESS) {
status = mouse_grab_status_;
}
switch (status) {
case GDK_GRAB_SUCCESS:
break;
case GDK_GRAB_ALREADY_GRABBED:
FailedWithGrabAlreadyGrabbed();
break;
case GDK_GRAB_INVALID_TIME:
FailedWithGrabInvalidTime();
break;
case GDK_GRAB_NOT_VIEWABLE:
FailedWithGrabNotViewable();
break;
case GDK_GRAB_FROZEN:
FailedWithGrabFrozen();
break;
default:
FailedWithUnknownError();
break;
}
DVLOG(1) << "Grab Success";
screen_locker_->OnGrabInputs();
}
}
void GrabWidget::TryUngrabOtherClients() {
#if !defined(NDEBUG)
{
int event_base, error_base;
int major, minor;
// Make sure we have XTest extension.
DCHECK(XTestQueryExtension(ui::GetXDisplay(),
&event_base, &error_base,
&major, &minor));
}
#endif
// The following code is an attempt to grab inputs by closing
// supposedly opened menu. This happens when a plugin has a menu
// opened.
if (mouse_grab_status_ == GDK_GRAB_ALREADY_GRABBED ||
mouse_grab_status_ == GDK_GRAB_FROZEN) {
// Successfully grabbed the keyboard, but pointer is still
// grabbed by other client. Another attempt to close supposedly
// opened menu by emulating keypress at the left top corner.
Display* display = ui::GetXDisplay();
Window root, child;
int root_x, root_y, win_x, winy;
unsigned int mask;
XQueryPointer(display,
ui::GetX11WindowFromGtkWidget(window_contents()),
&root, &child, &root_x, &root_y,
&win_x, &winy, &mask);
XTestFakeMotionEvent(display, -1, -10000, -10000, CurrentTime);
XTestFakeButtonEvent(display, 1, True, CurrentTime);
XTestFakeButtonEvent(display, 1, False, CurrentTime);
// Move the pointer back.
XTestFakeMotionEvent(display, -1, root_x, root_y, CurrentTime);
XFlush(display);
} else if (kbd_grab_status_ == GDK_GRAB_ALREADY_GRABBED ||
kbd_grab_status_ == GDK_GRAB_FROZEN) {
// Successfully grabbed the pointer, but keyboard is still grabbed
// by other client. Another attempt to close supposedly opened
// menu by emulating escape key. Such situation must be very
// rare, but handling this just in case
Display* display = ui::GetXDisplay();
KeyCode escape = XKeysymToKeycode(display, XK_Escape);
XTestFakeKeyEvent(display, escape, True, CurrentTime);
XTestFakeKeyEvent(display, escape, False, CurrentTime);
XFlush(display);
}
}
// BackgroundView for ScreenLocker, which layouts a lock widget in
// addition to other background components.
class ScreenLockerBackgroundView
: public chromeos::BackgroundView,
public chromeos::ScreenLocker::ScreenLockViewContainer {
public:
ScreenLockerBackgroundView(views::WidgetGtk* lock_widget,
views::View* screen_lock_view)
: lock_widget_(lock_widget),
screen_lock_view_(screen_lock_view) {
}
virtual ScreenMode GetScreenMode() const {
return kScreenLockerMode;
}
virtual void Layout() {
chromeos::BackgroundView::Layout();
gfx::Rect screen = bounds();
if (screen_lock_view_) {
gfx::Size size = screen_lock_view_->GetPreferredSize();
gfx::Point origin((screen.width() - size.width()) / 2,
(screen.height() - size.height()) / 2);
gfx::Size widget_size(screen.size());
widget_size.Enlarge(-origin.x(), -origin.y());
lock_widget_->SetBounds(gfx::Rect(origin, widget_size));
} else {
// No password entry. Move the lock widget to off screen.
lock_widget_->SetBounds(gfx::Rect(-100, -100, 1, 1));
}
}
// ScreenLocker::ScreenLockViewContainer implementation:
void SetScreenLockView(views::View* screen_lock_view) {
screen_lock_view_ = screen_lock_view;
Layout();
}
private:
views::WidgetGtk* lock_widget_;
views::View* screen_lock_view_;
DISALLOW_COPY_AND_ASSIGN(ScreenLockerBackgroundView);
};
} // namespace
namespace chromeos {
// static
ScreenLocker* ScreenLocker::screen_locker_ = NULL;
// A event observer that forwards gtk events from one window to another.
// See screen_locker.h for more details.
class MouseEventRelay : public MessageLoopForUI::Observer {
public:
MouseEventRelay(GdkWindow* src, GdkWindow* dest)
: src_(src),
dest_(dest),
initialized_(false) {
DCHECK(src_);
DCHECK(dest_);
}
virtual void WillProcessEvent(GdkEvent* event) {}
virtual void DidProcessEvent(GdkEvent* event) {
if (event->any.window != src_)
return;
if (!initialized_) {
gint src_x, src_y, dest_x, dest_y, width, height, depth;
gdk_window_get_geometry(dest_, &dest_x, &dest_y, &width, &height, &depth);
// wait to compute offset until the info bubble widget's location
// is available.
if (dest_x < 0 || dest_y < 0)
return;
gdk_window_get_geometry(src_, &src_x, &src_y, &width, &height, &depth);
offset_.SetPoint(dest_x - src_x, dest_y - src_y);
initialized_ = true;
}
if (event->type == GDK_BUTTON_PRESS ||
event->type == GDK_BUTTON_RELEASE) {
GdkEvent* copy = gdk_event_copy(event);
copy->button.window = dest_;
g_object_ref(copy->button.window);
copy->button.x -= offset_.x();
copy->button.y -= offset_.y();
gdk_event_put(copy);
gdk_event_free(copy);
} else if (event->type == GDK_MOTION_NOTIFY) {
GdkEvent* copy = gdk_event_copy(event);
copy->motion.window = dest_;
g_object_ref(copy->motion.window);
copy->motion.x -= offset_.x();
copy->motion.y -= offset_.y();
gdk_event_put(copy);
gdk_event_free(copy);
}
}
private:
GdkWindow* src_;
GdkWindow* dest_;
bool initialized_;
// Offset from src_'s origin to dest_'s origin.
gfx::Point offset_;
DISALLOW_COPY_AND_ASSIGN(MouseEventRelay);
};
// A event observer used to unlock the screen upon user's action
// without asking password. Used in BWSI and auto login mode.
// TODO(oshima): consolidate InputEventObserver and LockerInputEventObserver.
class InputEventObserver : public MessageLoopForUI::Observer {
public:
explicit InputEventObserver(ScreenLocker* screen_locker)
: screen_locker_(screen_locker),
activated_(false) {
}
virtual void WillProcessEvent(GdkEvent* event) {
if ((event->type == GDK_KEY_PRESS ||
event->type == GDK_BUTTON_PRESS ||
event->type == GDK_MOTION_NOTIFY) &&
!activated_) {
activated_ = true;
std::string not_used_string;
GaiaAuthConsumer::ClientLoginResult not_used;
screen_locker_->OnLoginSuccess(not_used_string,
not_used_string,
not_used,
false);
}
}
virtual void DidProcessEvent(GdkEvent* event) {
}
private:
chromeos::ScreenLocker* screen_locker_;
bool activated_;
DISALLOW_COPY_AND_ASSIGN(InputEventObserver);
};
// A event observer used to show the screen locker upon
// user action: mouse or keyboard interactions.
// TODO(oshima): this has to be disabled while authenticating.
class LockerInputEventObserver : public MessageLoopForUI::Observer {
public:
explicit LockerInputEventObserver(ScreenLocker* screen_locker)
: screen_locker_(screen_locker),
ALLOW_THIS_IN_INITIALIZER_LIST(
timer_(base::TimeDelta::FromSeconds(kScreenSaverIdleTimeout), this,
&LockerInputEventObserver::StartScreenSaver)) {
}
virtual void WillProcessEvent(GdkEvent* event) {
if ((event->type == GDK_KEY_PRESS ||
event->type == GDK_BUTTON_PRESS ||
event->type == GDK_MOTION_NOTIFY)) {
timer_.Reset();
screen_locker_->StopScreenSaver();
}
}
virtual void DidProcessEvent(GdkEvent* event) {
}
private:
void StartScreenSaver() {
screen_locker_->StartScreenSaver();
}
chromeos::ScreenLocker* screen_locker_;
base::DelayTimer<LockerInputEventObserver> timer_;
DISALLOW_COPY_AND_ASSIGN(LockerInputEventObserver);
};
//////////////////////////////////////////////////////////////////////////////
// ScreenLocker, public:
ScreenLocker::ScreenLocker(const UserManager::User& user)
: lock_window_(NULL),
lock_widget_(NULL),
screen_lock_view_(NULL),
captcha_view_(NULL),
grab_container_(NULL),
background_container_(NULL),
user_(user),
error_info_(NULL),
drawn_(false),
input_grabbed_(false),
// TODO(oshima): support auto login mode (this is not implemented yet)
// http://crosbug.com/1881
unlock_on_input_(user_.email().empty()),
locked_(false),
start_time_(base::Time::Now()) {
DCHECK(!screen_locker_);
screen_locker_ = this;
}
void ScreenLocker::Init() {
static const GdkColor kGdkBlack = {0, 0, 0, 0};
authenticator_ = LoginUtils::Get()->CreateAuthenticator(this);
gfx::Point left_top(1, 1);
gfx::Rect init_bounds(views::Screen::GetMonitorAreaNearestPoint(left_top));
LockWindow* lock_window = new LockWindow();
lock_window_ = lock_window;
lock_window_->Init(NULL, init_bounds);
gtk_widget_modify_bg(
lock_window_->GetNativeView(), GTK_STATE_NORMAL, &kGdkBlack);
g_signal_connect(lock_window_->GetNativeView(), "client-event",
G_CALLBACK(OnClientEventThunk), this);
// GTK does not like zero width/height.
if (!unlock_on_input_) {
screen_lock_view_ = new ScreenLockView(this);
screen_lock_view_->Init();
screen_lock_view_->SetEnabled(false);
screen_lock_view_->StartThrobber();
} else {
input_event_observer_.reset(new InputEventObserver(this));
MessageLoopForUI::current()->AddObserver(input_event_observer_.get());
}
// Hang on to a cast version of the grab widget so we can call its
// TryGrabAllInputs() method later. (Nobody else needs to use it, so moving
// its declaration to the header instead of keeping it in an anonymous
// namespace feels a bit ugly.)
GrabWidget* cast_lock_widget = new GrabWidget(this);
lock_widget_ = cast_lock_widget;
lock_widget_->MakeTransparent();
lock_widget_->InitWithWidget(lock_window_, gfx::Rect());
if (screen_lock_view_) {
GrabWidgetRootView* root_view = new GrabWidgetRootView(screen_lock_view_);
grab_container_ = root_view;
lock_widget_->SetContentsView(root_view);
}
lock_widget_->Show();
// Configuring the background url.
std::string url_string =
CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kScreenSaverUrl);
ScreenLockerBackgroundView* screen_lock_background_view_ =
new ScreenLockerBackgroundView(lock_widget_, screen_lock_view_);
background_container_ = screen_lock_background_view_;
background_view_ = screen_lock_background_view_;
background_view_->Init(GURL(url_string));
if (background_view_->ScreenSaverEnabled())
StartScreenSaver();
DCHECK(GTK_WIDGET_REALIZED(lock_window_->GetNativeView()));
WmIpc::instance()->SetWindowType(
lock_window_->GetNativeView(),
WM_IPC_WINDOW_CHROME_SCREEN_LOCKER,
NULL);
lock_window_->SetContentsView(background_view_);
lock_window_->Show();
cast_lock_widget->ClearGtkGrab();
// Call this after lock_window_->Show(); otherwise the 1st invocation
// of gdk_xxx_grab() will always fail.
cast_lock_widget->TryGrabAllInputs();
// Add the window to its own group so that its grab won't be stolen if
// gtk_grab_add() gets called on behalf on a non-screen-locker widget (e.g.
// a modal dialog) -- see http://crosbug.com/8999. We intentionally do this
// after calling ClearGtkGrab(), as want to be in the default window group
// then so we can break any existing GTK grabs.
GtkWindowGroup* window_group = gtk_window_group_new();
gtk_window_group_add_window(window_group,
GTK_WINDOW(lock_window_->GetNativeView()));
g_object_unref(window_group);
lock_window->set_toplevel_focus_widget(lock_widget_->window_contents());
// Create the SystemKeyEventListener so it can listen for system keyboard
// messages regardless of focus while screen locked.
SystemKeyEventListener::GetInstance();
}
void ScreenLocker::OnLoginFailure(const LoginFailure& error) {
DVLOG(1) << "OnLoginFailure";
UserMetrics::RecordAction(UserMetricsAction("ScreenLocker_OnLoginFailure"));
if (authentication_start_time_.is_null()) {
LOG(ERROR) << "authentication_start_time_ is not set";
} else {
base::TimeDelta delta = base::Time::Now() - authentication_start_time_;
VLOG(1) << "Authentication failure time: " << delta.InSecondsF();
UMA_HISTOGRAM_TIMES("ScreenLocker.AuthenticationFailureTime", delta);
}
EnableInput();
// Don't enable signout button here as we're showing
// MessageBubble.
string16 msg = l10n_util::GetStringUTF16(IDS_LOGIN_ERROR_AUTHENTICATING);
const std::string error_text = error.GetErrorString();
if (!error_text.empty())
msg += ASCIIToUTF16("\n") + ASCIIToUTF16(error_text);
InputMethodLibrary* input_method_library =
CrosLibrary::Get()->GetInputMethodLibrary();
if (input_method_library->GetNumActiveInputMethods() > 1)
msg += ASCIIToUTF16("\n") +
l10n_util::GetStringUTF16(IDS_LOGIN_ERROR_KEYBOARD_SWITCH_HINT);
ShowErrorBubble(UTF16ToWide(msg), BubbleBorder::BOTTOM_LEFT);
}
void ScreenLocker::OnLoginSuccess(
const std::string& username,
const std::string& password,
const GaiaAuthConsumer::ClientLoginResult& unused,
bool pending_requests) {
VLOG(1) << "OnLoginSuccess: Sending Unlock request.";
if (authentication_start_time_.is_null()) {
if (!username.empty())
LOG(WARNING) << "authentication_start_time_ is not set";
} else {
base::TimeDelta delta = base::Time::Now() - authentication_start_time_;
VLOG(1) << "Authentication success time: " << delta.InSecondsF();
UMA_HISTOGRAM_TIMES("ScreenLocker.AuthenticationSuccessTime", delta);
}
Profile* profile = ProfileManager::GetDefaultProfile();
if (profile) {
ProfileSyncService* service = profile->GetProfileSyncService(username);
if (service && !service->HasSyncSetupCompleted()) {
// If sync has failed somehow, try setting the sync passphrase here.
service->SetPassphrase(password, false, true);
}
}
if (CrosLibrary::Get()->EnsureLoaded())
CrosLibrary::Get()->GetScreenLockLibrary()->NotifyScreenUnlockRequested();
}
void ScreenLocker::BubbleClosing(Bubble* bubble, bool closed_by_escape) {
error_info_ = NULL;
screen_lock_view_->SetSignoutEnabled(true);
if (mouse_event_relay_.get()) {
MessageLoopForUI::current()->RemoveObserver(mouse_event_relay_.get());
mouse_event_relay_.reset();
}
}
void ScreenLocker::OnCaptchaEntered(const std::string& captcha) {
// Captcha dialog is only shown when LoginPerformer instance exists,
// i.e. blocking UI after password change is in place.
DCHECK(LoginPerformer::default_performer());
LoginPerformer::default_performer()->set_captcha(captcha);
// ScreenLockView ownership is passed to grab_container_.
// Need to save return value here so that compile
// doesn't fail with "unused result" warning.
views::View* view = secondary_view_.release();
view = NULL;
captcha_view_->SetVisible(false);
grab_container_->SetScreenLockView(screen_lock_view_);
background_container_->SetScreenLockView(screen_lock_view_);
screen_lock_view_->SetVisible(true);
screen_lock_view_->ClearAndSetFocusToPassword();
// Take CaptchaView ownership now that it's removed from grab_container_.
secondary_view_.reset(captcha_view_);
ShowErrorMessage(postponed_error_message_, false);
postponed_error_message_.clear();
}
void ScreenLocker::Authenticate(const string16& password) {
if (password.empty())
return;
authentication_start_time_ = base::Time::Now();
screen_lock_view_->SetEnabled(false);
screen_lock_view_->SetSignoutEnabled(false);
screen_lock_view_->StartThrobber();
// If LoginPerformer instance exists,
// initial online login phase is still active.
if (LoginPerformer::default_performer()) {
DVLOG(1) << "Delegating authentication to LoginPerformer.";
LoginPerformer::default_performer()->Login(user_.email(),
UTF16ToUTF8(password));
} else {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
NewRunnableMethod(authenticator_.get(),
&Authenticator::AuthenticateToUnlock,
user_.email(),
UTF16ToUTF8(password)));
}
}
void ScreenLocker::ClearErrors() {
if (error_info_) {
error_info_->Close();
error_info_ = NULL;
}
}
void ScreenLocker::EnableInput() {
if (screen_lock_view_) {
screen_lock_view_->SetEnabled(true);
screen_lock_view_->ClearAndSetFocusToPassword();
screen_lock_view_->StopThrobber();
}
}
void ScreenLocker::Signout() {
if (!error_info_) {
UserMetrics::RecordAction(UserMetricsAction("ScreenLocker_Signout"));
WmIpc::instance()->NotifyAboutSignout();
if (CrosLibrary::Get()->EnsureLoaded()) {
CrosLibrary::Get()->GetLoginLibrary()->StopSession("");
}
// Don't hide yet the locker because the chrome screen may become visible
// briefly.
}
}
void ScreenLocker::ShowCaptchaAndErrorMessage(const GURL& captcha_url,
const std::wstring& message) {
postponed_error_message_ = message;
if (captcha_view_) {
captcha_view_->SetCaptchaURL(captcha_url);
} else {
captcha_view_ = new CaptchaView(captcha_url, true);
captcha_view_->Init();
captcha_view_->set_delegate(this);
}
// CaptchaView ownership is passed to grab_container_.
views::View* view = secondary_view_.release();
view = NULL;
screen_lock_view_->SetVisible(false);
grab_container_->SetScreenLockView(captcha_view_);
background_container_->SetScreenLockView(captcha_view_);
captcha_view_->SetVisible(true);
// Take ScreenLockView ownership now that it's removed from grab_container_.
secondary_view_.reset(screen_lock_view_);
}
void ScreenLocker::ShowErrorMessage(const std::wstring& message,
bool sign_out_only) {
if (sign_out_only) {
screen_lock_view_->SetEnabled(false);
} else {
EnableInput();
}
screen_lock_view_->SetSignoutEnabled(sign_out_only);
// Make sure that active Sign Out button is not hidden behind the bubble.
ShowErrorBubble(message, sign_out_only ?
BubbleBorder::BOTTOM_RIGHT : BubbleBorder::BOTTOM_LEFT);
}
void ScreenLocker::OnGrabInputs() {
DVLOG(1) << "OnGrabInputs";
input_grabbed_ = true;
if (drawn_)
ScreenLockReady();
}
views::View* ScreenLocker::GetViewByID(int id) {
return lock_widget_->GetRootView()->GetViewByID(id);
}
// static
void ScreenLocker::Show() {
VLOG(1) << "In ScreenLocker::Show";
UserMetrics::RecordAction(UserMetricsAction("ScreenLocker_Show"));
DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI);
// Exit fullscreen.
Browser* browser = BrowserList::GetLastActive();
// browser can be NULL if we receive a lock request before the first browser
// window is shown.
if (browser && browser->window()->IsFullscreen()) {
browser->ToggleFullscreenMode();
}
if (!screen_locker_) {
VLOG(1) << "Show: Locking screen";
ScreenLocker* locker =
new ScreenLocker(UserManager::Get()->logged_in_user());
locker->Init();
} else {
// PowerManager re-sends lock screen signal if it doesn't
// receive the response within timeout. Just send complete
// signal.
VLOG(1) << "Show: locker already exists. Just sending completion event.";
if (CrosLibrary::Get()->EnsureLoaded())
CrosLibrary::Get()->GetScreenLockLibrary()->NotifyScreenLockCompleted();
}
}
// static
void ScreenLocker::Hide() {
DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI);
DCHECK(screen_locker_);
VLOG(1) << "Hide: Deleting ScreenLocker: " << screen_locker_;
MessageLoopForUI::current()->DeleteSoon(FROM_HERE, screen_locker_);
}
// static
void ScreenLocker::UnlockScreenFailed() {
DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI);
if (screen_locker_) {
// Power manager decided no to unlock the screen even if a user
// typed in password, for example, when a user closed the lid
// immediately after typing in the password.
VLOG(1) << "UnlockScreenFailed: re-enabling screen locker.";
screen_locker_->EnableInput();
} else {
// This can happen when a user requested unlock, but PowerManager
// rejected because the computer is closed, then PowerManager unlocked
// because it's open again and the above failure message arrives.
// This'd be extremely rare, but may still happen.
VLOG(1) << "UnlockScreenFailed: screen is already unlocked.";
}
}
// static
void ScreenLocker::InitClass() {
g_screen_lock_observer.Get();
}
////////////////////////////////////////////////////////////////////////////////
// ScreenLocker, private:
ScreenLocker::~ScreenLocker() {
DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI);
ClearErrors();
if (input_event_observer_.get())
MessageLoopForUI::current()->RemoveObserver(input_event_observer_.get());
if (locker_input_event_observer_.get()) {
lock_widget_->GetFocusManager()->UnregisterAccelerator(
views::Accelerator(ui::VKEY_ESCAPE, false, false, false), this);
MessageLoopForUI::current()->RemoveObserver(
locker_input_event_observer_.get());
}
gdk_keyboard_ungrab(GDK_CURRENT_TIME);
gdk_pointer_ungrab(GDK_CURRENT_TIME);
DCHECK(lock_window_);
VLOG(1) << "~ScreenLocker(): Closing ScreenLocker window.";
lock_window_->Close();
// lock_widget_ will be deleted by gtk's destroy signal.
screen_locker_ = NULL;
bool state = false;
NotificationService::current()->Notify(
NotificationType::SCREEN_LOCK_STATE_CHANGED,
Source<ScreenLocker>(this),
Details<bool>(&state));
if (CrosLibrary::Get()->EnsureLoaded())
CrosLibrary::Get()->GetScreenLockLibrary()->NotifyScreenUnlockCompleted();
}
void ScreenLocker::SetAuthenticator(Authenticator* authenticator) {
authenticator_ = authenticator;
}
void ScreenLocker::ScreenLockReady() {
VLOG(1) << "ScreenLockReady: sending completed signal to power manager.";
locked_ = true;
base::TimeDelta delta = base::Time::Now() - start_time_;
VLOG(1) << "Screen lock time: " << delta.InSecondsF();
UMA_HISTOGRAM_TIMES("ScreenLocker.ScreenLockTime", delta);
if (background_view_->ScreenSaverEnabled()) {
lock_widget_->GetFocusManager()->RegisterAccelerator(
views::Accelerator(ui::VKEY_ESCAPE, false, false, false), this);
locker_input_event_observer_.reset(new LockerInputEventObserver(this));
MessageLoopForUI::current()->AddObserver(
locker_input_event_observer_.get());
} else {
// Don't enable the password field until we grab all inputs.
EnableInput();
}
bool state = true;
NotificationService::current()->Notify(
NotificationType::SCREEN_LOCK_STATE_CHANGED,
Source<ScreenLocker>(this),
Details<bool>(&state));
if (CrosLibrary::Get()->EnsureLoaded())
CrosLibrary::Get()->GetScreenLockLibrary()->NotifyScreenLockCompleted();
}
void ScreenLocker::OnClientEvent(GtkWidget* widge, GdkEventClient* event) {
WmIpc::Message msg;
WmIpc::instance()->DecodeMessage(*event, &msg);
if (msg.type() == WM_IPC_MESSAGE_CHROME_NOTIFY_SCREEN_REDRAWN_FOR_LOCK) {
OnWindowManagerReady();
}
}
void ScreenLocker::OnWindowManagerReady() {
DVLOG(1) << "OnClientEvent: drawn for lock";
drawn_ = true;
if (input_grabbed_)
ScreenLockReady();
}
void ScreenLocker::ShowErrorBubble(const std::wstring& message,
BubbleBorder::ArrowLocation arrow_location) {
if (error_info_)
error_info_->Close();
gfx::Rect rect = screen_lock_view_->GetPasswordBoundsRelativeTo(
lock_widget_->GetRootView());
gfx::Rect lock_widget_bounds = lock_widget_->GetClientAreaScreenBounds();
rect.Offset(lock_widget_bounds.x(), lock_widget_bounds.y());
error_info_ = MessageBubble::ShowNoGrab(
lock_window_,
rect,
arrow_location,
ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_WARNING),
message,
std::wstring(), // TODO(nkostylev): Add help link.
this);
if (mouse_event_relay_.get())
MessageLoopForUI::current()->RemoveObserver(mouse_event_relay_.get());
mouse_event_relay_.reset(
new MouseEventRelay(lock_widget_->GetNativeView()->window,
error_info_->GetNativeView()->window));
MessageLoopForUI::current()->AddObserver(mouse_event_relay_.get());
}
void ScreenLocker::StopScreenSaver() {
if (background_view_->IsScreenSaverVisible()) {
VLOG(1) << "StopScreenSaver";
background_view_->HideScreenSaver();
if (screen_lock_view_) {
screen_lock_view_->SetVisible(true);
screen_lock_view_->RequestFocus();
}
EnableInput();
}
}
void ScreenLocker::StartScreenSaver() {
if (!background_view_->IsScreenSaverVisible()) {
VLOG(1) << "StartScreenSaver";
UserMetrics::RecordAction(
UserMetricsAction("ScreenLocker_StartScreenSaver"));
background_view_->ShowScreenSaver();
if (screen_lock_view_) {
screen_lock_view_->SetEnabled(false);
screen_lock_view_->SetVisible(false);
}
ClearErrors();
}
}
bool ScreenLocker::AcceleratorPressed(const views::Accelerator& accelerator) {
if (!background_view_->IsScreenSaverVisible()) {
StartScreenSaver();
return true;
}
return false;
}
} // namespace chromeos