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