├── .gitignore ├── xmake.lua ├── CMakeLists.txt ├── check_launch.js ├── LICENSE ├── .github └── workflows │ └── build.yml ├── README.md ├── README.en.md └── main.cc /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | .xmake -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | 2 | set_languages("cxx17") 3 | 4 | target("im-select-mspy") 5 | set_kind("binary") 6 | add_files("main.cc") 7 | add_cxxflags("/utf-8") 8 | add_links("comsuppw.lib") 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(im-select-mspy) 4 | 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | 8 | add_executable(im-select-mspy main.cc) 9 | target_compile_options(im-select-mspy PRIVATE "/utf-8") 10 | target_link_libraries(im-select-mspy comsuppw.lib) 11 | 12 | -------------------------------------------------------------------------------- /check_launch.js: -------------------------------------------------------------------------------- 1 | 2 | // This script is used to check if the executable is working properly. 3 | // because the vscode extension need utf-8 encoding, but some windows console only support cp932 encoding, app output may be no readable characters in the console. 4 | 5 | 6 | function main() { 7 | 8 | const cmd = "build\\Release\\im-select-mspy.exe"; 9 | const exec = require('child_process').exec; 10 | exec(cmd, (err, stdout) => { 11 | if (err) { 12 | console.error(err); 13 | return; 14 | } 15 | console.log(stdout); 16 | }); 17 | } 18 | 19 | main(); 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 elsejj 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. 2 | # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml 3 | name: CMake on multiple platforms 4 | 5 | on: 6 | push: 7 | tags: 8 | - "v*.*.*" 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{ matrix.os }} 13 | 14 | strategy: 15 | # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. 16 | fail-fast: false 17 | 18 | # Set up a matrix to run the following 3 configurations: 19 | # 1. 20 | # 2. 21 | # 3. 22 | # 23 | # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. 24 | matrix: 25 | os: [windows-latest] 26 | build_type: [Release] 27 | targetplatform: [x64, x86] 28 | 29 | steps: 30 | - uses: actions/checkout@v4 31 | 32 | - name: Configure CMake 33 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 34 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 35 | run: > 36 | cmake -B build/${{ matrix.targetplatform }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} 37 | 38 | - name: Build 39 | # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). 40 | run: | 41 | cmake --build build/${{ matrix.targetplatform }} --config ${{ matrix.build_type }} 42 | copy build/${{ matrix.targetplatform }}/${{ matrix.build_type }}/im-select-mspy.exe im-select-mspy.${{matrix.targetplatform}}.exe 43 | 44 | - name: Release 45 | uses: softprops/action-gh-release@v2 46 | with: 47 | files : im-select-mspy.${{matrix.targetplatform}}.exe 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | [VsCodeVim](https://github.com/VSCodeVim) 支持输入法的自动切换, 它通过一个外部的[输入法切换工具来实现](https://github.com/VSCodeVim/Vim/blob/f3f9850739e93fe5cc95827a64180fbf67fd377d/README.md#input-method), 目前这个工具推荐的是 [im-select](https://github.com/daipeihust/im-select). 4 | 5 | 由于从 Windows 10 开始, 系统默认不再安装英文键盘, 而是由输入法来提供英文输入, 而 im-select windows 版需要英文键盘, 所以在 Windows 10 上使用 VsCodeVim 时, 无法通过 im-select 来切换输入法, 也就无法通过 VsCodeVim 来切换输入法. 6 | 7 | 对于这个问题, 如果继续使用 im-select, 那么就必须安装英文键盘, 但英文键盘安装后, 切换逻辑会变得复杂. 8 | 9 | im-select-mspy 是一个针对微软拼音输入法的输入法切换工具, 它可以在不安装英文键盘的情况下, 完成 VsCodeVim 所需要的输入法信息获取和切换. 10 | 11 | 它的原理是通过 [UIAutomation](https://learn.microsoft.com/en-us/windows/win32/winauto/entry-uiauto-win32) 来获取当前输入法的信息, 然后通过 [SendInput](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput) 来模拟按键来切换输入法. 12 | 13 | 尽管本项目是主要为微软拼音输入法设计的, 但是理论上也可以用于其他输入法, 可以尝试通过如下的配置来进行适配. 14 | 15 | 目前的配置如下 16 | 17 | - `-t=任务栏` 程序通过该参数来寻找任务栏, 非简体中文系统可以设置为相应语言的名称 18 | - `-i=托盘输入指示器\s+(\w+)` 通过该正则表达式来获取当前输入法的状态, 其他输入法可以尝试修改该正则表达式来适配 19 | - `-k=shift` 通过该参数来设置切换输入法的快捷键, 其他输入法或使用其他快捷键可以尝试修改该参数来适配,如 `-k=ctrl+space` \ 20 | 如果切换输入法需要用到难以输入的特殊按键,可以填写 [Virtual Keys 键代码] 的16进制表示, 如 `-k=ctrl+0x7C` 代表 `ctrl+F13`. 21 | 22 | 但如果开启了任务栏自动隐藏功能,程序则可能无法获取到托盘输入指示器的按钮。 23 | 此时一种解决方式是开启微软拼音的 *输入法工具栏* (注意不是Windows 系统的 *桌面语言栏*),可以参考 [Microsoft简体中文 IME] 学习如何开启。 24 | 25 | 在无法从任务栏获取到按钮的情况下,程序会尝试从输入法工具栏获取到输入法状态,有如下的额外配置 26 | 27 | - `--toolbar="Windows 输入体验"` 通过该参数来寻找输入法工具栏, 非简体中文系统可以设置为相应语言的名称 28 | - `--toolbar-i="中/英文, (\\w+)"` 通过该正则表达式在工具栏中获取当前输入法的状态, 其他输入法可以尝试修改该正则表达式来适配 29 | 30 | 上述 `-t`, `-i`, `--toolbar`, `--toolbar-i` 可以通过 [Accessibility Insights](https://accessibilityinsights.io/docs/windows/overview/) 工具来获取. 31 | 32 | [Virtual Keys 键代码]: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes 33 | [Microsoft简体中文 IME]: https://support.microsoft.com/zh-cn/windows/microsoft%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87-ime-9b962a3b-2fa4-4f37-811c-b1886320dd72#id0ebh=microsoft_pinyin 34 | 35 | # 使用 36 | 37 | 使用方法参考 [VsCodeVim](https://github.com/VSCodeVim/Vim/blob/f3f9850739e93fe5cc95827a64180fbf67fd377d/README.md#input-method) 的说明. 38 | 39 | 以下是我的配置 40 | 41 | ```json 42 | { 43 | "vim.autoSwitchInputMethod.enable": true, 44 | "vim.autoSwitchInputMethod.defaultIM": "英语模式", 45 | "vim.autoSwitchInputMethod.obtainIMCmd": "D:\\workspace\\im-select-mspy\\build\\Release\\im-select-mspy.exe", 46 | "vim.autoSwitchInputMethod.switchIMCmd": "D:\\workspace\\im-select-mspy\\build\\Release\\im-select-mspy.exe {im}", 47 | } 48 | ``` 49 | 50 | 在命令行中的使用示例 51 | 52 | ```shell 53 | # 查看当前状态 54 | $ im-select-mspy.exe 55 | # 切换为中文模式 56 | $ im-select-mspy.exe -k="ctrl+shift+space" "中文模式" 57 | # 英文操作系统 58 | $ im-select-mspy.exe -t="Taskbar" -i="Tray Input Indicator (.+)" --toolbar="Windows Input Experience" --toolbar-i="Chinese/English \(Shift\), (.+)" "Chinese mode" 59 | ``` 60 | 61 | # 编译 62 | 63 | ``` 64 | md build 65 | cd build 66 | cmake .. 67 | cmake --build . --config Release 68 | ``` 69 | 70 | 71 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | [VsCodeVim](https://github.com/VSCodeVim) supports automatic input method switching, which is implemented through an external input method switching tool. Currently, the recommended tool is[im-select](https://github.com/daipeihust/im-select). 3 | 4 | However, starting from Windows 10, the system no longer installs an English keyboard by default and instead relies on the input method to provide English input. but im-select for Windows requires an English keyboard, making it impossible to switch input methods using im-select when using VsCodeVim on Windows 10 or later. 5 | 6 | To address this issue, if one continues to use im-select, an English keyboard must be installed, which can complicate the switching logic. im-select-mspy is an input method switching tool specifically designed for Microsoft Pinyin input method. It can obtain and switch input method information needed by VsCodeVim without the need to install an English keyboard. 7 | 8 | The tool works by using [UIAutomation](https://learn.microsoft.com/en-us/windows/win32/winauto/entry-uiauto-win32) to obtain information about the current input method and then using [SendInput](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput) to simulate keystrokes to switch input methods. 9 | 10 | Although this project is mainly designed for Microsoft Pinyin input method, it can theoretically be used for other input methods by modifying the configuration as follows. 11 | 12 | The current configuration is as follows: 13 | 14 | - `-t=任务栏`: the tool uses this parameter to find the taskbar. For non-Simplified Chinese systems, it can be set to the name of the corresponding language. 15 | - `-i=托盘输入指示器\s+(\w+):` this regular expression is used to obtain the current input method status. Other input methods can try to modify this regular expression to adapt. 16 | - `-k=shift`: this parameter sets the shortcut key for switching input methods. Other input methods or using other shortcut keys can try to modify this parameter to adapt, such as -k=ctrl+space. \ 17 | If special keys that are difficult to input are required for switching input methods, you can specify the hexadecimal representation of [Virtual Keys codes], such as -k=ctrl+0x7C which represents ctrl+F13. 18 | 19 | However, if the taskbar auto-hide function is enabled, the program may fail to obtain the tray input indicator button. 20 | In this case, one solution is to enable the Microsoft Pinyin *Input Method Toolbar* (note that this is not the Windows system's *Desktop Language Bar*). 21 | You can refer to [Microsoft Simplified Chinese IME] to learn how to enable it. 22 | 23 | When the button cannot be obtained from the taskbar, the program will try to obtain the input method status from the input method toolbar, with the following additional configurations: 24 | 25 | - `--toolbar="Windows 输入体验"`: the tool uses this parameter to find the input method toolbar. For non-Simplified Chinese systems, it can be set to the name of the corresponding language. 26 | - `--toolbar-i="中/英文, (\\w+)"`: this regular expression is used to obtain the current input method status from the toolbar. Other input methods can try to modify this regular expression to adapt. 27 | 28 | The above `-t`, `-i`, `--toolbar`, and `--toolbar-i` parameters can be obtained using the [Accessibility Insights](https://accessibilityinsights.io/docs/windows/overview/) . 29 | 30 | [Virtual Keys codes]: https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes 31 | [Microsoft Simplified Chinese IME]: https://support.microsoft.com/en-us/windows/microsoft-simplified-chinese-ime-9b962a3b-2fa4-4f37-811c-b1886320dd72 32 | 33 | # Usage 34 | Refer to the instructions in [VsCodeVim](https://github.com/VSCodeVim/Vim/blob/f3f9850739e93fe5cc95827a64180fbf67fd377d/README.md#input-method). 35 | 36 | Here is my configuration: 37 | 38 | ```json 39 | { 40 | "vim.autoSwitchInputMethod.enable": true, 41 | "vim.autoSwitchInputMethod.defaultIM": "英语模式", 42 | "vim.autoSwitchInputMethod.obtainIMCmd": "D:\\workspace\\im-select-mspy\\build\\Release\\im-select-mspy.exe", 43 | "vim.autoSwitchInputMethod.switchIMCmd": "D:\\workspace\\im-select-mspy\\build\\Release\\im-select-mspy.exe {im}", 44 | } 45 | ``` 46 | 47 | Example of usage in the command line: 48 | 49 | ```shell 50 | # Check current status 51 | $ im-select-mspy.exe 52 | # Switch to Chinese mode 53 | $ im-select-mspy.exe -k="ctrl+shift+space" "中文模式" 54 | # English UI 55 | $ im-select-mspy.exe -t="Taskbar" -i="Tray Input Indicator (.+)" --toolbar="Windows Input Experience" --toolbar-i="Chinese/English \(Shift\), (.+)" "Chinese mode" 56 | ``` 57 | 58 | # Compilation 59 | 60 | ``` 61 | md build 62 | cd build 63 | cmake .. 64 | cmake --build . --config Release 65 | ``` 66 | 67 | 68 | -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | 2 | #define UNICODE 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace std; 18 | 19 | 20 | typedef _com_ptr_t<_com_IIID> IUIAutomationPtr; 21 | typedef _com_ptr_t<_com_IIID> IUIAutomationElementPtr; 22 | typedef _com_ptr_t<_com_IIID> IUIAutomationConditionPtr; 23 | typedef _com_ptr_t<_com_IIID> IUIAutomationElementArrayPtr; 24 | typedef _com_ptr_t<_com_IIID> IUIAutomationInvokePatternPtr; 25 | 26 | 27 | // command line options 28 | struct CliOptions 29 | { 30 | // no prefix 31 | wstring mode; 32 | // -k= 33 | wstring switch_keys; 34 | // -t= 35 | wstring taskbar_name; 36 | // -i= 37 | wstring ime_capture_re; 38 | // --toolbar= 39 | wstring toolbar_name; 40 | // --toolbar_i= 41 | wstring toolbar_ime_capture_re; 42 | // -v 43 | bool verbose; 44 | 45 | wregex ime_capture; 46 | wregex toolbar_ime_capture; 47 | 48 | }; 49 | 50 | // ime button in taskbar 51 | struct ImeButton 52 | { 53 | wstring current_mode; 54 | IUIAutomationElementPtr pElement; 55 | }; 56 | 57 | wstring get_element_name(IUIAutomationElementPtr pElement) 58 | { 59 | _bstr_t name; 60 | pElement->get_CurrentName(name.GetAddress()); 61 | if (name.length() > 0) 62 | { 63 | return wstring((const wchar_t*)name); 64 | } 65 | else 66 | { 67 | return L""; 68 | } 69 | } 70 | 71 | vector split_string(const wstring & str, const wstring & delim) 72 | { 73 | vector result; 74 | wregex re(delim); 75 | wsregex_token_iterator first{ str.begin(), str.end(), re, -1 }, last; 76 | return { first, last }; 77 | } 78 | 79 | SHORT vk_from_text(const wstring & text) { 80 | if (text == L"shift") { 81 | return VK_SHIFT; 82 | } 83 | if (text == L"ctrl") { 84 | return VK_CONTROL; 85 | } 86 | if (text == L"alt") { 87 | return VK_MENU; 88 | } 89 | if (text == L"win") { 90 | return VK_LWIN; 91 | } 92 | if (text == L"space") { 93 | return VK_SPACE; 94 | } 95 | // Check for hexadecimal format (0xhh) 96 | // such that 0x1F means VK_MODECHANGE 97 | if (text.length() == 4 && text.substr(0, 2) == L"0x") { 98 | try { 99 | return static_cast(std::stoi(text.substr(2), nullptr, 16)); 100 | } 101 | catch (...) { 102 | } 103 | } 104 | return 0; 105 | } 106 | 107 | vector get_input_from_string(wstring str) 108 | { 109 | vector result; 110 | transform(str.begin(), str.end(), str.begin(), ::tolower); 111 | auto keys = split_string(str, L"\\+"); 112 | transform(keys.begin(), keys.end(), back_insert_iterator(result), [](const wstring & key) { 113 | INPUT input = { 0 }; 114 | input.type = INPUT_KEYBOARD; 115 | input.ki.wVk = vk_from_text(key); 116 | return input; 117 | }); 118 | transform(keys.rbegin(), keys.rend(), back_insert_iterator(result), [](const wstring & key) { 119 | INPUT input = { 0 }; 120 | input.type = INPUT_KEYBOARD; 121 | input.ki.wVk = vk_from_text(key); 122 | input.ki.dwFlags = KEYEVENTF_KEYUP; 123 | return input; 124 | }); 125 | return result; 126 | } 127 | 128 | 129 | 130 | 131 | ImeButton get_ime_button(const CliOptions & options) { 132 | IUIAutomationPtr pAutomation; 133 | IUIAutomationElementPtr pDesktop; 134 | IUIAutomationElementPtr pTaskBar; 135 | IUIAutomationConditionPtr pCondition; 136 | 137 | auto hr = pAutomation.CreateInstance(CLSID_CUIAutomation); 138 | 139 | pAutomation->GetRootElement(&pDesktop); 140 | 141 | pAutomation->CreatePropertyCondition(UIA_NamePropertyId, _variant_t(options.taskbar_name.c_str()), &pCondition); 142 | 143 | hr = pDesktop->FindFirst(TreeScope_Children, pCondition, &pTaskBar); 144 | 145 | pAutomation->CreatePropertyCondition(UIA_ControlTypePropertyId, _variant_t(UIA_ButtonControlTypeId), &pCondition); 146 | 147 | if (options.verbose) wcout << L"found taskbar: " << options.taskbar_name << endl; 148 | IUIAutomationElementArrayPtr arrButtons; 149 | pTaskBar->FindAll(TreeScope_Descendants, pCondition, &arrButtons); 150 | 151 | int length = 0; 152 | arrButtons->get_Length(&length); 153 | for (int i = 0; i < length; i++) 154 | { 155 | IUIAutomationElementPtr pButton; 156 | arrButtons->GetElement(i, &pButton); 157 | auto name = get_element_name(pButton); 158 | 159 | if (options.verbose) wcout << L"Is '" << name << L"' ime button? "; 160 | wsmatch match; 161 | if (regex_search(name, match, options.ime_capture)) { 162 | if (options.verbose) wcout << L"YES" << endl; 163 | return { match[1], pButton }; 164 | } 165 | if (options.verbose) wcout << L"NO" << endl; 166 | } 167 | return { L"", nullptr }; 168 | } 169 | 170 | /** 171 | * Look for the mode switch button in the IME toolbar 172 | * Suitable as a fallback solution when the tray input indicator is set to auto-hide along with the taskbar 173 | * Requires the IME toolbar to be enabled first, and ensure the "中/英文" button is displayed in the toolbar 174 | */ 175 | ImeButton get_ime_button_from_toolbar(const CliOptions &options) { 176 | IUIAutomationPtr pAutomation; 177 | IUIAutomationElementPtr pDesktop; 178 | IUIAutomationElementPtr pInputPanel; 179 | IUIAutomationConditionPtr pCondition; 180 | IUIAutomationElementArrayPtr arrButtons; 181 | 182 | // UI Layout Structure: 183 | // Element: Windows 输入体验 184 | // Element: 简体中文工具栏菜单列表 185 | // Element: 中/英文, 英语模式 186 | // Element: 设置 187 | 188 | pAutomation.CreateInstance(CLSID_CUIAutomation); 189 | pAutomation->GetRootElement(&pDesktop); 190 | pAutomation->CreatePropertyCondition(UIA_NamePropertyId, _variant_t(options.toolbar_name.c_str()), &pCondition); 191 | pDesktop->FindFirst(TreeScope_Descendants, pCondition, &pInputPanel); 192 | if (pInputPanel == nullptr) 193 | { 194 | return { L"", nullptr }; 195 | } 196 | if (options.verbose) wcout << L"found toolbar: " << options.toolbar_name << endl; 197 | 198 | pAutomation->CreatePropertyCondition(UIA_ControlTypePropertyId, _variant_t(UIA_ListItemControlTypeId), &pCondition); 199 | pInputPanel->FindAll(TreeScope_Descendants, pCondition, &arrButtons); 200 | int length = 0; 201 | arrButtons->get_Length(&length); 202 | for (int i = 0; i < length; i++) 203 | { 204 | IUIAutomationElementPtr pButton; 205 | arrButtons->GetElement(i, &pButton); 206 | auto name = get_element_name(pButton); 207 | if (options.verbose) wcout << L"Is '" << name << L"' ime button?"; 208 | if (wsmatch match; regex_search(name, match, options.toolbar_ime_capture)) 209 | { 210 | if (options.verbose) wcout << L"YES" << endl; 211 | return {match[1], pButton}; 212 | } 213 | if (options.verbose) wcout << L"NO" << endl; 214 | } 215 | return {L"", nullptr}; 216 | } 217 | 218 | // default chinese options 219 | CliOptions chinese_options() 220 | { 221 | CliOptions options; 222 | options.taskbar_name = L"任务栏"; 223 | options.ime_capture_re = L"托盘输入指示器\\s+(\\w+)"; //\\s+(\\S+)\\s*.+"; 224 | options.switch_keys = L"shift"; 225 | options.toolbar_name = L"Windows 输入体验"; 226 | options.toolbar_ime_capture_re = L"中/英文, (\\w+)"; 227 | options.verbose = false; 228 | return options; 229 | } 230 | 231 | // parse command line options 232 | CliOptions parse_options(int argc, wchar_t * argv[]) 233 | { 234 | CliOptions options = chinese_options(); 235 | for (int i = 1; i < argc; i++) 236 | { 237 | auto arg = argv[i]; 238 | if (arg[0] == L'-') 239 | { 240 | auto pos = wcschr(arg, L'='); 241 | if (pos) 242 | { 243 | auto key = wstring(arg + 1, pos); 244 | auto value = wstring(pos + 1); 245 | if (key == L"k") 246 | { 247 | options.switch_keys = value; 248 | } 249 | else if (key == L"t") 250 | { 251 | options.taskbar_name = value; 252 | } 253 | else if (key == L"i") 254 | { 255 | options.ime_capture_re = value; 256 | } 257 | else if (key == L"-toolbar") 258 | { 259 | options.toolbar_name = value; 260 | } 261 | else if (key == L"-toolbar-i") 262 | { 263 | options.toolbar_ime_capture_re = value; 264 | } 265 | } 266 | if (wcscmp(arg, L"-v") == 0) 267 | { 268 | options.verbose = true; 269 | } 270 | } 271 | else 272 | { 273 | options.mode = arg; 274 | } 275 | } 276 | if (options.ime_capture_re.length() > 0) 277 | { 278 | options.ime_capture = wregex(options.ime_capture_re); 279 | } 280 | if (options.toolbar_ime_capture_re.length() > 0) 281 | { 282 | options.toolbar_ime_capture = wregex(options.toolbar_ime_capture_re); 283 | } 284 | return options; 285 | } 286 | 287 | void print_options(const CliOptions & options) 288 | { 289 | wcout << L"taskbar name(-t): " << options.taskbar_name << endl; 290 | wcout << L"ime capture(-i): " << options.ime_capture_re << endl; 291 | wcout << L"switch keys(-k): " << options.switch_keys << endl; 292 | wcout << L"toolbar name(--toolbar): " << options.toolbar_name << endl; 293 | wcout << L"toolbar ime capture(--toolbar-i): " << options.toolbar_ime_capture_re << endl; 294 | wcout << L"mode: " << options.mode << endl; 295 | } 296 | 297 | string w2utf8(const wstring& str) 298 | { 299 | string buf; 300 | buf.resize(str.size() * 4); 301 | auto n = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), -1, buf.data(), (int)buf.length(), NULL, NULL); 302 | if (n == 0) { 303 | return ""; 304 | } 305 | buf.resize(n-1); 306 | return buf; 307 | } 308 | 309 | int wmain(int argc, wchar_t * argv[]) 310 | { 311 | std::ios::sync_with_stdio(false); 312 | std::locale::global( std::locale("") ); 313 | 314 | 315 | auto options = parse_options(argc, argv); 316 | 317 | //print_options(options); 318 | 319 | CoInitialize(NULL); 320 | 321 | ImeButton ime_button; 322 | 323 | try { 324 | ime_button = get_ime_button(options); 325 | } 326 | catch (_com_error& e) 327 | { 328 | wcout << L"get ime button failed: " << e.ErrorMessage() << endl; 329 | wcout << L"maybe the taskbar name (-t) is not correct" << endl; 330 | print_options(options); 331 | return 1; 332 | } 333 | 334 | if (!ime_button.pElement) { 335 | // Perhaps the taskbar is hidden. Try finding the toggle button in the toolbar. 336 | ime_button = get_ime_button_from_toolbar(options); 337 | } 338 | 339 | if (!ime_button.pElement) 340 | { 341 | wcout << L"ime button not found, maybe the ime_capture (-i) can't match the ime button name " << endl; 342 | print_options(options); 343 | return 1; 344 | } 345 | 346 | 347 | if (options.mode.empty()) 348 | { 349 | // get current mode 350 | cout << w2utf8(ime_button.current_mode) << endl; 351 | } 352 | else 353 | { 354 | // do switch 355 | if (options.mode != ime_button.current_mode) 356 | { 357 | auto input = get_input_from_string(options.switch_keys); 358 | SendInput((UINT)input.size(), input.data(), sizeof(input[0])); 359 | } 360 | } 361 | 362 | 363 | CoUninitialize(); 364 | return 0; 365 | } --------------------------------------------------------------------------------