diff --git a/Cargo.lock b/Cargo.lock index 7aa8e20a7..a0c84bef0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.6.13" @@ -256,6 +265,20 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "embed-resource" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6985554d0688b687c5cb73898a34fbe3ad6c24c58c238a4d91d5e840670ee9d" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -461,6 +484,7 @@ dependencies = [ "anyhow", "clap", "dirs", + "embed-resource", "encode_unicode", "evdev", "indoc", @@ -479,6 +503,7 @@ dependencies = [ "once_cell", "parking_lot", "radix_trie", + "regex", "rustc-hash", "sd-notify", "serde_json", @@ -857,7 +882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] @@ -914,6 +939,35 @@ dependencies = [ "thiserror", ] +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -990,6 +1044,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "signal-hook" version = "0.3.17" @@ -1181,11 +1244,26 @@ dependencies = [ "time-core", ] +[[package]] +name = "toml" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.9", +] + [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -1195,7 +1273,20 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.6", ] [[package]] @@ -1222,6 +1313,26 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1484,6 +1595,25 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index a1a50c488..e55685eb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,12 +81,18 @@ winapi = { version = "0.3.9", features = [ native-windows-gui = { version = "1.0.12", default_features = false } kanata-interception = { version = "0.2.0", optional = true } +[target.'cfg(target_os = "windows")'.build-dependencies] +embed-resource = { version = "2.4.2", optional = true } +indoc = { version = "2.0.4", optional = true } +regex = { version = "1.10.4", optional = true } + [features] default = ["tcp_server"] perf_logging = [] tcp_server = ["serde_json"] win_sendinput_send_scancodes = [] win_llhook_read_scancodes = [] +win_manifest = ["embed-resource", "indoc", "regex"] cmd = ["kanata-parser/cmd"] interception_driver = ["kanata-interception", "kanata-parser/interception_driver"] simulated_output = ["indoc"] diff --git a/EnableUIAccess/EnableUIAccess.ahk b/EnableUIAccess/EnableUIAccess.ahk new file mode 100644 index 000000000..5d75f0d4a --- /dev/null +++ b/EnableUIAccess/EnableUIAccess.ahk @@ -0,0 +1,346 @@ +/* + EnableUIAccess.ahk v1.01 by Lexikos + + USE AT YOUR OWN RISK + + Enables the uiAccess flag in an application's embedded manifest + and signs the file with a self-signed digital certificate. If the + file is in a trusted location (A_ProgramFiles or A_WinDir), this + allows the application to bypass UIPI (User Interface Privilege + Isolation, a part of User Account Control in Vista/7). It also + enables the journal playback hook (SendPlay). + + Command line params (mutually exclusive): + SkipWarning - don't display the initial warning + "" "" - attempt to run silently using the given file(s) + + This script and the provided Lib files may be used, modified, + copied, etc. without restriction. + +*/ + +#NoEnv + +#Include +#Include +#Include +#Include + +; Command line args: +in_file = %1% +out_file = %2% + +if (in_file = "") +MsgBox 49,, +(Join +This script enables the selected AutoHotkey.exe to bypass restrictions + imposed by UIPI, a component of UAC in Windows Vista and 7. To do this + it modifies an attribute in the file's embedded manifest and signs the + file using a self-signed digital certificate, which is then installed + in the local machine's Trusted Root Certification Authorities store.`n +`n +THE RESULTING EXECUTABLE MAY BE UNUSABLE ON ANY SYSTEM WHERE THIS + CERTIFICATE IS NOT INSTALLED.`n +`n +Continue at your own risk. +) +ifMsgBox Cancel + ExitApp + +if !A_IsAdmin +{ + if (in_file = "") + in_file := "SkipWarning" + cmd = "%A_ScriptFullPath%" + if !A_IsCompiled + { ; Use A_AhkPath in case the "runas" verb isn't registered for ahk files. + cmd = "%A_AhkPath%" %cmd% + } + Run *RunAs %cmd% "%in_file%" "%out_file%",, UseErrorLevel + ExitApp +} + +if (in_file = "" || in_file = "SkipWarning") +{ + ; Find AutoHotkey installation. + RegRead InstallDir, HKEY_LOCAL_MACHINE, SOFTWARE\AutoHotkey, InstallDir + if ErrorLevel && A_PtrSize=8 + RegRead InstallDir, HKLM, SOFTWARE\Wow6432Node\AutoHotkey, InstallDir + + ; Let user confirm or select file(s). + FileSelectFile in_file, 1, %InstallDir%\AutoHotkey.exe + , Select Source File, Executable Files (*.exe) + if ErrorLevel + ExitApp + FileSelectFile out_file, S16, %in_file% + , Select Destination File, Executable Files (*.exe) + if ErrorLevel + ExitApp + user_specified_files := true +} + +; Convert short paths to long paths. +Loop %in_file%, 0 + in_file := A_LoopFileLongPath +if (out_file = "") ; i.e. only one file was given via command line. + out_file := in_file +else + Loop %out_file%, 0 + out_file := A_LoopFileLongPath + +if Crypt.IsSigned(in_file) +{ + MsgBox 48,, Input file is already signed. The script will now exit. + ExitApp +} + +if user_specified_files && !IsTrustedLocation(out_file) +{ + MsgBox 49,, + (LTrim Join`s + The path you have selected is not a trusted location. If you choose + to continue, the uiAccess attribute will be set but will not have + any effect until the file is moved to a trusted location. Trusted + locations include \Program Files\ and \Windows\System32\. + ) + ifMsgBox Cancel + ExitApp +} + +if (in_file = out_file) +{ + ; The following should typically work even if the file is in use: + bak_file := in_file "~" A_Now ".bak" + FileMove %in_file%, %bak_file%, 1 + if ErrorLevel + Fail("Failed to rename selected file.") + in_file := bak_file +} +FileCopy %in_file%, %out_file%, 1 +if ErrorLevel + Fail("Failed to copy file to destination.") + + +; Set the uiAccess attribute in the file's manifest to "true". +if !EXE_uiAccess_set(out_file, true) + Fail("Failed to set uiAccess attribute in manifest.") + + +; Open the current user's "Personal" certificate store. +my := Cert.OpenStore(Cert.STORE_PROV_SYSTEM, 0, Cert.SYSTEM_STORE_CURRENT_USER, "wstr", "My") +if !my + Warn("Failed to open 'Personal' certificate store.") + +; Locate "AutoHotkey" certificate created by a previous run of this script. +ahk_cert := my.FindCertificates(0, Cert.FIND_SUBJECT_STR, "wstr", "AutoHotkey")[1] + +if !ahk_cert +{ + ; Create key container. + cr := Crypt.AcquireContext("AutoHotkey", 0, Crypt.PROV_RSA_FULL, Crypt.NEWKEYSET) + if !cr + Fail("Failed to create 'AutoHotkey' key container.") + + ; Generate key for certificate. + key := cr.GenerateKey(Crypt.AT_SIGNATURE, 1024, Crypt.EXPORTABLE) + + ; Create simple certificate name. + cn := new Cert.Name({CommonName: "AutoHotkey"}) + + ; Set end time to 10 years from now. + end_time := SystemTime.Now() + end_time.Year += 10 + + ; Create certificate using the parameters created above. + ahk_cert := cr.CreateSelfSignCertificate(cn, 0, end_time) + if !ahk_cert + Fail("Failed to create 'AutoHotkey' certificate.") + + ; Add certificate to current user's "Personal" store so they won't + ; need to create it again if they need to update the executable. + if !(my.AddCertificate(ahk_cert, Cert.STORE_ADD_NEW)) + Warn("Failed to add certificate to 'Personal' store.") + ; Proceed even if above failed, since it probably doesn't matter. + + ; Attempt to install certificate in trusted store. + root := Cert.OpenStore(Cert.STORE_PROV_SYSTEM, 0, Cert.SYSTEM_STORE_LOCAL_MACHINE, "wstr", "Root") + if !(root && root.AddCertificate(ahk_cert, Cert.STORE_ADD_USE_EXISTING)) + { + if (%True% != "Silent") + MsgBox 49,, + (LTrim Join`s + Failed to install certificate. If you continue, the executable + may become unusable until the certificate is manually installed. + This can typically be done via Digital Signatures tab on the + file's Properties dialog. + ) + ifMsgBox Cancel + ExitApp + } + + key.Dispose() + cr.Dispose() +} + +; Sign the file. +if !SignFile(out_file, ahk_cert, "AutoHotkey") + Fail("Failed to sign file.") + + +; In interactive mode, if not overwriting the original file, offer +; to create an additional context menu item for AHK files. +if (user_specified_files && in_file != out_file) +{ + RegRead uiAccessVerb, HKCR, AutoHotkeyScript\Shell\uiAccess\Command + if ErrorLevel + { + MsgBox 3,, Register "Run Script with UI Access" context menu item? + ifMsgBox Yes + { + RegWrite REG_SZ, HKCR, AutoHotkeyScript\Shell\uiAccess + ,, Run with UI Access + RegWrite REG_SZ, HKCR, AutoHotkeyScript\Shell\uiAccess\Command + ,, "%out_file%" "`%1" `%* + } + ifMsgBox Cancel + ExitApp + } +} + + +; IsTrustedLocation +; Returns true if path is a valid location for uiAccess="true". +IsTrustedLocation(path) +{ + ; http://msdn.microsoft.com/en-us/library/bb756929 + ; MSDN: "\Program Files\ and \windows\system32\ are currently the + ; two allowable protected locations." + ; However, \Program Files (x86)\ also appears to be allowed. + if InStr(path, A_ProgramFiles "\") = 1 + return true + if InStr(path, A_WinDir "\System32\") = 1 + return true + + ; On 64-bit systems, if this script is 32-bit, A_ProgramFiles is + ; %ProgramFiles(x86)%, otherwise it is %ProgramW6432%. So check + ; the opposite "Program Files" folder: + EnvGet other, % A_PtrSize=8 ? "ProgramFiles(x86)" : "ProgramW6432" + if (other != "" && InStr(path, other "\") = 1) + return true + + return false +} + + +; EXE_uiAccess_set +; Sets the uiAccess attribute in an executable file's manifest. +; file - Path of file. +; value - New value; must be boolean (0 or 1). +EXE_uiAccess_set(file, value) +{ + ; Load manifest from EXE file. + xml := ComObjCreate("Msxml2.DOMDocument") + xml.async := false + xml.setProperty("SelectionLanguage", "XPath") + xml.setProperty("SelectionNamespaces" + , "xmlns:v1='urn:schemas-microsoft-com:asm.v1' " + . "xmlns:v3='urn:schemas-microsoft-com:asm.v3'") + if !xml.load("res://" file "/#24/#1") + { + ; This will happen if the file doesn't exist or can't be opened, + ; or if it doesn't have an embedded manifest. + ErrorLevel := "load" + return false + } + + ; Check if any change is necessary. If the uiAccess attribute is + ; not present, it is effectively "false": + node := xml.selectSingleNode("/v1:assembly/v3:trustInfo/security" + . "/requestedPrivileges/requestedExecutionLevel") + if ((node && node.getAttribute("uiAccess") = "true") = value) + { + ErrorLevel := "already set" + return true + } + + ; The follow "IF" section should be unnecessary for AutoHotkey_L. + if !node + { + ; Get assembly node, which should always exist. + if !last := xml.selectSingleNode("/v1:assembly") + { + ErrorLevel := "invalid manifest" + return 0 + } + for _, name in ["trustInfo", "security", "requestedPrivileges" + , "requestedExecutionLevel"] + { + if !(node := last.selectSingleNode("*[local-name()='" name "']")) + { + static NODE_ELEMENT := 1 + node := xml.createNode(NODE_ELEMENT, name + , "urn:schemas-microsoft-com:asm.v3") + last.appendChild(node) + } + last := node + } + ; Since the requestedExecutionLevel node didn't exist before, + ; we must have just created it. Although this attribute *might* + ; not actually be required, it seems best to set it: + node.setAttribute("level", "asInvoker") + } + + ; Set the uiAccess attribute! + node.setAttribute("uiAccess", value ? "true" : "false") + + ; Retrieve XML text. + xml := RTrim(xml.xml, "`r`n") + + ; Convert to UTF-8. + VarSetCapacity(data, data_size := StrPut(xml, "utf-8") - 1) + StrPut(xml, &data, "utf-8") + + ; + ; Replace manifest resource. + ; + + hupd := DllCall("BeginUpdateResource", "str", file, "int", false) + if !hupd + { + ErrorLevel := "BeginUpdateResource" + return false + } + + ; Res type RT_MANIFEST (24), resource ID 1, language English (US) + r := DllCall("UpdateResource", "ptr", hupd, "ptr", 24, "ptr", 1 + , "ushort", 1033, "ptr", &data, "uint", data_size) + + if !DllCall("EndUpdateResource", "ptr", hupd, "int", !r) + { + ErrorLevel := "EndUpdateResource" + return false + } + if !r ; i.e. above succeeded only in discarding the failed changes. + { + ErrorLevel := "UpdateResource" + return false + } + ; Success! + ErrorLevel := 0 + return true +} + + +Fail(msg) +{ + if (%True% != "Silent") + MsgBox 16,, %msg%`n`nErrorLevel: %ErrorLevel%`nA_LastError: %A_LastError% + ExitApp +} + +Warn(msg) +{ + msg .= " (Err " ErrorLevel "; " A_LastError ")`n" + OutputDebug %msg% + FileAppend %msg%, * +} \ No newline at end of file diff --git a/EnableUIAccess/Lib/Cert.ahk b/EnableUIAccess/Lib/Cert.ahk new file mode 100644 index 000000000..7acba6d6a --- /dev/null +++ b/EnableUIAccess/Lib/Cert.ahk @@ -0,0 +1,384 @@ +class Cert +{ + ; Encoding Types +static X509_ASN_ENCODING := 0x00000001 + , PKCS_7_ASN_ENCODING := 0x00010000 + + ; Certificate Information Flags (CERT_INFO_*) +static INFO_VERSION_FLAG := 1 + , INFO_SERIAL_NUMBER_FLAG := 2 + , INFO_SIGNATURE_ALGORITHM_FLAG := 3 + , INFO_ISSUER_FLAG := 4 + , INFO_NOT_BEFORE_FLAG := 5 + , INFO_NOT_AFTER_FLAG := 6 + , INFO_SUBJECT_FLAG := 7 + , INFO_SUBJECT_PUBLIC_KEY_INFO_FLAG := 8 + , INFO_ISSUER_UNIQUE_ID_FLAG := 9 + , INFO_SUBJECT_UNIQUE_ID_FLAG := 10 + , INFO_EXTENSION_FLAG := 11 + + ; Certificate Comparison Functions (CERT_COMPARE_*) +static COMPARE_MASK := 0xFFFF + , COMPARE_SHIFT := (_ := 16) + , COMPARE_ANY := 0 + , COMPARE_SHA1_HASH := 1 + , COMPARE_NAME := 2 + , COMPARE_ATTR := 3 + , COMPARE_MD5_HASH := 4 + , COMPARE_PROPERTY := 5 + , COMPARE_PUBLIC_KEY := 6 + , COMPARE_HASH := Cert.COMPARE_SHA1_HASH + , COMPARE_NAME_STR_A := 7 + , COMPARE_NAME_STR_W := 8 + , COMPARE_KEY_SPEC := 9 + , COMPARE_ENHKEY_USAGE := 10 + , COMPARE_CTL_USAGE := Cert.COMPARE_ENHKEY_USAGE + , COMPARE_SUBJECT_CERT := 11 + , COMPARE_ISSUER_OF := 12 + , COMPARE_EXISTING := 13 + , COMPARE_SIGNATURE_HASH := 14 + , COMPARE_KEY_IDENTIFIER := 15 + , COMPARE_CERT_ID := 16 + , COMPARE_CROSS_CERT_DIST_POINTS := 17 + , COMPARE_PUBKEY_MD5_HASH := 18 + , COMPARE_SUBJECT_INFO_ACCESS := 19 + + ; dwFindType Flags (CERT_FIND_*) +static FIND_ANY := Cert.COMPARE_ANY << _ + , FIND_SHA1_HASH := Cert.COMPARE_SHA1_HASH << _ + , FIND_MD5_HASH := Cert.COMPARE_MD5_HASH << _ + , FIND_SIGNATURE_HASH := Cert.COMPARE_SIGNATURE_HASH << _ + , FIND_KEY_IDENTIFIER := Cert.COMPARE_KEY_IDENTIFIER << _ + , FIND_HASH := Cert.FIND_SHA1_HASH + , FIND_PROPERTY := Cert.COMPARE_PROPERTY << _ + , FIND_PUBLIC_KEY := Cert.COMPARE_PUBLIC_KEY << _ + , FIND_SUBJECT_NAME := (Cert.COMPARE_NAME << _) | Cert.INFO_SUBJECT_FLAG + , FIND_SUBJECT_ATTR := (Cert.COMPARE_ATTR << _) | Cert.INFO_SUBJECT_FLAG + , FIND_ISSUER_NAME := (Cert.COMPARE_NAME << _) | Cert.INFO_ISSUER_FLAG + , FIND_ISSUER_ATTR := (Cert.COMPARE_ATTR << _) | Cert.INFO_ISSUER_FLAG + , FIND_SUBJECT_STR := (Cert.COMPARE_NAME_STR_W << _) | Cert.INFO_SUBJECT_FLAG + , FIND_ISSUER_STR := (Cert.COMPARE_NAME_STR_W << _) | Cert.INFO_ISSUER_FLAG + , FIND_KEY_SPEC := Cert.COMPARE_KEY_SPEC << _ + , FIND_ENHKEY_USAGE := Cert.COMPARE_ENHKEY_USAGE << _ + , FIND_CTL_USAGE := Cert.FIND_ENHKEY_USAGE + , FIND_SUBJECT_CERT := Cert.COMPARE_SUBJECT_CERT << _ + , FIND_ISSUER_OF := Cert.COMPARE_ISSUER_OF << _ + , FIND_EXISTING := Cert.COMPARE_EXISTING << _ + , FIND_CERT_ID := Cert.COMPARE_CERT_ID << _ + , FIND_CROSS_CERT_DIST_POINTS := Cert.COMPARE_CROSS_CERT_DIST_POINTS << _ + , FIND_PUBKEY_MD5_HASH := Cert.COMPARE_PUBKEY_MD5_HASH << _ + , FIND_SUBJECT_INFO_ACCESS := Cert.COMPARE_SUBJECT_INFO_ACCESS << _ + + ; Certificate Store Provider Types (CERT_STORE_PROV_*) +static STORE_PROV_MSG := 1 + , STORE_PROV_MEMORY := 2 + , STORE_PROV_FILE := 3 + , STORE_PROV_REG := 4 + , STORE_PROV_PKCS7 := 5 + , STORE_PROV_SERIALIZED := 6 + , STORE_PROV_FILENAME_A := 7 + , STORE_PROV_FILENAME_W := 8 + , STORE_PROV_FILENAME := Cert.STORE_PROV_FILENAME_W + , STORE_PROV_SYSTEM_A := 9 + , STORE_PROV_SYSTEM_W := 10 + , STORE_PROV_SYSTEM := Cert.STORE_PROV_SYSTEM_W + , STORE_PROV_COLLECTION := 11 + , STORE_PROV_SYSTEM_REGISTRY_A := 12 + , STORE_PROV_SYSTEM_REGISTRY_W := 13 + , STORE_PROV_SYSTEM_REGISTRY := Cert.STORE_PROV_SYSTEM_REGISTRY_W + , STORE_PROV_PHYSICAL_W := 14 + , STORE_PROV_PHYSICAL := Cert.STORE_PROV_PHYSICAL_W + , STORE_PROV_LDAP_W := 16 + , STORE_PROV_LDAP := Cert.STORE_PROV_LDAP_W + , STORE_PROV_PKCS12 := 17 + + ; Certificate Store open/property flags (low-word; CERT_STORE_*) +static STORE_NO_CRYPT_RELEASE_FLAG := 0x0001 + , STORE_SET_LOCALIZED_NAME_FLAG := 0x0002 + , STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG := 0x0004 + , STORE_DELETE_FLAG := 0x0010 + , STORE_UNSAFE_PHYSICAL_FLAG := 0x0020 + , STORE_SHARE_STORE_FLAG := 0x0040 + , STORE_SHARE_CONTEXT_FLAG := 0x0080 + , STORE_MANIFOLD_FLAG := 0x0100 + , STORE_ENUM_ARCHIVED_FLAG := 0x0200 + , STORE_UPDATE_KEYID_FLAG := 0x0400 + , STORE_BACKUP_RESTORE_FLAG := 0x0800 + , STORE_READONLY_FLAG := 0x8000 + , STORE_OPEN_EXISTING_FLAG := 0x4000 + , STORE_CREATE_NEW_FLAG := 0x2000 + , STORE_MAXIMUM_ALLOWED_FLAG := 0x1000 + + ; Certificate System Store Flag Values (high-word; CERT_SYSTEM_STORE_*) +static SYSTEM_STORE_MASK := 0xFFFF0000 + , SYSTEM_STORE_RELOCATE_FLAG := 0x80000000 + , SYSTEM_STORE_UNPROTECTED_FLAG := 0x40000000 + ; Location of the system store: + , SYSTEM_STORE_LOCATION_MASK := 0x00FF0000 + , SYSTEM_STORE_LOCATION_SHIFT := (_ := 16) + ; Registry: HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE + , SYSTEM_STORE_CURRENT_USER_ID := 1 + , SYSTEM_STORE_LOCAL_MACHINE_ID := 2 + ; Registry: HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Services + , SYSTEM_STORE_CURRENT_SERVICE_ID := 4 + , SYSTEM_STORE_SERVICES_ID := 5 + ; Registry: HKEY_USERS + , SYSTEM_STORE_USERS_ID := 6 + ; Registry: HKEY_CURRENT_USER\Software\Policies\Microsoft\SystemCertificates + , SYSTEM_STORE_CURRENT_USER_GROUP_POLICY_ID := 7 + ; Registry: HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\SystemCertificates + , SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY_ID := 8 + ; Registry: HKEY_LOCAL_MACHINE\Software\Microsoft\EnterpriseCertificates + , SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE_ID := 9 + , SYSTEM_STORE_CURRENT_USER := (Cert.SYSTEM_STORE_CURRENT_USER_ID << _) + , SYSTEM_STORE_LOCAL_MACHINE := (Cert.SYSTEM_STORE_LOCAL_MACHINE_ID << _) + , SYSTEM_STORE_CURRENT_SERVICE := (Cert.SYSTEM_STORE_CURRENT_SERVICE_ID << _) + , SYSTEM_STORE_SERVICES := (Cert.SYSTEM_STORE_SERVICES_ID << _) + , SYSTEM_STORE_USERS := (Cert.SYSTEM_STORE_USERS_ID << _) + , SYSTEM_STORE_CURRENT_USER_GROUP_POLICY := (Cert.SYSTEM_STORE_CURRENT_USER_GROUP_POLICY_ID << _) + , SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY := (Cert.SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY_ID << _) + , SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE := (Cert.SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE_ID << _) + + ; Certificate name types (CERT_NAME_*) +static NAME_EMAIL_TYPE := 1 + , NAME_RDN_TYPE := 2 + , NAME_ATTR_TYPE := 3 + , NAME_SIMPLE_DISPLAY_TYPE := 4 + , NAME_FRIENDLY_DISPLAY_TYPE := 5 + , NAME_DNS_TYPE := 6 + , NAME_URL_TYPE := 7 + , NAME_UPN_TYPE := 8 + ; Certificate name flags + , NAME_ISSUER_FLAG := 0x00000001 + , NAME_DISABLE_IE4_UTF8_FLAG := 0x00010000 + , NAME_STR_ENABLE_PUNYCODE_FLAG := 0x00200000 + + ; dwAddDisposition values (CERT_STORE_ADD_*) +static STORE_ADD_NEW := 1 + , STORE_ADD_USE_EXISTING := 2 + , STORE_ADD_REPLACE_EXISTING := 3 + , STORE_ADD_ALWAYS := 4 + , STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES := 5 + , STORE_ADD_NEWER := 6 + , STORE_ADD_NEWER_INHERIT_PROPERTIES := 7 + + ; + ; Static Methods + ; + + OpenStore(pStoreProvider, dwMsgAndCertEncodingType, dwFlags, ParamType="Ptr", Param=0) + { + hCertStore := DllCall("Crypt32\CertOpenStore" + , "ptr", pStoreProvider + , "uint", dwMsgAndCertEncodingType + , "ptr", 0 ; hCryptProv + , "uint", dwFlags + , ParamType, Param) + if hCertStore + hCertStore := new this.Store(hCertStore) + return hCertStore + } + + GetStoreNames(dwFlags) + { + static cb := RegisterCallback("Cert_GetStoreNames_Callback", "F") + global Cert + DllCall("Crypt32\CertEnumSystemStore", "uint", dwFlags + , "ptr", 0, "ptr", &(names := []), "ptr", cb) + return names + } + + + ; + ; Certificate Name + ; + class Name + { + __New(Props) + { + static Fields := { + (Join, + CommonName: "CN" + LocalityName: "L" + Organization: "O" + OrganizationalUnit: "OU" + Email: "E" + Country: "C" + State: "ST" + StreetAddress: "STREET" + Title: "T" + GivenName: "G" + Initials: "I" + Surname: "SN" + Doman: "DC" + )} + static CERT_X500_NAME_STR := 3, Q := """" ; For readability. + + if IsObject(Props) + { + ; Build name string from caller-supplied object. + name_string := "" + for field_name, field_code in Fields + { + if Props.HasKey(field_name) + { + if (name_string != "") + name_string .= ";" + name_string .= field_code "=" Q RegExReplace(Props[field_name], Q, Q Q) Q + } + } + } + else + name_string := Props + + Loop 2 + { + if A_Index=1 + { ; First iteration: retrieve required size. + pbEncoded := 0 + cbEncoded := 0 + } + else + { ; Second iteration: retrieve encoded name. + this.SetCapacity("data", cbEncoded) + pbEncoded := this.GetAddress("data") + } + global Cert + if !DllCall("Crypt32\CertStrToName" + , "uint", Cert.X509_ASN_ENCODING + , "str", name_string + , "uint", CERT_X500_NAME_STR + , "ptr", 0 ; Reserved + , "ptr", pbEncoded + , "uint*", cbEncoded + , "str*", ErrorString) + { + ErrorLevel := ErrorString + return false + } + } + this.SetCapacity("blob", A_PtrSize*2) ; CERT_NAME_BLOB + NumPut(pbEncoded, NumPut(cbEncoded, this.p := this.GetAddress("blob"))) + } + } + + + ; + ; Certificate Store + ; + class Store + { + FindCertificates(dwFindFlags, dwFindType, FindParamType="ptr", FindParam=0) + { + global Cert + hStore := this.h + , dwCertEncodingType := Cert.X509_ASN_ENCODING | Cert.PKCS_7_ASN_ENCODING + , ctx := new Cert.Context(0) + , certs := [] + while ctx.p := DllCall("Crypt32\CertFindCertificateInStore" + , "ptr", hStore + , "uint", dwCertEncodingType + , "uint", dwFindFlags + , "uint", dwFindType + , FindParamType, FindParam + , "ptr", ctx.p ; If non-NULL, this context is freed. + , "ptr") + { + ; Each certificate context must be duplicated since the next + ; call will free it. + certs.Insert(ctx.Duplicate()) + } + ctx.p := 0 ; Above freed it already. + return certs + } + + AddCertificate(Certificate, dwAddDisposition) + { + if !DllCall("Crypt32\CertAddCertificateContextToStore" + , "ptr", this.h + , "ptr", Certificate.p + , "uint", dwAddDisposition + , "ptr*", pStoreContext) + return 0 + global Cert + return pStoreContext ? new Cert.Context(pStoreContext) : 0 + } + + __New(handle) + { + this.h := handle + } + + __Delete() + { + if this.h && DllCall("Crypt32\CertCloseStore", "ptr", this.h, "uint", 0) + this.h := 0 + } + + static Dispose := Cert.Store.__Delete ; Alias + } + + + ; + ; Certificate Context + ; + class Context + { + __New(ptr) + { + this.p := ptr + } + + __Delete() + { + if this.p && DllCall("Crypt32\CertFreeCertificateContext", "ptr", this.p) + this.p := 0 + } + + ; CertGetNameString + ; http://msdn.microsoft.com/en-us/library/aa376086 + GetNameString(dwType, dwFlags=0, pvTypePara=0) + { + if !this.p + return + cc := DllCall("Crypt32\CertGetNameString", "ptr", this.p, "uint", dwType, "uint", dwFlags, "ptr", pvTypePara, "ptr", 0, "uint", 0) + if cc <= 1 ; i.e. empty string. + return + VarSetCapacity(name, cc*2) + DllCall("Crypt32\CertGetNameString", "ptr", this.p, "uint", dwType, "uint", dwFlags, "ptr", pvTypePara, "str", name, "uint", cc) + return name + } + + ; CertDuplicateCertificateContext + ; http://msdn.microsoft.com/en-us/library/aa376045 + Duplicate() + { + return this.p && (p := DllCall("Crypt32\CertDuplicateCertificateContext", "ptr", this.p)) + ? new this.base(p) : p + } + + static Dispose := Cert.Context.__Delete ; Alias + } + + + ; + ; Error Detection + ; + __Get(name) + { + ListLines + MsgBox 16,, Attempt to access invalid property Cert.%name%. + Pause + } +} + + +; +; Internal +; + +Cert_GetStoreNames_Callback(pvSystemStore, dwFlags, pStoreInfo, pvReserved, pvArg) +{ + Object(pvArg).Insert(StrGet(pvSystemStore, "utf-16")) + return true +} \ No newline at end of file diff --git a/EnableUIAccess/Lib/Crypt.ahk b/EnableUIAccess/Lib/Crypt.ahk new file mode 100644 index 000000000..6e6770b52 --- /dev/null +++ b/EnableUIAccess/Lib/Crypt.ahk @@ -0,0 +1,161 @@ +class Crypt +{ + ; Provider Types +static PROV_RSA_FULL := 1 + , PROV_RSA_SIG := 2 + , PROV_DSS := 3 + , PROV_FORTEZZA := 4 + , PROV_MS_EXCHANGE := 5 + , PROV_SSL := 6 + , PROV_STT_MER := 7 ; <= XP + , PROV_STT_ACQ := 8 ; <= XP + , PROV_STT_BRND := 9 ; <= XP + , PROV_STT_ROOT := 10 ; <= XP + , PROV_STT_ISS := 11 ; <= XP + , PROV_RSA_SCHANNEL := 12 + , PROV_DSS_DH := 13 + , PROV_EC_ECDSA_SIG := 14 + , PROV_EC_ECNRA_SIG := 15 + , PROV_EC_ECDSA_FULL := 16 + , PROV_EC_ECNRA_FULL := 17 + , PROV_DH_SCHANNEL := 18 + , PROV_SPYRUS_LYNKS := 20 + , PROV_RNG := 21 + , PROV_INTEL_SEC := 22 + , PROV_REPLACE_OWF := 23 ; >= XP + , PROV_RSA_AES := 24 ; >= XP + + ; CryptAcquireContext - dwFlags + ; http://msdn.microsoft.com/en-us/library/aa379886 +static VERIFYCONTEXT := 0xF0000000 + , NEWKEYSET := 0x00000008 + , DELETEKEYSET := 0x00000010 + , MACHINE_KEYSET := 0x00000020 + , SILENT := 0x00000040 + , CRYPT_DEFAULT_CONTAINER_OPTIONAL := 0x00000080 + + ; CryptGenKey - dwFlag + ; http://msdn.microsoft.com/en-us/library/aa379941 +static EXPORTABLE := 0x00000001 + , USER_PROTECTED := 0x00000002 + , CREATE_SALT := 0x00000004 + , UPDATE_KEY := 0x00000008 + , NO_SALT := 0x00000010 + , PREGEN := 0x00000040 + , ARCHIVABLE := 0x00004000 + , FORCE_KEY_PROTECTION_HIGH := 0x00008000 + + ; Key Types +static AT_KEYEXCHANGE := 1 + , AT_SIGNATURE := 2 + + ; + ; METHODS + ; + + AcquireContext(Container, Provider, dwProvType, dwFlags) + { + if DllCall("Advapi32\CryptAcquireContext" + , "ptr*", hProv + , "ptr", Container ? &Container : 0 + , "ptr", Provider ? &Provider : 0 + , "uint", dwProvType + , "uint", dwFlags) + { + if (dwFlags & this.DELETEKEYSET) + ; Success, but hProv is invalid in this case. + return 1 + ; Wrap it up so it'll be released at some point. + return new this.Context(hProv) + } + return 0 + } + + IsSigned(FilePath) + { + return DllCall("Crypt32\CryptQueryObject" + , "uint", CERT_QUERY_OBJECT_FILE := 1 + , "wstr", FilePath + , "uint", CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED := 1<<10 + , "uint", CERT_QUERY_FORMAT_FLAG_BINARY := 2 + , "uint", 0 + , "uint*", dwEncoding + , "uint*", dwContentType + , "uint*", dwFormatType + , "ptr", 0 + , "ptr", 0 + , "ptr", 0) + } + + ; + ; Error Detection + ; + __Get(name) + { + ListLines + MsgBox 16,, Attempt to access invalid property Crypt.%name%. + Pause + } + + ; + ; CLASSES + ; + + class _Handle + { + __New(handle) + { + this.h := handle + } + + __Delete() + { + this.Dispose() + } + } + + class Context extends Crypt._Handle + { + GenerateKey(KeyType, KeyBitLength, dwFlags) + { + if DllCall("Advapi32\CryptGenKey" + , "ptr", this.h + , "uint", KeyType + , "uint", (KeyBitLength << 16) | dwFlags + , "ptr*", hKey) + { + global Crypt + return new Crypt.Key(hKey) + } + return 0 + } + + CreateSelfSignCertificate(NameObject, StartTime, EndTime) + { + ctx := DllCall("Crypt32\CertCreateSelfSignCertificate" + , "ptr", this.h + , "ptr", IsObject(NameObject) ? NameObject.p : NameObject + , "uint", 0, "ptr", 0, "ptr", 0 + , "ptr", IsObject(StartTime) ? StartTime.p : StartTime + , "ptr", IsObject(EndTime) ? EndTime.p : EndTime + , "ptr", 0, "ptr") + global Cert + return ctx ? new Cert.Context(ctx) : 0 + } + + Dispose() + { + if this.h && DllCall("Advapi32\CryptReleaseContext", "ptr", this.h, "uint", 0) + this.h := 0 + } + } + + class Key extends Crypt._Handle + { + Dispose() + { + if this.h && DllCall("Advapi32\CryptDestroyKey", "ptr", this.h) + this.h := 0 + } + } +} \ No newline at end of file diff --git a/EnableUIAccess/Lib/SignFile.ahk b/EnableUIAccess/Lib/SignFile.ahk new file mode 100644 index 000000000..85e1ce879 --- /dev/null +++ b/EnableUIAccess/Lib/SignFile.ahk @@ -0,0 +1,52 @@ +SignFile(File, CertCtx, Name) +{ + VarSetCapacity(wfile, 2 * StrPut(File, "utf-16")), StrPut(File, &wfile, "utf-16") + VarSetCapacity(wname, 2 * StrPut(Name, "utf-16")), StrPut(Name, &wname, "utf-16") + cert_ptr := IsObject(CertCtx) ? CertCtx.p : CertCtx + + VarSetCapacity(file_info, A_PtrSize*3, 0) ; SIGNER_FILE_INFO + NumPut(3*A_PtrSize, file_info, 0) + NumPut(&wfile, file_info, A_PtrSize) + + VarSetCapacity(dwIndex, 4, 0) + + VarSetCapacity(subject_info, A_PtrSize*4, 0) ; SIGNER_SUBJECT_INFO + NumPut(A_PtrSize*4, subject_info, 0) + NumPut(&dwIndex, subject_info, A_PtrSize) ; MSDN: "must be set to zero" in this case means "must be set to the address of a field containing zero". + NumPut(SIGNER_SUBJECT_FILE:=1, subject_info, A_PtrSize*2) + NumPut(&file_info, subject_info, A_PtrSize*3) + + VarSetCapacity(cert_store_info, A_PtrSize*4, 0) ; SIGNER_CERT_STORE_INFO + NumPut(A_PtrSize*4, cert_store_info, 0) + NumPut(cert_ptr, cert_store_info, A_PtrSize) + NumPut(SIGNER_CERT_POLICY_CHAIN:=2, cert_store_info, A_PtrSize*3) + + VarSetCapacity(cert_info, 8+A_PtrSize*2, 0) ; SIGNER_CERT + NumPut(8+A_PtrSize*2, cert_info, 0, "uint") + NumPut(SIGNER_CERT_STORE:=2, cert_info, 4, "uint") + NumPut(&cert_store_info, cert_info, 8) + + VarSetCapacity(authcode_attr, 8+A_PtrSize*3, 0) ; SIGNER_ATTR_AUTHCODE + NumPut(8+A_PtrSize*3, authcode_attr, 0, "uint") + NumPut(false, authcode_attr, 4, "int") ; fCommercial + NumPut(true, authcode_attr, 8) ; fIndividual + NumPut(&wname, authcode_attr, 8+A_PtrSize) + + VarSetCapacity(sig_info, 8+A_PtrSize*4, 0) ; SIGNER_SIGNATURE_INFO + NumPut(8+A_PtrSize*4, sig_info, 0, "uint") + NumPut(CALG_SHA1:=0x8004, sig_info, 4, "uint") + NumPut(SIGNER_AUTHCODE_ATTR:=1, sig_info, 8) + NumPut(&authcode_attr, sig_info, 8+A_PtrSize) + + hr := DllCall("MSSign32\SignerSign" + , "ptr", &subject_info + , "ptr", &cert_info + , "ptr", &sig_info + , "ptr", 0 ; pProviderInfo + , "ptr", 0 ; pwszHttpTimeStamp + , "ptr", 0 ; psRequest + , "ptr", 0 ; pSipData + , "uint") + + return 0 == (ErrorLevel := hr) +} \ No newline at end of file diff --git a/EnableUIAccess/Lib/SystemTime.ahk b/EnableUIAccess/Lib/SystemTime.ahk new file mode 100644 index 000000000..446e0b2ea --- /dev/null +++ b/EnableUIAccess/Lib/SystemTime.ahk @@ -0,0 +1,101 @@ +/* + SystemTime - Wrapper for Win32 SYSTEMTIME Structure + http://msdn.microsoft.com/en-us/library/ms724950 + + Usage Examples: + + ; Create structure from string. + st := SystemTime.FromString(A_Now) + + ; Shortcut: + st := SystemTime.Now() + + ; Update values. + st.FromString(A_Now) + + ; Retrieve components. + year := st.Year + month := st.Month + weekday := st.DayOfWeek + day := st.Day + hour := st.Hour + minute := st.Minute + second := st.Second + ms := st.Milliseconds + + ; Set or perform math on component. + st.Year += 10 + + ; Create structure to receive output from DllCall. + st := new SystemTime + DllCall("GetSystemTime", "ptr", st.p) + MsgBox % st.ToString() + + ; Fill external structure. + st := SystemTime.FromPointer(externalPointer) + st.FromString(A_Now) + + ; Convert external structure to string. + MsgBox % SystemTime.ToString(externalPointer) + +*/ + +class SystemTime +{ + FromString(str) + { + if this.p + st := this + else + st := new this + if !(p := st.p) + return 0 + FormatTime wday, %str%, WDay + wday -= 1 + FormatTime str, %str%, yyyy M '%wday%' d H m s '0' + Loop Parse, str, %A_Space% + NumPut(A_LoopField, p+(A_Index-1)*2, "ushort") + return st + } + + FromPointer(pointer) + { + return { p: pointer, base: this } ; Does not call __New. + } + + ToString(st = 0) + { + if !(p := (st ? (IsObject(st) ? st.p : st) : this.p)) + return "" + VarSetCapacity(s, 28), s := SubStr("000" NumGet(p+0, "ushort"), -3) + Loop 6 + if A_Index != 2 + s .= SubStr("0" NumGet(p+A_Index*2, "ushort"), -1) + return s + } + + Now() + { + return this.FromString(A_Now) + } + + __New() + { + if !(this.SetCapacity("struct", 16)) + || !(this.p := this.GetAddress("struct")) + return 0 + NumPut(0, NumPut(0, this.p, "int64"), "int64") + } + + __GetSet(name, value="") + { + static fields := {Year:0, Month:2, DayOfWeek:4, Day:6, Hour:8 + , Minute:10, Second:12, Milliseconds:14} + if fields.HasKey(name) + return value="" + ? NumGet( this.p + fields[name], "ushort") + : NumPut(value, this.p + fields[name], "ushort") + } + static __Get := SystemTime.__GetSet + static __Set := SystemTime.__GetSet +} \ No newline at end of file diff --git a/build.rs b/build.rs new file mode 100644 index 000000000..4d53b5bab --- /dev/null +++ b/build.rs @@ -0,0 +1,61 @@ +fn main() -> std::io::Result<()> { + #[cfg(all(target_os = "windows", feature = "win_manifest"))] + { + windows::build()?; + } + Ok(()) +} + +#[cfg(all(target_os = "windows", feature = "win_manifest"))] +mod windows { + use indoc::formatdoc; + use regex::Regex; + use std::fs::File; + use std::io::Write; + extern crate embed_resource; + + // println! during build + macro_rules! pb { + ($($tokens:tt)*) => {println!("cargo:warning={}", format!($($tokens)*))}} + + pub(super) fn build() -> std::io::Result<()> { + let manifest_path: &str = "./target/kanata.exe.manifest"; + + // Note about expected version format: + // MS says "Use the four-part version format: mmmmm.nnnnn.ooooo.ppppp" + // https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests + + let re_ver_build = Regex::new(r"^(?(\d+\.){2}\d+)[-a-zA-Z]+(?\d+)$").unwrap(); + let re_version3 = Regex::new(r"^(\d+\.){2}\d+$").unwrap(); + let mut version: String = env!("CARGO_PKG_VERSION").to_string(); + + if re_version3.find(&version).is_some() { + version = format!("{}.0", version); + } else if re_ver_build.find(&version).is_some() { + version = re_ver_build + .replace_all(&version, r"$vpre.$vpos") + .to_string(); + } else { + pb!("unknown version format '{}', using '0.0.0.0'", version); + version = "0.0.0.0".to_string(); + } + + let manifest_str = formatdoc!( + r#" + + + + + + + + + "#, + version + ); + let mut manifest_f = File::create(manifest_path)?; + write!(manifest_f, "{}", manifest_str)?; + embed_resource::compile("./src/kanata.exe.manifest.rc", embed_resource::NONE); + Ok(()) + } +} diff --git a/docs/config.adoc b/docs/config.adoc index 4ed5c2fdc..cc1ef28bc 100644 --- a/docs/config.adoc +++ b/docs/config.adoc @@ -3248,6 +3248,22 @@ https://github.com/jtroo/kanata/blob/main/cfg_samples/fancy_symbols.md[example c * hold `+⎇›+` and tap `+Delete+` would insert `+␡+` +[[windows-only-work-elevated]] +=== Windows only: enable in elevated windows +<> + +The default `kanata.exe` binary doesn't work in elevated windows (run with administrative privileges), +e.g., `Control Panel`. However, you can use AutoHotkey's "EnableUIAccess" script to self-sign the binary, +move it to "Program Files", then launching kanata from there will also work in these elevated windows. +See https://github.com/jtroo/kanata/blob/main/EnableUIAccess[EnableUIAccess] folder with the script +and its requires libraries (needs https://www.autohotkey.com/download/ahk-install.exe[AutoHotkey v1] installed) + +If compiling yourself, you should add the feature flag `win_manifest` +to enable the use of the `EnableUIAccess` script: + +``` +cargo build --win_manifest +``` [[test-your-config]] === Test your config diff --git a/justfile b/justfile index 8eb0f9dce..7797f0c38 100644 --- a/justfile +++ b/justfile @@ -12,12 +12,12 @@ build_release_linux output_dir: # Build the release binaries for Windows and put the binaries+cfg in the output directory. build_release_windows output_dir: - cargo build --release; cp target/release/kanata.exe "{{output_dir}}\kanata.exe" - cargo build --release --features interception_driver; cp target/release/kanata.exe "{{output_dir}}\kanata_wintercept.exe" - cargo build --release --features win_sendinput_send_scancodes; cp target/release/kanata.exe "{{output_dir}}\kanata_scancode_experimental.exe" - cargo build --release --features win_sendinput_send_scancodes,win_llhook_read_scancodes; cp target/release/kanata.exe "{{output_dir}}\kanata_winIOv2.exe" - cargo build --release --features cmd; cp target/release/kanata.exe "{{output_dir}}\kanata_cmd_allowed.exe" - cargo build --release --features cmd,interception_driver; cp target/release/kanata.exe "{{output_dir}}\kanata_wintercept_cmd_allowed.exe" + cargo build --release --features win_manifest; cp target/release/kanata.exe "{{output_dir}}\kanata.exe" + cargo build --release --features win_manifest,interception_driver; cp target/release/kanata.exe "{{output_dir}}\kanata_wintercept.exe" + cargo build --release --features win_manifest,win_sendinput_send_scancodes; cp target/release/kanata.exe "{{output_dir}}\kanata_scancode_experimental.exe" + cargo build --release --features win_manifest,win_sendinput_send_scancodes,win_llhook_read_scancodes; cp target/release/kanata.exe "{{output_dir}}\kanata_winIOv2.exe" + cargo build --release --features win_manifest,cmd; cp target/release/kanata.exe "{{output_dir}}\kanata_cmd_allowed.exe" + cargo build --release --features win_manifest,cmd,interception_driver; cp target/release/kanata.exe "{{output_dir}}\kanata_wintercept_cmd_allowed.exe" cp cfg_samples/kanata.kbd "{{output_dir}}" # Generate the sha256sums for all files in the output directory diff --git a/src/kanata.exe.manifest.rc b/src/kanata.exe.manifest.rc new file mode 100644 index 000000000..9c6384d3d --- /dev/null +++ b/src/kanata.exe.manifest.rc @@ -0,0 +1,2 @@ +#define RT_MANIFEST 24 +1 RT_MANIFEST "./target/kanata.exe.manifest"