├── inc ├── stdafx.h ├── targetver.h └── parg.h ├── src ├── stdafx.cpp ├── im-select-imm.cpp └── parg.c ├── out └── im-select-imm.exe ├── .gitignore ├── bucket └── im-select-imm.json ├── README_CN.md ├── README.md └── xmake.lua /inc/stdafx.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PEMessage/im-select-imm/HEAD/inc/stdafx.h -------------------------------------------------------------------------------- /src/stdafx.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PEMessage/im-select-imm/HEAD/src/stdafx.cpp -------------------------------------------------------------------------------- /inc/targetver.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PEMessage/im-select-imm/HEAD/inc/targetver.h -------------------------------------------------------------------------------- /out/im-select-imm.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PEMessage/im-select-imm/HEAD/out/im-select-imm.exe -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xmake cache 2 | .xmake/ 3 | build/ 4 | 5 | # MacOS Cache 6 | .DS_Store 7 | .vscode 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /bucket/im-select-imm.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.2", 3 | "description": "A improve of im-select", 4 | "homepage": "https://github.com/PEMessage/im-select-imm/", 5 | "url": "https://github.com/PEMessage/im-select-imm/releases/download/v1.0.2/im-select-imm_v1.0.2_x86.zip", 6 | "hash": "c3a4c6a01ccf9babc0846655eab06e66e4b805de2174eab94a5d185cef684cbd", 7 | "bin": "im-select-imm.exe", 8 | "license": "MIT", 9 | "checkver": "github", 10 | "autoupdate": { 11 | "url": "https://github.com/PEMessage/im-select-imm/releases/download/v$version/im-select-imm_v$version_x86.zip" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # Im-Select-Imm 2 | 3 | ## 介绍 4 | 5 | 原版im-select可以切换输入法, 但是不能切换中英文。 6 | 这个版本可以在第二个参数中切换中英文和全角半角。 7 | 二进制位于 `此目录/out/` 中 8 | 9 | ## 使用方式 10 | 11 | ### 下载 12 | 13 | ### 获取当前输入法的key 14 | 15 | ```shell 16 | /path/to/im-select-imm.exe 17 | Out: [当前输入法]-[输入法的当前模式] 18 | ``` 19 | 20 | ### 切换输入法 21 | 22 | ```shell 23 | /path/to/im-select.exe [目标输入法] 24 | Or 25 | /path/to/im-select.exe [目标输入法] [目标输入法的目标模式] 26 | Or 27 | /path/to/im-select.exe [目标输入法]-[目标输入法的目标模式] # 部分插件下只允许一个参数 28 | Or 29 | /path/to/im-select.exe -d 50 [目标输入法]-[目标输入法的目标模式] # 在切换输入法和模式之间插入延迟,默认30ms,可能可以改善一部分人无效的情况 30 | ``` 31 | ## 已测试输入法 32 | 33 | 1. 新旧微软拼音 34 | 2. 新微软日语输入法 35 | 36 | ### 对于微软输入法 37 | 38 | ``` 39 | For Microsoft Old Chinese IME(Win10 and Previous) : 40 | 0: English 41 | 1: Chinese 42 | For Microsoft New Chinese IME(Win11) : 43 | 0: English / Half Shape 44 | 1: Chinese / Half Shape 45 | 1024: English / Full Shape (虽然存在此模式,但实际仍然为半角) 46 | 1025: Chinese / Full Shape 47 | ``` 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Im-Select-Imm 2 | 3 | ## Introduce 4 | Orgin im-select could only change IME, 5 | This version allow you change IME Mode in second parameter, 6 | The pre-build exe is under the `thisdir/out/` 7 | [中文介绍](./README_CN.md) 8 | 9 | ## Usage 10 | 11 | ## Get Current IME key 12 | 13 | ```shell 14 | /path/to/im-select-imm.exe 15 | Out: [current IME]-[current Mode] 16 | ``` 17 | 18 | ## Switch IME 19 | 20 | ```shell 21 | /path/to/im-select-imm.exe [target IME] 22 | Or 23 | /path/to/im-select-imm.exe [target IME] [target IME Mode] 24 | Or 25 | /path/to/im-select-imm.exe [target IME]-[target IME Mode] 26 | Or 27 | /path/to/im-select-imm.exe -d 50 [target IME]-[target IME Mode] # add some delay, May fix some people don't work 28 | ``` 29 | ## Alredy Tested IME 30 | 31 | 1. Microsoft Chinese IME(New Windows11) 32 | 2. Microsoft Chinese IME(Old Windows10) 33 | 3. Microsoft Japenese IME(New Windows11) 34 | 35 | ## For Microsoft Chinese IME 36 | 37 | ``` 38 | For Microsoft Old Chinese IME(Win10 and Previous) : 39 | 0: English 40 | 1: Chinese 41 | For Microsoft New Chinese IME(Win11) : 42 | 0: English / Half Shape 43 | 1: Chinese / Half Shape 44 | 1024: English / Full Shape(in practice still half shape) 45 | 1025: Chinese / Full Shape 46 | ``` 47 | 48 | 49 | -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | add_rules("mode.release") 2 | 3 | set_values("wdk.env.winver", "win10") 4 | 5 | target("im-select-imm") 6 | set_kind("binary") 7 | add_files("src/*.cpp") 8 | add_files("src/*.c") 9 | 10 | add_includedirs("inc/") 11 | 12 | add_rules("win.sdk.application") 13 | add_ldflags("-subsystem:console") 14 | add_links("imm32") 15 | 16 | 17 | 18 | -- If you want to known more usage about xmake, please see https://xmake.io 19 | -- 20 | -- ## FAQ 21 | -- 22 | -- You can enter the project directory firstly before building project. 23 | -- 24 | -- $ cd projectdir 25 | -- 26 | -- 1. How to build project? 27 | -- 28 | -- $ xmake 29 | -- 30 | -- 2. How to configure project? 31 | -- 32 | -- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release] 33 | -- 34 | -- 3. Where is the build output directory? 35 | -- 36 | -- The default output directory is `./build` and you can configure the output directory. 37 | -- 38 | -- $ xmake f -o outputdir 39 | -- $ xmake 40 | -- 41 | -- 4. How to run and debug target after building project? 42 | -- 43 | -- $ xmake run [targetname] 44 | -- $ xmake run -d [targetname] 45 | -- 46 | -- 5. How to install target to the system directory or other output directory? 47 | -- 48 | -- $ xmake install 49 | -- $ xmake install -o installdir 50 | -- 51 | -- 6. Add some frequently-used compilation flags in xmake.lua 52 | -- 53 | -- @code 54 | -- -- add debug and release modes 55 | -- add_rules("mode.debug", "mode.release") 56 | -- 57 | -- -- add macro defination 58 | -- add_defines("NDEBUG", "_GNU_SOURCE=1") 59 | -- 60 | -- -- set warning all as error 61 | -- set_warnings("all", "error") 62 | -- 63 | -- -- set language: c99, c++11 64 | -- set_languages("c99", "c++11") 65 | -- 66 | -- -- set optimization: none, faster, fastest, smallest 67 | -- set_optimize("fastest") 68 | -- 69 | -- -- add include search directories 70 | -- add_includedirs("/usr/include", "/usr/local/include") 71 | -- 72 | -- -- add link libraries and search directories 73 | -- add_links("tbox") 74 | -- add_linkdirs("/usr/local/lib", "/usr/lib") 75 | -- 76 | -- -- add system link libraries 77 | -- add_syslinks("z", "pthread") 78 | -- 79 | -- -- add compilation and link flags 80 | -- add_cxflags("-stdnolib", "-fno-strict-aliasing") 81 | -- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true}) 82 | -- 83 | -- @endcode 84 | -- 85 | 86 | -------------------------------------------------------------------------------- /src/im-select-imm.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "parg.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | /* #define DEBUG */ 9 | 10 | using namespace std; 11 | 12 | int getInputMethod() { 13 | HWND hwnd = GetForegroundWindow(); //dll 14 | if (hwnd) { 15 | DWORD threadID = GetWindowThreadProcessId(hwnd, NULL); //dll 16 | HKL currentLayout = GetKeyboardLayout(threadID); //dll 17 | unsigned int x = (unsigned int)currentLayout & 0x0000FFFF; 18 | return ((int)x); 19 | } 20 | return 0; 21 | } 22 | void switchInputMethod(int locale) { 23 | if (locale < 0) { 24 | return; 25 | } 26 | HWND hwnd = GetForegroundWindow(); //dll 27 | LPARAM currentLayout = ((LPARAM)locale); 28 | PostMessage(hwnd, WM_INPUTLANGCHANGEREQUEST, 0, currentLayout); //dll 29 | } 30 | 31 | // API: https://learn.microsoft.com/en-us/previous-versions/aa913780(v=msdn.10) 32 | // For Microsoft Old Chinese IME(Win10 and Previous) : 33 | // 0: English 34 | // 1: Chinese 35 | // For Microsoft New Chinese IME(Win11) : 36 | // 0: English / Half Shape 37 | // 1: Chinese / Half Shape 38 | // 1024: English / Full Shape (Bit10 and Bit1 used) 39 | // 1025: Chinese / Full Shape 40 | LRESULT getInputMode(){ 41 | HWND foregroundWindow = GetForegroundWindow(); 42 | HWND foregroundIME = ImmGetDefaultIMEWnd(foregroundWindow); 43 | if(foregroundIME){ 44 | LRESULT result = SendMessage(foregroundIME, WM_IME_CONTROL, 0x001, 0); 45 | return result; 46 | } else { 47 | return 0; 48 | } 49 | } 50 | 51 | void switchInputMode(LRESULT mode){ 52 | if ( mode < 0 ) { 53 | return; 54 | } 55 | HWND foregroundWindow = GetForegroundWindow(); 56 | HWND foregroundIME = ImmGetDefaultIMEWnd(foregroundWindow); 57 | LPARAM currentMode = (LPARAM)mode; 58 | SendMessage(foregroundIME, WM_IME_CONTROL, IMC_SETCONVERSIONMODE, currentMode); 59 | } 60 | 61 | 62 | int main(int argc, char** argv) 63 | { 64 | // init parg 65 | struct parg_state ps; 66 | int c; 67 | parg_init(&ps); 68 | // h: help page 69 | // d: delay INT ms 70 | const char optstring[] = "hd:" ; 71 | int optend = parg_reorder(argc, argv, optstring, NULL); 72 | 73 | /* printf("%d\n", optend) ; */ 74 | 75 | 76 | int delay = 30 ; // ms 77 | while ((c = parg_getopt(&ps, optend, argv, optstring)) != -1) { 78 | switch (c) { 79 | case 'h': 80 | printf( \ 81 | "USAGE: \n" \ 82 | " im-select-imm [-h] [-d DELAY] [METHOD] [MODE]\n" \ 83 | "VERSION: \n" \ 84 | " 1.0.2 \n" \ 85 | ); 86 | return 0; 87 | case 'd': 88 | delay = atoi(ps.optarg); 89 | #ifdef DEBUG 90 | printf("DELAY: %d\n", delay); 91 | #endif 92 | break; 93 | case 1: 94 | // for remaining option 95 | break; 96 | } 97 | } 98 | 99 | // process position args 100 | 101 | int remian_argc = argc - ps.optind ; 102 | char **remain_argv = argv + ps.optind ; 103 | 104 | #ifdef DEBUG 105 | printf("OPTIND: %d\n", ps.optind); 106 | printf("REMIAN_ARGC: %d\n", remian_argc); 107 | for(int i = 1; i < argc; i++) { 108 | printf("ARGV[%d]: %s\n", i, argv[i]); 109 | } 110 | for(int i = 0; i < remian_argc; i++) { 111 | printf("REMAIN_ARGV[%d]: %s\n", i, remain_argv[i]); 112 | } 113 | #endif 114 | 115 | // get mode 116 | if ( remian_argc == 0 ) { 117 | int imID = getInputMethod(); 118 | int imMode = getInputMode(); 119 | printf("%d-%d\n", imID, imMode); 120 | return 0; 121 | } 122 | 123 | // not getmode, so is set mode 124 | LRESULT mode = -1; 125 | int method = -1; 126 | if( remian_argc == 1 ) { 127 | char *dash_p = strchr(remain_argv[0],'-'); 128 | if(dash_p){ 129 | char locale_str[16]; 130 | memccpy(locale_str,remain_argv[0],'-',sizeof locale_str); 131 | 132 | method = atoi(locale_str); 133 | mode = atoi(dash_p + 1); 134 | } else { 135 | method = atoi(remain_argv[0]); 136 | } 137 | } 138 | 139 | if ( remian_argc == 2 ) { 140 | // im-select-imm [Method] [Mode] 141 | method = atoi(remain_argv[0]); 142 | mode = atoi(remain_argv[1]); 143 | } 144 | #ifdef DEBUG 145 | printf("SET METHOD: %d\n", method); 146 | printf("SET MODE: %d\n", mode); 147 | #endif 148 | 149 | switchInputMethod(method); 150 | Sleep(delay); 151 | switchInputMode(mode); 152 | 153 | return 0; 154 | } 155 | 156 | -------------------------------------------------------------------------------- /inc/parg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * parg - parse argv 3 | * 4 | * Copyright 2015-2023 Joergen Ibsen 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | * DEALINGS IN THE SOFTWARE. 20 | * 21 | * SPDX-License-Identifier: MIT-0 22 | */ 23 | 24 | #ifndef PARG_H_INCLUDED 25 | #define PARG_H_INCLUDED 26 | 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | #define PARG_VER_MAJOR 1 /**< Major version number */ 32 | #define PARG_VER_MINOR 0 /**< Minor version number */ 33 | #define PARG_VER_PATCH 3 /**< Patch version number */ 34 | #define PARG_VER_STRING "1.0.3" /**< Version number as a string */ 35 | 36 | /** 37 | * Structure containing state between calls to parser. 38 | * 39 | * @see parg_init 40 | */ 41 | struct parg_state { 42 | const char *optarg; /**< Pointer to option argument, if any */ 43 | int optind; /**< Next index in argv to process */ 44 | int optopt; /**< Option value resulting in error, if any */ 45 | const char *nextchar; /**< Next character to process */ 46 | }; 47 | 48 | /** 49 | * Structure for supplying long options to `parg_getopt_long()`. 50 | * 51 | * @see parg_getopt_long 52 | */ 53 | struct parg_option { 54 | const char *name; /**< Name of option */ 55 | int has_arg; /**< Option argument status */ 56 | int *flag; /**< Pointer to flag variable */ 57 | int val; /**< Value of option */ 58 | }; 59 | 60 | /** 61 | * Values for `has_arg` flag in `parg_option`. 62 | * 63 | * @see parg_option 64 | */ 65 | typedef enum { 66 | PARG_NOARG, /**< No argument */ 67 | PARG_REQARG, /**< Required argument */ 68 | PARG_OPTARG /**< Optional argument */ 69 | } parg_arg_num; 70 | 71 | /** 72 | * Initialize `ps`. 73 | * 74 | * Must be called before using state with a parser. 75 | * 76 | * @see parg_state 77 | * 78 | * @param ps pointer to state 79 | */ 80 | void 81 | parg_init(struct parg_state *ps); 82 | 83 | /** 84 | * Parse next short option in `argv`. 85 | * 86 | * Elements in `argv` that contain short options start with a single dash 87 | * followed by one or more option characters, and optionally an option 88 | * argument for the last option character. Examples are '`-d`', '`-ofile`', 89 | * and '`-dofile`'. 90 | * 91 | * Consecutive calls to this function match the command-line arguments in 92 | * `argv` against the short option characters in `optstring`. 93 | * 94 | * If an option character in `optstring` is followed by a colon, '`:`', the 95 | * option requires an argument. If it is followed by two colons, the option 96 | * may take an optional argument. 97 | * 98 | * If a match is found, `optarg` points to the option argument, if any, and 99 | * the value of the option character is returned. 100 | * 101 | * If a match is found, but is missing a required option argument, `optopt` 102 | * is set to the option character. If the first character in `optstring` is 103 | * '`:`', then '`:`' is returned, otherwise '`?`' is returned. 104 | * 105 | * If no option character in `optstring` matches a short option, `optopt` 106 | * is set to the option character, and '`?`' is returned. 107 | * 108 | * If an element of argv does not contain options (a nonoption element), 109 | * `optarg` points to the element, and `1` is returned. 110 | * 111 | * An element consisting of a single dash, '`-`', is returned as a nonoption. 112 | * 113 | * Parsing stops and `-1` is returned, when the end of `argv` is reached, or 114 | * if an element contains '`--`'. 115 | * 116 | * Works similarly to `getopt`, if `optstring` were prefixed by '`-`'. 117 | * 118 | * @param ps pointer to state 119 | * @param argc number of elements in `argv` 120 | * @param argv array of pointers to command-line arguments 121 | * @param optstring string containing option characters 122 | * @return option value on match, `1` on nonoption element, `-1` on end of 123 | * arguments, '`?`' on unmatched option, '`?`' or '`:`' on option argument 124 | * error 125 | */ 126 | int 127 | parg_getopt(struct parg_state *ps, int argc, char *const argv[], 128 | const char *optstring); 129 | 130 | /** 131 | * Parse next long or short option in `argv`. 132 | * 133 | * Elements in `argv` that contain a long option start with two dashes 134 | * followed by a string, and optionally an equal sign and an option argument. 135 | * Examples are '`--help`' and '`--size=5`'. 136 | * 137 | * If no exact match is found, an unambiguous prefix of a long option will 138 | * match. For example, if '`foo`' and '`foobar`' are valid long options, then 139 | * '`--fo`' is ambiguous and will not match, '`--foo`' matches exactly, and 140 | * '`--foob`' is an unambiguous prefix and will match. 141 | * 142 | * If a long option match is found, and `flag` is `NULL`, `val` is returned. 143 | * 144 | * If a long option match is found, and `flag` is not `NULL`, `val` is stored 145 | * in the variable `flag` points to, and `0` is returned. 146 | * 147 | * If a long option match is found, but is missing a required option argument, 148 | * or has an option argument even though it takes none, `optopt` is set to 149 | * `val` if `flag` is `NULL`, and `0` otherwise. If the first character in 150 | * `optstring` is '`:`', then '`:`' is returned, otherwise '`?`' is returned. 151 | * 152 | * If `longindex` is not `NULL`, the index of the entry in `longopts` that 153 | * matched is stored there. 154 | * 155 | * If no long option in `longopts` matches a long option, '`?`' is returned. 156 | * 157 | * Handling of nonoptions and short options is like `parg_getopt()`. 158 | * 159 | * If no short options are required, an empty string, `""`, should be passed 160 | * as `optstring`. 161 | * 162 | * Works similarly to `getopt_long`, if `optstring` were prefixed by '`-`'. 163 | * 164 | * @see parg_getopt 165 | * 166 | * @param ps pointer to state 167 | * @param argc number of elements in `argv` 168 | * @param argv array of pointers to command-line arguments 169 | * @param optstring string containing option characters 170 | * @param longopts array of `parg_option` structures 171 | * @param longindex pointer to variable to store index of matching option in 172 | * @return option value on match, `0` for flag option, `1` on nonoption 173 | * element, `-1` on end of arguments, '`?`' on unmatched or ambiguous option, 174 | * '`?`' or '`:`' on option argument error 175 | */ 176 | int 177 | parg_getopt_long(struct parg_state *ps, int argc, char *const argv[], 178 | const char *optstring, 179 | const struct parg_option *longopts, int *longindex); 180 | 181 | /** 182 | * Reorder elements of `argv` so options appear first. 183 | * 184 | * If there are no long options, `longopts` may be `NULL`. 185 | * 186 | * The return value can be used as `argc` parameter for `parg_getopt()` and 187 | * `parg_getopt_long()`. 188 | * 189 | * @param argc number of elements in `argv` 190 | * @param argv array of pointers to command-line arguments 191 | * @param optstring string containing option characters 192 | * @param longopts array of `parg_option` structures 193 | * @return index of first nonoption in `argv` on success, `-1` on error 194 | */ 195 | int 196 | parg_reorder(int argc, char *argv[], 197 | const char *optstring, 198 | const struct parg_option *longopts); 199 | 200 | #ifdef __cplusplus 201 | } /* extern "C" */ 202 | #endif 203 | 204 | #endif /* PARG_H_INCLUDED */ 205 | -------------------------------------------------------------------------------- /src/parg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * parg - parse argv 3 | * 4 | * Copyright 2015-2023 Joergen Ibsen 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the "Software"), 8 | * to deal in the Software without restriction, including without limitation 9 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | * and/or sell copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | * DEALINGS IN THE SOFTWARE. 20 | * 21 | * SPDX-License-Identifier: MIT-0 22 | */ 23 | 24 | #include "parg.h" 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | /* 31 | * Check if state is at end of argv. 32 | */ 33 | static int 34 | is_argv_end(const struct parg_state *ps, int argc, char *const argv[]) 35 | { 36 | return ps->optind >= argc || argv[ps->optind] == NULL; 37 | } 38 | 39 | /* 40 | * Match nextchar against optstring. 41 | */ 42 | static int 43 | match_short(struct parg_state *ps, int argc, char *const argv[], 44 | const char *optstring) 45 | { 46 | const char *p = strchr(optstring, *ps->nextchar); 47 | 48 | if (p == NULL) { 49 | ps->optopt = *ps->nextchar++; 50 | return '?'; 51 | } 52 | 53 | /* If no option argument, return option */ 54 | if (p[1] != ':') { 55 | return *ps->nextchar++; 56 | } 57 | 58 | /* If more characters, return as option argument */ 59 | if (ps->nextchar[1] != '\0') { 60 | ps->optarg = &ps->nextchar[1]; 61 | ps->nextchar = NULL; 62 | return *p; 63 | } 64 | 65 | /* If option argument is optional, return option */ 66 | if (p[2] == ':') { 67 | return *ps->nextchar++; 68 | } 69 | 70 | /* Option argument required, so return next argv element */ 71 | if (is_argv_end(ps, argc, argv)) { 72 | ps->optopt = *ps->nextchar++; 73 | return optstring[0] == ':' ? ':' : '?'; 74 | } 75 | 76 | ps->optarg = argv[ps->optind++]; 77 | ps->nextchar = NULL; 78 | return *p; 79 | } 80 | 81 | /* 82 | * Match string at nextchar against longopts. 83 | */ 84 | static int 85 | match_long(struct parg_state *ps, int argc, char *const argv[], 86 | const char *optstring, 87 | const struct parg_option *longopts, int *longindex) 88 | { 89 | size_t len; 90 | int num_match = 0; 91 | int match = -1; 92 | int i; 93 | 94 | len = strcspn(ps->nextchar, "="); 95 | 96 | for (i = 0; longopts[i].name; ++i) { 97 | if (strncmp(ps->nextchar, longopts[i].name, len) == 0) { 98 | match = i; 99 | num_match++; 100 | /* Take if exact match */ 101 | if (longopts[i].name[len] == '\0') { 102 | num_match = 1; 103 | break; 104 | } 105 | } 106 | } 107 | 108 | /* Return '?' on no or ambiguous match */ 109 | if (num_match != 1) { 110 | ps->optopt = 0; 111 | ps->nextchar = NULL; 112 | return '?'; 113 | } 114 | 115 | assert(match != -1); 116 | 117 | if (longindex) { 118 | *longindex = match; 119 | } 120 | 121 | if (ps->nextchar[len] == '=') { 122 | /* Option argument present, check if extraneous */ 123 | if (longopts[match].has_arg == PARG_NOARG) { 124 | ps->optopt = longopts[match].flag ? 0 : longopts[match].val; 125 | ps->nextchar = NULL; 126 | return optstring[0] == ':' ? ':' : '?'; 127 | } 128 | else { 129 | ps->optarg = &ps->nextchar[len + 1]; 130 | } 131 | } 132 | else if (longopts[match].has_arg == PARG_REQARG) { 133 | /* Option argument required, so return next argv element */ 134 | if (is_argv_end(ps, argc, argv)) { 135 | ps->optopt = longopts[match].flag ? 0 : longopts[match].val; 136 | ps->nextchar = NULL; 137 | return optstring[0] == ':' ? ':' : '?'; 138 | } 139 | 140 | ps->optarg = argv[ps->optind++]; 141 | } 142 | 143 | ps->nextchar = NULL; 144 | 145 | if (longopts[match].flag != NULL) { 146 | *longopts[match].flag = longopts[match].val; 147 | return 0; 148 | } 149 | 150 | return longopts[match].val; 151 | } 152 | 153 | void 154 | parg_init(struct parg_state *ps) 155 | { 156 | ps->optarg = NULL; 157 | ps->optind = 1; 158 | ps->optopt = '?'; 159 | ps->nextchar = NULL; 160 | } 161 | 162 | int 163 | parg_getopt(struct parg_state *ps, int argc, char *const argv[], 164 | const char *optstring) 165 | { 166 | return parg_getopt_long(ps, argc, argv, optstring, NULL, NULL); 167 | } 168 | 169 | int 170 | parg_getopt_long(struct parg_state *ps, int argc, char *const argv[], 171 | const char *optstring, 172 | const struct parg_option *longopts, int *longindex) 173 | { 174 | assert(ps != NULL); 175 | assert(argv != NULL); 176 | assert(optstring != NULL); 177 | 178 | ps->optarg = NULL; 179 | 180 | if (argc < 2) { 181 | return -1; 182 | } 183 | 184 | /* Advance to next element if needed */ 185 | if (ps->nextchar == NULL || *ps->nextchar == '\0') { 186 | if (is_argv_end(ps, argc, argv)) { 187 | return -1; 188 | } 189 | 190 | ps->nextchar = argv[ps->optind++]; 191 | 192 | /* Check for nonoption element (including '-') */ 193 | if (ps->nextchar[0] != '-' || ps->nextchar[1] == '\0') { 194 | ps->optarg = ps->nextchar; 195 | ps->nextchar = NULL; 196 | return 1; 197 | } 198 | 199 | /* Check for '--' */ 200 | if (ps->nextchar[1] == '-') { 201 | if (ps->nextchar[2] == '\0') { 202 | ps->nextchar = NULL; 203 | return -1; 204 | } 205 | 206 | if (longopts != NULL) { 207 | ps->nextchar += 2; 208 | 209 | return match_long(ps, argc, argv, optstring, 210 | longopts, longindex); 211 | } 212 | } 213 | 214 | ps->nextchar++; 215 | } 216 | 217 | /* Match nextchar */ 218 | return match_short(ps, argc, argv, optstring); 219 | } 220 | 221 | /* 222 | * Reverse elements of `v` from `i` to `j`. 223 | */ 224 | static void 225 | reverse(char *v[], int i, int j) 226 | { 227 | while (j - i > 1) { 228 | char *tmp = v[i]; 229 | v[i] = v[j - 1]; 230 | v[j - 1] = tmp; 231 | ++i; 232 | --j; 233 | } 234 | } 235 | 236 | /* 237 | * Reorder elements of `argv` with no special cases. 238 | * 239 | * This function assumes there is no `--` element, and the last element 240 | * is not an option missing a required argument. 241 | * 242 | * The algorithm is described here: 243 | * http://hardtoc.com/2016/11/07/reordering-arguments.html 244 | */ 245 | static int 246 | parg_reorder_simple(int argc, char *argv[], 247 | const char *optstring, 248 | const struct parg_option *longopts) 249 | { 250 | struct parg_state ps; 251 | int change; 252 | int l = 0; 253 | int m = 0; 254 | int r = 0; 255 | 256 | if (argc < 2) { 257 | return argc; 258 | } 259 | 260 | do { 261 | int nextind; 262 | int c; 263 | 264 | parg_init(&ps); 265 | 266 | nextind = ps.optind; 267 | 268 | /* Parse until end of argument */ 269 | do { 270 | c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); 271 | } while (ps.nextchar != NULL && *ps.nextchar != '\0'); 272 | 273 | change = 0; 274 | 275 | do { 276 | /* Find next non-option */ 277 | for (l = nextind; c != 1 && c != -1;) { 278 | l = ps.optind; 279 | 280 | do { 281 | c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); 282 | } while (ps.nextchar != NULL && *ps.nextchar != '\0'); 283 | } 284 | 285 | /* Find next option */ 286 | for (m = l; c == 1;) { 287 | m = ps.optind; 288 | 289 | do { 290 | c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); 291 | } while (ps.nextchar != NULL && *ps.nextchar != '\0'); 292 | } 293 | 294 | /* Find next non-option */ 295 | for (r = m; c != 1 && c != -1;) { 296 | r = ps.optind; 297 | 298 | do { 299 | c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); 300 | } while (ps.nextchar != NULL && *ps.nextchar != '\0'); 301 | } 302 | 303 | /* Find next option */ 304 | for (nextind = r; c == 1;) { 305 | nextind = ps.optind; 306 | 307 | do { 308 | c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); 309 | } while (ps.nextchar != NULL && *ps.nextchar != '\0'); 310 | } 311 | 312 | if (m < r) { 313 | change = 1; 314 | reverse(argv, l, m); 315 | reverse(argv, m, r); 316 | reverse(argv, l, r); 317 | } 318 | } while (c != -1); 319 | } while (change != 0); 320 | 321 | return l + (r - m); 322 | } 323 | 324 | int 325 | parg_reorder(int argc, char *argv[], 326 | const char *optstring, 327 | const struct parg_option *longopts) 328 | { 329 | struct parg_state ps; 330 | int lastind; 331 | int optend; 332 | int c; 333 | 334 | assert(argv != NULL); 335 | assert(optstring != NULL); 336 | 337 | if (argc < 2) { 338 | return argc; 339 | } 340 | 341 | parg_init(&ps); 342 | 343 | /* Find end of normal arguments */ 344 | do { 345 | lastind = ps.optind; 346 | 347 | c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL); 348 | 349 | /* Check for trailing option with error */ 350 | if ((c == '?' || c == ':') && is_argv_end(&ps, argc, argv)) { 351 | lastind = ps.optind - 1; 352 | break; 353 | } 354 | } while (c != -1); 355 | 356 | optend = parg_reorder_simple(lastind, argv, optstring, longopts); 357 | 358 | /* Rotate `--` or trailing option with error into position */ 359 | if (lastind < argc) { 360 | reverse(argv, optend, lastind); 361 | reverse(argv, optend, lastind + 1); 362 | ++optend; 363 | } 364 | 365 | return optend; 366 | } 367 | --------------------------------------------------------------------------------