├── AHK_Portable_Installer.ahk ├── LICENSE.md ├── README.md ├── images └── ahk-pi-main.png ├── inc ├── TheArkive_reg2.ahk ├── _JXON.ahk └── funcs.ahk └── resources ├── AHK_pi_blue.ico ├── AHK_pi_green.ico ├── AHK_pi_orange.ico ├── AHK_pi_pink.ico ├── AHK_pi_red.ico ├── TemplateV1.ahk └── TemplateV2.ahk /AHK_Portable_Installer.ahk: -------------------------------------------------------------------------------- 1 | ; AHK v2 2 | ; ======================================================================================= 3 | ; thanks to boiler 4 | ; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=76602&p=332166&hilit=boiler+RAAV#p332166 5 | ; thanks to Rapte_Of_Suzaku 6 | ; https://autohotkey.com/board/topic/60985-get-paths-of-selected-items-in-an-explorer-window/ 7 | ; thanks to TeaDrinker 8 | ; https://www.autohotkey.com/boards/viewtopic.php?p=255169#p255169 9 | ; ======================================================================================= 10 | ; The above users' contributions were crucial to AHK Portable Installer now being fully portable. 11 | ; 12 | ; Also thanks to hoppfrosch for initial testing which helped get the basics working. 13 | ; ======================================================================================= 14 | 15 | ; "D:\Apps\DEV\AutoHotkey\AutoHotkey_2.0-a138-7538f26f\AutoHotkey64.exe" "D:\Drive\UserData\DEV\AHK\AHK_Portable_Installer\AHK Portable Installer.ahk" 16 | 17 | #SingleInstance Off ; Allow multiple instances, for when running in non-portable mode. 18 | ; This installer script is "consulted" when launching a script or when 19 | ; launching the compiler. So for a split second, there needs to be 2 20 | ; instances of the script while this "consulting" takes place. Once 21 | ; the end result is finally launched, this 2nd instance closes. 22 | 23 | SendMode "Input" ; Recommended for new scripts due to its superior speed and reliability. 24 | SetWorkingDir A_ScriptDir ; Ensures a consistent starting directory. 25 | 26 | #INCLUDE inc\_JXON.ahk 27 | #INCLUDE inc\TheArkive_reg2.ahk 28 | #INCLUDE inc\funcs.ahk 29 | 30 | Global oGui := "", Settings := "" 31 | 32 | class app { 33 | Static ver := "v1.29" 34 | , lastClick := 0, last_click_diff := 1000, last_xy := 0 35 | , ReadOnly := false ; this is for launching a version of AHK, and is set to TRUE when doing so - prevents unnecessary saving settings to disk 36 | , toggle := 0, w := 480, h := 225 37 | , http := ComObject("Msxml2.XMLHTTP") ; Msxml2.XMLHTTP ; WinHttp.WinHttpRequest.5.1 38 | , http_url_list := [] 39 | 40 | , latest_list := Map("AutoHotkey v1.1" ,{url:"https://www.autohotkey.com/download/1.1/version.txt" , format:"html", filter:""} 41 | ,"AutoHotkey v2.0" ,{url:"https://www.autohotkey.com/download/2.0/version.txt" , format:"html", filter:""} 42 | ,"Ahk2Exe" ,{url:"https://api.github.com/repos/AutoHotkey/Ahk2Exe/releases/latest", format:"json", filter:"Ahk2Exe"} 43 | ,"UPX" ,{url:"https://api.github.com/repos/upx/upx/releases/latest" , format:"json", filter:""}) 44 | 45 | , get_list := Map("AutoHotkey v1.1" ,{url:"https://www.autohotkey.com/download/1.1" , format:"html", filter:""} 46 | ,"AutoHotkey v2.0" ,{url:"https://www.autohotkey.com/download/2.0" , format:"html", filter:""} 47 | ,"Ahk2Exe" ,{url:"https://api.github.com/repos/AutoHotkey/Ahk2Exe/releases/latest", format:"json", filter:"Ahk2Exe"} 48 | ,"UPX" ,{url:"https://api.github.com/repos/upx/upx/releases/latest" , format:"json", filter:""} 49 | 50 | ,"MPRESS" ,{url:"https://web.archive.org/web/20130516045244if_/http://www.matcode.com/mpress.219.zip", format:"zip", filter:""}) 51 | 52 | , verUpdate := Map(), gui := "" 53 | , AhkDlVer := ["1.1","2.0"] ; versions of AHK inthe drop down menu, relates to the URL 54 | } 55 | 56 | OnExit(on_exit) 57 | 58 | If !DirExist("temp") 59 | DirCreate "temp" 60 | If !DirExist("versions") 61 | DirCreate "versions" 62 | 63 | If (A_Is64BitOS) 64 | reg.view := 64 65 | 66 | SettingsJSON := "" 67 | If FileExist("Settings.json") 68 | SettingsJSON := FileRead("Settings.json") 69 | Settings := (SettingsJSON && (SettingsJSON!='""')) ? Jxon_Load(&SettingsJSON) : Map() 70 | 71 | (!Settings.Has("AhkVersions")) ? Settings["AhkVersions"] := Map() : "" 72 | (!Settings.Has("posX")) ? Settings["posX"] := 300 : "" 73 | (!Settings.Has("posY")) ? Settings["posY"] := 300 : "" 74 | 75 | (!Settings.Has("DisableTooltips")) ? Settings["DisableTooltips"] := true : "" 76 | (!Settings.Has("PortableMode")) ? Settings["PortableMode"] := 0 : "" 77 | (!Settings.Has("ShowCompileScript")) ? Settings["ShowCompileScript"] := true : "" 78 | (!Settings.Has("ShowEditScript")) ? Settings["ShowEditScript"] := true : "" 79 | (!Settings.Has("ShowRunScript")) ? Settings["ShowRunScript"] := true : "" 80 | (!Settings.Has("HideTrayIcon")) ? Settings["HideTrayIcon"] := true : "" 81 | (!Settings.Has("MinimizeOnStart")) ? Settings["MinimizeOnStart"] := false : "" 82 | (!Settings.Has("CloseToTry")) ? Settings["CloseToTray"] := 0 : "" 83 | (!Settings.Has("SystemStartup")) ? Settings["SystemStartup"] := 0 : "" 84 | (!Settings.Has("PickIcon")) ? Settings["PickIcon"] := "Default" : "" 85 | (!Settings.Has("AutoUpdateCheck")) ? Settings["AutoUpdateCheck"] := true : "" 86 | (!Settings.Has("UpdateCheckDate")) ? Settings["UpdateCheckDate"] := "" : "" 87 | (!Settings.Has("AddToPath")) ? Settings["AddToPath"] := 0 : "" 88 | (!Settings.Has("CopyExe")) ? Settings["CopyExe"] := 0 : "" 89 | (!Settings.Has("RegisterAHKexe")) ? Settings["RegisterAHKexe"] := 0 : "" 90 | 91 | (!Settings.Has("ActiveVersionPath")) ? Settings["ActiveVersionPath"] := "" : "" 92 | (!Settings.Has("ActiveVersionDisp")) ? Settings["ActiveVersionDisp"] := "" : "" 93 | 94 | (!Settings.Has("TextEditorPath")) ? Settings["TextEditorPath"] := "notepad.exe" : "" 95 | 96 | (!Settings.Has("DLVersion")) ? Settings["DLVersion"] := "2.0" : "" 97 | 98 | (!Settings.Has("BaseFolder")) ? Settings["BaseFolder"] := "" : "" 99 | (!Settings.Has("InstallProfile")) ? Settings["InstallProfile"] := "Current User" : "" 100 | (!Settings.Has("reg")) ? Settings["reg"] := "HKEY_CURRENT_USER" : "" 101 | (!Settings.Has("UPX")) ? Settings["UPX"] := "win64" : "" 102 | 103 | If Settings["HideTrayIcon"] { 104 | A_IconHidden := true 105 | } 106 | 107 | app.latest_list["UPX"].filter := Settings["UPX"] 108 | app.get_list["UPX"].filter := Settings["UPX"] 109 | ; ==================================================================================== 110 | ; Processing Parameters for launching scripts and the Compiler. 111 | ; - Creates a 2nd instance in non-portable mode for a split second. 112 | ; ==================================================================================== 113 | 114 | If A_Args.Length { 115 | If FileExist(A_Args[1]) && RegExMatch(A_Args[1],"i).+\.ahk$") { 116 | A_Args[1] := "Launch" 117 | While A_Args.Has(2) 118 | A_Args.RemoveAt(2) 119 | 120 | old_args := A_Args 121 | For i, arg in old_args 122 | A_Args.Push(arg) 123 | } 124 | 125 | app.ReadOnly := true, in_file := A_Args[2], err := "" 126 | 127 | If !RegExMatch(A_Args[1],"(?:Compile|Launch(Admin)?)") 128 | err := "Invalid first parameter:`n`nParam one must be LAUNCH, LAUNCHADMIN, or COMPILE." 129 | Else If (A_Args.Length < 2) 130 | err := "Invalid second parameter.`n`nThere appears to be no script file specified." 131 | Else If !FileExist(in_file) && (in_file != "*") 132 | err := "Script file does not exist:`n`n" in_file 133 | If err { 134 | Msgbox(err,"ERROR",0x10) 135 | ExitApp 136 | } 137 | 138 | obj := proc_script(in_file, ((A_Args[1]="Compile")?true:false)) 139 | SplitPath in_file,,&dir 140 | If RegExMatch(A_Args[1],"Launch(?:Admin)?") { 141 | obj.admin := (A_Args[1] = "LaunchAdmin") ? true : false ; set admin mode 142 | ahkCmd := (obj.admin?"*RunAs ":"") '"' obj.exe '"' (obj.admin?" /restart ":" ") '"' in_file '"' (sp()?" " sp():"") 143 | } Else If (A_Args[1] = "Compile") 144 | ahkCmd := '"' obj.exe '" /in "' in_file '" /gui' 145 | 146 | If obj.err { 147 | Msgbox obj.err 148 | ExitApp 149 | } Else If !FileExist(obj.exe) && obj.exe { ; i suppose this might not happen very often, or ever? 150 | msg := "The following EXE cannot be found:`n`n" 151 | . obj.exe "`n`n" 152 | . "Did you recently move or rename some folders?" 153 | Msgbox(msg,"File not found",0x10) 154 | ExitApp 155 | } Else If !obj.exe { 156 | msg := "The following #Requires directive could not find a match:`n`n" 157 | . obj.cond "`n`n" 158 | . "If you are using the 'match' option, the #Requires filter may be too inspecific.`n`n" 159 | . "Either define a more specific #Requires filter, or don't use the 'match' option." 160 | Msgbox(msg,"No match found",0x10) 161 | } 162 | 163 | Run(ahkCmd, dir) 164 | ExitApp ; Close this instance after deciding which AHK.exe / compiler to run. 165 | } 166 | 167 | sp() { ; script params 168 | op := "", i := 3 169 | If (A_Args.Length >= 3) ; concat otherParams 170 | Loop (A_Args.Length - 2) 171 | op .= ((i>3) ? " " : "") '"' A_Args[i++] '"' 172 | return op 173 | } 174 | 175 | 176 | ; ==================================================================================== 177 | ; ==================================================================================== 178 | 179 | If Settings["PickIcon"] = "Default" { 180 | f := GetAhkProps(Settings["ActiveVersionPath"]) 181 | if f 182 | TraySetIcon(f.exePath,0) 183 | } Else TraySetIcon("resources\AHK_pi_" Settings["PickIcon"] ".ico") 184 | 185 | If A_IsCompiled { 186 | Loop 2 187 | A_TrayMenu.Delete("1&") 188 | } Else { 189 | Loop 9 190 | A_TrayMenu.Delete("1&") 191 | } 192 | A_TrayMenu.Insert("1&","Open",tray_menu) 193 | A_TrayMenu.Default := "Open" 194 | 195 | tray_menu(txt, pos, m) { 196 | If (txt="Open") 197 | runGui() 198 | Else If (txt="Exit") 199 | ExitApp 200 | } 201 | 202 | monitor := GetMonitorData() 203 | If Settings["posX"] > monitor.right Or Settings["posX"] < monitor.left 204 | Settings["posX"] := 200 205 | If Settings["posY"] > monitor.bottom Or Settings["posY"] < monitor.top 206 | Settings["posY"] := 200 207 | 208 | OnMessage(0x0200,WM_MOUSEMOVE) ; WM_MOUSEMOVE 209 | WM_MOUSEMOVE(wParam, lParam, Msg, hwnd) { 210 | Global Settings, oGui 211 | 212 | If (!Settings["DisableTooltips"]) { 213 | 214 | If (hwnd = oGui["ActivateExe"].Hwnd) 215 | ToolTip "Sets the Base Version.`r`n`r`n" 216 | . "Modify settings as desired first, including templates.`r`n" 217 | . "Then click this button." 218 | 219 | Else If (hwnd = oGui["ExeList"].Hwnd) 220 | ToolTip "List of Base Versions to choose from.`r`n" 221 | . "Select then click the [Install/Select] button.`r`n" 222 | . "Be sure to modify settings as desired first, like`r`n" 223 | . "context-menu items and Text Editor." 224 | 225 | Else If (hwnd = oGui["CurrentPath"].Hwnd) 226 | ToolTip oGui["CurrentPath"].Value 227 | 228 | Else 229 | ToolTip 230 | } 231 | } 232 | 233 | If (Settings["MinimizeOnStart"]) { 234 | If !Settings["CloseToTray"] 235 | runGui(true) 236 | } Else RunGui() 237 | 238 | runGui(minimize:=false) { 239 | Global oGui, Settings 240 | oGui := Gui("","AHK Portable Installer " app.ver) 241 | oGui.OnEvent("Close",gui_Close) 242 | 243 | oGui.Add("Text","vActiveVersionDisp xm y+8 w409 -E0x200 ReadOnly","Base Version:") 244 | oGui.Add("Button","vVersionDisp x+2 yp-4 w50","Latest").OnEvent("Click",GuiEvents) 245 | 246 | LV := oGui.Add("ListView","xm y+0 r5 w460 vExeList -Multi",["Description","Version","File Name","Full Path"]) 247 | LV.OnEvent("DoubleClick",GuiEvents), LV.OnEvent("Click",ListClick) 248 | LV.OnEvent("ContextMenu",conEvent) 249 | 250 | oGui.Add("Edit","vCurrentPath xm y+8 w440 -E0x200 ReadOnly","Path: ") 251 | 252 | oGui.Add("Button","vToggleSettings y+0","Settings").OnEvent("Click",GuiEvents) 253 | oGui.Add("Button","vHelp x+40","Help").OnEvent("Click",GuiEvents) 254 | oGui.Add("Button","vCompiler x+0","Compiler").OnEvent("Click",GuiEvents) 255 | oGui.Add("Button","vWindowSpy x+0","Window Spy").OnEvent("Click",GuiEvents) 256 | oGui.Add("Button","vUninstall x+0","Uninstall AHK").OnEvent("Click",GuiEvents) 257 | oGui.Add("Button","vActivateExe x+40 yp w78","Install").OnEvent("Click",GuiEvents) 258 | 259 | tabs := oGui.Add("Tab","y+10 x2 w476 h275",["Downloads","Basics","Options"]) 260 | 261 | tabs.UseTab("Downloads") 262 | 263 | oGui.Add("Text","xm y+10","Version:") 264 | oGui.Add("DropDownList","vDLVersion x+2 yp-4 w40").OnEvent("change",GuiEvents) 265 | oGui.Add("Button","vDownload xm+253 yp","Download").OnEvent("click",GuiEvents) 266 | oGui.Add("Button","vOpenFolder x+0 yp","Base Folder").OnEvent("click",GuiEvents) 267 | oGui.Add("Button","vOpenTemp x+0 yp","Temp Folder").OnEvent("click",GuiEvents) 268 | ctl := oGui.Add("ListView","vDLList xm y+10 w460 h181 -Multi",["File","Date"]) 269 | ctl.SetFont("s8","Consolas") 270 | 271 | tabs.UseTab("Basics") 272 | oGui.Add("Text","xm y+10","Custom Base Folder: (optional)") 273 | oGui.Add("Edit","y+0 r1 w410 vBaseFolder ReadOnly") 274 | oGui.Add("Button","x+0 vPickBaseFolder","...").OnEvent("Click",GuiEvents) 275 | oGui.Add("Button","x+0 vClearBaseFolder","X").OnEvent("Click",GuiEvents) 276 | 277 | oGui.Add("Text","xm y+4 Section","Install For:") 278 | oGui.Add("DropDownList","vInstallProfile y+0 r2 w100",["Current User","All Users"]).OnEvent("Change",GuiEvents) 279 | 280 | oGui.Add("Checkbox","vDisableTooltips xm y+10","Disable Tooltips").OnEvent("Click",GuiEvents) 281 | 282 | oGui.Add("Checkbox","vAutoUpdateCheck x+30 yp","Automatically check for updates").OnEvent("Click",GuiEvents) 283 | oGui.Add("Button","vCheckUpdateNow x+44 yp-4","Check Updates Now").OnEvent("Click",GuiEvents) 284 | 285 | oGui.Add("Text","xm y+4","Text Editor:") 286 | oGui.Add("Edit","xm y+0 w410 vTextEditorPath ReadOnly") 287 | oGui.Add("Button","x+0 vPickTextEditor","...").OnEvent("Click",GuiEvents) 288 | oGui.Add("Button","x+0 vDefaultTextEditor","X").OnEvent("Click",GuiEvents) 289 | 290 | oGui.Add("Button","vEditAhk1Template xm y+10 w230","Edit AHK v1 Template").OnEvent("Click",GuiEvents) 291 | oGui.Add("Button","vEditAhk2Template x+0 w230","Edit AHK v2 Template").OnEvent("Click",GuiEvents) 292 | 293 | tabs.UseTab("Options") 294 | 295 | oGui.Add("GroupBox","xm w456 h40 y+4","Context Menu - Only applies when Full Portable Mode is disabled") 296 | oGui.Add("Checkbox","vShowEditScript xp+10 yp+20","Show " Chr(34) "Edit Script" Chr(34)).OnEvent("Click",GuiEvents) 297 | oGui.Add("Checkbox","vShowCompileScript x+30","Show " Chr(34) "Compile Script" Chr(34)).OnEvent("Click",GuiEvents) 298 | oGui.Add("Checkbox","vShowRunScript x+30","Show " Chr(34) "Run as Admin" Chr(34)).OnEvent("Click",GuiEvents) 299 | 300 | oGui.Add("Checkbox","vPortableMode y+15 xm+10","Fully Portable Mode").OnEvent("Click",GuiEvents) 301 | oGui.Add("Checkbox","vHideTrayIcon x+25","Hide Tray Icon").OnEvent("Click",GuiEvents) 302 | oGui.Add("Checkbox","vCloseToTray x+67","Close to Tray").OnEvent("Click",GuiEvents) 303 | oGui.Add("Checkbox","vMinimizeOnStart y+10 xm+10","Minimize on Startup").OnEvent("Click",GuiEvents) 304 | 305 | oGui.Add("Text","x+26","Icon:") 306 | oGui.Add("DropDownList","vPickIcon w70 x+4 yp-3",["Default","Blue","Green","Orange","Pink","Red"]).OnEvent("Change",GuiEvents) 307 | 308 | oGui.Add("Checkbox","vSystemStartup x+61 yp+3","Run on system startup").OnEvent("Click",GuiEvents) 309 | 310 | oGui.Add("Checkbox","vAddToPath xm+10 y+10","Add to PATH on Install").OnEvent("Click",GuiEvents) 311 | oGui.Add("Checkbox","vCopyExe x+10",'Copy Installed EXE to "AutoHotkey.exe" on Install').OnEvent("Click",GuiEvents) 312 | oGui.Add("Checkbox","vRegisterAHKexe xm+10 y+10" 313 | ,'Register "AutoHotkey.exe" with .ahk files instead of Launcher on Install').OnEvent("Click",GuiEvents) 314 | 315 | oGui.Add("Text","xm+10 y+10","UPX:") 316 | oGui.Add("DropDownList","x+4 yp-3 w55 vUPX",["win32","win64"]).OnEvent("change",GuiEvents) 317 | 318 | oGui.Add("GroupBox","vHotkeys1 xm+10 y+10 w456 h60 y+4 Hidden","Hotkeys") 319 | txt := "MButton:`t`tRuns SELECTED scrtips in Explorer window/on desktop.`r`n" 320 | . "SHIFT + MButton:`tOpen script file in specified text editor.`r`n" 321 | . "CTRL + MButton:`tOpen the compiler with the selected script pre-filled." 322 | oGui.Add("Text","vHotkeys2 xp+10 yp+15 Hidden",txt) 323 | 324 | tabs.UseTab() 325 | 326 | oGui.Add("StatusBar","vStatusBar") 327 | app.gui := oGui 328 | 329 | x := Settings["posX"], y := Settings["posY"] 330 | PopulateSettings() 331 | ListExes() 332 | 333 | If (A_ScreenDPI != 96) 334 | app.h := 210 335 | 336 | oGui.Show("w" app.w " h" app.h " x" x " y" y (minimize?" Minimize":"")) 337 | oGui["StatusBar"].SetText("Administrator: " (A_IsAdmin?"YES":"NO")) 338 | 339 | If !Settings["AhkVersions"].Count 340 | CheckUpdate(1) 341 | Else If Settings["AutoUpdateCheck"] 342 | CheckUpdate(,true) 343 | } 344 | 345 | conEvent(g, row, rc, x, y) { 346 | Global oGui 347 | m := Menu() 348 | Click() 349 | m.Add("Copy version text",conMenu) 350 | m.Add() 351 | m.Add("Remove this version",conMenu) 352 | m.row := row 353 | m.show() 354 | } 355 | 356 | conMenu(iName, iPos, m) { 357 | Global oGui 358 | LV := oGui["ExeList"] 359 | If (iName = "Remove this version") { 360 | msg := "Are you sure you want to remove this version?`r`n`r`n" 361 | . "AutoHotkey v" LV.GetText(m.row, 2) 362 | If Msgbox(msg, "Remove Version",4) = "No" 363 | return 364 | 365 | dir := RegExReplace(oGui["ExeList"].GetText(m.row,4),"^Path: +") 366 | SplitPath dir,,&oDir 367 | 368 | Try DirDelete oDir, true 369 | Catch error as e { 370 | Msgbox "Access is denied, or an executable is still running." 371 | return 372 | } 373 | 374 | ticks := A_TickCount 375 | While (!(exist := DirExist(oDir)) && (A_TickCount - ticks) <= 500) 376 | Sleep 50 377 | 378 | If exist 379 | Msgbox "The delete command appears to have succeeded, but the folder still remains. Please delete it manually." 380 | 381 | ListExes() 382 | } Else If (iName = "Copy version text") { 383 | A_Clipboard := LV.GetText(m.row,2) 384 | } 385 | } 386 | 387 | PopulateSettings() { 388 | Global Settings, oGui 389 | 390 | oGui["BaseFolder"].Value := Settings["BaseFolder"] 391 | oGui["InstallProfile"].Text := Settings["InstallProfile"] 392 | oGui["AutoUpdateCheck"].value := Settings["AutoUpdateCheck"] 393 | oGui["TextEditorPath"].Value := Settings["TextEditorPath"] 394 | oGui["ShowEditScript"].Value := Settings["ShowEditScript"] 395 | oGui["ShowCompileScript"].Value := Settings["ShowCompileScript"] 396 | oGui["ShowRunScript"].Value := Settings["ShowRunScript"] 397 | oGui["DisableTooltips"].Value := Settings["DisableTooltips"] 398 | oGui["PortableMode"].Value := Settings["PortableMode"] 399 | oGui["AddToPath"].Value := Settings["AddToPath"] 400 | oGui["CopyExe"].Value := Settings["CopyExe"] 401 | oGui["RegisterAHKexe"].Value := Settings["RegisterAHKexe"] 402 | 403 | If (Settings["PortableMode"]) { 404 | oGui["ActivateExe"].Text := "Select" 405 | oGui["Uninstall"].Enabled := false 406 | oGui["Hotkeys1"].Visible := true 407 | oGui["Hotkeys2"].Visible := true 408 | } Else { 409 | oGui["ActivateExe"].Text := "Install" 410 | oGui["Uninstall"].Enabled := true 411 | oGui["Hotkeys1"].Visible := false 412 | oGui["Hotkeys2"].Visible := false 413 | } 414 | 415 | oGui["HideTrayIcon"].Value := Settings["HideTrayIcon"] 416 | oGui["CloseToTray"].Value := Settings["CloseToTray"] 417 | oGui["MinimizeOnStart"].Value := Settings["MinimizeOnStart"] 418 | oGui["PickIcon"].Text := Settings["PickIcon"] 419 | oGui["SystemStartup"].Value := Settings["SystemStartup"] 420 | oGui["UPX"].Text := Settings["UPX"] 421 | 422 | oGui["DLVersion"].Add(app.AhkDlVer) 423 | oGui["DLVersion"].Text := Settings["DLVersion"] 424 | 425 | PopulateDLList() 426 | SetActiveVersionGui() 427 | } 428 | 429 | GuiEvents(oCtl,Info) { 430 | Global Settings, oGui 431 | If (oCtl.Name = "ToggleSettings") { 432 | oGui["ExeList"].Focus() 433 | (app.toggle) ? oGui.Show("w" app.w " h" app.h) : oGui.Show("w480 h" ((A_ScreenDPI = 96) ? 480 : 465)) 434 | app.toggle := !app.toggle 435 | } Else If (oCtl.Name = "Compiler") { 436 | If !Settings["ActiveVersionPath"] { 437 | Msgbox "Install/Select an AutoHotkey version first." 438 | return 439 | } 440 | 441 | f := GetAhkProps(Settings["ActiveVersionPath"]) 442 | Run f.installDir "\Compiler\Ahk2Exe.exe" 443 | 444 | } Else If (oCtl.Name = "ActivateExe" or oCtl.Name = "ExeList") { 445 | If !oGui["ExeList"].GetNext() { 446 | Msgbox "Select an AutoHotkey version from the main list first." 447 | return 448 | } 449 | ActivateEXE() 450 | SaveSettings() 451 | 452 | } Else If (oCtl.Name = "CheckUpdateNow") { 453 | CheckUpdate(1) 454 | 455 | } Else If (oCtl.Name = "AutoUpdateCheck") { 456 | Settings["AutoUpdateCheck"] := oCtl.Value 457 | 458 | } Else If (oCtl.Name = "ClearBaseFolder") { 459 | Settings["BaseFolder"] := "", oGui["BaseFolder"].Value := "" 460 | ListExes() 461 | 462 | } Else If (oCtl.Name = "PickBaseFolder") { 463 | BaseFolder := (Settings.Has("BaseFolder")) ? Settings["BaseFolder"] : A_ScriptDir 464 | BaseFolder := FileSelect("D1",BaseFolder,"Select the base AHK folder:") 465 | 466 | If (BaseFolder And DirExist(BaseFolder)) { 467 | oGui["BaseFolder"].Value := BaseFolder 468 | Settings["BaseFolder"] := BaseFolder 469 | ListExes() 470 | } Else If (!DirExist(BaseFolder) And BaseFolder != "") 471 | MsgBox "Chosen folder does not exist." 472 | 473 | } Else If (oCtl.Name = "InstallProfile") { 474 | Settings["InstallProfile"] := oCtl.Text 475 | If (oCtl.Text = "Current User") 476 | Settings["reg"] := "HKEY_CURRENT_USER" 477 | Else If (oCtl.Text = "All Users") 478 | Settings["reg"] := "HKEY_LOCAL_MACHINE" 479 | 480 | } Else If (oCtl.Name = "DefaultTextEditor") { 481 | oGui["TextEditorPath"].Value := "notepad.exe", Settings["TextEditorPath"] := "notepad.exe" 482 | 483 | } Else If (oCtl.Name = "PickTextEditor") { 484 | textPath := (Settings["TextEditorPath"] = "notepad.exe") ? A_WinDir "\notepad.exe" : Settings["TextEditorPath"] 485 | TextEditorPath := FileSelect("1",textPath,"Select desired text editor:","Executable (*.exe)") 486 | 487 | If (TextEditorPath) { 488 | oGui["TextEditorPath"].Value := TextEditorPath 489 | Settings["TextEditorPath"] := TextEditorPath 490 | } 491 | 492 | } Else If (oCtl.Name = "Help") { 493 | If !Settings["ActiveVersionPath"] { 494 | Msgbox "Install/Select an AutoHotkey version first." 495 | return 496 | } 497 | 498 | curExe := Settings["ActiveVersionPath"] 499 | If (FileExist(curExe)) { 500 | f := GetAhkProps(curExe) 501 | 502 | Loop Files f.installDir "\*.chm" 503 | { 504 | If (A_Index = 1) { 505 | helpFile := A_LoopFileFullPath 506 | Break 507 | } 508 | } 509 | 510 | If (helpFile) 511 | Run helpFile,, "Max" 512 | Else 513 | Msgbox "No help file found." 514 | } 515 | 516 | } Else If (oCtl.Name = "EditAhk1Template") { 517 | Run Chr(34) Settings["TextEditorPath"] Chr(34) " " Chr(34) "resources\TemplateV1.ahk" 518 | 519 | } Else If (oCtl.Name = "EditAhk2Template") { 520 | Run Chr(34) Settings["TextEditorPath"] Chr(34) " " Chr(34) "resources\TemplateV2.ahk" 521 | 522 | } Else If (oCtl.Name = "WindowSpy") { 523 | If (Settings.Has("ActiveVersionPath") And FileExist(Settings["ActiveVersionPath"])) { 524 | SplitPath Settings["ActiveVersionPath"], &exeFile, &exeDir 525 | winSpy := exeDir "\WindowSpy.ahk" 526 | If (FileExist(winSpy)) 527 | Run Chr(34) exeDir "\WindowSpy.ahk" Chr(34) 528 | Else 529 | Msgbox "WindowSpy.ahk not found." 530 | } 531 | Else Msgbox "Install/Select an AutoHotkey version first." 532 | 533 | } Else If (oCtl.Name = "Uninstall") { 534 | If (MsgBox("Remove AutoHotkey from registry?","Uninstall AutoHotkey",0x24) = "Yes") 535 | UninstallAhk() 536 | 537 | } Else If (oCtl.Name = "DisableTooltips") { 538 | Settings[oCtl.Name] := oCtl.Value 539 | 540 | } Else If (oCtl.Name = "DebugNow") { 541 | Settings["DebugNow"] := oCtl.Value 542 | 543 | } Else If (oCtl.Name = "ShowEditScript") { ; context menu 544 | Settings["ShowEditScript"] := oCtl.Value 545 | 546 | } Else If (oCtl.Name = "ShowCompileScript") { ; context menu 547 | Settings["ShowCompileScript"] := oCtl.Value 548 | 549 | } Else If (oCtl.Name = "ShowRunScript") { ; context menu 550 | Settings["ShowRunScript"] := oCtl.Value 551 | 552 | } Else If (oCtl.Name = "PortableMode") { 553 | Settings["PortableMode"] := oCtl.value 554 | If (oCtl.value) 555 | oCtl.gui["ActivateExe"].Text := "Select" 556 | , oGui["Uninstall"].Enabled := false 557 | , oGui["Hotkeys1"].Visible := true 558 | , oGui["Hotkeys2"].Visible := true 559 | Else 560 | oCtl.gui["ActivateExe"].Text := "Install" 561 | , oGui["Uninstall"].Enabled := true 562 | , oGui["Hotkeys1"].Visible := false 563 | , oGui["Hotkeys2"].Visible := false 564 | 565 | } Else If (oCtl.Name = "HideTrayIcon") { 566 | If (!oGui["HideTrayIcon"].Value And !oGui["CloseToTray"].value) 567 | oCtl.Value := 1 568 | 569 | Settings["HideTrayIcon"] := oCtl.value 570 | oGui["CloseToTray"].Value := 0 571 | Settings["CloseToTray"] := 0 572 | A_IconHidden := true 573 | 574 | } Else if (oCtl.Name = "CloseToTray") { 575 | If (!oGui["HideTrayIcon"].Value And !oGui["CloseToTray"].value) 576 | oCtl.Value := 1 577 | 578 | Settings["CloseToTray"] := oCtl.value 579 | oGui["HideTrayIcon"].Value := 0 580 | Settings["HideTrayIcon"] := 0 581 | A_IconHidden := false 582 | 583 | } Else if (oCtl.Name = "MinimizeOnStart") { 584 | Settings["MinimizeOnStart"] := oCtl.Value 585 | 586 | } Else If (oCtl.Name = "PickIcon") { 587 | Settings["PickIcon"] := oCtl.Text 588 | If oCtl.Text = "Default" { 589 | f := GetAhkProps(Settings["ActiveVersionPath"]) 590 | TraySetIcon(f.exePath,0) 591 | } Else TraySetIcon("resources\AHK_pi_" oCtl.Text ".ico") 592 | 593 | } Else If (oCtl.Name = "SystemStartup") { 594 | Settings["SystemStartup"] := oCtl.Value 595 | lnk := A_AppData "\Microsoft\Windows\Start Menu\Programs\Startup\AHK Portable Installer.lnk" 596 | exe := A_ScriptDir "\AHK Portable Installer.exe" 597 | 598 | If !FileExist(exe) { 599 | msgbox "AHK Portable Installer.exe does not exist.`r`n`r`n" 600 | . "You need to download the latest AHK v2 zip package, and copy one of the EXE files into the main script folder " 601 | . "then you need to rename that EXE to:`r`n`r`n" 602 | . "AHK Portable Installer.exe" 603 | return 604 | } 605 | 606 | If oCtl.value { 607 | If Settings["PickIcon"] = "Default" { 608 | f := GetAhkProps(Settings["ActiveVersionPath"]) 609 | icon := f.exePath 610 | } Else icon := A_ScriptDir "\resources\AHK_pi_" Settings["PickIcon"] ".ico" 611 | 612 | FileCreateShortcut exe, lnk,,,,icon 613 | } Else If FileExist(lnk) 614 | FileDelete lnk 615 | 616 | } Else If (oCtl.Name = "DLVersion") { 617 | Settings["DLVersion"] := oCtl.Text 618 | PopulateDLList() 619 | 620 | } Else If (oCtl.Name = "Download") { 621 | DLFile() 622 | 623 | } Else If (oCtl.Name = "OpenFolder") { 624 | dest := (Settings["BaseFolder"] ? Settings["BaseFolder"] : A_ScriptDir "\versions") 625 | Run "explorer.exe " Chr(34) dest Chr(34) 626 | 627 | } Else If (oCtl.Name = "OpenTemp") { 628 | Run "explorer.exe " Chr(34) A_ScriptDir "\temp" Chr(34) 629 | 630 | } Else If (oCtl.Name = "VersionDisp") { 631 | oCtl.GetPos(&x,&y,,&h) 632 | verGui(x,y+h) 633 | 634 | } Else If (oCtl.Name = "AddToPath") || (oCtl.Name = "CopyExe") || (oCtl.Name = "RegisterAHKexe") { 635 | Settings[oCtl.Name] := oCtl.Value 636 | 637 | If (oCtl.Name = "CopyExe" && !oCtl.Value) { 638 | oCtl.gui["RegisterAHKexe"].Value := false 639 | Settings["RegisterAHKexe"] := false 640 | } Else If (oCtl.Name = "RegisterAHKexe" && oCtl.Value) { 641 | oCtl.gui["CopyExe"].Value := true 642 | Settings["CopyExe"] := true 643 | } 644 | } Else if (oCtl.name = "UPX") { 645 | Settings["UPX"] := oCtl.text 646 | Loop Files, A_ScriptDir "\temp\upx*.zip" 647 | FileDelete(A_LoopFileFullPath) 648 | app.latest_list["UPX"].filter := oCtl.text 649 | app.get_list["UPX"].filter := oCtl.text 650 | CheckUpdate(1) 651 | } 652 | } 653 | 654 | verGui(x,y) { 655 | m := menu() 656 | For name, obj in Settings["AhkVersions"] 657 | m.Add(name ": " obj["latest"] (app.verUpdate.Has(name) ? " *" : ""),(*) => "") 658 | m.Show(x,y) 659 | } 660 | 661 | PopulateDLList() { 662 | Global Settings 663 | LV := app.gui["DLList"] 664 | LV.Delete() 665 | name := "AutoHotkey v" app.gui["DLVersion"].Text 666 | LV.Opt("-Redraw") 667 | 668 | If (!name || !Settings["AhkVersions"].Has(name)) 669 | return 670 | 671 | For _file, obj in Settings["AhkVersions"][name]["list"] 672 | If RegExMatch(_file,"\.zip$") 673 | LV.Add(,_file,obj["date"]) 674 | 675 | LV.ModifyCol(1,300) 676 | LV.ModifyCol(2,120) 677 | LV.MOdifyCol(2,"SortDesc") 678 | LV.Opt("+Redraw") 679 | } 680 | 681 | DLFile() { ; download file if not already cached - then check hash if available 682 | Global Settings 683 | LV := app.gui["DLList"], name := "AutoHotkey v" app.gui["DLVersion"].Text, file_list := [] 684 | If !(row := LV.GetNext()) { 685 | MsgBox "Select a download first." 686 | return 687 | } 688 | 689 | src := app.get_list[name].url "/" 690 | dest := (Settings["BaseFolder"] ? Settings["BaseFolder"] : A_ScriptDir "\versions") "\" 691 | destTemp := A_ScriptDir "\temp\" 692 | 693 | zipFile := LV.GetText(row) 694 | src_url := Settings["AhkVersions"][name]["list"][zipFile]["url"] 695 | SplitPath zipFile,,,,&fileTitle 696 | 697 | If !FileExist(destTemp zipFile) { 698 | app.gui["StatusBar"].SetText("Downloading " zipFile "...") 699 | Try Download src_url, destTemp zipFile 700 | Catch { 701 | Msgbox "Host could not be reached. Check internet connection." 702 | return 703 | } 704 | } 705 | 706 | If (hash_file := check_hash()) { 707 | SplitPath hash_file,,,&hType 708 | 709 | src_url := Settings["AhkVersions"][name]["list"][hash_file]["url"] 710 | If !FileExist(destTemp hash_file) 711 | Download src hash_file, destTemp hash_file 712 | 713 | While !FileExist(destTemp hash_file) 714 | Sleep 100 715 | 716 | h1 := hash(destTemp zipFile,hType) 717 | h2 := FileRead(destTemp hash_file) 718 | 719 | If (h1 != h2) { 720 | Msgbox "File hash does not math!`r`n`r`nThe corrupt file will be deleted.`r`n`r`nTry redownloading the file." 721 | FileDelete destTemp zipFile 722 | FileDelete destTemp hash_file 723 | return 724 | } 725 | } 726 | 727 | dest := (Settings["BaseFolder"] ? Settings["BaseFolder"] : A_ScriptDir "\versions") "\" fileTitle 728 | If !DirExist(dest) 729 | DirCreate dest 730 | Else { 731 | Msgbox "Destination directory already exists. Manually delete this folder and try again." 732 | return 733 | } 734 | 735 | app.gui["StatusBar"].SetText("Decompressing " zipFile "...") 736 | objShell := ComObject("Shell.Application") 737 | zipFile := objShell.NameSpace(A_ScriptDir "\temp\" zipFile) 738 | objShell.NameSpace(dest).CopyHere(zipFile.Items()), objShell := "" 739 | 740 | check_extras(dest) 741 | 742 | ListExes() 743 | app.gui["StatusBar"].SetText("") 744 | 745 | check_hash() { 746 | For i, hType in ["md2", "md4", "md5", "sha1", "sha256", "sha384", "sha512"] { 747 | If Settings["AhkVersions"][name]["list"].Has(zipFile "." hType) 748 | return zipFile "." hType 749 | Else If Settings["AhkVersions"][name]["list"].Has(zipFile "." StrUpper(hType)) 750 | return zipFile "." StrUpper(hType) 751 | } 752 | } 753 | } 754 | 755 | check_extras(dest) { 756 | objShell := ComObject("Shell.Application") 757 | 758 | ahk2exe := check_github("Ahk2Exe") 759 | upx := check_github("UPX") 760 | mpress := check_mpress() 761 | 762 | If !DirExist(dest "\Compiler") { ; copy / extract Ahk2Exe if "Compiler" folder does not exist 763 | DirCreate dest "\Compiler" 764 | app.gui["StatusBar"].SetText("Decompressing " ahk2exe.update_file "...") 765 | zipFile := objShell.NameSpace(A_ScriptDir "\temp\" ahk2exe.update_file) 766 | objShell.NameSpace(dest "\Compiler").CopyHere(zipFile.Items()) 767 | } 768 | 769 | If !FileExist(dest "\Compiler\upx.exe") { ; copy / extract upx.exe 770 | SplitPath upx.update_file,,,,&title 771 | objShell.NameSpace(dest "\Compiler").CopyHere(A_ScriptDir "\temp\" upx.update_file "\" title "\upx.exe") 772 | } 773 | 774 | If !FileExist(dest "\Compiler\mpress.exe") ; copy / extract mpress.exe 775 | objShell.NameSpace(dest "\Compiler").CopyHere(A_ScriptDir "\temp\" mpress.update_file "\mpress.exe") 776 | 777 | objShell := "" 778 | } 779 | 780 | check_mpress() { 781 | if !FileExist(A_ScriptDir "\temp\mpress.219.zip") { 782 | Try Download app.get_list["MPRESS"].url, A_ScriptDir "\temp\mpress.219.zip" 783 | Catch 784 | Msgbox "Checking MPRESS...`n`nHost could not be reached. Check internet connection." 785 | } 786 | return obj := {file:"mpress.219.zip" 787 | , ver:"2.19" 788 | , update_file:"mpress.219.zip" 789 | , update_url:"" 790 | , update_ver:"" 791 | , update_path:"" 792 | , path:A_ScriptDir "\temp\mpress.219.zip"} 793 | } 794 | 795 | check_github(name:="") { 796 | Global Settings 797 | result := "", path:="", update := Settings["AhkVersions"][name] 798 | github := file_check() 799 | 800 | If !(github.file) || (update["latest"] != github.ver) { 801 | If FileExist(github.path) 802 | FileDelete github.path 803 | 804 | 805 | 806 | Try Download github.update_url, A_ScriptDir "\temp\" github.update_file 807 | Catch 808 | Msgbox "Checking " name "...`n`nHost could not be reached. Check internet connection." 809 | } 810 | 811 | return github 812 | 813 | file_check() { 814 | Loop Files A_ScriptDir "\temp\" name "*.zip" 815 | result := A_LoopFileName, path := A_LoopFileFullPath 816 | 817 | Switch name { 818 | Case "Ahk2Exe": ver := RegExReplace(result,"i)^Ahk2Exe(.+)\.zip","$1") 819 | Case "UPX" : ver := RegExReplace(result,"i)^upx\-([^\-]+)\-","$1") 820 | Default: ver := "" 821 | } 822 | 823 | ; msgbox result "`n`n" 824 | ; . ver "`n`n" 825 | ; . update["list"]["file"] "`n`n" 826 | ; . update["list"]["url"] "`n`n" 827 | ; . update["latest"] "`n`n" 828 | ; . A_ScriptDir "\temp\" update["list"]["file"] "`n`n" 829 | ; . path 830 | 831 | obj := {file:result 832 | , ver:ver 833 | , update_file:update["list"]["file"] 834 | , update_url:update["list"]["url"] 835 | , update_ver:update["latest"] 836 | , update_path:A_ScriptDir "\temp\" update["list"]["file"] 837 | , path:path} 838 | return obj 839 | } 840 | } 841 | 842 | ActivateEXE() { 843 | Global Settings, oGui 844 | 845 | If (Settings["reg"] = "HKEY_LOCAL_MACHING") && !A_IsAdmin { 846 | Msgbox "This program does not currently have Administrative privileges and cannot install for all users.`r`n`r`n" 847 | . "Change the install type to 'Current User' or re-run this program as Administrator." 848 | return 849 | } 850 | 851 | LV := oGui["ExeList"] ; ListView 852 | row := LV.GetNext(), exeFullPath := LV.GetText(row,4) 853 | 854 | If !Settings["PortableMode"] 855 | UninstallAhk() 856 | 857 | hive := Settings["reg"] 858 | launcher := (Settings["RegisterAHKexe"]) ? "AutoHotkey.exe" : "AHK_Portable_Installer.exe" 859 | 860 | ; props: product, version, installDir, type, bitness, exeFile, exePath, exeDir, variant 861 | f := GetAhkProps(exeFullPath) 862 | 863 | Settings["ActiveVersionDisp"] := Trim(f.product " " f.type " " f.bitness "-bit " f.variant) " " f.version 864 | Settings["ActiveVersionPath"] := exeFullPath 865 | 866 | oGui["ActiveVersionDisp"].Text := "Installed: " ; clear active version 867 | mpress := (FileExist(f.installDir "\Compiler\mpress.exe")) ? 1 : 0 868 | 869 | If Settings["PortableMode"] { 870 | SetActiveVersionGui() 871 | return 872 | } 873 | 874 | ; .ahk extension and template settings 875 | If reg.add(hive "\SOFTWARE\Classes\.ahk","","AutoHotkeyScript") { 876 | MsgBox reg.reason "`r`n`r`nTry running this script as Administrator." 877 | return 878 | } 879 | If reg.add(hive "\SOFTWARE\Classes\.ahk\ShellNew","ItemName","AutoHotkey Script") 880 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 881 | If reg.add(hive "\SOFTWARE\Classes\.ahk\ShellNew","NullFile","") 882 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 883 | 884 | ; update template according to majVersion 885 | If (hive = "HKEY_LOCAL_MACHINE") { 886 | If reg.add(hive "\SOFTWARE\Classes\.ahk\ShellNew","FileName","Template.ahk") 887 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 888 | 889 | templateText := FileRead("resources\" "TemplateV" f.majVersion ".ahk") 890 | If !FileExist(A_WinDir "\ShellNew") 891 | DirCreate A_WinDir "\ShellNew" 892 | Try FileDelete A_WinDir "\ShellNew\Template.ahk" 893 | FileAppend templateText, A_WinDir "\ShellNew\Template.ahk" 894 | } 895 | 896 | ; reg.delete(hive "\Software\AutoHotkey") 897 | Sleep 350 ; make it easier to see something happenend when re-installing over same version 898 | 899 | ; define ProgID 900 | root := hive "\SOFTWARE\Classes\AutoHotkeyScript\Shell" 901 | If reg.add(hive "\SOFTWARE\Classes\AutoHotkeyScript","","AutoHotkey Script") ; ProgID title 902 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 903 | If reg.add(hive "\SOFTWARE\Classes\AutoHotkeyScript\DefaultIcon","",'"' exeFullPath '",1') 904 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 905 | If reg.add(root,"","Open") 906 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 907 | 908 | ; Compiler Context Menu (Ahk2Exe) 909 | If Settings["ShowCompileScript"] { 910 | If reg.add(root "\Compile","","Compile Script") ; Compile context menu entry 911 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 912 | 913 | If (Settings["RegisterAHKexe"]) 914 | compiler := '"' f.exeDir '\Compiler\Ahk2Exe.exe" /in "%1" /gui' 915 | Else 916 | compiler := '"' A_ScriptDir '\AHK_Portable_Installer.exe" "' A_ScriptFullPath '" Compile "%1"' 917 | 918 | If reg.add(root "\Compile\Command","",compiler) 919 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 920 | } 921 | 922 | ; Edit Script 923 | If Settings["ShowEditScript"] { 924 | If reg.add(root "\Edit","","Edit Script") ; Edit context menu entry 925 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 926 | If reg.add(root "\Edit\Command","",'"' Settings["TextEditorPath"] '" "%1"') ; Edit command 927 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 928 | } 929 | 930 | ; Run Script 931 | If reg.add(root "\Open","","Run Script") 932 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 933 | 934 | If (!Settings["RegisterAHKexe"]) 935 | launcher := '"' A_ScriptDir '\AHK_Portable_Installer.exe" "' A_ScriptFullPath '" Launch "%1" %*' 936 | Else 937 | launcher := '"' A_ScriptDir '\AutoHotkey.exe" "%1" %*' 938 | 939 | If reg.add(root "\Open\Command","",launcher) ; Open verb/command 940 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 941 | 942 | ; Run Script as Admin 943 | If Settings["ShowRunScript"] { 944 | If reg.add(root "\RunAs","","Run Script as Admin") 945 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 946 | 947 | If (!Settings["RegisterAHKexe"]) 948 | launcher := '"' A_ScriptDir '\AHK_Portable_Installer.exe" "' A_ScriptFullPath '" LaunchAdmin "%1" %*' 949 | Else 950 | launcher := '"' A_ScriptDir '\AutoHotkey.exe" "%1" %*' 951 | 952 | If reg.add(root "\RunAs\Command","",launcher) ; RunAs verb/command 953 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 954 | } 955 | 956 | ; Ahk2Exe entries 957 | If reg.add("HKEY_CURRENT_USER\Software\AutoHotkey\Ahk2Exe","LastBinFile",f.type " " f.bitness "-bit.bin") ; auto set .bin file 958 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 959 | If reg.add("HKEY_CURRENT_USER\Software\AutoHotkey\Ahk2Exe","LastUseMPRESS",mpress) ; auto set mpress usage 960 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 961 | If reg.add("HKEY_CURRENT_USER\Software\AutoHotkey\Ahk2Exe","Ahk2ExePath",f.installDir "\Compiler\Ahk2Exe.exe") ; for easy reference... 962 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 963 | If reg.add("HKEY_CURRENT_USER\Software\AutoHotkey\Ahk2Exe","BitFilter",f.bitness "-bit") 964 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 965 | 966 | ; HKLM / Software / AutoHotkey install and version info 967 | If reg.add(hive "\Software\AutoHotkey","InstallDir",f.installDir) ; Default entries 968 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 969 | If reg.add(hive "\Software\AutoHotkey","StartMenuFolder","AutoHotkey") ; Default entries 970 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 971 | If reg.add(hive "\Software\AutoHotkey","Version",f.version) ; Default entries 972 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 973 | 974 | If reg.add(hive "\Software\AutoHotkey","MajorVersion",f.majVersion) ; just in case it's helpful 975 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 976 | If reg.add(hive "\Software\AutoHotkey","InstallExe",exeFullPath) 977 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 978 | If reg.add(hive "\Software\AutoHotkey","InstallBitness",f.bitness "-bit") 979 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 980 | If reg.add(hive "\Software\AutoHotkey","InstallProduct",Settings["ActiveVersionDisp"]) 981 | MsgBox reg.reason "`r`n`r`n" reg.lastKey 982 | 983 | AddToPath() ; always run this to have opportunity to remove the ahk-pi path 984 | 985 | If FileExist("AutoHotkey.exe") 986 | FileDelete A_ScriptDir "\AutoHotkey.exe" 987 | 988 | If Settings["CopyExe"] { 989 | _file := FileRead(Settings["ActiveVersionPath"],"RAW") 990 | FileAppend _file, "AutoHotkey.exe", "RAW" 991 | } 992 | 993 | DllCall("shell32\SHChangeNotify", "uint", 0x08000000, "uint", 0, "int", 0, "int", 0) ; thanks lexikos! 994 | 995 | SetActiveVersionGui() 996 | } 997 | 998 | AddToPath() { 999 | Global Settings 1000 | 1001 | hive := Settings["reg"] 1002 | 1003 | key := hive . ((hive="HKEY_CURRENT_USER") ? "\Environment" : "\SYSTEM\CurrentControlSet\Control\Session Manager\Environment") 1004 | list := StrSplit(orig_list := RegRead(key,"Path"),";") 1005 | 1006 | list.Push(A_ScriptDir) ; testing final result 1007 | 1008 | new_list := [] 1009 | For i, _path in list 1010 | If (_path && !RegExMatch(_path,"i)^\Q" A_ScriptDir "\E")) ; clear old ahk PATH contribution 1011 | new_list.Push(_path) 1012 | 1013 | If (Settings["AddToPath"] && (hive = "HKEY_CURRENT_USER") || (hive = "HKEY_LOCAL_MACHINE" && A_IsAdmin)) 1014 | new_list.Push(A_ScriptDir) ; only add path under proper conditions (previous line) 1015 | 1016 | str := "" 1017 | For i, _path in new_list 1018 | str .= _path ";" 1019 | 1020 | If (str != orig_list) ; Only modify the path if there is a change. 1021 | RegWrite str, "REG_EXPAND_SZ", key, "Path" ; This can be a removal or adding of ahk-pi path. 1022 | } 1023 | 1024 | UninstallAhk() { 1025 | Global Settings, oGui 1026 | 1027 | userchoice := true, result := "yes" 1028 | Try RegRead("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ahk\UserChoice","Hash") 1029 | Catch 1030 | userchoice := false 1031 | 1032 | If userchoice { 1033 | msg := "UserChoice for the .ahk extension has been activated. This will interfere with the normal functionality of AHK PI.`n`n" 1034 | . "This usually happens when the user uses 'Open With' and checks the checkbox to 'always use' the selected app to open .ahk files." 1035 | . "To remove this key, simply click 'Yes' to continue." 1036 | 1037 | If msgbox(msg,"User Attention Required",4) != "yes" 1038 | userchoice := false 1039 | } 1040 | 1041 | If userchoice 1042 | reg.delete("HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ahk\UserChoice") 1043 | 1044 | k1 := reg.delete(k1k := "HKLM\SOFTWARE\AutoHotkey") 1045 | k2 := reg.delete(k2k := "HKLM\SOFTWARE\Classes\.ahk") 1046 | k3 := reg.delete(k3k := "HKLM\SOFTWARE\Classes\AutoHotkeyScript") 1047 | 1048 | k4 := reg.delete(k4k := "HKCU\Software\AutoHotkey") 1049 | k5 := reg.delete(k5k := "HKCU\Software\Classes\AutoHotkey") 1050 | k6 := reg.delete(k6k := "HKCU\SOFTWARE\Classes\.ahk") 1051 | k7 := reg.delete(k7k := "HKCU\Software\Classes\AutoHotkeyScript") 1052 | 1053 | If (A_Is64BitOs) { 1054 | reg.view := 32 1055 | k8 := reg.delete(k8k := "HKLM\SOFTWARE\AutoHotkey") 1056 | k9 := reg.delete(k9k := "HKLM\SOFTWARE\Classes\.ahk") 1057 | k10 := reg.delete(k10k := "HKLM\SOFTWARE\Classes\AutoHotkeyScript") 1058 | reg.view := 64 1059 | } 1060 | 1061 | Try FileDelete A_WinDir "\ShellNew\Template.ahk" 1062 | 1063 | Settings["ActiveVersionPath"] := "" 1064 | Settings["ActiveVersionDisp"] := "" 1065 | SetActiveVersionGui() 1066 | 1067 | DllCall("shell32\SHChangeNotify", "uint", 0x08000000, "uint", 0, "int", 0, "int", 0) ; thanks lexikos! 1068 | } 1069 | 1070 | gui_Close(o) { 1071 | Global Settings 1072 | 1073 | app.gui.GetPos(&x,&y,&w,&h), dims := {x:x, y:y, w:w, h:h} 1074 | Settings["posX"] := dims.x, Settings["posY"] := dims.y 1075 | 1076 | If Settings["CloseToTray"] { 1077 | app.gui.Destroy() 1078 | return 1079 | } 1080 | 1081 | ExitApp 1082 | } 1083 | 1084 | on_exit(*) { 1085 | Global Settings 1086 | If (!app.ReadOnly) ; don't re-save every time script is launched by the registry 1087 | SaveSettings() 1088 | } 1089 | 1090 | SaveSettings() { 1091 | Global Settings 1092 | Try FileDelete "Settings.json" 1093 | SettingsJSON := Jxon_Dump(Settings,4) 1094 | FileAppend SettingsJSON, "Settings.json" 1095 | } 1096 | 1097 | SetActiveVersionGui() { 1098 | Global Settings, oGui 1099 | InstProd := "", ver := "", hive := Settings["reg"] 1100 | 1101 | If Settings["PortableMode"] { 1102 | ActiveVersion := (Settings.Has("ActiveVersionDisp")) ? Settings["ActiveVersionDisp"] : "" 1103 | oGui["ActiveVersionDisp"].Text := "Base Version: " ActiveVersion 1104 | } Else { 1105 | Try InstProd := reg.read(hive "\SOFTWARE\AutoHotkey","InstallProduct") 1106 | Try ver := reg.read(hive "\SOFTWARE\AutoHotkey","Version") 1107 | 1108 | regVer := (InstProd && ver) ? (InstProd) : "" 1109 | ActiveVersion := (Settings.Has("ActiveVersionDisp")) ? Settings["ActiveVersionDisp"] : "" 1110 | 1111 | oCtl := oGui["ActiveVersionDisp"] 1112 | If (regVer = "") 1113 | oCtl.Text := "AutoHotkey not installed!" 1114 | Else If (regVer != ActiveVersion or ActiveVersion = "") 1115 | oCtl.Text := "AutoHotkey version mismatch! Please reinstall!" ; this usually happens during a fresh install of Windows 1116 | Else 1117 | oCtl.Text := "Base Version: " ActiveVersion 1118 | oCtl := "" 1119 | } 1120 | } 1121 | 1122 | DisplayPathGui(oCtl,curRow) { 1123 | Global Settings, oGui 1124 | curPath := oCtl.GetText(curRow,4) 1125 | oGui["CurrentPath"].Text := "Path: " curPath 1126 | } 1127 | 1128 | ListClick(oCtl,Info) { 1129 | DisplayPathGui(oCtl,Info) 1130 | } 1131 | 1132 | ListExes() { 1133 | Global Settings, oGui 1134 | props := ["Name","Product version","File description"] 1135 | LV := oGui["ExeList"] ; ListView 1136 | LV.Opt("-Redraw"), LV.Delete() 1137 | 1138 | BaseFolder := (Settings["BaseFolder"] = "") ? A_ScriptDir "\versions" : Settings["BaseFolder"] 1139 | 1140 | list := Map() 1141 | 1142 | Loop Files BaseFolder "\AutoHotkey*.exe", "R" 1143 | { 1144 | _f := GetAhkProps(A_LoopFileFullPath) 1145 | 1146 | If ((A_LoopFileName = "AutoHotkey.exe") && (!_f.isAhkH)) 1147 | || RegExMatch(A_LoopFileFullPath,"i)\\(_*OLD_*|Compiler)\\") 1148 | continue 1149 | 1150 | If !list.Has(_f.product) 1151 | list[_f.product] := Map() 1152 | 1153 | If !list[_f.product].Has(_f.majVersion) 1154 | list[_f.product][_f.majVersion] := Map() 1155 | 1156 | list[_f.product][_f.majVersion][_f.date] := _f 1157 | } 1158 | 1159 | For prod, o1 in list { 1160 | For mv, o2 in o1 { 1161 | For date, f in o2 1162 | LV.Add("",f.product " " f.Type " " f.bitness "-bit",f.Version,f.exeFile,f.exePath) 1163 | } 1164 | } 1165 | 1166 | LV.ModifyCol(1,180), LV.ModifyCol(2,120), LV.ModifyCol(3,138), LV.ModifyCol(4,0) 1167 | LV.Opt("+Redraw") 1168 | 1169 | ActiveVersionPath := (Settings.Has("ActiveVersionPath")) ? Settings["ActiveVersionPath"] : "" 1170 | rows := LV.GetCount(), curRow := 0 1171 | 1172 | If (ActiveVersionPath and rows) { 1173 | Loop rows { 1174 | curPath := LV.GetText(A_Index,4) 1175 | If (ActiveVersionPath = curPath) { 1176 | curRow := A_Index 1177 | LV.Modify(curRow,"Vis Select") 1178 | break 1179 | } 1180 | } 1181 | } 1182 | 1183 | If (curRow) 1184 | DisplayPathGui(LV,curRow) 1185 | 1186 | LV.Focus() 1187 | } 1188 | 1189 | CheckUpdate(override:=0,confirm:=true) { 1190 | Global Settings 1191 | 1192 | If (!override) { 1193 | If (Settings["AutoUpdateCheck"] = 0) 1194 | return 1195 | Else If ( Settings["UpdateCheckDate"] = FormatTime(,"yyyy-MM-dd") ) 1196 | return 1197 | } 1198 | 1199 | app.verUpdate := Map() ; reset recorded updates 1200 | 1201 | For name, obj in app.latest_list 1202 | app.http_url_list.Push({url:obj.url, name:name, type:"version", format:obj.format, confirm:confirm, filter:obj.filter}) 1203 | ProcessURLs() 1204 | } 1205 | 1206 | ProcessURLs() { 1207 | If !app.http_url_list.Length 1208 | return 1209 | 1210 | obj := app.http_url_list[1] 1211 | Try { 1212 | app.http.Open("GET", obj.url, true) 1213 | app.http.onreadystatechange := CheckUpdate_callback.Bind(obj) 1214 | app.http.Send() 1215 | } Catch Error { 1216 | msg := "Could not reach " obj.name " page." 1217 | Msgbox msg, "Update Check Failed", 0x10 1218 | } 1219 | } 1220 | 1221 | CheckUpdate_callback(obj) { 1222 | Global Settings 1223 | 1224 | if (app.http.readyState != 4) ; not ready yet 1225 | return 1226 | 1227 | If (obj.type = "version") { 1228 | 1229 | if (app.http.Status != 200) { 1230 | 1231 | obj.confirm := false 1232 | obj.status := "failed: " app.http.Status 1233 | msg := "Could not reach " obj.name " page." 1234 | MsgBox(msg, "Update Check Failed", 0x10) 1235 | app.http_url_list := [] ; url clear list 1236 | app.verUpdate := Map() ; clear current updates list 1237 | 1238 | } else { 1239 | result := app.http.ResponseText 1240 | if obj.format = "json" 1241 | result := jxon_load(&result), result := RegExReplace(result["tag_name"],"Ahk2Exe","") 1242 | 1243 | If (!Settings["AhkVersions"].Has(obj.name) 1244 | || (Settings["AhkVersions"][obj.name]["latest"] != result) 1245 | || (Settings["AhkVersions"][obj.name]["list"].Count = 0)) 1246 | || (app.get_list[obj.name].filter && !InStr(app.get_list[obj.name].url,app.get_list[obj.name].filter)) { ; need to get download list for indicatd name 1247 | 1248 | app.http_url_list.Push({ url:app.get_list[obj.name].url ; add url to get download list 1249 | , name:obj.name 1250 | , type:"list" 1251 | , format:app.get_list[obj.name].format 1252 | , confirm:obj.confirm 1253 | , filter:app.get_list[obj.name].filter}) 1254 | 1255 | app.verUpdate[obj.name] := result 1256 | Settings["AhkVersions"][obj.name] := Map("latest",result,"list",Map()) 1257 | } 1258 | 1259 | Settings["UpdateCheckDate"] := FormatTime(,"yyyy-MM-dd") 1260 | app.http_url_list.RemoveAt(1) ; remove processed item 1261 | ProcessURLs() ; process next url 1262 | } 1263 | } Else if (obj.type = "list") { 1264 | 1265 | If (response:=app.http.ResponseText) && (obj.format = "html") 1266 | list := format_html(response) 1267 | else 1268 | list := format_json(response,obj.filter) 1269 | 1270 | Settings["AhkVersions"][obj.name]["list"] := list 1271 | 1272 | app.http_url_list.RemoveAt(1) ; remove processed item 1273 | ProcessURLs() ; process next url 1274 | 1275 | if !app.http_url_list.Length { ; when finished, display confirmation if enabled 1276 | if (obj.confirm) { 1277 | if (app.verUpdate.Count) { 1278 | If !InStr(title := app.gui.Title,"Update Available") 1279 | app.gui.Title := title " (Update Available!)" 1280 | } 1281 | } 1282 | 1283 | Settings["UpdateCheckDate"] := FormatTime(,"yyyy-MM-dd") 1284 | PopulateDLList() 1285 | } 1286 | } 1287 | 1288 | format_html(txt) { 1289 | list := Map(), ver := RegExReplace(obj.name,"AutoHotkey *v?","") 1290 | Loop Parse, txt, "`n", "`r" 1291 | { 1292 | If (r1 := RegExMatch(A_LoopField,'',&m) 1293 | && (r2 := RegExMatch(A_LoopField,'([^<]+)',&n))) { 1294 | If (m[1] = "/") || (m[1] = "/download/") || (m[1] = "version.txt") 1295 | || (m[1] = "_AHK-binaries.zip") || (m[1] = "zip%20versions/") || InStr(m[1],"Ahk2Exe") 1296 | Continue 1297 | If InStr(m[1],".zip") { 1298 | item := Map("date",Trim(n[1]," `t"),"url",obj.url "/" m[1]) 1299 | list[m[1]] := item 1300 | } 1301 | } 1302 | } 1303 | return list 1304 | } 1305 | 1306 | format_json(txt,filter:="") { 1307 | _map := jxon_load(&txt), idx := 1 1308 | 1309 | While _map["assets"].Has(idx) { 1310 | url := _map["assets"][idx]["browser_download_url"] 1311 | _file := (arr:=StrSplit(url,"/"))[arr.Length] 1312 | If InStr(url,filter) 1313 | return Map("date",_map["published_at"],"url" ,url,"file",_file) 1314 | idx++ 1315 | } 1316 | } 1317 | } 1318 | 1319 | LaunchScript(hk:="") { 1320 | Global Settings 1321 | obj := {exe:0} 1322 | 1323 | If (sel := Explorer_GetSelection()) { 1324 | a := StrSplit(sel,"`n","`r") 1325 | Loop a.Length { 1326 | SplitPath a[A_index],,,&ext 1327 | If (ext != "ahk") 1328 | Continue 1329 | 1330 | obj := proc_script(a[A_index]) 1331 | cmd := (obj.admin?"*RunAs ":"") obj.exe (obj.admin?" /restart ":" ") Chr(34) a[A_Index] Chr(34) 1332 | (obj.exe) ? Run(cmd) : "" 1333 | } 1334 | } 1335 | 1336 | return !!obj.exe 1337 | } 1338 | 1339 | LaunchCompiler(hk:="") { 1340 | If (sel := Explorer_GetSelection()) { 1341 | Loop Parse Explorer_GetSelection(), "`n", "`r" 1342 | { 1343 | SplitPath A_LoopField,,,&ext 1344 | If (ext != "ahk") 1345 | Continue 1346 | obj := proc_script(A_LoopField, true) 1347 | Run Chr(34) obj.exe Chr(34) " /in " Chr(34) A_LoopField Chr(34) " /gui" 1348 | } 1349 | } 1350 | } 1351 | 1352 | LaunchEditor(hk:="") { 1353 | Global Settings 1354 | 1355 | If (sel := Explorer_GetSelection()) { 1356 | Loop Parse , "`n", "`r" 1357 | { 1358 | SplitPath A_LoopField,,,&ext 1359 | If (ext != "ahk") 1360 | Continue 1361 | Run Chr(34) Settings["TextEditorPath"] Chr(34) " " Chr(34) A_LoopField Chr(34) 1362 | } 1363 | } 1364 | } 1365 | 1366 | dbg(_in) { 1367 | Loop Parse _in, "`n", "`r" 1368 | OutputDebug "AHK: " A_LoopField 1369 | } 1370 | 1371 | ; ==================================================================================== 1372 | ; MButton: Runs the selected script(s). 1373 | ; SHIFT + MButton: Opens selected script(s) in text editor. 1374 | ; CTRL + MButton: Launches Ahk2Exe with selected script(s) filled in. One instance per selection. 1375 | ; ==================================================================================== 1376 | #HotIf WinActive("ahk_exe explorer.exe") 1377 | MButton::LaunchScript(A_ThisHotkey) 1378 | +MButton::LaunchEditor(A_ThisHotkey) 1379 | ^MButton::LaunchCompiler(A_ThisHotkey) 1380 | 1381 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TheArkive 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AHK Portable Installer 2 | 3 | 4 | 5 | README Updated on 2023-01-19 6 | 7 | ## Latest Updates 8 | 9 | * fixed execution of #Requires directive to filter by major version, as is done by AHK 10 | * fixed execution of "match" option 11 | * updated readme 12 | 13 | Links: 14 | * [Ahk2Exe](https://github.com/AutoHotkey/Ahk2Exe) 15 | * [Window Spy](https://github.com/AutoHotkey/AutoHotkeyUX/blob/main/WindowSpy.ahk) 16 | 17 | Special thanks to the following users for their help and/or works that I was able to derive from, which were crucial to making this script what it is today: 18 | 19 | * boiler 20 | * teadrinker 21 | * Rapte_Of_Suzaku 22 | * lexikos 23 | * DaveT1 - for having a keen eye to find what I missed 24 | 25 | ## Intro 26 | 27 | [Posted on AutoHotkey.com Forums](https://www.autohotkey.com/boards/viewtopic.php?f=6&t=73056) 28 | 29 | This is a portable install manager and allows multiple versions of AutoHotkey to coexist simultaneously. This script is meant to work with the portable `.zip` archives of AutoHotkey, NOT the setup `.exe` versions. It is written in AHK v2. 30 | 31 | You can use this program in 1 of 2 ways: 32 | 1) Change/Switch the active AHK EXE version when desired. 33 | 2) Run multiple versions of AHK simultaneously without switching by using the `#Requires` directive in your scripts. This program will choose the EXE based on that criteria. 34 | 35 | This program can be run as an installer, or as a portable program. As an installer, registry entries are written to the system to implement the `.ahk` file type association. In Fully Portable mode no registry modifications are performed, and the program must remain running in the background. 36 | 37 | If you want to use AutoHotkey_H with this program, you can. Simply download your desired release(s) of AutoHotkey_H and extract the ZIP to a subfolder in the `base folder`. 38 | 39 | The default `base folder` is in `%A_ScriptDir%\versions`. 40 | 41 | ## Features 42 | * Download/remove AHK versions directly from the UI. 43 | * Download Ahk2Exe automatically (updates for pre-existing installs are still manual). 44 | * Fully Portable Mode. See the `Fully Portable Mode` section below. 45 | * Associate the `.ahk` extension with any version of AHK. (not in portable mode) 46 | * Selectively choose which context-menu items appear in the context menu. (not in portable mode) 47 | * Associate any text editor with "Edit Script" in context menu easily. (use SHIFT + MButton in portable mode) 48 | * Define as many versions of AHK as you want to run in parallel. 49 | * Checks for updates when prompted, or does so automatically if enabled. 50 | * Displays the latest versions of AHK v1 and v2 (with internet connection only). 51 | * Displays currently active version of AHK. 52 | * Download official AutoHotkey releases directly from the UI. 53 | * Easily invoke `WindowSpy.ahk`, `help file`, and the `Ahk2Exe` compiler for the selected `base version` from the UI. 54 | * Edit templates for new AutoHotkey.ahk files from GUI. (not in portable mode) 55 | * Optionally use this program like "AHK-EXE-Switcher" instead of a multi-version Launcher. 56 | 57 | ## Basic Setup 58 | 59 | Grab the latest copy of AHK v2 (currently recommended is v2.0.2), copy the desired version of `AutoHotkey32/64.exe` into the script dir and rename it to `AHK_Portable_Installer.exe`. Always run this EXE file to launch the script. 60 | 61 | Now you can download AutoHotkey through the UI. Just click `Settings`, pick a major version from the DropDownList, then select/downlad your desired version(s). To remove a version of AHK, right click on an entry in the main list, and select `Remove this version` from the context menu. 62 | 63 | The AutoHotkey `base folder` is located in the script directory by default. It can be moved to another location if desired. The structure of the `base folder` is described below. All downloads are cached. You can open the `base folder` and `temp folder` from the Download tab in Settings. 64 | 65 | Example: 66 | ``` 67 | C:\AutoHotkey\ <-- base folder -> place this anywhere you want, or leave it in the script folder 68 | \_OLD_\old_versions_here... <-- AHK folders here will not be parsed/listed 69 | \ahk version blah\... <-- AHK folders for each version 70 | \ahk_h another_version\... <-- AHK folders for each version 71 | \and_so_on\... <-- AHK folders for each version 72 | ``` 73 | 74 | Each subfolder should have it's own copy of a `help file`, `WindowSpy.ahk`, and `Compiler` folder with `Ahk2Exe` and all necessary components (like `.bin` files, `mpress.exe`, and/or `upx.exe` as needed). Note, that development version of AHK are likely to not include the `Ahk2Exe Compiler` and `WindowSpy.ahk`. See the links in the "Latest Updates" section at the top of this page if you need these components. 75 | 76 | Now you need to decide how you want to use this program: 77 | 1) Change the version as needed to run different scripts (like AHK-EXE-Switcher). 78 | 2) Use the `#Requires` directive in your scripts (recommended). You will need to make small changes to your scripts, but you will be able to run multiple versions of AHK simultaneously without switching. Read more below. 79 | 3) You also need to decide if you want to use this program as an installer (registers the `.ahk` extension on the system) or if you want to run in Fully Portable mode. 80 | 81 | ## Installer Mode 82 | 83 | This is the default mode. Simply leave `Fully Portable Mode` unchecked. When installing, the `Install For` option in the `Basics` tab determines whether registry modifications are made for the user, or for `All Users` (the system). You can optionally install for `All Users`, but you need to run this program as Admin. 84 | 85 | The `Base Folder`, as described above, is located in `%A_ScriptDir%\versions` by default. You can move this anywhere you wish, and then specify that folder in the `Base Folder` option. Leaving this setting blank will use the default folder location. 86 | 87 | When you click `Install` the `base version` is set and the script writes registry entries for the `.ahk` extension, and context menu entries (such as Right-Click > New, and "Run Script", "Compile Scrpt", etc). 88 | 89 | ## Fully Portable Mode 90 | 91 | Go to Settings > Options tab and check the `Fully Portable Mode` checkbox. In this mode no registry entries are written. You can use the hotkeys below to run and edit scripts. After you select your desired version click the `Select` button to change the selected base version. 92 | 93 | Hotkeys for Fully Portable mode: 94 | 95 | |Hotkey |Description | 96 | | -------------:| ---------------------------------------------------- | 97 | |MButton |Runs SELECTED script files. | 98 | |Shift + MButton|Open script file in specified text editor. | 99 | |Ctrl + MButton |Open the compiler with the selected script pre-filled.| 100 | 101 | If you want to use Fully Portable Mode, then you will also want to consider the following settings in the Options tab: 102 | 103 | * Hide Tray Icon (maybe uncheck this - check the rest as desired) 104 | * Close to Tray 105 | * Minimize on Startup 106 | * Run on System Startup 107 | 108 | ## Using the #Requires directive 109 | 110 | Use the `#Requires` directive in your scripts to have the Launcher dynamically select which AHK EXE to use for running the script. 111 | 112 | ``` 113 | SYNTAX: #Requires [product] [version] [; options] 114 | ``` 115 | 116 | Available options: 117 | 118 | * 32-bit = use only 32-bit AHK version 119 | * 64-bit = use only 64-but AHK version 120 | * admin = try to elevate and use *RunAs verb to run as Admin 121 | * match = match the version number exactly in #Requires instead of ensuring minimum version is available. 122 | 123 | ``` 124 | Note: "32-bit" and "64-bit" cannot be combined. Also when using the "match" option, the match must be exact. An entry like: 125 | 126 | #Requires AutoHotkey v1.1 ; match 127 | 128 | ... will not work, because version numbers are actually longer than that. Therefore no match will be found. 129 | ``` 130 | 131 | Example: 132 | ``` 133 | ---------------------------------------------------------------- 134 | * Uses a136, with system bitness. 135 | 136 | #Requires AutoHotkey 2.0-a136 137 | ---------------------------------------------------------------- 138 | * Uses the latest v2 alpha on the system and uses only 32-bit. 139 | 140 | #Requires AutoHotkey 2.0-a ; 32-bit 141 | ---------------------------------------------------------------- 142 | * Uses v2.0.1 or greater and only 64-bit. 143 | * If using "64-bit" on a 32-bit system an error is thrown. 144 | * "match" ensures only version 2.0.1 is used, otherwise an error is thrown if not found. 145 | 146 | #Requires AutoHotkey 2.0.1 ; 64-bit match 147 | ---------------------------------------------------------------- 148 | * Uses only v2 alpha a138, uses 64-bit and runs script as admin. 149 | 150 | #Requires AutoHotkey 2.0-a138 ; 64-bit admin 151 | ---------------------------------------------------------------- 152 | * For versions of AutoHotkey that don't have 153 | the #Requires directive, simply comment 154 | out the directive but format it the same. 155 | 156 | ; #Requires AutoHotkey 1.1.33 ; 32-bit 157 | ---------------------------------------------------------------- 158 | ``` 159 | 160 | The `[product]` and `[version]` are required parameters in the `#Requires` directive. How the script selects the EXE to use is the same a the `#Requires` directive. 161 | 162 | Notes: 163 | * If you don't specify an exact version, the **latest version** available in the versions folder that matches the `#Requires` directive string is used. 164 | * If you don't specify a `#Requires` directive then the selected/installed `base version` is used. 165 | * Usage of options will further modify how the `#Requires` directive operates. 166 | 167 | ## Compiling Scripts 168 | 169 | The `#Requires` directive is also used to determine which compiler to use. The logic is the same as the section above. If no `#Requires` directive is used, then the selected `base version` compiler is used. 170 | 171 | ## Setting up Ahk2Exe 172 | 173 | All versions of AutoHotkey v1 come with a compiler (Ahk2Exe), `.bin` files, and `mpress.exe`. These versions of `Ahk2Exe` also work for AutoHotkey v2. Just copy `Ahk2Exe.exe` and `mpress.exe` from the AHK v1 compiler folder to the AHK v2 compiler folder. DO NOT copy the `.bin` files from the AHK v1 folder. 174 | 175 | The latest versions of AutoHotkey_H v1 and v2, as of this document update, both have their own separate updated compilers which contain `Ahk2Exe.exe`. 176 | 177 | So if you were to run all versions of AutoHotkey in parallel (in theory) your compiler setup for each version of AutoHotkey in your base folder should follow these general guidelines: 178 | 179 | * All AutoHotkey v1 and v2 folders should have the same latest release of Ahk2Exe, but the `.bin` files will be different. 180 | * All AutoHotkey_H v1 folders should have the same version compiler. Just replace the older compiler with the newer one. 181 | * All AutoHotkey_H v2 folders should have the same version compiler. Just replace the older compiler with the newer one. 182 | 183 | This setup will allow you to properly compile any version of AutoHotkey you wish to setup on your system. 184 | 185 | ## Options Tab - Install Options 186 | 187 | The below options pertain to using this program as an Installer only. All other options not discussed either pertain to the Context Menu, or to "Fully Portable Mode". I gave the below options arbitrary numbers for easy reference in this document. 188 | 189 | #### Option #1: `Add to PATH on Install` 190 | 191 | This option adds the script directory to the PATH environment variable. This setting follows the `Install For` option on the `Basics` tab. This allows the Launcher (`AHK_Portable_Installer.exe`) to be used on the command line. (See "Command Line Usage" below.) 192 | 193 | #### Option #2: `Copy Installed EXE to "AutoHotkey.exe" on Install` 194 | 195 | If this option and Option #1 are enabled, then the installed AHK EXE will be copied to the script directory as `AutoHotkey.exe`. You can then use this on the command line according to the AutoHotkey docs. 196 | 197 | Checking this option alone, while copying the installed EXE to the script directory, will basically have no worth while effect on the system. Normally this option is used with Option #1, Option #3, or both. 198 | 199 | #### Option #3: `Register "AutoHotkey.exe" with .ahk files instead of Launcher on Install` 200 | 201 | If this option is enabled, then the usage of the `#Requires` directive as explained above will have no effect. Only the installed/selected version of AutoHotkey will be used to run `.ahk` scripts. This effectively turns this program into an "EXE switcher" similar to "AHK-EXE-Swapper". 202 | 203 | If you check this option, Option #2 will be automatically checked. If you uncheck Option #2, then this option will be automatically unchecked. 204 | 205 | ## Launcher Command Line Usage (AHK_Portable_Installer.exe) 206 | 207 | Usage: 208 | 209 | ``` 210 | AHK_Portable_Installer.exe [Behavior] "path\to\script.ahk" [params...] 211 | 212 | [Behavior] and [params...] are optional. 213 | 214 | Simple usage: 215 | AHK_Portable_Installer.exe "path\to\script.ahk" [params...] 216 | 217 | Specify Behavior: 218 | AHK_Portable_Installer.exe [Launch|LaunchAdmin|Compiler] "path\to\script.ahk" [params...] 219 | ``` 220 | 221 | If the first parameter is an `.ahk` script file, then the default behavior is `Launch`. If the behavior is specified as `Launch` and the `Admin` option is used on the `#Requires` directive within the script, then the script will still be launched as admin. 222 | 223 | Place parameters to be passed to the script after the `path\to\scrpt.ahk` parameter as usual. 224 | 225 | When launching scripts on the command line with `AHK_Portable_Installer.exe` , the script will be launched with an EXE according to the #Requires directive. If no `#Requires` directive is found, then the script will be launched by the installed/selectd EXE from the main UI. 226 | 227 | ## What AHK Portable Installer does NOT do... 228 | 229 | This is a PORTABLE installer, so this script: 230 | 231 | * WILL NOT write or remove registry entries that deal with modifying the App list of installed programs. 232 | * WILL NOT create a separate `.ahk2` extension or any other extension besides `.ahk`. 233 | 234 | ## Troubleshooting and avoiding problems 235 | 236 | If you need help post on the forums, or [visit the join #ahk on IRC or visit the AutoHotkey Discord](https://www.autohotkey.com/boards/viewtopic.php?f=76&t=59&p=406501&hilit=irc#p406501). I frequently go on IRC, and I'm usually always connected on Discord. Let me know what setup issues you are having and I'll see what I can do. 237 | 238 | Otherwise here are some basics to keep in mind: 239 | 240 | 1) It is NOT recommended to run this script along side a normal installation of AutoHotkey with the setup program, it is however theoretically possible. But this script will override the proper install with its own settings in the registry. This will render the Uninstall option of your legit install inoperable. You will need to reinstall your legit install in order to do a normal removal from the "Installed Programs" list in the Windows Control Panel / App Settings. 241 | 242 | 2) If you move your `base folder`, then you must "re-install" your chosen AutoHotkey `base version`. Simply click `Install/Select` again to update the setting. Keep in mind, if you don't specify a custom `base folder`, and you move the location of AHK Portable Installer, then you are also moving the `base folder` to the default location which is:\ 243 | \ 244 | `%A_ScriptDir%\versions` 245 | 246 | 3) Every version folder of AutoHotkey should have its own `help file`, `WindowSpy.ahk`, and `Compiler` folder with your desired compiler components. 247 | 248 | 4) You should generally always use the latest Ahk2Exe. Remember there are 3 different Ahk2Exe's: 249 | * AutoHotkey v1 and v2 use the same version. 250 | * AutoHotkey_H v1 has it's own compiler. The latest release will work for all verions, just replace the olders ones with the latest. 251 | * AutoHotkey_H v2 has it's own compiler. The latest release will work for all verions, just replace the olders ones with the latest. 252 | 253 | 5) In regards to `A_AhkPath`: 254 | 255 | For older compiled scripts, this is usually pulled from the registry. If you are managing multiple versions of AHK using this tool, remember that the `base version` selected from the main GUI is the only one written to the registry. 256 | 257 | 6) Running scripts in Fully Portable Mode: 258 | You must first `single click` with the `left mouse button` to select a script, and THEN you can `middle click` to run the script. Usually after running a script it is not uncommon for the selection to be undone, especially when scripts are on the desktop. So just remember, SELECT the script first (single left click) then `middle click` to run, every time. If the file is already selected you can just `middle click` to run. 259 | 260 | 7) Wrong PID (Process ID) when using Run/RunWait:\ 261 | This is something you will encounter if you use `Run(my_script.ahk,,,&pid)`. Since this program is the actual launcher for the script, this is what is happening: 262 | * User script invokes `Run(my_script.ahk,,,&pid)`. 263 | * Registry queries file type association. 264 | * Launcher is invoked. <-- this is PID saved in &pid 265 | * Launcher determines which version of AHK to use. 266 | * Launcher uses `Run()` to launch the script which has a different PID. 267 | 268 | So the PID you are getting is that of the Launcher, not the actual script. I'm afraid there is no way around this, but generally there are better ways of interacting with a program (or an AHK script) than using the PID. 269 | 270 | There is of course nothing stopping you from running the script with an actual AHK EXE yourself on the command line. So if you use: 271 | ``` 272 | Run("'X:\Path\to\AutoHotkey.exe' 'path\to\script.ahk'",,,&pid) 273 | ``` 274 | ... then you will get the PID of the specified script as expected. 275 | 276 | ## To-Do List 277 | 278 | * Allow options to compile one script into multiple versions with minimal clicks (maybe). 279 | 280 | ## Other remarks... 281 | 282 | This program will NOT circumvent User Account Control settings. If you leave UAC enabled this program will still work as long as you keep the `Install For` option in the `Basics` tab set to "Current User". 283 | 284 | If you want to install for "All Users" (which affects the entire system) then you must run this program as admin. You can do this by right-clicking on the Launcher EXE (`AHK_Portable_Installer.exe`) and selecting "Run as administrator". 285 | 286 | If you want to completely disable UAC on Win 7+ you need to disable Admin Approval Mode as well. Note that this will have the effect of launching ALL scripts (and all programs on your system for that matter) as admin. And all scripts will have UI access as well. At this time, UI Access is not possible with AHK Portable Installer when UAC/Admin Approval Mode is enabled. 287 | 288 | Below is the registry key modifications needed to disable UAC and Admin Approval Mode in one registry key, but first open the User Account Control window and pull the slider all the way down. Then merge the below registry setting. 289 | 290 | ``` 291 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System] 292 | "EnableLUA"=dword:00000000 293 | ``` 294 | 295 | If you don't have some of these registry keys/items you need to create them. 296 | 297 | You can also follow [this tutorial on TenForums](https://www.tenforums.com/tutorials/112488-enable-disable-user-account-control-uac-windows.html). 298 | 299 | 300 | 301 | ## *** WARNING ABOUT DISABLING UAC *** 302 | If you disable UAC, you will disable some of the built-in countermeasures in Windows that protect your system. 303 | 304 | Do not do this unless you are: 305 | 1) Able to administer your own system security (like a paid anti-virus subscription). 306 | 2) You are willing to accept the consequences. 307 | 308 | --- 309 | 310 | Any feedback would be appreciated. Hopefully this tool will help people, and just get better over time. 311 | -------------------------------------------------------------------------------- /images/ahk-pi-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheArkive/ahk-pi/a3146b8299de0567c96a295857670ed9a86f68f0/images/ahk-pi-main.png -------------------------------------------------------------------------------- /inc/TheArkive_reg2.ahk: -------------------------------------------------------------------------------- 1 | ; ====================================================================================== 2 | ; Reg class - wrapper for the REG command. 3 | ; ====================================================================================== 4 | ; This was made to attempt making a wider set of functionality for interacting with the 5 | ; registry feel more "native" to AutoHotkey. Also, less errors are thrown, for example 6 | ; when trying to read the (Default) value of a key, you will get "" if the data of the 7 | ; (Default) value is a zero-length string, or is unset. But you can still check if the 8 | ; value is actually unset. Read more below. 9 | ; 10 | ; Much of this class mirrors what AHK already does with registry related commands and 11 | ; functions, and of course adds to it. 12 | ; 13 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 14 | ; Methods 15 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 16 | ; reg.query(key, value := "", recurse := false) 17 | ; 18 | ; - Similar to the REG QUERY command. 19 | ; 20 | ; - If recurse = false, specify only a key to query all values, as well as a list 21 | ; of keys (and their default values) in the specified key. If you also specifify 22 | ; a value, then only that value's data is returned. 23 | ; 24 | ; - If recurse = true, specify only a key to query all values in all subkeys. If 25 | ; you also specify a value, then all instaces of that value in all subkeys is 26 | ; queried. 27 | ; 28 | ; - Output is a Map(), listing the key queried, and all subkeys. 29 | ; 30 | ; - If value = "", that queries the (Default) value of a key/subkey. 31 | ; 32 | ; Usage Examples: 33 | ; 34 | ; string := reg.query("HKCU\My_key", "my_value") 35 | ; 36 | ; -> Returns the data stored in "my_value" as a string in the same manner as RegRead(). 37 | ; 38 | ; assoc_array := reg.query("HKCU\My_key",,true) 39 | ; 40 | ; -> Returns a Map() of all values and sub-keys. 41 | ; 42 | ; assoc_array := reg.query("HKCU\My_key","My_value",true) 43 | ; 44 | ; -> Returns a Map() of all instances of "My_value" in the specified key and subkeys. 45 | ; 46 | ; + Getting data from an array after using reg.query() 47 | ; 48 | ; my_data := arr["HKLM\sub_key"]["value"]["data"] 49 | ; my_data_type := arr["HKLM\sub_key"]["value"]["data_type"] 50 | ; (boolean) check_unset := arr["HKLM\sub_key"]["value"]["unset"] ; only true when (Default) value is unset. 51 | ; 52 | ; - ["unset"] will indicate if the return value of "" is zero-length or actually unset. 53 | ; 54 | ; reg.add(key, value := "", data := "", rgType := "REG_SZ") 55 | ; 56 | ; - Specify only a key, to add a registry key with the (Default) value unset. 57 | ; 58 | ; - Specify a key and value, to write data to that value. 59 | ; 60 | ; - The default data type is REG_SZ. 61 | ; 62 | ; Usage Examples: 63 | ; 64 | ; boolean := reg.add("HKCU\Software\my_key") 65 | ; 66 | ; -> Attempts to add "my_key" subkey to the registry key "HKCU\Software". 67 | ; 68 | ; boolean := reg.add("HKCU\Software\my_key","my_value") 69 | ; 70 | ; -> Attempts to add "my_value" to the registry key "HKCU\Software\my_key" 71 | ; with no data (a zero-length string). 72 | ; 73 | ; boolean := reg.add("HKCU\Software\my_key","my_value","some_data") 74 | ; 75 | ; -> Attempts to add "my_value" to the registry key "HKCU\Software\my_key" 76 | ; with the string "some_data" as the string value. 77 | ; 78 | ; boolean := reg.add("HKCU\Software\my_key","my_value","some_data`r`n more_data","REG_MULTI_SZ") 79 | ; 80 | ; -> Attempts to add "my_value" to the registry key "HKCU\Software\my_key" with the 81 | ; string "some_data" as the data. The data type "REG_MULTI_SZ" is specified. 82 | ; The default delimiter for REG_MULTI_SZ input is "`n" or "`r`n". 83 | ; 84 | ; reg.delete(key, value := "", clearKey := false) 85 | ; 86 | ; - Specify only a key to delete that key. 87 | ; 88 | ; - Specify a key and value to delete the value. 89 | ; 90 | ; - If clearKey = true, then the specified key's values are cleared. All subkeys and 91 | ; values in those subkeys are not touched. If value is specified in this case, it 92 | ; is ignored. 93 | ; 94 | ; boolean := reg.delete("HKCU\Software\my_key") 95 | ; 96 | ; -> Deletes registry key "HKCU\Software\my_key". 97 | ; 98 | ; boolean := reg.delete("HKCU\Software\my_key",,true) 99 | ; 100 | ; -> Deletes all values within the key "HKCU\Software\my_key". All subkeys 101 | ; and values within subkeys are left intact, as well as the specified key. 102 | ; 103 | ; boolean := reg.delete("HKCU\Software\my_key","my_value") 104 | ; 105 | ; -> Delete value named "my_value" from registry key "HKCU\Software\my_key". 106 | ; 107 | ; reg.export(key, file := "", overwrite := true) 108 | ; 109 | ; - Exports the specified key to a .reg file. If no file name is specified, then the name 110 | ; of the specified subkey is used as the file name. This may fail if the specified subkey 111 | ; contains characters that are not valid for file names. The default action is to overwrite 112 | ; the .reg file if it exists in the destination directory. 113 | ; 114 | ; reg.import(file) 115 | ; 116 | ; - Imports the specified .reg file. 117 | ; 118 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 119 | ; Properties 120 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 121 | ; reg.view 122 | ; 123 | ; - Default value = "" 124 | ; - Other values = 32 or 64 125 | ; - Specifies whether to look at the 32 or 64 bit side of the registry. Changing 126 | ; this value to 32 or 64 will invoke SetRegView. Reading this value will return 127 | ; the current contents of A_RegView, however if the current reg view is "Default" 128 | ; then the returned value will be "". 129 | ; - If the user attempts to apply any other value besides 32, 64, or "", then "" 130 | ; is automatically applied (which is the same as "Default" for SetRegView). 131 | ; 132 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 133 | ; The .reason, .LastError, and .cmd properties are reset when a method() is called. 134 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 135 | ; reg.reason 136 | ; 137 | ; - Contains text describing error message. On no error, this value is blank. 138 | ; 139 | ; reg.LastError 140 | ; 141 | ; - Contains A_LastError code, or the most appropriate equivalent value. A value of 142 | ; zero indicates success. 143 | ; 144 | ; reg.cmd 145 | ; 146 | ; - Contains the full command line passed to RunWait() in case you want to double check it. 147 | ; 148 | ; Usage Examples: 149 | ; 150 | ; cur_reg_view := reg.view ; gets the current reg view 151 | ; 152 | ; reg.view := new_regView ; sets the new reg view (32, 64, or "") 153 | ; 154 | ; error_reason := reg.reason ; gets error message, when reg.method() returns false. 155 | ; 156 | ; err_code := reg.LastError 157 | ; 158 | ; ====================================================================================== 159 | ; EXAMPLES 160 | ; ====================================================================================== 161 | ; 162 | ; #INCLUDE _JXON.ahk ; https://www.autohotkey.com/boards/viewtopic.php?f=83&t=74799 163 | ; test := jxon_dump(reg.query("HKCR\*","AttributeMask",true),4) ; gets all data from values named "AttributeMask" in HKCR\* (recursive) 164 | ; A_Clipboard := test 165 | ; msgbox test 166 | ; 167 | ; ====================================================================================== 168 | ; 169 | ; #INCLUDE _JXON.ahk ; https://www.autohotkey.com/boards/viewtopic.php?f=83&t=74799 170 | ; test := jxon_dump(reg.query("HKCR\*","",true),4) ; gets all (Default) values from all subkeys (recursive) 171 | ; A_Clipboard := test 172 | ; msgbox test 173 | ; 174 | ; ====================================================================================== 175 | ; 176 | ; #INCLUDE _JXON.ahk ; https://www.autohotkey.com/boards/viewtopic.php?f=83&t=74799 177 | ; test := jxon_dump(reg.query("HKCR\*",""),4) ; gets all (Default) values from specified key and 1st level subkeys (not recursive) 178 | ; A_Clipboard := test 179 | ; msgbox test 180 | ; 181 | ; ====================================================================================== 182 | ; 183 | ; output_array := reg.query("HKEY_CLASSES_ROOT\*") ; looping through array returned by reg.query() 184 | ; msg_txt := "" 185 | ; For key, key_contents in output_array { ; looping through reg.query() output in a FOR loop 186 | ; msg_txt .= key "`r`n" 187 | ; For value, val_info in key_contents { 188 | ; msg_txt .= " VALUE: " value " (TYPE: " val_info["type"] ")`r`n" 189 | ; msg_txt .= " DATA: " (val_info["unset"] ? "(value not set)" : val_info["data"]) "`r`n" 190 | ; } 191 | ; } 192 | ; msgbox msg_txt 193 | ; 194 | ; ====================================================================================== 195 | ; 196 | ; test := reg.read("HKCU\*","") ; reads default value of key "HKCR\*" and doesn't throw an error 197 | ; msgbox "default value: " test "`r`nunset? " (reg.unset ? "yes" : "no") ; check reg.unset for determining if a (Default) value is unset 198 | ; 199 | ; ====================================================================================== 200 | ; 201 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2") ; adds only a key 202 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2","test3") ; adds a blank value named "test3" 203 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2","test3","value data") ; adds value "test3" set to "value data" 204 | ; 205 | ; ====================================================================================== 206 | ; 207 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2\test3","","test") ; add a simple test key with (Default) value data set 208 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2\test3","test_value","data for test value") ; add test value 209 | ; Msgbox "test key, test value, and (Default) data set" 210 | ; reg.delete("HKCU\Software\AutoHotkey\test1\test2\test3","") ; clears default value of test key 211 | ; msgbox "default value of test key cleared" 212 | ; reg.add("HKCU\Software\AutoHotkey\test1\test2\test3","test_value") ; overwrite test_value with zero-length string 213 | ; msgbox "overwrites test value with zero-length string" 214 | ; reg.delete("HKCU\Software\AutoHotkey\test1\test2\test3","test_value") ; removes test_value 215 | ; msgbox "test value removed" 216 | ; reg.delete("HKCU\Software\AutoHotkey\test1") ; remove the key 217 | ; msgbox "test key removed" 218 | ; 219 | ; ====================================================================================== 220 | ; 221 | ; msgbox "adding dummy test values" 222 | ; reg.add("HKCU\Software\AutoHotkey","","test") 223 | ; reg.add("HKCU\Software\AutoHotkey","poof1","test1") 224 | ; reg.add("HKCU\Software\AutoHotkey","poof2","test2") 225 | ; msgbox "now .delete() and set clearKey = true" 226 | ; reg.delete("HKCU\Software\AutoHotkey",,true) 227 | ; msgbox "now refresh the registry/RegEdit. All test values should be cleared." 228 | ; 229 | ; ====================================================================================== 230 | ; 231 | ; msgbox "WARNING: Please move through this example SLOWLY." 232 | ; msgbox "EXPORTING`r`n`r`nzero means success: " reg.export("HKCU\Software\AutoHotkey","HKCU-Ahk-registry.reg") ; exports HKCR\* to a .reg file 233 | ; msgbox "reg hive exported`r`n`r`nCheck exported file contents if you wish.`r`n`r`nRename AutoHotkey in HKCU\Software to AutoHotkey2 now for this test" 234 | ; msgbox "IMPORTING`r`n`r`nzero means success: " reg.import("HKCU-Ahk-registry.reg") "`r`n`r`nManuall refresh registry/RegEdit now (F5)." 235 | ; msgbox "registry import done`r`n`r`nIt is safe to delete AutoHotkey2 key now." 236 | ; FileDelete "HKCU-Ahk-registry.reg" 237 | ; msgbox "test reg file deleted" 238 | ; 239 | ; ====================================================================================== 240 | ; Examples testing error messages. 241 | ; ====================================================================================== 242 | ; 243 | ; msgbox reg.read("HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers","") ; should be restricted 244 | ; msgbox "Error info: " reg.LastError " / " reg.reason "`r`n`r`nActual last error: " A_LastError 245 | ; 246 | ; msgbox reg.export("HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers","test.reg") ; should be restricted 247 | ; msgbox "Error info: " reg.LastError " / " reg.reason "`r`n`r`nActual last error: " A_LastError 248 | ; 249 | ; msgbox reg.add("HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers","","test data") ; should be restricted 250 | ; msgbox "Error info: " reg.LastError " / " reg.reason "`r`n`r`nActual last error: " A_LastError 251 | ; 252 | ; msgbox reg.delete("HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers","") ; should be restricted 253 | ; msgbox "Error info: " reg.LastError " / " reg.reason "`r`n`r`nActual last error: " A_LastError 254 | ; 255 | ; ====================================================================================== 256 | ; The following .reg file contents should fail, this key should be restricted. 257 | ; Copy below contents to a error_test.reg file and place it in the same folder as 258 | ; this script. Attempt to import with commands below to see error codes/messages. 259 | ; If for some reason you have access to this key, it is recommended you remove the 260 | ; default value from the specified key below. 261 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 262 | ; Windows Registry Editor Version 5.00 263 | ; 264 | ; [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers] 265 | ; @="testing" 266 | ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 267 | ; msgbox reg.import("error_test.reg") 268 | ; msgbox "Error info: " reg.LastError " / " reg.reason "`r`n`r`nActual last error: " A_LastError 269 | ; RegDelete "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\NetDrivers" ; ONLY run this if for some reason the above commands 270 | ; ; actually write to this key's default value. 271 | ; 272 | ; ====================================================================================== 273 | ; 274 | ; msgbox "result: " reg.export("HKCU\Software\AutoHotkey","overwrite_test.reg") ; overwrite test 275 | ; msgbox "should fail: " reg.export("HKCU\Software\AutoHotkey","overwrite_test.reg",false) ; should fail 276 | ; FileDelete "overwrite_test.reg" ; clean up test file 277 | ; 278 | ; ====================================================================================== 279 | 280 | class reg { 281 | Static null := Chr(3), cmd := "", unset := false, reason := "", LastError := 0, lastKey := "" 282 | 283 | Static query(key, value := "", recurse := false) { ; REG QUERY simulated 284 | this.lastKey := key 285 | this.unset := false, this.LastError := 0, this.result := "", this.cmd := "" 286 | If (this.access_test(key)) ; test for access denied - this is very appropriate here 287 | return this.LastError 288 | 289 | output := Map(), output.CaseSense := false, readValue := true ; init base array 290 | If (value = "") { ; only append first default value if (value = "") 291 | d := Map(), d.CaseSense := false, d["data"] := "", d["type"] := "REG_SZ" ; init default value for base key 292 | Try d["data"] := RegRead(key,"") ; try to look up first default key 293 | d["unset"] := (A_LastError = 2) ? true : false ; check if first default value is unset 294 | output[this.ExpandRoot(key)] := Map("(Default)",d) ; write first default value 295 | } 296 | 297 | Loop Reg key, (recurse ? "VKR" : "VK") 298 | { 299 | k := A_LoopRegKey, v := A_LoopRegName, t := A_LoopRegType, m := A_LoopRegTimeModified 300 | (t = "KEY") ? (k := k "\" v, v := "", t := "REG_SZ") : "" 301 | readValue := (value = this.null) ? true : (value = v) ? true : false ; read value based on params/filters 302 | 303 | If (readValue) { 304 | If !output.Has(k) 305 | n := Map(), n.CaseSense := false, output[k] := n 306 | d := Map(), d.CaseSense := false, d["data"] := "", d["type"] := t 307 | Try d["data"] := RegRead(k,v) 308 | d["unset"] := (A_LastError = 2) ? true : false 309 | output[k][!v ? "(Default)" : v] := d 310 | } 311 | } 312 | this.LastError := 0, this.reason := "" 313 | return output 314 | } 315 | Static read(key, value := "") { 316 | this.lastKey := key 317 | result := "", this.cmd := "" 318 | Try result := RegRead(key,value) 319 | this.LastError := A_LastError 320 | this.reason := this.validate_error(A_LastError) 321 | this.unset := (this.LastError = 2) ? true : false 322 | return result 323 | } 324 | Static add(key, value := "", data := "", rgType := "REG_SZ") { 325 | this.lastKey := key 326 | this.unset := false, this.LastError := 0, this.result := "", this.cmd := "" 327 | If (value = this.null And data = "") { ; write a blank vlaue 328 | Try RegWrite data, rgType, key 329 | If !A_LastError 330 | Try RegDelete key 331 | } Else ; write a value 332 | Try RegWrite data, rgType, key, value 333 | this.reason := this.validate_error(A_LastError), this.LastError := A_LastError 334 | return this.LastError 335 | } 336 | Static delete(key, value := "", clearKey := false) { 337 | this.lastKey := key 338 | this.unset := false, this.LastError := 0, this.result := "", this.cmd := "", curValue := "" 339 | If (value = this.null And !clearKey) { 340 | Try RegDeleteKey key 341 | } Else If (value = this.null And clearKey) { 342 | Loop Reg key 343 | { 344 | curValue := A_LoopRegName 345 | Try RegDelete A_LoopRegKey, A_LoopRegName 346 | If A_LastError 347 | Break 348 | } 349 | If !A_LastError 350 | curValue := "" 351 | } Else 352 | Try RegDelete key, value 353 | 354 | this.reason := this.validate_error(A_LastError) (curValue ? "`r`n`r`n Key: " key "`r`nValue: " curValue : "") 355 | this.LastError := A_LastError 356 | return this.LastError 357 | } 358 | Static export(key, file := "", overwrite := true) { 359 | this.lastKey := key 360 | this.unset := false, this.LastError := 0, this.result := "", this.cmd := "" 361 | If (this.access_test(key)) ; test for access denied 362 | return this.LastError 363 | 364 | SplitPath key, &endKey, &pathKey 365 | file := (file="") ? endKey ".reg" : file ; default file name is key name 366 | file := (SubStr(file,-4) != ".reg") ? file ".reg" : file 367 | v := this.validateFileName(file) ; validate provided file/path 368 | 369 | If (!v) { 370 | this.reason := "Invalid Filename", this.LastError := 1 371 | } Else If (!overwrite And FileExist(file)) 372 | this.reason := "File exists, no overwrite", this.LastError := 1 373 | Else { 374 | r := (this.view = 32) ? " /reg:32" : (this.view = 64) ? " /reg:64" : "" 375 | o := (overwrite ? " /y" : "") ; check for overwrite, adjust command line 376 | this.cmd := "reg export " key " " Chr(34) file Chr(34) o r 377 | result := RunWait(this.cmd) 378 | If A_LastError 379 | this.reason := this.validate_error(A_LastError), this.LastError := A_LastError 380 | Else If result 381 | this.reason := "Key may not exist", this.LastError := 1 382 | } 383 | return this.LastError 384 | } 385 | Static import(file, key := "") { ; specify key if you want to test for access first 386 | this.lastKey := key 387 | this.unset := false, this.LastError := 0, this.result := "", this.cmd := "" 388 | If (key And this.access_test(key)) ; test for access denied 389 | return this.LastError 390 | 391 | v := this.validateFileName(file) ; validate provided file/path 392 | r := (this.view = 32) ? " /reg:32" : (this.view = 64) ? " /reg:64" : "" 393 | 394 | If !v 395 | this.LastError := 1, this.reason := "Invalid file name." 396 | if FileExist(file) { 397 | this.cmd := "reg import " Chr(34) file Chr(34) r 398 | result := RunWait(this.cmd) 399 | If A_LastError 400 | this.reason := this.validate_error(A_LastError), this.LastError := A_LastError 401 | Else If result 402 | this.reason := "Key may not exist, or access is denied", this.LastError := 1 403 | } Else 404 | this.LastError := 1, this.reason := "File does not exist." 405 | 406 | return this.LastError 407 | } 408 | Static access_test(key) { 409 | this.lastKey := key 410 | Try test := RegRead(key,"") 411 | If (A_LastError = 5) { ; test for access rights / access denied 412 | this.LastError := A_LastError, this.reason := this.validate_error(A_LastError) 413 | return this.LastError 414 | } Else 415 | return 0 416 | } 417 | Static validate_error(errNum) { 418 | If (errNum = 1) 419 | result := "General failure. Key/value/file may not exist, or may not be invalid." 420 | Else If (errNum = 2) 421 | result := "File not found" 422 | Else if (errNum = 5) 423 | result := "Access Denied" 424 | Else 425 | result := errNum 426 | 427 | return result 428 | } 429 | Static validateFileName(inPath) { 430 | If !inPath 431 | return false 432 | SplitPath inPath, &outFile, &outDir 433 | result := (outDir ? FileExist(outDir) : true), invalidChars := "><:/\|?*" Chr(34) 434 | Loop Parse invalidChars 435 | result := !result ? false : !InStr(outFile, A_LoopField) 436 | return result 437 | } 438 | Static ExpandRoot(key) { 439 | key := RegExReplace(key,"i)^HKCR\\","HKEY_CLASSES_ROOT\",,1) , key := RegExReplace(key,"i)^HKCU\\","HKEY_CURRENT_USER\",,1) 440 | key := RegExReplace(key,"i)^HKLM\\","HKEY_LOCAL_MACHINE\",,1), key := RegExReplace(key,"i)^HKU\\" ,"HKEY_USERS\",,1) 441 | key := RegExReplace(key,"i)^HKCC\\","HKEY_CURRENT_CONFIG",,1), key := RegExReplace(key,"i)^HKPD\\","HKEY_PERFORMANCE_DATA",,1) 442 | return key 443 | } 444 | Static CollapseRoot(key) { 445 | key := RegExReplace(key,"i)^HKEY_CLASSES_ROOT\\","HKCR\",,1) , key := RegExReplace(key,"i)^HKEY_CURRENT_USER\\","HKCU\",,1) 446 | key := RegExReplace(key,"i)^HKEY_LOCAL_MACHINE\\","HKLM\",,1) , key := RegExReplace(key,"i)^HKEY_USERS\\","HKU\",,1) 447 | key := RegExReplace(key,"i)^HKEY_CURRENT_CONFIG\\","HKCC\",,1), key := RegExReplace(key,"i)^HKEY_PERFORMANCE_DATA\\","HKPD\",,1) 448 | return key 449 | } 450 | Static view { ; handle reg view natively with AHK 451 | Set { 452 | SetRegView ((value = 32 Or value = 64) ? value : "Default" ) 453 | } 454 | Get { 455 | return ((A_RegView = "Default") ? "" : A_RegView) 456 | } 457 | } 458 | } -------------------------------------------------------------------------------- /inc/_JXON.ahk: -------------------------------------------------------------------------------- 1 | ;;;; AHK v2 2 | ; Example =================================================================================== 3 | ; =========================================================================================== 4 | 5 | ; Msgbox "The idea here is to create several nested arrays, save to text with jxon_dump(), and then reload the array with jxon_load(). The resulting array should be the same.`r`n`r`nThis is what this example shows." 6 | ; a := Map(), b := Map(), c := Map(), d := Map(), e := Map(), f := Map() ; Object() is more technically correct than {} but both will work. 7 | 8 | ; d["g"] := 1, d["h"] := 2, d["i"] := ["purple","pink","pippy red"] 9 | ; e["g"] := 1, e["h"] := 2, e["i"] := Map("1","test1","2","test2","3","test3") 10 | ; f["g"] := 1, f["h"] := 2, f["i"] := [1,2,Map("a",1.0009,"b",2.0003,"c",3.0001)] 11 | 12 | ; a["test1"] := "test11", a["d"] := d 13 | ; b["test3"] := "test33", b["e"] := e 14 | ; c["test5"] := "test55", c["f"] := f 15 | 16 | ; myObj := Map() 17 | ; myObj["a"] := a, myObj["b"] := b, myObj["c"] := c, myObj["test7"] := "test77", myObj["test8"] := "test88" 18 | 19 | ; g := ["blue","green","red"], myObj["h"] := g ; add linear array for testing 20 | 21 | ; q := Chr(34) 22 | ; textData2 := Jxon_dump(myObj,4) ; ===> convert array to JSON 23 | ; msgbox "JSON output text:`r`n===========================================`r`n(Should match second output.)`r`n`r`n" textData2 24 | 25 | ; newObj := Jxon_load(&textData2) ; ===> convert json back to array 26 | 27 | ; textData3 := Jxon_dump(newObj,4) ; ===> break down array into 2D layout again, should be identical 28 | ; msgbox "Second output text:`r`n===========================================`r`n(should be identical to first output)`r`n`r`n" textData3 29 | 30 | ; ExitApp 31 | 32 | ; =========================================================================================== 33 | ; End Example ; ============================================================================= 34 | ; =========================================================================================== 35 | 36 | ; originally posted by user coco on AutoHotkey.com 37 | ; https://github.com/cocobelgica/AutoHotkey-JSON 38 | 39 | Jxon_Load(&src, args*) { 40 | static q := Chr(34) 41 | 42 | key := "", is_key := false 43 | stack := [ tree := [] ] 44 | is_arr := Map(tree, 1) ; ahk v2 45 | next := q "{[01234567890-tfn" 46 | pos := 0 47 | 48 | while ( (ch := SubStr(src, ++pos, 1)) != "" ) { 49 | if InStr(" `t`n`r", ch) 50 | continue 51 | if !InStr(next, ch, true) { 52 | testArr := StrSplit(SubStr(src, 1, pos), "`n") 53 | 54 | ln := testArr.Length 55 | col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1)) 56 | 57 | msg := Format("{}: line {} col {} (char {})" 58 | , (next == "") ? ["Extra data", ch := SubStr(src, pos)][1] 59 | : (next == "'") ? "Unterminated string starting at" 60 | : (next == "\") ? "Invalid \escape" 61 | : (next == ":") ? "Expecting ':' delimiter" 62 | : (next == q) ? "Expecting object key enclosed in double quotes" 63 | : (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'" 64 | : (next == ",}") ? "Expecting ',' delimiter or object closing '}'" 65 | : (next == ",]") ? "Expecting ',' delimiter or array closing ']'" 66 | : [ "Expecting JSON value(string, number, [true, false, null], object or array)" 67 | , ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1] 68 | , ln, col, pos) 69 | 70 | throw Error(msg, -1, ch) 71 | } 72 | 73 | obj := stack[1] 74 | memType := Type(obj) 75 | is_array := (memType = "Array") ? 1 : 0 76 | 77 | if i := InStr("{[", ch) { ; start new object / map? 78 | val := (i = 1) ? Map() : Array() ; ahk v2 79 | 80 | is_array ? obj.Push(val) : obj[key] := val 81 | stack.InsertAt(1,val) 82 | 83 | is_arr[val] := !(is_key := ch == "{") 84 | next := q (is_key ? "}" : "{[]0123456789-tfn") 85 | } else if InStr("}]", ch) { 86 | stack.RemoveAt(1) 87 | next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}" 88 | } else if InStr(",:", ch) { 89 | is_key := (!is_array && ch == ",") 90 | next := is_key ? q : q "{[0123456789-tfn" 91 | } else { ; string | number | true | false | null 92 | if (ch == q) { ; string 93 | i := pos 94 | while i := InStr(src, q,, i+1) { 95 | val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C") 96 | if (SubStr(val, -1) != "\") 97 | break 98 | } 99 | if !i ? (pos--, next := "'") : 0 100 | continue 101 | 102 | pos := i ; update pos 103 | 104 | val := StrReplace(val, "\/", "/") 105 | val := StrReplace(val, "\" . q, q) 106 | , val := StrReplace(val, "\b", "`b") 107 | , val := StrReplace(val, "\f", "`f") 108 | , val := StrReplace(val, "\n", "`n") 109 | , val := StrReplace(val, "\r", "`r") 110 | , val := StrReplace(val, "\t", "`t") 111 | 112 | i := 0 113 | while i := InStr(val, "\",, i+1) { 114 | if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0 115 | continue 2 116 | 117 | xxxx := Abs("0x" . SubStr(val, i+2, 4)) ; \uXXXX - JSON unicode escape sequence 118 | if (xxxx < 0x100) 119 | val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6) 120 | } 121 | 122 | if is_key { 123 | key := val, next := ":" 124 | continue 125 | } 126 | } else { ; number | true | false | null 127 | val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos) 128 | 129 | if IsInteger(val) 130 | val += 0 131 | else if IsFloat(val) 132 | val += 0 133 | else if (val == "true" || val == "false") 134 | val := (val == "true") 135 | else if (val == "null") 136 | val := "" 137 | else if is_key { 138 | pos--, next := "#" 139 | continue 140 | } 141 | 142 | pos += i-1 143 | } 144 | 145 | is_array ? obj.Push(val) : obj[key] := val 146 | next := obj == tree ? "" : is_array ? ",]" : ",}" 147 | } 148 | } 149 | 150 | return tree[1] 151 | } 152 | 153 | Jxon_Dump(obj, indent:="", lvl:=1) { 154 | static q := Chr(34) 155 | 156 | if IsObject(obj) { 157 | memType := Type(obj) ; Type.Call(obj) 158 | is_array := (memType = "Array") ? 1 : 0 159 | 160 | if (memType ? (memType != "Object" And memType != "Map" And memType != "Array") : (ObjGetCapacity(obj) == "")) 161 | throw Error("Object type not supported.`r`n`r`nObj Type: " Type(obj), -1, Format("", ObjPtr(obj))) 162 | 163 | if IsInteger(indent) 164 | { 165 | if (indent < 0) 166 | throw Error("Indent parameter must be a postive integer.", -1, indent) 167 | spaces := indent, indent := "" 168 | 169 | Loop spaces ; ===> changed 170 | indent .= " " 171 | } 172 | indt := "" 173 | 174 | Loop indent ? lvl : 0 175 | indt .= indent 176 | 177 | lvl += 1, out := "" ; Make #Warn happy 178 | for k, v in obj { 179 | if IsObject(k) || (k == "") 180 | throw Error("Invalid object key.", -1, k ? Format("", ObjPtr(obj)) : "") 181 | 182 | if !is_array ;// key ; ObjGetCapacity([k], 1) 183 | out .= (ObjGetCapacity([k]) ? Jxon_Dump(k) : q k q) (indent ? ": " : ":") ; token + padding 184 | 185 | out .= Jxon_Dump(v, indent, lvl) ; value 186 | . ( indent ? ",`n" . indt : "," ) ; token + indent 187 | } 188 | 189 | if (out != "") { 190 | out := Trim(out, ",`n" . indent) 191 | if (indent != "") 192 | out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1) 193 | } 194 | 195 | return is_array ? "[" . out . "]" : "{" . out . "}" 196 | } else { ; Number 197 | If (Type(obj) != "String") 198 | return obj 199 | Else { 200 | obj := StrReplace(obj,"\","\\") 201 | obj := StrReplace(obj,"`t","\t") 202 | obj := StrReplace(obj,"`r","\r") 203 | obj := StrReplace(obj,"`n","\n") 204 | obj := StrReplace(obj,"`b","\b") 205 | obj := StrReplace(obj,"`f","\f") 206 | obj := StrReplace(obj,"/","\/") 207 | obj := StrReplace(obj,q,"\" q) 208 | return q obj q 209 | } 210 | } 211 | } 212 | 213 | -------------------------------------------------------------------------------- /inc/funcs.ahk: -------------------------------------------------------------------------------- 1 | ; ================================================================= 2 | ; ahkProps := GetAhkProps(sInput) 3 | ; Returns ahk properties in a Map(). 4 | ; > sInput assumes the following path format: 5 | ; - X:\path\base_path\AhkName AhkVersion\[AHK_H subfolder\]AutoHotkey[type].exe 6 | ; - [type] = A32 / U32 / U64 / HA32 / HA32_MT / HU32 / HU32_MT / HU64 / HU64_MT 7 | ; 8 | ; props: ahkProduct, ahkVersion, installDir, ahkType, bitness, exeFile, exePath, exeDir, variant 9 | ; 10 | ; ahkprop key values: 11 | ; - product = AHK / AutoHotkey / AHK_H / AutoHotkey_H ... however it is typed 12 | ; - version = Ex: 1.1.32.00 13 | ; - majVersion = first char of version 14 | ; - type = Unicode / ANSI 15 | ; - bitness = 32-bit / 64-bit 16 | ; - installDir = base folder where Ahk2Exe and help file resides 17 | ; - exeFile = full name of exe file 18 | ; - exePath = full path to and including the exe file 19 | ; - exeDir = dir exe is located in 20 | ; - variant = MT for "multi-threading" or blank ("") 21 | 22 | 23 | ; sFile := FileSelect() 24 | ; If !sFile 25 | ; exitapp 26 | ; SplitPath sFile, &_FileExt, &_Dir, &_Ext, &_File, &_Drv 27 | ; objShl := ComObject("Shell.Application") 28 | ; objDir := objShl.NameSpace(_Dir) 29 | ; objItm := objDir.ParseName(_FileExt) 30 | ; msgbox "Product Name:`t" objItm.ExtendedProperty("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 7") "`r`n" 31 | ; . "File Desc:`t" objItm.ExtendedProperty("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 3") "`r`n" 32 | ; . "Product Ver:`t" objItm.ExtendedProperty("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 8") "`r`n" ; (not listed in propkey.h) 33 | ; . "Copyright:`t" objItm.ExtendedProperty("{64440492-4C8B-11D1-8B70-080036B11A03} 11") 34 | 35 | 36 | GetAhkProps(sInput) { 37 | If (!FileExist(sInput)) 38 | return "" 39 | 40 | SplitPath sInput, &ahkFile, &curDir 41 | objShl := ComObject("Shell.Application") 42 | objDir := objShl.NameSpace(curDir) 43 | objItm := objDir.ParseName(ahkFile) 44 | FileDesc := objItm.ExtendedProperty("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 3") 45 | ahkVersion := RegExReplace(objItm.ExtendedProperty("{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 8"),"\, ?",".") 46 | 47 | arr := StrSplit(FileDesc," ") 48 | ahkProduct := arr[1] 49 | bitness := (SubStr(ahkVersion,1,3) = "1.0") ? 32 : StrReplace(arr[arr.Length],"-bit","") ; thanks to use LBJ for this 50 | isAhkH := (ahkProduct = "AutoHotkey_H")?true:false 51 | ahkType := (arr.Length = 3) ? arr[2] : "Unicode" 52 | 53 | var := "", installDir := curDir 54 | 55 | If (InStr(sInput,"\Win32a_MT\")) 56 | installDir := StrReplace(installDir,"\Win32a_MT"), var := "MT" 57 | Else If (InStr(sInput,"\Win32a\")) 58 | installDir := StrReplace(installDir,"\Win32a") 59 | Else If (InStr(sInput,"\Win32w_MT\")) 60 | installDir := StrReplace(installDir,"\Win32w_MT"), var := "MT" 61 | Else If (InStr(sInput,"\Win32w\")) 62 | installDir := StrReplace(installDir,"\Win32w") 63 | Else If (InStr(sInput,"\x64w_MT\")) 64 | installDir := StrReplace(installDir,"\x64w_MT"), var := "MT" 65 | Else If (InStr(sInput,"\x64w\")) 66 | installDir := StrReplace(installDir,"\x64w") 67 | 68 | ahkProps := {exePath:sInput, installDir:installDir, product:ahkProduct, version:ahkVersion, majVersion:SubStr(ahkVersion,1,1) 69 | , type:ahkType, bitness:bitness, variant:var, exeFile:ahkFile, exeDir:curDir, isAhkH:isAhkH, date:FileGetTime(sInput,"M")} 70 | 71 | If (ahkType = "" || bitness = "") 72 | return "" 73 | Else 74 | return ahkProps 75 | } 76 | 77 | ; =========================================================================== 78 | ; created by TheArkive 79 | ; Usage: Specify X/Y coords to get info on which monitor that point is on, 80 | ; and the bounds of that monitor. If no X/Y is specified then the 81 | ; current mouse X/Y coords are used. 82 | ; =========================================================================== 83 | GetMonitorData(x:="", y:="") { 84 | CoordMode "Mouse", "Screen" ; CoordMode Mouse, Screen ; AHK v1 85 | If (x = "" Or y = "") 86 | MouseGetPos &x, &y 87 | actMon := 0 88 | 89 | monCount := MonitorGetCount() ; SysGet, monCount, MonitorCount ; AHK v1 90 | Loop monCount { ; Loop % monCount { ; AHK v1 91 | MonitorGet(A_Index,&mLeft,&mTop,&mRight,&mBottom) ; SysGet, m, Monitor, A_Index ; AHK v1 92 | 93 | If (mLeft = "" And mTop = "" And mRight = "" And mBottom = "") 94 | Continue 95 | 96 | If (x >= (mLeft) And x <= (mRight-1) And y >= mTop And y <= (mBottom-1)) { 97 | monList := {}, monList.left := mLeft, monList.right := mRight 98 | monList.top := mTop, monList.bottom := mBottom, monList.active := A_Index 99 | monList.x := x, monList.y := y 100 | monList.Cx := ((mRight - mLeft) / 2) + mLeft 101 | monList.Cy := ((mBottom - mTop) / 2) + mTop 102 | monList.w := mRight - mLeft, monList.h := mBottom - mTop 103 | Break 104 | } 105 | } 106 | 107 | return monList 108 | } 109 | 110 | ; ==================================================================================== 111 | ; Explorer_GetSelection() 112 | ; ==================================================================================== 113 | ; thanks to boiler 114 | ; https://www.autohotkey.com/boards/viewtopic.php?f=6&t=76602&p=332166&hilit=boiler+RAAV#p332166 115 | ; thanks to Rapte_Of_Suzaku 116 | ; https://autohotkey.com/board/topic/60985-get-paths-of-selected-items-in-an-explorer-window/ 117 | ; thanks to TeaDrinker 118 | ; https://www.autohotkey.com/boards/viewtopic.php?p=255169#p255169 119 | ; ==================================================================================== 120 | Explorer_GetSelection(hwnd:=0, usePath:=false) { ; thanks to boiler, from his RAAV script, slightly modified 121 | hWnd := (!hwnd) ? WinExist("A") : hwnd 122 | winClass := WinGetClass("ahk_id " . hwnd) 123 | 124 | if !(winClass ~= "((Cabinet|Explore)WClass|WorkerW|Progman)") ; add checking for icons on desktop 125 | Return 126 | 127 | for window in ComObject("Shell.Application").Windows 128 | if (hWnd = window.HWND) && (oShellFolderView := window.document) 129 | break 130 | 131 | result := "" 132 | If (winClass = "WorkerW" Or winClass = "Progman") { 133 | root := (SubStr(A_Desktop,-1)=="\") ? SubStr(A_Desktop,1,-1) : A_Desktop 134 | 135 | items := ListViewGetContent("Selected", "SysListView321", hwnd) 136 | Loop Parse items, "`n", "`r" 137 | result .= ((A_Index>1)?"`n":"") root "\" SubStr(A_LoopField,1,InStr(A_LoopField,Chr(9))-1) 138 | } Else { 139 | root := oShellFolderView.Folder.Self.Path 140 | 141 | for item in oShellFolderView.SelectedItems 142 | result .= (result = "" ? "" : "`n") . item.path 143 | } 144 | 145 | if !result And usePath 146 | result := root 147 | 148 | Return result 149 | } 150 | 151 | ; ==================================================================================== 152 | ; This func parses a script for a #Requires directive. If one is found, the param 153 | ; is parsed to determine which EXE to use to run the script. If not found 154 | ; ==================================================================================== 155 | proc_script(in_script, compiler:=false) { 156 | Global Settings 157 | admin := false, stdoutdebug := false, exe := "", err := "", cond := "", match := false 158 | _bitness := bitness := A_Is64BitOS ? 64 : 32 159 | baseFolder := Settings["BaseFolder"] ? Settings["BaseFolder"] : A_ScriptDir "\versions" 160 | 161 | If !FileExist(in_script) 162 | return {exe:"", admin:false, err:"File does not exist.", cond:""} 163 | 164 | script_text := FileRead(in_script) 165 | 166 | If RegExMatch(script_text,"im)^(?:;[ `t]+)?#Requires.*",&m) { 167 | arr := StrSplit(Trim(cond:=m[0],";`t "),";") 168 | vArr := StrSplit(Trim(RegExReplace(arr[1],"(#Requires|\" Chr(34) ")",""),";`t ")," ") 169 | 170 | If !vArr.Has(2) 171 | return {exe:"", admin:false, err:"Specify the product (AutoHotkey / AutoHotkey_H) when using #REQUIRES directive.", cond:cond} 172 | 173 | isAhkH := RegExMatch((prod := vArr[1]),"AutoHotkey_?H") ? true : false 174 | ver := (SubStr(vArr[2],1,1) = "v") ? SubStr(vArr[2],2) : vArr[2] 175 | 176 | If arr.Has(2) { 177 | For i, opt in StrSplit(Trim(arr[2]," `t")," ") 178 | If InStr(opt,"-bit") || (opt = 32 || opt = 64) 179 | _bitness := StrReplace(opt,"-bit","") 180 | Else If (opt = "admin") 181 | admin := true 182 | Else If (opt = "stdoutdebug") 183 | stdoutdebug := true 184 | Else If (opt = "match") 185 | match := true 186 | } 187 | 188 | If (_bitness > bitness) 189 | return {exe:"", admin:false, err:"64-bit executable specified on 32-bit system - halting.",cond:cond} 190 | 191 | lastVer := ver, mVer := SubStr(ver,1,1) 192 | Loop Files baseFolder "\AutoHotkey*.exe", "R" 193 | { 194 | f := GetAhkProps(A_LoopFileFullPath) 195 | 196 | If ((A_LoopFileName = "AutoHotkey.exe") && (!f.isAhkH)) 197 | || RegExMatch(A_LoopFileFullPath,"i)\\(_*OLD_*|Compiler)\\") 198 | || (isAhkH != f.isAhkH) ; matching exclusive for AHK_H status 199 | || (_bitness != f.bitness) 200 | || (VerCompare(f.version,ver) = -1) 201 | || (mVer != f.majVersion) 202 | Continue 203 | 204 | If (comp := VerCompare(f.version,lastVer)) >= 0 ; find highest version match 205 | exe := f.exePath, lastVer := f.version 206 | 207 | If match && (comp != 0) ; if no match, and 'match' option is used, clear "exe" var 208 | exe := "" 209 | 210 | If match && (comp=0) ; stop on equal match if "match" is specified 211 | Break 212 | } 213 | 214 | } Else { 215 | exe := Settings["ActiveVersionPath"] 216 | } 217 | 218 | If (compiler) { 219 | f := GetAhkProps(exe) 220 | exe := f.installDir "\Compiler\Ahk2Exe.exe" 221 | } 222 | 223 | If (!exe && !Settings["ActiveVersionPath"]) 224 | err := "You need to select / install a version of AutoHotkey from the main UI." 225 | 226 | return {exe:exe, admin:admin, err:err, cond:cond, stdoutdebug:stdoutdebug} 227 | } 228 | 229 | 230 | ; ======================================================================= 231 | ; hash() - Supports MD2, MD4, MD5, SHA1, SHA256, SHA384, SHA512 232 | ; buf = can be a buffer, a string, or a file name (string) 233 | ; hashType = pick one [MD2, MD4, MD5, SHA1, SHA256, SHA384, SHA512] 234 | ; ======================================================================= 235 | hash(buf,hashType:="sha256") { 236 | If (Type(buf) = "String") && FileExist(buf) ; to expand: https://www.phdcc.com/cryptorc4.htm 237 | buf := FileRead(buf,"RAW") 238 | Else If (Type(buf) = "String") 239 | buf := Buffer(StrPut(txt := buf),0) 240 | , StrPut(txt, buf) 241 | Else if (Type(buf) != "Buffer") 242 | return -1 ; invalid value passed in buffer 243 | 244 | hashList := {MD2:0x8001, MD4:0x8002, MD5:0x8003, SHA:0x8004, SHA1:0x8004 245 | , SHA256:0x800C, SHA384:0x800D, SHA512:0x800E} 246 | 247 | r1 := DllCall("advapi32\CryptAcquireContext","UPtr*",&hProv:=0,"Ptr",0,"Ptr",0 248 | ,"UInt",PROV_RSA_AES:=0x18,"UInt",0xF0000000) 249 | 250 | r3 := DllCall("advapi32\CryptCreateHash","UPtr",hProv,"UInt",hashList.%hashType% ; 0x800C 251 | ,"UInt",0,"UInt",0,"UPtr*",&hHash:=0) 252 | 253 | r5 := DllCall("advapi32\CryptHashData","UPtr",hHash,"UPtr",buf.ptr,"UInt",buf.size,"UInt",0) 254 | 255 | r6 := DllCall("advapi32\CryptGetHashParam","UPtr",hHash,"UInt",HP_HASHVAL:=0x2 256 | ,"UPtr",0,"UInt*",&iSize1:=0,"UInt",0) 257 | outHash := Buffer(iSize1,0), outVal := "" 258 | r7 := DllCall("advapi32\CryptGetHashParam","UPtr",hHash,"UInt",HP_HASHVAL:=0x2 259 | ,"UPtr",outHash.ptr,"UInt*",&iSize2:=iSize1,"UInt",0) 260 | Loop iSize2 261 | outVal .= Format("{:02X}",NumGet(outHash,A_Index-1,"UChar")) 262 | 263 | r4 := DllCall("advapi32\CryptDestroyHash","UPtr",hHash) 264 | r2 := DllCall("advapi32\CryptReleaseContext","UPtr",hProv,"UInt",0) 265 | 266 | return outVal 267 | } 268 | 269 | -------------------------------------------------------------------------------- /resources/AHK_pi_blue.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheArkive/ahk-pi/a3146b8299de0567c96a295857670ed9a86f68f0/resources/AHK_pi_blue.ico -------------------------------------------------------------------------------- /resources/AHK_pi_green.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheArkive/ahk-pi/a3146b8299de0567c96a295857670ed9a86f68f0/resources/AHK_pi_green.ico -------------------------------------------------------------------------------- /resources/AHK_pi_orange.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheArkive/ahk-pi/a3146b8299de0567c96a295857670ed9a86f68f0/resources/AHK_pi_orange.ico -------------------------------------------------------------------------------- /resources/AHK_pi_pink.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheArkive/ahk-pi/a3146b8299de0567c96a295857670ed9a86f68f0/resources/AHK_pi_pink.ico -------------------------------------------------------------------------------- /resources/AHK_pi_red.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheArkive/ahk-pi/a3146b8299de0567c96a295857670ed9a86f68f0/resources/AHK_pi_red.ico -------------------------------------------------------------------------------- /resources/TemplateV1.ahk: -------------------------------------------------------------------------------- 1 | ; AHK v1 Template 2 | #NoEnv ; Recommended for performance and compatibility with future AHK releases, but not AHK2. 3 | ; #Warn ; Enable warnings to assist with detecting common errors. 4 | SendMode Input ; Recommended for new scripts due to its superior speed and reliability. 5 | SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory. 6 | ;#NoTrayIcon -------------------------------------------------------------------------------- /resources/TemplateV2.ahk: -------------------------------------------------------------------------------- 1 | ; AHK v2 Template 2 | ; #Warn ; Enable warnings to assist with detecting common errors. 3 | SendMode "Input" ; Recommended for new scripts due to its superior speed and reliability. 4 | SetWorkingDir A_ScriptDir ; Ensures a consistent starting directory. --------------------------------------------------------------------------------