This is more than just an elaborate prank:
- It is meant to show that AutoHotkey is AutoHotkey, regardless of the language; that the ease with which it can be used to get things done is primarily due to its library of functions, not its unique language.
- It is a test of the current state of AutoHotkey v2, its flexibility and capabilities.
- It is for sounding out some ideas for the future of AutoHotkey (post v2.0).
I fully intend to bring the development of v2 alpha to an end within a few months, by releasing a beta. Anyone who has looked at v2-thoughts recently might have noticed there's not much there that hasn't been deferred. I do have another list of items to look into or change (which has grown while working on this project), but they are small things that I'll either implement soon or defer until a later release. If I don't set a deadline, there will always be more things added to the list before I finish the last "important" item.
Did you know v2.0-a001 was released on April 2, 2011?
Being familiar with several languages before I found AutoHotkey, and having an interest in languages in general, it's always seemed inevitable to me that AutoHotkey would be brought to other languages. I haven't done it myself because my interest lies more with developing the language than actually using it to get things done. It seemed to me that "porting" all of the functions would be tedious work, but not difficult. It still puzzles me that several users have asked for it, but no one (to my knowledge) has attempted it (not counting scripts like this one).
In 2016, I hit a wall of sorts; I wasn't happy with the state of the language, but didn't know what to do about it. I thought about porting to other languages, but the last thing I wanted was to burn out (more) by working on something tedious. So I created a project to experiment with binding to different languages and frameworks, without the tedious work of actually porting the existing functions. It would take a declaration like nil MouseGetPos(out int X, out int Y) and generate "glue" code to bind a C++ function into a COM library (usable from JScript and VBScript), a .NET assembly, Python and Lua, with language-appropriate syntax. I put it aside once I got it to a basic functioning state, for whatever reason; probably because all of the interesting parts were already done, and by that time I had decided to make The Big Changes and move forward with v2.
That project isn't dead, just on hold. Post-v2.0, I plan to clean up the code and separate most of AutoHotkey's functionality from its script engine, with a view to binding the functionality to other languages, opening up other possibilities for the script engine, and making the code more maintainable, among other reasons. However, although v2.0-beta.1 might not be far away, there is still a lot of work to be done to catch up on code maintenance (I have a long TODO list) and polish things up for a proper v2.0.0 release.
This Script
The original concept for this script came from @Aurelain's Exo. I started rewriting it for AutoHotkey v2 and JsRT in 2015, but I didn't get very far. Some parts of v2 still weren't flexible enough for first-class integration, like Hotkey If requiring an #if already present in the script. This iteration of the script was written over the last few weeks.
The basic premise is incredibly simple:
Code: Select all
jscode := '
(
hotkey('#+n', function() { run('notepad') }); // No fat arrows in this version of JScript.
hotkey('#n', openNotepad);
function openNotepad() {
if (winExist('ahk_exe notepad.exe'))
winActivate();
else
run('notepad.exe');
}
)'
js := ComObjCreate("ScriptControl"), js.Language := "JScript" ; Requires 32-bit !
for f in [Hotkey, Run, WinExist, WinActivate]
js.AddObject(RegExReplace(f.name, '^\w', '$L0'), f)
js.Eval(jscode)
This works on the surface, but there are a number of problems:
- When an error is thrown by AutoHotkey back to JavaScript back to AutoHotkey, the type of the error is lost and there's nothing indicating which part of the JavaScript code was executing. Fortunately, JsRT provides some simple functions for putting the engine into an "exception state", checking whether it's in that state, and retrieving whatever value was thrown by the script. I use this to translate AutoHotkey errors to JavaScript errors, and throw JavaScript errors back to OnError, which shows an error dialog.
- I solve a number of problems by wrapping all functions which are "exported" to JavaScript, and translating parameters and the return value as needed:
- In JavaScript, omitting a parameter is the same as passing undefined, but undefined passes through the IDispatch interface as VT_EMPTY, which AutoHotkey translates to "". Passing "" isn't always the same as omitting the parameter.
- true translates to -1 (VARIANT_TRUE), which produces counter-intuitive results for some functions.
- AutoHotkey returns integers for boolean values, where one would expect proper boolean values in JavaScript.
- When a callback function is passed from JavaScript to AutoHotkey through the built-in COM support, it gets a new ComObject wrapper each time. This means that functions which use the callback as an identifier won't work as expected; e.g. calling setTimer twice would register two timers, or fail to delete the timer if period is 0.
- Objects returned by AutoHotkey functions don't behave like JavaScript objects.
- Objects passed to AutoHotkey functions are required to have properties that don't follow JavaScript conventions, like Ptr, Size and Hwnd.
- A couple of functions currently require an actual AutoHotkey Array, and won't work with anything else.
- Some functions require the use of ByRef for output, which currently requires a native AutoHotkey VarRef. Even if a function to create one (i.e. _ => &_) is provided, output parameters are not the JavaScript way.
- Functions which take string parameters don't use JavaScript semantics for converting the value to a string. (I haven't fixed this yet because it would probably require marking which parameters require such conversion, or new language features which are tentatively planned for the distant future.)
- Various bugs and several minor inconsistencies which will be fixed by v2.0-a130.
- No way to conditionally put directives such as #Persistent, #InstallKeybdHook and #InstallMouseHook into effect at runtime; i.e. one must rely on the side-effects of other functions. I realized that simply replacing these with functions increases flexibility and requires hardly any change to scripts.
- No way to use OnMessage without making the script persistent. I worked around it by using window subclassing, but this has some obvious and some hidden drawbacks.
- No way to customize the tray menu without making the script persistent. (The tray menu is customized to restore the standard items which are normally omitted by compiled scripts.) I worked around it by avoiding A_TrayMenu and instead showing some other menu in its place. However, replicating the standard behaviour was unexpectedly complicated, especially because the window subclassing caused issues with Pause.
- Some directives cannot be replicated at runtime, and/or control settings which can't be retrieved by the script. In some cases this simplifies the code (e.g. sizing the key history buffer at load time avoids issues of thread safety), but in some other cases a script function or variable would work just as well or better.
In v2.0-a130, OnMessage and custom tray menu items will not make the script persistent; instead, the script will have to call Persistent() if needed. I figure this increases flexibility without sacrificing much convenience. (Follow the links for detailed comments.)
Another issue I was already aware of is that unless you Suspend all hotkeys, creating hotkeys with the Hotkey function is relatively inefficient:
A possible solution would be to not enable hotkeys until some time (relatively short, maybe 100ms) has passed or the auto-execute thread completes. I think this generally wouldn't be noticeable.Creating hotkeys via the double-colon syntax performs better than using the Hotkey function because the hotkeys can all be enabled as a batch when the script starts (rather than one by one). Therefore, it is best to use this function to create only those hotkeys whose key names are not known until after the script has started running.
Now on GitHub:
AutoHotkey-jk