Skip to content

Instantly share code, notes, and snippets.

@G33kDude
Created June 20, 2020 05:43
Show Gist options
  • Select an option

  • Save G33kDude/a1483a62c4a085fc1b441c92875d3197 to your computer and use it in GitHub Desktop.

Select an option

Save G33kDude/a1483a62c4a085fc1b441c92875d3197 to your computer and use it in GitHub Desktop.

Revisions

  1. G33kDude created this gist Jun 20, 2020.
    321 changes: 321 additions & 0 deletions MATRIC.ahk
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,321 @@
    #NoEnv
    SetBatchLines, -1

    #Include <Socket>

    MATRIC_CONFIG_PATH := A_Desktop "\..\Documents\.matric\config.json"
    APP_NAME := "AutoHotkey"
    API_PORT := 50300
    RESP_PORT := 50301

    ; Assert that MATRIC is running
    if !WinExist("ahk_exe MatricServer.exe")
    throw Exception("Please start MATRIC")

    ; Assert the integration API is enabled
    if !GetMatricConfig().EnableIntegrationAPI
    throw Exception("Please enable the MATRIC integration API")

    ; Open the UDP socket listening on 50301
    udpClient := new BetterUDP()
    udpClient.OnRecv := Func("OnRecv")
    udpClient.Bind(["127.0.0.1", RESP_PORT])
    udpClient.Listen()

    ; Get the PIN from MATRIC
    if !(pin := GetPIN(MATRIC_CONFIG_PATH, APP_NAME))
    throw Exception("Could not retrieve connection PIN")

    ; Issue a command to MATRIC
    UDPSend(
    ( JOIN ; ahk
    {
    "command": "GETCONNECTEDCLIENTS",
    "appName": APP_NAME,
    "appPIN": pin
    }
    ))
    return

    ; Read the MATRIC config to find the PIN for appName
    ; If one is not present, trigger Matric to generate one
    GetPIN(configPath, appName)
    {
    ; Look for the PIN
    for i, entry in GetMatricConfig().AuthorizedApps
    if (entry.appName == appName)
    return entry.appPin

    ; Get MATRIC to generate a PIN
    UDPSend({"command": "CONNECT", "appName": appName})
    WinWait, Authorization required ahk_exe MatricServer.exe
    ControlSend, ahk_parent, {Enter}
    WinWaitClose

    ; Look for the PIN again
    for i, entry in GetMatricConfig().AuthorizedApps
    if (entry.appName == appName)
    return entry.appPin
    }

    ; Read and parse the MATRIC config file
    GetMatricConfig()
    {
    global MATRIC_CONFIG_PATH
    return Jxon_Load(FileOpen(MATRIC_CONFIG_PATH, "r").Read())
    }

    ; Handle response data received from MATRIC
    OnRecv(sock)
    {
    MsgBox, % "Received message from MATRIC:`n`n"
    . Jxon_Dump(Jxon_Load(sock.RecvText()), 4)
    }

    ; Wrapper function to make it easier to send commands to MATRIC
    UDPSend(object)
    {
    global API_PORT, udpClient
    udpClient.SendTextTo(Jxon_Dump(object), ["127.0.0.1", API_PORT])
    }

    ; Add some functions to the UDP class that really ought to be there
    class BetterUDP extends SocketUDP
    {
    SendTextTo(text, address, encoding:="UTF-8")
    {
    VarSetCapacity(buffer, StrPut(text, encoding)
    * (encoding="UTF-16" || encoding="cp1200" ? 2 : 1))
    length := StrPut(text, &buffer, encoding)
    return this.SendTo(&buffer, length, address)
    }

    SendTo(pBuffer, length, address)
    {
    addr := DllCall("WS2_32\inet_addr", "AStr", address[1], "UInt")
    port := DllCall("WS2_32\htons", "UShort", address[2], "UShort")
    VarSetCapacity(sa_in, 16, 0)
    NumPut(2, sa_in, 0, "UShort") ; AF_INET
    NumPut(port, sa_in, 2, "UShort")
    NumPut(addr, sa_in, 4, "UInt")
    DllCall("Ws2_32\sendto", "UInt", this.Socket, "Ptr", pBuffer
    , "Int", length, "Int", 0, "Ptr", &sa_in, "Int", 16)
    }
    }


    ; --- Coco's JSON Library ---
    ; https://github.com/cocobelgica/AutoHotkey-JSON

    Jxon_Load(ByRef src, args*)
    {
    static q := Chr(34)

    key := "", is_key := false
    stack := [ tree := [] ]
    is_arr := { (tree): 1 }
    next := q . "{[01234567890-tfn"
    pos := 0
    while ( (ch := SubStr(src, ++pos, 1)) != "" )
    {
    if InStr(" `t`n`r", ch)
    continue
    if !InStr(next, ch, true)
    {
    ln := ObjLength(StrSplit(SubStr(src, 1, pos), "`n"))
    col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1))

    msg := Format("{}: line {} col {} (char {})"
    , (next == "") ? ["Extra data", ch := SubStr(src, pos)][1]
    : (next == "'") ? "Unterminated string starting at"
    : (next == "\") ? "Invalid \escape"
    : (next == ":") ? "Expecting ':' delimiter"
    : (next == q) ? "Expecting object key enclosed in double quotes"
    : (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'"
    : (next == ",}") ? "Expecting ',' delimiter or object closing '}'"
    : (next == ",]") ? "Expecting ',' delimiter or array closing ']'"
    : [ "Expecting JSON value(string, number, [true, false, null], object or array)"
    , ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1]
    , ln, col, pos)

    throw Exception(msg, -1, ch)
    }

    is_array := is_arr[obj := stack[1]]

    if i := InStr("{[", ch)
    {
    val := (proto := args[i]) ? new proto : {}
    is_array? ObjPush(obj, val) : obj[key] := val
    ObjInsertAt(stack, 1, val)

    is_arr[val] := !(is_key := ch == "{")
    next := q . (is_key ? "}" : "{[]0123456789-tfn")
    }

    else if InStr("}]", ch)
    {
    ObjRemoveAt(stack, 1)
    next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}"
    }

    else if InStr(",:", ch)
    {
    is_key := (!is_array && ch == ",")
    next := is_key ? q : q . "{[0123456789-tfn"
    }

    else ; string | number | true | false | null
    {
    if (ch == q) ; string
    {
    i := pos
    while i := InStr(src, q,, i+1)
    {
    val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C")
    static end := A_AhkVersion<"2" ? 0 : -1
    if (SubStr(val, end) != "\")
    break
    }
    if !i ? (pos--, next := "'") : 0
    continue

    pos := i ; update pos

    val := StrReplace(val, "\/", "/")
    , val := StrReplace(val, "\" . q, q)
    , val := StrReplace(val, "\b", "`b")
    , val := StrReplace(val, "\f", "`f")
    , val := StrReplace(val, "\n", "`n")
    , val := StrReplace(val, "\r", "`r")
    , val := StrReplace(val, "\t", "`t")

    i := 0
    while i := InStr(val, "\",, i+1)
    {
    if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0
    continue 2

    ; \uXXXX - JSON unicode escape sequence
    xxxx := Abs("0x" . SubStr(val, i+2, 4))
    if (A_IsUnicode || xxxx < 0x100)
    val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6)
    }

    if is_key
    {
    key := val, next := ":"
    continue
    }
    }

    else ; number | true | false | null
    {
    val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos)

    ; For numerical values, numerify integers and keep floats as is.
    ; I'm not yet sure if I should numerify floats in v2.0-a ...
    static number := "number", integer := "integer"
    if val is %number%
    {
    if val is %integer%
    val += 0
    }
    ; in v1.1, true,false,A_PtrSize,A_IsUnicode,A_Index,A_EventInfo,
    ; SOMETIMES return strings due to certain optimizations. Since it
    ; is just 'SOMETIMES', numerify to be consistent w/ v2.0-a
    else if (val == "true" || val == "false")
    val := %val% + 0
    ; AHK_H has built-in null, can't do 'val := %value%' where value == "null"
    ; as it would raise an exception in AHK_H(overriding built-in var)
    else if (val == "null")
    val := ""
    ; any other values are invalid, continue to trigger error
    else if (pos--, next := "#")
    continue

    pos += i-1
    }

    is_array? ObjPush(obj, val) : obj[key] := val
    next := obj==tree ? "" : is_array ? ",]" : ",}"
    }
    }

    return tree[1]
    }

    Jxon_Dump(obj, indent:="", lvl:=1)
    {
    static q := Chr(34)

    if IsObject(obj)
    {
    static Type := Func("Type")
    if Type ? (Type.Call(obj) != "Object") : (ObjGetCapacity(obj) == "")
    throw Exception("Object type not supported.", -1, Format("<Object at 0x{:p}>", &obj))

    is_array := 0
    for k in obj
    is_array := k == A_Index
    until !is_array

    static integer := "integer"
    if indent is %integer%
    {
    if (indent < 0)
    throw Exception("Indent parameter must be a postive integer.", -1, indent)
    spaces := indent, indent := ""
    Loop % spaces
    indent .= " "
    }
    indt := ""
    Loop, % indent ? lvl : 0
    indt .= indent

    lvl += 1, out := "" ; Make #Warn happy
    for k, v in obj
    {
    if IsObject(k) || (k == "")
    throw Exception("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", &obj) : "<blank>")

    if !is_array
    out .= ( ObjGetCapacity([k], 1) ? Jxon_Dump(k) : q . k . q ) ;// key
    . ( indent ? ": " : ":" ) ; token + padding
    out .= Jxon_Dump(v, indent, lvl) ; value
    . ( indent ? ",`n" . indt : "," ) ; token + indent
    }

    if (out != "")
    {
    out := Trim(out, ",`n" . indent)
    if (indent != "")
    out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1)
    }

    return is_array ? "[" . out . "]" : "{" . out . "}"
    }

    ; Number
    else if (ObjGetCapacity([obj], 1) == "")
    return obj

    ; String (null -> not supported by AHK)
    if (obj != "")
    {
    obj := StrReplace(obj, "\", "\\")
    , obj := StrReplace(obj, "/", "\/")
    , obj := StrReplace(obj, q, "\" . q)
    , obj := StrReplace(obj, "`b", "\b")
    , obj := StrReplace(obj, "`f", "\f")
    , obj := StrReplace(obj, "`n", "\n")
    , obj := StrReplace(obj, "`r", "\r")
    , obj := StrReplace(obj, "`t", "\t")

    static needle := (A_AhkVersion<"2" ? "O)" : "") . "[^\x20-\x7e]"
    while RegExMatch(obj, needle, m)
    obj := StrReplace(obj, m[0], Format("\u{:04X}", Ord(m[0])))
    }

    return q . obj . q
    }