14e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
24e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
34e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// found in the LICENSE file.
44e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
54e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/browser/ui/views/app_list/win/activation_tracker_win.h"
64e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
74e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "base/time/time.h"
8010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)#include "chrome/browser/ui/app_list/app_list_shower_views.h"
9010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)#include "chrome/browser/ui/views/app_list/win/app_list_service_win.h"
10a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)#include "ui/app_list/views/app_list_view.h"
11a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)#include "ui/views/widget/widget.h"
124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
134e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)namespace {
144e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
15a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)const wchar_t kJumpListClassName[] = L"DV2ControlHost";
16a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)const wchar_t kTrayClassName[] = L"Shell_TrayWnd";
17a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)const int kFocusCheckIntervalMS = 250;
184e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}  // namespace
204e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
21010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)ActivationTrackerWin::ActivationTrackerWin(AppListServiceWin* service)
22010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    : service_(service),
23f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      taskbar_has_focus_(false) {
24010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  service_->shower().app_list()->AddObserver(this);
254e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
264e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
274e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)ActivationTrackerWin::~ActivationTrackerWin() {
28010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  DCHECK(service_->shower().app_list());
29010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  service_->shower().app_list()->RemoveObserver(this);
304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  timer_.Stop();
314e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
324e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
33a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)void ActivationTrackerWin::OnActivationChanged(views::Widget* /*widget*/,
34a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                                               bool active) {
354e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (active) {
364e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    timer_.Stop();
374e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return;
384e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
394e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
40f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  taskbar_has_focus_ = false;
414e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  timer_.Start(FROM_HERE,
424e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)               base::TimeDelta::FromMilliseconds(kFocusCheckIntervalMS), this,
43f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)               &ActivationTrackerWin::MaybeDismissAppList);
444e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
454e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
464e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)void ActivationTrackerWin::OnViewHidden() {
474e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  timer_.Stop();
484e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
494e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
50f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)void ActivationTrackerWin::MaybeDismissAppList() {
51f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  if (!ShouldDismissAppList())
52f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return;
53f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
54010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  service_->DismissAppList();
55f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)}
56f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
57f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)bool ActivationTrackerWin::ShouldDismissAppList() {
58f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // The app launcher should be hidden when it loses focus, except for the cases
59f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // necessary to allow the launcher to be pinned or closed via the taskbar
60f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // context menu. This will return true to dismiss the app launcher unless one
61f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // of the following conditions are met:
62f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // - the app launcher is focused, or
63f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // - the taskbar's jump list is focused, or
64f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // - the taskbar is focused with the right mouse button pressed.
65f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
664e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // Remember if the taskbar had focus without the right mouse button being
674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // down.
68f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  bool taskbar_had_focus = taskbar_has_focus_;
69f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  taskbar_has_focus_ = false;
704e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
714e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // First get the taskbar and jump lists windows (the jump list is the
724e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // context menu which the taskbar uses).
73f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  HWND jump_list_hwnd = FindWindow(kJumpListClassName, NULL);
744e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  HWND taskbar_hwnd = FindWindow(kTrayClassName, NULL);
754e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
764e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // First work out if the left or right button is currently down.
774e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  int swapped = GetSystemMetrics(SM_SWAPBUTTON);
784e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  int left_button = swapped ? VK_RBUTTON : VK_LBUTTON;
794e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  bool left_button_down = GetAsyncKeyState(left_button) < 0;
804e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  int right_button = swapped ? VK_LBUTTON : VK_RBUTTON;
814e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  bool right_button_down = GetAsyncKeyState(right_button) < 0;
824e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
834e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // Now get the window that currently has focus.
844e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  HWND focused_hwnd = GetForegroundWindow();
854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (!focused_hwnd) {
864e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // Sometimes the focused window is NULL. This can happen when the focus is
87f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    // changing due to a mouse button press. Dismiss the launcher if and only if
88f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    // no button is being pressed.
89f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    return !right_button_down && !left_button_down;
904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
924e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  while (focused_hwnd) {
934e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // If the focused window is the right click menu (called a jump list) or
944e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // the app list, don't hide the launcher.
95010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    HWND app_list_hwnd = service_->shower().app_list()->GetHWND();
96010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    if (focused_hwnd == jump_list_hwnd || focused_hwnd == app_list_hwnd)
97f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return false;
984e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
994e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    if (focused_hwnd == taskbar_hwnd) {
1004e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      // If the focused window is the taskbar, and the right button is down,
1014e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      // don't hide the launcher as the user might be bringing up the menu.
1024e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      if (right_button_down)
103f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        return false;
1044e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1054e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      // There is a short period between the right mouse button being down
1064e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      // and the menu gaining focus, where the taskbar has focus and no button
107f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // is down. If the taskbar is observed in this state one time, the
108f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // launcher is not dismissed. If it happens for two consecutive timer
109f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      // ticks, it is dismissed.
110f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if (!taskbar_had_focus) {
111f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        taskbar_has_focus_ = true;
112f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        return false;
1134e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      }
114f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      return true;
1154e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    }
1164e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    focused_hwnd = GetParent(focused_hwnd);
1174e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
1184e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
119f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // If we get here, the focused window is not the taskbar, its context menu, or
120f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  // the app list.
121f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  return true;
1224e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
123