Autohotkey
Constructive Language Criticism
& Script Merging
Written by Miodrag Milic
Download
examples used in this article
Table of Contents
Constructive Language
Criticism
Commands
with the same meaning
Multithreading & script
merging
The final
part – adding the flag
Some Thoughts About The
Future
This article is written for experienced users of Autohotkey and for the author of this powerful open-source scripting language. I will try to underscore some problematic issues in the current language environment and to influence future developing plans with some, by my experience, useful ideas and propositions.
I will also write about some methodologies I am using in my personal developing, while discussing issues with AHK. I don’t want to recommend this as programming reference or how things should be done - this is just what I find appropriate for achieving some things that are not obvious in regular AHK usage.
I find implementation of labels in AHK different from almost any other I encounter.
The problem is in the fact that labels are poorly context aware, that is, they can be set at almost any location. You can jump out of function, inside a function and up to day, I am uncertain where can I put them. For instance, you can’t jump into the loop, or at the end of the function but I constantly discover some new ways to jump, many of them are suggested by AHK users on the official forum.
Look at this example of handling windows message WM_KEY.
OnMessage(WM_KEY, “Dispatch”) ;jump inside a function
return
MessageHandler(wparam,lparam)
{
Goto functionCall
Dispatch:
Lparam := Application_lparam
wparam := Application_wparam
functionCall:
; do some stuff here
}
Whitespace influence the code execution.
MyFunction()
{ global ;will never be executed
...
}
The one generally can’t organize code the way he/she wants because whitespace influence compilation. global in the above code will never be executed because of the { in the same line, and error can be easily overseen. This is consequence of string literals witch don’t have to be quoted by design and absence of command terminator:
(1) s = some text
(2) s := “some text"
Both sentences are legal in AHK. Mentioned reasons don’t allow us to write code like:
s := “some text”; Send %s%; return;
I used ; here as command separator since it is often used in this role in other languages (it is a comment character in AHK)
I propose changes here so the whitespace doesn’t influence the program flow.
We lose: Easy variable assignment like (1) and syntax
s = %s% new text %p%
witch would have to be done only using “.” Operator, this way:
s := s . “new text” . p
witch I consider as trivial difference.
We get: - Better code organization and so, better handling and maintaince.
- One way to assign variables instead 2, which means less errors.
global & local keywords can be really confusing at the beginning. With practice this fades away but still some things are not obvious.
There is also that thing about arrays. Assume that you want to access global and local arrays inside a function:
MyFunction()
{
(1) global (2) local
(1) local i, j (2) global globalArray_*
...
globalArray_%i% := “something global”
localArray_%j% := “something local”
}
If you use (1) you will have access to the globalArray, but localArray will be also created global.
If you use (2) you will have to explicitly name all globalArray items, witch can’t be done for large arrays.
So there is no way to have both global and local arrays in the same function.
You can construct wrapper like this:
MyFunction()
{
local
...
SetGlobal(globalArray_%i%, “something global”)
localArray_%j% := “something local”
}
SetGlobal(ByRef var, value)
{
global
var := value
}
but that is far from convenient
Solution:
Adding metacharacter for arrays, like * so (1) can be written as
(1) global
(1) local i, j, localArray_*
The meaning of “*” should be understood as:
All variables with the prefix equal to word
before * will be treated as local/global
Another very important thing about scope is that we have only 2 of them.
Unit scope should be also considered or even namespace to achieve script merging possible.
I will talk about this later when discussing script merging.
This is another thing that obviously needs improvement in AHK. You can’t access individual string characters because there are no arrays in AHK. So you can’t write something like this:
s := “Some long string with configuration details”
if (s[20]=”F” && s[43]=”3”)
...
Now you must extract each string position with StringMid function or use &, possibly create some wrapper around it but that is no wise solution for reasons that will become apparent latter in the text, when script merging is discussed.
Solution:
Adding array reference operator, like [] or any other convenient. This can be done internally or via preprocessor that will translate [] calls to appropriate function calls, like StringMid. We lose [] in naming variables but we get convenient mechanism for array manipulation.
I guess compatibility is the only reason why AHK have 2 commands doing the same thing on almost every place. This is confusing, error prone, and not effective at all, since function parameters are expressions while their alternatives use literals. Those commands that don’t have function alternatives are harder to use, since they can not be placed in expressions so separate code lines and variables must be created. Then, if you want those variables to be local thus to avoid global variable cloud fields you have to make sure you are doing it in a function, because subroutines can’t have local variables. Documentation fog is another problem.
My suggestion is to mark non-function commands obsolete and to keep function versions (and to create function alternatives for commands that don’t have them).
I am aware that this is a major compatibility issue, but compatibility sometimes must be broken for the sake of the greater good. Appropriate directive can be added to new scripts to make AHK recognize them as new (and command line parameter also), thus to invoke older compiler scheme when running obsolete scripts. Thus we can have both types of scripts on transition.
We get:
Sophisticated code, capable of better constructs.
Fewer errors
Less variables and code lines.
We lose:
We would have to specify additional directive at the start of every script, like #AHK_2
This will happen only in transition, since later, this can be set as default script environment, similar to what we will have with #NoEnv directive.
AHK can not have multiple threads of execution. I find this to be the major problem, since without it you can’t run multiple, by all other means standalone scripts, by merging them into the single script.
Although this is true in general, script merging can be facilitated in situations when scripts don’t run in parallel because scripts will not interrupt each other. This is usually the case with the hotkeys.
The reasons for merging scripts are various, and most of them will become apparent at the end of this discussion.
Things that need to be taken into account are:
- Names (GUIs, menus, labels, function names, and global variables)
- Includes and autorun sections
- Tray menu
- WinAPI message handlers
The primer that follows observes 2 scripts that I created, FavMenu and Editor.
FavMenu is Open/Save standard dialog enhancer that will create hotkey for you to easily change the path within a dialog without the need to browse to the destination and Editor is its menu editor. Those scripts are the proof of the concept for the technique that I will discuss in the next section.
Both scripts are complex, with more then 1000 lines each, but FavMenu is separated into several ahk scripts to keep developing easier and to keep GUIs as separate units. FavMenu has 2 Guis, named Properties & Setup, while Editor has single GUI that is launched on startup. FavMenu can be merged into a single script without includes at the end.
Technique starts by defining naming scheme to simulate OO environment by using “_” instead of typical “.”
All functions, labels, global variables and menu names are constructed using script’s base name:
FavMenu_myFunction
FavMenu_myLabel
Editor_globalVar
...
This will solve the issue of name collision when merging, since scripts can be created to have unique prefix.
FavMenu GUIs are in separate scripts so I didn’t use FavMenu prefix for them but their own names ( Setup_, Properties_ )
This is done to shorten the length of elements inside the script and to keep focus on OO design but the final merged script will have 3 prefixes instead the one: FavMenu_, Properties_ and Setup_
The best practice is to keep number of global variables as small as possible since they can be named without prefix at all witch leads to code that is easier to read. This is problematic with subroutines witch don’t have local variables so wrapper MUST be created for each subroutine that can’t be avoided, like those for menus:
FavMenu_SetTrayMenu()
{
Menu, tray, add, Setup, FavMenu_trayDispatch
Menu, tray, add, Disable, FavMenu_trayDispatch
Menu, tray, add
Menu, tray, add, About, FavMenu_trayDispatch
Menu, tray, add, Reload, FavMenu_trayDispatch
Menu, tray, add, Exit, FavMenu_trayDispatch
}
;------------------------------------------------------------
FavMenu_TrayHandler()
{
global
local msg
...
}
FavMenu_trayDispatch:
FavMenu_TrayHandler()
Return
To quickly convert subroutine to function do the following:
- replace : with ()
- add new line and write {
- replace return with }
- place global keyword as the first function command
- place Dispatch wrapper bellow.
- make sure none of the function’s variables are global except when really needed.
There is no need to translate subroutines that don’t need local variables like:
FavMenu_MenuHotKey:
FavMenu_CreateFullMenu()
FavMenu_Show()
FavMenu_Destroy()
Return
Since all includes are literally pasted into specified position via #include directive it is the best to put them at the end of the mother script.
However, there might be things inside the child script that should be executed in the autorun section of the parent script.
The rule here is simple:
Do as little as you
must in autorun section. Use Init
function instead.
All commands once in the autorun section, move into the initializer function that should be named SCRIPT_Init().
Furthermore, some things have to be obeyed inside initializer body:
#NoEnv
#SingleInstance force (0)
FAVMENU_Init()
Return
FAVMENU_Init()
{
global
DetectHiddenWindows, on (1)
SetKeyDelay, -1
#include messages.ahk (2)
#include tc_cmnds.ahk
Favmenu_title = TC FavMenu (3)
Favmenu_version = 1.0b8
Favmenu_configFile = Config.ini
...
}
By using this approach, Autorun section will be executed only when script is run as standalone, that is, if FavMenu.ahk is not used within some mother script. When merging, its autorun section will be placed somewhere in the middle of the mother script and will never be executed.
Mother script then should call Init functions of all merged scripts to correctly setup their working environments:
Mother script
FAVMENU_Init()
EDITOR_Init()
Return
...
#include FavMenu
#include Editor
At the end, script can be run as standalone or within mother
script with this simple mechanism.
Sections (0) – (3) exist to solve some problems with directives .
(0) Place here all directives that will influence standalone run but are not appropriate while calling inside other scripts. I typically use here #NoEnv & #SingleInstance. Those directives may change behavior of other scripts so it is the best to keep them private.
(1) Place here all directives that should be executed no matter the calling context.
(2) Since FavMenu is consisting of multiple scripts I put things here that should be run in autorun section.
(3) Initialize your global variables here.
Problem here is in the strange syntax of the GUI command. GUIs don’t have names, but are referenced as numbers. This is obviously a problem in merging if 2 scripts use the same gui number. GUI numbers will also depend on the sequence of Init functions in the mother script.
The first thing that has to be done is to avoid using numbers in GUI definitions but variables. I standardized this variable in my scripts to be named as Window_GUI. Furthermore, I place GUI declaration in Window_Create function:
Content of GUI_Setup.ahk:
Setup_Create()
{
global
Gui, %Setup_GUI%:-sysmenu
Gui, %Setup_GUI%:Add, Text, x75 y10 w156 h17, Total Commander Exe && Ini
Gui, %Setup_GUI%:Add, Edit, vSetup_eTcExe x23 y32 w260 h20
...
}
Then, add 1 parameter to the Init function:
FavMenu.ahk:
FAVMENU_Init( guiStart=0 )
{
global
DetectHiddenWindows, on
SetKeyDelay, -1
...
;set guis
Properties_GUI := guiStart + 1
Setup_GUI := guiStart + 2
return guiStart + 2
}
Using this technique, mother script can call FavMenu & Editor using this approach:
g := FAVMENU_Init(g)
g := EDITOR_Init(g)
When FavMenu script is called standalone its guiStart parameter will default to 1, since in its own autorun section there is only
one function call - call to the Init without parameters - so everything will work correctly.
The problem here emerges with the use of GUI labels, like NGuiClose. Labels can not be created dynamically. So there is no way to set a label like
%Setup_GUI%GuiClose:
...
return
The only solution to this problem is to set those events via the message monitors. This will produce more problems as different scripts
will monitor the same window message with different handlers. The scheme for this will be presented latter in the text.
After next section I will describe the most problematic aspect in AHK script merging – message handlers.
The thing to remember here - this technique will be extended to handle other problems - is that recognition of script context (is it executed within another script or directly) lay in the parameters of the Init function.
The problem: If script A set the tray menu, then script B does so, menu will become merged and all except the last item with the same name will be lost. There are several solutions to this, I am describing 2 of them and I am using second one in my example.
If you want final Tray menu to contain all items from all scripts you are merging you have to check if items with the same name already exist or otherwise you will see only the last one added. AHK doesn’t have menu item identifiers and they are distinguished only by name. So we must check if another script has already added the item we want to add and if that is the case, to change the name of our item, by adding invisible characters as item suffix, thus making its name appear the same but having different binary interpretation. Invisible character is so called pseudo-space, symbol ALT 0160. This is script that adds two items with the same name:
Menu, myMenu, UseErrorLevel, on
Menu, myMenu, add, item1, MenuHandler
Menu, myMenu, rename, item1, item1
if (!ErrorLevel)
menu myMenu,add,item1 ,MenuHandler
Note the pseudo-space after “item1” and before “,” in the last command. In real situations the script should check what is the first available sufix to set since there may be several items with the same name. For each successive item with the same name script should add additional pseudo-space.
Another approach is more elegant and without so much additional code: Init function should receive another argument, witch is the name of the Tray submenu in witch to place scripts own menu. If this argument is empty, script should add its menu items directly, if not it should add its menu items in the Tray submenu supplied.
FAVMENU_Init( guiStart=1, subMenu=”” )
{
Global
...
FavMenu_SetTrayMenu( subMenu )
}
FavMenu_SetTrayMenu( subMenu )
{
menu := subMenu
if subMenu =
{
;set standalone environement
menu := “Tray”
Menu, %menu%, icon, shell32.dll, 44
Menu, %menu%, NoStandard
Menu, %menu%, tip, TC Fav Menu
}
Menu, %menu%, add, ...
}
Using this approach, when script is run standalone it will create its own tray menu with its own icon. If run within another script it will create its items under the submenu witch should be set by the mother script since it is responsible for the Tray:
g := FAVMENU_Init(g, "Application_FavMenu")
EDITOR_Init(g, “Application_Editor”)
Menu, Tray, Add, FavMenu, :Application_FavMenu
Menu, Tray, Add, Editor, :Application_Editor
Menu, Tray, Icon, ...
return
#include FavMenu.ahk
#include Editor.ahk
The problem: If both script A and script B set the message handler for the same windows message, the handler of the first one will be deleted.
In my script-merging technique this situation presents serious problem.
introducing GUI names had a side-effect of replacing GUI events with appropriate message events.
Every GUI must monitor WM_SYSCOMMAND to catch ALT F4 to be able to replace NGuiClose event.
Furthermore, I missed OnShow event, witch is currently not
available in AHK, so I introduced custom (user) windows message witch I call
WM_AHKSHOW, that will be sent to the window itself after all controls are
created and before calling Gui, Show.
Let me show you how I handle this in the FavMenu.
FavMenu consist of 2 GUIs, Properties and Setup. When Favmenu is displayed, the one can edit menu item properties via CTRL ENTER shortcut in the Properties GUI. This window will allow you to edit item’s title, command and icon. Setup GUI is used to create FavMenu environment, that is, all things needed for application to run normally: location of the menu file, location of the file manager, desired hotkeys and so on... This is typical scenario, almost any serious script will have some interface for this, and at least 2 GUIs.
Since Favmenu is large script, I divided it into many
separate scripts, most important I encapsulated both Guis in separate files
called GUI_Properties.ahk and GUI_Setup.ahk.
This is how Properties GUI is created.
Properties_Create()
{
global
Gui, %Properties_GUI%:Add, Text, x16
y5 w80 h16 vProperties_sTitle, Title
Gui, %Properties_GUI%:Add, Edit, x16
y22 w200 h18 vProperties_eTitle
...
...
Gui, %Properties_GUI%:Add, Text, x16
y102 w80 h16, Icon
Gui, %Properties_GUI%:Add, Picture,
x46 y102 w12 h12 vProperties_picIcon
BackgroundTrans AltSubmit
Gui, %Properties_GUI%:Add, Edit, x17 y119
w248 h20 vProperties_eIcon,
Gui,
%Properties_GUI%:Add, Button, x267 y121
w18 h17 gProperties_OnBrowseClickDispatch, ..
Gui, %Properties_GUI%:Add, Button, x16
y155 w104 h19 gProperties_OnSaveClickDispatch, &Save
Gui, %Properties_GUI%:+LastFound
Properties_hwnd := WinExist() + 0
;call start event
DllCall("SendMessage",
"uint", Properties_hwnd, "uint", WM_AHKSHOW,
"uint", 0, "uint", 0)
;show the window
Gui, %Properties_GUI%:Show, x370 y313 h185
w299, Properties
...
}
%Properties_GUI% is set via FavMenus Init function (described in Multiple Guis section).
Then I take its handle using combination of WinExist and LastFound option. Add 0 at the end to force its decimal format instead hex.
Before showing the window, I am sending the message to it, to let it know that it is about to be displayed.
I use this event later to set starting values for the controls in the GUI.
Setup_GUI is created exactly the same way.
Connecting messages to GUIs is done in messages.ahk & msg_dispatch.ahk
Content of messages.ahk
WM_SYSCOMMAND = 0x112
WM_KEYDOWN = 0x100
WM_APP = 0x8000
WM_AHKSHOW := WM_APP + 0x100
SC_CLOSE = 0xF060
OnMessage(
WM_AHKSHOW, "FavMenu_MessageMonitor")
OnMessage(
WM_SYSCOMMAND, "FavMenu_MessageMonitor")
OnMessage(
WM_KEYDOWN, "FavMenu_MessageMonitor")
Since above commands need to be executed in the autorun section of the FavMenu witch is redirected to the Favmenu_Init(..) function, I include above file in this function.
FAVMENU_Init( ...
)
{
global
DetectHiddenWindows, on
SetKeyDelay, -1
#include includes\messages.ahk
...
This will enable message handlers on the startup of the FavMenu.
Now, since all messages are received by the single function, FavMenu_MessageMonitor witch resides in msg_dispatch.ahk script, I needed a mechanism to transmit the messages to appropriate GUIs.
That is, if Properties GUI received a message, or its control, call its message handler, or do the same for the Setup GUI. This is the code that does this:
FavMenu_MessageMonitor(wparam,
lparam, msg, hwnd)
{
global Properties_hwnd, Setup_hwnd
h := DllCall("GetParent",
"uint", hwnd)
if h = 0
h := hwnd
if (h = Properties_hwnd)
Properties_Monitor(wparam, lparam, msg)
if (h = Setup_hwnd)
Setup_Monitor(wparam, lparam, msg)
}
;-----------------------------------------------------------------------------------
Properties_Monitor(wparam,
lparam, msg)
{
global
if (msg = WM_KEYDOWN)
return Properties_OnKeyDown(wparam,
lparam)
if (msg = WM_AHKSHOW)
return Properties_Show()
if (msg = WM_SYSCOMMAND and wparam =
SC_CLOSE)
return Properties_Close()
}
;-----------------------------------------------------------------------------------
Setup_Monitor(wparam,
lparam, msg)
{
global
if (msg = WM_KEYDOWN)
return Setup_OnKeyDown(wparam,
lparam)
if (msg = WM_AHKSHOW)
return Setup_Show()
if (msg = WM_SYSCOMMAND and wparam =
SC_CLOSE)
return Setup_Close()
}
This kind of setup will make us
create black boxes for each Gui. Controls are set on GUI_Show() event, windows
are destroyed or hidden in GUI_Close() event.
The same is done in the other
standalone script, Editor.ahk, but
directly without includes.
EDITOR_Init( ...
)
{
global
OnMessage(0x0112, "Editor_MessageHandler") ; WM_SYSCOMMAND
OnMessage(0x0100, "Editor_MessageHandler") ; WM_KEYDOWN
...
Now, both scripts will act fine when called standalone, but, when merged, the latest included script will erase message handlers (if set) for all previous GUIs. Very bad.
OnMessage( msgNum, handler ) AHK command when called without second parameter will return current message handler for the message number at the first parameter. You might think that we can save this function in the variable to be able to use it this way:
oldMessageHandler :=
OnMessage(WM_KEYDOWN)
OnMessage(WM_KEYDOWN, OnKeyDown)
...
OnKeyDown(wparam, lparam)
{
; do my stuf here
; at the end, call original handler
%oldMessageHandler%(wparam, lparam)
}
Unfortunately, this isn’t possible in AHK in the current version.
Fortunately, we can use subroutines that can be called from a variable via GoSub %label% command.
I will introduce a new function here that I will use to achieve above code:
Application_AddMessageHandler (msg, label)
Msg
- Message to monitor
Label – Subroutine that will receive the message.
Parameters will be transmited via global variables
Application_mMsg, Application_mHwnd, Application_mWparam, Application_mLparam
Content of the file _Application.ahk
Application_AddMessageHandler(msg, label)
{
global
if !Application_aFlag_%label%
{
Application_hCount += 1
Application_aMsgHandlers_%Application_hCount%
:= label
Application_aFlag_%label%
:= true
}
OnMessage(msg,
"Application_MessageMonitor")
}
;-----------------------------------------------------------------------
Application_MessageMonitor(wparam, lparam, msg, hwnd)
{
global
local label
Application_mMsg := msg
Application_mHwnd := hwnd
Application_mWparam := wparam
Application_mLparam := lparam
{
GoSub % Application_aMsgHandlers_%A_Index%
}
}
With this, we will replace OnMessage(..) calls with Application_AddMessageHandler calls.
Every script should include _Application.ahk at the end of its inserts. In merged environment this will include this script only once. Furthermore, child scripts must have only one message monitor that will dispatch messages to its own windows. It is important to save the handles of the Guis to be able to recognize if one particular received message is for your GUI or some other in the merged script. That’s why I use this kind of code in the Editor and FavMenu:
Editor_MessageHandler(...)
global Editor_hwnd
h := DllCall("GetParent",
"uint", hwnd)
if h = 0
h := hwnd + 0
if (h != Editor_hwnd)
return
...
So what we have here are two different situations:
1. If the child script is run standalone, Application will be just another dispatcher and will dispatch all messages to the script’s actual message handler.
2. Child scripts that run at the same time in the merged environment will all have their messages dispatched witch was the point of this construction.
In order to distinguish exactly if the script is run standalone or inside other script, another and the last parameter to the Init(…) function must be added.
EDITOR_Init( lastGUI=0, subMenu="", bStandalone=true )
{
Application_AddMessageHandler(
0x0112,
"Editor_MessageHandlerDispatch")
Application_AddMessageHandler(
0x0100,
"Editor_MessageHandlerDispatch")
Editor_GUI := lastGui + 1
Editor_standalone := bStandalone
...
...
return lastGui + 1
}
Now we are able to omit some interface details that are not needed in merged environment.
For instance, if Editor is run standalone it will exit application on ALT F4. But when running as a part of a group of scripts, it will choose to destroy its GUI and free the resources occupied by internal arrays used for the interface elements:
Editor_Close()
{
global
if (Editor_Standalone)
Editor_Exit()
else
{
Editor_mnuCnt := 0
Gui %Editor_GUI%:Destroy
;clean up arrays here
}
}
FavMenu chooses to skip Exit and Reload items in the tray menu and not to set the tray icon.
if (Favmenu_standalone)
{
FavMenu_trayMenu := "Tray"
Menu, %FavMenu_trayMenu%, icon,
shell32.dll, 44
Menu, %FavMenu_trayMenu%, NoStandard
Menu, %FavMenu_trayMenu%, Tip,
FavMenu
}
Menu, %FavMenu_trayMenu%, add, Setup, FavMenu_trayDispatch
Menu, %FavMenu_trayMenu%, add, Disable, FavMenu_trayDispatch
Menu, %FavMenu_trayMenu%, add
Menu, %FavMenu_trayMenu%, add, About, FavMenu_trayDispatch
if (Favmenu_standalone)
{
Menu, %FavMenu_trayMenu%, add,
Reload, FavMenu_trayDispatch
Menu, %FavMenu_trayMenu%, add, Exit, FavMenu_trayDispatch
}
We did both scripts to support mergining. Now its time to merge them.
Look at this beauty:
Content of PARENT.ahk:
g := 1
g := FAVMENU_Init(g,
"TCFavMenu", false)
g := EDITOR_Init(g, "Editor",
false)
SetTrayMenu()
return
;----------------------------------------------------------------------------
+p::
Editor_Run()
return
+e::
ExitApp
return
;----------------------------------------------------------------------------
trayHandler:
if (A_ThisMenuItem = "Exit")
ExitApp
if (A_ThisMenuItem =
"Editor")
Editor_Run()
if (A_ThisMenuItem = "Show FavMenu")
{
t := Favmenu_Options_MenuPos
Favmenu_Options_MenuPos := 1 ;
set mouse position
FavMenu_Create()
Favmenu_Options_MenuPos := t ; restore old position
}
return
;----------------------------------------------------------------------------
SetTrayMenu()
{
Menu,
Tray, nostandard
Menu, Tray, Icon, shell32.dll, 42
Menu,
Tray, Add, FavMenu, :FavMenu
Menu, Tray, Add,
Menu,
Tray, Add, Show FavMenu, trayHandler
Menu, Tray, Add, Editor, trayHandler
Menu, Tray, Add,
Menu, Tray, Add, Exit, trayHandler
}
;----------------------------------------------------------------------------
#include
Favmenu.ahk
#include
Editor.ahk
#include
_APPLICATION.ahk
The Parent is exactly 38 lines long.
The difference is that only one AutoHotKey instance is running, not 2. On every next standalone script this will become more and more pronounced.
Another beauty of this system is that the Parent here is actually acting as a plug-in container and child scripts as plugins. Parent can create its own interface for merged environment, it can even enable/disable plugins. Furthermore, nothing limits the level of depth in merging so you can create packs that will be included in other more general packs and so on…
Imagine: one system utilities pack, one keyboard remapper pack and one general pack all united in single parent that can control them all through “published” interface of its children. By “published interface” I mean about scripts functions and options that are to be exposed to be used in scripts control. That will depend on desired level of interaction. In this model, we have this available for parent:
EDITOR_Init(...) ;must
FAVMENU_Init(...) ;must
FavMenu_Create() ;show the favmenu
Favmenu_Options_MenuPos ;menu position: 1 = mouse, 2 = cursor, 3 = center
Editor_Run() ;run the editor
The only note is that the script can not run anything continuous since it will be interrupted by other scripts if they are activated. One script can run in merged scenario without other scripts interfering, continuously, without any problems.
This is not possible because AHK does not support multiple threads of execution.
Favmenu & Editor can be run as standalone or merged. Start them standalone first to see how they act when they are called normally. I created some simple menu in menu.ini and set the hotkey for the Favmenu to be Left Windows. Notice the yellow star icon in the tray for the Favmenu and regular AHK icon in the title of the Editor’s GUI. You can see 2 A