| // Copyright 2010-2015, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include <windows.h> |
| #include <ime.h> |
| #include <msctf.h> |
| |
| #include <string> |
| #include <memory> |
| |
| #include "base/logging.h" |
| #include "base/system_util.h" |
| #include "base/version.h" |
| #include "client/client_interface.h" |
| #include "config/config_handler.h" |
| #include "ipc/ipc_mock.h" |
| #include "session/commands.pb.h" |
| #include "testing/base/public/googletest.h" |
| #include "testing/base/public/gunit.h" |
| #include "win32/base/input_state.h" |
| #include "win32/base/keyboard.h" |
| #include "win32/base/keyevent_handler.h" |
| |
| namespace mozc { |
| namespace win32 { |
| namespace { |
| |
| using commands::Context; |
| using commands::Output; |
| using std::unique_ptr; |
| |
| class TestableKeyEventHandler : public KeyEventHandler { |
| public: |
| // Change access rights |
| using KeyEventHandler::HandleKey; |
| using KeyEventHandler::ConvertToKeyEvent; |
| using KeyEventHandler::UnlockKanaLock; |
| using KeyEventHandler::MaybeSpawnTool; |
| }; |
| |
| const BYTE kPressed = 0x80; |
| const BYTE kToggled = 0x01; |
| LPARAM CreateLParam(uint16 repeat_count, |
| uint8 scan_code, |
| bool is_extended_key, |
| bool has_context_code, |
| bool is_previous_state_down, |
| bool is_in_transition_state) { |
| DWORD value = 0; |
| value |= repeat_count; |
| value |= (static_cast<DWORD>(scan_code) << 16); |
| value |= (is_extended_key ? (1 << 24) : 0); |
| value |= (has_context_code ? (1 << 29) : 0); |
| value |= (is_previous_state_down ? (1 << 30) : 0); |
| value |= (is_in_transition_state ? (1 << 31) : 0); |
| const LPARAM param = static_cast<LPARAM>(value); |
| #if defined(_M_X64) |
| // In x64 environment, upper DWORD will be filled with 0. |
| EXPECT_EQ(0, param & 0xffffffff00000000); |
| #endif // _M_X64 |
| return param; |
| } |
| |
| class TestServerLauncher : public client::ServerLauncherInterface { |
| public: |
| explicit TestServerLauncher(IPCClientFactoryMock *factory) |
| : factory_(factory), |
| start_server_result_(false), |
| start_server_called_(false), |
| server_protocol_version_(IPC_PROTOCOL_VERSION) {} |
| |
| virtual void Ready() {} |
| virtual void Wait() {} |
| virtual void Error() {} |
| |
| virtual bool StartServer(client::ClientInterface *client) { |
| if (!response_.empty()) { |
| factory_->SetMockResponse(response_); |
| } |
| factory_->SetServerProtocolVersion(server_protocol_version_); |
| start_server_called_ = true; |
| return start_server_result_; |
| } |
| |
| virtual bool ForceTerminateServer(const string &name) { |
| return true; |
| } |
| |
| virtual bool WaitServer(uint32 pid) { |
| return true; |
| } |
| |
| virtual void OnFatal(ServerLauncherInterface::ServerErrorType type) { |
| LOG(ERROR) << static_cast<int>(type); |
| error_map_[static_cast<int>(type)]++; |
| } |
| |
| int error_count(ServerLauncherInterface::ServerErrorType type) { |
| return error_map_[static_cast<int>(type)]; |
| } |
| |
| bool start_server_called() const { |
| return start_server_called_; |
| } |
| |
| void set_start_server_called(bool start_server_called) { |
| start_server_called_ = start_server_called; |
| } |
| |
| virtual void set_restricted(bool restricted) {} |
| |
| virtual void set_suppress_error_dialog(bool suppress) {} |
| |
| virtual void set_server_program(const string &server_path) {} |
| |
| virtual const string &server_program() const { |
| static const string path; |
| return path; |
| } |
| |
| void set_start_server_result(const bool result) { |
| start_server_result_ = result; |
| } |
| |
| void set_server_protocol_version(uint32 server_protocol_version) { |
| server_protocol_version_ = server_protocol_version; |
| } |
| |
| uint32 set_server_protocol_version() const { |
| return server_protocol_version_; |
| } |
| |
| void set_mock_after_start_server(const Output &mock_output) { |
| mock_output.SerializeToString(&response_); |
| } |
| |
| private: |
| IPCClientFactoryMock *factory_; |
| bool start_server_result_; |
| bool start_server_called_; |
| uint32 server_protocol_version_; |
| string response_; |
| map<int, int> error_map_; |
| }; |
| |
| class KeyboardMock : public Win32KeyboardInterface { |
| public: |
| explicit KeyboardMock(bool initial_kana_lock_state) { |
| if (initial_kana_lock_state) { |
| key_state_.SetState(VK_KANA, kPressed); |
| } |
| } |
| bool kana_locked() const { |
| return ((key_state_.GetState(VK_KANA) & kPressed) == kPressed); |
| } |
| virtual bool IsKanaLocked(const KeyboardStatus & /*keyboard_state*/) { |
| return kana_locked(); |
| } |
| virtual bool SetKeyboardState(const KeyboardStatus &keyboard_state) { |
| key_state_ = keyboard_state; |
| return true; |
| } |
| virtual bool GetKeyboardState(KeyboardStatus *keyboard_state) { |
| *keyboard_state = key_state_; |
| return true; |
| } |
| virtual bool AsyncIsKeyPressed(int virtual_key) { |
| return key_state_.IsPressed(virtual_key); |
| } |
| virtual int ToUnicode(UINT wVirtKey, UINT wScanCode, |
| const BYTE *lpKeyState, LPWSTR pwszBuff, int cchBuff, UINT wFlags) { |
| // We use a mock class in case the Japanese keyboard layout is not |
| // available on this system. This emulator class should work well in most |
| // cases. It returns an unicode character (if any) as if Japanese keyboard |
| // layout was currently active. |
| return JapaneseKeyboardLayoutEmulator::ToUnicode( |
| wVirtKey, wScanCode, lpKeyState, pwszBuff, cchBuff, wFlags); |
| } |
| virtual UINT SendInput(const vector<INPUT> &input) { |
| // Not implemented. |
| return 0; |
| } |
| |
| private: |
| KeyboardStatus key_state_; |
| DISALLOW_COPY_AND_ASSIGN(KeyboardMock); |
| }; |
| |
| class MockState { |
| public: |
| MockState() |
| : client_(nullptr), |
| launcher_(nullptr) {} |
| explicit MockState(const Output &mock_response) |
| : client_(client::ClientFactory::NewClient()), |
| launcher_(nullptr) { |
| client_factory_.SetConnection(true); |
| client_factory_.SetResult(true); |
| client_factory_.SetServerProductVersion(Version::GetMozcVersion()); |
| client_factory_.SetMockResponse(mock_response.SerializeAsString()); |
| client_->SetIPCClientFactory(&client_factory_); |
| |
| // |launcher| will be deleted by the |client| |
| launcher_ = new TestServerLauncher(&client_factory_); |
| client_->SetServerLauncher(launcher_); |
| launcher_->set_start_server_result(true); |
| } |
| |
| client::ClientInterface * mutable_client() { |
| return client_.get(); |
| } |
| |
| bool GetGeneratedRequest(mozc::commands::Input *input) { |
| return input->ParseFromString(client_factory_.GetGeneratedRequest()); |
| } |
| |
| bool start_server_called() { |
| return launcher_->start_server_called(); |
| } |
| |
| private: |
| IPCClientFactoryMock client_factory_; |
| unique_ptr<client::ClientInterface> client_; |
| TestServerLauncher *launcher_; |
| DISALLOW_COPY_AND_ASSIGN(MockState); |
| }; |
| |
| } // namespace |
| |
| class KeyEventHandlerTest : public testing::Test { |
| protected: |
| KeyEventHandlerTest() {} |
| virtual ~KeyEventHandlerTest() {} |
| virtual void SetUp() { |
| SystemUtil::SetUserProfileDirectory(FLAGS_test_tmpdir); |
| mozc::config::ConfigHandler::GetDefaultConfig(&default_config_); |
| mozc::config::ConfigHandler::SetConfig(default_config_); |
| } |
| |
| virtual void TearDown() { |
| mozc::config::ConfigHandler::SetConfig(default_config_); |
| } |
| |
| vector<KeyInformation> GetDefaultDirectModeKeys() const { |
| return KeyInfoUtil::ExtractSortedDirectModeKeys(default_config_); |
| } |
| |
| vector<KeyInformation> GetDirectModeKeysCtrlJToEnableIME() const { |
| config::Config config; |
| config.CopyFrom(default_config_); |
| |
| const char custom_keymap_table[] = |
| "status\tkey\tcommand\n" |
| "DirectInput\tCtrl j\tIMEOn\n"; |
| |
| config.set_session_keymap(mozc::config::Config::CUSTOM); |
| config.set_custom_keymap_table(custom_keymap_table); |
| |
| return KeyInfoUtil::ExtractSortedDirectModeKeys(config); |
| } |
| |
| vector<KeyInformation> GetDirectModeKeysCtrlBackslashToEnableIME() const { |
| config::Config config; |
| config.CopyFrom(default_config_); |
| |
| const char custom_keymap_table[] = |
| "status\tkey\tcommand\n" |
| "DirectInput\tCtrl \\\tIMEOn\n"; |
| |
| config.set_session_keymap(mozc::config::Config::CUSTOM); |
| config.set_custom_keymap_table(custom_keymap_table); |
| |
| return KeyInfoUtil::ExtractSortedDirectModeKeys(config); |
| } |
| |
| mozc::config::Config default_config_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(KeyEventHandlerTest); |
| }; |
| |
| TEST_F(KeyEventHandlerTest, HankakuZenkakuTest) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| Output output; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // "Hankaku/Zenkaku" |
| { |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_DBE_DBCSCHAR, kPressed); |
| |
| const VirtualKey virtual_key = |
| VirtualKey::FromVirtualKey(VK_DBE_DBCSCHAR); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.open = false; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, |
| &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_FALSE(actual_input.key().has_key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_FALSE(actual_input.key().activated()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(0, actual_input.key().modifier_keys_size()); |
| EXPECT_TRUE(actual_input.key().has_special_key()); |
| EXPECT_EQ(commands::KeyEvent::HANKAKU, actual_input.key().special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, ClearKanaLockInAlphanumericMode) { |
| // Call UnlockKanaLockIfNeeded just after the IME starts to handle key event |
| // because there might be no chance to unlock an unexpected Kana-Lock except |
| // for the key event handler in some tricky cases. |
| |
| const bool kKanaLocked = true; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| EXPECT_TRUE(keyboard.kana_locked()); |
| |
| InputState next_state; |
| Output output; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // "Escape" |
| { |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_ESCAPE, kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_ESCAPE); |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x01, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| false, // is_previous_state_down, |
| false)); // is_in_transition_state |
| EXPECT_EQ(0x00010001, lparam.lparam()); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.open = false; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeProcessKey( |
| virtual_key, lparam.GetScanCode(), lparam.IsKeyDownInImeProcessKey(), |
| keyboard_status, behavior, initial_state, context, |
| mock.mutable_client(), &keyboard, &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_FALSE(result.should_be_eaten); |
| EXPECT_FALSE(result.should_be_sent_to_server); |
| EXPECT_FALSE(next_state.open); |
| EXPECT_FALSE(mock.start_server_called()); |
| EXPECT_FALSE(keyboard.kana_locked()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, ClearKanaLockEvenWhenIMEIsDisabled) { |
| // Even in the safe mode such as logon screen, it would be better to clear |
| // kana-lock in some cases. This helps users to input their password as |
| // expected except that they used half-width katakana for their password. |
| |
| const bool kKanaLocked = true; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| EXPECT_TRUE(keyboard.kana_locked()); |
| |
| InputState next_state; |
| Output output; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = true; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // "A" |
| { |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('A', kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('A'); |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x1e, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| false, // is_previous_state_down, |
| false)); // is_in_transition_state |
| EXPECT_EQ(0x1e0001, lparam.lparam()); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.open = false; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeProcessKey( |
| virtual_key, lparam.GetScanCode(), lparam.IsKeyDownInImeProcessKey(), |
| keyboard_status, behavior, initial_state, context, |
| mock.mutable_client(), &keyboard, &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_FALSE(result.should_be_eaten); |
| EXPECT_FALSE(result.should_be_sent_to_server); |
| EXPECT_FALSE(next_state.open); |
| EXPECT_FALSE(mock.start_server_called()); |
| EXPECT_FALSE(keyboard.kana_locked()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, CustomActivationKeyTest) { |
| // We might want to allow users to use their preferred key combinations |
| // to open/close IME. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| // Use Ctrl+J to turn on IME. |
| behavior.direct_mode_keys = GetDirectModeKeysCtrlJToEnableIME(); |
| |
| Context context; |
| |
| // Ctrl+J |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('J'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('J', kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = false; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('j', actual_input.key().key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_FALSE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| // A user can assign CTRL+\ to enable IME. See b/3033135 for details. |
| TEST_F(KeyEventHandlerTest, Issue3033135_VK_OEM_102) { |
| // We might want to allow users to use their preferred key combinations |
| // to open/close IME. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDirectModeKeysCtrlBackslashToEnableIME(); |
| |
| Context context; |
| |
| // Ctrl+\ (VK_OEM_102; Backslash in 106/109 Japanese Keyboard) |
| { |
| const VirtualKey virtual_key = |
| VirtualKey::FromVirtualKey(VK_OEM_102); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_OEM_102, kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = false; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('\\', actual_input.key().key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_FALSE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| // A user can assign CTRL+\ to enable IME. See b/3033135 for details. |
| TEST_F(KeyEventHandlerTest, Issue3033135_VK_OEM_5) { |
| // We might want to allow users to use their preferred key combinations |
| // to open/close IME. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDirectModeKeysCtrlBackslashToEnableIME(); |
| |
| Context context; |
| |
| // Ctrl+\ (VK_OEM_5; Yen in 106/109 Japanese Keyboard) |
| { |
| const VirtualKey virtual_key = |
| VirtualKey::FromVirtualKey(VK_OEM_5); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_OEM_5, kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = false; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('\\', actual_input.key().key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_FALSE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, HandleCtrlH) { |
| // When a user presses an alphabet key and a control key, keyboard-layout |
| // drivers produce a control code (0x01,...,0x20), to which the session |
| // server assigns its own code. To avoid conflicts between a control code |
| // and one internally-used by the session server, we should decompose a |
| // control code into a tuple of an ASCII alphabet and a modifier key. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Ctrl+H should be sent to the server as 'h' + |KeyEvent::CTRL|. |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('H'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('H', kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('h', actual_input.key().key_code()); // must be non-capitalized. |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, HandleCtrlShiftH) { |
| // This is an exception of a key handling rule of the Windows client where |
| // VK_SHIFT and VK_CONTROL are pressed. The Windows client expects the server |
| // never eats a key when Control and Shift is pressed except that the key is |
| // VK_A, ..., or, VK_Z, or other special keys defined in Mozc protocol such as |
| // backspace or space. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Ctrl+Shift+H should be sent to the server as |
| // 'h' + |KeyEvent::CTRL| + |KeyEvent::Shift|. |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('H'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('H', kPressed); |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('h', actual_input.key().key_code()); // must be non-capitalized. |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(2, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_EQ(commands::KeyEvent::SHIFT, actual_input.key().modifier_keys(1)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, HandleCapsH) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // [CapsLock] h should be sent to the server as 'H' + |KeyEvent::Caps|. |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('H'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('H', kPressed); |
| keyboard_status.SetState(VK_CAPITAL, kToggled); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('H', actual_input.key().key_code()); // must be capitalized. |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CAPS, actual_input.key().modifier_keys(0)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, HandleCapsShiftH) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // [CapsLock] Shift+H should be sent to the server as |
| // 'h' + |KeyEvent::Caps|. |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('H'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('H', kPressed); |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState(VK_CAPITAL, kToggled); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('h', actual_input.key().key_code()); // must be non-capitalized. |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CAPS, actual_input.key().modifier_keys(0)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, HandleCapsCtrlH) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // [CapsLock] Ctrl+H should be sent to the server as |
| // 'H' + |KeyEvent::CTRL| + |KeyEvent::Caps|. |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('H'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('H', kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| keyboard_status.SetState(VK_CAPITAL, kToggled); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('H', actual_input.key().key_code()); // must be capitalized. |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(2, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_EQ(commands::KeyEvent::CAPS, actual_input.key().modifier_keys(1)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, HandleCapsShiftCtrlH) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // [CapsLock] Ctrl+Shift+H should be sent to the server as |
| // 'h' + |KeyEvent::CTRL| + |KeyEvent::Shift| + |KeyEvent::Caps|. |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('H'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('H', kPressed); |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| keyboard_status.SetState(VK_CAPITAL, kToggled); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('h', actual_input.key().key_code()); // must be non-capitalized. |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(3, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_EQ(commands::KeyEvent::SHIFT, actual_input.key().modifier_keys(1)); |
| EXPECT_EQ(commands::KeyEvent::CAPS, actual_input.key().modifier_keys(2)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, HandleCtrlHat) { |
| // When a user presses some keys with control key, keyboard-layout |
| // drivers may not produce any character but the server expects a key event. |
| // For example, suppose that the Mozc keybindings includes Ctrl+^. |
| // On 106/109 Japanese keyboard, you can actually use this key combination |
| // as VK_OEM_7 + VK_CONTROL. On 101/104 English keyboard, however, |
| // should we interpret VK_6 + VK_SHIFT + VK_CONTROL as Ctrl+^ ? |
| // As a temporal solution to be consistent with the GUI tool, the Windows |
| // client expects the server never eats a key when Control and Shift is |
| // pressed except that the key is VK_A, ..., or, VK_Z, or other special keys |
| // defined in Mozc protocol such as backspace or space. |
| // TODO(komatsu): Clarify the expected algorithm for the client. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Ctrl+^ should be sent to the server as '^' + |KeyEvent::CTRL|. |
| { |
| // '^' on 106/109 Japanese keyboard. |
| const VirtualKey virtual_key = |
| VirtualKey::FromVirtualKey(VK_OEM_7); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_OEM_7, kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('^', actual_input.key().key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, HandleCtrlShift7) { |
| // As commented in KeyEventHandlerTest::HandleCtrlHat, the Windows |
| // client expects the server never eats a key when Control and Shift is |
| // pressed except that the key is VK_A, ..., or, VK_Z, or other special keys |
| // defined in Mozc protocol such as backspace or space, which means that |
| // VK_7 + VK_SHIFT + VK_CONTROL on 106/109 Japanese keyboard will not be |
| // sent to the server as Ctrl+'\'' nor Ctrl+Shift+'7' even though |
| // Ctrl+'\'' is available on 101/104 English keyboard. |
| // TODO(komatsu): Clarify the expected algorithm for the client. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(false); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // VK_7 + VK_SHIFT + VK_CONTROL must not be sent to the server as |
| // '\'' + |KeyEvent::CTRL| nor '7' + |KeyEvent::CTRL| + |KeyEvent::SHIFT|. |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('7'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('7', kPressed); |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_FALSE(result.should_be_eaten); |
| EXPECT_FALSE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_FALSE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, HandleCtrlShiftSpace) { |
| // This is an exception of a key handling rule of the Windows client where |
| // VK_SHIFT and VK_CONTROL are pressed. The Windows client expects the |
| // server may eat a special key when Control and Shift is pressed. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // VK_SPACE + VK_SHIFT + VK_CONTROL must be sent to the server as |
| // |KeyEvent::SPACE| + |KeyEvent::CTRL| + |KeyEvent::SHIFT| |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_SPACE); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_SPACE, kPressed); |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_FALSE(actual_input.key().has_key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(2, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_EQ(commands::KeyEvent::SHIFT, actual_input.key().modifier_keys(1)); |
| EXPECT_TRUE(actual_input.key().has_special_key()); |
| EXPECT_EQ(commands::KeyEvent::SPACE, actual_input.key().special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, HandleCtrlShiftBackspace) { |
| // This is an exception of a key handling rule of the Windows client where |
| // VK_SHIFT and VK_CONTROL are pressed. The Windows client expects the |
| // server may eat a special key when Control and Shift is pressed. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // VK_BACK + VK_SHIFT + VK_CONTROL must be sent to the server as |
| // |KeyEvent::BACKSPACE| + |KeyEvent::CTRL| + |KeyEvent::SHIFT| |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_BACK); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_BACK, kPressed); |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_FALSE(actual_input.key().has_key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(2, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_EQ(commands::KeyEvent::SHIFT, actual_input.key().modifier_keys(1)); |
| EXPECT_TRUE(actual_input.key().has_special_key()); |
| EXPECT_EQ(commands::KeyEvent::BACKSPACE, actual_input.key().special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, Issue2903247_KeyUpShouldNotBeEaten) { |
| // In general, key up event should not be eaten by the IME. |
| // See b/2903247 for details. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Release 'F6' |
| { |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_F6, kPressed); |
| |
| const VirtualKey last_keydown_virtual_key = |
| VirtualKey::FromVirtualKey(VK_F6); |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_F6); |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x40, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| true, // is_previous_state_down, |
| true)); // is_in_transition_state |
| EXPECT_EQ(0xc0400001, lparam.lparam()); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| initial_state.last_down_key = last_keydown_virtual_key; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeProcessKey( |
| virtual_key, lparam.GetScanCode(), lparam.IsKeyDownInImeProcessKey(), |
| keyboard_status, behavior, initial_state, context, |
| mock.mutable_client(), &keyboard, &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_FALSE(result.should_be_eaten); |
| EXPECT_FALSE(result.should_be_sent_to_server); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, ProtocolAnomaly_ModiferKeyMayBeSentOnKeyUp) { |
| // Currently, the Mozc server expects the client to send key-up events in |
| // some special cases. See comments in ImeCore::ImeProcessKey for details. |
| // Unfortunately, current implementation does not take some tricky key |
| // sequences such as b/2899541 into account. |
| // TODO(yukawa): Fix b/2899541 and add unit tests. |
| // TODO(yukawa): File this issue as a protocol bug so that we can improve |
| // the Mozc protocol later. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Press Shift |
| { |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_SHIFT); |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x2a, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| false, // is_previous_state_down, |
| false)); // is_in_transition_state |
| EXPECT_EQ(0x002a0001, lparam.lparam()); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeProcessKey( |
| virtual_key, lparam.GetScanCode(), lparam.IsKeyDownInImeProcessKey(), |
| keyboard_status, behavior, initial_state, context, |
| mock.mutable_client(), &keyboard, &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_FALSE(result.should_be_eaten); |
| EXPECT_FALSE(result.should_be_sent_to_server); |
| EXPECT_FALSE(mock.start_server_called()); |
| } |
| |
| // Release Shift |
| { |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| |
| const VirtualKey previous_virtual_key = |
| VirtualKey::FromVirtualKey(VK_SHIFT); |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_SHIFT); |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x2a, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| false, // is_previous_state_down, |
| true)); // is_in_transition_state |
| EXPECT_EQ(0x802a0001, lparam.lparam()); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| initial_state.last_down_key = previous_virtual_key; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeProcessKey( |
| virtual_key, lparam.GetScanCode(), lparam.IsKeyDownInImeProcessKey(), |
| keyboard_status, behavior, initial_state, context, |
| mock.mutable_client(), &keyboard, &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| } |
| |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::TEST_SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| // Interestingly we have to set SHIFT modifier in spite of the Shift key |
| // has been just released. |
| EXPECT_EQ(commands::KeyEvent::SHIFT, actual_input.key().modifier_keys(0)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, |
| ProtocolAnomaly_ModifierShiftShouldBeRemovedForPrintableChar) { |
| // Currently, the Mozc server expects the client remove Shift modifier if |
| // the key generates any printable character. |
| // TODO(yukawa): File this issue as a protocol bug so that we can improve |
| // the Mozc protocol later. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Press 'Shift+A' |
| { |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState('A', kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('A'); |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x1e, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| false, // is_previous_state_down, |
| false)); // is_in_transition_state |
| EXPECT_EQ(0x1e0001, lparam.lparam()); |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeProcessKey( |
| virtual_key, lparam.GetScanCode(), lparam.IsKeyDownInImeProcessKey(), |
| keyboard_status, behavior, initial_state, context, |
| mock.mutable_client(), &keyboard, &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| } |
| |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::TEST_SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('A', actual_input.key().key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| // Interestingly, Mozc client is required not to set Shift here. |
| EXPECT_EQ(0, actual_input.key().modifier_keys_size()); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, |
| ProtocolAnomaly_ModifierKeysShouldBeRemovedAsForSomeSpecialKeys) { |
| // Currently, the Mozc server expects the client remove all modifiers as for |
| // some special keys such as VK_DBE_KATAKANA. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| mock_output.set_mode(commands::FULL_KATAKANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::FULL_KATAKANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::FULL_KATAKANA); |
| |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Press 'Shift+Katakana' |
| { |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState(VK_DBE_KATAKANA, kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_DBE_KATAKANA); |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x70, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| true, // is_previous_state_down, |
| false)); // is_in_transition_state |
| EXPECT_EQ(0x40700001, lparam.lparam()); |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeProcessKey( |
| virtual_key, lparam.GetScanCode(), lparam.IsKeyDownInImeProcessKey(), |
| keyboard_status, behavior, initial_state, context, |
| mock.mutable_client(), &keyboard, &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| // This is one of force activation keys. |
| EXPECT_TRUE(mock.start_server_called()); |
| |
| // Should be Full-Katakana |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN | |
| IME_CMODE_KATAKANA, next_state.logical_conversion_mode); |
| } |
| |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::TEST_SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_input_style()); |
| EXPECT_FALSE(actual_input.key().has_key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| // Interestingly, Mozc client is required not to set Shift here. |
| EXPECT_EQ(0, actual_input.key().modifier_keys_size()); |
| EXPECT_TRUE(actual_input.key().has_special_key()); |
| EXPECT_EQ(commands::KeyEvent::KATAKANA, actual_input.key().special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, |
| ProtocolAnomaly_KeyCodeIsFullWidthHiraganaWhenKanaLockIsEnabled) { |
| // Currently, the Mozc client is required to do extra work for Kana-Input. |
| // The client should set |key_code()| as if Kana-lock was disabled. |
| // TODO(yukawa): File this issue as a protocol bug so that we can improve |
| // the Mozc protocol later. |
| |
| const bool kKanaLocked = true; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Press 'A' with Kana-lock |
| { |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('A', kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('A'); |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x1e, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| false, // is_previous_state_down, |
| false)); // is_in_transition_state |
| EXPECT_EQ(0x1e0001, lparam.lparam()); |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeProcessKey( |
| virtual_key, lparam.GetScanCode(), lparam.IsKeyDownInImeProcessKey(), |
| keyboard_status, behavior, initial_state, context, |
| mock.mutable_client(), &keyboard, &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| } |
| |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::TEST_SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('a', actual_input.key().key_code()); |
| EXPECT_TRUE(actual_input.key().has_key_string()); |
| // "あ" |
| EXPECT_EQ("\xE3\x81\xA1", actual_input.key().key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(0, actual_input.key().modifier_keys_size()); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, |
| CheckKeyCodeWhenAlphabeticalKeyIsPressedWithCtrl) { |
| // When a user presses an alphabet key and a control key, keyboard-layout |
| // drivers produce a control code (0x01,...,0x20), to which the session |
| // server assigns its own code. To avoid conflicts between a control code |
| // and one internally-used by the session server, we should decompose a |
| // control code into a tuple of an ASCII alphabet and a modifier key. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Press 'Ctrl+A' |
| { |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| keyboard_status.SetState('A', kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('A'); |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x1e, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| false, // is_previous_state_down, |
| false)); // is_in_transition_state |
| EXPECT_EQ(0x1e0001, lparam.lparam()); |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeProcessKey( |
| virtual_key, lparam.GetScanCode(), lparam.IsKeyDownInImeProcessKey(), |
| keyboard_status, behavior, initial_state, context, |
| mock.mutable_client(), &keyboard, &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| } |
| |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::TEST_SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('a', actual_input.key().key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, |
| CheckKeyCodeWhenAlphabeticalKeyIsPressedWithCtrlInKanaMode) { |
| // When a user presses an alphabet key and a control key, keyboard-layout |
| // drivers produce a control code (0x01,...,0x20), to which the session |
| // server assigns its own code. This should not be passed to the server |
| // as a Kana-input character. See b/9684668. |
| |
| const bool kKanaLocked = true; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Press 'Ctrl+A' |
| { |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_CONTROL, kPressed); |
| keyboard_status.SetState('A', kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('A'); |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x1e, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| false, // is_previous_state_down, |
| false)); // is_in_transition_state |
| EXPECT_EQ(0x1e0001, lparam.lparam()); |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeProcessKey( |
| virtual_key, lparam.GetScanCode(), lparam.IsKeyDownInImeProcessKey(), |
| keyboard_status, behavior, initial_state, context, |
| mock.mutable_client(), &keyboard, &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| } |
| |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::TEST_SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('a', actual_input.key().key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::CTRL, actual_input.key().modifier_keys(0)); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| |
| TEST_F(KeyEventHandlerTest, |
| Issue2801503_ModeChangeWhenIMEIsGoingToBeTurnedOff) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| mock_output.set_mode(commands::DIRECT); |
| mock_output.mutable_status()->set_activated(false); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| |
| Context context; |
| |
| // Press 'Hankaku/Zenkaku' to close IME. |
| { |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_DBE_DBCSCHAR, kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_DBE_DBCSCHAR); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| |
| InputState initial_state; |
| // Assume that the temporal half-alphanumeric is on-going. |
| initial_state.logical_conversion_mode = IME_CMODE_ALPHANUMERIC; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| // IME will be turned off. |
| EXPECT_FALSE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| // Next conversion status is determined by mock_output.status() instead of |
| // mock_output.mode(), which is unfortunately |commands::DIRECT| in this |
| // case. (This was the main reason why http://b/2801503 happened) |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, Issue3029665_KanaLocked_WO) { |
| const bool kKanaLocked = true; |
| |
| Output mock_output; |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| EXPECT_TRUE(keyboard.kana_locked()); |
| |
| InputState next_state; |
| Output output; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // "を" |
| { |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('0'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState('0', kPressed); |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, |
| &next_state, &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| } |
| |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_TRUE(actual_input.key().has_key_code()); |
| EXPECT_EQ('0', actual_input.key().key_code()); |
| EXPECT_TRUE(actual_input.key().has_key_string()); |
| // "を" |
| EXPECT_EQ("\343\202\222", actual_input.key().key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(0, actual_input.key().modifier_keys_size()); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, |
| Issue3109571_ShiftHenkanShouldBeValid) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| |
| Context context; |
| |
| // Press 'Shift + Henkan' |
| { |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState(VK_CONVERT, kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_CONVERT); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, |
| &next_state, &output); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::SHIFT, actual_input.key().modifier_keys(0)); |
| EXPECT_TRUE(actual_input.key().has_special_key()); |
| EXPECT_EQ(commands::KeyEvent::HENKAN, actual_input.key().special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, |
| Issue3109571_ShiftMuhenkanShouldBeValid) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| |
| Context context; |
| |
| // Press 'Shift + Muhenkan' |
| { |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState(VK_NONCONVERT, kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_NONCONVERT); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, |
| &next_state, &output); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(1, actual_input.key().modifier_keys_size()); |
| EXPECT_EQ(commands::KeyEvent::SHIFT, actual_input.key().modifier_keys(0)); |
| EXPECT_TRUE(actual_input.key().has_special_key()); |
| EXPECT_EQ(commands::KeyEvent::MUHENKAN, actual_input.key().special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, Issue7098463_HideSuggestWindow) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| mock_output.set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HIRAGANA); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| |
| Context context; |
| context.set_suppress_suggestion(true); |
| |
| // Press 'A' |
| { |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState('A', kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('A'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, |
| &next_state, &output); |
| } |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_context()); |
| EXPECT_TRUE(actual_input.context().suppress_suggestion()); |
| } |
| } |
| |
| TEST(SimpleImeKeyEventHandlerTest, ToggleInputStyleByRomanKey) { |
| const bool kKeyDown = true; |
| const bool kKeyUp = false; |
| |
| const VirtualKey key_VK_DBE_ROMAN = VirtualKey::FromVirtualKey(VK_DBE_ROMAN); |
| const VirtualKey key_VK_DBE_NOROMAN = |
| VirtualKey::FromVirtualKey(VK_DBE_NOROMAN); |
| |
| // If you hit Alt+Hiragana/Katakana when VK_DBE_ROMAN has been pressed, |
| // you will receive key events in the following order. |
| // VK_DBE_ROMAN Up |
| // VK_DBE_NOROMAN Down |
| // If you hit Alt+Hiragana/Katakana when VK_DBE_NOROMAN has been pressed, |
| // you will receive key events in the following order. |
| // VK_DBE_NOROMAN Up |
| // VK_DBE_ROMAN Down |
| |
| // Here, we make sure if a key down message flip the input style when the IME |
| // is turned on. |
| |
| // [Roman -> Kana] by VK_DBE_NOROMAN when IME is ON |
| { |
| InputState state; |
| state.open = true; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = false; |
| behavior.use_romaji_key_to_toggle_input_style = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyUp, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = false; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyDown, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| } |
| |
| // [Kana -> Roman] by VK_DBE_NOROMAN when IME is ON |
| { |
| InputState state; |
| state.open = true; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = true; |
| behavior.use_romaji_key_to_toggle_input_style = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyUp, state, |
| &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyDown, state, |
| &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| } |
| |
| // [Roman -> Kana] by VK_DBE_ROMAN when IME is ON |
| { |
| InputState state; |
| state.open = true; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = false; |
| behavior.use_romaji_key_to_toggle_input_style = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyUp, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = false; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyDown, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| } |
| |
| // [Kana -> Roman] by VK_DBE_ROMAN when IME is ON |
| { |
| InputState state; |
| state.open = true; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = true; |
| behavior.use_romaji_key_to_toggle_input_style = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyUp, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyDown, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| } |
| |
| // Here, we make sure if a key down message flip the input style when the IME |
| // is turned off. |
| |
| // [Roman -> Roman] by VK_DBE_NOROMAN when IME is off |
| { |
| InputState state; |
| state.open = false; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = false; |
| behavior.use_romaji_key_to_toggle_input_style = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyUp, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = false; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyDown, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| } |
| |
| // [Kana -> Kana] by VK_DBE_NOROMAN when IME is off |
| { |
| InputState state; |
| state.open = false; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = true; |
| behavior.use_romaji_key_to_toggle_input_style = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyUp, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyDown, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| } |
| |
| // [Roman -> Roman] by VK_DBE_ROMAN when IME is off |
| { |
| InputState state; |
| state.open = false; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = false; |
| behavior.use_romaji_key_to_toggle_input_style = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyUp, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = false; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyDown, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| } |
| |
| // [Kana -> Kana] by VK_DBE_ROMAN when IME is off |
| { |
| InputState state; |
| state.open = false; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = true; |
| behavior.use_romaji_key_to_toggle_input_style = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyUp, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyDown, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| } |
| |
| // [Roman -> Roman] by VK_DBE_NOROMAN when |
| // |behavior.use_romaji_key_to_toggle_input_style| is false |
| { |
| InputState state; |
| state.open = true; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = false; |
| behavior.use_romaji_key_to_toggle_input_style = false; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyUp, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = false; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyDown, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| } |
| |
| // [Kana -> Kana] by VK_DBE_NOROMAN when |
| // |behavior.use_romaji_key_to_toggle_input_style| is false |
| { |
| InputState state; |
| state.open = true; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = true; |
| behavior.use_romaji_key_to_toggle_input_style = false; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyUp, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyDown, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| } |
| |
| // [Roman -> Roman] by VK_DBE_ROMAN when |
| // |behavior.use_romaji_key_to_toggle_input_style| is false |
| { |
| InputState state; |
| state.open = true; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = false; |
| behavior.use_romaji_key_to_toggle_input_style = false; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyUp, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = false; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyDown, state, &behavior); |
| EXPECT_FALSE(behavior.prefer_kana_input); |
| } |
| |
| // [Kana -> Kana] by VK_DBE_ROMAN when |
| // |behavior.use_romaji_key_to_toggle_input_style| is false |
| { |
| InputState state; |
| state.open = true; |
| // conversion status will not be cared about. |
| state.logical_conversion_mode = 0; |
| |
| InputBehavior behavior; |
| |
| behavior.prefer_kana_input = true; |
| behavior.use_romaji_key_to_toggle_input_style = false; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_NOROMAN, kKeyUp, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| |
| behavior.prefer_kana_input = true; |
| TestableKeyEventHandler::UpdateBehaviorInImeProcessKey( |
| key_VK_DBE_ROMAN, kKeyDown, state, &behavior); |
| EXPECT_TRUE(behavior.prefer_kana_input); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, Issue3504241_VKPacketAsRawInput) { |
| // To fix b/3504241, VK_PACKET must be supported. |
| |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Release VK_PACKET ('あ') |
| { |
| KeyboardStatus keyboard_status; |
| |
| const wchar_t kHiraganaA = L'\u3042'; |
| const VirtualKey virtual_key = VirtualKey::FromCombinedVirtualKey( |
| (static_cast<DWORD>(kHiraganaA) << 16) | VK_PACKET); |
| const VirtualKey last_keydown_virtual_key = |
| VirtualKey::FromVirtualKey(VK_ESCAPE); |
| |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| // VK_PACKET will be handled by the server. |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| } |
| |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_key_code()); |
| EXPECT_TRUE(actual_input.key().has_key_string()); |
| // "あ" |
| EXPECT_EQ("\343\201\202", actual_input.key().key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(0, actual_input.key().modifier_keys_size()); |
| EXPECT_FALSE(actual_input.key().has_special_key()); |
| } |
| } |
| |
| TEST_F(KeyEventHandlerTest, CapsLock) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Press VK_CAPITAL |
| { |
| KeyboardStatus keyboard_status; |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_CAPITAL); |
| |
| const BYTE scan_code = 0; // will be ignored in this test; |
| const bool is_key_down = true; |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| // VK_PACKET will be handled by the server. |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| } |
| |
| { |
| commands::Input actual_input; |
| EXPECT_TRUE(mock.GetGeneratedRequest(&actual_input)); |
| EXPECT_EQ(commands::Input::SEND_KEY, actual_input.type()); |
| EXPECT_TRUE(actual_input.has_key()); |
| EXPECT_FALSE(actual_input.key().has_key_code()); |
| EXPECT_FALSE(actual_input.key().has_key_string()); |
| EXPECT_TRUE(actual_input.key().has_activated()); |
| EXPECT_TRUE(actual_input.key().activated()); |
| EXPECT_TRUE(actual_input.key().has_mode()); |
| EXPECT_EQ(commands::HIRAGANA, actual_input.key().mode()); |
| EXPECT_FALSE(actual_input.key().has_modifiers()); |
| EXPECT_EQ(0, actual_input.key().modifier_keys_size()); |
| EXPECT_TRUE(actual_input.key().has_special_key()); |
| EXPECT_EQ(commands::KeyEvent::CAPS_LOCK, actual_input.key().special_key()); |
| } |
| } |
| |
| // In IMM32 mode, the OS handles VK_KANJI to activate IME. So we must not send |
| // it to the server. Otherwise, IME On/Off flipping happens twice and a user |
| // cannot activate IME by VK_KANJI. |
| TEST_F(KeyEventHandlerTest, KanjiKey_Issue7970379) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| behavior.direct_mode_keys = GetDefaultDirectModeKeys(); |
| |
| Context context; |
| |
| // Press VK_KANJI |
| { |
| KeyboardStatus keyboard_status; |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey(VK_KANJI); |
| |
| const BYTE scan_code = 0; // will be ignored in this test; |
| const bool is_key_down = true; |
| |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| // VK_KANJI must not be handled by the server. |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_FALSE(result.should_be_eaten); |
| EXPECT_FALSE(result.should_be_sent_to_server); |
| } |
| } |
| |
| // Temporal alphanumeric mode will be stored into |visible_conversion_mode|. |
| TEST_F(KeyEventHandlerTest, Issue8524269_ComebackMode) { |
| const bool kKanaLocked = false; |
| |
| Output mock_output; |
| mock_output.set_consumed(true); |
| mock_output.set_mode(commands::HALF_ASCII); |
| mock_output.mutable_status()->set_activated(true); |
| mock_output.mutable_status()->set_mode(commands::HALF_ASCII); |
| mock_output.mutable_status()->set_comeback_mode(commands::HIRAGANA); |
| |
| MockState mock(mock_output); |
| KeyboardMock keyboard(kKanaLocked); |
| |
| InputState next_state; |
| KeyEventHandlerResult result; |
| |
| InputBehavior behavior; |
| behavior.prefer_kana_input = kKanaLocked; |
| behavior.disabled = false; |
| |
| Context context; |
| |
| // Press 'Shift+A' |
| { |
| InputState initial_state; |
| initial_state.logical_conversion_mode = |
| IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN; |
| initial_state.visible_conversion_mode = |
| initial_state.logical_conversion_mode; |
| initial_state.open = true; |
| |
| KeyboardStatus keyboard_status; |
| keyboard_status.SetState(VK_SHIFT, kPressed); |
| keyboard_status.SetState('A', kPressed); |
| |
| const VirtualKey virtual_key = VirtualKey::FromVirtualKey('A'); |
| const BYTE scan_code = 0; // will be ignored in this test |
| const bool is_key_down = true; |
| const LParamKeyInfo lparam(CreateLParam( |
| 0x0001, // repeat_count |
| 0x1e, // scan_code |
| false, // is_extended_key, |
| false, // has_context_code, |
| false, // is_previous_state_down, |
| false)); // is_in_transition_state |
| EXPECT_EQ(0x1e0001, lparam.lparam()); |
| |
| Output output; |
| result = TestableKeyEventHandler::ImeToAsciiEx( |
| virtual_key, scan_code, is_key_down, keyboard_status, behavior, |
| initial_state, context, mock.mutable_client(), &keyboard, &next_state, |
| &output); |
| |
| EXPECT_TRUE(result.succeeded); |
| EXPECT_TRUE(result.should_be_eaten); |
| EXPECT_TRUE(result.should_be_sent_to_server); |
| |
| EXPECT_TRUE(next_state.open); |
| EXPECT_TRUE(mock.start_server_called()); |
| EXPECT_EQ(IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_ROMAN, |
| next_state.logical_conversion_mode); |
| // Visible mode should be half alphanumeric. |
| EXPECT_EQ(IME_CMODE_ALPHANUMERIC | IME_CMODE_ROMAN, |
| next_state.visible_conversion_mode); |
| } |
| } |
| |
| } // namespace win32 |
| } // namespace mozc |