activation_tracker_win.cc revision f2477e01787aa58f445919b809d89e252beef54f
1// Copyright 2013 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/ui/views/app_list/win/activation_tracker_win.h"
6
7#include "base/time/time.h"
8
9namespace {
10
11static const wchar_t kJumpListClassName[] = L"DV2ControlHost";
12static const wchar_t kTrayClassName[] = L"Shell_TrayWnd";
13static const int kFocusCheckIntervalMS = 250;
14
15}  // namespace
16
17ActivationTrackerWin::ActivationTrackerWin(
18    app_list::AppListView* view,
19    const base::Closure& on_should_dismiss)
20    : view_(view),
21      on_should_dismiss_(on_should_dismiss),
22      reactivate_on_next_focus_loss_(false),
23      taskbar_has_focus_(false) {
24  view_->AddObserver(this);
25}
26
27ActivationTrackerWin::~ActivationTrackerWin() {
28  view_->RemoveObserver(this);
29  timer_.Stop();
30}
31
32void ActivationTrackerWin::OnActivationChanged(
33    views::Widget* /*widget*/, bool active) {
34  if (active) {
35    timer_.Stop();
36    return;
37  }
38
39  taskbar_has_focus_ = false;
40  timer_.Start(FROM_HERE,
41               base::TimeDelta::FromMilliseconds(kFocusCheckIntervalMS), this,
42               &ActivationTrackerWin::MaybeDismissAppList);
43}
44
45void ActivationTrackerWin::OnViewHidden() {
46  timer_.Stop();
47}
48
49void ActivationTrackerWin::MaybeDismissAppList() {
50  if (!ShouldDismissAppList())
51    return;
52
53  if (reactivate_on_next_focus_loss_) {
54    // Instead of dismissing the app launcher, re-activate it.
55    reactivate_on_next_focus_loss_ = false;
56    view_->GetWidget()->Activate();
57    return;
58  }
59
60  on_should_dismiss_.Run();
61}
62
63bool ActivationTrackerWin::ShouldDismissAppList() {
64  // The app launcher should be hidden when it loses focus, except for the cases
65  // necessary to allow the launcher to be pinned or closed via the taskbar
66  // context menu. This will return true to dismiss the app launcher unless one
67  // of the following conditions are met:
68  // - the app launcher is focused, or
69  // - the taskbar's jump list is focused, or
70  // - the taskbar is focused with the right mouse button pressed.
71
72  // Remember if the taskbar had focus without the right mouse button being
73  // down.
74  bool taskbar_had_focus = taskbar_has_focus_;
75  taskbar_has_focus_ = false;
76
77  // First get the taskbar and jump lists windows (the jump list is the
78  // context menu which the taskbar uses).
79  HWND jump_list_hwnd = FindWindow(kJumpListClassName, NULL);
80  HWND taskbar_hwnd = FindWindow(kTrayClassName, NULL);
81
82  // First work out if the left or right button is currently down.
83  int swapped = GetSystemMetrics(SM_SWAPBUTTON);
84  int left_button = swapped ? VK_RBUTTON : VK_LBUTTON;
85  bool left_button_down = GetAsyncKeyState(left_button) < 0;
86  int right_button = swapped ? VK_LBUTTON : VK_RBUTTON;
87  bool right_button_down = GetAsyncKeyState(right_button) < 0;
88
89  // Now get the window that currently has focus.
90  HWND focused_hwnd = GetForegroundWindow();
91  if (!focused_hwnd) {
92    // Sometimes the focused window is NULL. This can happen when the focus is
93    // changing due to a mouse button press. Dismiss the launcher if and only if
94    // no button is being pressed.
95    return !right_button_down && !left_button_down;
96  }
97
98  while (focused_hwnd) {
99    // If the focused window is the right click menu (called a jump list) or
100    // the app list, don't hide the launcher.
101    if (focused_hwnd == jump_list_hwnd || focused_hwnd == view_->GetHWND())
102      return false;
103
104    if (focused_hwnd == taskbar_hwnd) {
105      // If the focused window is the taskbar, and the right button is down,
106      // don't hide the launcher as the user might be bringing up the menu.
107      if (right_button_down)
108        return false;
109
110      // There is a short period between the right mouse button being down
111      // and the menu gaining focus, where the taskbar has focus and no button
112      // is down. If the taskbar is observed in this state one time, the
113      // launcher is not dismissed. If it happens for two consecutive timer
114      // ticks, it is dismissed.
115      if (!taskbar_had_focus) {
116        taskbar_has_focus_ = true;
117        return false;
118      }
119      return true;
120    }
121    focused_hwnd = GetParent(focused_hwnd);
122  }
123
124  // If we get here, the focused window is not the taskbar, its context menu, or
125  // the app list.
126  return true;
127}
128