├── AutoHotkey32.exe ├── AutoHotkey64.exe ├── README.md ├── ahkDefine.ahk └── lib ├── CentBrowser.ahk ├── Class_Array.ahk ├── Class_CDP.ahk ├── Class_Clipboard.ahk ├── Class_Ctrl.ahk ├── Class_Date.ahk ├── Class_Gdip.ahk ├── Class_Gui.ahk ├── Class_Map.ahk ├── Class_Mouse.ahk ├── Class_Number.ahk ├── Class_Pinyin.ahk ├── Class_String.ahk ├── Class_Timer.ahk ├── Class_UIA.ahk ├── Class_XYXY.ahk ├── ComVar.ahk ├── Explorer.ahk ├── LVICE_XXS.ahk ├── Socket.ahk ├── WatchFolder.ahk ├── WebSocket.ahk ├── WinHttpRequest.ahk ├── YAML.ahk ├── hyaray.ahk ├── msedge.ahk └── pinyin.txt /AutoHotkey32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyaray/ahk_v2_lib/bbd293640b4872f73b1b337c6240a19ad869c01f/AutoHotkey32.exe -------------------------------------------------------------------------------- /AutoHotkey64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyaray/ahk_v2_lib/bbd293640b4872f73b1b337c6240a19ad869c01f/AutoHotkey64.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ahk_v2_lib 2 | `AutoHotkey64.exe` and v2-beta libs 3 | 4 | `AutoHotkey64.exe` is base on [thqby/AutoHotkey_H](https://github.com/thqby/AutoHotkey_H), 5 | and compiled `ahkDefine.ahk` using [Ahk2Exe](https://github.com/AutoHotkey/Ahk2Exe) to add basic methods and functions. 6 | 7 | compile commandline: `"Ahk2Exe.exe" /in "d:\ahkDefine.ahk" /out "D:\AutoHotkey64.exe" /base "c:\AutoHotkey\AutoHotkey64.exe" /resourceid #2` 8 | > `/resourceid #2` is the key param. 9 | -------------------------------------------------------------------------------- /ahkDefine.ahk: -------------------------------------------------------------------------------- 1 | #Requires AutoHotkey v2.0-beta 2 | #SingleInstance Force 3 | #warn Unreachable, off 4 | #MapCaseSense off 5 | SetControlDelay(-1) 6 | SetKeyDelay(-1) 7 | CoordMode("mouse", "window") 8 | CoordMode("tooltip", "window") 9 | CoordMode("pixel", "window") 10 | CoordMode("caret", "window") 11 | CoordMode("menu", "window") 12 | 13 | ;@Ahk2Exe-SetProductVersion %A_AhkVersion%hy 14 | 15 | A_UserProfile := EnvGet("USERPROFILE") 16 | A_LocalAppdata := EnvGet("LOCALAPPDATA") 17 | ;TODO 添加 A_LineDir 18 | 19 | ;map 可用.访问属性(OA首页按F4出错) 20 | ;map.prototype.DefineProp('__get', {call: (self, key, *) => self[key]}) 21 | ;map.prototype.DefineProp('__set', {call: (self, key, params, value) => self[key] := value}) 22 | 23 | ;@Ahk2Exe-IgnoreBegin 24 | ;@Ahk2Exe-Obey U_bits, = %A_PtrSize% * 8 25 | ;@Ahk2Exe-Obey U_type, = "%A_IsUnicode%" ? "Unicode" : "ANSI" 26 | ;@Ahk2Exe-ExeName %A_ScriptName~\.[^\.]+$%_%U_type%_%U_bits% 27 | ;@Ahk2Exe-Let vvvv = "v" 28 | CodeVersion := "1.2.3.4" 29 | ;@Ahk2Exe-Let U_version = %A_PriorLine~U)^(.+"){1}(.+)".*$~$2% 30 | company := "My Company" 31 | ;@Ahk2Exe-Let U_company = %A_PriorLine~U)^(.+"){3}(.+)".*$~$2% 32 | ;@Ahk2Exe-IgnoreEnd 33 | 34 | ;#include lib\JSON.ahk 35 | ;#include lib\struct.ahk 36 | 37 | #include lib\Class_String.ahk 38 | #include lib\Class_Number.ahk 39 | #include lib\Class_Array.ahk 40 | #include lib\Class_Map.ahk 41 | #include lib\Class_Date.ahk 42 | #include lib\Class_Timer.ahk 43 | #include lib\Yaml.ahk 44 | #include lib\WinHttpRequest.ahk 45 | #include lib\Class_Gui.ahk 46 | #include lib\LVICE_XXS.ahk 47 | #include lib\WatchFolder.ahk 48 | 49 | #include lib\Socket.ahk 50 | #include lib\Class_CDP.ahk 51 | #include lib\Class_Clipboard.ahk 52 | #include lib\CentBrowser.ahk 53 | #include lib\msedge.ahk 54 | #include lib\Class_Pinyin.ahk 55 | #include lib\hyaray.ahk 56 | #include lib\Class_XYXY.ahk 57 | #include lib\Class_Mouse.ahk 58 | #include lib\Class_Ctrl.ahk 59 | #include lib\Class_UIA.ahk 60 | #include lib\Class_Gdip.ahk 61 | 62 | #include lib\Explorer.ahk 63 | -------------------------------------------------------------------------------- /lib/CentBrowser.ahk: -------------------------------------------------------------------------------- 1 | class _CB extends _CDP { 2 | 3 | ;只是为了方便打开网页 4 | __new(url:="", fp:=unset, funAfterDo:=unset) { 5 | if (url != "") { 6 | if (isset(fp)) 7 | oCB := _CDP.smartGet(fp) 8 | else 9 | oCB := _CDP.smartGet("chrome") 10 | oCB.tabOpenLink(url, funAfterDo?) 11 | } 12 | } 13 | 14 | static onekey() { 15 | if (WinActive("ahk_class Chrome_WidgetWin_1 ahk_exe chrome.exe")) { 16 | WinMinimize ;防止左下角的网址残留 17 | WinHide 18 | } else { 19 | _CDP.smartGet("chrome").tabOpenLink() 20 | } 21 | } 22 | 23 | ;网页内容的元素 24 | static getElementOfMain(key:=unset) { 25 | elWin := UIA.ElementFromHandle(WinActive("A"), true) 26 | el := elWin.FindFirst(UIA.CreatePropertyCondition("ControlType", "ToolBar")) 27 | el := el.GetParent().GetNext() 28 | if (!isset(key)) 29 | return el 30 | switch key { 31 | case "y": return el.GetBoundingRectangle()[2] 32 | case "mark": return el 33 | default: return el 34 | } 35 | return el 36 | } 37 | 38 | ;ctrl-f查找 39 | static search(str)=>sendEx("{escape}{ctrl down}f{ctrl up}", 100, str) 40 | 41 | ;扩展所在目录 42 | static getExtensionDir(extName) { 43 | switch extName { 44 | case "FireShot": extID := "mcbpblocgmgfnpjjppndjkmgjaogfceg" 45 | case "surfingkeys": extID := "gfbliohnnapiefjpjlpjnehglfpaknnc" 46 | case "SwitchyOmega": extID := "padekgcemlokbadohgkifijomclgjgif" 47 | case "tampermonkey": extID := "dhdgffkkebhmkfjojejmpbldmpobfkfo" 48 | default: return 49 | } 50 | extDir := "s:\CentBrowser\User Data\Default\Extensions\" . extID 51 | ;子文件夹数量 52 | cnt := 0 53 | loop files, extDir . "\*", "D" { 54 | if (A_LoopFileAttrib ~= "[HS]") 55 | continue 56 | dirName := A_LoopFileName 57 | cnt++ 58 | } 59 | if (cnt == 1) 60 | extDir .= "\" . dirName 61 | ; msgbox(extDir) 62 | return extDir 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /lib/Class_Array.ahk: -------------------------------------------------------------------------------- 1 | ;AutoHotkey原生不支持对象,都要new才能用,所以用工具类更合适 2 | ;如何判断二维数组 arr[1] is array? 3 | ;默认都是不修改原arr,方法内都会clone()并操作 4 | ;除了方法名以r开头的会修改原arr,且无返回值(rMoveDown) 5 | ;ip 1.2 转成 192.168.1.2 6 | ;NOTE map 方法已自带,参数是(v,k),注意区分 map 的返回值和被 map 修改之后的 arr 7 | ;arr.map(xxx) 命令运行之后,arr 已经被修改了 8 | ;arr := arr.map(xxx) 加了赋值,这是 map 的函数返回值拼接的结果 9 | ;提取二维数组的第1项组成一维数组 10 | ;arr2.map((a)=>a[1]) 11 | ;内置方法 12 | ; arr.indexOf(val, start_index:=1) 13 | ; arr.FindIndex(callback: (value [, index]) => Boolean, start_index := 1) 14 | ; arr.join(",") 15 | ; arr.filter((v)=>v == 2) 返回true的保留,python和js也都是此逻辑 16 | ; arr.sort(callback?: (a, b) => Integer) => $this 修改原数组,示例: arr.sort((a,b)=>a[2]-b[2]) 17 | 18 | 19 | ; https://autohotkey.com/board/topic/83081-ahk-l-customizing-object-and-array 20 | defprop := object.DefineProp.bind(array.prototype) 21 | proto := _Array.prototype 22 | for k in proto.OwnProps() { 23 | if (k != "__Class") 24 | defprop(k, proto.GetOwnPropDesc(k)) 25 | } 26 | 27 | class _Array extends Array { 28 | 29 | toString() => this.toJson() 30 | 31 | ;只处理第1级 32 | ;如果fitOutdebug,则前面增加换行符让调试信息更整齐 33 | toString1(fitOutdebug:=false) { 34 | if (!this.length) 35 | return "" 36 | if (this.length == 1) 37 | return json.stringify(this[1]) 38 | res := json.stringify(this[1]) 39 | if (fitOutdebug) 40 | res := "`n" . res 41 | for obj in this { 42 | if (A_Index > 1) 43 | res .= "`n" . json.stringify(obj) 44 | } 45 | return res 46 | } 47 | 48 | ;暂时只支持2维 49 | ;第2维长度只看第1个值 50 | size() { 51 | if (!this.length) 52 | return [0] 53 | res := [this.length] 54 | if (this[1] is array) 55 | res.push(this[1].length) 56 | return res 57 | } 58 | 59 | ;只支持一维数组,普通值对比 60 | equal(arr) { 61 | if (this.length != arr.length) 62 | return false 63 | for v in this { 64 | if (v != arr[A_Index]) 65 | return false 66 | } 67 | return true 68 | } 69 | 70 | toArr2() { 71 | if (!this.length) 72 | return this 73 | if (this[1] is array) 74 | return this 75 | arr := this 76 | arr2 := [] 77 | if (!isobject(arr[1])) { 78 | for v in arr 79 | arr2.push([v]) 80 | } else if (arr[1] is map) { ;NOTE map 81 | for obj in arr { 82 | i := A_Index 83 | arr[i] := [] 84 | for k,v in obj ;TODO k 是否要获取 85 | arr2[i].push(v) 86 | } 87 | } 88 | return arr2 89 | } 90 | 91 | ;获取 92 | count(value) { 93 | res := 0 94 | for v in this { 95 | if (v = value) 96 | res++ 97 | } 98 | return res 99 | } 100 | 101 | ;删除重复 102 | unique() { 103 | arr := [] 104 | obj := map() 105 | for v in this { 106 | if (obj.has(v)) 107 | continue 108 | arr.push(v) 109 | obj[v] := 1 110 | } 111 | return arr 112 | } 113 | 114 | deleteEmpty() { 115 | arr := [] 116 | for v in this { 117 | if (v != "") 118 | arr.push(v) 119 | } 120 | return arr 121 | } 122 | 123 | ;二维数组,删除第n列 124 | delete(c) { 125 | arr2 := this 126 | for arr in arr2 127 | arr.RemoveAt(c) 128 | return arr2 129 | } 130 | 131 | extend(arr) { 132 | arr0 := this 133 | for v in arr 134 | arr0.push(v) 135 | return arr0 136 | } 137 | 138 | ;不要最后一个 end=-1 139 | ;切片功能 140 | slice(start:=1, end:=0, step:=1) { 141 | len := this.length 142 | i := start < 1 ? len + start : start 143 | if (i < 1 || i > len) 144 | return [] 145 | j := end < 1 ? len + end : end 146 | if (j < i || j > len) 147 | return [] 148 | arrRes := [] 149 | reverse := false 150 | if step = 0 { 151 | throw error("Slice: step cannot be 0",-1) 152 | } else if step < 0 { 153 | if i < j 154 | throw error("Slice: if step is negative then start value must be greater than end value", -1) 155 | while i >= j { 156 | arrRes.push(this[i]) 157 | i += step 158 | } 159 | } else { 160 | if (i > j) 161 | throw error("Slice: start value must be smaller than end value", -1) 162 | while (i <= j) { 163 | arrRes.push(this[i]) 164 | i += step 165 | } 166 | } 167 | return arrRes 168 | } 169 | 170 | reverse() { 171 | arrRes := [] 172 | loop(this.length) 173 | arrRes.push(this[-A_Index]) 174 | return arrRes 175 | } 176 | 177 | toMapAsKey(val:="") { 178 | obj := map() 179 | for v in this 180 | obj[v] := val 181 | return obj 182 | } 183 | 184 | ;arr2 转成字典数组 185 | ;[ [A1,B1], [A2,B2] ] [title1, title2] 186 | ;返回[ 187 | ; map("title1",A1, "title2",B1), 188 | ; map("title1",A2, "title2",B2), 189 | ;] 190 | ; 191 | ;[A1,B1], [title1, title2] 192 | ;返回 map("title1",A1, "title2",B1) 193 | toMapEx(arrTitle) { 194 | arrRes := [] 195 | if (this[1] is array) { 196 | for arr in this { 197 | obj := map() 198 | for v in arr { 199 | if isset(v) ;过滤null 200 | obj[arrTitle[A_Index]] := v 201 | } 202 | arrRes.push(obj) 203 | } 204 | return arrRes 205 | } else { 206 | obj := map() 207 | for v in this { 208 | if isset(v) ;过滤null 209 | obj[arrTitle[A_Index]] := v 210 | } 211 | return obj 212 | } 213 | } 214 | 215 | ;目前只在 _Pwd 里使用(用于给每行做索引) 216 | ;[ [A1,B1], [A2,B2] ] [title1, title2] 217 | ;{ 218 | ; title1 : [A1,B1], 219 | ; title2 : [A2,B2], 220 | ;} 221 | toMapByArray(arrTitle:=unset) { 222 | arr := this 223 | obj := map() 224 | if (isset(arrTitle)) { 225 | if (arrTitle is array) { 226 | for v in arr ;TODO 是否判断长度 227 | obj[arrTitle[A_Index]] := v 228 | } else if (arrTitle == 0) { ;当 key 229 | for v in this 230 | obj[v] := A_Index 231 | } else if (arrTitle == 1) { ;当 value 232 | for v in this 233 | obj[A_Index] := v 234 | } 235 | } else { 236 | for v in this 237 | obj[A_Index] := v 238 | } 239 | return obj 240 | } 241 | 242 | ;funDistinct 返回值当 key 用来筛选 243 | ;参数都是(v,k) 244 | ;arr.filter2((v,k)=>v[1]!="", (v,k)=>v[1]) 245 | filter2(fun, funDistinct:=unset) { 246 | arrRes := [] 247 | if (!isset(funDistinct)) { 248 | for k, v in this { 249 | if (fun(k, v)) 250 | arrRes.push(v) 251 | } 252 | } else { 253 | obj := map() 254 | for k, v in this { 255 | key := funDistinct(v,k) 256 | if (fun(v,k) && !obj.has(key)) { 257 | arrRes.push(v) 258 | obj[key] := "" 259 | } 260 | } 261 | } 262 | return arrRes 263 | } 264 | 265 | reduce(fun, v0:=unset) { 266 | arr := this 267 | if (!isset(v0)) { 268 | if (!arr.length) 269 | throw TypeError("错误:空数组,且未传入参数") 270 | idx := 1 271 | res := arr[1] 272 | } else { 273 | idx := 0 274 | res := v0 275 | } 276 | loop(arr.length-idx) 277 | res := fun(res, arr[idx+A_Index]) 278 | return res 279 | } 280 | 281 | ;转成表格字符串(方便查看) 282 | ;NOTE 数据结构要统一 283 | /* 284 | arr := [ 285 | [1,2], 286 | [3,4], 287 | ] 288 | 或 289 | arr := [ 290 | map( 291 | "a",1, 292 | "b",2, 293 | ), 294 | map( 295 | "a",3, 296 | "b",4, 297 | ), 298 | ] 299 | */ 300 | toTable(charItem:="`t", arrKey:=unset) { 301 | arr := this 302 | if (!arr.length) 303 | return "" 304 | res := "" 305 | if (arr[1] is array) { 306 | for arr1 in arr { 307 | for v in arr1 { 308 | if isobject(v) 309 | res .= "{}" . charItem 310 | else 311 | res .= v . charItem 312 | } 313 | res := rtrim(res, charItem) . "`n" 314 | } 315 | } else if (arr[1] is map) { 316 | ;arrTitle 317 | if isset(arrKey) { 318 | arrTitle := arrKey 319 | } else { 320 | arrTitle := [] 321 | for k, v in arr[1] 322 | arrTitle.push(k) 323 | } 324 | res := arrTitle.join(charItem) . "`n" 325 | ;data 326 | for map1 in arr { 327 | for v in arrTitle { 328 | if (isobject(map1[v])) { 329 | switch type(map1[v]) { 330 | case "Array": 331 | vThis := map1[v].toJson() 332 | case "Map": 333 | vThis := "{}" 334 | default: 335 | vThis := format("{{1}}", type(map1[v])) 336 | } 337 | } else { 338 | vThis := map1[v] 339 | } 340 | res .= vThis . charItem 341 | } 342 | res := rtrim(res, charItem) . "`n" 343 | } 344 | } 345 | return rtrim(res, "`n") 346 | } 347 | 348 | sum() { 349 | res := 0 350 | for v in this 351 | res += v 352 | return res 353 | } 354 | 355 | ;参考python的 sorted 356 | ;用map的key来排序,没有过滤功能 357 | ;NOTE reverse还要考虑相同值的情况,是否也要倒序,如下示例的,2的两项顺序 358 | ;arr2 := [ 359 | ; [2, 3], 360 | ; [2, 4], 361 | ; [3, "b"], 362 | ; [1, "c"], 363 | ;] 364 | sorted(fun, reverse:=0) { 365 | oa := map() 366 | for o in this { 367 | k := fun(o) 368 | if (oa.has(k)) 369 | oa[k].push(o) 370 | else 371 | oa[k] := [o] 372 | } 373 | arrRes := [] 374 | for k, ao in oa { 375 | switch reverse { 376 | case 0: 377 | arrRes.extend(ao) 378 | case 1: ;内部 o 正序 379 | arrRes := ao.extend(arrRes) 380 | case 2: ;内部 o 倒序 381 | loop (ao.length) 382 | arrRes.push(ao[-A_Index]) 383 | } 384 | } 385 | return arrRes 386 | } 387 | 388 | ;递归平铺所有内容(删除文件夹) 389 | ;每项是 map,如果含 keySub 则递归读取其下的内容(比如hyobj) 390 | ;每项是 array,如果为 map 则递归读取其下的内容(比如yaml) 391 | static noBranch(arr, keySub:="") { 392 | if (!arr.length) 393 | return [] 394 | arrRes := [] 395 | if (arr[1] is map) { 396 | if (keySub == "") 397 | throw ValueError("keySub 未定义") 398 | for obj in arr { 399 | if (obj.has(keySub)) ;为目录 400 | arrRes.extend(this.noBranch(obj[keySub],keySub)) ;NOTE 递归 401 | else { 402 | arrRes.push(obj) 403 | } 404 | } 405 | } else if (arr[1] is array) { 406 | for arr1 in arr { 407 | if (arr1 is map) { ;为目录 408 | for k1, v1 in arr1 { 409 | if (v1 is array) { 410 | arrRes.extend(this.noBranch(v1)) 411 | } else if (v1 is string) { 412 | msgbox(v1) ;TODO 413 | } else { 414 | msgbox(type(v1)) 415 | } 416 | } 417 | } else { 418 | arrRes.push(arr1) 419 | } 420 | } 421 | } 422 | return arrRes 423 | } 424 | 425 | ;参考 noBranch 的场景 426 | ;对每个 item 进行处理 427 | ;map 只是无脑对 item 进行处理,这个增加了对子目录 item 的处理 428 | mapEx(funDeal) { 429 | return f(this) 430 | f(arr) { 431 | arrRes := [] 432 | for item in arr { 433 | if (item is map) { ;NOTE 判断为目录 434 | for k1, v1 in item 435 | arrRes.push(this.mapEx(v1, funDeal)) ;NOTE 递归 436 | } else if (item is array) { 437 | arrRes.push(funDeal(item)) ;NOTE 是函数返回值,而不是修改后的 item 438 | } else if (item is string) { 439 | msgbox(item) 440 | } 441 | } 442 | return arrRes 443 | } 444 | } 445 | 446 | ;提取对象的所有key对应的值到数组 447 | ;比如多个map("key","aaa","hotkey","B")组成数据 448 | ;提取所有key的值aaa到数组 449 | static oValueOfKey2Arr(arr, subKey, key) { 450 | arrRes := [] 451 | tmp(arr, subKey, key) 452 | return arrRes 453 | tmp(arr, subKey, key) { 454 | for v in arr { 455 | if (v.has(subKey)) 456 | tmp(v[subKey], subKey, key) 457 | else if (v.has(key)) 458 | arrRes.push(v[key]) 459 | } 460 | } 461 | } 462 | 463 | ;如果arrKeyValue为["hotkey", "H"],则删除obj.hotkey为"H"的obj 464 | static oRemoveByKey(arr, arrKeyValue) { 465 | arrNew := arr.clone() 466 | for k, v in arrNew { 467 | if (v[arrKeyValue[1]] = arrKeyValue[2]) 468 | arrNew.RemoveAt(k) 469 | } 470 | return arrNew 471 | } 472 | 473 | static oRemoveByValue(arr, value) { ;删除第一个对应的key 474 | arr.RemoveAt(arr.indexOf(value)) 475 | } 476 | 477 | ;[[1,2],[3,4]]转为[1,2,3,4] 478 | static oArrTwo2Arr(arr) { ;2维转成1维数组 479 | arrRes := [] 480 | for k, v in arr { 481 | if (isobject(v)) { 482 | for k1, v1 in v 483 | arrRes.push(v1) 484 | } else { 485 | arrRes.push(v) 486 | } 487 | } 488 | return arrRes 489 | } 490 | 491 | ;修改行,列,参考 numpy 492 | ;msgbox(json.stringify([1,2,3,4,5,6].reshape(2,3))) 493 | reshape(rs, cs) { 494 | arr := this 495 | if (arr.length == 0) 496 | return [] 497 | arrRes := [[]] 498 | i := 0 499 | r := 1 500 | if (arr[1] is array) { ;二维数组 501 | for arr1 in arr { 502 | for v in arr1 { 503 | i++ 504 | addRecord(v) 505 | } 506 | } 507 | } else { 508 | for v in arr { 509 | i++ 510 | addRecord(v) 511 | } 512 | } 513 | return arrRes 514 | addRecord(v) { 515 | if (arrRes[r].length == cs) { 516 | arrRes.push([v]) 517 | r++ 518 | } else 519 | arrRes[r].push(v) 520 | } 521 | } 522 | 523 | ;v1移到v0后 524 | moveAfterValue(v1, v0) { 525 | arr := this 526 | i1 := arr.indexOf(v1) 527 | if (!i1) 528 | throw ValueError(format("{1}不在数组内", v1)) 529 | i0 := arr.indexOf(v0) 530 | if (!i0) 531 | throw ValueError(format("{1}不在数组内", v0)) 532 | arr.RemoveAt(i1) 533 | arr.InsertAt(i0+1, v1) 534 | return arr 535 | } 536 | 537 | moveDown(arr, idx) { 538 | arrNew := arr.clone() 539 | if (idx>0 && idx 1) { 550 | obj := arr[idx] 551 | arr.RemoveAt(idx) 552 | arr.insertat(idx-1, obj) 553 | } 554 | return arrNew 555 | } 556 | 557 | rMoveUp(arr, idx) { 558 | if (idx > 1) { 559 | obj := arr[idx] 560 | arr.RemoveAt(idx) 561 | arr.insertat(idx-1, obj) 562 | } 563 | } 564 | 565 | rMoveDown(arr, idx) { 566 | if (idx>0 && idx arr.length - start + 1) 599 | throw "l无效" 600 | arrRes := [] 601 | loop(l) 602 | arrRes.push(arr[start+A_Index-1]) 603 | return arrRes 604 | } 605 | */ 606 | 607 | ;获取num的mod值 608 | arr2Mod(arr, num) { 609 | arrRes := [] 610 | for k, v in arr 611 | arrRes.push(mod(v, num)) 612 | return arrRes 613 | } 614 | 615 | ;每个项目的第1项增加序号[[1,8],[2,7]] 616 | addIndex(arr) { 617 | arrNew := [] 618 | for k, v in arr 619 | arrNew.push([A_Index, v]) 620 | return arrNew 621 | } 622 | 623 | ;arrBase为[1,2,3,4] 624 | ;arr为[6,7],则返回[1,2,6,7] 625 | mergeRight(arrBase) { 626 | arr := this 627 | if (!arr.length) 628 | return arrBase 629 | if (arrBase.length = arr.length) 630 | return arr 631 | loop(arr.length) 632 | arrBase[-A_Index] := arr[-A_Index] 633 | return arrBase 634 | } 635 | mergeLeft(arrBase) { 636 | arr := this 637 | if (!arr.length) 638 | return arrBase 639 | if (arrBase.length = arr.length) 640 | return arr 641 | for v in arr 642 | arrBase[A_Index] := v 643 | return arrBase 644 | } 645 | 646 | ;转成字符串,带key . A_Tab 647 | joinWithKey(charKey:="`t", charEnd:="`n") { 648 | arr := this 649 | res := "" 650 | for k, v in arr 651 | res .= k . charKey . v . charEnd 652 | return RTrim(res, charEnd) 653 | } 654 | 655 | ;arr[1]为基准,数字+1,统计缺少的项 656 | ;如果少4,5,6,7则合并成4-7 657 | ;Objects有同名方法 658 | joinNumLost(arr) { 659 | if (!arr.length) 660 | return 661 | numSave := arr[1] 662 | res := [] ;记录缺失数字 663 | for v in arr { 664 | if (A_Index > 1 && v > numSave + 1) { 665 | if (v-numSave == 2) 666 | res.push(numSave+1) 667 | else 668 | res.push((numSave+1) . "-" . (v-1)) 669 | } 670 | numSave := v 671 | } 672 | return res 673 | } 674 | 675 | ;----------------------arr每项都是obj-------------------------- 676 | 677 | ;比如获取arr[n] = map("a",v1, "b",v1)里键b的值为3的序号n,则函数参数为(obj, "b", 3) 678 | oGetIndexByKeyValue(obj, key, value) { 679 | for k, v in obj 680 | if (v[key] = value) 681 | return k 682 | } 683 | 684 | ;根据key,value获取二维数组的两个索引,返回数组 685 | ;比如获取obj[m][n] = map("a",x1, "b",y1)里键b的值为3的序号[m,n],则函数参数为(obj, "b", 3) 686 | getArrIndexByKeyValue(obj, key, value) { 687 | for k1, v1 in obj { 688 | for k2, v2 in v1 { 689 | if (v2[key] = value) 690 | return [k1,k2] 691 | } 692 | } 693 | } 694 | 695 | ;每项包括oldKey的对象添加newKey,值为newValue(可以用%key%替换obj[key](优先)或%key%变量值) 696 | ;比如给obj添加icon项,值为"d:\QQ\%Value%.jpg") 697 | oObjAddKey(subKey, newKey, newValue) { 698 | arrRes := this.clone() 699 | return tmp(arrRes, subKey, newKey, newValue) 700 | tmp(arr, subKey, newKey, newValue) { 701 | for k, v in arr { 702 | if (v.has(subKey)) 703 | v[subKey] := tmp(v[subKey], subKey, newKey, newValue) 704 | else 705 | v[newKey] := this.oKeyVar2Str(newValue, v) ;字符串替换 706 | } 707 | return arr 708 | } 709 | } 710 | 711 | ;把%var%转换成obj[var]的值 712 | ;比如%key%.jpg,value转成obj[key] 713 | oKeyVar2Str(str, obj) { 714 | global 715 | reg := "(.*?)%(\w+)%(.*)" 716 | startPos := 1 717 | loop { ;有变量 718 | p := RegExMatch(str, reg, &m, startPos) 719 | if (p) { 720 | if (obj.has(m[2])) { 721 | val := obj[m[2]] 722 | startPos := m.pos(2)+ strlen(val) - 1 723 | str := substr(str,1,p-1) . m[1] . val . m[3] 724 | } else { 725 | startPos := m.pos(2)+ strlen(val) + 1 726 | str := substr(str,1,p-1) . m[1] . "%" . val . "%" . m[3] 727 | } 728 | } else { 729 | return str 730 | } 731 | } 732 | } 733 | 734 | ;比如多个map("key","key1","hotkey","B")组成的1维数组 735 | oSort(arr, key, tp:="") { ;默认升序,D为降序 736 | obj := map() ;记录 737 | for k, v in arr { 738 | keySort := v[key] 739 | if (obj.has(keySort)) ;已有结果 740 | obj[keySort].push(v) 741 | else 742 | obj[keySort] := [v] 743 | } 744 | arrRes := [] 745 | for k, v in obj { 746 | for k1, v1 in v 747 | arrRes.push(v1) 748 | } 749 | return arrRes 750 | } 751 | 752 | ;遍历所有元素的排列组合,返回二维数组 753 | ;比如[1,2,3] 生成6种 754 | traverseGroups() { 755 | arr := this 756 | switch arr.length { 757 | case 1: 758 | return [arr] 759 | case 2: 760 | return [arr, [arr[2],arr[1]]] 761 | default: 762 | item := arr.pop() 763 | arr1 := this.traverseGroups() 764 | arr2 := [] 765 | for a in arr1 { 766 | loop (a.length) { ;item 插入a的n个位置 767 | i := A_Index 768 | aTmp := [] 769 | for v in a { 770 | if (A_Index == i) 771 | aTmp.push(item) 772 | aTmp.push(v) 773 | } 774 | arr2.push(aTmp) 775 | } 776 | ;msgbox(json.stringify(arr2, 4)) 777 | ;最后再加一种情况 778 | a.push(item) 779 | arr2.push(a) 780 | } 781 | return arr2 782 | } 783 | } 784 | 785 | } 786 | 787 | ;arr := [ 788 | ; map( 789 | ; "a",1, 790 | ; "sub", map( 791 | ; "a",11, 792 | ; ) 793 | ; ), 794 | ; map( 795 | ; "b",2, 796 | ; "sub", map( 797 | ; "b",22, 798 | ; ) 799 | ; ), 800 | ;] 801 | ;arr := [ 802 | ; ["a","aa","aaa"], 803 | ; ["b","bb","bbb"], 804 | ;] 805 | ;msgbox(["a","b"].filter((x)=>instr(x, "b"))) 806 | ;msgbox(string(arr)) 807 | ;msgbox([1,2]) 808 | -------------------------------------------------------------------------------- /lib/Class_Clipboard.ahk: -------------------------------------------------------------------------------- 1 | ;枚举 https://docs.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats 2 | ; https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-formats#standard-clipboard-formats 3 | ; https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclipboardformata 4 | ;HTML Clipboard Format http://msdn.microsoft.com/en-us/library/aa767917 5 | ;msgbox(json.stringify(_Clipboard.arrDate(), 4)) 6 | 7 | ;hClip := dllcall("RegisterClipboardFormat", "str","png", "uint") 8 | ;if !dllcall("IsClipboardFormatAvailable", "uint",hClip) 9 | ; throw Error("Clipboard does not have PNG stream data.") 10 | ;dllcall("GetClipboardData", "uint",hClip, "ptr")) 11 | 12 | ;遍历 ClipboardAll() 用法见 _Excel.rngByClipboard() 13 | ;_Clipboard.fromFiles(["z:\blank.html","z:\Class_CDP.ahk"]) 14 | class _Clipboard { 15 | 16 | ;thqby 提供 2022.12.13 17 | fromFiles(files, MoveFiles:=false) { 18 | static PreferredDropEffect := dllcall("RegisterClipboardFormat", "Str", "Preferred DropEffect", "uint") 19 | total_len := 0 20 | for fp in files 21 | total_len += strlen(fp) + 1 22 | if (total_len && dllcall("OpenClipboard", "ptr",A_ScriptHwnd) && dllcall("EmptyClipboard")) { 23 | hDrop := dllcall("GlobalAlloc", "uint",0x42, "uint",20 + (total_len + 1) << 1, "uptr") 24 | p := NumPut("uint",20, "int64",0, "uint",0, "uint",1, dllcall("GlobalLock", "ptr",hDrop)) 25 | files.Push("") 26 | for fp in files 27 | p += strput(fp, p) 28 | dllcall("GlobalUnlock", "ptr",hDrop) 29 | dllcall("SetClipboardData", "uint",0x0F, "uptr",hDrop) 30 | hMem := dllcall("GlobalAlloc", "uint",0x42, "uint",4, "uptr") 31 | pMem := dllcall("GlobalLock", "ptr", hMem) 32 | numput("uchar", MoveFiles ? 2 : 1, pMem) 33 | dllcall("GlobalUnlock", "ptr",hMem) 34 | dllcall("SetClipboardData", "uint",PreferredDropEffect, "ptr",hMem) 35 | dllcall("CloseClipboard") 36 | return true 37 | } 38 | return false 39 | } 40 | 41 | ;判断顺序 42 | ;文件 *15 43 | ;图片 *2 8 17 44 | ;Excel range 1-5 7-8 13-14 16-17 *129 45 | ;文本 1 7 *13 16 46 | ;html 47 | ;相关 xl.ClipboardFormats https://docs.microsoft.com/zh-cn/office/vba/api/excel.xlclipboardformat 48 | static getTypes() { 49 | ClipboardForamts := map( 50 | 1 , "CF_TEXT", ; 51 | 2 , "CF_BITMAP", ; 52 | 3 , "CF_METAFILEPICT", ; 53 | 4 , "CF_SYLK", 54 | 5 , "CF_DIF", 55 | 6 , "CF_TIFF", 56 | 7 , "CF_OEMTEXT", ; 57 | 8 , "CF_DIB", 58 | 9 , "CF_PALETTE", 59 | 10 , "CF_PENDATA", 60 | 11 , "CF_RIFF", 61 | 12 , "CF_WAVE", 62 | 13 , "CF_UNICODETEXT", 63 | 14 , "CF_ENHMETAFILE", 64 | 15 , "CF_HDROP", 65 | 16 , "CF_LOCALE", 66 | 17 , "CF_DIBV5", ; 67 | 128 , "CF_OWNERDISPLAY", ; 68 | 129 , "CF_DSPTEXT", ; 69 | 130 , "CF_DSPBITMAP", ; 70 | 131 , "CF_DSPMETAFILEPICT", ; 71 | 142 , "CF_DSPENHMETAFILE", ; 72 | 512 , "CF_PRIVATEFIRST", 73 | 767 , "CF_PRIVATELAST", 74 | 768 , "CF_GDIOBJFIRST", 75 | 1023 ,"CF_GDIOBJLAST", 76 | ) 77 | ;dllcall("OpenClipboard", "Ptr",A_ScriptHwnd) ;【2018年3月18日】不打开这个,则下面的长度,无法获取,打开的话,可能造成一些脚本性能上的问题,刚放开就崩溃了,所以还是关了为好 78 | arr := [] 79 | for n, str in ClipboardForamts { 80 | if (dllcall("User32.dll\IsClipboardFormatAvailable", "UInt",n, "Int")) 81 | arr.push(n) 82 | } 83 | return arr 84 | } 85 | 86 | ;_Clipboard.isPic() 87 | static isPic() => dllcall("IsClipboardFormatAvailable", "UInt",8) && !dllcall("IsClipboardFormatAvailable", "UInt",13) 88 | static isFile() => dllcall("IsClipboardFormatAvailable", "UInt",15) 89 | ;static isHtml() { 90 | ; return dllcall("RegisterClipboardFormat", "str","HTML Format") 91 | ;} 92 | 93 | ;msgbox(json.stringify(_Clipboard.arrDate(), 4)) 94 | ;static arrDate() { 95 | ; ClipboardForamts := map( 96 | ; 1 , "CF_TEXT", ; 97 | ; 2 , "CF_BITMAP", ; 98 | ; 3 , "CF_METAFILEPICT", ; 99 | ; 4 , "CF_SYLK", 100 | ; 5 , "CF_DIF", 101 | ; 6 , "CF_TIFF", 102 | ; 7 , "CF_OEMTEXT", ; 103 | ; 8 , "CF_DIB", 104 | ; 9 , "CF_PALETTE", 105 | ; 10 , "CF_PENDATA", 106 | ; 11 , "CF_RIFF", 107 | ; 12 , "CF_WAVE", 108 | ; 13 , "CF_UNICODETEXT", 109 | ; 14 , "CF_ENHMETAFILE", 110 | ; 15 , "CF_HDROP", 111 | ; 16 , "CF_LOCALE", 112 | ; 17 , "CF_DIBV5", ; 113 | ; 128 , "CF_OWNERDISPLAY", ; 114 | ; 129 , "CF_DSPTEXT", ; 115 | ; 130 , "CF_DSPBITMAP", ; 116 | ; 131 , "CF_DSPMETAFILEPICT", ; 117 | ; 142 , "CF_DSPENHMETAFILE", ; 118 | ; 512 , "CF_PRIVATEFIRST", 119 | ; 767 , "CF_PRIVATELAST", 120 | ; 768 , "CF_GDIOBJFIRST", 121 | ; 1023 ,"CF_GDIOBJLAST", 122 | ; 49161 ,"CF_GDIOBJLAST", 123 | ; dllcall("RegisterClipboardFormat", "str","HTML Format"), "CF_HTML", 124 | ; dllcall("RegisterClipboardFormat", "Str","Link", "UInt"), "CF_LINK", 125 | ; dllcall("RegisterClipboardFormat", "Str","VimClipboard2", "UInt"), "CF_Vim", 126 | ; ) 127 | ; arr := [] 128 | ; n := 0 129 | ; oBuf := ClipboardAll() 130 | ; while (idFormat := NumGet(oBuf, n, "uint")) { 131 | ; size := NumGet(oBuf, n+4, "uint") 132 | ; if (ClipboardForamts.has(idFormat)) { 133 | ; if (ClipboardForamts[idFormat] == "CF_UNICODETEXT") 134 | ; arr.push([idFormat, ClipboardForamts[idFormat], strget(oBuf.ptr+n+4+A_PtrSize,-size, "utf-16")]) 135 | ; else 136 | ; arr.push([idFormat, ClipboardForamts[idFormat], strget(oBuf.ptr+n+4+A_PtrSize,-size, "CP0")]) 137 | ; } 138 | ; n += 4 + A_PtrSize + size 139 | ; } 140 | ; return arr 141 | ;} 142 | 143 | static clear() { 144 | if (!dllcall("OpenClipboard", "ptr",0)) 145 | return false 146 | dllcall("EmptyClipboard") 147 | dllcall("CloseClipboard") 148 | return true 149 | } 150 | 151 | static isEmpty() => !dllcall("CountClipboardFormats") 152 | 153 | } 154 | -------------------------------------------------------------------------------- /lib/Class_Date.ahk: -------------------------------------------------------------------------------- 1 | ;时间格式 2 | ; 时间戳 3 | ; YYMMDDHHmmss 4 | class _Date { 5 | static MonthInfo := "" 6 | static LeapType := "" 7 | static LeapMonth := "" 8 | static Chuyi := "" 9 | 10 | ;arr := [2024,1,2] 11 | __new(arr) { 12 | if (arr is ComObject) 13 | arr := arr.value 14 | if (arr is string) 15 | arr := StrSplit(arr, ["-","/"]) 16 | this.char := "-" 17 | this.year := arr[1] 18 | if (strlen(this.year) == 2) 19 | this.year := "20" . this.year 20 | this.month := arr[2] 21 | this.day := arr[3] 22 | this.value := format("{1}{2}{3}", this.year,this.month.zfill(2),this.day.zfill(2)) 23 | ;日期和月份的长度,有一个为1,则为1 24 | this.len := (strlen(this.month)==1 || strlen(this.day)==1) ? 1 : 2 25 | ;msgbox(this.value . "`n" . this.len) 26 | } 27 | 28 | ;天数 29 | ;_Date("2023-03-09").add(n) 30 | add(n:=1, tp:="days") { 31 | sDate := DateAdd(this.value . "000000", n, tp).left(8) 32 | res := format("{1}{4}{2}{4}{3}", substr(sDate,1,4),substr(sDate,5,2),substr(sDate,7,2),this.char) 33 | return res 34 | } 35 | 36 | ;会议时间:整10分,时长默认1小时 37 | ;比如 09:20-10:20 38 | static meetingTime() { 39 | m0 := integer(A_Min) 40 | h0 := integer(A_Hour) 41 | m := round(m0, -1) 42 | h := m0 > 55 ? string(h0+1) : h0 43 | return format("{1}:{2}-{3}:{2}", format("{:02s}",h),m,format("{:02s}",h+1)) 44 | } 45 | 46 | ;上1季度 47 | static lastQuarter(month:=0) { 48 | if (!month) 49 | month := integer(A_MM) 50 | ;当前季度 51 | res := mod(month-1, 3) + 1 52 | ;上一季度 53 | if (res > 1) 54 | return res - 1 55 | else 56 | return 4 57 | } 58 | 59 | ;转成 A_Now 的格式 60 | static toTime(t){ 61 | t := RegExReplace(t, "\D+") 62 | return t.addLeft(A_Now) 63 | } 64 | 65 | static daysOfMonth(year_Month) { ;计算某年某个月的天数,格式为201501 66 | if (StrLen(year_month) != 6) 67 | msgbox("参数:" . year_Month . "`n错误:计算月份天数的参数非6个数字") 68 | m := LTrim(substr(year_month,5), "0") 69 | a := map(1,31,3,31,4,30,5,31,6,30,7,31,8,31,9,30,10,31,11,30,12,31) 70 | if (a.has(m)) { 71 | return a[m] 72 | } else { 73 | if (this.isRunnian(substr(year_month, 1, 4))) 74 | return 29 75 | else 76 | return 28 77 | } 78 | } 79 | 80 | static daysOfJidu(str) { ;计算某年第N季度的天数,格式为20151 81 | if !(str ~= "^\d{4}[1-4]$") ;查看参数是否为5个数字并把季度赋值给jidu 82 | msgbox(format("参数{1}错误:计算季度天数的参数非5个数字(或季度>4)", str)) 83 | jidu := substr(str, strlen(str)) 84 | if (jidu > 1) 85 | return 91 + (jidu > 2) 86 | else if (this.isRunnian(substr(str, 1, 4))) ;闰年 87 | return 91 88 | else 89 | return 90 90 | } 91 | 92 | static getPrevMonthLastDay(day:="") => substr(DateAdd(A_Now, -day, "day"), 1, 8) ;通过日期(20191220)获取上月最后一天(8位数) 93 | 94 | static isZhouri(t) => (FormatTime(t,"WDay") == 1) ;获取星期几 ;判断是否周日(20180102这样的8位数字字符串) 95 | 96 | static isRunnian(year) { ;判断年份是否为闰年,是则返回1,否则0 97 | if (RegExMatch(year, "^\d{4}$") == 0) { 98 | msgbox("错误:年份有误,脚本退出",, "T1") 99 | return 100 | } 101 | return (mod(year, 400) == 0 || (mod(year, 4) == 0 && mod(year, 100) != 0)) 102 | } 103 | 104 | static weekDay() => instr("2345671", A_WDay) ;获取星期几(周一为1) 105 | 106 | static today(strFormat:="yyyy/MM/dd") => FormatTime(, strFormat) ;"2019/04/03" 107 | 108 | ;修改日期格式 109 | ;2013/2/3 -> 20130203 110 | ;分隔符:任意非数字的同个符号 111 | static toFormat(sDate, char:="") { 112 | arr := StrSplit(sDate, ["-","/","."]) 113 | if (arr.length <= 1) 114 | return 115 | res := arr[1] 116 | loop(2) { 117 | if (strlen(arr[A_Index+1]) == 1) 118 | arr[A_Index+1] := "0" . arr[A_Index+1] 119 | res .= char . arr[A_Index+1] 120 | } 121 | return res 122 | } 123 | 124 | static toNongli(t) { ;公历t(格式为20180101)转农历 125 | year := substr(t, 1, 4) ;获取年份 126 | if (year > 2100 || year < 1900) 127 | return false 128 | this.nongliAnalyze(year) 129 | newyear := year . this.Chuyi 130 | if (t < newyear) { ;NOTE 时间在上一年春节前,则读取上一年的农历 131 | year-- 132 | this.nongliAnalyze(year) 133 | } 134 | newyear := year . this.Chuyi 135 | month := substr(t, 5, 2) 136 | day := substr(t, 7, 2) 137 | ;获取时间和春节的距离 138 | d := t 139 | d := DateDiff(d, newyear, "days") 140 | d++ ;因为和春节(不是除夕)相减,所以要+1 141 | ;计算农历日期 142 | if (this.LeapMonth) ;有闰月则闰月插入到该月之后 143 | this.MonthInfo := substr(this.MonthInfo, 1, this.LeapMonth) . this.LeapType . substr(this.MonthInfo, this.LeapMonth + 1) 144 | loop(13) { 145 | thisDays := 29 + substr(this.MonthInfo, A_Index, 1) 146 | if (d > thisDays) { 147 | tmp := d 148 | d -= thisDays 149 | } else { 150 | LMonth := (this.LeapMonth && (A_Index > this.LeapMonth)) ? (A_Index - 1) : A_Index ;有闰月且循环次数大于闰月的月份 151 | break 152 | } 153 | } 154 | LMonth := format("{:02s}", LMonth) 155 | LDay := format("{:02s}", LDate) 156 | LDate := year . LMonth . LDay ;完成 157 | ;转换成习惯性叫法 158 | ;Tiangan=甲,乙,丙,丁,戊,已,庚,辛,壬,癸 159 | ;Dizhi=子,丑,寅,卯,辰,巳,午,未,申,酉,戌,亥 160 | ;Shengxiao=鼠,牛,虎,兔,龙,蛇,马,羊,猴,鸡,狗,猪 161 | ;loop,parse,Tiangan,`, 162 | ;Tiangan%A_Index%:=A_LoopField 163 | ;Dizhi%A_Index%:=A_LoopField 164 | ;loop,parse,Dizhi,`, 165 | ;loop,parse,Shengxiao,`, 166 | ;Shengxiao%A_Index%:=A_LoopField 167 | ;Order1:=mod((year-4),10)+1 168 | ;Order2:=mod((year-4),12)+1 169 | ;year:=Tiangan%Order1% . Dizhi%Order2% . "(" . Shengxiao%Order2% . ")" 170 | ;yuefen=正,二,三,四,五,六,七,八,九,十,十一,腊 171 | ;loop,parse,yuefen,`, 172 | ;yuefen%A_Index%:=A_LoopField 173 | ;LMonth:=yuefen%LMonth% 174 | ;rizi = 初一,初二,初三,初四,初五,初六,初七,初八,初九,初十,十一,十二,十三,十四,十五,十六,十七,十八,十九,二十,廿一,廿二,廿三,廿四,廿五,廿六,廿七,廿八,廿九,三十 175 | ;loop,parse,rizi,`, 176 | ;rizi%A_Index%:=A_LoopField 177 | ;LDay:=rizi%LDay% 178 | ;LDate=%year%年%LMonth%月%LDay% 179 | return LDate 180 | } 181 | 182 | ;bLeap 比如闰四月,bLeap 则表示第2个四月 183 | static toGongli(t, bLeap:=false) { 184 | ;分解农历年月日(不包含前面的0) 185 | year := substr(t, 1, 4) 186 | month := integer(substr(t, 5, 2)) 187 | day := integer(substr(t, StrLen(t)-1)) 188 | ;各种错误日期格式 189 | if (year>2100 || year<1900 || month>12 || month<1 || day>30 || day<1) 190 | return false 191 | ;获取农历详情日期 192 | this.nongliAnalyze(year) 193 | ;计算到当天到当年农历新年的天数 194 | Sum := 0 195 | if (this.LeapMonth) { ;有闰月 196 | ;生成新的月份信息,闰4月则插入this.LeapType到第4个数字后面 197 | thisMonthInfo := substr(this.MonthInfo, 1, this.LeapMonth) 198 | . this.LeapType 199 | . substr(this.MonthInfo, this.LeapMonth+1) 200 | if (this.LeapMonth!=month && bLeap) 201 | msgbox("该月不是闰月") 202 | ;month < this.LeapMonth ;不考勤闰月 203 | ;month > this.LeapMonth ;考勤闰月 204 | ;month = this.LeapMonth && bLeap ;考勤闰月 205 | if (month>this.LeapMonth || (month==this.LeapMonth && bLeap)) { 206 | loop(month) { 207 | thisMonth := substr(thisMonthInfo, A_Index, 1) 208 | Sum := Sum + 29 + thisMonth 209 | } 210 | } else { ;无视闰月 211 | loop(month-1) { 212 | thisMonth := substr(thisMonthInfo, A_Index, 1) 213 | Sum := Sum + 29 + thisMonth 214 | } 215 | } 216 | } else { 217 | loop(month-1) { 218 | thisMonthInfo := this.MonthInfo 219 | thisMonth := substr(thisMonthInfo, A_Index, 1) 220 | Sum := Sum + 29 + thisMonth 221 | } 222 | } 223 | GDate := DateAdd(year . this.Chuyi, Sum+day-1, "days") 224 | return substr(Gdate, 1, 8) 225 | } 226 | 227 | static nongliAnalyze(year) { ;获取t(格式为20160212)的农历所在年份数据 228 | this.MonthInfo := "" 229 | this.LeapType := "" 230 | this.LeapMonth := "" 231 | this.Chuyi := "" 232 | 233 | ;前三位,Hex,转Bin,表示当年每月的类型,1为大月(30天),0为小月(29天) 234 | ;第四位,Dec,表示闰月天数,1为大月(30天),0为小月(29天) 235 | ;第五位,Hex,转Dec,表示是否闰月,0为不闰,否则为闰月月份 236 | ;后两位,Hex,转Dec,表示当年正月初一公历日期,格式MMDD 237 | if (year > 2100 || year < 1900) 238 | return false 239 | a := map( 240 | "1899","AB500D2","1900","4BD0883","1901","4AE00DB","1902","A5700D0","1903","54D0581","1904","D2600D8","1905","D9500CC","1906","655147D","1907","56A00D5","1908","9AD00CA","1909","55D027A","1910","4AE00D2","1911","A5B0682","1912","A4D00DA","1913","D2500CE","1914","D25157E","1915","B5500D6","1916","56A00CC","1917","ADA027B","1918","95B00D3","1919","49717C9","1920","49B00DC","1921","A4B00D0","1922","B4B0580","1923","6A500D8","1924","6D400CD","1925","AB5147C","1926","2B600D5","1927","95700CA","1928","52F027B","1929","49700D2","1930","6560682","1931","D4A00D9","1932","EA500CE","1933","6A9157E","1934","5AD00D6","1935","2B600CC","1936","86E137C","1937","92E00D3","1938","C8D1783","1939","C9500DB","1940","D4A00D0","1941","D8A167F","1942","B5500D7","1943","56A00CD","1944","A5B147D","1945","25D00D5","1946","92D00CA","1947","D2B027A","1948","A9500D2","1949","B550781","1950","6CA00D9","1951","B5500CE","1952","535157F","1953","4DA00D6","1954","A5B00CB","1955","457037C","1956","52B00D4","1957","A9A0883","1958","E9500DA","1959","6AA00D0","1960","AEA0680","1961","AB500D7","1962","4B600CD","1963","AAE047D","1964","A5700D5","1965","52600CA","1966","F260379","1967","D9500D1","1968","5B50782","1969","56A00D9","1970","96D00CE","1971","4DD057F","1972","4AD00D7","1973","A4D00CB","1974","D4D047B","1975","D2500D3","1976","D550883","1977","B5400DA","1978","B6A00CF","1979","95A1680","1980","95B00D8","1981","49B00CD","1982","A97047D","1983","A4B00D5","1984","B270ACA","1985","6A500DC","1986","6D400D1","1987","AF40681","1988","AB600D9","1989","93700CE","1990","4AF057F","1991","49700D7","1992","64B00CC","1993","74A037B","1994","EA500D2","1995","6B50883","1996","5AC00DB","1997","AB600CF","1998","96D0580","1999","92E00D8","2000","C9600CD","2001","D95047C","2002","D4A00D4","2003","DA500C9","2004","755027A","2005","56A00D1","2006","ABB0781","2007","25D00DA","2008","92D00CF","2009","CAB057E","2010","A9500D6","2011","B4A00CB","2012","BAA047B","2013","AD500D2","2014","55D0983","2015","4BA00DB","2016","A5B00D0","2017","5171680","2018","52B00D8","2019","A9300CD","2020","795047D","2021","6AA00D4","2022","AD500C9","2023","5B5027A","2024","4B600D2","2025","96E0681","2026","A4E00D9","2027","D2600CE","2028","EA6057E","2029","D5300D5","2030","5AA00CB","2031","76A037B","2032","96D00D3","2033","4AB0B83","2034","4AD00DB","2035","A4D00D0","2036","D0B1680","2037","D2500D7","2038","D5200CC","2039","DD4057C","2040","B5A00D4","2041","56D00C9","2042","55B027A","2043","49B00D2","2044","A570782","2045","A4B00D9","2046","AA500CE","2047","B25157E","2048","6D200D6","2049","ADA00CA","2050","4B6137B","2051","93700D3","2052","49F08C9","2053","49700DB","2054","64B00D0","2055","68A1680","2056","EA500D7","2057","6AA00CC","2058","A6C147C","2059","AAE00D4","2060","92E00CA","2061","D2E0379","2062","C9600D1","2063","D550781","2064","D4A00D9","2065","DA400CD","2066","5D5057E","2067","56A00D6","2068","A6C00CB","2069","55D047B","2070","52D00D3","2071","A9B0883","2072","A9500DB","2073","B4A00CF","2074","B6A067F","2075","AD500D7","2076","55A00CD","2077","ABA047C","2078","A5A00D4","2079","52B00CA","2080","B27037A","2081","69300D1","2082","7330781","2083","6AA00D9","2084","AD500CE","2085","4B5157E","2086","4B600D6","2087","A5700CB","2088","54E047C","2089","D1600D2","2090","E960882","2091","D5200DA","2092","DAA00CF","2093","6AA167F","2094","56D00D7","2095","4AE00CD","2096","A9D047D","2097","A2D00D4","2098","D1500C9","2099","F250279","2100","D5200D1", 241 | ) 242 | this.Chuyi := format("{:04s}" ,(substr(a[year],-2).h2d())) 243 | ;通过前3位,获取当年月份,1为大月,0为小月 244 | this.MonthInfo := format("{:012s}", ("0x" . substr(a[year],1,3)).h2b()) 245 | ;获取闰月类型,1为大月30天,0为小月29天 246 | this.LeapType := substr(a[year], 4, 1) 247 | ;闰月的月份 248 | this.LeapMonth := format("0x{1}", substr(a[year],5,1)).h2d() 249 | } 250 | 251 | ;返回标准14位数的时间格式 252 | static timeAdd(tm0, n, tp:="seconds") { 253 | ;补全时间为14位数 254 | tm0 := this.toTime(tm0) 255 | return DateAdd(tm0, n, tp) 256 | } 257 | 258 | ;时间-时间计算 259 | static diff(t1, t2, tp:="minutes") => DateDiff(this.toTime(t1), this.toTime(t2), tp) 260 | 261 | ;-----------------------------------time----------------------------------- 262 | 263 | ;NOTE 时间点格式: 264 | ;1666353432 格式 unix 265 | ;19700101000000 格式 tt 266 | ;08:59:59.222 格式 time 267 | ;0.99956 格式 float 268 | static tt2unix(tt:=""){ 269 | if (tt == "") 270 | tt := A_Now 271 | return DateDiff(tt, "19700101000000", "seconds") 272 | } 273 | 274 | ;时间戳转 tt 275 | static unix2tt(ms)=> DateAdd("19700101000000", ms, "seconds") 276 | 277 | ;小数点时间转成xx:xx:xx.xxx 278 | ;0.99956 -> 23:59:22 279 | static timeFromFloat(tm) { 280 | if (tm == 0) 281 | return 0 282 | ms := 24*60*60*1000 * tm 283 | return this.timeFromMs(ms) 284 | } 285 | 286 | ;3610590 →01:00:10.590 287 | ;tp 1=显示毫秒 0=直接删除毫秒 NOTE -1=根据毫秒四舍五入 288 | static timeFromMs(ms:=0, tp:=0) { 289 | if !(ms is integer) 290 | throw TypeError(format("type(ms) = {1}", type(ms))) 291 | OutputDebug(format("i#{1} {2}:ms={3}", A_LineFile,A_LineNumber,ms)) 292 | if (tp==-1 && mod(ms,1000)>=500) 293 | ms += 500 294 | arr := [] 295 | loop 3 296 | arr.push(floor(mod((ms / (1000 * 60**(3-A_Index))),60))) 297 | ;OutputDebug(format("i#{1} {2}:arr={3}", A_LineFile,A_LineNumber,json.stringify(arr,4))) 298 | if (tp == 1) { 299 | arr.push(mod(ms, 1000)) 300 | return format("{:02}:{:02}:{:02}.{:03}",arr*) 301 | } else { 302 | return format("{:02}:{:02}:{:02}",arr*) 303 | } 304 | } 305 | 306 | ;xx:xx:xx.xxx 时间转成秒数(小数点) 307 | ;this.time2second("01:10:01") 308 | static time2second(t) { 309 | if (t == "") 310 | return 0 311 | if !instr(t, ":") 312 | return t 313 | tp := instr(t, "-") ;负数 314 | t := ltrim(t, "-") 315 | ;t := RegExReplace(t, ":|:") 316 | arr := StrSplit(t, ".") ;判断有没有毫秒 317 | b := StrSplit(arr[1], ":") 318 | res := 0 319 | loop(b.length) { 320 | ;OutputDebug(format("i#{1} {2}:thisSecond={3}", A_LineFile,A_LineNumber,b[-A_Index] * (60**(A_Index-1)))) 321 | res += b[-A_Index] * (60**(A_Index-1)) 322 | } 323 | ;OutputDebug(format("i#{1} {2}:res={3}", A_LineFile,A_LineNumber,res)) 324 | res := (arr.length > 1) ? round(res+arr[2]/1000, 3) : res 325 | ;OutputDebug(format("i#{1} {2}:res={3}", A_LineFile,A_LineNumber,res)) 326 | if (tp) 327 | res := 0 - res 328 | return res 329 | } 330 | 331 | static getRebootTime() => DateAdd(A_Now, -A_TickCount//1000, "seconds") ;获取重启时间 332 | 333 | ;A_TickCount 格式的时间差 334 | static tc2time(tc) => FormatTime(DateAdd(A_Now, -(A_TickCount-tc)//1000, "s"), "yyyy-MM-dd HH:mm:ss") 335 | 336 | } 337 | ;msgbox(_Date.timeFromFloat(0.99956)) 338 | ;msgbox(_Date.timeFromFloat(9)) 339 | ;msgbox(_Date.time2second("01:10")) 340 | ;msgbox(_Date.timeFromMs(3610590, 1)) 341 | -------------------------------------------------------------------------------- /lib/Class_Gui.ahk: -------------------------------------------------------------------------------- 1 | class _Gui { 2 | 3 | ; https://www.autohotkey.com/boards/viewtopic.php?f=82&t=131752 4 | static getTextWidth(ctrl, arrText) { 5 | hFont := SendMessage(0x31,,, ctrl) ;WM_GETFONT 6 | hDC := dllcall("GetDC", "Ptr", 0, "Ptr") 7 | hObj := dllcall("SelectObject", "Ptr", hDC, "Ptr", hFont, "Ptr") 8 | res := [] 9 | for text in arrText { 10 | height := dllcall("DrawText", "Ptr",hDC, "Str",text, "Int",strlen(text), "Ptr",RECT:=buffer(16), "UInt",0x400) ;DT_CALCRECT=0x400 11 | res.push(numget(RECT, 8, "UInt") - numget(RECT, "UInt")) 12 | } 13 | dllcall("SelectObject", "Ptr", hDC, "Ptr", hObj, "Ptr") 14 | dllcall("ReleaseDC", "Ptr", 0, "Ptr", hDC) 15 | return res 16 | } 17 | 18 | static lv_show(arr2) { 19 | oGG := _Gui(arr2) 20 | ;oGG.callback := (p*)=>msgbox(json.stringify(p, 4)) 21 | ;oGG.title := doctype 22 | ;oGG.setFontSize(11) 23 | ;oGG.setWidth(1600) 24 | oGG.data_arr2() 25 | oGG.ListViewEx(300) 26 | oGG.lv_bindClick() 27 | return oGG 28 | } 29 | 30 | static play() { 31 | oGui := gui("-caption +AlwaysOnTop +Border +E0x80000 +LastFound +ToolWindow") 32 | oGui.OnEvent("escape", doEscape) 33 | oGui.OnEvent("close", doEscape) 34 | oGui.SetFont("cRed s22") 35 | oGui.MarginX := 0 36 | oGui.MarginY := 0 37 | oVideo := oGui.AddActiveX("x0 y0 w960 h600", "WMPLayer.ocx") 38 | oVideo.value.url:="e:\Video\大自然在说话(合集).mp4" 39 | oVideo.value.uiMode := "full" ; no WMP controls播放器界面模式,可为Full, Mini, None, Invisible 40 | oVideo.value.stretchToFit := 1 ; video is streched to the given activex range 41 | oVideo.value.enableContextMenu := 1 ; 右键菜单 42 | oVideo.value.settings.setMode("loop", true) 43 | oGui.show() 44 | doEscape(oGui, p*) => oGui.destroy() 45 | } 46 | 47 | ;TODO 48 | ; mouseAnalyze() { 49 | ; oGui := gui("+AlwaysOnTop +ToolWindow -caption") 50 | ; oGui.AddText("section x20", "xWin") 51 | ; oGui.AddText("ys", "yWin") 52 | ; oGui.AddText("ys", "xScreen") 53 | ; oGui.AddText("ys", "yScreen") 54 | ; oGui.AddText("ys", "cl") 55 | ; oGui.show() 56 | ; } 57 | 58 | ;getColor() { ; TODO 59 | ; oGui := gui("+AlwaysOnTop +ToolWindow -caption") 60 | ; oGui.OnEvent("escape",doEscape) 61 | ; ;oGui.color 62 | ;} 63 | 64 | ;转成没层级的数组,只包含key和val 65 | static hyobj2arr(obj, mark:=1) { 66 | arr := [] 67 | for v in obj { 68 | if (v["key"] == "") ;首先过滤key为空的 NOTE 69 | continue 70 | if (isobject(v.sub)) { ;为目录 71 | for v in A_ThisFunc(v.sub, 0) 72 | arr.push(v) 73 | } else 74 | arr.push([v["key"], v.val]) 75 | } 76 | return arr 77 | } 78 | 79 | ;示例见 en.meta_fields() 80 | __new(data) { 81 | this.data := deepclone(data) 82 | this.title := "" 83 | this.res := [] 84 | this.rs := 40 ;默认行数 85 | this.setFontSize(13) 86 | this.setWidth(1200, []) 87 | this.querys := map() 88 | ;switch tp { 89 | ;case "lvcopy": 90 | ; this.headers := [] 91 | ; this.data_arr2() 92 | ; if (!this.headers.length) 93 | ;default: 94 | ;} 95 | } 96 | 97 | createGui(opts:="") { 98 | switch opts { 99 | case "": opts := "+resize +AlwaysOnTop +Border +LastFound +ToolWindow" 100 | } 101 | oGui := gui(opts) 102 | ;oGui.BackColor := "222222" 103 | oGui.title := this.title 104 | oGui.MarginX := 20 105 | oGui.MarginY := 20 106 | oGui.SetFont(format("s{1}", this.fontsize)) 107 | oGui.OnEvent("escape", ObjBindMethod(this,"do_gui_destroy")) 108 | return oGui 109 | } 110 | 111 | ;复制用 112 | ;单击复制单元格 113 | ListViewEx(maxWidth:=300) { 114 | oGui := this.createGui() 115 | this.oLV := oGui.AddListView(format("VScroll count grid w{1} r{2}", this.w-50,this.rs+2), this.headers) 116 | this.oLV.header := SendMessage(0x101F,,, this.oLV.hwnd) ; LVM_GETHEADER - get hWnd of Header control 117 | dllcall("UxTheme.dll\SetWindowTheme", "ptr",this.oLV.hwnd, "WStr","Explorer", "Ptr",0) ; Set 'Explorer' theme 118 | ControlSetStyle("^0x100", this.oLV.Header) ; toggle the HDS_FILTERBAR style 119 | this.oLV.OnNotify(-312, ObjBindMethod(this,"do_lv_FilterChange")) ; HDN_FILTERCHANGE 120 | this.oLV.name := "lv" 121 | this.lv_addData(maxWidth) ;NOTE 122 | oGui.AddButton("default center section", "确定").OnEvent("click", ObjBindMethod(this,"do_btn_click")) 123 | oGui.AddButton("center ys", "复制当前表格内容").OnEvent("click", ObjBindMethod(this,"do_btn_copy")) 124 | ;oGui.AddButton("center ys", "全选").OnEvent("click", ObjBindMethod(this,"do_btn_lv_select_all")) 125 | ;oGui.AddButton("center ys", "全不选").OnEvent("click", ObjBindMethod(this,"do_btn_lv_select_none")) 126 | oGui.AddStatusBar(, format("共有{1}项结果", this.data.length)) 127 | oGui.show(format("w{1} center", this.w)) 128 | this.LVICE := LVICE_XXS(this.oLV) 129 | } 130 | 131 | lv_addData(maxWidth:=300) { 132 | this.data_querys() 133 | this.oLV.opt("-Redraw") 134 | this.oLv.delete() 135 | for arr in this.data1 136 | this.oLV.add(, arr*) 137 | ;设置宽度 138 | cntCol := this.oLV.GetCount("column") 139 | if (this.widths.length) { 140 | loop(cntCol) { 141 | if this.widths is integer 142 | w := this.widths 143 | else if A_Index <= this.widths.length 144 | w := this.widths[A_Index] 145 | else 146 | w := 100 147 | this.oLV.ModifyCol(A_Index, w) 148 | } 149 | } else { 150 | ;this.oLV.ModifyCol(A_Index, 1000//cntCol) 151 | loop(cntCol) 152 | this.oLV.ModifyCol(A_Index, "AutoHdr") 153 | ;TODO 调整过长的列 154 | arrWidth := [] 155 | loop(this.oLV.GetCount("column")) 156 | arrWidth.push(SendMessage(0x1000+29,A_Index-1,,, this.oLV)) 157 | OutputDebug(format("i#{1} {2}:{3} arrWidth={4}", A_LineFile.fn(),A_LineNumber,A_ThisFunc,json.stringify(arrWidth))) 158 | for width in arrWidth { 159 | if (width > maxWidth) 160 | this.oLV.ModifyCol(A_Index, maxWidth) 161 | } 162 | } 163 | this.oLV.opt("+Redraw") 164 | } 165 | 166 | data_querys() { 167 | OutputDebug(format("i#{1} {2}:{3} querys={4}", A_LineFile,A_LineNumber,A_ThisFunc,json.stringify(this.querys,4))) 168 | if (!this.querys.count) { 169 | this.data1 := this.data 170 | return 171 | } 172 | this.data1 := [] 173 | for arr in this.data { 174 | for i, v in this.querys { 175 | ;OutputDebug(format("i#{1} {2}:{3} arr[{4}]={5} v={6}", A_LineFile,A_LineNumber,A_ThisFunc,i,arr[i],v)) 176 | if (isobject(arr[i]) || !instr(arr[i],v)) 177 | continue 2 178 | } 179 | this.data1.push(arr) 180 | } 181 | ;OutputDebug(format("i#{1} {2}:{3} this.data1={4}", A_LineFile,A_LineNumber,A_ThisFunc,json.stringify(this.data1,4))) 182 | } 183 | 184 | lv_bindClick(method:="Click") { 185 | this.oLV.OnNotify(-2, ObjBindMethod(this.LVICE,method)) 186 | } 187 | 188 | lv_bindDoubleClick(method:="DoubleClick") { 189 | this.oLV.OnNotify(-3, ObjBindMethod(this.LVICE,method)) 190 | } 191 | 192 | setFontSize(size) { 193 | this.fontsize := size 194 | } 195 | 196 | setWidth(w, widths:=unset) { 197 | this.w := w.fromDPI() 198 | if (isset(widths)) { ;百分比 199 | this.widths := widths.map((p)=>this.w*p//100) 200 | } else { 201 | this.widths := [] 202 | } 203 | } 204 | 205 | ;处理arr1或ao 206 | data_arr2(min_rs:=40) { 207 | if (this.data is array) { 208 | ;this.rs := this.data.length 209 | if (!this.data.length) 210 | return 211 | if !(this.data[1] is array) { 212 | if (!isobject(this.data[1])) { 213 | this.headers := ["field"] 214 | for v in this.data 215 | this.data[A_Index] := [v] 216 | } else if (this.data[1] is map) { ;NOTE map 217 | ;获取标题 218 | this.headers := this.data[1].keys() 219 | ;map转数组 220 | for obj in this.data { 221 | this.data[A_Index] := obj.values() 222 | } 223 | } 224 | } else { ;正规数据 225 | this.headers := this.data[1].map((v,k)=>"f" . k) 226 | } 227 | } else if (this.data is map) { 228 | this.headers := ["key", "value"] 229 | ;this.rs := this.data.count 230 | this.data := this.data.items() 231 | } 232 | ;if this.rs < min_rs 233 | ;this.rs := min_rs 234 | } 235 | 236 | ;-----------------------------------event_do----------------------------------- 237 | do_gui_destroy(oGui) { 238 | oGui.destroy() 239 | if (this.HasOwnProp("callback")) 240 | this.callback(this.res) ;NOTE 结束后回调 241 | } 242 | 243 | ;do_gui_escape(oGui) { 244 | ; this.do_gui_destroy(oGui) 245 | ;} 246 | 247 | do_lv_FilterChange(oLV, LParam) { 248 | static NMHDR_Size := A_PtrSize * 3, 249 | TypeOffset := (4 * 6) + (A_PtrSize * 3), 250 | FilterOffset := TypeOffset + A_PtrSize 251 | FilterText := "" 252 | FilterTextLength := 256 253 | i := numget(LParam, NMHDR_Size, "int") ; get the current column index 254 | VarSetStrCapacity(&FilterText, FilterTextLength) ; String buffer for HDTEXTFILTER struct 255 | HDTEXTFILTER := buffer(A_PtrSize * 2, 0) ; HDTEXTFILTER struct ----------------------------------- 256 | numput("Ptr", strptr(FilterText), HDTEXTFILTER, 0) ; add pointer to string buffer variable 257 | numput("int", FilterTextLength, HDTEXTFILTER, A_PtrSize) ; buffer size 258 | HDItem := buffer((4*6) + (A_PtrSize*6), 0) ; HDITEM struct ----------------------------------------- 259 | numput("uint", 0x100 , HDItem, 0) ; Set the Mask to HDI_FILTER := 0x100 260 | numput("uint", 0x0 , HDItem, TypeOffset) ; Set the type to HDFT_ISSTRING := 0x0 261 | numput("Ptr" , HDTEXTFILTER.Ptr, HDItem, FilterOffset) ; add pointer to HDTEXTFILTER struct 262 | ; send message to get the item 263 | SendMessage(0x120B, i, HDItem.Ptr, oLV.Header) ; HDM_GETITEM = 0x120B 264 | VarSetStrCapacity(&FilterText, -1) ; update the internally-stored string length 265 | i += 1 266 | if (FilterText == "") 267 | this.querys.delete(i) 268 | else 269 | this.querys[i] := FilterText 270 | this.lv_addData() 271 | ;tooltip(LParam "column index - " i "`n`nFilter = " FilterText) 272 | } 273 | 274 | do_btn_click(ctl, param*) { 275 | oLV := ctl.gui["lv"] 276 | this.res := [] 277 | loop(oLV.GetCount()) { 278 | if (SendMessage(0x102C, A_Index-1, 0x2000, oLV.hwnd)) { 279 | r := A_Index 280 | this.res.push([]) 281 | loop(oLv.GetCount("column")) 282 | this.res[-1].push(oLv.GetText(r, A_Index)) 283 | } 284 | } 285 | this.do_gui_destroy(ctl.gui) 286 | } 287 | 288 | do_btn_copy(ctl, param*) { 289 | A_Clipboard := "`n".join(this.data1, A_Tab) 290 | tooltip(A_Clipboard) 291 | SetTimer(tooltip, -3000) 292 | } 293 | 294 | do_btn_lv_select_all(ctl, p*) { 295 | oLV := ctl.gui["lv"] 296 | loop(oLV.GetCount()) 297 | oLV.modify(A_Index, "+check") 298 | } 299 | 300 | do_btn_lv_select_none(ctl, p*) { 301 | oLV := ctl.gui["lv"] 302 | loop(oLV.GetCount()) 303 | oLV.modify(A_Index, "-check") 304 | } 305 | 306 | } 307 | -------------------------------------------------------------------------------- /lib/Class_Map.ahk: -------------------------------------------------------------------------------- 1 | ;不修改原obj 2 | ;优先使用Arrays 3 | ;连续的 key 保存在 arr里,如何用arr:=["a","b"]一次性引用=obj["a"]["b"] 4 | ;自带了get(key, default) 5 | ;TODO CDP返回的map,key有顺序,ahk如何实现? 6 | 7 | ;v1 https://www.autohotkey.com/board/topic/83081-ahk-l-customizing-object-and-array 8 | defprop := object.DefineProp.bind(map.prototype) 9 | proto := _Map.prototype 10 | for k in proto.OwnProps() { 11 | if (k != "__Class") 12 | defprop(k, proto.GetOwnPropDesc(k)) 13 | } 14 | 15 | class _Map extends map { 16 | ;CaseSense := 0 17 | ;Default := "" 18 | 19 | ; i同array的序号 20 | _index(isValue:=0, i:=-1) { 21 | e := this.__enum() 22 | i := (i < 0) ? this.count-1+i : i-2 23 | numput("uint", i, objptr(e), 6*A_PtrSize+16) 24 | e(&k:=0) 25 | return isValue ? this[k] : k 26 | } 27 | 28 | ;参考python 29 | ;如果指定了i,则返回对应序号的k 30 | ;TODO 如何自定义顺序 31 | keys(i:=unset) { 32 | if (isset(i)) 33 | return this._index(0, i) 34 | arr := [] 35 | for k, v in this 36 | arr.push(k) 37 | return arr 38 | } 39 | values(i:=unset) { 40 | if (isset(i)) 41 | return this._index(1, i) 42 | arr := [] 43 | for k, v in this 44 | arr.push(v) 45 | return arr 46 | } 47 | 48 | ;转成二维数组arr2(参考python) 49 | items() { 50 | arr := [] 51 | for k, v in this 52 | arr.push([k, v]) 53 | return arr 54 | } 55 | 56 | ;通过数组来获取值 57 | getEx(arr, default:="") { 58 | if !(arr is array) 59 | arr := [arr] 60 | if (!arr.length) 61 | return this 62 | obj := this 63 | for v in arr { 64 | if (obj.has(v)) { 65 | obj := obj[v] 66 | } else { 67 | obj := default 68 | break 69 | } 70 | } 71 | return obj 72 | } 73 | 74 | ;TODO CDP返回的map,key有顺序,ahk如何实现? 75 | cloneEx() { 76 | obj := map() 77 | for k, v in this 78 | obj[k] := v 79 | return obj 80 | } 81 | 82 | ;遍历 arr,看是否包含 83 | hasEx(arr) { 84 | for v in arr { 85 | if this.has(v) 86 | return v 87 | } 88 | return "" 89 | } 90 | ;obj的值覆盖this 91 | ;名称来源python 92 | update(obj) { 93 | for k, v in obj 94 | this[k] := v 95 | return this 96 | } 97 | 98 | ;添加 obj 中不存在的键 99 | extend(obj) { 100 | for k, v in obj { 101 | if (!this.has(k)) 102 | this[k] := v 103 | } 104 | return this 105 | } 106 | 107 | toString() => super.toJson() 108 | 109 | ;arrKey 分别为 key [前,后,每个key之间]的符号定义 110 | ;arrValue 分别为 value [前,后]的符号定义 111 | /* 112 | 比如 obj := map( 113 | "a", [a1,a2], 114 | "b", [b3,b4], 115 | ) 116 | 转成 2 级markdown,并每个 key 中间插入空行 117 | obj.toStringEx(["## ","`n","`n"], ["### ","`n"]) 118 | 结果: 119 | ## a 120 | ### a1 121 | ### a2 122 | 123 | ## b 124 | ### b3 125 | ### b4 126 | */ 127 | ;NOTE 暂不支持嵌套 128 | toStringEx(arrKey, arrValue) { 129 | res := "" 130 | for title, arr in this { 131 | res .= format("{1}{2}{3}", arrKey[1],title,arrKey[2]) 132 | if (arr is array) { 133 | for v in arr 134 | res .= format("{1}{2}{3}", arrValue[1],v,arrValue[2]) 135 | } else { 136 | res .= format("{1}{2}{3}", arrValue[1],arr,arrValue[2]) 137 | } 138 | (arrKey.length > 2) && res .= arrKey[3] 139 | } 140 | return rtrim(res, arrKey[3]) 141 | } 142 | 143 | ;转成表格字符串(方便查看) 144 | ;数据结构要统一 145 | /* 146 | obj := map( 147 | "a",[1,2], 148 | "b",[3,4], 149 | ) 150 | */ 151 | toTable(charItem:="`t") { 152 | obj := this 153 | if (!obj.count) 154 | return "" 155 | res := "" 156 | ;记录第1项的类型 157 | for k, v in obj { 158 | isArray := v is array 159 | break 160 | } 161 | if (isArray) { 162 | for k, arr1 in obj { 163 | res .= k 164 | for v in arr1 165 | res .= charItem . v 166 | res := rtrim(res, charItem) . "`n" 167 | } 168 | } else { 169 | for k, v in obj { 170 | res .= format("{1}{2}{3}`n", k,charItem,v) 171 | } 172 | } 173 | return rtrim(res, charItem) . "`n" 174 | } 175 | 176 | filter(fun) { 177 | objRes := map() 178 | for k, v in this { 179 | if (fun(k, v)) 180 | objRes[k] := v 181 | } 182 | return objRes 183 | } 184 | 185 | ;简单键值对的比较 186 | ;tp 1=值不同,返回 key, [v0, v1] 187 | ;tp 0=键不同,返回 arrKey 188 | ;obj0放前面 189 | ;另见 IUIAutomationElement.compareUIE() 190 | compare(obj0, tp:=1) { 191 | obj0.default := "" 192 | obj1 := this 193 | obj1.default := "" 194 | objRes := map() 195 | switch tp { 196 | case 0: 197 | for k, v in obj1 { 198 | if (!obj0.has(k)) 199 | objRes[k] := 1 200 | } 201 | for k, v in obj0 { ;记录 obj0 中独有的内容 202 | if (v != obj1[k] && !objRes.has(k)) 203 | objRes[k] := [v, "null"] 204 | } 205 | case 1: 206 | for k, v in obj1 { 207 | if (v != obj0[k]) 208 | objRes[k] := [obj0[k], v] 209 | } 210 | for k, v in obj0 { ;记录 obj0 中独有的内容 211 | if (v != obj1[k] && !objRes.has(k)) 212 | objRes[k] := [v, "null"] 213 | } 214 | } 215 | return (objRes.count) ? objRes : map() 216 | objToTable(obj, charItem:="`t") { 217 | if (!obj.count) 218 | return "" 219 | res := "" 220 | ;记录第1项的类型 221 | for k, v in obj { 222 | tp := type(v) 223 | break 224 | } 225 | if (tp == "Array") { 226 | for k, arr1 in obj { 227 | res .= k 228 | for v in arr1 229 | res .= charItem . v 230 | res := rtrim(res, charItem) . "`n" 231 | } 232 | } 233 | return res 234 | } 235 | } 236 | 237 | toSqlWhere(tp:="=") { 238 | sWhere := "" 239 | for k, v in this { 240 | if (type(v) == "String") 241 | v := format("'{1}'", v) 242 | sWhere .= format("{1} {2} {3} and", k,v) 243 | } 244 | return substr(sWhere, 1, strlen(sWhere)-4) ;删除末尾 and 245 | } 246 | 247 | deleteEmpty() { 248 | objRes := this.clone() 249 | arr := [] 250 | for k, v in objRes { 251 | if (v == "") 252 | arr.push(k) ;直接objRes.delete会使得遍历出错 253 | } 254 | for k, v in arr 255 | objRes.delete(v) 256 | return objRes 257 | } 258 | 259 | deleteFalse(obj) { ;删除对象中的false内容(包括0) 260 | arr := [] 261 | for k, v in obj { 262 | if (!v) 263 | arr.push(k) ;直接obj.delete会使得遍历出错 264 | } 265 | objRes := obj.clone() 266 | for k, v in arr 267 | objRes.delete(v) 268 | return obj 269 | } 270 | 271 | path2obj(obj, path) { ;返回对象的字符串路径对应的对象 272 | path := RegExReplace(path, "(\[|\]|\.)+", "|") 273 | ps := StrSplit(trim(path,"|"), "|") 274 | ;ps := StrSplit(trim(path,"[]."), ["[", "].", "][", "]", "."]) 275 | for k, v in ps 276 | obj := obj[v] 277 | return obj 278 | } 279 | 280 | ;第一个k为基准,数字+1,统计缺少的项 281 | ;如果少4,5,6,7则合并成4-7 282 | ;Arrays有同名方法 283 | joinNumLost(joinNum:=true) { 284 | numSave := "" 285 | for k, _ in this { 286 | numSave := k 287 | break 288 | } 289 | if (numSave == "") 290 | return 291 | arrRes := [] ;记录缺失数字 292 | for k, _ in this { 293 | if (A_Index == 1){ 294 | numSave := k 295 | continue 296 | } else if (k > numSave + 1) { 297 | cha := k-numSave 298 | if (cha == 2) 299 | arrRes.push(numSave+1) 300 | else { 301 | if (joinNum) { 302 | arrRes.push((numSave+1) . "-" . (k-1)) 303 | } else { 304 | loop(cha-1) 305 | arrRes.push(numSave+A_Index) 306 | } 307 | } 308 | numSave := k 309 | } 310 | } 311 | ;msgbox(json.stringify(arrRes, 4)) 312 | return arrRes 313 | } 314 | 315 | } 316 | 317 | -------------------------------------------------------------------------------- /lib/Class_Mouse.ahk: -------------------------------------------------------------------------------- 1 | ;NOTE 坐标小数点处理见 hyf_offsetRect,本库只处理 CoordMode 2 | ;moveR 坐标进行 .toDPI 来兼容 3 | ;NOTE 光标用 CaretGetPos 获取 4 | ;修改光标见 _IME.curModify() 5 | ;鼠标点击和移动类 6 | 7 | class _Mouse { 8 | static bMoving := false 9 | static speed := 1 10 | static xySave := [] 11 | 12 | static save(md:="screen") { 13 | cmMouse := A_CoordModeMouse 14 | CoordMode("mouse", md) 15 | MouseGetPos(&xSave, &ySave) 16 | _Mouse.xySave := [xSave, ySave] 17 | CoordMode("mouse", cmMouse) 18 | return _Mouse.xySave 19 | } 20 | 21 | static back(cnt:=unset) { 22 | if (!_Mouse.xySave.length) 23 | return 24 | cmMouse := A_CoordModeMouse 25 | CoordMode("mouse", "screen") 26 | MouseMove(_Mouse.xySave[1], _Mouse.xySave[2], 0) 27 | if (isset(cnt)) { 28 | if (cnt is integer) { 29 | sleep(10) 30 | click(cnt) 31 | } else { 32 | MouseGetPos(,, &hwndMouse) 33 | WinActivate(hwndMouse) 34 | } 35 | } 36 | CoordMode("mouse", cmMouse) 37 | } 38 | 39 | ;获取鼠标当前的速度 40 | static saveSpeed() { 41 | dllcall("SystemParametersInfo", "UInt",SPI_GETMOUSESPEED:=0x70, "UInt",0, "Ptr*",&OrigMouseSpeed:=0, "UInt",0) 42 | return OrigMouseSpeed 43 | ; 现在在倒数第二个参数中设置较低的速度 (范围为 1-20, 10 是默认值): 44 | ;dllcall("SystemParametersInfo", "UInt",SPI_SETMOUSESPEED, "UInt",0, "Ptr",3, "UInt",0) 45 | ;dllcall("SystemParametersInfo", "UInt",SPI_SETMOUSESPEED:=0x71, "UInt",0, "Ptr",OrigMouseSpeed, "UInt",0) ; 恢复原始速度 46 | } 47 | 48 | static getMousePercent(n:=3) { 49 | cmMouse := A_CoordModeMouse 50 | CoordMode("Mouse", "screen") 51 | MouseGetPos(&x0, &y0, &hwnd) 52 | CoordMode("Mouse", cmMouse) 53 | WinGetPos(&winX, &winY, &winW, &winH, hwnd) 54 | ;OutputDebug(format("i#{1} {2}:{3} x0={4},winX={5},winW={6}", A_LineFile.fn(),A_LineNumber,A_ThisFunc,x0,winX,winW)) 55 | x0P := round((x0-winX) / winW, n) 56 | y0P := round((y0-winY) / winH, n) 57 | return [x0P, y0P] 58 | } 59 | 60 | ;主要用来生成代码 61 | ;如果偏右/下,则返回负数 62 | ;xPercent=100则不会获取相对坐标 63 | static getXYByWindowR(arrXY:="", xPercent:=80, yPercent:=80, winTitle:="") { 64 | cmMouse := A_CoordModeMouse 65 | CoordMode("mouse", "window") 66 | WinGetPos(&x, &y, &w, &h, winTitle) 67 | if (isobject(arrXY)) { 68 | x := arrXY[1] 69 | y := arrXY[2] 70 | } 71 | if (x > w * xPercent/100) 72 | x := x - w 73 | if (y > h * yPercent/100) 74 | y := y - h 75 | CoordMode("mouse", cmMouse) 76 | return [x, y] 77 | } 78 | 79 | ;获取当前鼠标的Window坐标 80 | static getXYByWindow(&x, &y) { 81 | cmMouse := A_CoordModeMouse 82 | CoordMode("mouse", "window") 83 | MouseGetPos(&x, &y) 84 | CoordMode("mouse", cmMouse) 85 | return [x, y] 86 | } 87 | 88 | ;获取当前鼠标的screen坐标 89 | static getXYByScreen(&x:=0, &y:=0) { 90 | cmMouse := A_CoordModeMouse 91 | CoordMode("mouse", "screen") 92 | MouseGetPos(&x, &y) 93 | CoordMode("mouse", cmMouse) 94 | return [x, y] 95 | } 96 | 97 | ;target 0=鼠标所在屏幕 1=另一屏幕 98 | ;获取w,h形状居中显示的起始x,y坐标 99 | static getPosOfRect(w, h, target:=0) { 100 | CoordMode("mouse", "screen") 101 | MouseGetPos(&xMouse, &yMouse) 102 | ;OutputDebug(format("i#{1} {2}:{3} xMouse={4} sysget(0)={5} sysget(78)={6}", A_LineFile,A_LineNumber,A_ThisFunc,xMouse,sysget(0),sysget(78))) 103 | if ((xMouse < sysget(0)) != target) { ;主屏 104 | x := sysget(0)//2 105 | y := sysget(1)//2 106 | ;OutputDebug(format("i#{1} {2}:{3} to main x={4} y={5}", A_LineFile,A_LineNumber,A_ThisFunc,x,y)) 107 | } else { 108 | x := (sysget(78)+sysget(0))//2 - w//2 109 | y := sysget(79)//2 - h//2 110 | ;OutputDebug(format("i#{1} {2}:{3} to second x={4} y={5}", A_LineFile,A_LineNumber,A_ThisFunc,x,y)) 111 | } 112 | ;if (yMouse > sysget(0)) { 113 | ; if (target) 114 | ; x := sysget(0)//2 115 | ; else 116 | ; x := sysget(78)//2 - w//2 117 | ;} 118 | ;if ((yMouse < A_ScreenHeight) == target) 119 | ; y := A_ScreenHeight//2 - h//2 120 | ;else 121 | ; y := (sysget(79)+A_ScreenHeight)//2 - h//2 122 | return [x, y] 123 | } 124 | 125 | ;鼠标所在的文本 126 | static mouseText() { 127 | ;return IUIAutomationElement(IUIAutomation().ElementFromPoint()).CurrentName() 128 | ; oAcc := Acc_ObjectFromPoint(child, x, y) 129 | ; try 130 | ; return oAcc.accValue(child) 131 | ; try 132 | ; return oAcc.accName(child) 133 | } 134 | 135 | ;笔直移动 to 为4个方向 up/down/left/right 136 | ;this.shiftMove("right", (p*)=>GetKeyState("CapsLock", "P"), (p*)=>send("{LButton down}"), (p*)=>send("{LButton up}")) 137 | static shiftMove(to:="right", funState:="", funBefore:="", funAfter:="") { 138 | if (this.bMoving) { 139 | this.speed++ 140 | tooltip(this.speed) 141 | SetTimer(tooltip, -1000) 142 | return 143 | } else 144 | this.speed := 1 145 | if (!this.bMoving && isobject(funBefore)) 146 | funBefore.call() 147 | this.bMoving := true 148 | SetTimer(%to . "Move"%, -1) ;用 SetTimer 解放当前线程,再次运行可增加速度 149 | rightMove() { 150 | while (funState.call()) { 151 | MouseMove(this.speed, 0, 0, "R") 152 | sleep(1) 153 | } 154 | after() 155 | } 156 | leftMove() { 157 | while (funState.call()) { 158 | MouseMove(-this.speed, 0, 0, "R") 159 | sleep(1) 160 | } 161 | after() 162 | } 163 | upMove() { 164 | while (funState.call()) { 165 | MouseMove(0, -this.speed, 0, "R") 166 | sleep(1) 167 | } 168 | after() 169 | } 170 | downMove() { 171 | while (funState.call()) { 172 | MouseMove(0, this.speed, 0, "R") 173 | sleep(1) 174 | } 175 | after() 176 | } 177 | after() { ;放函数里,延迟调用 178 | this.bMoving := false 179 | this.speed := 1 180 | if (isobject(funAfter)) 181 | funAfter.call() 182 | } 183 | } 184 | 185 | ;控件中心位置 window 坐标 186 | static getCtrlXY(ctl, winTitle:="") { 187 | WinExist(winTitle) 188 | WinGetClientPos(&xClient, &yClient) 189 | WinGetPos(&xWin, &yWin) 190 | ControlGetPos(&x, &y, &w, &h, ctl) 191 | return [x+xClient-xWin+w//2, y+yClient-yWin+h//2] 192 | } 193 | 194 | static clickCtrl(ctl, winTitle:="") { 195 | arrXY := this.getCtrlXY(ctl, winTitle) 196 | this.clickByClient() 197 | } 198 | 199 | static getColorByWindow(x, y) { ;获取鼠标位置的颜色(6位16进制) 200 | cmPixel := A_CoordModePixel 201 | CoordMode("pixel", "window") 202 | cl := substr(PixelGetColor(x,y), 3) 203 | CoordMode("pixel", cmPixel) 204 | return cl 205 | } 206 | 207 | static getColorByScreen(x, y, moveto:=false) { ;获取鼠标位置的颜色(6位16进制) 208 | cmPixel := A_CoordModePixel 209 | CoordMode("pixel", "screen") 210 | if (moveto) 211 | _Mouse.moveByScreen(x, y) 212 | cl := substr(PixelGetColor(x,y), 3) 213 | CoordMode("pixel", cmPixel) 214 | return cl 215 | } 216 | 217 | ;xy := _Mouse.searchColorByScreen(xyxy, cl) 218 | ;if (xy.length) 219 | ; xxx 220 | static searchColorByScreen(xyxy, cl, p:=0) { 221 | cmPixel := A_CoordModePixel 222 | CoordMode("pixel", "screen") 223 | if (PixelSearch(&x, &y, xyxy[1],xyxy[2],xyxy[3],xyxy[4], cl, p)) 224 | res := [x, y] 225 | else 226 | res := [] 227 | CoordMode("pixel", cmPixel) 228 | return res 229 | } 230 | 231 | static getXYColor(screenX, screenY) { ;获取鼠标位置的颜色 232 | CoordMode("pixel", "screen") 233 | strRGB := substr(PixelGetColor(screenX,screenY), 3) 234 | r := substr(strRGB, 1, 2) 235 | g := substr(strRGB, 3, 2) 236 | b := substr(strRGB, 5, 2) 237 | obj := map() 238 | obj["RGB"] := map() 239 | obj["RGB"]["16进制"] := map() 240 | obj["RGB"]["16进制"][1] := map() 241 | obj["RGB"]["十进制"] := map() 242 | obj["RGB"]["十进制"][1] := map() 243 | obj["RGB"]["16进制"][1][1] := r 244 | obj["RGB"]["16进制"][1][2] := g 245 | obj["RGB"]["16进制"][1][3] := b 246 | obj["RGB"]["16进制"][2] := strRGB 247 | obj["RGB"]["16进制"][3] := "#" . strRGB 248 | obj["RGB"]["16进制"][4] := "0x" . strRGB 249 | obj["RGB"]["十进制"][1][1] := format("{:d}", "0x" . r) 250 | obj["RGB"]["十进制"][1][2] := format("{:d}", "0x" . g) 251 | obj["RGB"]["十进制"][1][3] := format("{:d}", "0x" . b) 252 | obj["RGB"]["十进制"][2] := format("{:d}", "0x" . strRGB) 253 | ;strBGR := b . g . r 254 | return obj 255 | } 256 | 257 | static fblSwitch(obj, arr1, arr2) { ;arr1分辨率下的n坐标转成arr2分辨率下的坐标 258 | res := map() 259 | for k, v in obj { 260 | if (isobject(v)) 261 | res[k] := this.fblBase(v, arr1, arr2) 262 | else if (k = 1 || instr(k, "w")) { ;按A_ScreenWidth转换 263 | ;msgbox(k . "`n" . v . "`n" . arr1[1] . "`n" . arr2[1] . "`n" . hyf_fblNumSwitch(v, arr1[1], arr2[1])) 264 | res[k] := this.fblBase(v, arr1[1], arr2[1]) 265 | } else 266 | res[k] := this.fblBase(v, arr1[2], arr2[2]) 267 | } 268 | return res 269 | fblBase(n, w1, w2) { ;w1分辨率下的n坐标转成w2分辨率下的坐标 270 | return floor(n*w2 / w1) 271 | } 272 | } 273 | 274 | ;n0为第一个线段中点坐标,l为线段长度,查看n在第几格 275 | ;0123456789 276 | ;---------- 277 | ;n0=3,l=4 278 | ;得出n1=7,n2=11,n3=15,n4=19,n5=23 279 | static index(n, n0, l) { 280 | if (n < (n0-l/2)) 281 | return 282 | loop { 283 | if (abs(n - (n0 + l*(A_Index-1))) <= l/2) { 284 | return A_Index 285 | } 286 | } 287 | } 288 | 289 | static waitCursorByFun(fun, ms:=10000) { 290 | endtime := A_TickCount + ms 291 | loop { 292 | CaretGetPos(&x, &y) 293 | if (fun(x,y)) 294 | return true 295 | if (A_TickCount > endtime) 296 | return 0 297 | } 298 | } 299 | 300 | ;window坐标转screen坐标 301 | static toScreen(xWindow, yWindow:="") { 302 | WinGetPos(&xWin, &yWin,,, "A") 303 | return [xWindow+xWin, yWindow+yWin] 304 | } 305 | static toWindow(xScreen, yScreen:="") { 306 | WinGetPos(&xWin, &yWin,,, "A") 307 | return [xScreen-xWin, yScreen-yWin] 308 | } 309 | static toClient(xScreen, yScreen:="") { 310 | WinGetClientPos(&xWin, &yWin,,, "A") 311 | return [xScreen-xWin, yScreen-yWin] 312 | } 313 | 314 | ;获取指定坐标点的鼠标光标特征码 315 | static shapeByWindow(x, y, toStr:=0) { 316 | this.moveByWindow(x, y) 317 | MouseMove(x, y, 0, "R") 318 | return this.shapeResult(this.shapeBase(), toStr:=0) 319 | } 320 | 321 | ;获取指定坐标点的鼠标光标特征码 322 | static shapeR(x, y, toStr:=0) { 323 | MouseMove(x, y, 0, "R") 324 | return this.shapeResult(this.shapeBase(), toStr) 325 | } 326 | 327 | ;shapeBase结果简化 328 | static shapeResult(n, toStr:=false) { 329 | switch n { 330 | case 126898458: 331 | return toStr ? "button" : 1 332 | case 161920: 333 | return toStr ? "input" : 2 334 | case 130504298: 335 | return toStr ? "normal" : 3 336 | default: 337 | return toStr ? "other" : 4 338 | } 339 | } 340 | 341 | ;获取鼠标所在位置的光标特征码,by nnrxin 342 | shapeBase() { 343 | size := 16 + A_PtrSize 344 | pCursorInfo := buffer(size, 0) 345 | numput("uint", size, pCursorInfo, 0) ;*声明出 结构 的大小cbSize = 20字节 346 | ; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getcursorinfo 347 | dllcall("GetCursorInfo", "ptr",pCursorInfo, "int") ;获取 结构-光标信息 348 | ; msgbox(numget(pCursorInfo, 8, "uint")) ;含义是什么? 349 | if (!numget(pCursorInfo, 4, "uint")) ;当光标隐藏时,直接输出特征码为0 350 | return 0 351 | obj := map( 352 | 65541, "normal", 353 | 65543, "input", 354 | 65569, "click", 355 | 1705339, "excel_normal", 356 | 2951675, "excel_fill", 357 | 94309089, "excel_move", 358 | ) 359 | code := numget(pCursorInfo, 8, "Ptr") 360 | if obj.has(code) 361 | return obj[code] 362 | msgbox(code . "`n未定义",,0x40000) 363 | return code 364 | ;hIcon := buffer(size, 0) ;创建 结构-图标信息 365 | ; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-geticoninfo 366 | ;dllcall("GetIconInfo", "ptr",hIcon, "ptr",pCursorInfo, "int") ;获取 结构-图标信息 367 | ;lpvMaskBits := buffer(128, 0) ;创造 数组-掩图信息(128字节) 368 | ;dllcall("GetBitmapBits", "ptr", numget(hIcon, 12), "uint", 128, "uint", &lpvMaskBits) ;读取数组-掩图信息 369 | ;MaskCode := 0 370 | ;loop(128) ;掩图码 371 | ; MaskCode += numget(lpvMaskBits, A_Index, "UChar") ;累加拼合 372 | ;if (numget(hIcon, 16, "uint") != "0") { ;颜色图不为空时(彩色图标时) 373 | ; lpvColorBits := buffer(4096, 0) ;创造 数组-色图信息(4096字节) 374 | ; dllcall("GetBitmapBits", "ptr", numget(hIcon, 16), "uint", 4096, "uint", &lpvColorBits) ;读取 数组-色图信息 375 | ; loop(256) ;色图码 376 | ; ColorCode += numget(lpvColorBits, A_Index*16-3, "UChar") ;累加拼合 377 | ;} else { 378 | ; ColorCode := "0" 379 | ;} 380 | ;dllcall("DeleteObject", "ptr",numget(hIcon,12)) ; *清理掩图 381 | ;dllcall("DeleteObject", "ptr",numget(hIcon,16)) ; *清理色图 382 | ;pCursorInfo := buffer(0) ;清空 结构-光标信息 383 | ;hIcon := buffer(0) ;清空 结构-图标信息 384 | ;lpvMaskBits := buffer(0) ;清空 数组-掩图 385 | ;lpvColorBits := buffer(0) ;清空 数组-色图 386 | ;130504298 普通状态 387 | ;161920 说明是输入状态 388 | ;126898458 说明是按钮或超链接 389 | ;135470 Excel空心十字架 390 | ;144310 Excel实心十字架 391 | ;return MaskCode//2 . ColorCode ;输出特征码 392 | } 393 | 394 | static moveByWindow(x, y:=1) { 395 | this._move("window", x, y) 396 | } 397 | static moveByScreen(x, y:=1) { 398 | this._move("screen", x, y) 399 | } 400 | 401 | ;如果x是数组,则y直接当n用(调用时不需要中间空一个参数) NOTE 402 | ;如果是负数,则点击高/宽相减的坐标 403 | static clickByWindow(x, y:=1, n:=1) { 404 | this._click("window", x, y, n) 405 | } 406 | static clickByScreen(x, y:=1, n:=1) { 407 | this._click("screen", x, y, n) 408 | } 409 | static clickByClient(x, y:=1, n:=1) { ;获取控件的坐标是client 410 | this._click("client", x, y, n) 411 | } 412 | 413 | static clickStayByWindow(x, y:=1, n:=1) { ;点击后鼠标不回到原位置 414 | this._clickStay("window", n, x, y) 415 | } 416 | static clickStayByScreen(x, y:=1, n:=1) { 417 | this._clickStay("screen", n, x, y) 418 | } 419 | static clickStayByClient(x, y:=1, n:=1) { ;获取控件的坐标是client 420 | this._clickStay("client", n, x, y) 421 | } 422 | 423 | static clickToPercent(x:=0, y:=0, n:=1, hwnd:=unset) { 424 | if (x is array) { 425 | for arr in x 426 | this.clickToPercent(arr*) 427 | return 428 | } 429 | xy := hyf_offsetXY(x, y, false, hwnd?) 430 | this.clickStayByScreen(xy[1], xy[2], n) 431 | return xy 432 | } 433 | 434 | ;如果x是数组,则y直接当n用(调用时不需要中间空一个参数) NOTE 435 | static downByWindow(x, y:=1) { 436 | this.downBase("window", x, y) 437 | } 438 | static downByScreen(x, y:=1, n:=1) { 439 | this.downBase("screen", x, y) 440 | } 441 | static downByClient(x, y:=1, n:=1) { ;获取控件的坐标是client 442 | this.downBase("client", x, y) 443 | } 444 | 445 | static moveR(x:=0, y:=0, n:=1) => MouseMove(x.toDPI(), y.toDPI(), 0, "R") 446 | 447 | ;如果x是数组,则y直接当n用(调用时不需要中间空一个参数) NOTE 448 | ;NOTE 鼠标会停留 449 | static clickR(x:=0, y:=0, n:=1) { 450 | _Mouse.moveR(x, y) 451 | sleep(20) 452 | click(n) 453 | } 454 | 455 | ;鼠标会回到原坐标 456 | static clickBackR(x, y:=1, n:=1) { 457 | cmMouse := A_CoordModeMouse 458 | CoordMode("mouse", "Screen") 459 | MouseGetPos(&x0, &y0) 460 | if (x is array) { 461 | MouseMove(x[1], x[2], 0, "R") 462 | sleep(20) 463 | click(y) 464 | } else { 465 | MouseMove(x, y, 0, "R") 466 | sleep(20) 467 | click(n) 468 | } 469 | MouseMove(x0, y0, 0) 470 | CoordMode("mouse", cmMouse) 471 | } 472 | 473 | ;dllcall("SetCursorPos", int,xScreen, int,yScreen) 474 | static _move(mode, x, y) { 475 | cmMouse := A_CoordModeMouse 476 | CoordMode("mouse", mode) 477 | if (x is array) { 478 | if (x[1] < 0 || x[2] < 0) { ;处理负数(则用宽/高加上负数) 479 | WinGetPos(,, &w, &h, "A") 480 | if (x[1] < 0) 481 | x[1] := w+x[1] 482 | if (x[2] < 0) 483 | x[2] := h+x[2] 484 | } 485 | MouseMove(x[1], x[2]) 486 | } else { 487 | if (x < 0 || y < 0) { 488 | WinGetPos(,, &w, &h, "A") 489 | if (x < 0) 490 | x := w+x 491 | if (y < 0) 492 | y := h+y 493 | } 494 | MouseMove(x, y, 0) 495 | } 496 | CoordMode("mouse", cmMouse) 497 | } 498 | 499 | static _click(mode, x, y:=1, n:=1) { 500 | ;保存坐标 501 | CoordMode("mouse", "Screen") 502 | MouseGetPos(&x0, &y0) 503 | this._move(mode, x, y) 504 | sleep(20) 505 | if (isobject(x)) 506 | click(y) 507 | else 508 | click(n) 509 | sleep(10) 510 | MouseMove(x0, y0, 0) 511 | CoordMode("mouse", "Screen") 512 | } 513 | 514 | static _clickStay(mode, n:=1, arr*) { 515 | this._move(mode, arr[1], arr[2]) 516 | sleep(20) 517 | click(n) 518 | ; isobject(x) ? click(y) : click(n) 519 | } 520 | 521 | static downBase(mode, x, y:=1) { ;按下左键不放 522 | cmMouse := A_CoordModeMouse 523 | CoordMode("mouse", mode) 524 | if (isobject(x)) 525 | MouseMove(x[1], x[2]) 526 | else 527 | MouseMove(x, y, 0) 528 | sleep(20) 529 | send("{LButton down}") 530 | CoordMode("mouse", cmMouse) 531 | } 532 | 533 | ;m := MenuCreate() 534 | ;h := 45 ;每个框的间距 535 | ;m.add("(&1)每个到末尾", ObjBindMethod(_Mouse,"clickThrough",A_ScreenHeight-50, h, 1)) 536 | ;m.add("(&A)每个到指定", ObjBindMethod(_Mouse,"clickThrough",0,h,1)) 537 | ;m.add("(&2)每2个到末尾",ObjBindMethod(_Mouse,"clickThrough",A_ScreenHeight-50, h, 2)) 538 | ;m.add("(&B)每2个到指定",ObjBindMethod(_Mouse,"clickThrough",0,h,2)) 539 | ;m.show() 540 | ;从当前位置点击到目标位置 541 | static clickThrough(yMax:=0, part:=0, nFenge:=1, ms:=20) { 542 | cmMouse := A_CoordModeMouse 543 | CoordMode("mouse", "screen") ;用全屏模式不需要后面的 sleep 544 | ;sleep(100) ;等待窗口激活 545 | MouseGetPos(&x, &y0) 546 | if (!yMax) { 547 | tooltip("移到最终位置并按任意键") 548 | ih := InputHook() 549 | ih.VisibleNonText := false 550 | ih.KeyOpt("{All}", "E") 551 | ih.start() 552 | suspend true 553 | ih.wait() 554 | suspend false 555 | tooltip 556 | MouseGetPos(, &yMax) 557 | } 558 | if (!part) { 559 | oInput := inputbox("请输入区域内点击数量") 560 | if (oInput.result=="Cancel" || oInput.value == "") 561 | throw ValueError("empty string") 562 | part := round((yMax-y0)/(oInput.value-1)) 563 | } 564 | if (!nFenge) { 565 | oInput := inputbox("每几个点击一次",,, 1) 566 | if (oInput.result=="Cancel" || oInput.value == "") 567 | throw ValueError("empty string") 568 | nFenge := oInput.value 569 | } 570 | yLoop := y0 571 | while(yLoop <= round(part/2+yMax)) { 572 | this.clickStayByScreen(x, yLoop) 573 | sleep(ms) 574 | yLoop += part*nFenge 575 | } 576 | CoordMode("mouse", cmMouse) 577 | } 578 | 579 | static moveWindow(ThisHotkey) { 580 | key := RegExReplace(ThisHotkey, ".*\s") 581 | CoordMode "Mouse" ; Switch to screen/absolute coordinates. 582 | MouseGetPos &x0Mouse, &y0Mouse, &hwnd 583 | WinGetPos &x0Win, &y0Win,,, hwnd 584 | if (!WinGetMinMax(hwnd)) ; Only if the window isn't maximized 585 | SetTimer(watchMouse, 10) ; Track the mouse as the user drags it. 586 | watchMouse() { 587 | if !GetKeyState(key, "P") { ; Button has been released, so drag is complete. 588 | SetTimer(watchMouse, 0) 589 | return 590 | } 591 | if GetKeyState("Escape", "P") { ; Escape has been pressed, so drag is cancelled. 592 | SetTimer(watchMouse, 0) 593 | WinMove(x0Win, y0Win,,, hwnd) 594 | return 595 | } 596 | ; Otherwise, reposition the window to match the change in mouse coordinates 597 | ; caused by the user having dragged the mouse: 598 | CoordMode("Mouse") 599 | MouseGetPos &x1Mouse, &y1Mouse 600 | WinGetPos &x1Win, &y1Win,,, hwnd 601 | SetWinDelay -1 ; Makes the below move faster/smoother. 602 | WinMove x1Win + x1Mouse - x0Mouse, y1Win + y1Mouse - y0Mouse,,, hwnd 603 | x0Mouse := x1Mouse ; Update for the next timer-call to this subroutine. 604 | y0Mouse := y1Mouse 605 | } 606 | } 607 | 608 | ;两个坐标转成rect 609 | static xyxy2rect(arr) { 610 | return [arr[1],arr[2], arr[3]-arr[1], arr[4]-arr[2]] 611 | } 612 | 613 | } 614 | 615 | -------------------------------------------------------------------------------- /lib/Class_Number.ahk: -------------------------------------------------------------------------------- 1 | ;多个数字的处理,可用 arr* 方式,使用 _Array 的功能 2 | ;NOTE 很多时候数字是当字符串用的,字符串不在此功能内 3 | ;复杂运算可用 thqby 的 NTLCalc 4 | 5 | defprop := object.DefineProp.bind(number.prototype) 6 | proto := _Number.prototype 7 | for k in proto.OwnProps() { 8 | if (k != "__Class") 9 | defprop(k, proto.GetOwnPropDesc(k)) 10 | } 11 | 12 | class _Number { 13 | 14 | highParam() => this >> 16 15 | lowParam() => this & 0xffff 16 | 17 | hasDialog() => WinGetStyle(this) & 0x08000000 18 | 19 | ;TODO 不靠谱 20 | ;hwnd 对应窗口是否弹框 21 | isDialog(hwndMain:=0) { 22 | ;过滤主窗口 23 | if (hwndMain && this == hwndMain) 24 | return false 25 | style := WinGetStyle(this) 26 | if !(style & 0x10000000) { ;不可见 27 | ;OutputDebug(format("i#{1} {2}:{3} style={4} not visible", A_LineFile,A_LineNumber,A_ThisFunc,style)) 28 | return false 29 | } 30 | if (style & 0x80000000) 31 | return true 32 | ;if (style & 0x1000000) ;可最大化(不靠谱) 33 | ; return false 34 | ;hParent := dllcall("GetParent", "Ptr",this) ;不靠谱,往往为0 35 | ;if (hParent) { 36 | ; exe0 := WinGetProcessName(this) 37 | ; try { 38 | ; exe1 := WinGetProcessName(hParent) 39 | ; } catch { 40 | ; OutputDebug(format("i#{1} {2}:{3} this={4}, hParent={5} get exeName failed", A_LineFile,A_LineNumber,A_ThisFunc,this,hParent)) 41 | ; return false 42 | ; } else { 43 | ; OutputDebug(format("i#{1} {2}:{3} exe0={4},exe1={5}", A_LineFile,A_LineNumber,A_ThisFunc,exe0,exe1)) 44 | ; return (exe0 == exe1) 45 | ; } 46 | ;} else { 47 | ; OutputDebug(format("w#{1} {2}:{3} style={4} unknown", A_LineFile,A_LineNumber,A_ThisFunc,style)) 48 | ;} 49 | return true 50 | } 51 | 52 | ;转大写金额 53 | ;作者:sikongshan 54 | ;更新日期:2022年09月14日 55 | ;限制:因为不涉及到大数值计算,可以到20位或者更高,但是中文转回阿拉伯的时候,超过17位数值则会有问题(受限于ahk计算),下次考虑拼接方式避免 56 | toAmount() { 57 | sNum := string(this) 58 | arrNum := StrSplit(sNum,".") 59 | if (strlen(arrNum[1])>17) ;设定20位, 60 | return sNum 61 | res:="" 62 | objNum := {0:"零",1:"壹",2:"贰",3:"叁",4:"肆",5:"伍",6:"陆",7:"柒",8:"捌",9:"玖"} 63 | objDanwu := {1:"元",2:"拾",3:"佰",4:"仟",5:"万",6:"拾",7:"佰",8:"仟",9:"亿",10:"拾",11:"佰",12:"仟",13:"兆",14:"拾",15:"佰",16:"仟",17:"万",18:"拾",19:"佰",20:"仟"} 64 | objDicimal := {1:"角",2:"分",3:"毫",4:"厘"} 65 | ;整数部分 66 | loop(strlen(arrNum[1])) 67 | res := objNum[substr(arrNum[1], -A_Index, 1)] . objDanwu[A_Index] . res 68 | loop 3 { 69 | res:=RegExReplace(res,"零(拾|佰|仟)","零") 70 | res:=RegExReplace(res,"零{1,3}","零") 71 | res:=RegExReplace(res,"零(?=(兆|亿|万|元))","") 72 | res:=RegExReplace(res,"亿零万","亿") 73 | res:=RegExReplace(res,"兆零亿","兆") 74 | } 75 | ;小数部分 76 | if(arrNum.length > 1) { 77 | DP := arrNum[2] 78 | res .= "零" 79 | loop parse, DP { 80 | A_LoopField 81 | if(A_Index>5) 82 | break 83 | if(A_LoopField=0) 84 | continue 85 | res .= format("{1}{2}", objNum[A_LoopField],objDicimal[A_Index]) 86 | } 87 | } else { 88 | res .= "整" 89 | } 90 | return res 91 | } 92 | 93 | ;超过numA用 A-Z 94 | ;逆向见 _String.toNum(10) 95 | toABCD(numA:=10, bLower:=false) { 96 | if (this < numA) 97 | return string(this) 98 | else 99 | return chr(65+bLower*32-numA+this) 100 | } 101 | 102 | ;获取 103 | next { 104 | get => this+1 105 | } 106 | 107 | ;计算 108 | pidGetCommandLine() { 109 | for item in ComObjGet("winmgmts:").ExecQuery(format("Select * from Win32_Process where ProcessId={1}", this)) 110 | return item.CommandLine 111 | } 112 | 113 | part(v, vOutRange:=0) { 114 | w := this ;一般是总宽 115 | if (v == 0) 116 | return 0 117 | if (v < 0) { 118 | if (v > -1) { ;小数点 119 | v := round(w * (1-abs(v))) 120 | } else { 121 | v := w - abs(v) 122 | if (v < 0) 123 | v := vOutRange 124 | } 125 | } else if (v <= 1) { ;小数点 TODO 是否按小数点计算 126 | v := round(w * v) 127 | } else if (v > w) { 128 | v := vOutRange 129 | } 130 | return v 131 | } 132 | 133 | ;分区整数 134 | diskInt() { 135 | mb := this*1024 136 | ;return this*1024 137 | n := 7.84423828125 138 | return ceil(ceil(mb/n) * n) 139 | } 140 | 141 | ;用NTLCalc(str) 142 | floatErr() => number(string(round(this+0.00000001,6)).delete0()) 143 | delete0() { 144 | if (integer(this) == this) 145 | return integer(this) 146 | return float(string(this).rtrim("0")) 147 | } 148 | 149 | ;左边填充0(补0) 150 | ;format("{:02s}", 1) 151 | zfill(l) => format(format("{:0{1}s}",l), string(this)) 152 | 153 | mod1(num, m) => mod(num-1, m)+1 154 | 155 | toDPI() => integer(this*A_ScreenDPI/96) 156 | fromDPI() => integer(this*96/A_ScreenDPI) 157 | 158 | ;转换 159 | ;Excel的列号 160 | ;xl.ConvertFormula("R99C99", -4150,1) 161 | ;xl.ConvertFormula("AZ1000", 1,-4150) 162 | toCol() { 163 | num := this 164 | res := "" 165 | while (num) { 166 | md := mod(num-1,26) + 1 167 | res := chr(md+64) . res 168 | num := (num-md) // 26 169 | } 170 | return res 171 | } 172 | 173 | ;hwnd 174 | toRect() { 175 | WinGetPos(&x, &y, &w, &h, this) 176 | return [x,y,w,h] 177 | } 178 | 179 | ;123转成一二三(语音朗读用) 180 | toZh() { 181 | res := "" 182 | arr := ["零","一","二","三","四","五","六","七","八","九"] 183 | loop parse, string(this) 184 | res .= arr[A_LoopField+1] 185 | return res 186 | } 187 | 188 | ;xx:xx:xx.xxx 189 | ;msgbox(60.toTime()) 190 | ;msgbox(61.toTime()) 191 | ;msgbox(3600.toTime()) 192 | ;msgbox(3601.toTime()) 193 | toTime() { 194 | num := this 195 | point := mod(num, 1) 196 | num := integer(num) 197 | res := "" 198 | loop(3) { 199 | n := 60**(3-A_Index) 200 | if (num >= n) { 201 | res .= string(num // n) . ":" 202 | num := mod(num, n) 203 | } else if (res != "") { 204 | res .= "00:" 205 | } 206 | } 207 | return rtrim(res, ":") 208 | } 209 | 210 | ;转中文数字 211 | toZhnum() { 212 | num := this 213 | arrNum := ["一","二","三","四","五","六","七","八","九","十"] 214 | if (num <= 10) 215 | return arrNum[num] 216 | else if (num < 20) ;NOTE 避免 一十一 217 | return "十" . arrNum[mod(num,10)] 218 | arrYiWan := [ 219 | [100000000, "亿"], 220 | [10000, "万"], 221 | [1, ""], 222 | ] 223 | res := "" 224 | for arr in arrYiWan { 225 | if (num >= arr[1]) { 226 | res .= format("{1}{2}", qian(num//arr[1],A_Index==1),arr[2]) 227 | ;msgbox(res . "`num" . num . "`num" . arr[1] . "`num" . (num//arr[1])) 228 | num := mod(num, arr[1]) 229 | } 230 | } 231 | return ltrim(res, "零") 232 | qian(num, trimBoth:=true) { ;处理万以内数字 233 | static arrQian := [ 234 | [1000, "千"], 235 | [100, "百"], 236 | [10, "十"], 237 | [1, ""], 238 | ] 239 | resQian := "" 240 | for arr in arrQian { 241 | if (num >= arr[1]) { 242 | resQian .= arrNum[num // arr[1]] . arr[2] 243 | num := mod(num, arr[1]) 244 | } else { 245 | if (substr(resQian, -1) != "零") 246 | resQian .= "零" 247 | } 248 | } 249 | resQian := trimBoth ? trim(resQian, "零") : rtrim(resQian, "零") 250 | if (substr(resQian,1,2) == "一十") ;NOTE 处理一十 251 | resQian := substr(resQian, 2) 252 | return resQian 253 | } 254 | } 255 | 256 | ;--------------------------进制转换-------------------------------- 257 | ;其他进制可用十进制中转 258 | 259 | ;10进制转r进制,numA默认10(同16进制) 260 | d2r(r, numA:=10) { 261 | num := this 262 | obj := map() 263 | if (numA == 10) { 264 | loop(10) 265 | obj[A_Index-1] := A_Index-1 266 | } 267 | loop(26) 268 | obj.push(chr(64+A_Index)) 269 | ;hyf_msgbox(obj) 270 | res := "" 271 | while(num > 0) { 272 | md := mod(num,r) ;逻辑和hyf_num2Column不一样 273 | res := obj[md] . res 274 | num := (num-md) // r 275 | } 276 | return res 277 | } 278 | 279 | d2b(r:=2) { ;十进制转2进制 280 | num := this 281 | res := "" 282 | while(num) { 283 | res := mod(num, r) . res 284 | num //= r 285 | } 286 | return res 287 | } 288 | 289 | d2h(tp:="") { ;十进制转16进制 ;以f为例,tp:"":f,"X":F,"0xx":0xf, "0xX":0xF,"0Xx":"0Xf", "0XX":"0XF" 290 | if (tp = "") 291 | f := "{:x}" 292 | else if (tp == "X") 293 | f := "{:X}" 294 | else if (tp == "0xx") 295 | f := "0x{:x}" 296 | else if (tp == "0xX") 297 | f := "0x{:X}" 298 | else if (tp == "0Xx") 299 | f := "0X{:x}" 300 | else if (tp == "0XX") 301 | f := "0X{:X}" 302 | return format(f, this) 303 | } 304 | 305 | } 306 | 307 | class range { 308 | __new(start, end?, step:=1) { 309 | if !step 310 | throw TypeError("Invalid 'step' parameter") 311 | if (!isset(end)) { 312 | end := start 313 | start := 1 314 | } 315 | if (end < start) && (step > 0) 316 | step := -step 317 | this.start := start, this.end := end, this.step := step 318 | } 319 | 320 | __enum(varCount) { 321 | start := this.start - this.step 322 | end := this.end 323 | step := this.step 324 | counter := 0 325 | EnumElements(&element) { 326 | start := start + step 327 | if ((step > 0) && (start > end)) || ((step < 0) && (start < end)) 328 | return false 329 | element := start 330 | return true 331 | } 332 | EnumIndexAndElements(&index, &element) { 333 | start := start + step 334 | if ((step > 0) && (start > end)) || ((step < 0) && (start < end)) 335 | return false 336 | index := ++counter 337 | element := start 338 | return true 339 | } 340 | return (varCount = 1) ? EnumElements : EnumIndexAndElements 341 | } 342 | 343 | } 344 | ;msgbox(30.toZhnum()) 345 | -------------------------------------------------------------------------------- /lib/Class_Pinyin.ahk: -------------------------------------------------------------------------------- 1 | ;msgbox(_Pinyin("A1").main("为你好")) 2 | ;msgbox(json.stringify(_Pinyin("a").obj["为"], 4)) 3 | ;timeSave := A_TickCount 4 | ;_Pinyin("B1") 5 | ;msgbox(A_TickCount - timeSave) 6 | 7 | class _Pinyin { 8 | 9 | static closure(tp) { 10 | oPinyin := _Pinyin(tp) ;NOTE oPinyin初始化比较费时,所以打包进来利用 11 | return (x)=>oPinyin.main(x) 12 | } 13 | 14 | ;static regAlpha := "[āáǎàōóǒòēéěèīíǐìūúǔùǖǘǚǜ]" 15 | 16 | ;obj["为"] = ["wei2", wei1] 17 | ;tpAlpab 以 hān 为列 18 | ; a = h 19 | ; A = H 20 | ; aa = han 21 | ; Aa = Han 22 | ; a0 = hān 23 | ; A0 = Hān 24 | ; a1 = han1 25 | ; A1 = Han1 26 | __new(tpAlpha:="Aa", toObj:=true) { 27 | SplitPath(A_LineFile,, &dir) 28 | this.sFile := rtrim(fileread("d:\TC\soft\AutoHotkey\lib\pinyin.txt","utf-8"),"`r`n") 29 | objTmp := map( 30 | "ā","a1", "á","a2", "ǎ","a3", "à","a4", 31 | "ō","o1", "ó","o2", "ǒ","o3", "ò","o4", 32 | "ē","e1", "é","e2", "ě","e3", "è","e4", 33 | "ī","i1", "í","i2", "ǐ","i3", "ì","i4", 34 | "ū","u1", "ú","u2", "ǔ","u3", "ù","u4", 35 | "ǖ","v1", "ǘ","v2", "ǚ","v3", "ǜ","v4", 36 | ) 37 | if (instr(tpAlpha,"_")) { 38 | this.char := " " 39 | tpAlpha := StrReplace(tpAlpha, "_") 40 | } else { 41 | this.char := "" 42 | } 43 | switch strlen(tpAlpha) { 44 | case 1: ;删除音标 45 | this.sFile := RegExReplace(this.sFile, "\s.\K\S*") ;删除多余的内容 46 | this.sFile := RegExReplace(this.sFile, "(\s(.))(\s\2)+", "$1") ;NOTE 删除多音字重复的声母 47 | for sd, a1 in objTmp 48 | this.sFile := RegExReplace(this.sFile, sd, substr(a1,1,1)) 49 | case 2: 50 | if (substr(tpAlpha, 2, 1) != "0") { ;非 ā模式 51 | for sd, a1 in objTmp 52 | this.sFile := RegExReplace(this.sFile, sd . "(\w*)", format("{1}$1{2}", substr(a1,1,1),substr(a1,2,1))) 53 | } 54 | if (substr(tpAlpha, 2, 1) == "a") 55 | this.sFile := RegExReplace(this.sFile, "\d") 56 | } 57 | ;转大写 58 | timeSave := A_TickCount 59 | if (substr(tpAlpha,1,1) ~= "[A-Z]") 60 | this.sFile := RegExReplace(this.sFile, "\s\K([a-z])", "$U1") 61 | if (toObj) { 62 | this.obj := map() ;汉字和拼音对照表 63 | loop parse, this.sFile, "`n", "`r" { 64 | arr := StrSplit(A_LoopField, "`t") 65 | this.obj[arr[1]] := StrSplit(arr[2]," ") 66 | } 67 | } 68 | OutputDebug(format("i#{1} {2}:oPinyin time={3}", A_LineFile,A_LineNumber,A_TickCount - timeSave)) 69 | ;msgbox(json.stringify(this.obj, 4)) 70 | } 71 | 72 | main(str) { ;获取全拼 73 | if (str == "") 74 | return 75 | res := "" 76 | loop parse, str { 77 | if (A_LoopField ~= "[[:ascii:]]") { 78 | res .= A_LoopField 79 | } else { 80 | if this.HasOwnProp("obj") { 81 | if (this.obj.has(A_LoopField)) { 82 | res .= this.obj[A_LoopField][1] . this.char ;NOTE 只能取第1个 83 | } else { ;一般不会 84 | res .= A_LoopField . this.char 85 | } 86 | } else { 87 | if (RegExMatch(this.sFile, A_LoopField . "\s(\S+)", &m)) { ;只提取第1个 88 | res .= m[1] . this.char 89 | } else { ;一般不会 90 | res .= A_LoopField . this.char 91 | } 92 | } 93 | } 94 | } 95 | return rtrim(res, this.char) 96 | } 97 | 98 | ;判断拼音是否有效 99 | ;比如yuan 或 yuan1 100 | check(pinyin) { 101 | pinyin := StrLower(pinyin) 102 | if (pinyin ~= "\d") { ;有声调 103 | pinyin := this.toSd(pinyin) 104 | return instr(this.sFile, pinyin) 105 | } else { 106 | reg := format("\s{1}(\s|$)", pinyin.toReg()) 107 | res := (this.sFile ~= reg) 108 | ;if !res 109 | ; msgbox(reg . "`n" . res . "`n" . substr(this.sFile, res, 9)) 110 | return res 111 | } 112 | } 113 | 114 | ;拼音转成正则 115 | ;xuan → xu[āáǎàa]n 116 | ;可指定 sd 117 | toReg(pinyin, sd:=0) { 118 | pinyin := StrLower(pinyin) 119 | if (instr(pinyin, "a")) 120 | return sd==0 ? StrReplace(pinyin,"a","[āáǎàa]") : StrReplace(pinyin,"a",substr("āáǎàa",sd,1)) 121 | if (instr(pinyin, "o")) 122 | return sd==0 ? StrReplace(pinyin,"o","[ōóǒòo]") : StrReplace(pinyin,"o",substr("ōóǒòo",sd,1)) 123 | if (instr(pinyin, "e")) 124 | return sd==0 ? StrReplace(pinyin,"e","[ēéěèe]") : StrReplace(pinyin,"e",substr("ēéěèe",sd,1)) 125 | if (pinyin ~= "[iu]") { 126 | loop(strlen(pinyin)) { ;逆向遍历 127 | char := substr(pinyin, -A_Index, 1) 128 | switch char { 129 | case "i": 130 | return sd==0 ? StrReplace(pinyin, char, "[īíǐìi]") : StrReplace(pinyin, char, substr("īíǐìi", sd, 1)) 131 | case "u": 132 | return sd==0 ? StrReplace(pinyin, char, "[ūúǔùu]") : StrReplace(pinyin, char, substr("ūúǔùu", sd, 1)) 133 | } 134 | } 135 | } else { ;ü 136 | return sd==0 ? StrReplace(pinyin, "ü", "[ǖǘǚǜü]") : StrReplace(pinyin, "ü", substr("ǖǘǚǜü", sd, 1)) 137 | } 138 | } 139 | 140 | ;拼音转成声调 141 | ;xuan1 → xuān 142 | toSd(pinyin) { 143 | pinyin := StrLower(pinyin) 144 | sd := (pinyin ~= "\d") ? RegExReplace(pinyin, "\D") : 5 145 | if (instr(pinyin, "a")) 146 | return StrReplace(pinyin, "a", substr("āáǎàa", sd, 1)) 147 | if (instr(pinyin, "o")) 148 | return StrReplace(pinyin, "o", substr("ōóǒòo", sd, 1)) 149 | if (instr(pinyin, "e")) 150 | return StrReplace(pinyin, "e", substr("ēéěèe", sd, 1)) 151 | if (pinyin ~= "[iu]") { 152 | loop(strlen(pinyin)) { ;逆向遍历 153 | char := substr(pinyin, -A_Index, 1) 154 | switch char { 155 | case "i": 156 | return StrReplace(pinyin, char, substr("īíǐìi", sd, 1)) 157 | case "u": 158 | return StrReplace(pinyin, char, substr("ūúǔùu", sd, 1)) 159 | } 160 | } 161 | } else { ;ü 162 | return StrReplace(pinyin, "ü", substr("ǖǘǚǜü", sd, 1)) 163 | } 164 | } 165 | 166 | ;判断拼音有哪些声调 167 | ;for cell in ox().selection { 168 | ; if (cell.value == "") 169 | ; continue 170 | ; arr := cell.value.toArrSd() 171 | ; xl.ScreenUpdating := false 172 | ; if (arr.length < 5) { 173 | ; s := "12345" 174 | ; for v in arr 175 | ; s := StrReplace(s, v) 176 | ; ;msgbox(cell.address(false, false) . "`n" . s,,0x40000) 177 | ; Excel_Comment._addString(cell, s) 178 | ; } 179 | ; xl.ScreenUpdating := true 180 | ;} 181 | toArrSd(pinyin) { 182 | pinyin := StrLower(pinyin) 183 | arr := [] 184 | loop(5) { 185 | reg := format("\s{1}(\s|$)", this.toReg(A_Index)) 186 | if (this.sFile ~= reg) 187 | arr.push(A_Index) 188 | } 189 | return arr 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /lib/Class_Timer.ahk: -------------------------------------------------------------------------------- 1 | ;记录一次性定时器 2 | ;persistent 3 | ;_Timer.add("a", tips, funA, 1000) 4 | ;_Timer.add("b", tips, funB, 3000) 5 | ;_Timer.show() 6 | ;sleep(2000) 7 | ;_Timer.show() 8 | class _Timer { 9 | ;每项[key, time_do, fun, tips, info] 10 | ;key 为标识,作为覆盖依据 11 | ;time_do 用来排序 12 | ;getData 返回[key, info] 13 | static ao := [] 14 | 15 | static add(key, tips, fun, ms, info:="") { 16 | time_do := DateAdd(A_Now, ms//1000, "seconds") 17 | found := false 18 | obj := map( 19 | "key", key, 20 | "time_do", time_do, 21 | "fun", fun, 22 | "tips", tips, 23 | "info", info, 24 | ) 25 | for o in this.ao { 26 | if (o["key"] == key) { 27 | found := true 28 | SetTimer(this.ao[A_Index]["fun"], 0) ;NOTE 删除 SetTimer 29 | this.ao[A_Index] := obj 30 | break 31 | } 32 | } 33 | if (!found) 34 | this.ao.push(obj) 35 | ;OutputDebug(format("i#{1} {2}:{3} this.ao={4}", A_LineFile.fn(),A_LineNumber,A_ThisFunc,json.stringify(this.ao,4))) 36 | this.ao.sort((a,b)=>StrCompare(a["time_do"],b["time_do"])) 37 | ;OutputDebug(format("i#{1} {2}:{3} this.ao={4}", A_LineFile.fn(),A_LineNumber,A_ThisFunc,json.stringify(this.ao,4))) 38 | SetTimer(fun, -ms) 39 | } 40 | 41 | static update() { 42 | new_ao := [] 43 | now := A_Now 44 | for a in this.ao { 45 | ;OutputDebug(format("i#{1} {2}:{3} {4} {5}", A_LineFile.fn(),A_LineNumber,A_ThisFunc,substr(now,9,6),substr(a[1],9,6))) 46 | if (StrCompare(now, a["time_do"]) < 0) 47 | new_ao.push(a) 48 | } 49 | this.ao := new_ao 50 | } 51 | 52 | static getCount() => this.ao.length 53 | 54 | ;不update 55 | static getData(idx:=1) { 56 | if (idx == 0) 57 | return this.ao.map((a)=>[a["key"],a["info"]]) 58 | if (this.ao.length >= idx) { 59 | obj := this.ao[idx] 60 | return [obj["key"], obj["info"]] 61 | } 62 | return [] 63 | } 64 | 65 | static getSeconds(idx:=1) { 66 | if (this.ao.length >= idx) { 67 | sec := DateDiff(this.ao[idx]["time_do"], A_Now, "seconds") 68 | OutputDebug(format("i#{1} {2}:{3} sec={4}", A_LineFile.fn(),A_LineNumber,A_ThisFunc,sec)) 69 | return sec 70 | } else { 71 | return 0 72 | } 73 | } 74 | 75 | static show(x:=0, y:=0) { 76 | this.update() 77 | str := "`n".join(this.ao.map((a)=>format("{1}|{2}: {3}", a["key"],a["info"],a["tips"]))) 78 | cmToolTip := A_CoordModeToolTip 79 | CoordMode("ToolTip", "screen") 80 | tooltip(str, x,y, 9) 81 | CoordMode("ToolTip", cmToolTip) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /lib/Class_XYXY.ahk: -------------------------------------------------------------------------------- 1 | ;oXY := XYXY("A") 2 | ;msgbox(json.stringify(oXY.main(0,0.44, -5,0.55), 4)) 3 | class XYXY { 4 | 5 | ;tp 0=向内偏移 1=向外偏移 6 | __new(rect, var:=unset, tp:=0) { 7 | this.tp := tp 8 | if (isset(rect)) { 9 | if (rect is string || rect is integer) { 10 | WinGetPos(&winX, &winY, &winW, &winH, rect) 11 | this.wx := winX 12 | this.wy := winY 13 | this.ww := winW 14 | this.wh := winH 15 | this.rect := [winX,winY,winW,winH] 16 | } else if (rect is array) { 17 | this.wx := rect[1] 18 | this.wy := rect[2] 19 | this.ww := rect[3] 20 | this.wh := rect[4] 21 | this.rect := rect 22 | } 23 | } 24 | if (isset(var)) 25 | this.var := var 26 | } 27 | 28 | ;NOTE 一般调用这个就行, 29 | main(args*) { 30 | switch args.length { 31 | case 1: return this.dealx(args[1]) ;默认x(获取y,手工调用 dealy) 32 | case 2: return [this.dealx(args[1]), this.dealy(args[2])] 33 | case 4: return [this.dealx(args[1]), this.dealy(args[2]), this.dealx(args[3]), this.dealy(args[4])] 34 | default: return this.dealx(args[1]) ;NOTE y则手工执行this.dealy 35 | } 36 | } 37 | 38 | ;支持批量处理x 39 | dealx(v:=unset, x:=unset, w:=unset) { 40 | ;支持传入和默认值 41 | if (!isset(v)) 42 | v := this.var[1] 43 | if (v is array) ;NOTE 批量处理x 44 | return v.map((x)=>this.dealx(x)) 45 | if (!isset(x)) { 46 | x := this.wx 47 | w := this.ww 48 | } 49 | return this._deal(v,x,w) 50 | } 51 | 52 | dealy(v:=unset, y:=unset, h:=unset) { 53 | ;支持传入和默认值,默认处理y,如果y需要手工传入值(this.y, this.wy, this.wh) 54 | if (!isset(v)) 55 | v := this.var[2] 56 | if (v is array) ;NOTE 批量处理x 57 | return v.map((y)=>this.dealy(y)) 58 | if (!isset(y)) { 59 | y := this.wy 60 | h := this.wh 61 | } 62 | return this._deal(v,y,h) 63 | } 64 | 65 | ;NOTE 66 | _deal(v, x, w) { 67 | if (v is float) 68 | v := round(w * v) 69 | if (this.tp == 0) 70 | res := x + w*(v<0) + v 71 | else 72 | res := x + w*(v>0) + v 73 | return res 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /lib/ComVar.ahk: -------------------------------------------------------------------------------- 1 | ; Construction and deconstruction VARIANT struct 2 | class ComVar { 3 | /** 4 | * Construction VARIANT struct, `ptr` property points to the address, `__item` property returns var's Value 5 | * @param vVal Values that need to be wrapped, supports String, Integer, Double, Array, ComValue, ComObjArray 6 | * ### example 7 | * `var1 := ComVar('string'), MsgBox(var1[])` 8 | * 9 | * `var2 := ComVar([1,2,3,4],, true)` 10 | * 11 | * `var3 := ComVar(ComValue(0xb, -1))` 12 | * @param vType Variant's type, VT_VARIANT(default) 13 | * @param convert Convert AHK's array to ComObjArray 14 | */ 15 | __new(vVal := unset, vType := 0xC, convert := false) { 16 | static size := 8 + 2 * A_PtrSize 17 | this.var := buffer(size, 0), this.owner := true 18 | this.ref := ComValue(0x4000 | vType, this.var.Ptr + (vType = 0xC ? 0 : 8)) 19 | if (isset(vVal)) { 20 | if (type(vVal) == "ComVar") { 21 | this.var := vVal.var, this.ref := vVal.ref, this.obj := vVal, this.owner := false 22 | } else { 23 | if (isobject(vVal)) { 24 | if (vType != 0xC) 25 | this.ref := ComValue(0x400C, this.var.ptr) 26 | if (convert && (vVal is array)) { 27 | switch type(vVal[1]) { 28 | case "Integer": vType := 3 29 | case "String": vType := 8 30 | case "Float": vType := 5 31 | case "ComValue", "ComObject": vType := ComObjType(vVal[1]) 32 | default: vType := 0xC 33 | } 34 | ComObjFlags(obj := ComObjArray(vType, vVal.Length), -1), i := 0, this.ref[] := obj 35 | for v in vVal 36 | obj[i++] := v 37 | } else { 38 | this.ref[] := vVal 39 | } 40 | } else { 41 | this.ref[] := vVal 42 | } 43 | } 44 | } 45 | } 46 | __delete() => (this.owner ? dllcall("oleaut32\VariantClear", "ptr",this.var) : 0) 47 | __item { 48 | get => this.ref[] 49 | set => this.ref[] := value 50 | } 51 | ptr => this.var.ptr 52 | size => this.var.size 53 | type { 54 | get => numget(this.var, "ushort") 55 | set { 56 | if (!this.IsVariant) 57 | throw PropertyError("VarType is not VT_VARIANT, type is read-only.", -2) 58 | numput("ushort", Value, this.var) 59 | } 60 | } 61 | IsVariant => ComObjType(this.ref) & 0xC 62 | } 63 | -------------------------------------------------------------------------------- /lib/Explorer.ahk: -------------------------------------------------------------------------------- 1 | ;有些 windows 错误是通过 explorer.exe 通知的,所以写在这里 2 | class Explorer { 3 | 4 | ;打印机→管理纸张 5 | static print_papers() => UIA.FindControl("Button", "打印服务器属性").ClickByMouse() 6 | ;static print_noSleep() 7 | 8 | static print_addPaper() { 9 | if WinActive("打印服务器 属性") { ;添加纸张 10 | ;24.1*9.3 11 | ControlSetChecked(true, "Button3") 12 | sleep(100) 13 | ControlFocus("Edit2") 14 | SendMessage(EM_SETSEL:=0xB1, 0, -1, "Edit2") 15 | msgbox("找不到纸如何处理?",,0x40000) 16 | } 17 | } 18 | 19 | static get() { 20 | hwnd := WinGetID("A") 21 | return Explorer(hwnd).dir() 22 | } 23 | 24 | ;修改目录 25 | static setDir(dn) { 26 | if (wind := this.get()) 27 | wind.navigate("file:///" . dn) 28 | else 29 | this.open(dn) 30 | } 31 | 32 | ;定位并选中某文件 33 | static open(fp) => run(format('explorer /select, "{1}"', fp)) ;select后面的逗号不能省 34 | 35 | ;选择特定文件 36 | static select() { 37 | sfv := this.get().document 38 | for item in sfv.folder.items { 39 | if (1) 40 | sfv.SelectItem(item, true) 41 | } 42 | } 43 | 44 | 45 | ;遍历当前目录 46 | static getItem(winTitle:="A") { 47 | for item in this.get(winTitle).document.folder.items { 48 | msgbox(item.name . "`n" . item.type) 49 | ;if (item.name = vItem) 50 | ;{ 51 | ;objWin.document.SelectItem(item, dwFlags) 52 | ;return 53 | ;} 54 | } 55 | } 56 | 57 | ;traverse() ;遍历 ? 58 | ;{ 59 | ;a := [] 60 | ;for o in ComObject("Shell.application").Windows 61 | ;{ 62 | ;p := RegExReplace(o.LocationURL, "^file:\/+") ;去除前面的file:/// 63 | ;a.push(StrReplace(p, "/", "\")) ;替换/为\ 64 | ;} 65 | ;return a 66 | ;} 67 | 68 | ;要扫描并修复** 69 | ;原因:电源供电不足+没有安全插拔造成的 70 | ;TODO 不一定有效 71 | static errorUSBScan() { 72 | "sc stop stisvc".runCmdHide() ;关闭 ShellHWDetection 要先关这个 73 | "sc stop ShellHWDetection".runCmdHide() 74 | "sc config ShellHWDetection start= disabled".runCmdHide() ;disabled前必须带空格 75 | } 76 | 77 | __new(hwnd:=0) { 78 | if (!hwnd) 79 | hwnd := WinActive("ahk_class CabinetWClass ahk_exe explorer.exe") 80 | else if (hwnd is string) 81 | hwnd := WinExist(hwnd) 82 | this.hwnd := hwnd 83 | for wind in ComObject("Shell.Application").Windows { 84 | if (wind.hwnd == hwnd) { 85 | OutputDebug(format("i#{1} {2}:{3} get win={4}", A_LineFile.fn(),A_LineNumber,A_ThisFunc,this.hwnd)) 86 | this.win := wind 87 | return 88 | } 89 | } 90 | } 91 | 92 | ;NOTE 只支持 Explorer 的保存对话框,其他软件不支持 93 | dir() => this.win.document.folder.self.path 94 | 95 | ;所有选中的文件路径 96 | arrSelectFilePath() { 97 | cls := WinGetClass(this.hwnd) 98 | arr := [] 99 | if (cls ~= "(Cabinet|Explore)WClass") { ;主界面 100 | for item in this.win.document.SelectedItems 101 | arr.push(item.path) 102 | } else if (cls ~= "Progman|WorkerW") { 103 | loop parse, ListViewGetContent(,"SysListView321", "ahk_class " . cls), "`n", "`r" 104 | arr.push(A_Desktop . "\" . A_LoopField) 105 | } 106 | return arr 107 | } 108 | 109 | class dialog { 110 | 111 | __new(hwnd) { 112 | if (hwnd is string) 113 | hwnd := WinExist(hwnd) 114 | this.hwnd := hwnd 115 | OutputDebug(format("i#{1} {2}:{3} this.hwnd={4}", A_LineFile,A_LineNumber,A_ThisFunc,this.hwnd)) 116 | for wind in ComObject("Shell.Application").Windows { 117 | if (wind.hwnd == hwnd) { 118 | this.win := wind 119 | return 120 | } 121 | } 122 | } 123 | 124 | ;获取文件名 125 | ;NOTE 若获取不到扩展名,则返回空字符串 126 | fn() { 127 | fn := ControlGetText("Edit1", this.hwnd) 128 | OutputDebug(format("i#{1} {2}:{3} fn={4}", A_LineFile,A_LineNumber,A_ThisFunc,fn)) 129 | if (fn ~= "\.\w+$") { ;TODO 有类似扩展名则判定为文件名 130 | return fn 131 | } else { 132 | ext := this.ext(this.hwnd) 133 | if (ext != "") 134 | return format("{1}.{2}", fn,ext) 135 | else ;NOTE 没有扩展名,则返回空 136 | return "" 137 | } 138 | } 139 | 140 | ;获取扩展名 141 | ext() { 142 | str := ControlGetText("ComboBox2", this.hwnd) 143 | if (str ~= "\*\.\w+\)$") { 144 | ext := rtrim(RegExReplace(str, ".*\."), ")") 145 | OutputDebug(format("i#{1} {2}:{3} ext={4}", A_LineFile,A_LineNumber,A_ThisFunc,ext)) 146 | return ext 147 | } ;TODO 待完善 148 | } 149 | 150 | ;获取当前完整路径 151 | ;NOTE 若获取不到扩展名,则返回空字符串 152 | fp() { 153 | fn := this.fn() 154 | if (fn != "") { 155 | dn := this.dir() 156 | fp := format("{1}\{2}", dn,fn) 157 | OutputDebug(format("i#{1} {2}:{3} fp={4}", A_LineFile,A_LineNumber,A_ThisFunc,fp)) 158 | return fp 159 | } 160 | return "" 161 | } 162 | 163 | ;获取保存对话框的目录路径 164 | dir() { 165 | if (!WinExist(this.hwnd) || WinGetClass() != "#32770") 166 | return 167 | for ctl in WinGetControls() { 168 | if (substr(ctl, 1, 15) != "ToolbarWindow32") 169 | continue 170 | str := ControlGetText(ctl) 171 | if (substr(str,1,3) == "地址:") { 172 | res := substr(str,5) 173 | switch res { 174 | case "桌面": return A_Desktop 175 | case "文档": return A_MyDocuments 176 | default: return res 177 | } 178 | } 179 | } 180 | ;不支持其他程序的【保存】窗口 181 | ;return substr(this.get(WinGetID()).LocationURL, 9) 182 | } 183 | 184 | ;static dialogErrorText() { 185 | ; elWin := UIA.ElementFromHandle(WinGetID()) 186 | ; cond := UIA.CreatePropertyCondition("ControlType", "Text") 187 | ; el := elWin.FindFirst(cond) 188 | ; return el.CurrentName 189 | ;} 190 | 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /lib/LVICE_XXS.ahk: -------------------------------------------------------------------------------- 1 | ; https://www.autohotkey.com/boards/viewtopic.php?f=83&t=94046 2 | ; ====================================================================================================================== 3 | ; class LVICE_XXS - ListView in-cell editing for AHK v2 - minimal version 4 | ; ====================================================================================================================== 5 | class LVICE_XXS { 6 | 7 | __new(LV) { 8 | ;if (type(LV) != "gui.ListView") 9 | ; throw error("class LVICE requires a GuiControl object of type gui.ListView!") 10 | this.ClickFunc := ObjBindMethod(this, "Click") 11 | this.DoubleClickFunc := ObjBindMethod(this, "DoubleClick") 12 | this.BeginLabelEditFunc := ObjBindMethod(this, "BeginLabelEdit") 13 | this.EndLabelEditFunc := ObjBindMethod(this, "EndLabelEdit") 14 | this.CommandFunc := ObjBindMethod(this, "Command") 15 | this.LV := LV 16 | this.hWnd := LV.hWnd 17 | } 18 | 19 | __delete() { 20 | if dllcall("IsWindow", "Ptr", this.hWnd, "uint") 21 | this.LV.OnNotify(-3, this.DoubleClickFunc, 0) 22 | this.ClickFunc := "" 23 | this.DoubleClickFunc := "" 24 | this.BeginLabelEditFunc := "" 25 | this.EndLabelEditFunc := "" 26 | this.CommandFunc := "" 27 | } 28 | 29 | ;OnClick() { 30 | ; LV.OnNotify(-2, this.ClickFunc) 31 | ; LV.OnNotify(-3, this.DoubleClickFunc) 32 | ;} 33 | 34 | ; ------------------------------------------------------------------------------------------------------------------- 35 | ; NM_CLICK (list view) notification 36 | ; ------------------------------------------------------------------------------------------------------------------- 37 | Click(LV, L, p*) { 38 | critical 39 | r := numget(L + (A_PtrSize * 3), 0, "int") 40 | c := numget(L + (A_PtrSize * 3), 4, "int") 41 | arr := this.getLineText(LV, r+1) 42 | ;CellText := LV.GetText(r + 1, c + 1) 43 | tooltip(format("R{1}C{2}", r+1,c+1), 0, 0, 9) 44 | SetTimer(tooltip.bind(,,, 9), -1000) 45 | CellText := arr[c+1] 46 | A_Clipboard := CellText 47 | tooltip(CellText) 48 | SetTimer(tooltip, -1000) 49 | return arr 50 | } 51 | 52 | getLineText(LV, r, p*) { 53 | arr := [] 54 | loop(LV.GetCount("column")) 55 | arr.push(LV.GetText(r, A_Index)) 56 | return arr 57 | } 58 | 59 | ; ------------------------------------------------------------------------------------------------------------------- 60 | ; NM_DBLCLK (list view) notification 61 | ; ------------------------------------------------------------------------------------------------------------------- 62 | DoubleClick(LV, L) { 63 | critical 64 | Item := numget(L + (A_PtrSize * 3), 0, "int") 65 | Subitem := numget(L + (A_PtrSize * 3), 4, "int") 66 | CellText := LV.GetText(Item + 1, SubItem + 1) 67 | RC := buffer(16, 0) 68 | numput("int", 0, "int", SubItem, RC) 69 | dllcall("SendMessage", "Ptr", LV.hWnd, "uint", 0x1038, "Ptr", Item, "Ptr", RC) ; LVM_GETSUBITEMRECT 70 | this.CX := numget(RC, 0, "int") 71 | if (Subitem = 0) 72 | this.CW := dllcall("SendMessage", "Ptr", LV.hWnd, "uint", 0x101D, "Ptr", 0, "Ptr", 0, "int") ; LVM_GETCOLUMNWIDTH 73 | else 74 | this.CW := numget(RC, 8, "int") - this.CX 75 | this.CY := numget(RC, 4, "int") 76 | this.CH := numget(RC, 12, "int") - this.CY 77 | this.Item := Item 78 | this.Subitem := Subitem 79 | this.LV.OnNotify(-175, this.BeginLabelEditFunc) 80 | dllcall("PostMessage", "Ptr", LV.hWnd, "uint", 0x1076, "Ptr", Item, "Ptr", 0) ; LVM_EDITLABEL 81 | } 82 | 83 | ; ------------------------------------------------------------------------------------------------------------------- 84 | ; LVN_BEGINLABELEDIT notification 85 | ; ------------------------------------------------------------------------------------------------------------------- 86 | BeginLabelEdit(LV, L) { 87 | critical 88 | this.HEDT := dllcall("SendMessage", "Ptr", LV.hWnd, "uint", 0x1018, "Ptr", 0, "Ptr", 0, "UPtr") 89 | this.ItemText := LV.GetText(this.Item + 1, this.Subitem + 1) 90 | dllcall("SendMessage", "Ptr", this.HEDT, "uint", 0x00D3, "Ptr", 0x01, "Ptr", 4) ; EM_SETMARGINS, EC_LEFTMARGIN 91 | dllcall("SendMessage", "Ptr", this.HEDT, "uint", 0x000C, "Ptr", 0, "Ptr", strptr(this.ItemText)) ; WM_SETTEXT 92 | dllcall("SetWindowPos", "Ptr", this.HEDT, "Ptr", 0, "int", this.CX, "int", this.CY, 93 | "int", this.CW, "int", this.CH, "uint", 0x04) 94 | OnMessage(0x0111, this.CommandFunc, -1) 95 | this.LV.OnNotify(-175, this.BeginLabelEditFunc, 0) 96 | this.LV.OnNotify(-176, this.EndLabelEditFunc) 97 | return false 98 | 99 | } 100 | ; ------------------------------------------------------------------------------------------------------------------- 101 | ; LVN_ENDLABELEDIT notification 102 | ; ------------------------------------------------------------------------------------------------------------------- 103 | EndLabelEdit(LV, L) { 104 | static OffText := 16 + (A_PtrSize * 4) 105 | critical 106 | this.LV.OnNotify(-176, this.EndLabelEditFunc, 0) 107 | OnMessage(0x0111, this.CommandFunc, 0) 108 | if (TxtPtr := numget(L, OffText, "UPtr")) { 109 | ItemText := strget(TxtPtr) 110 | if (ItemText != this.ItemText) 111 | LV.modify(this.Item + 1, "Col" . (this.Subitem + 1), ItemText) 112 | } 113 | return false 114 | } 115 | 116 | ; ------------------------------------------------------------------------------------------------------------------- 117 | ; WM_COMMAND notification 118 | ; ------------------------------------------------------------------------------------------------------------------- 119 | Command(W, L, M, H) { 120 | critical 121 | if (L = this.HEDT) { 122 | N := (W >> 16) & 0xFFFF 123 | if (N = 0x0400) || (N = 0x0300) || (N = 0x0100) { ; EN_UPDATE | EN_CHANGE | EN_SETFOCUS 124 | dllcall("SetWindowPos", "Ptr", L, "Ptr", 0, "int", this.CX, "int", this.CY, 125 | "int", this.CW, "int", this.CH, "uint", 0x04) 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/Socket.ahk: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * @description simple implementation of a socket Server and Client. 3 | * @author thqby 4 | * @date 2024/04/21 5 | * @version 1.0.4 6 | ***********************************************************************/ 7 | 8 | /** 9 | * Contains two base classes, `Socket.Server` and `Socket.Client`, 10 | * and handles asynchronous messages by implementing the `on%EventName%(err)` method of the class. 11 | * If none of these methods are implemented, it will be synchronous mode. 12 | */ 13 | class Socket { 14 | ; sock type 15 | static TYPE := { STREAM: 1, DGRAM: 2, RAW: 3, RDM: 4, SEQPACKET: 5 } 16 | ; address family 17 | static AF := { UNSPEC: 0, UNIX: 1, INET: 2, IPX: 6, APPLETALK: 16, NETBIOS: 17, INET6: 23, IRDA: 26, BTH: 32 } 18 | ; sock protocol 19 | static IPPROTO := { ICMP: 1, IGMP: 2, RFCOMM: 3, TCP: 6, UDP: 17, ICMPV6: 58, RM: 113 } 20 | static EVENT := { READ: 1, WRITE: 2, OOB: 4, ACCEPT: 8, CONNECT: 16, CLOSE: 32, QOS: 64, GROUP_QOS: 128, ROUTING_INTERFACE_CHANGE: 256, ADDRESS_LIST_CHANGE: 512 } 21 | ; flags of send/recv 22 | static MSG := { OOB: 1, PEEK: 2, DONTROUTE: 4, WAITALL: 8, INTERRUPT: 0x10, PUSH_IMMEDIATE: 0x20, PARTIAL: 0x8000 } 23 | static __sockets_table := Map() 24 | static __New() { 25 | #DllLoad ws2_32.dll 26 | if this != Socket 27 | throw Error('Invalid base class') 28 | if err := DllCall('ws2_32\WSAStartup', 'ushort', 0x0202, 'ptr', WSAData := Buffer(394 + A_PtrSize, 0)) 29 | throw OSError(err) 30 | if NumGet(WSAData, 2, 'ushort') != 0x0202 31 | throw Error('Winsock version 2.2 not available') 32 | this.DefineProp('__Delete', { call: (*) => DllCall('ws2_32\WSACleanup') }) 33 | proto := this.base.Prototype 34 | for k, v in { addr: '', async: 0, Ptr: -1 }.OwnProps() 35 | proto.DefineProp(k, { value: v }) 36 | for k in this.EVENT.OwnProps() 37 | k := 'On' StrTitle(k), proto.DefineProp(k, { set: get_setter(k) }) 38 | get_setter(name) { 39 | return (self, value) => (self.DefineProp(name, { call: value }), self.UpdateMonitoring()) 40 | } 41 | } 42 | static GetLastError() => DllCall('ws2_32\WSAGetLastError') 43 | 44 | class AddrInfo { 45 | static Prototype.size := 48 46 | static Call(host, port := 0) { 47 | if port { 48 | if err := DllCall('ws2_32\GetAddrInfoW', 'str', host, 'str', String(port), 'ptr', 0, 'ptr*', &addr := 0) 49 | throw OSError(err, -1) 50 | return { base: this.Prototype, ptr: addr, __Delete: this => DllCall('ws2_32\FreeAddrInfoW', 'ptr', this) } 51 | } 52 | ; struct sockaddr_un used to connect to AF_UNIX socket 53 | NumPut('ushort', 1, buf := Buffer(158, 0), 48), StrPut(host, buf.Ptr + 50, 'cp0') 54 | NumPut('int', 0, 'int', 1, 'int', 0, 'int', 0, 'uptr', 110, 'ptr', 0, 'ptr', buf.Ptr + 48, buf) 55 | return { base: this.Prototype, buf: buf, ptr: buf.Ptr } 56 | } 57 | flags => NumGet(this, 'int') 58 | family => NumGet(this, 4, 'int') 59 | socktype => NumGet(this, 8, 'int') 60 | protocol => NumGet(this, 12, 'int') 61 | addrlen => NumGet(this, 16, 'uptr') 62 | canonname => StrGet(NumGet(this, 16 + A_PtrSize, 'ptr') || StrPtr('')) 63 | addr => NumGet(this, 16 + 2 * A_PtrSize, 'ptr') 64 | next => (p := NumGet(this, 16 + 3 * A_PtrSize, 'ptr')) && ({ base: this.Base, ptr: p }) 65 | addrstr => (this.family = 1 ? StrGet(this.addr + 2, 'cp0') : !DllCall('ws2_32\WSAAddressToStringW', 'ptr', this.addr, 'uint', this.addrlen, 'ptr', 0, 'ptr', b := Buffer(s := 2048), 'uint*', &s) && StrGet(b)) 66 | } 67 | 68 | class base { 69 | __Delete() { 70 | if this.Ptr == -1 71 | return 72 | this.UpdateMonitoring(false) 73 | DllCall('ws2_32\closesocket', 'ptr', this) 74 | this.Ptr := -1 75 | } 76 | 77 | ; Gets the current message size of the receive buffer. 78 | MsgSize() { 79 | static FIONREAD := 0x4004667F 80 | if DllCall('ws2_32\ioctlsocket', 'ptr', this, 'uint', FIONREAD, 'uint*', &argp := 0) 81 | throw OSError(Socket.GetLastError()) 82 | return argp 83 | } 84 | 85 | ; Choose to receive the corresponding event according to the implemented method. `CONNECT` event is unimplemented 86 | UpdateMonitoring(start := true) { 87 | static FIONBIO := 0x8004667E, id_to_event := init_table() 88 | static WM_SOCKET := DllCall('RegisterWindowMessage', 'str', 'WM_AHK_SOCKET', 'uint') 89 | flags := 0 90 | if start 91 | for k, v in Socket.EVENT.OwnProps() 92 | if this.HasMethod('on' k) 93 | flags |= v 94 | if flags { 95 | Socket.__sockets_table[this.Ptr] := ObjPtr(this), this.async := 1 96 | OnMessage(WM_SOCKET, On_WM_SOCKET, 255) 97 | this.DefineProp('_async_select', { call: async_select }) 98 | } else { 99 | try { 100 | Socket.__sockets_table.Delete(this.Ptr) 101 | if !Socket.__sockets_table.Count 102 | OnMessage(WM_SOCKET, On_WM_SOCKET, 0) 103 | } 104 | } 105 | if this.async { 106 | DllCall('ws2_32\WSAAsyncSelect', 'ptr', this, 'ptr', A_ScriptHwnd, 'uint', WM_SOCKET, 'uint', flags) 107 | if !flags && start && !DllCall('ws2_32\ioctlsocket', 'ptr', this, 'int', FIONBIO, 'uint*', 0) 108 | this.async := 0 109 | } 110 | return flags 111 | static On_WM_SOCKET(wp, lp, *) { 112 | if !sk := Socket.__sockets_table.Get(wp, 0) 113 | return 114 | sk := ObjFromPtrAddRef(sk) 115 | sk._async_select(ev := lp & 0xffff, false) 116 | sk.On%id_to_event[ev]%((lp >> 16) & 0xffff) 117 | sk._async_select(ev) 118 | } 119 | async_select(this, _flags, add := true) { 120 | if _flags=32 121 | return 122 | if add 123 | flags |= _flags 124 | else flags &= ~_flags 125 | r := DllCall('ws2_32\WSAAsyncSelect', 'ptr', this, 'ptr', A_ScriptHwnd, 'uint', WM_SOCKET, 'uint', flags) 126 | } 127 | init_table() { 128 | m := Map() 129 | for k, v in Socket.EVENT.OwnProps() 130 | m[v] := k 131 | return m 132 | } 133 | } 134 | } 135 | 136 | class Client extends Socket.base { 137 | static Prototype.isConnected := 1 138 | /** 139 | * Create a socket client to connect to the specified server. 140 | * @param {String} host The name of host, if port is 0, the value should be the path of pipe or file. 141 | * @param {Number} port Listen to the specified port, and if it is 0, listen to the pipe or file. 142 | * @param {Socket.TYPE} socktype The type of socket. 143 | * @param {Socket.IPPROTO} protocol The protocol of socket. 144 | * @example https://github.com/thqby/ahk2_lib/issues/27 145 | */ 146 | __New(host, port?, socktype := Socket.TYPE.STREAM, protocol := 0) { 147 | this.addrinfo := host is Socket.AddrInfo ? host : Socket.AddrInfo(host, port?) 148 | last_family := -1, err := ai := 0 149 | loop { 150 | if !connect(this, A_Index > 1) || err == 10035 151 | return this.DefineProp('ReConnect', { call: connect }) 152 | } until !ai 153 | throw OSError(err, -1) 154 | connect(this, next := false) { 155 | this.isConnected := 0 156 | if !ai := !next ? (last_family := -1, this.addrinfo) : ai && ai.next 157 | return 10061 158 | if ai.family == 1 && SubStr(ai.addrstr, 1, 9) = '\\.\pipe\' 159 | token := { 160 | ptr: DllCall('CreateNamedPipeW', 'str', ai.addrstr, 'uint', 1, 'uint', 4, 'uint', 1, 'uint', 0, 'uint', 0, 'uint', 0, 'ptr', 0, 'ptr'), 161 | __Delete: this => DllCall('CloseHandle', 'ptr', this) 162 | } 163 | if last_family != ai.family && this.Ptr != -1 164 | this.__Delete() 165 | while this.Ptr == -1 { 166 | if -1 == this.Ptr := DllCall('ws2_32\socket', 'int', ai.family, 'int', socktype, 'int', protocol, 'ptr') 167 | return (err := Socket.GetLastError(), connect(this, 1), err) 168 | last_family := ai.family 169 | } 170 | this.addr := ai.addrstr, this.HasMethod('onConnect') && this.UpdateMonitoring() 171 | if !DllCall('ws2_32\connect', 'ptr', this, 'ptr', ai.addr, 'uint', ai.addrlen) 172 | return (this.UpdateMonitoring(), this.isConnected := 1, err := 0) 173 | return err := Socket.GetLastError() 174 | } 175 | } 176 | 177 | _OnConnect(err) { 178 | if !err 179 | this.isConnected := 1 180 | else if err == 10061 && (err := this.ReConnect(true)) == 10035 181 | return 182 | else throw OSError(err) 183 | } 184 | 185 | ; When it is a client, it is used to reconnect after disconnecting from the server. 186 | ReConnect(next := false) => 10061 187 | 188 | /** 189 | * Sends data on a connected socket. 190 | * @param {Buffer | Integer} buf The data to be transmitted. 191 | * @param {Integer} size The size of data. 192 | * @param {Socket.MSG} flags A set of flags that specify the way in which the call is made. 193 | * This parameter is constructed by using the bitwise OR operator with any of the following values. 194 | * - DONTROUTE — Specifies that the data should not be subject to routing. A Windows Sockets service provider can choose to ignore this flag. 195 | * - OOB — Sends OOB data (stream-style socket such as SOCK_STREAM only). 196 | * @returns {Integer} The total number of bytes sent. 197 | */ 198 | Send(buf, size?, flags := 0) { 199 | if (size := DllCall('ws2_32\send', 'ptr', this, 'ptr', buf, 'int', size ?? buf.Size, 'int', flags)) == -1 200 | throw OSError(Socket.GetLastError()) 201 | return size 202 | } 203 | 204 | SendText(text, flags := 0, encoding := 'utf-8') { 205 | buf := Buffer(StrPut(text, encoding) - ((encoding = 'utf-16' || encoding = 'cp1200') ? 2 : 1)) 206 | size := StrPut(text, buf, encoding) 207 | return this.Send(buf, size, flags) 208 | } 209 | 210 | /** 211 | * @param {Socket.MSG} flags A set of flags that specify the way in which the call is made. 212 | * This parameter is constructed by using the bitwise OR operator with any of the following values. 213 | * - OOB — Processes Out Of Band (OOB) data. 214 | * - PEEK — Peeks at the incoming data. The data is copied into the buffer, but is not removed from the input queue. 215 | * - WAITALL 216 | */ 217 | _recv(buf, size, flags := 0) => DllCall('ws2_32\recv', 'ptr', this, 'ptr', buf, 'int', size, 'int', flags) 218 | 219 | Recv(&buf, maxsize := 0x7fffffff, flags := 0, timeout := 0) { 220 | endtime := A_TickCount + timeout 221 | while !(size := this.MsgSize()) && (!timeout && !this.async || A_TickCount < endtime) 222 | Sleep(10) 223 | if !size 224 | return 0 225 | buf := Buffer(size := Min(maxsize, size)) 226 | if (size := this._recv(buf, size, flags)) == -1 227 | throw OSError(Socket.GetLastError()) 228 | return size 229 | } 230 | 231 | RecvText(flags := 0, timeout := 0, encoding := 'utf-8') { 232 | if size := this.Recv(&buf, , flags, timeout) 233 | return StrGet(buf, size, encoding) 234 | return '' 235 | } 236 | 237 | RecvLine(flags := 0, timeout := 0, encoding := 'utf-8') { 238 | static MSG_PEEK := Socket.MSG.PEEK 239 | endtime := A_TickCount + timeout, buf := Buffer(1, 0), t := flags | MSG_PEEK 240 | while !(pos := InStr((size := this.Recv(&buf, , t, timeout && (endtime - A_TickCount)), StrGet(buf, size, encoding)), '`n')) { 241 | if this.async || timeout && A_TickCount > endtime 242 | return '' 243 | Sleep(10) 244 | } 245 | size := this.Recv(&buf, pos * (encoding = 'utf-16' || encoding = 'cp1200' ? 2 : 1), flags) 246 | return StrGet(buf, size, encoding) 247 | } 248 | } 249 | 250 | class Server extends Socket.base { 251 | /** 252 | * Create a socket server to listen to the specified port or local file. 253 | * @param {Number} port Listen to the specified port, and if it is 0, listen to the pipe or file. 254 | * @param {String} host The name of host, if port is 0, the value should be the path of pipe or file. 255 | * @param {Socket.TYPE} socktype The type of socket. 256 | * @param {Socket.IPPROTO} protocol The protocol of socket. 257 | * @param {Integer} backlog The maximum length of the queue of pending connections. 258 | * @example https://github.com/thqby/ahk2_lib/issues/27 259 | */ 260 | __New(port := 0, host := '0.0.0.0', socktype := Socket.TYPE.STREAM, protocol := 0, backlog := 4) { 261 | _ := ai := Socket.AddrInfo(host, port), ptr := last_family := -1 262 | if ai.family == 1 263 | this.file := make_del_token(ai.addrstr) 264 | loop { 265 | if last_family != ai.family { 266 | (ptr != -1) && (DllCall('ws2_32\closesocket', 'ptr', ptr), this.Ptr := -1) 267 | if -1 == (ptr := DllCall('ws2_32\socket', 'int', ai.family, 'int', socktype, 'int', protocol, 'ptr')) 268 | continue 269 | last_family := ai.family, this.Ptr := ptr 270 | } 271 | if !DllCall('ws2_32\bind', 'ptr', ptr, 'ptr', ai.addr, 'uint', ai.addrlen, 'int') 272 | && !DllCall('ws2_32\listen', 'ptr', ptr, 'int', backlog) 273 | return (this.addr := ai.addrstr, this.UpdateMonitoring(), 0) 274 | } until !ai := ai.next 275 | throw OSError(Socket.GetLastError(), -1) 276 | make_del_token(file) { 277 | if SubStr(file, 1, 9) = '\\.\pipe\' 278 | token := { 279 | ptr: DllCall('CreateNamedPipeW', 'str', file, 'uint', 1, 'uint', 4, 'uint', backlog, 'uint', 0, 'uint', 0, 'uint', 0, 'ptr', 0, 'ptr'), 280 | __Delete: this => DllCall('CloseHandle', 'ptr', this) 281 | } 282 | else 283 | token := { file: file, __Delete: this => FileExist(this.file) && FileDelete(this.File) }, token.__Delete() 284 | return token 285 | } 286 | } 287 | 288 | _accept(&addr?) { 289 | if -1 == (ptr := DllCall('ws2_32\accept', 'ptr', this, 'ptr', addr := Buffer(addrlen := 128, 0), 'int*', &addrlen, 'ptr')) 290 | throw OSError(Socket.GetLastError()) 291 | if NumGet(addr, 'ushort') != 1 292 | DllCall('ws2_32\WSAAddressToStringW', 'ptr', addr, 'uint', addrlen, 'ptr', 0, 'ptr', b := Buffer(s := 2048), 'uint*', &s), addr := StrGet(b) 293 | else addr := this.addr 294 | return ptr 295 | } 296 | 297 | /** 298 | * Accept the connection of socket client. 299 | * @param {typeof Socket.Client} clientType The class for socket instantiation. 300 | * @returns {Socket.Client} 301 | */ 302 | AcceptAsClient(clientType := Socket.Client) { 303 | ptr := this._accept(&addr) 304 | sock := { base: clientType.Prototype, ptr: ptr, async: this.async, addr: addr } 305 | sock.UpdateMonitoring() 306 | return sock 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /lib/WatchFolder.ahk: -------------------------------------------------------------------------------- 1 | #Requires AutoHotKey v2.0-beta.1 2 | 3 | ; ====================================================================================================================== 4 | ; Function: Notifies about changes within folders. 5 | ; This is a rewrite of HotKeyIt's WatchDirectory() released at 6 | ; https://www.autohotkey.com/boards/viewtopic.php?t=95659 7 | ; https://github.com/AHK-just-me/WatchFolder 8 | ; Tested with: AHK 2.0-beta.1 (U32/U64) 9 | ; Tested on: Win 10 Pro x64 10 | ; Usage: WatchFolder(Folder, UserFunc[, SubTree := False[, Watch := 3]]) 11 | ; Parameters: 12 | ; Folder - The full qualified path of the folder to be watched. 13 | ; Pass the string "**PAUSE" and set UserFunc to either True or False to pause respectively resume 14 | ; watching. 15 | ; Pass the string "**END" and an arbitrary value in UserFunc to completely stop watching anytime. 16 | ; If not, it will be done internally on exit. 17 | ; UserFunc - The name of a user-defined function to call on changes. The function must accept at least 18 | ; two parameters: 19 | ; 1: The path of the affected folder. The final backslash is not included even if it is a drive's 20 | ; root directory (e.g. C:). 21 | ; 2: An array of change notifications containing the following keys: 22 | ; Action: One of the integer values specified as FILE_ACTION_... (see below). 23 | ; In case of renaming Action is set to FILE_ACTION_RENAMED (4). 24 | ; Name: The full path of the changed file or folder. 25 | ; OldName: The previous path in case of renaming, otherwise not used. 26 | ; IsDir: True if Name is a directory; otherwise False. In case of Action 2 (removed) IsDir 27 | ; is always False. 28 | ; Pass the string "**DEL" to remove the directory from the list of watched folders. 29 | ; SubTree - Set to true if you want the whole subtree to be watched (i.e. the contents of all sub-folders). 30 | ; Default: False - sub-folders aren't watched. 31 | ; Watch - The kind of changes to watch for. This can be one or any combination of the FILE_NOTIFY_CHANGES_... 32 | ; values specified below. 33 | ; Default: 0x03 - FILE_NOTIFY_CHANGE_FILE_NAME + FILE_NOTIFY_CHANGE_DIR_NAME 34 | ; Return values: 35 | ; Returns True on success; otherwise False. 36 | ; Change history: 37 | ; 1.0.00.00/2021-10-??/just me - initial release 38 | ; License: 39 | ; The Unlicense -> http://unlicense.org/ 40 | ; Remarks: 41 | ; Due to the limits of the API function WaitForMultipleObjects() you cannot watch more than 42 | ; MAXIMUM_WAIT_OBJECTS (64) folders simultaneously. 43 | ; MSDN: 44 | ; ReadDirectoryChangesW docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw 45 | ; FILE_NOTIFY_CHANGE_FILE_NAME = 1 (0x00000001) : Notify about renaming, creating, or deleting a file. 46 | ; FILE_NOTIFY_CHANGE_DIR_NAME = 2 (0x00000002) : Notify about creating or deleting a directory. 47 | ; FILE_NOTIFY_CHANGE_ATTRIBUTES = 4 (0x00000004) : Notify about attribute changes. 48 | ; FILE_NOTIFY_CHANGE_SIZE = 8 (0x00000008) : Notify about any file-size change. 49 | ; FILE_NOTIFY_CHANGE_LAST_WRITE = 16 (0x00000010) : Notify about any change to the last write-time of files. 50 | ; FILE_NOTIFY_CHANGE_LAST_ACCESS = 32 (0x00000020) : Notify about any change to the last access time of files. 51 | ; FILE_NOTIFY_CHANGE_CREATION = 64 (0x00000040) : Notify about any change to the creation time of files. 52 | ; FILE_NOTIFY_CHANGE_SECURITY = 256 (0x00000100) : Notify about any security-descriptor change. 53 | ; FILE_NOTIFY_INFORMATION docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-file_notify_information 54 | ; FILE_ACTION_ADDED = 1 (0x00000001) : The file was added to the directory. 55 | ; FILE_ACTION_REMOVED = 2 (0x00000002) : The file was removed from the directory. 56 | ; FILE_ACTION_MODIFIED = 3 (0x00000003) : The file was modified. 57 | ; FILE_ACTION_RENAMED = 4 (0x00000004) : The file was renamed (not defined by Microsoft). 58 | ; FILE_ACTION_RENAMED_OLD_NAME = 4 (0x00000004) : The file was renamed and this is the old name. 59 | ; FILE_ACTION_RENAMED_NEW_NAME = 5 (0x00000005) : The file was renamed and this is the new name. 60 | ; GetOverlappedResult docs.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getoverlappedresult 61 | ; CreateFile docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew 62 | ; FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 63 | ; FILE_FLAG_OVERLAPPED = 0x40000000 64 | ; ====================================================================================================================== 65 | WatchFolder(Folder, UserFunc, SubTree := False, Watch := 0x03) { 66 | ; Static DummyObject := {Base: {__Delete: Func("WatchFolder").Bind("**END", "")}} 67 | Static Dummy := OnExit(WatchFolder.Bind("**END", "Exit")) 68 | Static TimerID := "**" . A_TickCount 69 | Static TimerFunc := WatchFolder.Bind(TimerID, "") 70 | Static MAXIMUM_WAIT_OBJECTS := 64 71 | Static MAX_DIR_PATH := 260 - 12 + 1 72 | Static SizeOfFNI := 0xFFFF ; size of the FILE_NOTIFY_INFORMATION structure buffer (64 KB) 73 | Static SizeOfOVL := 32 ; size of the OVERLAPPED structure (64-bit) 74 | Static FolderObj := {} 75 | Static EventMap := Map() 76 | Static WaitObjects := 0 77 | Static BytesRead := 0 78 | Static Paused := False 79 | ; =================================================================================================================== 80 | If (Folder = "") 81 | Return False 82 | SetTimer(TimerFunc, 0) 83 | RebuildWaitObjects := False 84 | ; =================================================================================================================== 85 | If (Folder = TimerID) { ; called by timer 86 | If (ObjCount := EventMap.Count) && !Paused { 87 | ObjIndex := DllCall("WaitForMultipleObjects", "UInt", ObjCount, "Ptr", WaitObjects, "Int", 0, "UInt", 0, "UInt") 88 | While (ObjIndex >= 0) && (ObjIndex < ObjCount) { 89 | Event := NumGet(WaitObjects, ObjIndex * A_PtrSize, "UPtr") 90 | Folder := EventMap[Event] 91 | If DllCall("GetOverlappedResult", "Ptr", Folder.Handle, "Ptr", Folder.OVL, "UIntP", &BytesRead, "Int", True) { 92 | Changes := [] 93 | FNIAddr := Folder.FNI.Ptr 94 | FNIMax := FNIAddr + BytesRead 95 | OffSet := 0 96 | PrevIndex := 0 97 | PrevAction := 0 98 | PrevName := "" 99 | Loop { 100 | FNIAddr += Offset 101 | OffSet := NumGet(FNIAddr + 0, "UInt") 102 | Action := NumGet(FNIAddr + 4, "UInt") 103 | Length := NumGet(FNIAddr + 8, "UInt") // 2 104 | Name := Folder.Folder . "\" . StrGet(FNIAddr + 12, Length, "UTF-16") 105 | IsDir := InStr(FileExist(Name), "D") ? 1 : 0 106 | PrevIndex := Changes.Length 107 | If (Name = PrevName) { 108 | If (Action = PrevAction) 109 | Continue 110 | If (Action = 1) && (PrevAction = 2) { 111 | PrevAction := Action 112 | Changes.RemoveAt(PrevIndex) 113 | Continue 114 | } 115 | } 116 | If (Action = 4) 117 | Changes.Push({Action: Action, IsDir: 0, Name: "", OldName: Name}) 118 | Else If (Action = 5) && (PrevAction = 4) { 119 | Changes[PrevIndex].Name := Name 120 | Changes[PrevIndex].IsDir := IsDir 121 | } 122 | Else 123 | Changes.Push({Action: Action, IsDir: IsDir, Name: Name, OldName: ""}) 124 | PrevAction := Action 125 | PrevName := Name 126 | } Until (Offset = 0) || ((FNIAddr + Offset) > FNIMax) 127 | If (Changes.Length > 0) 128 | Folder.Func.Call(Folder.Folder, Changes) 129 | DllCall("ResetEvent", "Ptr", Event) 130 | DllCall("ReadDirectoryChangesW", "Ptr", Folder.Handle, "Ptr", Folder.FNI, "UInt", SizeOfFNI, 131 | "Int", Folder.SubTree, "UInt", Folder.Watch, "UInt", 0, 132 | "Ptr", Folder.OVL, "Ptr", 0) 133 | } 134 | ObjIndex := DllCall("WaitForMultipleObjects", "UInt", ObjCount, "Ptr", WaitObjects, "Int", 0, "UInt", 0, "UInt") 135 | Sleep(0) 136 | } 137 | } 138 | } 139 | ; =================================================================================================================== 140 | Else If (Folder = "**PAUSE") { ; called to pause/resume watching 141 | Paused := !!UserFunc 142 | RebuildObjects := Paused 143 | } 144 | ; =================================================================================================================== 145 | Else If (Folder = "**END") { ; called to stop watching 146 | For Event, Folder In EventMap { 147 | DllCall("CloseHandle", "Ptr", Event) 148 | DllCall("CloseHandle", "Ptr", Folder.Handle) 149 | } 150 | FolderObj := {} 151 | EventMap := [] 152 | Paused := False 153 | Return (UserFunc = "Exit" ? False : True) 154 | } 155 | ; =================================================================================================================== 156 | Else { ; called to add, update, or remove folders 157 | Folder := RTrim(Folder, "\") 158 | LongPath := "" 159 | VarSetStrCapacity(&LongPath, MAX_DIR_PATH) 160 | If !DllCall("GetLongPathNameW", "Str", Folder, "Ptr", StrPtr(LongPath), "UInt", MAX_DIR_PATH, "UInt") 161 | Return False 162 | VarSetStrCapacity(&LongPath, -1) 163 | Folder := LongPath 164 | If FolderObj.HasOwnProp(Folder) { ; update or remove 165 | Event := FolderObj.%Folder% 166 | DllCall("CloseHandle", "Ptr", EventMap[Event].Handle) 167 | DllCall("CloseHandle", "Ptr", Event) 168 | EventMap.Delete(Event) 169 | FolderObj.DeleteProp(Folder) 170 | RebuildWaitObjects := True 171 | } 172 | If InStr(FileExist(Folder), "D") && (UserFunc != "**DEL") && (EventMap.Count < MAXIMUM_WAIT_OBJECTS) { 173 | If (UserFunc Is Func) && (UserFunc.MinParams >= 2) && (Watch &= 0x017F) { 174 | Handle := DllCall("CreateFile", "Str", Folder . "\", "UInt", 0x01, "UInt", 0x07, "Ptr",0, "UInt", 0x03, 175 | "UInt", 0x42000000, "Ptr", 0, "UPtr") 176 | If (Handle > 0) { 177 | Event := DllCall("CreateEvent", "Ptr", 0, "Int", 1, "Int", 0, "Ptr", 0) 178 | FNI := Buffer(SizeOfFNI, 0) 179 | OVL := Buffer(SizeOfOVL, 0) 180 | NumPut("Ptr", Event, OVL, 8 + (A_PtrSize * 2)) 181 | DllCall("ReadDirectoryChangesW", "Ptr", Handle, "Ptr", FNI, "UInt", SizeOfFNI, "Int", SubTree 182 | , "UInt", Watch, "UInt", 0, "Ptr", OVL, "Ptr", 0) 183 | EventMap[Event] := {Folder: Folder, Func: UserFunc, Handle: Handle, Subtree: !!SubTree, 184 | Watch: Watch, FNI: FNI, OVL: OVL} 185 | FolderObj.%Folder% := Event 186 | RebuildWaitObjects := True 187 | } 188 | } 189 | } 190 | If (RebuildWaitObjects) { 191 | WaitObjects := Buffer(MAXIMUM_WAIT_OBJECTS * A_PtrSize, 0) 192 | Addr := WaitObjects.Ptr 193 | For Event In EventMap 194 | Addr := NumPut("Ptr", Event, Addr) 195 | } 196 | } 197 | ; =================================================================================================================== 198 | If (EventMap.Count > 0) 199 | SetTimer(TimerFunc, -100) 200 | Return (RebuildWaitObjects) ; returns True on success, otherwise False 201 | } 202 | -------------------------------------------------------------------------------- /lib/WebSocket.ahk: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * @description The websocket client implemented through winhttp, 3 | * requires that the system version be no less than win8. 4 | * @author thqby 5 | * @date 2024/01/27 6 | * @version 1.0.7 7 | ***********************************************************************/ 8 | 9 | #DllLoad winhttp.dll 10 | class WebSocket { 11 | Ptr := 0, async := 0, readyState := 0, url := '' 12 | 13 | ; The array of HINTERNET handles, [hSession, hConnect, hRequest(onOpen) | hWebSocket?] 14 | HINTERNETs := [] 15 | 16 | ; when request is opened 17 | onOpen() => 0 18 | ; when server sent a close frame 19 | onClose(status, reason) => 0 20 | ; when server sent binary message 21 | onData(data, size) => 0 22 | ; when server sent UTF-8 message 23 | onMessage(msg) => 0 24 | reconnect() => 0 25 | 26 | /** 27 | * @param {String} Url the url of websocket 28 | * @param {Object} Events an object of `{open:(this)=>void,data:(this, data, size)=>bool,message:(this, msg)=>bool,close:(this, status, reason)=>void}` 29 | * @param {Integer} Async Use asynchronous mode 30 | * @param {Object|Map|String} Headers Additional request headers to use when creating connections 31 | * @param {Integer} TimeOut Set resolve, connect, send and receive timeout 32 | */ 33 | __New(Url, Events := 0, Async := true, Headers := '', TimeOut := 0, InitialSize := 8192) { 34 | static contexts := Map() 35 | if (!RegExMatch(Url, 'i)^((?wss?)://)?((?[^:]+):(?.+)@)?(?[^/:\s]+)(:(?\d+))?(?/\S*)?$', &m)) 36 | Throw WebSocket.Error('Invalid websocket url') 37 | if !hSession := DllCall('Winhttp\WinHttpOpen', 'ptr', 0, 'uint', 0, 'ptr', 0, 'ptr', 0, 'uint', Async ? 0x10000000 : 0, 'ptr') 38 | Throw WebSocket.Error() 39 | this.async := Async := !!Async, this.url := Url 40 | this.HINTERNETs.Push(hSession) 41 | port := m.PORT ? Integer(m.PORT) : m.SCHEME = 'ws' ? 80 : 443 42 | dwFlags := m.SCHEME = 'wss' ? 0x800000 : 0 43 | if TimeOut 44 | DllCall('Winhttp\WinHttpSetTimeouts', 'ptr', hSession, 'int', TimeOut, 'int', TimeOut, 'int', TimeOut, 'int', TimeOut, 'int') 45 | if !hConnect := DllCall('Winhttp\WinHttpConnect', 'ptr', hSession, 'wstr', m.HOST, 'ushort', port, 'uint', 0, 'ptr') 46 | Throw WebSocket.Error() 47 | this.HINTERNETs.Push(hConnect) 48 | switch Type(Headers) { 49 | case 'Object', 'Map': 50 | s := '' 51 | for k, v in Headers is Map ? Headers : Headers.OwnProps() 52 | s .= '`r`n' k ': ' v 53 | Headers := LTrim(s, '`r`n') 54 | case 'String': 55 | default: 56 | Headers := '' 57 | } 58 | if (Events) { 59 | for k, v in Events.OwnProps() 60 | if (k ~= 'i)^(open|data|message|close)$') 61 | this.DefineProp('on' k, { call: v }) 62 | } 63 | if (Async) { 64 | this.DefineProp('shutdown', { call: async_shutdown }) 65 | .DefineProp('receive', { call: receive }) 66 | .DefineProp('_send', { call: async_send }) 67 | } else this.__cache_size := InitialSize 68 | connect(this), this.DefineProp('reconnect', { call: connect }) 69 | 70 | connect(self) { 71 | if !self.HINTERNETs.Length 72 | Throw WebSocket.Error('The connection is closed') 73 | self.shutdown() 74 | if !hRequest := DllCall('Winhttp\WinHttpOpenRequest', 'ptr', hConnect, 'wstr', 'GET', 'wstr', m.PATH, 'ptr', 0, 'ptr', 0, 'ptr', 0, 'uint', dwFlags, 'ptr') 75 | Throw WebSocket.Error() 76 | self.HINTERNETs.Push(hRequest), self.onOpen() 77 | if (Headers) 78 | DllCall('Winhttp\WinHttpAddRequestHeaders', 'ptr', hRequest, 'wstr', Headers, 'uint', -1, 'uint', 0x20000000, 'int') 79 | if (!DllCall('Winhttp\WinHttpSetOption', 'ptr', hRequest, 'uint', 114, 'ptr', 0, 'uint', 0, 'int') 80 | || !DllCall('Winhttp\WinHttpSendRequest', 'ptr', hRequest, 'ptr', 0, 'uint', 0, 'ptr', 0, 'uint', 0, 'uint', 0, 'uptr', 0, 'int') 81 | || !DllCall('Winhttp\WinHttpReceiveResponse', 'ptr', hRequest, 'ptr', 0) 82 | || !DllCall('Winhttp\WinHttpQueryHeaders', 'ptr', hRequest, 'uint', 19, 'ptr', 0, 'wstr', status := '00000', 'uint*', 10, 'ptr', 0, 'int') 83 | || status != '101') 84 | Throw IsSet(status) ? WebSocket.Error('Invalid status: ' status) : WebSocket.Error() 85 | if !self.Ptr := DllCall('Winhttp\WinHttpWebSocketCompleteUpgrade', 'ptr', hRequest, 'ptr', 0) 86 | Throw WebSocket.Error() 87 | DllCall('Winhttp\WinHttpCloseHandle', 'ptr', self.HINTERNETs.Pop()) 88 | self.HINTERNETs.Push(self.Ptr), self.readyState := 1 89 | (Async && async_receive(self)) 90 | } 91 | 92 | async_receive(self) { 93 | static on_read_complete := get_sync_callback(), hHeap := DllCall('GetProcessHeap', 'ptr') 94 | static msg_gui := Gui(), wm_ahkmsg := DllCall('RegisterWindowMessage', 'str', 'AHK_WEBSOCKET_STATUSCHANGE', 'uint') 95 | static pHeapReAlloc := DllCall('GetProcAddress', 'ptr', DllCall('GetModuleHandle', 'str', 'kernel32', 'ptr'), 'astr', 'HeapReAlloc', 'ptr') 96 | static pSendMessageW := DllCall('GetProcAddress', 'ptr', DllCall('GetModuleHandle', 'str', 'user32', 'ptr'), 'astr', 'SendMessageW', 'ptr') 97 | static pWinHttpWebSocketReceive := DllCall('GetProcAddress', 'ptr', DllCall('GetModuleHandle', 'str', 'winhttp', 'ptr'), 'astr', 'WinHttpWebSocketReceive', 'ptr') 98 | static _ := (OnMessage(wm_ahkmsg, WEBSOCKET_READ_WRITE_COMPLETE, 0xff), DllCall('SetParent', 'ptr', msg_gui.Hwnd, 'ptr', -3)) 99 | ; #DllLoad E:\projects\test\test\x64\Debug\test.dll 100 | ; on_read_complete := DllCall('GetProcAddress', 'ptr', DllCall('GetModuleHandle', 'str', 'test', 'ptr'), 'astr', 'WINHTTP_STATUS_READ_COMPLETE', 'ptr') 101 | NumPut('ptr', pws := ObjPtr(self), 'ptr', msg_gui.Hwnd, 'uint', wm_ahkmsg, 'uint', InitialSize, 'ptr', hHeap, 102 | 'ptr', cache := DllCall('HeapAlloc', 'ptr', hHeap, 'uint', 0, 'uptr', InitialSize, 'ptr'), 'uptr', 0, 'uptr', InitialSize, 103 | 'ptr', pHeapReAlloc, 'ptr', pSendMessageW, 'ptr', pWinHttpWebSocketReceive, 104 | contexts[pws] := context := Buffer(11 * A_PtrSize)), self.__send_queue := [] 105 | context.DefineProp('__Delete', { call: self => DllCall('HeapFree', 'ptr', hHeap, 'uint', 0, 'ptr', NumGet(self, 3 * A_PtrSize + 8, 'ptr')) }) 106 | DllCall('Winhttp\WinHttpSetOption', 'ptr', self, 'uint', 45, 'ptr*', context.Ptr, 'uint', A_PtrSize) 107 | DllCall('Winhttp\WinHttpSetStatusCallback', 'ptr', self, 'ptr', on_read_complete, 'uint', 0x80000, 'uptr', 0, 'ptr') 108 | if err := DllCall('Winhttp\WinHttpWebSocketReceive', 'ptr', self, 'ptr', cache, 'uint', InitialSize, 'uint*', 0, 'uint*', 0) 109 | self.onError(err) 110 | } 111 | 112 | static WEBSOCKET_READ_WRITE_COMPLETE(wp, lp, msg, hwnd) { 113 | static map_has := Map.Prototype.Has 114 | if !map_has(contexts, ws := NumGet(wp, 'ptr')) || (ws := ObjFromPtrAddRef(ws)).readyState != 1 115 | return 116 | switch lp { 117 | case 5: ; WRITE_COMPLETE 118 | try ws.__send_queue.Pop() 119 | case 4: ; WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE 120 | if err := NumGet(wp, A_PtrSize, 'uint') 121 | return ws.onError(err) 122 | rea := ws.QueryCloseStatus(), ws.shutdown() 123 | return ws.onClose(rea.status, rea.reason) 124 | default: ; WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE 125 | data := NumGet(wp, A_PtrSize, 'ptr') 126 | size := NumGet(wp, 2 * A_PtrSize, 'uptr') 127 | if lp == 2 128 | return ws.onMessage(StrGet(data, size, 'utf-8')) 129 | else return ws.onData(data, size) 130 | } 131 | } 132 | 133 | static async_send(self, type, buf, size) { 134 | if (self.readyState != 1) 135 | Throw WebSocket.Error('websocket is disconnected') 136 | (q := self.__send_queue).InsertAt(1, buf) 137 | while (err := DllCall('Winhttp\WinHttpWebSocketSend', 'ptr', self, 'uint', type, 'ptr', buf, 'uint', size, 'uint')) = 4317 && A_Index < 60 138 | Sleep(15) 139 | if err 140 | q.RemoveAt(1), self.onError(err) 141 | } 142 | 143 | static async_shutdown(self) { 144 | if self.Ptr 145 | DllCall('Winhttp\WinHttpSetOption', 'ptr', self, 'uint', 45, 'ptr*', 0, 'uint', A_PtrSize) 146 | (WebSocket.Prototype.shutdown)(self) 147 | try contexts.Delete(ObjPtr(self)) 148 | } 149 | 150 | static get_sync_callback() { 151 | mcodes := ['g+wMVot0JBiF9g+E0QAAAItEJBw9AAAQAHUVi0YkagVW/3YI/3YE/9Beg8QMwhQAPQAACAAPhaYAAACLBolEJASLRCQgU1VXi1AEx0QkFAAAAADHRCQYAAAAAIP6BHRsi04Yi+qLAI0MAYlOGIPlAXV2i0YUiUQkFI1EJBBSUP92CItGJP92BIlMJCjHRhgAAAAA/9CNfhyFwHQHi14MOx91UYsHK0YYagBqAFCLRhQDRhhQ/3QkMItGKP/QhcB0HT3dEAAAdBaJRCQUagSNRCQUUP92CItGJP92BP/QX11bXoPEDMIUAIteHI1+HDvLcrED24tGIFP/dhRqAP92EP/QhcB0B4lGFIkf65aF7XSSx0QkFA4AB4DrsQ==', 152 | 'SIXSD4QvAQAASIlcJCBBVkiD7FBIi9pMi/FBgfgAABAAdR9Ii0sITIvCi1IQQbkFAAAA/1NASItcJHhIg8RQQV7DQYH4AAAIAA+F3gAAAEiLAkljUQRIiWwkYEiJRCQwM8BIiXQkaEiJfCRwSMdEJDgAAAAASIlEJECD+gQPhIYAAABFiwGL6kiLQyhNjQQATIlDKIPlAQ+FnAAAAEiLQyBMi8qLUxBIi0sITIlEJEBMjUQkMEiJRCQ4SMdDKAAAAAD/U0BIjXswSIXAdAiLcxRIOzd1c0SLB0UzyUiLUyBJi85EK0MoSANTKEjHRCQgAAAAAP9TSIXAdCM93RAAAHQci8BIiUQkOItTEEyNRCQwSItLCEG5BAAAAP9TQEiLdCRoSItsJGBIi3wkcEiLXCR4SIPEUEFew0iLczBIjXswTDvGcpBIA/ZMi0MgTIvOSItLGDPS/1M4SIXAdAxIiUMgSIk36Wz///+F7Q+EZP///0jHRCQ4DgAHgOuM'] 153 | DllCall('crypt32\CryptStringToBinary', 'str', hex := mcodes[A_PtrSize >> 2], 'uint', 0, 'uint', 1, 'ptr', 0, 'uint*', &s := 0, 'ptr', 0, 'ptr', 0) && 154 | DllCall('crypt32\CryptStringToBinary', 'str', hex, 'uint', 0, 'uint', 1, 'ptr', code := Buffer(s), 'uint*', &s, 'ptr', 0, 'ptr', 0) && 155 | DllCall('VirtualProtect', 'ptr', code, 'uint', s, 'uint', 0x40, 'uint*', 0) 156 | return code 157 | /*c++ source, /FAc /O2 /GS- 158 | struct Context { 159 | void *obj; 160 | HWND hwnd; 161 | UINT msg; 162 | UINT initial_size; 163 | HANDLE heap; 164 | BYTE *data; 165 | size_t size; 166 | size_t capacity; 167 | decltype(&HeapReAlloc) ReAlloc; 168 | decltype(&SendMessageW) Send; 169 | decltype(&WinHttpWebSocketReceive) Receive; 170 | }; 171 | void __stdcall WINHTTP_STATUS_READ_WRITE_COMPLETE( 172 | void *hInternet, 173 | Context *dwContext, 174 | DWORD dwInternetStatus, 175 | WINHTTP_WEB_SOCKET_STATUS *lpvStatusInformation, 176 | DWORD dwStatusInformationLength) { 177 | if (!dwContext) 178 | return; 179 | auto &context = *dwContext; 180 | if (dwInternetStatus == WINHTTP_CALLBACK_FLAG_WRITE_COMPLETE) 181 | return (void)context.Send(context.hwnd, context.msg, (WPARAM)dwContext, 5); 182 | else if (dwInternetStatus != WINHTTP_CALLBACK_FLAG_READ_COMPLETE) 183 | return; 184 | UINT_PTR param[3] = { (UINT_PTR)context.obj, 0 }; 185 | DWORD err; 186 | switch (auto bt = lpvStatusInformation->eBufferType) 187 | { 188 | case WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE: 189 | goto close; 190 | default: 191 | size_t new_size; 192 | auto is_fragment = bt & 1; 193 | context.size += lpvStatusInformation->dwBytesTransferred; 194 | if (!is_fragment) { 195 | param[1] = (UINT_PTR)context.data; 196 | param[2] = context.size; 197 | context.size = 0; 198 | if (!context.Send(context.hwnd, context.msg, (WPARAM)param, bt) || 199 | (new_size = (size_t)context.initial_size) == context.capacity) 200 | break; 201 | } 202 | else if (context.size >= context.capacity) 203 | new_size = context.capacity << 1; 204 | else break; 205 | if (auto p = context.ReAlloc(context.heap, 0, context.data, new_size)) 206 | context.data = (BYTE *)p, context.capacity = new_size; 207 | else if (is_fragment) { 208 | param[1] = E_OUTOFMEMORY; 209 | goto close; 210 | } 211 | break; 212 | } 213 | err = context.Receive(hInternet, context.data + context.size, DWORD(context.capacity - context.size), 0, 0); 214 | if (err && err != ERROR_INVALID_OPERATION) { 215 | param[1] = err; 216 | close: context.Send(context.hwnd, context.msg, (WPARAM)param, WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE); 217 | } 218 | }*/ 219 | } 220 | 221 | static receive(*) { 222 | Throw WebSocket.Error('Used only in synchronous mode') 223 | } 224 | } 225 | 226 | __Delete() { 227 | this.shutdown() 228 | while (this.HINTERNETs.Length) 229 | DllCall('Winhttp\WinHttpCloseHandle', 'ptr', this.HINTERNETs.Pop()) 230 | } 231 | 232 | onError(err, what := 0) { 233 | if err != 12030 234 | Throw WebSocket.Error(err, what - 5) 235 | if this.readyState == 3 236 | return 237 | this.readyState := 3 238 | try this.onClose(1006, '') 239 | } 240 | 241 | class Error extends Error { 242 | __New(err := A_LastError, what := -4) { 243 | static module := DllCall('GetModuleHandle', 'str', 'winhttp', 'ptr') 244 | if err is Integer 245 | if (DllCall("FormatMessage", "uint", 0x900, "ptr", module, "uint", err, "uint", 0, "ptr*", &pstr := 0, "uint", 0, "ptr", 0), pstr) 246 | err := (msg := StrGet(pstr), DllCall('LocalFree', 'ptr', pstr), msg) 247 | else err := OSError(err).Message 248 | super.__New(err, what) 249 | } 250 | } 251 | 252 | queryCloseStatus() { 253 | if (!DllCall('Winhttp\WinHttpWebSocketQueryCloseStatus', 'ptr', this, 'ushort*', &usStatus := 0, 'ptr', vReason := Buffer(123), 'uint', 123, 'uint*', &len := 0)) 254 | return { status: usStatus, reason: StrGet(vReason, len, 'utf-8') } 255 | else if (this.readyState > 1) 256 | return { status: 1006, reason: '' } 257 | } 258 | 259 | /** @param type BINARY_MESSAGE = 0, BINARY_FRAGMENT = 1, UTF8_MESSAGE = 2, UTF8_FRAGMENT = 3 */ 260 | _send(type, buf, size) { 261 | if (this.readyState != 1) 262 | Throw WebSocket.Error('websocket is disconnected') 263 | if err := DllCall('Winhttp\WinHttpWebSocketSend', 'ptr', this, 'uint', type, 'ptr', buf, 'uint', size, 'uint') 264 | return this.onError(err) 265 | } 266 | 267 | ; sends a utf-8 string to the server 268 | sendText(str) { 269 | if (size := StrPut(str, 'utf-8') - 1) { 270 | StrPut(str, buf := Buffer(size), 'utf-8') 271 | this._send(2, buf, size) 272 | } else 273 | this._send(2, 0, 0) 274 | } 275 | 276 | send(buf) => this._send(0, buf, buf.Size) 277 | 278 | receive() { 279 | if (this.readyState != 1) 280 | Throw WebSocket.Error('websocket is disconnected') 281 | ptr := (cache := Buffer(size := this.__cache_size)).Ptr, offset := 0 282 | while (!err := DllCall('Winhttp\WinHttpWebSocketReceive', 'ptr', this, 'ptr', ptr + offset, 'uint', size - offset, 'uint*', &dwBytesRead := 0, 'uint*', &eBufferType := 0)) { 283 | switch eBufferType { 284 | case 1, 3: 285 | offset += dwBytesRead 286 | if offset == size 287 | cache.Size := size *= 2, ptr := cache.Ptr 288 | case 0, 2: 289 | offset += dwBytesRead 290 | if eBufferType == 2 291 | return StrGet(ptr, offset, 'utf-8') 292 | cache.Size := offset 293 | return cache 294 | case 4: 295 | rea := this.QueryCloseStatus(), this.shutdown() 296 | try this.onClose(rea.status, rea.reason) 297 | return 298 | } 299 | } 300 | (err != 4317 && this.onError(err)) 301 | } 302 | 303 | shutdown() { 304 | if (this.readyState = 1) { 305 | this.readyState := 2 306 | DllCall('Winhttp\WinHttpWebSocketClose', 'ptr', this, 'ushort', 1006, 'ptr', 0, 'uint', 0) 307 | this.readyState := 3 308 | } 309 | while (this.HINTERNETs.Length > 2) 310 | DllCall('Winhttp\WinHttpCloseHandle', 'ptr', this.HINTERNETs.Pop()) 311 | this.Ptr := 0 312 | } 313 | } 314 | 315 | ; ws := WebSocket(wss_or_ws_url, { 316 | ; message: (self, data) => FileAppend(Data '`n', '*', 'utf-8'), 317 | ; close: (self, status, reason) => FileAppend(status ' ' reason '`n', '*', 'utf-8') 318 | ; }) 319 | ; ws.sendText('hello'), Sleep(100) 320 | ; ws.send(0, Buffer(10), 10), Sleep(100) 321 | -------------------------------------------------------------------------------- /lib/WinHttpRequest.ahk: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * @file: WinHttpRequest.ahk 3 | * @description: 网络请求库 4 | * @author thqby 5 | * @date 2021/08/01 6 | * @version 0.0.18 7 | ***********************************************************************/ 8 | 9 | class WinHttpRequest { 10 | static AutoLogonPolicy := { 11 | Always: 0, 12 | OnlyIfBypassProxy: 1, 13 | Never: 2 14 | } 15 | static Option := { 16 | UserAgentString: 0, 17 | URL: 1, 18 | URLCodePage: 2, 19 | EscapePercentInURL: 3, 20 | SslErrorIgnoreFlags: 4, 21 | SelectCertificate: 5, 22 | EnableRedirects: 6, 23 | UrlEscapeDisable: 7, 24 | UrlEscapeDisableQuery: 8, 25 | SecureProtocols: 9, 26 | EnableTracing: 10, 27 | RevertImpersonationOverSsl: 11, 28 | EnableHttpsToHttpRedirects: 12, 29 | EnablePassportAuthentication: 13, 30 | MaxAutomaticRedirects: 14, 31 | MaxResponseHeaderSize: 15, 32 | MaxResponseDrainSize: 16, 33 | EnableHttp1_1: 17, 34 | EnableCertificateRevocationCheck: 18, 35 | RejectUserpwd: 19 36 | } 37 | static PROXYSETTING := { 38 | PRECONFIG: 0, 39 | DIRECT: 1, 40 | PROXY: 2 41 | } 42 | static SETCREDENTIALSFLAG := { 43 | SERVER: 0, 44 | PROXY: 1 45 | } 46 | static SecureProtocol := { 47 | SSL2: 0x08, 48 | SSL3: 0x20, 49 | TLS1: 0x80, 50 | TLS1_1: 0x200, 51 | TLS1_2: 0x800, 52 | All: 0xA8 53 | } 54 | static SslErrorFlag := { 55 | UnknownCA: 0x0100, 56 | CertWrongUsage: 0x0200, 57 | CertCNInvalid: 0x1000, 58 | CertDateInvalid: 0x2000, 59 | Ignore_All: 0x3300 60 | } 61 | 62 | __New(UserAgent := unset) { 63 | (this.whr := ComObject('WinHttp.WinHttpRequest.5.1')).Option[0] := IsSet(UserAgent) ? UserAgent : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68' 64 | } 65 | 66 | request(url, method := 'GET', post_data?, headers := {}) { 67 | this.Open(method, url) 68 | for k, v in headers.OwnProps() 69 | this.SetRequestHeader(k, v) 70 | if (isset(post_data) && isobject(post_data)) 71 | post_data := json.stringify(post_data) 72 | this.Send(post_data?) 73 | return this.ResponseText 74 | } 75 | enableRequestEvents(Enable := true) { 76 | static vtable := init_vtable() 77 | if !Enable 78 | return this._ievents := this._ref := 0 79 | if this._ievents 80 | return 81 | IConnectionPointContainer := ComObjQuery(pwhr := ComObjValue(this.whr), '{B196B284-BAB4-101A-B69C-00AA00341D07}') 82 | DllCall('ole32\CLSIDFromString', 'str', '{F97F4E15-B787-4212-80D1-D380CBBF982E}', 'ptr', IID_IWinHttpRequestEvents := Buffer(16)) 83 | ComCall(4, IConnectionPointContainer, 'ptr', IID_IWinHttpRequestEvents, 'ptr*', IConnectionPoint := ComValue(0xd, 0)) ; IConnectionPointContainer->FindConnectionPoint 84 | IWinHttpRequestEvents := Buffer(3 * A_PtrSize) 85 | NumPut('ptr', vtable.Ptr, 'ptr', ObjPtr(this), 'ptr', ObjPtr(IWinHttpRequestEvents), IWinHttpRequestEvents) 86 | ComCall(5, IConnectionPoint, 'ptr', IWinHttpRequestEvents, 'uint*', &dwCookie := 0) ; IConnectionPoint->Advise 87 | this._ievents := { __Delete: (*) => ComCall(6, IConnectionPoint, 'uint', dwCookie) } 88 | static init_vtable() { 89 | vtable := Buffer(A_PtrSize * 7), offset := vtable.Ptr 90 | for nParam in StrSplit('3113213') 91 | offset := NumPut('ptr', CallbackCreate(EventHandler.Bind(A_Index), , Integer(nParam)), offset) 92 | vtable.DefineProp('__Delete', { call: __Delete }) 93 | return vtable 94 | static EventHandler(index, this, arg1 := 0, arg2 := 0) { 95 | if (index < 4) { 96 | IEvents := NumGet(this, A_PtrSize * 2, 'ptr') 97 | if index == 1 98 | NumPut('ptr', this, arg2) 99 | if index == 3 100 | ObjRelease(IEvents) 101 | else ObjAddRef(IEvents) 102 | return 0 103 | } 104 | req := ObjFromPtrAddRef(NumGet(this, A_PtrSize, 'ptr')) 105 | req.readyState := index - 2 106 | switch index { 107 | case 4: ; OnResponseStart 108 | try req.OnResponseStart(arg1, StrGet(arg2, 'utf-16')) 109 | case 5: ; OnResponseDataAvailable 110 | try req.OnResponseDataAvailable( 111 | NumGet((pSafeArray := NumGet(arg1, 'ptr')) + 8 + A_PtrSize, 'ptr'), 112 | NumGet(pSafeArray + 8 + A_PtrSize * 2, 'uint')) 113 | case 6: ; OnResponseFinished 114 | try req._ref := 0, req.OnResponseFinished() 115 | case 7: ; OnError 116 | try req.readyState := req._ref := 0, req.OnError(arg1, StrGet(arg2, 'utf-16')) 117 | } 118 | } 119 | static __Delete(this) { 120 | loop 7 121 | CallbackFree(NumGet(this, (A_Index - 1) * A_PtrSize, 'ptr')) 122 | } 123 | } 124 | } 125 | 126 | ;#region IWinHttpRequest https://learn.microsoft.com/en-us/windows/win32/winhttp/iwinhttprequest-interface 127 | SetProxy(ProxySetting, ProxyServer, BypassList) => this.whr.SetProxy(ProxySetting, ProxyServer, BypassList) 128 | SetCredentials(UserName, Password, Flags) => this.whr.SetCredentials(UserName, Password, Flags) 129 | SetRequestHeader(Header, Value) => this.whr.SetRequestHeader(Header, Value) 130 | GetResponseHeader(Header) => this.whr.GetResponseHeader(Header) 131 | GetAllResponseHeaders() => this.whr.GetAllResponseHeaders() 132 | Send(Body?) { 133 | if this._ievents 134 | this._ref := this 135 | return this.whr.Send(Body?) 136 | } 137 | Open(verb, url, async := false) { 138 | this.readyState := 0 139 | this.whr.Open(verb, url, async) 140 | this.readyState := 1 141 | } 142 | WaitForResponse(Timeout := -1) => this.whr.WaitForResponse(Timeout) 143 | Abort() => (this._ref := this.readyState := 0, this.whr.Abort()) 144 | SetTimeouts(ResolveTimeout := 0, ConnectTimeout := 60000, SendTimeout := 30000, ReceiveTimeout := 30000) => this.whr.SetTimeouts(ResolveTimeout, ConnectTimeout, SendTimeout, ReceiveTimeout) 145 | SetClientCertificate(ClientCertificate) => this.whr.SetClientCertificate(ClientCertificate) 146 | SetAutoLogonPolicy(AutoLogonPolicy) => this.whr.SetAutoLogonPolicy(AutoLogonPolicy) 147 | 148 | Status => this.whr.Status 149 | StatusText => this.whr.StatusText 150 | ResponseText => this.whr.ResponseText 151 | ResponseBody { 152 | get { 153 | pSafeArray := ComObjValue(t := this.whr.ResponseBody) 154 | pvData := NumGet(pSafeArray + 8 + A_PtrSize, 'ptr') 155 | cbElements := NumGet(pSafeArray + 8 + A_PtrSize * 2, 'uint') 156 | return ClipboardAll(pvData, cbElements) 157 | } 158 | } 159 | ResponseStream => this.whr.responseStream 160 | Option[Opt] { 161 | get => this.whr.Option[Opt] 162 | set => (this.whr.Option[Opt] := Value) 163 | } 164 | Headers { 165 | get { 166 | m := Map(), m.Default := '' 167 | loop parse this.GetAllResponseHeaders(), '`r`n' 168 | if (p := InStr(A_LoopField, ':')) 169 | m[SubStr(A_LoopField, 1, p - 1)] .= LTrim(SubStr(A_LoopField, p + 1)) 170 | return m 171 | } 172 | } 173 | /** 174 | * The OnError event occurs when there is a run-time error in the application. 175 | * @prop {(this,errCode,errDesc)=>void} OnError 176 | */ 177 | OnError := 0 178 | /** 179 | * The OnResponseDataAvailable event occurs when data is available from the response. 180 | * @prop {(this,safeArray)=>void} OnResponseDataAvailable 181 | */ 182 | OnResponseDataAvailable := 0 183 | /** 184 | * The OnResponseStart event occurs when the response data starts to be received. 185 | * @prop {(this,status,contentType)=>void} OnResponseDataAvailable 186 | */ 187 | OnResponseStart := 0 188 | /** 189 | * The OnResponseFinished event occurs when the response data is complete. 190 | * @prop {(this)=>void} OnResponseDataAvailable 191 | */ 192 | OnResponseFinished := 0 193 | ;#endregion 194 | 195 | readyState := 0, whr := 0, _ievents := 0 196 | static __New() { 197 | if this != WinHttpRequest 198 | return 199 | this.DeleteProp('__New') 200 | for prop in ['OnError', 'OnResponseDataAvailable', 'OnResponseStart', 'OnResponseFinished'] 201 | this.Prototype.DefineProp(prop, { set: make_setter(prop) }) 202 | make_setter(prop) => (this, value := 0) => value && (this.DefineProp(prop, { call: value }), this.enableRequestEvents()) 203 | } 204 | } -------------------------------------------------------------------------------- /lib/YAML.ahk: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * @description: YAML/JSON格式字符串序列化和反序列化, 修改自[HotKeyIt/Yaml](https://github.com/HotKeyIt/Yaml) 3 | * 修复了一些YAML解析的bug, 增加了对true/false/null类型的支持, 保留了数值的类型 4 | * @author thqby, HotKeyIt 5 | * @date 2023/02/23 6 | * @version 1.0.7 7 | ***********************************************************************/ 8 | 9 | class YAML { 10 | static null := ComValue(1, 0), true := ComValue(0xB, 1), false := ComValue(0xB, 0) 11 | 12 | /** 13 | * Converts a AutoHotkey Object Notation (YAML/JSON) string into an object. 14 | * @param text A valid YAML/JSON string. 15 | * @param mergedarray A array to be merged 16 | * @param keepbooltype convert true/false/null to YAML.true / YAML.false / YAML.null where it's true, otherwise 1 / 0 / '' 17 | */ 18 | static parse(text, mergedarray := 0, keepbooltype := false) { 19 | static undefined := ComValue(0, 0), _fun_ := A_PtrSize = 8 ? "SIXJdExED7cBZkWFwHUM60BIg8ECZkWFwHQkZkGD+Ap0IWZBg/gNRA+3QQJ142ZBg/gKdB9Ig8ECZkWFwHXjSInIw4XSdAUx0maJEUiNQQLDMcDDhdJ0BTHAZokBSI1BBMOQkJCQ" : "i0QkBIXAdEsPtxBmhdJ1CutBg8ACZoXSdDdmg/oKdCBmg/oND7dQAnXoZoP6CnQmg8ACZoXSdejzw422AAAAAItMJAiFyXQFMdJmiRCDwALD88MxwMOLTCQIhcl0BTHSZokQg8AEw5A=", _sz_, NXTLN, _op_, __ := (DllCall("crypt32\CryptStringToBinary", "str", _fun_, "uint", 0, "uint", 1, "ptr", 0, "uint*", &_sz_ := 0, "ptr", 0, "ptr", 0), NXTLN := DllCall("GlobalAlloc", "uint", 0, "ptr", _sz_, "ptr"), DllCall("VirtualProtect", "ptr", NXTLN, "ptr", _sz_, "uint", 0x40, "uint*", &_op_ := 0), DllCall("crypt32\CryptStringToBinary", "str", _fun_, "uint", 0, "uint", 1, "ptr", NXTLN, "uint*", &_sz_, "ptr", 0, "ptr", 0)) 20 | keepbooltype ? (_true := YAML.true, _false := YAML.false, _null := YAML.null) : (_true := true, _false := false, _null := "") 21 | if (text = "") 22 | return [] 23 | text := RegExReplace(RegExReplace(text, "m)(\s+|^)\#.*"), ",\s*,", ',"",') ;NOTE 添加 by 火冷 <2023-03-05 11:02:21> 24 | if InStr("[{", SubStr(text := LTrim(text, " `t`n`r"), 1, 1)) 25 | return PJ(&text, mergedarray) 26 | return CY(&text, mergedarray) 27 | 28 | CY(&txt, Y := 0) { ; convert yaml to object 29 | NewDoc := 0, _C := "", _CE := "", _S := "", _K := "", V := undefined, N := false, _L := "", K := O := "", VQ := "", h := "", VC := "" 30 | txt .= Chr(0) Chr(0) Chr(0) Chr(0), P := StrPtr(txt), A := Map(), D := [0], I := [0], D.Length := I.Length := 1000 31 | D.Default := I.Default := 0 32 | ; LS := RegExMatch(txt, "m)^[^\s].*:(\s+\#.*)?\R(\s+)", &LS) ? LS.Len(2) * (Ord(LS.2) = 9 ? 2 : 1) : 2 ; indentation length (A_Tab = 2 Spaces, 2 Tabs = 4 Spaces) 33 | LS := 2 34 | ;~ while P:=G(LP:=P,LF){ 35 | while P && (LP := P, P := DllCall(NXTLN, "PTR", P, "Int", true, "cdecl PTR"), LF := StrGet(LP), P) { ;P:=(LP:=P,!p||!NumGet(p,"UShort")?0:(str:=StrSplit(StrGet(P),"`n","`r",2)).Length?(p+=StrLen(LF:=str[1])*2,p+=!NumGet(p,"UShort") ? 0 : NumGet(p,"USHORT")=13?4:2):0){ 36 | if (!Y && InStr(LF, "---") = 1) || (InStr(LF, "---") = 1 && (Y.Push(""), NEWDOC := 0, D[1] := 0, _L := _LL := O := _Q := _K := _S := _T := _V := "", 1)) || (InStr(LF, "...") = 1 && NEWDOC := 1) || (LF = "") || RegExMatch(LF, "^\s+$") 37 | continue 38 | else if NEWDOC 39 | throw Error("Document ended but new document not specified.", 0, LF) 40 | if RegExMatch(LF, "^\s*#") || InStr(LF, "``%") = 1 ; Comments, tag, document start/end or empty line, ignore 41 | continue 42 | else if _C || (_S && SubStr(LF, 1, LL * LS) = CL(LL + 1)) || (V !== undefined && !(K && _.SEQ) && SubStr(LF, 1, LL * LS) = CL(LL + 1)) { ; Continuing line incl. scalars 43 | if _Q && !_K { ; Sequence 44 | if D[L].Length && IsObject(VC := D[L].Pop()) 45 | throw Error("Malformed inline YAML string") ; Error if previous value is an object 46 | else D[L].Push(VC (VC ? (InStr(_S, '|') == 1 ? '`n' : ' ') : "") _CE := LTrim(LF, "`t ")) ; append value to previous item ; append value to previous item 47 | } else if IsObject(VC := D[L][K]) 48 | throw Error("Malformed inline YAML string") ; Error if previous value is an object 49 | else D[L][K] := VC (VC ? (InStr(_S, '|') == 1 ? '`n' : ' ') : "") _CE := LTrim(LF, "`t ") ; append value to previous item 50 | continue 51 | } else if _C && (SubStr(_CE, -1) != _C) 52 | throw Error("Unexpected character", 0, (_Q ? D[L][D[L].Length] : D[L][K])) ; else check if quoted value was ended with a quote 53 | else _C := "" ; reset continuation 54 | if (CM := InStr(LF, " #")) && !RegExMatch(LF, ".*[`"'].*\s\#.*[`"'].*") ; check for comments and remove 55 | LF := SubStr(LF, 1, CM - 1) 56 | ; Split line into yaml elements 57 | if SubStr(LTrim(LF, " `t"), 1, 1) = ":" 58 | throw Error("Unexpected character.", 0, ':') 59 | RegExMatch(LF, "S)^(?\s+)?(?-\s)?(?`".*`"\s*:(\s|$)|'.*'\s*:(\s|$)|[^:`"'\{\[]+\s*:(\s|$))?\s*(?[\|\>][+-]?)?\s*(?!!\w+)?\s*(?\*[^\s\t]+)?\s*(?&[^\s\t]+)?\s*(?`".+`"|'.+'|.+)?\s*$", &_) 60 | L := LL := CS(_.LVL, LS), Q := _.SEQ, K := _.KEY, S := _.SCA, T := SubStr(_.TYP, 3), V := UQ(_.VAL, T != "") 61 | VQ := InStr(".''.`"`".", "." SubStr(LTrim(_.VAL, " `t"), 1, 1) SubStr(RTrim(_.VAL, " `t"), -1) ".") 62 | if L > 1 { 63 | if LL = _LL 64 | L := _L 65 | else if LL > _LL 66 | I[LL] := L := _L + 1 67 | else if LL < _LL 68 | if !I[LL] 69 | throw Error("Indentation problem.", 0, LF) 70 | else L := I[LL] + (LL + 1 == _LL && Q != '' && D[LL] is Map && D[_LL] is Array) 71 | } 72 | if Trim(_[], " `t") = "-" ; empty sequence not cached by previous line 73 | V := undefined, Q := "-" 74 | else if V = "" && _.VAL = "" 75 | V := undefined 76 | else if V !== undefined && !Q ; only a value is catched, convert to key 77 | if K = "" 78 | K := V, V := undefined 79 | if !Q && SubStr(RTrim(K, " `t"), -1) != ":" ; not a sequence and key is missing : 80 | if L > _L && (D[_L][_K] := K, LL := _LL, L := _L, K := _K, Q := _Q, _S := ">") 81 | continue 82 | else throw Error("Invalid key.", 0, LF) 83 | else if K != "" ; trim key if not empty 84 | K := UQ(RTrim(K, ": "), true) 85 | Loop _L = "" ? D.Length - 1 : _L ? _L - L : 0 ; remove objects in deeper levels created before 86 | D[L + A_Index] := 0, I[L + A_Index] := 0 87 | if !VQ && _.VAL != "" && !InStr("'`"", _C := SubStr(LTrim(_.VAL, " `t"), 1, 1)) ; check if value started with a quote and was not closed so next line continues 88 | _C := "" 89 | if _L != L && !D[L] ; object in this level not created yet 90 | if L = 1 { ; first level, use or create main object 91 | if Y && Type(Y[Y.Length]) != "String" && ((Q && Type(Y[Y.Length]) != "Array") || (!Q && Type(Y[Y.Length]) = "Array")) 92 | throw Error("Mapping Item and Sequence cannot be defined on the same level.", 0, LF) ; trying to create sequence on the same level as key or vice versa 93 | else D[L] := Y ? (Type(Y[Y.Length]) = "String" ? (Y[Y.Length] := Q ? [] : Map()) : Y[Y.Length]) : (Y := Q ? [[]] : [Map()])[1] 94 | } else if !_Q && Type(D[L - 1][_K]) = (Q ? "Array" : "Map") ; use previous object 95 | D[L] := D[L - 1][_K] 96 | else D[L] := O := Q ? [] : Map(), _A ? A[_A] := O : "", _Q ? (N && D[L - 1].Pop(), D[L - 1].Push(O)) : D[L - 1][_K] := O, O := "" ; create new object 97 | _A := "" ; reset alias 98 | if Q && K ; Sequence containing a key, create object 99 | D[L].Push(O := Map()), D[++L] := O, Q := O := "", LL += 1 100 | if (Q && Type(D[L]) != "Array" || !Q && Type(D[L]) = "Array") { 101 | if (Q && N && !D[L + 1] && D[L].Has(_K)) 102 | D[L][_K] := O := [], I[L] := LL, LL += 1, D[++L] := O, O := "" 103 | else if (Q && Type(D[L + 1]) = "Array" || !Q && Type(D[L + 1]) = "Map") 104 | L++ 105 | else 106 | throw Error("Mapping Item and Sequence cannot be defined on the same level,", 0, LF) ; trying to create sequence on the same level as key or vice versa 107 | } 108 | if T = "binary" { ; !!binary 109 | O := Buffer(StrLen(V) // 2), PBIN := O.Ptr 110 | Loop Parse V 111 | if ("" != h .= A_LoopField) && !Mod(A_Index, 2) 112 | NumPut("UChar", "0x" h, PBIN, A_Index / 2 - 1), h := "" 113 | } else if T = "set" 114 | throw Error("Tag 'set' is not supported") ; tag !!set is not supported 115 | else V := T = "int" || T = "float" ? V + 0 : T = "str" ? V "" : T = "null" ? _null : T = "bool" ? (V = "true" ? _true : V = "false" ? _false : V) : V ; tags !!int !!float !!str !!null !!bool - else seq map omap ignored 116 | if _.ASET 117 | A[_A := SubStr(_.ASET, 2)] := V 118 | if _.AGET 119 | V := A[SubStr(_.AGET, 2)] 120 | else if V is ComValue { 121 | } else if !VQ && SubStr(LTrim(V, " `t"), 1, 1) = "{" ; create json map object 122 | O := Map(), _A ? A[_A] := O : "", P := (YO(O, LP + InStr(LF, V) * 2, L)) 123 | else if !VQ && SubStr(LTrim(V, " `t"), 1, 1) = "[" ; create json sequence object 124 | O := [], _A ? A[_A] := O : "", P := (YA(O, LP + InStr(LF, V) * 2, L)) 125 | N := false 126 | if _S ~= '^[>|]\+?$' { 127 | if D[L] is Map 128 | D[L][_K] .= '`n' 129 | else D[L][D[L].Length] .= '`n' 130 | } 131 | if Q ; push sequence value into an object 132 | ; (V !== undefined ? D[L].Push(O ? O : S ? "" : V) : 0) 133 | (V !== undefined ? D[L].Push(O ? O : S ? "" : V) : (N := true, D[L].Push(_null))) 134 | else D[L][K] := O ? O : D[L].Has(K) ? D[L][K] : S ? "" : V = undefined ? (N := true, _null) : V ; add key: value into object 135 | if !Q && V !== undefined ; backup yaml elements 136 | _L := L, _LL := LL, O := _Q := _K := _S := _T := _V := "" ;_L:= 137 | else _L := L, _LL := LL, _Q := Q, _K := K, _S := S, _T := T, _V := V, O := "" 138 | } 139 | if _S ~= '^[>|]\+?$' { 140 | if D[L] is Map 141 | D[L][K] .= '`n' 142 | else D[L][D[L].Length] .= '`n' 143 | } 144 | if Y && Type(Y[Y.Length]) = "String" 145 | Y.Pop() 146 | return SubStr(txt, 1, 3) != "---" && Y.Length = 1 ? Y[1] : Y 147 | } 148 | YO(O, P, L) { ; YamlObject: convert json map 149 | v := q := k := b := 0, key := val := lf := nl := "" 150 | while ("" != c := Chr(NumGet(p, "UShort"))) { 151 | if c = "`n" || (c = "`r" && 10 = NumGet(p + 2, "UShort")) { 152 | if (q || k || (!v && !k) || SubStr(Ltrim(StrGet(p + (c = "`n" ? 2 : 4)), " `t`r`n"), 1, 1) = "}") && P += c = "`n" ? 2 : 4 153 | continue 154 | else throw Error("Malformed inline YAML string", 0, StrGet(p - 6)) 155 | } else if !q && (c = " " || c = A_Tab) && P += 2 156 | continue 157 | else if !v && (c = '"' || c = "'") && (q := c, v := 1, P += 2) 158 | continue 159 | else if !v && k && (c = "[" || c = "{") && (P := c = "[" ? YA(O[key] := [], P + 2, L) : YO(O[key] := Map(), P + 2, L), key := "", k := 0, 1) 160 | continue 161 | else if v && !k && ((!q && c = ":") || (q && !b && q = c)) && (v := 0, key := q ? (InStr(key, "\") ? UC(key) : key) : Trim(key, " `t"), k := 1, q := 0, P += 2) 162 | continue 163 | else if v && k && ((!q && c = ",") || (q && !b && q = c)) && (v := 0, O[key] := q ? (InStr(val, "\") ? UC(val) : val) : IsNumber(val) ? val + 0 : val = "true" ? _true : val = "false" ? _false : val = "null" ? _null : val, val := "", key := "", q := 0, k := 0, P += 2) 164 | continue 165 | else if !q && c = "}" && (k && v ? (O[key] := val = "true" ? _true : val = "false" ? _false : val = "null" ? _null : val, 1) : 1) { 166 | ;~ if ((tp:=G(P+2,lf))&&(NumGet(P+2,"UShort")=10||NumGet(P+4,"UShort")=10||(nl:=RegExMatch(lf,"^\s+?$"))||RegExMatch(lf,"^\s*[,\}\]]"))) 167 | if ((tp := DllCall(NXTLN, "PTR", P + 2, "Int", false, "cdecl PTR"), lf := StrGet(P + 2), tp) && (NumGet(P + 2, "UShort") = 0 || (nl := RegExMatch(lf, "^\s+?$")) || RegExMatch(lf, "^\s*[,\}\]]"))) 168 | return nl ? DllCall(NXTLN, "PTR", P + 2, "Int", true, "cdecl PTR") : lf ? P + 2 : NumGet(P + 4, "UShort") = 0 ? P + 6 : P + 4 ; in case `r`n we have 2 times NULL chr 169 | else if !tp 170 | return NumGet(P + 4, "UShort") = 0 ? P + 6 : P + 4 ; in case `r`n we have 2 times NULL chr 171 | else throw Error("Malformed inline YAML string.", 0, StrGet(p)) 172 | } else if !v && (c = "," || c = ":" || c = " " || c = "`t") && P += 2 173 | continue 174 | else if !v && (!k ? (key := c) : val := c, b := !b && c = "\", v := 1, P += 2) 175 | continue 176 | else if v && (!k ? (key .= c) : val .= c, b := !b && c = "\", P += 2) 177 | continue 178 | else throw Error("Undefined") 179 | } 180 | return P 181 | } 182 | YA(O, P, L) { ; YamlArray: convert json sequence 183 | s := "", v := q := c := b := tp := 0, lf := nl := "" 184 | while ("" != c := Chr(NumGet(p, "UShort"))) { 185 | if c = "`n" || (c = "`r" && 10 = NumGet(p + 2, "UShort")) { 186 | if (q || !v || SubStr(Ltrim(StrGet(p + (c = "`n" ? 2 : 4)), " `t`r`n"), 1, 1) = "]") && P += c = "`n" ? 2 : 4 187 | continue 188 | else throw Error("Malformed inline YAML string.", 0, s "`n" StrGet(p - 6)) 189 | } else if !q && (c = " " || c = A_Tab) && (lf != '' && lf .= c, P += 2) 190 | continue 191 | else if !v && (c = '"' || c = "'") && (q := c, v := 1, P += 2) 192 | continue 193 | else if !v && (c = "[" || c = "{") && (P := c = "[" ? YA((O.Push(lf := []), lf), P + 2, L) : YO((O.Push(lf := Map()), lf), P + 2, L), lf := "", 1) 194 | continue 195 | else if v && ((!q && c = ",") || (q && !b && c = q)) && (v := 0, O.Push(q ? (InStr(lf, "\") ? UC(lf) : lf) : IsNumber(lf) ? lf + 0 : lf = "true" ? _true : lf = "false" ? _false : lf = "null" ? _null : lf), q := 0, lf := "", P += 2) 196 | continue 197 | else if !q && c = "]" && (v ? (O.Push(lf = "true" ? _true : lf = "false" ? _false : lf = "null" ? _null : lf), 1) : 1) { 198 | ;~ if ((tp:=G(P+2,lf))&&(NumGet(P+2,"UShort")=10||NumGet(P+4,"UShort")=10||(nl:=RegExMatch(lf,"^\s+?$"))||RegExMatch(lf,"^\s*[,\}\]]"))) 199 | if ((tp := DllCall(NXTLN, "PTR", P + 2, "Int", false, "cdecl PTR"), lf := StrGet(P + 2), tp) && (NumGet(P + 2, "UShort") = 0 || (nl := RegExMatch(lf, "^\s+?$")) || RegExMatch(lf, "^\s*[,\}\]]"))) 200 | return nl ? DllCall(NXTLN, "PTR", P + 2, "Int", true, "cdecl PTR") : lf ? P + 2 : NumGet(P + 4, "UShort") = 0 ? P + 6 : P + 4 ; in case `r`n we have 2 times NULL chr 201 | else if !tp 202 | return NumGet(P + 4, "UShort") = 0 ? P + 6 : P + 4 ; in case `r`n we have 2 times NULL chr 203 | else throw Error("Malformed inline YAML string.", 0, StrGet(p)) 204 | } else if !v && (c = "," || c = " " || c = "`t") && P += 2 ;InStr(", `t",c) 205 | continue 206 | else if !v && (lf .= c, v := 1, b := c = "\", P += 2) 207 | continue 208 | else if v && (lf .= c, b := !b && c = "\", P += 2) 209 | continue 210 | else throw Error("Undefined") 211 | } 212 | return P 213 | } 214 | PJ(&S, Y) { ; PureJSON: convert pure JSON Object 215 | NQ := "", LF := "", LP := 0, P := "", R := "" 216 | D := [C := (A := InStr(S, "[") = 1) ? [] : Map()], S := LTrim(SubStr(S, 2), " `t`r`n"), L := 1, N := 0, V := K := "", Y ? (Y.Push(C), J := Y) : J := [C], !(Q := InStr(S, '"') != 1) ? S := LTrim(S, '"') : "" 217 | Loop Parse S, '"' { 218 | Q := NQ ? 1 : !Q 219 | NQ := Q && (SubStr(A_LoopField, -3) = "\\\" || (SubStr(A_LoopField, -1) = "\" && SubStr(A_LoopField, -2) != "\\")) 220 | if !Q { 221 | if (t := Trim(A_LoopField, " `t`r`n")) = "," || (t = ":" && V := 1) 222 | continue 223 | else if t && (InStr("{[]},:", SubStr(t, 1, 1)) || RegExMatch(t, "^-?\d*(\.\d*)?\s*[,\]\}]")) { 224 | Loop Parse t { 225 | if N && N-- 226 | continue 227 | if InStr("`n`r `t", A_LoopField) 228 | continue 229 | else if InStr("{[", A_LoopField) { 230 | if !A && !V 231 | throw Error("Malformed JSON - missing key.", 0, t) 232 | C := A_LoopField = "[" ? [] : Map(), A ? D[L].Push(C) : D[L][K] := C, D.Has(++L) ? D[L] := C : D.Push(C), V := "", A := Type(C) = "Array" 233 | continue 234 | } else if InStr("]}", A_LoopField) { 235 | if !A && V 236 | throw Error("Malformed JSON - missing value.", 0, t) 237 | else if L = 0 238 | throw Error("Malformed JSON - to many closing brackets.", 0, t) 239 | else C := --L = 0 ? "" : D[L], A := Type(C) = "Array" 240 | } else if !(InStr(" `t`r,", A_LoopField) || (A_LoopField = ":" && V := 1)) { 241 | if RegExMatch(SubStr(t, A_Index), "m)^(null|false|true|-?\d+\.?\d*)\s*[,}\]\r\n]", &R) && (N := R.Len(0) - 2, R := R.1, 1) { 242 | if A 243 | C.Push(R = "null" ? _null : R = "true" ? _true : R = "false" ? _false : IsNumber(R) ? R + 0 : R) 244 | else if V 245 | C[K] := R = "null" ? _null : R = "true" ? _true : R = "false" ? _false : IsNumber(R) ? R + 0 : R, K := V := "" 246 | else throw Error("Malformed JSON - missing key.", 0, t) 247 | } else 248 | throw Error("Malformed JSON - unrecognized character-", 0, A_LoopField " in " t) 249 | } 250 | } 251 | } else if InStr(t, ':') > 1 252 | throw Error("Malformed JSON - unrecognized character-", 0, SubStr(t, 1, 1) " in " t) 253 | } else if NQ && (P .= A_LoopField '"', 1) 254 | continue 255 | else if A 256 | LF := P A_LoopField, C.Push(InStr(LF, "\") ? UC(LF) : LF), P := "" 257 | else if V 258 | LF := P A_LoopField, C[K] := InStr(LF, "\") ? UC(LF) : LF, K := V := P := "" 259 | else 260 | LF := P A_LoopField, K := InStr(LF, "\") ? UC(LF) : LF, P := "" 261 | } 262 | return J[1] 263 | } 264 | UQ(S, K := false) { ; UnQuote: remove quotes 265 | return (t := SubStr(S := Trim(S, " `t"), 1, 1) SubStr(S, -1)) = '""' ? (InStr(S, "\") ? UC(SubStr(S, 2, -1)) : SubStr(S, 2, -1)) : t = "''" ? SubStr(S, 2, -1) : K ? S : IsNumber(S) ? S + 0 : S = "null" ? _null : S = "true" ? _true : S = "false" ? _false : S 266 | } 267 | CS(s, x) { ; Convert Spaces to level, 1 = first level = no spaces 268 | return ((StrLen(s) << (Ord(s) = 9)) // x) + 1 269 | } 270 | UC(S, e := 1) { ; UniChar: convert unicode and special characters 271 | static m := Map(Ord('"'), '"', Ord("a"), "`a", Ord("b"), "`b", Ord("t"), "`t", Ord("n"), "`n", Ord("v"), "`v", Ord("f"), "`f", Ord("r"), "`r", Ord("e"), Chr(0x1B), Ord("N"), Chr(0x85), Ord("P"), Chr(0x2029), 0, "", Ord("L"), Chr(0x2028), Ord("_"), Chr(0xA0)) 272 | v := "" 273 | Loop Parse S, "\" 274 | if !((e := !e) && A_LoopField = "" ? v .= "\" : !e ? (v .= A_LoopField, 1) : 0) 275 | v .= (t := InStr("ux", SubStr(A_LoopField, 1, 1)) ? SubStr(A_LoopField, 1, RegExMatch(A_LoopField, "i)^[ux]?([\dA-F]{4})?([\dA-F]{2})?\K") - 1) : "") && RegexMatch(t, "i)^[ux][\da-f]+$") ? Chr(Abs("0x" SubStr(t, 2))) SubStr(A_LoopField, RegExMatch(A_LoopField, "i)^[ux]?([\dA-F]{4})?([\dA-F]{2})?\K")) : m.Has(Ord(A_LoopField)) ? m[Ord(A_LoopField)] SubStr(A_LoopField, 2) : "\" A_LoopField, e := A_LoopField = "" ? e : !e 276 | return v 277 | } 278 | CL(i) { ; Convert level to spaces 279 | Loop (s := "", i - 1) 280 | s .= " " 281 | return s 282 | } 283 | } 284 | 285 | /** 286 | * Converts a AutoHotkey Array/Map/Object to a Object Notation (YAML/JSON) string. 287 | * @param obj A AutoHotkey value, usually an object or array or map, to be converted. 288 | * @param expandlevel The level of (YAML/JSON) string need to expand, by default return YAML string and expand all. 289 | * @param space (Only JSON)Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. 290 | * @param unicode_escaped Convert non-ascii characters to \uxxxx where unicode_escaped = true 291 | * @returns JSON string where expandlevel <= 0, otherwise YAML string 292 | */ 293 | static stringify(obj, expandlevel := unset, space := " ", unicode_escaped := false) { 294 | expandlevel := IsSet(expandlevel) ? expandlevel : 100000 295 | return Trim(CO(obj, expandlevel)) 296 | 297 | CO(O, J := 0, R := 0, Q := 0) { ; helper: convert object to yaml string 298 | static M1 := "{", M2 := "}", S1 := "[", S2 := "]", N := "`n", C := ",", S := "- ", E := "", K := ":" 299 | if (OT := Type(O)) = "Array" { 300 | D := J < 1 && !R ? S1 : "" 301 | for key, value in O { 302 | if ((VT := Type(value)) = "Buffer" && J > 0) { 303 | Loop (VAL := "", PTR := value.Ptr, value.size) 304 | VAL .= format("{1:.2X}", NumGet(PTR + A_Index - 1, "UCHAR")) 305 | value := "!!binary " VAL, F := E 306 | } else 307 | F := VT = "Array" ? "S" : InStr("Map,Object", VT) ? "M" : E 308 | Z := VT = "Array" && value.Length = 0 ? "[]" : ((VT = "Map" && value.count = 0) || (VT = "Object" && ObjOwnPropCount(value) = 0)) ? "{}" : "" 309 | if J <= R 310 | D .= (J + R < 0 ? "`n" CL(R + 2) : "") (F ? (%F%1 (Z ? "" : CO(value, J, R + 1, F)) %F%2) : ES(value, J, unicode_escaped, 0)) (OT = "Array" && O.Length = A_Index ? E : C) 311 | else if ((D := D N CL(R + 1) S) || 1) && F 312 | D .= Z ? Z : (J <= (R + 1) ? %F%1 : E) (F = "M" ? LTrim(CO(value, J, R + 1, F), " `t`n") : CO(value, J, R + 1, F)) (J <= (R + 1) ? %F%2 : E) 313 | else D .= ES(value, J, unicode_escaped, 2) 314 | } 315 | } else { 316 | D := J < 1 && !R ? M1 : "" 317 | for key, value in (OT := Type(O)) = "Map" ? (Y := 1, O) : (Y := 0, O.OwnProps()) { 318 | if ((VT := Type(value)) = "Buffer" && J > 0) { 319 | Loop (VAL := "", PTR := value.Ptr, value.size) 320 | VAL .= format("{1:.2X}", NumGet(PTR + A_Index - 1, "UCHAR")) 321 | value := "!!binary " VAL, F := E 322 | } else 323 | F := VT = "Array" ? "S" : InStr("Map,Object", VT) ? "M" : E 324 | Z := VT = "Array" && value.Length = 0 ? "[]" : ((VT = "Map" && value.count = 0) || (VT = "Object" && ObjOwnPropCount(value) = 0)) ? "{}" : "" 325 | if J <= R 326 | D .= (J + R < 0 ? "`n" CL(R + 2) : "") (Q = "S" && A_Index = 1 ? M1 : E) ES(key, J, unicode_escaped, 1) K (F ? (%F%1 (Z ? "" : CO(value, J, R + 1, F)) %F%2) : ES(value, J, unicode_escaped, 0)) (Q = "S" && A_Index = (Y ? O.count : ObjOwnPropCount(O)) ? M2 : E) (J != 0 || R ? (A_Index = (Y ? O.count : ObjOwnPropCount(O)) ? E : C) : E) 327 | else if ((D := D N CL(R + 1) ES(key, J, unicode_escaped, 3) K " ") || 1) && F 328 | D .= Z ? Z : (J <= (R + 1) ? %F%1 : E) CO(value, J, R + 1, F) (J <= (R + 1) ? %F%2 : E) 329 | else D .= ES(value, J, unicode_escaped, 2) 330 | if J = 0 && !R 331 | D .= (A_Index < (Y ? O.count : ObjOwnPropCount(O)) ? C : E) 332 | } 333 | } 334 | if J < 0 && J + R < 0 335 | D .= "`n" CL(R + 1) 336 | if R = 0 337 | D := LTrim(D, "`n") (J < 1 ? (OT = "Array" ? S2 : M2) : "") 338 | return D 339 | } 340 | ES(S, J := 1, U := false, K := -1) { ; EscIfNeed: check if escaping needed and convert to unicode notation 341 | static ascii := Map("\", "\", "`a", "a", "`b", "b", "`t", "t", "`n", "n", "`v", "v", "`f", "f", "`r", "r", "`"", "`"") 342 | switch Type(S) { 343 | case "Float": 344 | if (v := '', d := InStr(S, 'e')) 345 | v := SubStr(S, d), S := SubStr(S, 1, d - 1) 346 | if ((StrLen(S) > 17) && (d := RegExMatch(S, "(99999+|00000+)\d{0,3}$"))) 347 | S := Round(S, Max(1, d - InStr(S, ".") - 1)) 348 | return S v 349 | case "Integer": 350 | return S 351 | case "String": 352 | v := "" 353 | if (U && RegExMatch(S, "m)[\x{7F}-\x{7FFF}]")) { 354 | Loop Parse S 355 | v .= ascii.Has(A_LoopField) ? "\" ascii[A_LoopField] : Ord(A_LoopField) < 128 ? A_LoopField : "\u" format("{1:.4X}", Ord(A_LoopField)) 356 | return '"' v '"' 357 | } else if (J < 1) { 358 | Loop Parse S 359 | v .= ascii.Has(A_LoopField) ? "\" ascii[A_LoopField] : A_LoopField 360 | return '"' v '"' 361 | } else if (K < 2) { 362 | if (RegExMatch(S, "m)[\{\}\[\]`"'\s:,]|^\s|\s$")) { 363 | Loop Parse S 364 | v .= ascii.Has(A_LoopField) ? "\" ascii[A_LoopField] : A_LoopField 365 | return '"' v '"' 366 | } 367 | } else if (RegExMatch(S, "m)[\{\[`"'\r\n]|:\s|,\s|\s#|^[\s#\-:>]|\s$")) { 368 | Loop Parse S 369 | v .= ascii.Has(A_LoopField) ? "\" ascii[A_LoopField] : A_LoopField 370 | return '"' v '"' 371 | } else if (K = 2 && (IsNumber(S) || RegExMatch(S, "i)^(true|false|null)$"))) 372 | return '"' S '"' 373 | return S || '""' 374 | default: 375 | return S == YAML.true ? "true" : S == YAML.false ? "false" : "null" 376 | } 377 | } 378 | CL(i) { ; Convert level to spaces 379 | Loop (s := "", i - 1) 380 | s .= space 381 | return s 382 | } 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /lib/msedge.ahk: -------------------------------------------------------------------------------- 1 | class _Edge extends _CDP { 2 | 3 | ;只是为了方便打开网页 4 | __new(url:="", funAfterDo:=unset) { 5 | if (url != "") 6 | _CDP.smartGet("msedge").tabOpenLink(url, funAfterDo?) 7 | } 8 | 9 | static onekey() { 10 | if (WinActive("ahk_class Chrome_WidgetWin_1 ahk_exe msedge.exe")) { 11 | WinMinimize ;防止左下角的网址残留 12 | WinHide 13 | } else { 14 | _CDP.smartGet("msedge").tabOpenLink() 15 | } 16 | } 17 | 18 | } 19 | --------------------------------------------------------------------------------