OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/chromeos/login/screen_locker.h" | 5 #include "chrome/browser/chromeos/login/screen_locker.h" |
6 | 6 |
| 7 #include <gdk/gdkx.h> |
7 #include <string> | 8 #include <string> |
8 #include <vector> | 9 #include <vector> |
| 10 #include <X11/extensions/XTest.h> |
| 11 #include <X11/keysym.h> |
9 | 12 |
10 #include "app/l10n_util.h" | 13 #include "app/l10n_util.h" |
11 #include "app/resource_bundle.h" | 14 #include "app/resource_bundle.h" |
| 15 #include "app/x11_util.h" |
12 #include "base/command_line.h" | 16 #include "base/command_line.h" |
13 #include "base/metrics/histogram.h" | 17 #include "base/metrics/histogram.h" |
14 #include "base/message_loop.h" | 18 #include "base/message_loop.h" |
15 #include "base/singleton.h" | 19 #include "base/singleton.h" |
16 #include "base/string_util.h" | 20 #include "base/string_util.h" |
17 #include "base/timer.h" | 21 #include "base/timer.h" |
18 #include "base/utf_string_conversions.h" | 22 #include "base/utf_string_conversions.h" |
19 #include "chrome/browser/browser_list.h" | 23 #include "chrome/browser/browser_list.h" |
20 #include "chrome/browser/browser_thread.h" | 24 #include "chrome/browser/browser_thread.h" |
21 #include "chrome/browser/browser_window.h" | 25 #include "chrome/browser/browser_window.h" |
(...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
267 ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), | 271 ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), |
268 grab_failure_count_(0), | 272 grab_failure_count_(0), |
269 kbd_grab_status_(GDK_GRAB_INVALID_TIME), | 273 kbd_grab_status_(GDK_GRAB_INVALID_TIME), |
270 mouse_grab_status_(GDK_GRAB_INVALID_TIME) { | 274 mouse_grab_status_(GDK_GRAB_INVALID_TIME) { |
271 } | 275 } |
272 | 276 |
273 virtual void Show() { | 277 virtual void Show() { |
274 views::WidgetGtk::Show(); | 278 views::WidgetGtk::Show(); |
275 } | 279 } |
276 | 280 |
277 void ClearGrab() { | 281 void ClearGtkGrab() { |
278 GtkWidget* current_grab_window; | 282 GtkWidget* current_grab_window; |
279 // Grab gtk input first so that the menu holding grab will close itself. | 283 // Grab gtk input first so that the menu holding gtk grab will |
| 284 // close itself. |
280 gtk_grab_add(window_contents()); | 285 gtk_grab_add(window_contents()); |
281 | 286 |
282 // Make sure there is no grab widget so that gtk simply propagates | 287 // Make sure there is no gtk grab widget so that gtk simply propagates |
283 // an event. This is necessary to allow message bubble and password | 288 // an event. This is necessary to allow message bubble and password |
284 // field, button to process events simultaneously. GTK | 289 // field, button to process events simultaneously. GTK |
285 // maintains grab widgets in a linked-list, so we need to remove | 290 // maintains grab widgets in a linked-list, so we need to remove |
286 // until it's empty. | 291 // until it's empty. |
287 while ((current_grab_window = gtk_grab_get_current()) != NULL) | 292 while ((current_grab_window = gtk_grab_get_current()) != NULL) |
288 gtk_grab_remove(current_grab_window); | 293 gtk_grab_remove(current_grab_window); |
289 } | 294 } |
290 | 295 |
291 virtual gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event) { | 296 virtual gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event) { |
292 WidgetGtk::OnButtonPress(widget, event); | 297 WidgetGtk::OnButtonPress(widget, event); |
293 // Never propagate event to parent. | 298 // Never propagate event to parent. |
294 return true; | 299 return true; |
295 } | 300 } |
296 | 301 |
297 // Try to grab all inputs. It initiates another try if it fails to | 302 // Try to grab all inputs. It initiates another try if it fails to |
298 // grab and the retry count is within a limit, or fails with CHECK. | 303 // grab and the retry count is within a limit, or fails with CHECK. |
299 void TryGrabAllInputs(); | 304 void TryGrabAllInputs(); |
300 | 305 |
| 306 // This method tries to steal pointer/keyboard grab from other |
| 307 // client by sending events that will hopefully close menus or windows |
| 308 // that have the grab. |
| 309 void TryUngrabOtherClients(); |
| 310 |
301 private: | 311 private: |
302 virtual void HandleGrabBroke() { | 312 virtual void HandleGrabBroke() { |
303 // Input should never be stolen from ScreenLocker once it's | 313 // Input should never be stolen from ScreenLocker once it's |
304 // grabbed. If this happens, it's a bug and has to be fixed. We | 314 // grabbed. If this happens, it's a bug and has to be fixed. We |
305 // let chrome crash to get a crash report and dump, and | 315 // let chrome crash to get a crash report and dump, and |
306 // SessionManager will terminate the session to logout. | 316 // SessionManager will terminate the session to logout. |
307 CHECK(kbd_grab_status_ != GDK_GRAB_SUCCESS || | 317 CHECK(kbd_grab_status_ != GDK_GRAB_SUCCESS || |
308 mouse_grab_status_ != GDK_GRAB_SUCCESS) | 318 mouse_grab_status_ != GDK_GRAB_SUCCESS) |
309 << "Grab Broke. quitting"; | 319 << "Grab Broke. quitting"; |
310 } | 320 } |
311 | 321 |
312 chromeos::ScreenLocker* screen_locker_; | 322 chromeos::ScreenLocker* screen_locker_; |
313 ScopedRunnableMethodFactory<GrabWidget> task_factory_; | 323 ScopedRunnableMethodFactory<GrabWidget> task_factory_; |
314 | 324 |
315 // The number times the widget tried to grab all focus. | 325 // The number times the widget tried to grab all focus. |
316 int grab_failure_count_; | 326 int grab_failure_count_; |
317 // Status of keyboard and mouse grab. | 327 // Status of keyboard and mouse grab. |
318 GdkGrabStatus kbd_grab_status_; | 328 GdkGrabStatus kbd_grab_status_; |
319 GdkGrabStatus mouse_grab_status_; | 329 GdkGrabStatus mouse_grab_status_; |
320 | 330 |
321 DISALLOW_COPY_AND_ASSIGN(GrabWidget); | 331 DISALLOW_COPY_AND_ASSIGN(GrabWidget); |
322 }; | 332 }; |
323 | 333 |
324 void GrabWidget::TryGrabAllInputs() { | 334 void GrabWidget::TryGrabAllInputs() { |
325 ClearGrab(); | 335 // Grab x server so that we can atomically grab and take |
326 | 336 // action when grab fails. |
| 337 gdk_x11_grab_server(); |
327 if (kbd_grab_status_ != GDK_GRAB_SUCCESS) { | 338 if (kbd_grab_status_ != GDK_GRAB_SUCCESS) { |
328 kbd_grab_status_ = gdk_keyboard_grab(window_contents()->window, FALSE, | 339 kbd_grab_status_ = gdk_keyboard_grab(window_contents()->window, FALSE, |
329 GDK_CURRENT_TIME); | 340 GDK_CURRENT_TIME); |
330 } | 341 } |
331 if (mouse_grab_status_ != GDK_GRAB_SUCCESS) { | 342 if (mouse_grab_status_ != GDK_GRAB_SUCCESS) { |
332 mouse_grab_status_ = | 343 mouse_grab_status_ = |
333 gdk_pointer_grab(window_contents()->window, | 344 gdk_pointer_grab(window_contents()->window, |
334 FALSE, | 345 FALSE, |
335 static_cast<GdkEventMask>( | 346 static_cast<GdkEventMask>( |
336 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | | 347 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | |
337 GDK_POINTER_MOTION_MASK), | 348 GDK_POINTER_MOTION_MASK), |
338 NULL, | 349 NULL, |
339 NULL, | 350 NULL, |
340 GDK_CURRENT_TIME); | 351 GDK_CURRENT_TIME); |
341 } | 352 } |
342 if ((kbd_grab_status_ != GDK_GRAB_SUCCESS || | 353 if ((kbd_grab_status_ != GDK_GRAB_SUCCESS || |
343 mouse_grab_status_ != GDK_GRAB_SUCCESS) && | 354 mouse_grab_status_ != GDK_GRAB_SUCCESS) && |
344 grab_failure_count_++ < kMaxGrabFailures) { | 355 grab_failure_count_++ < kMaxGrabFailures) { |
345 LOG(WARNING) << "Failed to grab inputs. Trying again in " | 356 LOG(WARNING) << "Failed to grab inputs. Trying again in " |
346 << kRetryGrabIntervalMs << " ms: kbd=" | 357 << kRetryGrabIntervalMs << " ms: kbd=" |
347 << kbd_grab_status_ << ", mouse=" << mouse_grab_status_; | 358 << kbd_grab_status_ << ", mouse=" << mouse_grab_status_; |
| 359 TryUngrabOtherClients(); |
| 360 gdk_x11_ungrab_server(); |
348 MessageLoop::current()->PostDelayedTask( | 361 MessageLoop::current()->PostDelayedTask( |
349 FROM_HERE, | 362 FROM_HERE, |
350 task_factory_.NewRunnableMethod(&GrabWidget::TryGrabAllInputs), | 363 task_factory_.NewRunnableMethod(&GrabWidget::TryGrabAllInputs), |
351 kRetryGrabIntervalMs); | 364 kRetryGrabIntervalMs); |
352 } else { | 365 } else { |
| 366 gdk_x11_ungrab_server(); |
353 CHECK_EQ(GDK_GRAB_SUCCESS, kbd_grab_status_) | 367 CHECK_EQ(GDK_GRAB_SUCCESS, kbd_grab_status_) |
354 << "Failed to grab keyboard input:" << kbd_grab_status_; | 368 << "Failed to grab keyboard input:" << kbd_grab_status_; |
355 CHECK_EQ(GDK_GRAB_SUCCESS, mouse_grab_status_) | 369 CHECK_EQ(GDK_GRAB_SUCCESS, mouse_grab_status_) |
356 << "Failed to grab pointer input:" << mouse_grab_status_; | 370 << "Failed to grab pointer input:" << mouse_grab_status_; |
357 DVLOG(1) << "Grab Success"; | 371 DVLOG(1) << "Grab Success"; |
358 screen_locker_->OnGrabInputs(); | 372 screen_locker_->OnGrabInputs(); |
359 } | 373 } |
360 } | 374 } |
361 | 375 |
| 376 void GrabWidget::TryUngrabOtherClients() { |
| 377 #if !defined(NDEBUG) |
| 378 { |
| 379 int event_base, error_base; |
| 380 int major, minor; |
| 381 // Make sure we have XTest extension. |
| 382 DCHECK(XTestQueryExtension(x11_util::GetXDisplay(), |
| 383 &event_base, &error_base, |
| 384 &major, &minor)); |
| 385 } |
| 386 #endif |
| 387 |
| 388 // The following code is an attempt to grab inputs by closing |
| 389 // supposedly opened menu. This happens when a plugin has a menu |
| 390 // opened. |
| 391 if (mouse_grab_status_ == GDK_GRAB_ALREADY_GRABBED || |
| 392 mouse_grab_status_ == GDK_GRAB_FROZEN) { |
| 393 // Successfully grabbed the keyboard, but pointer is still |
| 394 // grabbed by other client. Another attempt to close supposedly |
| 395 // opened menu by emulating keypress at the left top corner. |
| 396 Display* display = x11_util::GetXDisplay(); |
| 397 Window root, child; |
| 398 int root_x, root_y, win_x, winy; |
| 399 unsigned int mask; |
| 400 XQueryPointer(display, |
| 401 x11_util::GetX11WindowFromGtkWidget(window_contents()), |
| 402 &root, &child, &root_x, &root_y, |
| 403 &win_x, &winy, &mask); |
| 404 XTestFakeMotionEvent(display, -1, -10000, -10000, CurrentTime); |
| 405 XTestFakeButtonEvent(display, 1, True, CurrentTime); |
| 406 XTestFakeButtonEvent(display, 1, False, CurrentTime); |
| 407 // Move the pointer back. |
| 408 XTestFakeMotionEvent(display, -1, root_x, root_y, CurrentTime); |
| 409 XFlush(display); |
| 410 } else if (kbd_grab_status_ == GDK_GRAB_ALREADY_GRABBED || |
| 411 kbd_grab_status_ == GDK_GRAB_FROZEN) { |
| 412 // Successfully grabbed the pointer, but keyboard is still grabbed |
| 413 // by other client. Another attempt to close supposedly opened |
| 414 // menu by emulating escape key. Such situation must be very |
| 415 // rare, but handling this just in case |
| 416 Display* display = x11_util::GetXDisplay(); |
| 417 KeyCode escape = XKeysymToKeycode(display, XK_Escape); |
| 418 XTestFakeKeyEvent(display, escape, True, CurrentTime); |
| 419 XTestFakeKeyEvent(display, escape, False, CurrentTime); |
| 420 XFlush(display); |
| 421 } |
| 422 } |
| 423 |
362 // BackgroundView for ScreenLocker, which layouts a lock widget in | 424 // BackgroundView for ScreenLocker, which layouts a lock widget in |
363 // addition to other background components. | 425 // addition to other background components. |
364 class ScreenLockerBackgroundView : public chromeos::BackgroundView { | 426 class ScreenLockerBackgroundView : public chromeos::BackgroundView { |
365 public: | 427 public: |
366 ScreenLockerBackgroundView(views::WidgetGtk* lock_widget, | 428 ScreenLockerBackgroundView(views::WidgetGtk* lock_widget, |
367 views::View* screen_lock_view) | 429 views::View* screen_lock_view) |
368 : lock_widget_(lock_widget), | 430 : lock_widget_(lock_widget), |
369 screen_lock_view_(screen_lock_view) { | 431 screen_lock_view_(screen_lock_view) { |
370 } | 432 } |
371 | 433 |
(...skipping 233 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
605 | 667 |
606 DCHECK(GTK_WIDGET_REALIZED(lock_window_->GetNativeView())); | 668 DCHECK(GTK_WIDGET_REALIZED(lock_window_->GetNativeView())); |
607 WmIpc::instance()->SetWindowType( | 669 WmIpc::instance()->SetWindowType( |
608 lock_window_->GetNativeView(), | 670 lock_window_->GetNativeView(), |
609 WM_IPC_WINDOW_CHROME_SCREEN_LOCKER, | 671 WM_IPC_WINDOW_CHROME_SCREEN_LOCKER, |
610 NULL); | 672 NULL); |
611 | 673 |
612 lock_window_->SetContentsView(background_view_); | 674 lock_window_->SetContentsView(background_view_); |
613 lock_window_->Show(); | 675 lock_window_->Show(); |
614 | 676 |
| 677 cast_lock_widget->ClearGtkGrab(); |
| 678 |
| 679 // Call this after lock_window_->Show(); otherwise the 1st invocation |
| 680 // of gdk_xxx_grab() will always fail. |
615 cast_lock_widget->TryGrabAllInputs(); | 681 cast_lock_widget->TryGrabAllInputs(); |
616 | 682 |
617 // Add the window to its own group so that its grab won't be stolen if | 683 // Add the window to its own group so that its grab won't be stolen if |
618 // gtk_grab_add() gets called on behalf on a non-screen-locker widget (e.g. | 684 // gtk_grab_add() gets called on behalf on a non-screen-locker widget (e.g. |
619 // a modal dialog) -- see http://crosbug.com/8999. We intentionally do this | 685 // a modal dialog) -- see http://crosbug.com/8999. We intentionally do this |
620 // after calling TryGrabAllInputs(), as want to be in the default window group | 686 // after calling ClearGtkGrab(), as want to be in the default window group |
621 // then so we can break any existing GTK grabs. | 687 // then so we can break any existing GTK grabs. |
622 GtkWindowGroup* window_group = gtk_window_group_new(); | 688 GtkWindowGroup* window_group = gtk_window_group_new(); |
623 gtk_window_group_add_window(window_group, | 689 gtk_window_group_add_window(window_group, |
624 GTK_WINDOW(lock_window_->GetNativeView())); | 690 GTK_WINDOW(lock_window_->GetNativeView())); |
625 g_object_unref(window_group); | 691 g_object_unref(window_group); |
626 | 692 |
627 // Don't let X draw default background, which was causing flash on | 693 // Don't let X draw default background, which was causing flash on |
628 // resume. | 694 // resume. |
629 gdk_window_set_back_pixmap(lock_window_->GetNativeView()->window, | 695 gdk_window_set_back_pixmap(lock_window_->GetNativeView()->window, |
630 NULL, false); | 696 NULL, false); |
(...skipping 294 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
925 | 991 |
926 bool ScreenLocker::AcceleratorPressed(const views::Accelerator& accelerator) { | 992 bool ScreenLocker::AcceleratorPressed(const views::Accelerator& accelerator) { |
927 if (!background_view_->IsScreenSaverVisible()) { | 993 if (!background_view_->IsScreenSaverVisible()) { |
928 StartScreenSaver(); | 994 StartScreenSaver(); |
929 return true; | 995 return true; |
930 } | 996 } |
931 return false; | 997 return false; |
932 } | 998 } |
933 | 999 |
934 } // namespace chromeos | 1000 } // namespace chromeos |
OLD | NEW |