1 // Copyright 2019 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 "remoting/host/keyboard_layout_monitor.h"
6
7 #include <Carbon/Carbon.h>
8 #include <CoreFoundation/CoreFoundation.h>
9 #include <CoreServices/CoreServices.h>
10
11 #include <utility>
12
13 #include "base/bind.h"
14 #include "base/callback.h"
15 #include "base/logging.h"
16 #include "base/mac/scoped_cftyperef.h"
17 #include "base/memory/weak_ptr.h"
18 #include "base/optional.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/threading/sequenced_task_runner_handle.h"
21 #include "remoting/proto/control.pb.h"
22 #include "ui/events/keycodes/dom/dom_code.h"
23 #include "ui/events/keycodes/dom/keycode_converter.h"
24
25 namespace remoting {
26
27 namespace {
28
29 class KeyboardLayoutMonitorMac : public KeyboardLayoutMonitor {
30 public:
31 KeyboardLayoutMonitorMac(
32 base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback);
33
34 ~KeyboardLayoutMonitorMac() override;
35
36 void Start() override;
37
38 private:
39 struct CallbackContext {
40 scoped_refptr<base::SequencedTaskRunner> task_runner;
41 base::WeakPtr<KeyboardLayoutMonitorMac> weak_ptr;
42 };
43
44 void OnLayoutChanged(const protocol::KeyboardLayout& new_layout);
45
46 static void SelectedKeyboardInputSourceChangedCallback(
47 CFNotificationCenterRef center,
48 void* observer,
49 CFNotificationName name,
50 const void* object,
51 CFDictionaryRef userInfo);
52
53 static void QueryLayoutOnMainLoop(CallbackContext* callback_context);
54
55 base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback_;
56 std::unique_ptr<CallbackContext> callback_context_;
57 base::WeakPtrFactory<KeyboardLayoutMonitorMac> weak_ptr_factory_;
58 };
59
60 base::Optional<protocol::LayoutKeyFunction> GetFixedKeyFunction(int keycode);
61 base::Optional<protocol::LayoutKeyFunction> GetCharFunction(UniChar char_code,
62 int keycode);
63
KeyboardLayoutMonitorMac(base::RepeatingCallback<void (const protocol::KeyboardLayout &)> callback)64 KeyboardLayoutMonitorMac::KeyboardLayoutMonitorMac(
65 base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback)
66 : callback_(std::move(callback)), weak_ptr_factory_(this) {}
67
~KeyboardLayoutMonitorMac()68 KeyboardLayoutMonitorMac::~KeyboardLayoutMonitorMac() {
69 if (!callback_context_) {
70 return;
71 }
72
73 CFNotificationCenterRemoveObserver(
74 CFNotificationCenterGetDistributedCenter(), callback_context_.get(),
75 kTISNotifySelectedKeyboardInputSourceChanged, nullptr);
76
77 // The distributed notification center posts all notifications from the
78 // process's main run loop. Schedule deletion from the same loop to ensure
79 // we don't delete the callback context while a notification is being
80 // dispatched.
81 // Store the callback context pointer in a local variable so the block
82 // captures it directly instead of capturing this.
83 CallbackContext* callback_context = callback_context_.release();
84 CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^(void) {
85 delete callback_context;
86 });
87 // No need to explicitly wake up the main loop here. It's fine if the
88 // deletion is delayed slightly until the next time the main loop wakes up
89 // normally.
90 }
91
Start()92 void KeyboardLayoutMonitorMac::Start() {
93 DCHECK(!callback_context_);
94 callback_context_.reset(new CallbackContext{
95 base::SequencedTaskRunnerHandle::Get(), weak_ptr_factory_.GetWeakPtr()});
96 CFNotificationCenterAddObserver(
97 CFNotificationCenterGetDistributedCenter(), callback_context_.get(),
98 SelectedKeyboardInputSourceChangedCallback,
99 kTISNotifySelectedKeyboardInputSourceChanged, nullptr,
100 CFNotificationSuspensionBehaviorDeliverImmediately);
101
102 // Get the initial layout.
103 // Store the callback context pointer in a local variable so the block
104 // captures it directly instead of capturing this.
105 CallbackContext* callback_context = callback_context_.get();
106 base::ScopedCFTypeRef<CFRunLoopRef> main_loop(CFRunLoopGetMain(),
107 base::scoped_policy::RETAIN);
108 CFRunLoopPerformBlock(main_loop, kCFRunLoopCommonModes, ^(void) {
109 QueryLayoutOnMainLoop(callback_context);
110 });
111 CFRunLoopWakeUp(main_loop);
112 }
113
OnLayoutChanged(const protocol::KeyboardLayout & new_layout)114 void KeyboardLayoutMonitorMac::OnLayoutChanged(
115 const protocol::KeyboardLayout& new_layout) {
116 callback_.Run(new_layout);
117 }
118
119 // static
SelectedKeyboardInputSourceChangedCallback(CFNotificationCenterRef center,void * observer,CFNotificationName name,const void * object,CFDictionaryRef userInfo)120 void KeyboardLayoutMonitorMac::SelectedKeyboardInputSourceChangedCallback(
121 CFNotificationCenterRef center,
122 void* observer,
123 CFNotificationName name,
124 const void* object,
125 CFDictionaryRef userInfo) {
126 CallbackContext* callback_context = static_cast<CallbackContext*>(observer);
127 QueryLayoutOnMainLoop(callback_context);
128 }
129
130 // static
QueryLayoutOnMainLoop(KeyboardLayoutMonitorMac::CallbackContext * callback_context)131 void KeyboardLayoutMonitorMac::QueryLayoutOnMainLoop(
132 KeyboardLayoutMonitorMac::CallbackContext* callback_context) {
133 base::ScopedCFTypeRef<TISInputSourceRef> input_source(
134 TISCopyCurrentKeyboardLayoutInputSource());
135 base::ScopedCFTypeRef<CFDataRef> layout_data(
136 static_cast<CFDataRef>(TISGetInputSourceProperty(
137 input_source, kTISPropertyUnicodeKeyLayoutData)),
138 base::scoped_policy::RETAIN);
139
140 if (!layout_data) {
141 LOG(WARNING) << "Failed to query keyboard layout.";
142 return;
143 }
144
145 protocol::KeyboardLayout layout_message;
146
147 std::uint8_t keyboard_type = LMGetKbdType();
148 PhysicalKeyboardLayoutType layout_type = KBGetLayoutType(keyboard_type);
149
150 for (ui::DomCode key : KeyboardLayoutMonitor::kSupportedKeys) {
151 // Lang1 and Lang2 are only present on JIS-style keyboards.
152 if ((key == ui::DomCode::LANG1 || key == ui::DomCode::LANG2) &&
153 layout_type != kKeyboardJIS) {
154 continue;
155 }
156
157 // Skip Caps Lock until we decide how/if we want to handle it.
158 if (key == ui::DomCode::CAPS_LOCK) {
159 continue;
160 }
161
162 std::uint32_t usb_code = ui::KeycodeConverter::DomCodeToUsbKeycode(key);
163 int keycode = ui::KeycodeConverter::DomCodeToNativeKeycode(key);
164
165 auto& key_actions =
166 *(*layout_message.mutable_keys())[usb_code].mutable_actions();
167
168 for (int shift_level = 0; shift_level < 4; ++shift_level) {
169 base::Optional<protocol::LayoutKeyFunction> fixed_function =
170 GetFixedKeyFunction(keycode);
171 if (fixed_function) {
172 key_actions[shift_level].set_function(*fixed_function);
173 continue;
174 }
175
176 std::uint32_t modifier_state = 0;
177 if (shift_level & 1) {
178 modifier_state |= shiftKey;
179 }
180 if (shift_level & 2) {
181 modifier_state |= optionKey;
182 }
183 std::uint32_t deadkey_state = 0;
184 UniChar result_array[255];
185 UniCharCount result_length = 0;
186 UCKeyTranslate(reinterpret_cast<const UCKeyboardLayout*>(
187 CFDataGetBytePtr(layout_data)),
188 keycode, kUCKeyActionDown, modifier_state >> 8,
189 keyboard_type, kUCKeyTranslateNoDeadKeysMask,
190 &deadkey_state, base::size(result_array), &result_length,
191 result_array);
192
193 if (result_length == 0) {
194 continue;
195 }
196
197 if (result_length == 1) {
198 base::Optional<protocol::LayoutKeyFunction> char_function =
199 GetCharFunction(result_array[0], keycode);
200 if (char_function) {
201 key_actions[shift_level].set_function(*char_function);
202 continue;
203 }
204 }
205
206 key_actions[shift_level].set_character(
207 base::UTF16ToUTF8(base::StringPiece16(result_array, result_length)));
208 }
209
210 if (key_actions.size() == 0) {
211 layout_message.mutable_keys()->erase(usb_code);
212 }
213 }
214
215 callback_context->task_runner->PostTask(
216 FROM_HERE,
217 base::BindOnce(&KeyboardLayoutMonitorMac::OnLayoutChanged,
218 callback_context->weak_ptr, std::move(layout_message)));
219 }
220
GetFixedKeyFunction(int keycode)221 base::Optional<protocol::LayoutKeyFunction> GetFixedKeyFunction(int keycode) {
222 // Some keys are not represented in the layout and always have the same
223 // function.
224 switch (keycode) {
225 case kVK_Command:
226 case kVK_RightCommand:
227 return protocol::LayoutKeyFunction::COMMAND;
228 case kVK_Shift:
229 case kVK_RightShift:
230 return protocol::LayoutKeyFunction::SHIFT;
231 case kVK_CapsLock:
232 return protocol::LayoutKeyFunction::CAPS_LOCK;
233 case kVK_Option:
234 case kVK_RightOption:
235 return protocol::LayoutKeyFunction::OPTION;
236 case kVK_Control:
237 case kVK_RightControl:
238 return protocol::LayoutKeyFunction::CONTROL;
239 case kVK_F1:
240 return protocol::LayoutKeyFunction::F1;
241 case kVK_F2:
242 return protocol::LayoutKeyFunction::F2;
243 case kVK_F3:
244 return protocol::LayoutKeyFunction::F3;
245 case kVK_F4:
246 return protocol::LayoutKeyFunction::F4;
247 case kVK_F5:
248 return protocol::LayoutKeyFunction::F5;
249 case kVK_F6:
250 return protocol::LayoutKeyFunction::F6;
251 case kVK_F7:
252 return protocol::LayoutKeyFunction::F7;
253 case kVK_F8:
254 return protocol::LayoutKeyFunction::F8;
255 case kVK_F9:
256 return protocol::LayoutKeyFunction::F9;
257 case kVK_F10:
258 return protocol::LayoutKeyFunction::F10;
259 case kVK_F11:
260 return protocol::LayoutKeyFunction::F11;
261 case kVK_F12:
262 return protocol::LayoutKeyFunction::F12;
263 case kVK_F13:
264 return protocol::LayoutKeyFunction::F13;
265 case kVK_F14:
266 return protocol::LayoutKeyFunction::F14;
267 case kVK_F15:
268 return protocol::LayoutKeyFunction::F15;
269 case kVK_F16:
270 return protocol::LayoutKeyFunction::F16;
271 case kVK_F17:
272 return protocol::LayoutKeyFunction::F17;
273 case kVK_F18:
274 return protocol::LayoutKeyFunction::F18;
275 case kVK_F19:
276 return protocol::LayoutKeyFunction::F19;
277 case kVK_JIS_Kana:
278 return protocol::LayoutKeyFunction::KANA;
279 case kVK_JIS_Eisu:
280 return protocol::LayoutKeyFunction::EISU;
281 default:
282 return base::nullopt;
283 }
284 }
285
GetCharFunction(UniChar char_code,int keycode)286 base::Optional<protocol::LayoutKeyFunction> GetCharFunction(UniChar char_code,
287 int keycode) {
288 switch (char_code) {
289 case kHomeCharCode:
290 return protocol::LayoutKeyFunction::HOME;
291 case kEnterCharCode:
292 // Numpad Enter
293 return protocol::LayoutKeyFunction::ENTER;
294 case kEndCharCode:
295 return protocol::LayoutKeyFunction::END;
296 case kHelpCharCode:
297 // Old Macs called this key "Help". Newer Macs lack it altogether (and
298 // have "Fn" in this position).
299 // TODO(rkjnsn): See how the latest macOS handles this key, and consider
300 // hiding this key or creating a distinct HELP function, as appropriate.
301 return protocol::LayoutKeyFunction::INSERT;
302 case kBackspaceCharCode:
303 return protocol::LayoutKeyFunction::BACKSPACE;
304 case kTabCharCode:
305 return protocol::LayoutKeyFunction::TAB;
306 case kPageUpCharCode:
307 return protocol::LayoutKeyFunction::PAGE_UP;
308 case kPageDownCharCode:
309 return protocol::LayoutKeyFunction::PAGE_DOWN;
310 case kReturnCharCode:
311 // Writing system return key.
312 return protocol::LayoutKeyFunction::ENTER;
313 case kFunctionKeyCharCode:
314 // The known keys with this char code are handled by GetFixedKeyFunction.
315 return protocol::LayoutKeyFunction::UNKNOWN;
316 case kEscapeCharCode:
317 if (keycode == kVK_ANSI_KeypadClear) {
318 return protocol::LayoutKeyFunction::CLEAR;
319 }
320 return protocol::LayoutKeyFunction::ESCAPE;
321 case kLeftArrowCharCode:
322 return protocol::LayoutKeyFunction::ARROW_LEFT;
323 case kRightArrowCharCode:
324 return protocol::LayoutKeyFunction::ARROW_RIGHT;
325 case kUpArrowCharCode:
326 return protocol::LayoutKeyFunction::ARROW_UP;
327 case kDownArrowCharCode:
328 return protocol::LayoutKeyFunction::ARROW_DOWN;
329 case kDeleteCharCode:
330 return protocol::LayoutKeyFunction::DELETE_;
331 default:
332 return base::nullopt;
333 }
334 }
335
336 } // namespace
337
Create(base::RepeatingCallback<void (const protocol::KeyboardLayout &)> callback,scoped_refptr<base::SingleThreadTaskRunner> input_task_runner)338 std::unique_ptr<KeyboardLayoutMonitor> KeyboardLayoutMonitor::Create(
339 base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback,
340 scoped_refptr<base::SingleThreadTaskRunner> input_task_runner) {
341 return std::make_unique<KeyboardLayoutMonitorMac>(std::move(callback));
342 }
343
344 } // namespace remoting
345