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