| Index: chrome/browser/chromeos/login/screen_locker.cc
|
| diff --git a/chrome/browser/chromeos/login/screen_locker.cc b/chrome/browser/chromeos/login/screen_locker.cc
|
| index 2d8ccd08ea5d0ed9f4e6899a2e923814298f6266..666bf8afa4d745449a7df1424e9ff56a031de098 100644
|
| --- a/chrome/browser/chromeos/login/screen_locker.cc
|
| +++ b/chrome/browser/chromeos/login/screen_locker.cc
|
| @@ -4,11 +4,15 @@
|
|
|
| #include "chrome/browser/chromeos/login/screen_locker.h"
|
|
|
| +#include <gdk/gdkx.h>
|
| #include <string>
|
| #include <vector>
|
| +#include <X11/extensions/XTest.h>
|
| +#include <X11/keysym.h>
|
|
|
| #include "app/l10n_util.h"
|
| #include "app/resource_bundle.h"
|
| +#include "app/x11_util.h"
|
| #include "base/command_line.h"
|
| #include "base/metrics/histogram.h"
|
| #include "base/message_loop.h"
|
| @@ -274,12 +278,13 @@ class GrabWidget : public views::WidgetGtk {
|
| views::WidgetGtk::Show();
|
| }
|
|
|
| - void ClearGrab() {
|
| + void ClearGtkGrab() {
|
| GtkWidget* current_grab_window;
|
| - // Grab gtk input first so that the menu holding grab will close itself.
|
| + // Grab gtk input first so that the menu holding gtk grab will
|
| + // close itself.
|
| gtk_grab_add(window_contents());
|
|
|
| - // Make sure there is no grab widget so that gtk simply propagates
|
| + // 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
|
| @@ -298,6 +303,11 @@ class GrabWidget : public views::WidgetGtk {
|
| // 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 HandleGrabBroke() {
|
| // Input should never be stolen from ScreenLocker once it's
|
| @@ -322,8 +332,9 @@ class GrabWidget : public views::WidgetGtk {
|
| };
|
|
|
| void GrabWidget::TryGrabAllInputs() {
|
| - ClearGrab();
|
| -
|
| + // 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);
|
| @@ -345,11 +356,14 @@ void GrabWidget::TryGrabAllInputs() {
|
| 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();
|
| CHECK_EQ(GDK_GRAB_SUCCESS, kbd_grab_status_)
|
| << "Failed to grab keyboard input:" << kbd_grab_status_;
|
| CHECK_EQ(GDK_GRAB_SUCCESS, mouse_grab_status_)
|
| @@ -359,6 +373,54 @@ void GrabWidget::TryGrabAllInputs() {
|
| }
|
| }
|
|
|
| +void GrabWidget::TryUngrabOtherClients() {
|
| +#if !defined(NDEBUG)
|
| + {
|
| + int event_base, error_base;
|
| + int major, minor;
|
| + // Make sure we have XTest extension.
|
| + DCHECK(XTestQueryExtension(x11_util::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 = x11_util::GetXDisplay();
|
| + Window root, child;
|
| + int root_x, root_y, win_x, winy;
|
| + unsigned int mask;
|
| + XQueryPointer(display,
|
| + x11_util::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 = x11_util::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 {
|
| @@ -612,12 +674,16 @@ void ScreenLocker::Init() {
|
| 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 TryGrabAllInputs(), as want to be in the default window group
|
| + // 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,
|
|
|