1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //     * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //     * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //     * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 #include "gui/config_dialog/keybinding_editor.h"
31 
32 #if defined(OS_ANDROID) || defined(OS_NACL)
33 #error "This platform is not supported."
34 #endif  // OS_ANDROID || OS_NACL
35 
36 #ifdef OS_WIN
37 #include <windows.h>
38 #include <imm.h>
39 #include <ime.h>
40 #elif OS_LINUX
41 #define XK_MISCELLANY
42 #include <X11/keysymdef.h>
43 #endif
44 
45 #include <QtCore/QString>
46 #include <QtWidgets/QMessageBox>
47 #include <QtWidgets/QMenu>
48 #include <QtWidgets/QPushButton>
49 #include <QtWidgets/QTableWidget>
50 
51 #include "base/logging.h"
52 #include "base/util.h"
53 
54 namespace mozc {
55 namespace gui {
56 
57 namespace {
58 struct QtKeyEntry {
59   Qt::Key qt_key;
60   const char *mozc_key_name;
61 };
62 
63 // TODO(taku): check it these mappings are correct.
64 const QtKeyEntry kQtKeyModifierNonRequiredTable[] = {
65   { Qt::Key_Escape, "Escape" },
66   { Qt::Key_Tab, "Tab" },
67   { Qt::Key_Backtab, "Tab" },   // Qt handles Tab + Shift as a special key
68   { Qt::Key_Backspace, "Backspace" },
69   { Qt::Key_Return, "Enter" },
70   { Qt::Key_Enter, "Enter" },
71   { Qt::Key_Insert, "Insert" },
72   { Qt::Key_Delete, "Delete" },
73   { Qt::Key_Home, "Home" },
74   { Qt::Key_End, "End" },
75   { Qt::Key_Left, "Left" },
76   { Qt::Key_Up, "Up" },
77   { Qt::Key_Right, "Right" },
78   { Qt::Key_Down, "Down" },
79   { Qt::Key_PageUp, "PageUp" },
80   { Qt::Key_PageDown, "PageDown" },
81   { Qt::Key_Space, "Space" },
82   { Qt::Key_F1, "F1" },
83   { Qt::Key_F2, "F2" },
84   { Qt::Key_F3, "F3" },
85   { Qt::Key_F4, "F4" },
86   { Qt::Key_F5, "F5" },
87   { Qt::Key_F6, "F6" },
88   { Qt::Key_F7, "F7" },
89   { Qt::Key_F8, "F8" },
90   { Qt::Key_F9, "F9" },
91   { Qt::Key_F10, "F10" },
92   { Qt::Key_F11, "F11" },
93   { Qt::Key_F12, "F12" },
94   { Qt::Key_F13, "F13" },
95   { Qt::Key_F14, "F14" },
96   { Qt::Key_F15, "F15" },
97   { Qt::Key_F16, "F16" },
98   { Qt::Key_F17, "F17" },
99   { Qt::Key_F18, "F18" },
100   { Qt::Key_F19, "F19" },
101   { Qt::Key_F20, "F20" },
102   { Qt::Key_F21, "F21" },
103   { Qt::Key_F22, "F22" },
104   { Qt::Key_F23, "F23" },
105   { Qt::Key_F24, "F24" }
106 };
107 
108 #ifdef OS_WIN
109 struct WinVirtualKeyEntry {
110   DWORD virtual_key;
111   const char *mozc_key_name;
112 };
113 
114 const WinVirtualKeyEntry kWinVirtualKeyModifierNonRequiredTable[] = {
115   //  { VK_DBE_HIRAGANA, "Kana" },       // Kana
116   // "Hiragana" and "Kana" are the same key on Mozc
117   { VK_DBE_HIRAGANA, "Hiragana" },       // Hiragana
118   { VK_DBE_KATAKANA, "Katakana" },        // Ktakana
119   { VK_DBE_ALPHANUMERIC, "Eisu" },   // Eisu
120   // TODO(taku): better to support Romaji key
121   // { VK_DBE_ROMAN, "Romaji" },           // Romaji
122   // { VK_DBE_NOROMAN, "Romaji" },         // Romaji
123   { VK_NONCONVERT, "Muhenkan" },     // Muhenkan
124   { VK_CONVERT, "Henkan" },           // Henkan
125   // JP109's Hankaku/Zenkaku key has two V_KEY for toggling IME-On and Off.
126   // Althogh these are visible keys on 109JP, Mozc doesn't support them.
127   { VK_DBE_SBCSCHAR, "Hankaku/Zenkaku" },        // Zenkaku/hankaku
128   { VK_DBE_DBCSCHAR, "Hankaku/Zenkaku" },        // Zenkaku/hankaku
129   // { VK_KANJI, "Kanji" },  // Do not support Kanji
130 };
131 #elif OS_LINUX
132 struct LinuxVirtualKeyEntry {
133   uint16 virtual_key;
134   const char *mozc_key_name;
135 };
136 
137 const LinuxVirtualKeyEntry kLinuxVirtualKeyModifierNonRequiredTable[] = {
138   { XK_Muhenkan, "Muhenkan" },
139   { XK_Henkan, "Henkan" },
140   { XK_Hiragana, "Hiragana" },
141   { XK_Katakana, "Katakana" },
142   // We need special hack for Hiragana_Katakana key. For the detail, please see
143   // KeyBindingFilter::AddKey implementation.
144   { XK_Hiragana_Katakana, "Hiragana" },
145   { XK_Eisu_toggle, "Eisu" },
146   { XK_Zenkaku_Hankaku, "Hankaku/Zenkaku" },
147 };
148 #endif
149 
150 // On Windows Hiragana/Eisu keys only emits KEY_DOWN event.
151 // for these keys we don't hanlde auto-key repeat.
IsDownOnlyKey(const QKeyEvent & key_event)152 bool IsDownOnlyKey(const QKeyEvent &key_event) {
153 #ifdef OS_WIN
154   const DWORD virtual_key = key_event.nativeVirtualKey();
155   return (virtual_key == VK_DBE_ALPHANUMERIC ||
156           virtual_key == VK_DBE_HIRAGANA ||
157           virtual_key == VK_DBE_KATAKANA);
158 #else
159   return false;
160 #endif  // OS_WIN
161 }
162 
IsAlphabet(const char key)163 bool IsAlphabet(const char key) {
164   return (key >= 'a' && key <= 'z');
165 }
166 }  // namespace
167 
168 class KeyBindingFilter : public QObject {
169  public:
170   KeyBindingFilter(QLineEdit *line_edit, QPushButton *ok_button);
171   virtual ~KeyBindingFilter();
172 
173   enum KeyState {
174     DENY_KEY,
175     ACCEPT_KEY,
176     SUBMIT_KEY
177   };
178 
179  protected:
180   bool eventFilter(QObject *obj, QEvent *event);
181 
182  private:
183   void Reset();
184 
185   // add new "qt_key" to the filter.
186   // return true if the current key_bindings the KeyBindingFilter holds
187   // is valid. Composed key_bindings are stored to "result"
188   KeyState AddKey(const QKeyEvent &key_event, QString *result);
189 
190   // encode the current key binding
191   KeyState Encode(QString *result) const;
192 
193   bool committed_;
194   bool ctrl_pressed_;
195   bool alt_pressed_;
196   bool shift_pressed_;
197   QString modifier_required_key_;
198   QString modifier_non_required_key_;
199   QString unknown_key_;
200   QLineEdit *line_edit_;
201   QPushButton *ok_button_;
202 };
203 
KeyBindingFilter(QLineEdit * line_edit,QPushButton * ok_button)204 KeyBindingFilter::KeyBindingFilter(QLineEdit *line_edit,
205                                    QPushButton *ok_button)
206     : committed_(false),
207       ctrl_pressed_(false),
208       alt_pressed_(false),
209       shift_pressed_(false),
210       line_edit_(line_edit),
211       ok_button_(ok_button) {
212   Reset();
213 }
214 
~KeyBindingFilter()215 KeyBindingFilter::~KeyBindingFilter() {}
216 
Reset()217 void KeyBindingFilter::Reset() {
218   ctrl_pressed_ = false;
219   alt_pressed_ = false;
220   shift_pressed_ = false;
221   modifier_required_key_.clear();
222   modifier_non_required_key_.clear();
223   unknown_key_.clear();
224   committed_ = true;
225   ok_button_->setEnabled(false);
226 }
227 
Encode(QString * result) const228 KeyBindingFilter::KeyState KeyBindingFilter::Encode(QString *result) const {
229   CHECK(result);
230 
231   // We don't accept any modifier keys for Hiragana, Eisu, Hankaku/Zenkaku keys.
232   // On Windows, KEY_UP event is not raised for Hiragana/Eisu keys
233   // until alternative keys (e.g., Eisu for Hiragana and Hiragana for Eisu)
234   // are pressed. If Hiragana/Eisu key is pressed, we assume that
235   // the key is already released at the same time.
236   // Hankaku/Zenkaku key is preserved key and modifier keys are ignored.
237   if (modifier_non_required_key_ == "Hiragana" ||
238       modifier_non_required_key_ == "Katakana" ||
239       modifier_non_required_key_ == "Eisu" ||
240       modifier_non_required_key_ == "Hankaku/Zenkaku") {
241     *result = modifier_non_required_key_;
242     return KeyBindingFilter::SUBMIT_KEY;
243   }
244 
245   QStringList results;
246 
247   if (ctrl_pressed_) {
248     results << "Ctrl";
249   }
250 
251   if (shift_pressed_) {
252     results << "Shift";
253   }
254 
255   if (alt_pressed_) {
256 #ifdef OS_MACOSX
257     results << "Option";
258 #else
259     // Do not support and show keybindings with alt for Windows
260     // results << "Alt";
261 #endif
262   }
263 
264   const bool has_modifier = !results.isEmpty();
265 
266   if (!modifier_non_required_key_.isEmpty()) {
267     results << modifier_non_required_key_;
268   }
269 
270   if (!modifier_required_key_.isEmpty()) {
271     results << modifier_required_key_;
272   }
273 
274   // in release binary, unknown_key_ is hidden
275 #ifndef NO_LOGGING
276   if (!unknown_key_.isEmpty()) {
277     results << unknown_key_;
278   }
279 #endif
280 
281   KeyBindingFilter::KeyState result_state = KeyBindingFilter::ACCEPT_KEY;
282 
283   if (!unknown_key_.isEmpty()) {
284     result_state = KeyBindingFilter::DENY_KEY;
285   }
286 
287   const char key = modifier_required_key_.isEmpty() ?
288       0 : modifier_required_key_[0].toLatin1();
289 
290   // Alt or Ctrl or these combinations
291   if ((alt_pressed_ || ctrl_pressed_) &&
292       modifier_non_required_key_.isEmpty() &&
293       modifier_required_key_.isEmpty()) {
294     result_state = KeyBindingFilter::DENY_KEY;
295   }
296 
297   // TODO(taku) Shift + 3 ("#" on US-keyboard) is also valid
298   // keys, but we disable it for now, since we have no way
299   // to get the original key "3" from "#" only with Qt layer.
300   // need to see platform dependent scan code here.
301 
302   // Don't support Shift only
303   // Shift in composition is set to EDIT_INSERT by default.
304   // Now we do not make the keybindings for EDIT_INSERT configurable.
305   // For avoiding complexity, we do not support Shift here.
306   if (shift_pressed_ && !ctrl_pressed_ && !alt_pressed_ &&
307       modifier_required_key_.isEmpty() &&
308       modifier_non_required_key_.isEmpty()) {
309     result_state = KeyBindingFilter::DENY_KEY;
310   }
311 
312   // Don't support Shift + 'a' only
313   if (shift_pressed_ && !ctrl_pressed_ && !alt_pressed_ &&
314       !modifier_required_key_.isEmpty() && IsAlphabet(key)) {
315     result_state = KeyBindingFilter::DENY_KEY;
316   }
317 
318   // Don't support Shift + Ctrl + '@'
319   if (shift_pressed_ && !modifier_required_key_.isEmpty() &&
320       !IsAlphabet(key)) {
321     result_state = KeyBindingFilter::DENY_KEY;
322   }
323 
324   // no modifer for modifier_required_key
325   if (!has_modifier && !modifier_required_key_.isEmpty()) {
326     result_state = KeyBindingFilter::DENY_KEY;
327   }
328 
329   // modifier_required_key and modifier_non_required_key
330   // cannot co-exist
331   if (!modifier_required_key_.isEmpty() &&
332       !modifier_non_required_key_.isEmpty()) {
333     result_state = KeyBindingFilter::DENY_KEY;
334   }
335 
336   // no valid key
337   if (results.empty()) {
338     result_state = KeyBindingFilter::DENY_KEY;
339   }
340 
341   *result = results.join(" ");
342 
343   return result_state;
344 }
345 
AddKey(const QKeyEvent & key_event,QString * result)346 KeyBindingFilter::KeyState KeyBindingFilter::AddKey(
347     const QKeyEvent &key_event, QString *result) {
348   CHECK(result);
349   result->clear();
350 
351   const int qt_key = key_event.key();
352 
353   // modifier keys
354   switch (qt_key) {
355 #ifdef OS_MACOSX
356     case Qt::Key_Meta:
357       ctrl_pressed_ = true;
358       return Encode(result);
359     case Qt::Key_Alt:   // Option key
360      //    case Qt::Key_Control:  Command key
361       alt_pressed_ = true;
362       return Encode(result);
363 #else
364     case Qt::Key_Control:
365       ctrl_pressed_ = true;
366       return Encode(result);
367       //    case Qt::Key_Meta:  // Windows key
368     case Qt::Key_Alt:
369       alt_pressed_ = true;
370       return Encode(result);
371 #endif
372     case Qt::Key_Shift:
373       shift_pressed_ = true;
374       return Encode(result);
375     default:
376       break;
377   }
378 
379   // non-printable command, which doesn't require modifier keys
380   for (size_t i = 0; i < arraysize(kQtKeyModifierNonRequiredTable); ++i) {
381     if (kQtKeyModifierNonRequiredTable[i].qt_key == qt_key) {
382       modifier_non_required_key_ =
383           kQtKeyModifierNonRequiredTable[i].mozc_key_name;
384       return Encode(result);
385     }
386   }
387 
388 #ifdef OS_WIN
389   // Handle JP109's Muhenkan/Henkan/katakana-hiragana and Zenkaku/Hankaku
390   const DWORD virtual_key = key_event.nativeVirtualKey();
391   for (size_t i = 0; i < arraysize(kWinVirtualKeyModifierNonRequiredTable);
392        ++i) {
393     if (kWinVirtualKeyModifierNonRequiredTable[i].virtual_key ==
394         virtual_key) {
395       modifier_non_required_key_ =
396           kWinVirtualKeyModifierNonRequiredTable[i].mozc_key_name;
397       return Encode(result);
398     }
399   }
400 #elif OS_LINUX
401   const uint16 virtual_key = key_event.nativeVirtualKey();
402 
403   // The XKB defines three types of logical key code: "xkb::Hiragana",
404   // "xkb::Katakana" and "xkb::Hiragana_Katakana".
405   // On most of Linux distributions, any key event against physical
406   // "ひらがな/カタカナ" key is likely to be mapped into
407   // "xkb::Hiragana_Katakana" regardless of the state of shift modifier. This
408   // means that you are likely to receive "Shift + xkb::Hiragana_Katakana"
409   // rather than "xkb::Katakana" when you physically press Shift +
410   // "ひらがな/カタカナ".
411   // On the other hand, Mozc protocol expects that Shift + "ひらがな/カタカナ"
412   // key event is always interpret as "{special_key: KeyEvent::KATAKANA}"
413   // without shift modifier. This is why we have the following special treatment
414   // against "shift + XK_Hiragana_Katakana". See b/6087341 for the background
415   // information.
416   // We use |key_event.modifiers()| instead of |shift_pressed_| because
417   // |shift_pressed_| is no longer valid in the following scenario.
418   //   1. Press "Shift"
419   //   2. Press "Hiragana/Katakana"  (shift_pressed_ == true)
420   //   3. Press "Hiragana/Katakana"  (shift_pressed_ == false)
421   const bool with_shift = (key_event.modifiers() & Qt::ShiftModifier) != 0;
422   if (with_shift && (virtual_key == XK_Hiragana_Katakana)) {
423     modifier_non_required_key_ = "Katakana";
424     return Encode(result);
425   }
426 
427   // Handle JP109's Muhenkan/Henkan/katakana-hiragana and Zenkaku/Hankaku
428   for (size_t i = 0; i < arraysize(kLinuxVirtualKeyModifierNonRequiredTable);
429        ++i) {
430     if (kLinuxVirtualKeyModifierNonRequiredTable[i].virtual_key ==
431         virtual_key) {
432       modifier_non_required_key_ =
433           kLinuxVirtualKeyModifierNonRequiredTable[i].mozc_key_name;
434       return Encode(result);
435     }
436   }
437 #endif
438 
439   if (qt_key == Qt::Key_yen) {
440     // Japanese Yen mark, treat it as backslash for compatibility
441     modifier_non_required_key_ = "\\";
442     return Encode(result);
443   }
444 
445   // printable command, which requires modifier keys
446   if ((qt_key >= 0x21 && qt_key <= 0x60) ||
447       (qt_key >= 0x7B && qt_key <= 0x7E)) {
448     if (qt_key >= 0x41 && qt_key <= 0x5A) {
449       modifier_required_key_ = static_cast<char>(qt_key - 'A' + 'a');
450     } else {
451       modifier_required_key_ = static_cast<char>(qt_key);
452     }
453     return Encode(result);
454   }
455 
456   unknown_key_.sprintf("<UNK:0x%x 0x%x 0x%x>",
457                        key_event.key(),
458                        key_event.nativeScanCode(),
459                        key_event.nativeVirtualKey());
460 
461   return Encode(result);
462 }
463 
eventFilter(QObject * obj,QEvent * event)464 bool KeyBindingFilter::eventFilter(QObject *obj, QEvent *event) {
465   if ((event->type() == QEvent::KeyPress ||
466        event->type() == QEvent::KeyRelease) &&
467       !IsDownOnlyKey(*static_cast<QKeyEvent *>(event)) &&
468       static_cast<QKeyEvent *>(event)->isAutoRepeat()) {
469     // ignores auto key repeat. just eat the event
470     return true;
471   }
472 
473   // TODO(taku): the following sequence doesn't work as once
474   // user relase any of keys, the statues goes to "submitted"
475   // 1. Press Ctrl + a
476   // 2. Release a, but keep pressing Ctrl
477   // 3. Press b  (the result should be "Ctrl + b").
478 
479   if (event->type() == QEvent::KeyPress) {
480     // when the state is committed, reset the internal key binding
481     if (committed_) {
482       Reset();
483       line_edit_->clear();
484     }
485     committed_ = false;
486     const QKeyEvent *key_event = static_cast<QKeyEvent *>(event);
487     QString result;
488     const KeyBindingFilter::KeyState state = AddKey(*key_event, &result);
489     ok_button_->setEnabled(state != KeyBindingFilter::DENY_KEY);
490     line_edit_->setText(result);
491     line_edit_->setCursorPosition(0);
492     line_edit_->setAttribute(Qt::WA_InputMethodEnabled, false);
493     if (state == KeyBindingFilter::SUBMIT_KEY) {
494       committed_ = true;
495     }
496     return true;
497   } else if (event->type() == QEvent::KeyRelease) {
498     // when any of key is released, change the state "committed";
499     line_edit_->setCursorPosition(0);
500     committed_ = true;
501     return true;
502   }
503 
504   return QObject::eventFilter(obj, event);
505 }
506 
KeyBindingEditor(QWidget * parent,QWidget * trigger_parent)507 KeyBindingEditor::KeyBindingEditor(QWidget *parent, QWidget *trigger_parent)
508     : QDialog(parent), trigger_parent_(trigger_parent) {
509   setupUi(this);
510 #if defined(OS_LINUX)
511   // Workaround for the issue https://github.com/google/mozc/issues/9
512   // Seems that even after clicking the button for the keybinding dialog,
513   // the edit is not raised. This might be a bug of setFocusProxy.
514   setWindowFlags(Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint |
515                  Qt::Tool | Qt::WindowStaysOnTopHint);
516 #else
517   setWindowFlags(Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint |
518                  Qt::Tool);
519 #endif
520 
521   QPushButton *ok_button =
522       KeyBindingEditorbuttonBox->button(QDialogButtonBox::Ok);
523   CHECK(ok_button != NULL);
524 
525   filter_.reset(new KeyBindingFilter(KeyBindingLineEdit, ok_button));
526   KeyBindingLineEdit->installEventFilter(filter_.get());
527 
528   // no right click
529   KeyBindingLineEdit->setContextMenuPolicy(Qt::NoContextMenu);
530   KeyBindingLineEdit->setMaxLength(32);
531   KeyBindingLineEdit->setAttribute(Qt::WA_InputMethodEnabled, false);
532 
533 #ifdef OS_WIN
534   ::ImmAssociateContext(reinterpret_cast<HWND>(KeyBindingLineEdit->winId()), 0);
535 #endif
536 
537   QObject::connect(KeyBindingEditorbuttonBox,
538                    SIGNAL(clicked(QAbstractButton *)),
539                    this,
540                    SLOT(Clicked(QAbstractButton *)));
541 
542   setFocusProxy(KeyBindingLineEdit);
543 }
544 
~KeyBindingEditor()545 KeyBindingEditor::~KeyBindingEditor() {}
546 
Clicked(QAbstractButton * button)547 void KeyBindingEditor::Clicked(QAbstractButton *button) {
548   switch (KeyBindingEditorbuttonBox->buttonRole(button)) {
549     case QDialogButtonBox::AcceptRole:
550       QDialog::accept();
551       break;
552     default:
553       QDialog::reject();
554       break;
555   }
556 }
557 
GetBinding() const558 const QString KeyBindingEditor::GetBinding() const {
559   return KeyBindingLineEdit->text();
560 }
561 
SetBinding(const QString & binding)562 void KeyBindingEditor::SetBinding(const QString &binding) {
563   KeyBindingLineEdit->setText(binding);
564   KeyBindingLineEdit->setCursorPosition(0);
565 }
566 }  // namespace gui
567 }  // namespace mozc
568