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