├── Makefile ├── cjson.dll ├── pime ├── freeabc.lua ├── init.lua └── server.lua ├── pipe.c ├── server.lua └── server ├── input_methods └── freeabc │ ├── en.ico │ ├── freeabc.ico │ ├── ime.json │ └── zh.ico ├── x64 └── PIMETextService.dll └── x86 └── PIMETextService.dll /Makefile: -------------------------------------------------------------------------------- 1 | pipe.dll : pipe.c 2 | gcc -g -Wall --shared -o $@ $^ -I/usr/local/include -L/usr/local/bin -llua53 3 | 4 | clean : 5 | rm pipe.dll 6 | -------------------------------------------------------------------------------- /cjson.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwu/freeabc/9f6dd2a3c1bdd19d8a155c6da2f3059f0f0c2935/cjson.dll -------------------------------------------------------------------------------- /pime/freeabc.lua: -------------------------------------------------------------------------------- 1 | local pime = require "pime" 2 | 3 | local freeabc = { 4 | english = true, 5 | composition = nil, 6 | path = nil, 7 | } 8 | 9 | local FREEABC_GUID = "{7C42702A-3DDD-4AFF-B936-F7BF00BF2E1D}" 10 | --local CTRL_SPACE_GUID = "{574053A5-6F3D-451B-A048-802AF4E101BC}" 11 | 12 | function freeabc:init(req) 13 | print "init" 14 | end 15 | 16 | function freeabc:onLangProfileActivated(req) 17 | assert(req.guid == FREEABC_GUID) 18 | end 19 | 20 | function freeabc:_setpath(path) 21 | self.path = path 22 | end 23 | 24 | function freeabc:onActivate(req, resp) 25 | print("add", resp.add_button) 26 | self.english = not req.isKeyboardOpen 27 | if self.english then 28 | resp:add_button(self.path.."\\en.ico") 29 | else 30 | resp:add_button(self.path.."\\zh.ico") 31 | end 32 | if self.composition then 33 | resp:composition(self.composition) 34 | end 35 | 36 | resp:commit "Hello" 37 | end 38 | 39 | function freeabc:onDeactivate(req, resp) 40 | resp:remove_button() 41 | end 42 | 43 | function freeabc:onMenu() 44 | print("on menu") 45 | --[[ 46 | return { 47 | { 48 | text = "啦啦啦啦", 49 | id = 1, 50 | checked = true,s 51 | } 52 | } 53 | ]] 54 | end 55 | 56 | function freeabc:onKeyboardStatusChanged(req, resp) 57 | if self.english ~= not req.opened then 58 | self.english = not req.opened 59 | if self.english then 60 | print("change en") 61 | resp:change_button "d:\\project\\freeabc\\en.ico" 62 | else 63 | print("change zh") 64 | resp:change_button "d:\\project\\freeabc\\zh.ico" 65 | end 66 | end 67 | return true 68 | end 69 | 70 | function freeabc:onCommand(req, resp) 71 | if req.id == 0 then 72 | print("On command 0") 73 | resp:keyboard(self.english) 74 | else 75 | print("click menu", req.id) 76 | end 77 | return true 78 | end 79 | 80 | local function is_key_down(req, code) 81 | -- lua is base 1, so use [code + 1] 82 | return (req.keyStates[code+1] & (1 << 7)) ~= 0 83 | end 84 | 85 | local support = "abcdefghijklmnopqrstuvwxyz0123456789" 86 | do 87 | local tmp = {} 88 | for i=1,#support do 89 | tmp[support:byte(i)] = true 90 | end 91 | support = tmp 92 | end 93 | function freeabc:filterKeyDown(req, resp) 94 | local c = req.charCode 95 | if support[c] and not is_key_down(req, pime.VK_MENU) 96 | and not is_key_down(req, pime.VK_CONTROL) then 97 | return true 98 | end 99 | if self.composition == nil then 100 | return false 101 | end 102 | if c == 8 or c == 27 or c == 32 then 103 | return true 104 | end 105 | resp:commit(self.composition) 106 | self.composition = nil 107 | if c == 13 then 108 | resp:keyboard(false) 109 | end 110 | return false 111 | end 112 | 113 | function freeabc:onKeyDown(req, resp) 114 | local c = req.charCode 115 | if c <= 32 then 116 | if c == 13 then -- CR 117 | -- resp:keyboard(false) 118 | -- self.composition = nil 119 | elseif c == 27 then -- ESC 120 | resp:composition "" 121 | self.composition = nil 122 | elseif c == 8 then -- BACKSPACE 123 | self.composition = self.composition:sub(1,-2) 124 | resp:composition(self.composition) 125 | if self.composition == "" then 126 | self.composition = nil 127 | end 128 | elseif c == 32 then -- SPACE 129 | -- end composition 130 | resp:commit(self.composition) 131 | self.composition = nil 132 | end 133 | return true 134 | end 135 | self.composition = (self.composition or "") .. string.char(req.charCode):rep(req.repeatCount+1) 136 | resp:composition(self.composition) 137 | return true 138 | end 139 | 140 | 141 | return freeabc -------------------------------------------------------------------------------- /pime/init.lua: -------------------------------------------------------------------------------- 1 | local pime = {} 2 | 3 | --[[ 4 | commitString : string -- 提交字符串 5 | compositionString : string -- 正在输入的字符串 6 | compositionCursor : integer -- 输入字符串光标位置 7 | candidateList : { strings } -- 备选列表 8 | showCandidates : boolean -- 显示备选列表窗 9 | candidateCursor : integer -- 备选列表窗光标 10 | 11 | addButton : { 12 | id : string -- "windows-mode-icon" 为托盘按钮 13 | style : integer -- C API style, 一般不用 14 | icon: string -- full path to a *.ico file 15 | commandId: integer -- an integer ID which will be passed to onCommand() when the button is clicked. 16 | text: string -- text on the button (optional) 17 | tooltip: string -- (optional) 18 | type: "button", "menu", "toggle" -- (optional, button is the default) 19 | enable: boolean -- if the button is enabled (optional) 20 | toggled: boolean -- is the button toggled, only valid if type is "toggle" (optional) 21 | } 22 | 23 | removeButton : { names } -- id 列表 24 | changeButton : { buttons } -- button 列表 25 | 26 | addPreservedKey : { 27 | guid : { 28 | keyCode : integer , -- VK_* 29 | modifiers : integer , 30 | TF_MOD_ALT = 0x0001 31 | TF_MOD_CONTROL = 0x0002 32 | TF_MOD_SHIFT = 0x0004 33 | TF_MOD_RALT = 0x0008 34 | TF_MOD_RCONTROL = 0x0010 35 | TF_MOD_RSHIFT = 0x0020 36 | TF_MOD_LALT = 0x0040 37 | TF_MOD_LCONTROL = 0x0080 38 | TF_MOD_LSHIFT = 0x0100 39 | TF_MOD_ON_KEYUP = 0x0200 40 | TF_MOD_IGNORE_ALL_MODIFIER = 0x0400 41 | } 42 | } 43 | 44 | setSelKeys : string "1234567890" -- 显示在备选表上的字符 45 | customizeUI : { 46 | candFontName : string, 47 | candFontSize : integer, 48 | candPerRow : integer, 49 | candUseCursor : boolean, 50 | } 51 | showMessage : { 52 | message : string, 53 | duration : integer, -- 秒 54 | } 55 | 56 | init : { 57 | id : string 58 | isConsole : boolean 59 | isMetroApp : boolean 60 | isUiLess : boolean 61 | isWindows8Above : boolean 62 | } 63 | 64 | onActivate 65 | onDeactivate 66 | 67 | onLangProfileActivated : { 68 | guid : string -- should be {7C42702A-3DDD-4AFF-B936-F7BF00BF2E1D} freeabc 69 | -- multi mod support 70 | } 71 | onLangProfileDeactivated : { guid } 72 | 73 | type keymap { 74 | charCode : integer 75 | keyCode : integer 76 | repeatCount : integer 77 | scanCode : integer 78 | isExtended : boolean 79 | keyStates : integer[256] 80 | } 81 | 82 | filterKeyDown : keymap 83 | return : true means filter 84 | filterKeyUp : keymap 85 | return : true means filter 86 | onKeyDown : keymap 87 | return : true means processed 88 | onKeyUp : keymap 89 | return : true means processed 90 | onPreservedKey : guid 91 | return : true means processed 92 | onCommand : { 93 | id : integer (commandId when addButton) 94 | type : integer (left click or right click) 95 | COMMAND_LEFT_CLICK = 0, 96 | COMMAND_RIGHT_CLICK = 1, 97 | COMMAND_MENU =2, 98 | } 99 | return : true means processd 100 | onMenu : { id : integer } 101 | return : { 102 | id : integer 103 | text : string 104 | checked : boolean 105 | submenu : { ... } 106 | } 107 | onCompartmentChanged : { guid } 108 | onKeyboardStatusChanged : { opened : boolean } 109 | onCompositionTerminated : { forced : boolean } -- called just before current composition is terminated for doing cleanup. 110 | 111 | ]] 112 | 113 | local response = {} ; response.__index = response 114 | 115 | function pime.dispatch(req, dispatcher) 116 | local resp = setmetatable({ success = nil, seqNum = req.seqNum }, response) 117 | local f = dispatcher[req.method] 118 | if not f then 119 | resp.success = false 120 | return resp 121 | end 122 | 123 | local ok, err = pcall(f, dispatcher, req, resp) 124 | if resp.success == nil then 125 | resp.success = ok 126 | end 127 | if not ok then 128 | print(err) 129 | elseif err ~= nil then 130 | resp["return"] = err 131 | end 132 | return resp 133 | end 134 | 135 | local function set(s) 136 | for k,v in ipairs(s) do 137 | s[k] = nil 138 | s[v] = true 139 | end 140 | return s 141 | end 142 | 143 | local valid_command = set { 144 | "init", 145 | "onActivate", 146 | "onDeactivate", 147 | "onLangProfileActivated", 148 | "onLangProfileDeactivated", 149 | "filterKeyDown", 150 | "filterKeyUp", 151 | "onKeyDown", 152 | "onKeyUp", 153 | "onPreservedKey", 154 | "onCommand", 155 | "onMenu", 156 | "onCompartmentChanged", 157 | "onKeyboardStatusChanged", 158 | "onCompositionTerminated", 159 | } 160 | function pime.check(dispatcher) 161 | for k,v in pairs(dispatcher) do 162 | if type(v) == "function" and type(k) == "string" 163 | and k:sub(1,1) ~= "_" then 164 | if not valid_command[k] then 165 | error( k .. " is invalid method") 166 | end 167 | end 168 | end 169 | end 170 | 171 | function response:commit(str) 172 | self.commitString = tostring(str) 173 | end 174 | 175 | function response:composition(str , cursor) 176 | self.compositionString = tostring(str) 177 | if cursor then 178 | self.compositionCursor = math.floor(tonumber(cursor)) 179 | end 180 | end 181 | 182 | function response:candidate(list , cursor) 183 | assert(type(list) == "table") 184 | self.candidateList = list 185 | if cursor then 186 | self.candidateCursor = math.floor(tonumber(cursor)) 187 | end 188 | end 189 | 190 | function response:show(show) 191 | self.showCandidates = show ~= false 192 | end 193 | 194 | function response:keyboard(open) 195 | self.openKeyboard = open 196 | end 197 | 198 | function response:add_button(icon) 199 | self.addButton = {{ 200 | id = "windows-mode-icon", -- windows 8+ only support this 201 | type = "button", 202 | icon = icon, 203 | }} 204 | end 205 | 206 | function response:remove_button() 207 | self.removeButton = { "windows-mode-icon" } 208 | end 209 | 210 | function response:change_button(icon) 211 | self.changeButton = {{ 212 | id = "windows-mode-icon", -- windows 8+ only support this 213 | icon = icon, 214 | }} 215 | end 216 | 217 | function response:add_preserved_key(guid, keycode, modifier) 218 | local add = self.addPreservedKey 219 | if not add then 220 | add = {} 221 | self.addPreservedKey = add 222 | end 223 | add[guid] = { 224 | keyCode = keycode, 225 | modifiers = modifier, 226 | } 227 | end 228 | 229 | function response:set_select(str) 230 | self.SelKeys = tostring(str) 231 | end 232 | 233 | function response:customize(font, size, number, cursor) 234 | self.customizeUI = { 235 | candFontName = font, 236 | candFontSize = size, 237 | candPerRow = number, 238 | candUseCursor = cursor, 239 | } 240 | end 241 | 242 | function response:message(text, d) 243 | self.showMessage = { 244 | message = text, 245 | duration = d, 246 | } 247 | end 248 | 249 | pime.TF_MOD_ALT = 0x0001 250 | pime.TF_MOD_CONTROL = 0x0002 251 | pime.TF_MOD_SHIFT = 0x0004 252 | pime.TF_MOD_RALT = 0x0008 253 | pime.TF_MOD_RCONTROL = 0x0010 254 | pime.TF_MOD_RSHIFT = 0x0020 255 | pime.TF_MOD_LALT = 0x0040 256 | pime.TF_MOD_LCONTROL = 0x0080 257 | pime.TF_MOD_LSHIFT = 0x0100 258 | pime.TF_MOD_ON_KEYUP = 0x0200 259 | pime.TF_MOD_IGNORE_ALL_MODIFIER = 0x0400 260 | 261 | pime.VK_LBUTTON =0x01 262 | pime.VK_RBUTTON =0x02 263 | pime.VK_CANCEL =0x03 264 | pime.VK_MBUTTON =0x04 265 | pime.VK_XBUTTON1 =0x05 266 | pime.VK_XBUTTON2 =0x06 267 | pime.VK_BACK =0x08 268 | pime.VK_TAB =0x09 269 | pime.VK_CLEAR =0x0C 270 | pime.VK_RETURN =0x0D 271 | pime.VK_SHIFT =0x10 272 | pime.VK_CONTROL =0x11 273 | pime.VK_MENU =0x12 274 | pime.VK_PAUSE =0x13 275 | pime.VK_CAPITAL =0x14 276 | pime.VK_KANA =0x15 277 | pime.VK_HANGEUL =0x15 278 | pime.VK_HANGUL =0x15 279 | pime.VK_JUNJA =0x17 280 | pime.VK_FINAL =0x18 281 | pime.VK_HANJA =0x19 282 | pime.VK_KANJI =0x19 283 | pime.VK_ESCAPE =0x1B 284 | pime.VK_CONVERT =0x1C 285 | pime.VK_NONCONVERT =0x1D 286 | pime.VK_ACCEPT =0x1E 287 | pime.VK_MODECHANGE =0x1F 288 | pime.VK_SPACE =0x20 289 | pime.VK_PRIOR =0x21 290 | pime.VK_NEXT =0x22 291 | pime.VK_END =0x23 292 | pime.VK_HOME =0x24 293 | pime.VK_LEFT =0x25 294 | pime.VK_UP =0x26 295 | pime.VK_RIGHT =0x27 296 | pime.VK_DOWN =0x28 297 | pime.VK_SELECT =0x29 298 | pime.VK_PRINT =0x2A 299 | pime.VK_EXECUTE =0x2B 300 | pime.VK_SNAPSHOT =0x2C 301 | pime.VK_INSERT =0x2D 302 | pime.VK_DELETE =0x2E 303 | pime.VK_HELP =0x2F 304 | 305 | pime.VK_LWIN =0x5B 306 | pime.VK_RWIN =0x5C 307 | pime.VK_APPS =0x5D 308 | pime.VK_SLEEP =0x5F 309 | pime.VK_NUMPAD0 =0x60 310 | pime.VK_NUMPAD1 =0x61 311 | pime.VK_NUMPAD2 =0x62 312 | pime.VK_NUMPAD3 =0x63 313 | pime.VK_NUMPAD4 =0x64 314 | pime.VK_NUMPAD5 =0x65 315 | pime.VK_NUMPAD6 =0x66 316 | pime.VK_NUMPAD7 =0x67 317 | pime.VK_NUMPAD8 =0x68 318 | pime.VK_NUMPAD9 =0x69 319 | pime.VK_MULTIPLY =0x6A 320 | pime.VK_ADD =0x6B 321 | pime.VK_SEPARATOR =0x6C 322 | pime.VK_SUBTRACT =0x6D 323 | pime.VK_DECIMAL =0x6E 324 | pime.VK_DIVIDE =0x6F 325 | pime.VK_F1 =0x70 326 | pime.VK_F2 =0x71 327 | pime.VK_F3 =0x72 328 | pime.VK_F4 =0x73 329 | pime.VK_F5 =0x74 330 | pime.VK_F6 =0x75 331 | pime.VK_F7 =0x76 332 | pime.VK_F8 =0x77 333 | pime.VK_F9 =0x78 334 | pime.VK_F10 =0x79 335 | pime.VK_F11 =0x7A 336 | pime.VK_F12 =0x7B 337 | pime.VK_F13 =0x7C 338 | pime.VK_F14 =0x7D 339 | pime.VK_F15 =0x7E 340 | pime.VK_F16 =0x7F 341 | pime.VK_F17 =0x80 342 | pime.VK_F18 =0x81 343 | pime.VK_F19 =0x82 344 | pime.VK_F20 =0x83 345 | pime.VK_F21 =0x84 346 | pime.VK_F22 =0x85 347 | pime.VK_F23 =0x86 348 | pime.VK_F24 =0x87 349 | pime.VK_NUMLOCK =0x90 350 | pime.VK_SCROLL =0x91 351 | pime.VK_OEM_NEC_EQUAL =0x92 352 | pime.VK_OEM_FJ_JISHO =0x92 353 | pime.VK_OEM_FJ_MASSHOU =0x93 354 | pime.VK_OEM_FJ_TOUROKU =0x94 355 | pime.VK_OEM_FJ_LOYA =0x95 356 | pime.VK_OEM_FJ_ROYA =0x96 357 | pime.VK_LSHIFT =0xA0 358 | pime.VK_RSHIFT =0xA1 359 | pime.VK_LCONTROL =0xA2 360 | pime.VK_RCONTROL =0xA3 361 | pime.VK_LMENU =0xA4 362 | pime.VK_RMENU =0xA5 363 | pime.VK_BROWSER_BACK =0xA6 364 | pime.VK_BROWSER_FORWARD =0xA7 365 | pime.VK_BROWSER_REFRESH =0xA8 366 | pime.VK_BROWSER_STOP =0xA9 367 | pime.VK_BROWSER_SEARCH =0xAA 368 | pime.VK_BROWSER_FAVORITES =0xAB 369 | pime.VK_BROWSER_HOME =0xAC 370 | pime.VK_VOLUME_MUTE =0xAD 371 | pime.VK_VOLUME_DOWN =0xAE 372 | pime.VK_VOLUME_UP =0xAF 373 | pime.VK_MEDIA_NEXT_TRACK =0xB0 374 | pime.VK_MEDIA_PREV_TRACK =0xB1 375 | pime.VK_MEDIA_STOP =0xB2 376 | pime.VK_MEDIA_PLAY_PAUSE =0xB3 377 | pime.VK_LAUNCH_MAIL =0xB4 378 | pime.VK_LAUNCH_MEDIA_SELECT =0xB5 379 | pime.VK_LAUNCH_APP1 =0xB6 380 | pime.VK_LAUNCH_APP2 =0xB7 381 | pime.VK_OEM_1 =0xBA 382 | pime.VK_OEM_PLUS =0xBB 383 | pime.VK_OEM_COMMA =0xBC 384 | pime.VK_OEM_MINUS =0xBD 385 | pime.VK_OEM_PERIOD =0xBE 386 | pime.VK_OEM_2 =0xBF 387 | pime.VK_OEM_3 =0xC0 388 | pime.VK_OEM_4 =0xDB 389 | pime.VK_OEM_5 =0xDC 390 | pime.VK_OEM_6 =0xDD 391 | pime.VK_OEM_7 =0xDE 392 | pime.VK_OEM_8 =0xDF 393 | pime.VK_OEM_AX =0xE1 394 | pime.VK_OEM_102 =0xE2 395 | pime.VK_ICO_HELP =0xE3 396 | pime.VK_ICO_00 =0xE4 397 | pime.VK_PROCESSKEY =0xE5 398 | pime.VK_ICO_CLEAR =0xE6 399 | pime.VK_PACKET =0xE7 400 | pime.VK_OEM_RESET =0xE9 401 | pime.VK_OEM_JUMP =0xEA 402 | pime.VK_OEM_PA1 =0xEB 403 | pime.VK_OEM_PA2 =0xEC 404 | pime.VK_OEM_PA3 =0xED 405 | pime.VK_OEM_WSCTRL =0xEE 406 | pime.VK_OEM_CUSEL =0xEF 407 | pime.VK_OEM_ATTN =0xF0 408 | pime.VK_OEM_FINISH =0xF1 409 | pime.VK_OEM_COPY =0xF2 410 | pime.VK_OEM_AUTO =0xF3 411 | pime.VK_OEM_ENLW =0xF4 412 | pime.VK_OEM_BACKTAB =0xF5 413 | pime.VK_ATTN =0xF6 414 | pime.VK_CRSEL =0xF7 415 | pime.VK_EXSEL =0xF8 416 | pime.VK_EREOF =0xF9 417 | pime.VK_PLAY =0xFA 418 | pime.VK_ZOOM =0xFB 419 | pime.VK_NONAME =0xFC 420 | pime.VK_PA1 =0xFD 421 | pime.VK_OEM_CLEAR =0xFE 422 | 423 | return pime -------------------------------------------------------------------------------- /pime/server.lua: -------------------------------------------------------------------------------- 1 | local pipe = require "pipe" 2 | local json = require "cjson" 3 | 4 | local server = {} 5 | 6 | local fd 7 | local debug 8 | 9 | function server.connect() 10 | fd = pipe.connect "pime" 11 | print("client connected", fd) 12 | end 13 | 14 | function server.debug() 15 | debug = true 16 | end 17 | 18 | local function one_request(dispatcher) 19 | local data = pipe.read(fd) 20 | if debug then print("<===", data) end 21 | local req = json.decode(data) 22 | local resp = dispatcher(req) 23 | local reply = json.encode(resp) 24 | if debug then print("===>", reply) end 25 | pipe.write(fd, reply) 26 | end 27 | 28 | function server.run(dispatcher) 29 | print "Start" 30 | while true do 31 | if not fd then 32 | server.connect() 33 | end 34 | local ok, err = pcall(one_request,dispatcher) 35 | if not ok then 36 | print("ERR:", err) 37 | pipe.close(fd) 38 | fd = nil 39 | end 40 | end 41 | end 42 | 43 | return server -------------------------------------------------------------------------------- /pipe.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include // for UNLEN 3 | #include // for security attributes constants 4 | #include // for ACL 5 | #include 6 | #include 7 | 8 | static PSECURITY_DESCRIPTOR g_securittyDescriptor = NULL; 9 | static SECURITY_ATTRIBUTES g_securityAttributes = {0}; 10 | static PACL g_acl = NULL; 11 | static EXPLICIT_ACCESSA g_explicitAccesses[2]; 12 | static PSID g_everyoneSID = NULL; 13 | static PSID g_allAppsSID = NULL; 14 | 15 | static void 16 | init() { 17 | // create security attributes for the pipe 18 | // http://msdn.microsoft.com/en-us/library/windows/desktop/hh448449(v=vs.85).aspx 19 | // define new Win 8 app related constants 20 | memset(&g_explicitAccesses, 0, sizeof(g_explicitAccesses)); 21 | // Create a well-known SID for the Everyone group. 22 | // FIXME: we should limit the access to current user only 23 | // See this article for details: https://msdn.microsoft.com/en-us/library/windows/desktop/hh448493(v=vs.85).aspx 24 | 25 | SID_IDENTIFIER_AUTHORITY worldSidAuthority = {SECURITY_WORLD_SID_AUTHORITY}; 26 | AllocateAndInitializeSid(&worldSidAuthority, 1, 27 | SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &g_everyoneSID); 28 | 29 | // https://services.land.vic.gov.au/ArcGIS10.1/edESRIArcGIS10_01_01_3143/Python/pywin32/PLATLIB/win32/Demos/security/explicit_entries.py 30 | 31 | g_explicitAccesses[0].grfAccessPermissions = GENERIC_ALL; 32 | g_explicitAccesses[0].grfAccessMode = SET_ACCESS; 33 | g_explicitAccesses[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; 34 | g_explicitAccesses[0].Trustee.pMultipleTrustee = NULL; 35 | g_explicitAccesses[0].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; 36 | g_explicitAccesses[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; 37 | g_explicitAccesses[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; 38 | g_explicitAccesses[0].Trustee.ptstrName = (LPTSTR)g_everyoneSID; 39 | 40 | // FIXME: will this work under Windows 7 and Vista? 41 | // create SID for app containers 42 | SID_IDENTIFIER_AUTHORITY appPackageAuthority = {SECURITY_APP_PACKAGE_AUTHORITY}; 43 | AllocateAndInitializeSid(&appPackageAuthority, 44 | SECURITY_BUILTIN_APP_PACKAGE_RID_COUNT, 45 | SECURITY_APP_PACKAGE_BASE_RID, 46 | SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE, 47 | 0, 0, 0, 0, 0, 0, &g_allAppsSID); 48 | 49 | g_explicitAccesses[1].grfAccessPermissions = GENERIC_ALL; 50 | g_explicitAccesses[1].grfAccessMode = SET_ACCESS; 51 | g_explicitAccesses[1].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; 52 | g_explicitAccesses[1].Trustee.pMultipleTrustee = NULL; 53 | g_explicitAccesses[1].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; 54 | g_explicitAccesses[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; 55 | g_explicitAccesses[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; 56 | g_explicitAccesses[1].Trustee.ptstrName = (LPTSTR)g_allAppsSID; 57 | 58 | // create DACL 59 | DWORD err = SetEntriesInAcl(2, g_explicitAccesses, NULL, &g_acl); 60 | if (0 == err) { 61 | // security descriptor 62 | g_securittyDescriptor = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); 63 | InitializeSecurityDescriptor(g_securittyDescriptor, SECURITY_DESCRIPTOR_REVISION); 64 | 65 | // Add the ACL to the security descriptor. 66 | SetSecurityDescriptorDacl(g_securittyDescriptor, TRUE, g_acl, FALSE); 67 | } 68 | 69 | g_securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); 70 | g_securityAttributes.lpSecurityDescriptor = g_securittyDescriptor; 71 | g_securityAttributes.bInheritHandle = TRUE; 72 | } 73 | 74 | static void 75 | cleanup() { 76 | if(g_everyoneSID != NULL) 77 | FreeSid(g_everyoneSID); 78 | if (g_allAppsSID != NULL) 79 | FreeSid(g_allAppsSID); 80 | if (g_securittyDescriptor != NULL) 81 | LocalFree(g_securittyDescriptor); 82 | if (g_acl != NULL) 83 | LocalFree(g_acl); 84 | } 85 | 86 | // References: 87 | // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365588(v=vs.85).aspx 88 | static HANDLE 89 | connect_pipe(const char* app_name) { 90 | HANDLE pipe = INVALID_HANDLE_VALUE; 91 | char username[UNLEN + 1]; 92 | DWORD unlen = UNLEN + 1; 93 | if (GetUserNameA(username, &unlen)) { 94 | // add username to the pipe path so it will not clash with other users' pipes. 95 | char pipe_name[MAX_PATH]; 96 | sprintf(pipe_name, "\\\\.\\pipe\\%s\\%s_pipe", username, app_name); 97 | const size_t buffer_size = 1024; 98 | // create the pipe 99 | pipe = CreateNamedPipeA(pipe_name, 100 | PIPE_ACCESS_DUPLEX, 101 | PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 102 | PIPE_UNLIMITED_INSTANCES, 103 | buffer_size, 104 | buffer_size, 105 | NMPWAIT_USE_DEFAULT_WAIT, 106 | &g_securityAttributes); 107 | 108 | if (pipe != INVALID_HANDLE_VALUE) { 109 | // try to connect to the named pipe 110 | // NOTE: this is a blocking call 111 | if (FALSE == ConnectNamedPipe(pipe, NULL)) { 112 | // fail to connect the pipe 113 | CloseHandle(pipe); 114 | pipe = INVALID_HANDLE_VALUE; 115 | } 116 | } 117 | } 118 | return pipe; 119 | } 120 | 121 | static void 122 | close_pipe(HANDLE pipe) { 123 | FlushFileBuffers(pipe); 124 | DisconnectNamedPipe(pipe); 125 | CloseHandle(pipe); 126 | } 127 | 128 | static int 129 | read_pipe(HANDLE pipe, char* buf, unsigned long len, unsigned long* error) { 130 | DWORD read_len = 0; 131 | BOOL success = ReadFile(pipe, buf, len, &read_len, NULL); 132 | if (error != NULL) 133 | *error = success ? 0 : (unsigned long)GetLastError(); 134 | return (int)read_len; 135 | } 136 | 137 | static int 138 | write_pipe(HANDLE pipe, const char* data, unsigned long len, unsigned long* error) { 139 | DWORD write_len = 0; 140 | BOOL success = WriteFile(pipe, data, len, &write_len, NULL); 141 | if (error != NULL) 142 | *error = success ? 0 : (unsigned long)GetLastError(); 143 | return (int)write_len; 144 | } 145 | 146 | BOOL APIENTRY 147 | DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { 148 | switch (ul_reason_for_call) { 149 | case DLL_PROCESS_ATTACH: 150 | DisableThreadLibraryCalls(hModule); // disable DllMain calls due to new thread creation 151 | init(); 152 | break; 153 | case DLL_PROCESS_DETACH: 154 | cleanup(); 155 | break; 156 | } 157 | return TRUE; 158 | } 159 | 160 | #include 161 | #include 162 | 163 | static int 164 | lconnect(lua_State *L) { 165 | HANDLE pipe = connect_pipe(luaL_checkstring(L,1)); 166 | if (pipe == INVALID_HANDLE_VALUE) 167 | return luaL_error(L, "connect failed"); 168 | lua_pushlightuserdata(L, pipe); 169 | return 1; 170 | } 171 | 172 | static int 173 | lclose(lua_State *L) { 174 | HANDLE pipe = lua_touserdata(L, 1); 175 | if (pipe == NULL) 176 | return luaL_error(L, "invalid pipe"); 177 | close_pipe(pipe); 178 | return 0; 179 | } 180 | 181 | #define READ_SIZE 1024 182 | 183 | static int 184 | lread(lua_State *L) { 185 | HANDLE pipe = lua_touserdata(L, 1); 186 | unsigned long error = 0; 187 | char tmp[READ_SIZE]; 188 | int rd = read_pipe(pipe, tmp, READ_SIZE, &error); 189 | if (error == ERROR_MORE_DATA) { 190 | luaL_Buffer b; 191 | luaL_buffinitsize(L, &b, 2*READ_SIZE); 192 | luaL_addlstring(&b, tmp, rd); 193 | for (;;) { 194 | char * tmp = luaL_prepbuffsize(&b, READ_SIZE); 195 | int rd = read_pipe(pipe, tmp, READ_SIZE, &error); 196 | if (error == ERROR_MORE_DATA) { 197 | luaL_addsize(&b, rd); 198 | } else if (error == ERROR_IO_PENDING) { 199 | continue; 200 | } else if (error != 0) { 201 | return luaL_error(L, "read error: %d", error); 202 | } else { 203 | luaL_pushresult(&b); 204 | return 1; 205 | } 206 | } 207 | } else if (error != 0) { 208 | return luaL_error(L, "read error: %d", error); 209 | } 210 | lua_pushlstring(L, tmp, rd); 211 | return 1; 212 | } 213 | 214 | static int 215 | lwrite(lua_State *L) { 216 | HANDLE pipe = lua_touserdata(L, 1); 217 | size_t sz = 0; 218 | const char * data = luaL_checklstring(L, 2, &sz); 219 | int offset = luaL_optinteger(L, 3, 0); 220 | if (offset >= sz) { 221 | return luaL_error(L, "invalid offset"); 222 | } 223 | data += offset; 224 | sz -= offset; 225 | unsigned long error = 0; 226 | int wt = write_pipe(pipe, data, sz, &error); 227 | if (error != 0) { 228 | return luaL_error(L, "write error : %u", error); 229 | } 230 | if (wt == sz) 231 | return 0; 232 | lua_pushinteger(L, offset + wt); 233 | return 1; 234 | } 235 | 236 | int 237 | luaopen_pipe(lua_State *L) { 238 | luaL_checkversion(L); 239 | luaL_Reg l[] = { 240 | {"connect", lconnect }, 241 | {"close", lclose }, 242 | {"read", lread }, 243 | {"write", lwrite }, 244 | { NULL, NULL }, 245 | }; 246 | 247 | luaL_newlib(L,l); 248 | 249 | return 1; 250 | } 251 | 252 | -------------------------------------------------------------------------------- /server.lua: -------------------------------------------------------------------------------- 1 | local pime = require "pime" 2 | local server = require "pime.server" 3 | local freeabc = require "pime.freeabc" 4 | 5 | --server.debug() 6 | 7 | freeabc:_setpath "d:\\project\\freeabc" 8 | pime.check(freeabc) 9 | server.run(function (req) 10 | return pime.dispatch(req, freeabc) 11 | end) 12 | -------------------------------------------------------------------------------- /server/input_methods/freeabc/en.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwu/freeabc/9f6dd2a3c1bdd19d8a155c6da2f3059f0f0c2935/server/input_methods/freeabc/en.ico -------------------------------------------------------------------------------- /server/input_methods/freeabc/freeabc.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwu/freeabc/9f6dd2a3c1bdd19d8a155c6da2f3059f0f0c2935/server/input_methods/freeabc/freeabc.ico -------------------------------------------------------------------------------- /server/input_methods/freeabc/ime.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Free ABC", 3 | "version": "0.1", 4 | "guid": "{7C42702A-3DDD-4AFF-B936-F7BF00BF2E1D}", 5 | "locale": "zh-CN", 6 | "icon": "freeabc.ico", 7 | "win8_icon": "zh.ico", 8 | "moduleName": "freeabc_ime", 9 | "serviceName": "FreeABCTextService", 10 | "configTool": "unknown" 11 | } 12 | -------------------------------------------------------------------------------- /server/input_methods/freeabc/zh.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwu/freeabc/9f6dd2a3c1bdd19d8a155c6da2f3059f0f0c2935/server/input_methods/freeabc/zh.ico -------------------------------------------------------------------------------- /server/x64/PIMETextService.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwu/freeabc/9f6dd2a3c1bdd19d8a155c6da2f3059f0f0c2935/server/x64/PIMETextService.dll -------------------------------------------------------------------------------- /server/x86/PIMETextService.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwu/freeabc/9f6dd2a3c1bdd19d8a155c6da2f3059f0f0c2935/server/x86/PIMETextService.dll --------------------------------------------------------------------------------