1 // Copyright 2018 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 "ash/public/cpp/accelerators.h"
6 
7 #include "base/callback.h"
8 #include "base/no_destructor.h"
9 #include "base/stl_util.h"
10 
11 namespace ash {
12 
13 namespace {
14 
15 AcceleratorController* g_instance = nullptr;
16 
GetVolumeAdjustmentCallback()17 base::RepeatingClosure* GetVolumeAdjustmentCallback() {
18   static base::NoDestructor<base::RepeatingClosure> callback;
19   return callback.get();
20 }
21 
22 }  // namespace
23 
24 const AcceleratorData kAcceleratorData[] = {
25     {true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, SWITCH_TO_LAST_USED_IME},
26     {false, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN, SWITCH_TO_LAST_USED_IME},
27     {true, ui::VKEY_TAB, ui::EF_ALT_DOWN, CYCLE_FORWARD_MRU},
28     {true, ui::VKEY_TAB, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
29      CYCLE_BACKWARD_MRU},
30     {true, ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_NONE, TOGGLE_OVERVIEW},
31     {true, ui::VKEY_BROWSER_SEARCH, ui::EF_NONE, TOGGLE_APP_LIST},
32     {true, ui::VKEY_BROWSER_SEARCH, ui::EF_SHIFT_DOWN,
33      TOGGLE_APP_LIST_FULLSCREEN},
34     {true, ui::VKEY_WLAN, ui::EF_NONE, TOGGLE_WIFI},
35     {true, ui::VKEY_PRIVACY_SCREEN_TOGGLE, ui::EF_NONE, PRIVACY_SCREEN_TOGGLE},
36     {true, ui::VKEY_KBD_BRIGHTNESS_DOWN, ui::EF_NONE, KEYBOARD_BRIGHTNESS_DOWN},
37     {true, ui::VKEY_KBD_BRIGHTNESS_UP, ui::EF_NONE, KEYBOARD_BRIGHTNESS_UP},
38     // Maximize button.
39     {true, ui::VKEY_MEDIA_LAUNCH_APP2, ui::EF_CONTROL_DOWN, TOGGLE_MIRROR_MODE},
40     {true, ui::VKEY_MEDIA_LAUNCH_APP2, ui::EF_ALT_DOWN, SWAP_PRIMARY_DISPLAY},
41     // Cycle windows button.
42     {true, ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_CONTROL_DOWN, TAKE_SCREENSHOT},
43     {true, ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
44      TAKE_PARTIAL_SCREENSHOT},
45     {true, ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN,
46      TAKE_WINDOW_SCREENSHOT},
47     {true, ui::VKEY_BRIGHTNESS_DOWN, ui::EF_NONE, BRIGHTNESS_DOWN},
48     {true, ui::VKEY_BRIGHTNESS_DOWN, ui::EF_ALT_DOWN, KEYBOARD_BRIGHTNESS_DOWN},
49     {true, ui::VKEY_BRIGHTNESS_UP, ui::EF_NONE, BRIGHTNESS_UP},
50     {true, ui::VKEY_BRIGHTNESS_UP, ui::EF_ALT_DOWN, KEYBOARD_BRIGHTNESS_UP},
51     {true, ui::VKEY_BRIGHTNESS_DOWN, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
52      MAGNIFIER_ZOOM_OUT},
53     {true, ui::VKEY_BRIGHTNESS_UP, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
54      MAGNIFIER_ZOOM_IN},
55     {true, ui::VKEY_L, ui::EF_COMMAND_DOWN, LOCK_SCREEN},
56     {true, ui::VKEY_L, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN, SUSPEND},
57     // The lock key on Chrome OS keyboards produces F13 scancodes.
58     {true, ui::VKEY_F13, ui::EF_NONE, LOCK_PRESSED},
59     {false, ui::VKEY_F13, ui::EF_NONE, LOCK_RELEASED},
60     // Generic keyboards can use VKEY_SLEEP to mimic ChromeOS keyboard's lock
61     // key.
62     {true, ui::VKEY_SLEEP, ui::EF_NONE, LOCK_PRESSED},
63     {false, ui::VKEY_SLEEP, ui::EF_NONE, LOCK_RELEASED},
64     {true, ui::VKEY_POWER, ui::EF_NONE, POWER_PRESSED},
65     {false, ui::VKEY_POWER, ui::EF_NONE, POWER_RELEASED},
66     {true, ui::VKEY_M, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, OPEN_FILE_MANAGER},
67     {true, ui::VKEY_OEM_2, ui::EF_CONTROL_DOWN, OPEN_GET_HELP},
68     {true, ui::VKEY_OEM_2, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
69      OPEN_GET_HELP},
70     {true, ui::VKEY_T, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN, OPEN_CROSH},
71     {true, ui::VKEY_I, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
72      TOUCH_HUD_MODE_CHANGE},
73     {true, ui::VKEY_I,
74      ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN,
75      TOUCH_HUD_CLEAR},
76     {true, ui::VKEY_H, ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN,
77      TOGGLE_HIGH_CONTRAST},
78     {true, ui::VKEY_Z, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
79      TOGGLE_SPOKEN_FEEDBACK},
80     {true, ui::VKEY_D, ui::EF_COMMAND_DOWN, TOGGLE_DICTATION},
81     {true, ui::VKEY_OEM_COMMA, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
82      SWITCH_TO_PREVIOUS_USER},
83     {true, ui::VKEY_OEM_PERIOD, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
84      SWITCH_TO_NEXT_USER},
85     // Single shift release turns off caps lock.
86     {false, ui::VKEY_LSHIFT, ui::EF_NONE, DISABLE_CAPS_LOCK},
87     {false, ui::VKEY_SHIFT, ui::EF_NONE, DISABLE_CAPS_LOCK},
88     {false, ui::VKEY_RSHIFT, ui::EF_NONE, DISABLE_CAPS_LOCK},
89     // Accelerators to toggle Caps Lock.
90     // The following is triggered when Search is released while Alt is still
91     // down. The key_code here is LWIN (for search) and Alt is a modifier.
92     {false, ui::VKEY_LWIN, ui::EF_ALT_DOWN, TOGGLE_CAPS_LOCK},
93     // The following is triggered when Alt is released while search is still
94     // down. The key_code here is MENU (for Alt) and Search is a modifier
95     // (EF_COMMAND_DOWN is used for Search as a modifier).
96     {false, ui::VKEY_MENU, ui::EF_COMMAND_DOWN, TOGGLE_CAPS_LOCK},
97     {true, ui::VKEY_VOLUME_MUTE, ui::EF_NONE, VOLUME_MUTE},
98     {true, ui::VKEY_VOLUME_DOWN, ui::EF_NONE, VOLUME_DOWN},
99     {true, ui::VKEY_VOLUME_UP, ui::EF_NONE, VOLUME_UP},
100     {true, ui::VKEY_ESCAPE, ui::EF_COMMAND_DOWN, SHOW_TASK_MANAGER},
101     {true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
102      SWITCH_TO_NEXT_IME},
103     {true, ui::VKEY_I, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, OPEN_FEEDBACK_PAGE},
104     {true, ui::VKEY_Q, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, EXIT},
105     {true, ui::VKEY_N, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
106      NEW_INCOGNITO_WINDOW},
107     {true, ui::VKEY_N, ui::EF_CONTROL_DOWN, NEW_WINDOW},
108     {true, ui::VKEY_T, ui::EF_CONTROL_DOWN, NEW_TAB},
109     {true, ui::VKEY_OEM_MINUS, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
110      SCALE_UI_UP},
111     {true, ui::VKEY_OEM_PLUS, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
112      SCALE_UI_DOWN},
113     {true, ui::VKEY_0, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN, SCALE_UI_RESET},
114     {true, ui::VKEY_BROWSER_REFRESH, ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
115      ROTATE_SCREEN},
116     {true, ui::VKEY_BROWSER_REFRESH,
117      ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN, ROTATE_WINDOW},
118     {true, ui::VKEY_T, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, RESTORE_TAB},
119     // This corresponds to the "Print Screen" key.
120     {true, ui::VKEY_SNAPSHOT, ui::EF_NONE, TAKE_SCREENSHOT},
121     {true, ui::VKEY_SNAPSHOT, ui::EF_ALT_DOWN, TAKE_PARTIAL_SCREENSHOT},
122     // On Chrome OS, Search key is mapped to LWIN. The Search key binding should
123     // act on release instead of press when using Search as a modifier key for
124     // extended keyboard shortcuts.
125     {false, ui::VKEY_LWIN, ui::EF_NONE, TOGGLE_APP_LIST},
126     {false, ui::VKEY_LWIN, ui::EF_SHIFT_DOWN, TOGGLE_APP_LIST_FULLSCREEN},
127     {true, ui::VKEY_MEDIA_LAUNCH_APP2, ui::EF_NONE, TOGGLE_FULLSCREEN},
128     {true, ui::VKEY_MEDIA_LAUNCH_APP2, ui::EF_SHIFT_DOWN, TOGGLE_FULLSCREEN},
129     {true, ui::VKEY_ESCAPE, ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN, UNPIN},
130     {true, ui::VKEY_L, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, FOCUS_SHELF},
131     {true, ui::VKEY_V, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, FOCUS_PIP},
132     {true, ui::VKEY_HELP, ui::EF_NONE, SHOW_SHORTCUT_VIEWER},
133     {true, ui::VKEY_OEM_2, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
134      SHOW_SHORTCUT_VIEWER},
135     {true, ui::VKEY_OEM_2,
136      ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN,
137      SHOW_SHORTCUT_VIEWER},
138     {true, ui::VKEY_F14, ui::EF_NONE, SHOW_SHORTCUT_VIEWER},
139     {true, ui::VKEY_N, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
140      TOGGLE_MESSAGE_CENTER_BUBBLE},
141     {true, ui::VKEY_P, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN, SHOW_STYLUS_TOOLS},
142     {true, ui::VKEY_S, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
143      TOGGLE_SYSTEM_TRAY_BUBBLE},
144     // Until we have unified settings and notifications the "hamburger"
145     // key opens quick settings.
146     {true, ui::VKEY_SETTINGS, ui::EF_NONE, TOGGLE_SYSTEM_TRAY_BUBBLE},
147     {true, ui::VKEY_K, ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN,
148      SHOW_IME_MENU_BUBBLE},
149     {true, ui::VKEY_1, ui::EF_ALT_DOWN, LAUNCH_APP_0},
150     {true, ui::VKEY_2, ui::EF_ALT_DOWN, LAUNCH_APP_1},
151     {true, ui::VKEY_3, ui::EF_ALT_DOWN, LAUNCH_APP_2},
152     {true, ui::VKEY_4, ui::EF_ALT_DOWN, LAUNCH_APP_3},
153     {true, ui::VKEY_5, ui::EF_ALT_DOWN, LAUNCH_APP_4},
154     {true, ui::VKEY_6, ui::EF_ALT_DOWN, LAUNCH_APP_5},
155     {true, ui::VKEY_7, ui::EF_ALT_DOWN, LAUNCH_APP_6},
156     {true, ui::VKEY_8, ui::EF_ALT_DOWN, LAUNCH_APP_7},
157     {true, ui::VKEY_9, ui::EF_ALT_DOWN, LAUNCH_LAST_APP},
158 
159     // Window management shortcuts.
160     {true, ui::VKEY_OEM_4, ui::EF_ALT_DOWN, WINDOW_CYCLE_SNAP_LEFT},
161     {true, ui::VKEY_OEM_6, ui::EF_ALT_DOWN, WINDOW_CYCLE_SNAP_RIGHT},
162     {true, ui::VKEY_OEM_MINUS, ui::EF_ALT_DOWN, WINDOW_MINIMIZE},
163     {true, ui::VKEY_OEM_PLUS, ui::EF_ALT_DOWN, TOGGLE_MAXIMIZED},
164     {true, ui::VKEY_BROWSER_FORWARD, ui::EF_CONTROL_DOWN, FOCUS_NEXT_PANE},
165     {true, ui::VKEY_BROWSER_BACK, ui::EF_CONTROL_DOWN, FOCUS_PREVIOUS_PANE},
166     {true, ui::VKEY_BROWSER_BACK, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
167      FOCUS_NEXT_PANE},
168     {true, ui::VKEY_BROWSER_BACK, ui::EF_NONE, MINIMIZE_TOP_WINDOW_ON_BACK},
169 
170     // Moving active window between displays shortcut.
171     {true, ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN,
172      MOVE_ACTIVE_WINDOW_BETWEEN_DISPLAYS},
173 
174     // Magnifiers shortcuts.
175     {true, ui::VKEY_D, ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN,
176      TOGGLE_DOCKED_MAGNIFIER},
177     {true, ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN,
178      TOGGLE_FULLSCREEN_MAGNIFIER},
179 
180     // Media Player shortcuts.
181     {true, ui::VKEY_MEDIA_NEXT_TRACK, ui::EF_NONE, MEDIA_NEXT_TRACK},
182     {true, ui::VKEY_MEDIA_PAUSE, ui::EF_NONE, MEDIA_PAUSE},
183     {true, ui::VKEY_MEDIA_PLAY, ui::EF_NONE, MEDIA_PLAY},
184     {true, ui::VKEY_MEDIA_PLAY_PAUSE, ui::EF_NONE, MEDIA_PLAY_PAUSE},
185     {true, ui::VKEY_MEDIA_PREV_TRACK, ui::EF_NONE, MEDIA_PREV_TRACK},
186     {true, ui::VKEY_MEDIA_STOP, ui::EF_NONE, MEDIA_STOP},
187     {true, ui::VKEY_OEM_103, ui::EF_NONE, MEDIA_REWIND},
188     {true, ui::VKEY_OEM_104, ui::EF_NONE, MEDIA_FAST_FORWARD},
189 
190     // Assistant shortcuts.
191     {true, ui::VKEY_A, ui::EF_COMMAND_DOWN, START_ASSISTANT},
192     {true, ui::VKEY_ASSISTANT, ui::EF_NONE, START_ASSISTANT},
193 
194     // IME mode change key.
195     {true, ui::VKEY_MODECHANGE, ui::EF_NONE, SWITCH_TO_NEXT_IME},
196 
197     // Debugging shortcuts that need to be available to end-users in
198     // release builds.
199     {true, ui::VKEY_U, kDebugModifier, PRINT_UI_HIERARCHIES},
200 
201     // Virtual Desks shortcuts.
202     // Desk activation:
203     {true, ui::VKEY_OEM_4, ui::EF_COMMAND_DOWN, DESKS_ACTIVATE_DESK},
204     {true, ui::VKEY_OEM_6, ui::EF_COMMAND_DOWN, DESKS_ACTIVATE_DESK},
205     // Moving windows to desks:
206     {true, ui::VKEY_OEM_4, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN,
207      DESKS_MOVE_ACTIVE_ITEM},
208     {true, ui::VKEY_OEM_6, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN,
209      DESKS_MOVE_ACTIVE_ITEM},
210     // TODO(afakhry): Implement activating and moving windows to a desk by
211     // its index directly.
212 
213     // TODO(yusukes): Handle VKEY_MEDIA_STOP, and
214     // VKEY_MEDIA_LAUNCH_MAIL.
215 };
216 
217 const size_t kAcceleratorDataLength = base::size(kAcceleratorData);
218 
219 const AcceleratorData kDisableWithNewMappingAcceleratorData[] = {
220     // Desk creation and removal:
221     // Due to https://crbug.com/976487, Search + "=" is always automatically
222     // rewritten to F12, and so is Search + "-" to F11. So we had to implement
223     // the following two shortcuts as Shift + F11/F12 until we resolve the above
224     // issue, accepting the fact that these two shortcuts might sometimes be
225     // consumed by apps and pages (since they're not search-based).
226     // TODO(afakhry): Change the following to Search+Shift+"+"/"-" once
227     // https://crbug.com/976487 is fixed.
228     {true, ui::VKEY_F12, ui::EF_SHIFT_DOWN, DESKS_NEW_DESK},
229     {true, ui::VKEY_F11, ui::EF_SHIFT_DOWN, DESKS_REMOVE_CURRENT_DESK},
230 };
231 
232 const size_t kDisableWithNewMappingAcceleratorDataLength =
233     base::size(kDisableWithNewMappingAcceleratorData);
234 
235 const AcceleratorData kEnableWithNewMappingAcceleratorData[] = {
236     // Desk creation and removal:
237     {true, ui::VKEY_OEM_PLUS, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN,
238      DESKS_NEW_DESK},
239     {true, ui::VKEY_OEM_MINUS, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN,
240      DESKS_REMOVE_CURRENT_DESK},
241 
242     // Desk activation:
243     {true, ui::VKEY_LEFT, ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN,
244      DESKS_ACTIVATE_DESK},
245     {true, ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN,
246      DESKS_ACTIVATE_DESK},
247 
248     // Moving windows to desks:
249     {true, ui::VKEY_LEFT, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN,
250      DESKS_MOVE_ACTIVE_ITEM},
251     {true, ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN,
252      DESKS_MOVE_ACTIVE_ITEM},
253 
254     // Snap
255     {true, ui::VKEY_OEM_COMMA,
256      ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
257      WINDOW_CYCLE_SNAP_LEFT},
258     {true, ui::VKEY_OEM_PERIOD,
259      ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN,
260      WINDOW_CYCLE_SNAP_RIGHT},
261 
262     // Zoom
263     {true, ui::VKEY_UP,
264      ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
265      SCALE_UI_UP},
266     {true, ui::VKEY_DOWN,
267      ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
268      SCALE_UI_DOWN},
269     {true, ui::VKEY_BACK,
270      ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
271      SCALE_UI_RESET},
272 
273     // Shortcut Viewer
274     {true, ui::VKEY_OEM_2, ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN,
275      SHOW_SHORTCUT_VIEWER},
276 };
277 
278 const size_t kEnableWithNewMappingAcceleratorDataLength =
279     base::size(kEnableWithNewMappingAcceleratorData);
280 
281 // static
Get()282 AcceleratorController* AcceleratorController::Get() {
283   return g_instance;
284 }
285 
286 // static
SetVolumeAdjustmentSoundCallback(const base::RepeatingClosure & closure)287 void AcceleratorController::SetVolumeAdjustmentSoundCallback(
288     const base::RepeatingClosure& closure) {
289   DCHECK(GetVolumeAdjustmentCallback()->is_null() || closure.is_null());
290   *GetVolumeAdjustmentCallback() = std::move(closure);
291 }
292 
293 // static
PlayVolumeAdjustmentSound()294 void AcceleratorController::PlayVolumeAdjustmentSound() {
295   if (*GetVolumeAdjustmentCallback())
296     GetVolumeAdjustmentCallback()->Run();
297 }
298 
AcceleratorController()299 AcceleratorController::AcceleratorController() {
300   DCHECK_EQ(nullptr, g_instance);
301   g_instance = this;
302 }
303 
~AcceleratorController()304 AcceleratorController::~AcceleratorController() {
305   DCHECK_EQ(this, g_instance);
306   g_instance = nullptr;
307 }
308 
309 }  // namespace ash
310