Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[module] MMenu 1.0 b1


  • Please log in to reply
126 replies to this topic
toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005

why sleep in loop doesn't work, as it would make ideal solution.

That's what I think too. It would simplify the code a lot.
Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
I was sure it was cuz of critical somewhere in your code or in MMenu code, as sleep -1 doesn't work when critical is on. But there is no any...

I also checked your entire code carefully and I put some debugining tooltips in mmenu here and there and I didn't find a reason why sleep doesn't break the loop and let the menu update....

So, it seems that only Chris can help.

So, the formulation of question is: why the sleep in the code bellow doesn't put the loop on hold:

critical, off
    Loop, %Path%\*, 1, 1
    {
[color=blue]		sleep 50; -1, 100, 1000[/color]
        If A_LoopFileAttrib contains D
          {
            NumDirs++
            If !Mod(NumDirs, 20)
                MMenu_Set(%menu%, Type "Folder", NumDirs)
        }Else{
            NumFiles++
            Size += A_LoopFileSize    ;in bytes
            If !Mod(NumFiles, 100)
                MMenu_Set(%menu%, Type "Files", NumFiles)
          }
        LastModTime := LastModTime > A_LoopFileTimeModified ? LastModTime : A_LoopFileTimeModified
        If StopToGatherData
            Return
     }

Posted Image

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
I didn't carefully study this, but it might be due to the fact that once a thread has been interrupted by another thread, it doesn't execute at all until the top thread finishes. In other words, Sleep cannot return control to a dormant/underlying thread. However, Sleep does give new threads an opportunity launch.

In light of this, scripts that need delicate asynchronous behavior should use one or more timers, each of which is designed to finish quickly. Such timers can use Critical to avoid any chance of being interrupted.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Chris
We already succesifully used timer to break the loop like this (with some pseudo code)


DummyTimer: 
     StartToGatherData("P", A_ProgramFiles, CONTINUE=TRUE) 
return

...

Function( bContinue=?)
{
 ...
 Loop, %Path%\*, 1, 1 
      { 
       
      if !Mod(A_Index, 50) 
      { 
         SetTimer, DummyTimer, 50 
         return 
      }


I.E. on every 50 files here, loop will be breaken for 50ms to allow menu to update. This works fine.

Let me try to examine this according to what you said.

0. MMenu_Show is called and this function runs during all time menu is active.

1. Timer is activated by INIT event:
MMenu_Show( main, A_SCreenWidth/2-100, A_ScreenHeight/2-100, "OnMMenu", "[color=green]IOnInit UOnUninit[/color]" )
... 

OnInit:
  If (M_Menu = Prog){
      StopToGatherData := False
       SetTimer, StartProg, 10
  }
Return

StartProg timer routine starts routine StartToGatherData("P", A_ProgramFiles) after 10 ms to allow INIT function to exit.

2. After INIT is fired above, it will set the timer and exit. Before that moment MMenu submenu is not displayed and we have 2 threads: Show and OnInit. After OnInit enables timer it returns after what MMenu displays the submenu and waits idle still in Show function.

3. After 10ms timer fires and StartToGatherData is called. At that moment script is idle in the Show function waiting for the timer to fire. When it does, timer func becomes TOP thread, and Show is put behind.

4. Function comes to the loop which update 2 submenu items out of total 6, on each iteration. This will launch new threads of MMenu (OnDraw, OnMeasure) which execute very fast (lets say <20ms). So if I put sleep 300 at the start of the loop, after 2*2*20ms all MMenu threads finished already and if OnDraw and OnMeasure are called for all items in the submenu 6*40=240ms so script is idle 80ms for sure and probably much more.

So, while the script is idle, we have 2 active threads, StartToGatherData on TOP which was executed by timer and MMenu_Show just beneath it. So, as I see it, there is no other TOP thread abot 100ms after sleep 300 executes, hence, it should be interupted by new threads launched at that time.

OK, now while loop is in progress user moves mouse around. OnSelect event fires. When it fires, we are in the loop at some position, not necesseraly idle. OnSelect can fire while MMenu is doing OnDraw/OnMeasure to update the item or it can fire when we are sleeping. If it fires while there are MMenu threads running Sleep can not break the loop as it has thread above it. I wonder if WM_MENUSELECT that Select MMenu event is monitoring is buffered while MMenu is executing updater threads.
If it fires while Sleep is waiting and all MMenu threads are finished, the loop should be interupted as I see it, as there are no other threads running at the moment.

Huh...

----------------
EDIT: OnMeasure and OnDraw are not necessary for non-icon menus, so I removed OnMessage events for them for toralf's script:

;oldMeasure	:=  OnMessage(WM_MEASUREITEM,	"MMenu_OnMeasure")
;oldDraw		:=  OnMessage(WM_DRAWITEM,		"MMenu_OnDraw")

After that no MMenu thread interupts timer function, and all text updates of the menu are handled by OS.

So, what is the problem here Chris ?
Posted Image

toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
In the mean time I coded a small little function that does do the update with timers. It is very efficient. I'll post it soon.
Thanks for looking into it. If you find a robust solution I have no problem to trash my function.
Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
If it works fine, that it must be good.

Its really not trivial to deduce what happened here, as you can see in anylise above. If I ever realise what happens here, I will be glad to inform you :)
Posted Image

toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
Here is the MMenu_UpdateItem function.

First the example script:
#SingleInstance force
#NoEnv

	main := MMenu_Create()
	prog := MMenu_Create()
	wind := MMenu_Create()
	regi := MMenu_Create()

	MMenu_Add( main, "Move on a item","", "", "d iDoNothing")
	MMenu_Add( main, "Programs", "", "", "m" prog)
	MMenu_Add( main, "Windows",  "", "", "m" wind)
	MMenu_Add( main, "Registry", "", "", "m" regi)

  MMenu_Add(prog, "Please wait!",  "", "", "d iPInfo")
  MMenu_Add(prog, "Path",          "", "", "iDoNothing")
  MMenu_Add(prog, "# of folders",  "", "", "iDoNothing")
  MMenu_Add(prog, "# of files",    "", "", "iDoNothing")
  MMenu_Add(prog, "Last modified", "", "", "iDoNothing")
  MMenu_Add(prog, "Sum file size", "", "", "iDoNothing")

  MMenu_Add(prog, "Result", "", "", "b| d iPDoNothing")
  MMenu_Add(prog, "x",      "", "", "iPPath")
  MMenu_Add(prog, "x",      "", "", "iPFolder")
  MMenu_Add(prog, "x",      "", "", "iPFiles")
  MMenu_Add(prog, "x",      "", "", "iPLastMod")
  MMenu_Add(prog, "x",      "", "", "iPSize")

  MMenu_Add(wind, "Please wait!",  "", "", "d iWInfo")
  MMenu_Add(wind, "Path",          "", "", "iDoNothing")
  MMenu_Add(wind, "# of folders",  "", "", "iDoNothing")
  MMenu_Add(wind, "# of files",    "", "", "iDoNothing")
  MMenu_Add(wind, "Last modified", "", "", "iDoNothing")
  MMenu_Add(wind, "Sum file size", "", "", "iDoNothing")

  MMenu_Add(wind, "Result", "", "", "b| d iDoNothing")
  MMenu_Add(wind, "x",      "", "", "iWPath")
  MMenu_Add(wind, "x",      "", "", "iWFolder")
  MMenu_Add(wind, "x",      "", "", "iWFiles")
  MMenu_Add(wind, "x",      "", "", "iWLastMod")
  MMenu_Add(wind, "x",      "", "", "iWSize")

  MMenu_Add(regi, "Please wait!",  "", "", "d iRInfo")
  MMenu_Add(regi, "Path",          "", "", "iDoNothing")
  MMenu_Add(regi, "# of keys",  "", "", "iDoNothing")
  MMenu_Add(regi, "# of values",    "", "", "iDoNothing")
  MMenu_Add(regi, "Last modified", "", "", "iDoNothing")

  MMenu_Add(regi, "Result", "", "", "b| d iDoNothing")
  MMenu_Add(regi, "x",      "", "", "iRPath")
  MMenu_Add(regi, "x",      "", "", "iRKeys")
  MMenu_Add(regi, "x",      "", "", "iRValues")
  MMenu_Add(regi, "x",      "", "", "iRLastMod")

  PathsToBeSearched =                      ;collect the root paths
    (LTrim
      Folder|%A_ProgramFiles%|%prog%|P
      Folder|%A_WINDIR%|%wind%|W
      Registry|HKCU\Software\Microsoft\Windows NT\CurrentVersion|%regi%|R
    )
  MMenu_UpdateItems(PathsToBeSearched)     ;start to search the root paths and update the menus
  
	;show menu
  MMenu_Show( main, A_SCreenWidth/2-100, A_ScreenHeight/2-100, "OnMMenuSelection", "UOnUInit")
	ExitApp
return

#include includes
#include MMenu_UpdateItems.ahk
#include MMenu.ahk
#include structs.ahk

OnUInit:
  If (MMenu = main)   ;when main menu closes, stop search (if not finished yet).
      MMenu_UpdateItems("Stop")
return

OnMMenuSelection:
return

And this is the function that can be included (after the autoexec section)
Script = MMenu_UpdateItems
Version = 1.0

msgbox, 16, Wrongly included, This script "%Script%" is wrongly included.`n Since it contains a subroutine with a return, it should be included after the auto-exec section.`nIf done correctly this Msgbox will not pop up.
Return

; update menu items with info on paths
; item menu names must have a common name (ID) and end with "Info/Path/Folder/Keys/Files/Values/LastMod/Size"
; to start the gathering of data specify a string with the data to collect
; to stop the gathering, specify "S[top]" as the parameter
;
; the string with the data to collect is a list of root folders or keys for which data should be put into menu
; needed data for each root folder/key are: "type | path | menu # | menu item names"
;       type                either F[older] or R[egistry]
;       path                the root path (folder or key) from which the search starts
;       menu #              menu number the fields are in
;       menu item names     the common beginning of the menu item IDs
;                             - their individual ID ends with:
;                                   Info
;                                   Path
;                                   Folder Or Keys
;                                   Files  Or Values
;                                   LastMod
;                                   Size   (not available for registry paths)
; Nothing happens in the case that idividual items do not exist in the menu. Thus you can leave some fields out.
; you need to have permission to read the folders or keys to get the data
; as a limited user you might not be able to get the last modified date for registry keys even though that you can read them
MMenu_UpdateItems(String){
    global MMenu_UpdateItems_Stop
    If (InStr(String,"s")=1) {            ;check if the search should be stopped
        MMenu_UpdateItems_Stop := True        ;set status var
        Return
      }
    MMenu_UpdateItems_Stop := False       ;set status var
    
    ;set the list of root folders for which data should be put into menu
    MMenu_UpdateItems_NextFolder(String)
    
    ;start extra thread to gather data for root folders and fill it into menu while it is shown
    SetTimer, MMenu_UpdateItems, On
  }

;get the number of folders and files/keys and values for a given path and put into menu
MMenu_UpdateItems:
  SetTimer, MMenu_UpdateItems, Off         ;stop timer, it will restart itself when needed

  If MMenu_UpdateItems_Stop                ;stop depending on status var, e.g. when menu closed
      Return 

  If ! MMenu_UpdateItems {                 ;if there is currently no root folder that needs to be looked at
      If ! MMenu_UpdateItems := MMenu_UpdateItems_NextFolder()   ;get next root folder with data
          Return                                                      ;when there is no root folder left stop the gathering of data
      StringSplit, MMenu_UpdateItems, MMenu_UpdateItems, |       ;split root folder/key data into components
      MMenu_UpdateItems_Type := MMenu_UpdateItems1               ;root path type
      MMenu_UpdateItems      := MMenu_UpdateItems2               ;root path
      MMenu_UpdateItems_Menu := MMenu_UpdateItems3               ;menu number
      MMenu_UpdateItems_Name := MMenu_UpdateItems4               ;menu item names
      ;fill menu with root path
      MMenu_Set(MMenu_UpdateItems_Menu, MMenu_UpdateItems_Name "Path", MMenu_UpdateItems)                    
      ;inform user by changing the menu title
      MMenu_Set(MMenu_UpdateItems_Menu, MMenu_UpdateItems_Name "Info", "Please wait!")
; FileAppend, %MMenu_UpdateItems%`n, Filename.txt
      ;reset data and gather from root path
      If (InStr(MMenu_UpdateItems_Type,"f")=1)
          MMenu_UpdateItems := MMenu_UpdateItems_Folder(MMenu_UpdateItems "`n",MMenu_UpdateItems_Menu,MMenu_UpdateItems_Name,"Reset")
      Else
          MMenu_UpdateItems := MMenu_UpdateItems_Registry(MMenu_UpdateItems "`n",MMenu_UpdateItems_Menu,MMenu_UpdateItems_Name,"Reset")
  }Else
      ;gather data for folders in paths
      If (InStr(MMenu_UpdateItems_Type,"f")=1)
          MMenu_UpdateItems := MMenu_UpdateItems_Folder(MMenu_UpdateItems,MMenu_UpdateItems_Menu,MMenu_UpdateItems_Name)
      Else
          MMenu_UpdateItems := MMenu_UpdateItems_Registry(MMenu_UpdateItems,MMenu_UpdateItems_Menu,MMenu_UpdateItems_Name)

; FileAppend, %MMenu_UpdateItems%`n, Filename.txt

  If MMenu_UpdateItems                   ;if there are still folders to be looked at
      SetTimer, MMenu_UpdateItems, 250      ;start itself in an extra thread to look for these folders
  Else{                                  ;otherwise
      MMenu_Set(MMenu_UpdateItems_Menu, MMenu_UpdateItems_Name "Info", "Details:")  ;inform user by changing the menu title
      SetTimer, MMenu_UpdateItems, On                  ;start itself again for next root folder
    }
Return

;get the number of folders and files for a given path
; it stops working after a while to let a menu update
; optimized for speed and responsiveness of the menu
MMenu_UpdateItems_Folder(FolderList,menu,name,Reset = 0){
    Static NumFolder,NumFiles,Size,LastModTime,LastFolder

    SetBatchLines, -1        ;speed!

    If Reset {               ;reset, e.g. a new folder path gets searched
        LastModTime = 0
        NumFolder   = 0
        NumFiles    = 0
        Size        = 0
        LastFolder  =
      }
  
    Loop, Parse, FolderList, `n    ;go through list of folders
      {
        If (A_Index > 75)          ;after 75 folders got searched, stop (will be resumed from here with next call)
            Break
        If A_LoopField is space    ;don't search pure `n
            Continue
        DoneFolder .= A_LoopField "`n"     ;remember folders that have been searched
        If (LastFolder = A_LoopField)      ;never scan a folder twice (can happen when there was an error during reading the first time)
            Continue
        LastFolder = %A_LoopField%
        Loop, %A_LoopField%\*, 1, 0        ;get subfolders and files in that folder
          {
            If InStr(A_LoopFileAttrib, "D") {     ;count folders and remember their subfolders to be searched
                NumFolder++
                FolderList .= A_LoopFileLongPath "`n" 
            }Else{                                ;count files, add file size and get latest mod time
                NumFiles++
                Size += A_LoopFileSize
              }
            LastModTime := LastModTime > A_LoopFileTimeModified ? LastModTime : A_LoopFileTimeModified
          }
      }
    StringReplace, FolderList, FolderList, %DoneFolder%     ;remove already searched folders from list
    MMenu_Set(menu, name "Folder",  NumFolder)              ;set until-now gathered data into menu fields
    MMenu_Set(menu, name "Files",   NumFiles)
    MMenu_Set(menu, name "Size",    MMenu_UpdateItems_HumanReadableSize(Size))
    MMenu_Set(menu, name "LastMod", MMenu_UpdateItems_HumanReadableDate(LastModTime))
    Return FolderList                                       ;return still to be searched list of folders
  }
MMenu_UpdateItems_Registry(KeyList,menu,name,Reset = 0){
    Static NumKeys,NumValues,LastModTime,Lastkey

    SetBatchLines, -1        ;speed!

    If Reset {               ;reset, e.g. a new registry path gets searched
        LastModTime = 0
        NumKeys     = 0
        NumValues   = 0
        Lastkey     =
      }

    Loop, Parse, KeyList, `n    ;go through list of keys
      {
        If (A_Index > 75)          ;after 75 keys got searched, stop (will be resumed from here with next call)
            Break
        If A_LoopField is space    ;don't search pure `n
            Continue
        DoneKeys .= A_LoopField "`n"     ;remember keys that have been searched
        If (Lastkey = A_LoopField)      ;never scan a key twice (can happen when there was an error during reading the first time)
            Continue
        Lastkey = %A_LoopField%
        StringLeft, RootKey, A_LoopField, InStr(A_LoopField,"\") - 1    ;get root key and key
        StringTrimLeft, Key, A_LoopField, InStr(A_LoopField,"\")
        Loop, %RootKey%, %Key%, 1, 0            ;get subkeys and values in that key
          {
            If InStr(A_LoopRegType, "Key") {    ;count subkeys and remember their full key path to be searched
                NumKeys++
                KeyList .=  A_LoopRegKey "\" A_LoopRegSubKey "\" A_LoopRegName "`n"
                LastModTime := LastModTime > A_LoopRegTimeModified ? LastModTime : A_LoopRegTimeModified
            }Else{                              ;count values and get latest mod time
                NumValues++
              }
          }
      }
    StringReplace, KeyList, KeyList, %DoneKeys%     ;remove already searched keys from list
    MMenu_Set(menu, name "Keys",    NumKeys)          ;set until-now gathered data into menu fields
    MMenu_Set(menu, name "Values",  NumValues)
    MMenu_Set(menu, name "LastMod", MMenu_UpdateItems_HumanReadableDate(LastModTime))
    Return KeyList                                  ;return still to be searched list of keys
  }

;convert file size in bytes into Gb/Mb/Kb and concatenate unit
MMenu_UpdateItems_HumanReadableSize(Size){
    If (Size > 1024 * 1024 * 1024)
        Size := Round(Size / (1024 * 1024 * 1024),2) " Gb"
    Else If (Size > 1024 * 1024)
        Size := Round(Size / (1024 * 1024),2) " Mb"
    Else If (Size > 1024)
        Size := Round(Size / 1024,2) " Kb"
    Else
        Size .= " b"
    Return Size
  }

;convert a date string into a shortdate
MMenu_UpdateItems_HumanReadableDate(Date){
    FormatTime, Date, %Date%, ShortDate
    Return Date
  }

;Return the next topmost item in a `n separated list and remove it from the list
;specify SetAsNewList to set a new list
MMenu_UpdateItems_NextFolder(SetAsNewList = ""){
    Static List
    If SetAsNewList {
        List = %SetAsNewList%
        Return
      }
    StringSplit, Item, List, `n 
    List := RegExReplace(List,"\Q" Item1 "\E\n?", "", "", 1, 1)
    Return Item1
  }

Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
I would like to have a MMenu_Get function, to retrieve the content of a menu item. This is what I use currently. But I was hoping that you could create a robust function and include it into your distro:
MMenu_Get( pMenu, pItem){

	local hMenu := MMenu_aMenu[%pMenu%]

	local idx := MMenu_getItemIdx( pMenu, pItem )



	if !idx				;if invalid item

		return 0



	if (hMenu = "")

		return 0



  Return MMenu_aItem[%Idx%]_title

}

What is the reason you use the title array in your code? Couldn't you use the DllCall("GetMenuString")? Or would that be too slow? Just curious. Thanks for the MMenu function.
Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Hej toralf.

First, thx for the updater function. It seems to work nice here, it eats keyboard movement but that is expected. Also, windows and registry were populated after Program Files, but I guess that is by design. If so, you could stop enumeration of submenu when it is closed and continue with new one that is open then return to the first one..... etc...



I would like to have a MMenu_Get function,

The absence of the get function is the reason for beta 1. I planeed it for final release.

What is the reason you use the title array in your code

Title array will be removed for the final. No special reason excpet I didn't completely understand Menu SDK at the beginning.

Thx for your work.
Posted Image

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004

on every 50 files here, loop will be breaken for 50ms to allow menu to update. This works fine.
...
Let me try to examine this according to what you said.
...
I wonder if WM_MENUSELECT that Select MMenu event is monitoring is buffered while MMenu is executing updater threads.
If it fires while Sleep is waiting and all MMenu threads are finished, the loop should be interupted as I see it, as there are no other threads running at the moment.
...
So, what is the problem here Chris ?

I don't understand this particular script enough to say. However, I can say that if an OnMessage thread is interrupted -- or takes longer than 20ms to finish -- that particular message number might be lost (unmonitored) if it arrives during that time. So generally, it's best to use Critical to avoid interruptions in these situations.

If you conclude that there's a bug in AutoHotkey, I think the only way I'd want to analyze it is via a much simpler script, preferably one that doesn't use the WinAPI at all.

toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
May I suggest to change the global output vars to MMenu_ID, MMenu_Menu and MMenu_Title? This would then be more consistent to the module idea.
Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
I put it like that as I wonted it to be similar to AHK variables, like A_MenuItem. But perhaps I should do as you say...
Posted Image

toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
Hi maj,

Is it possible to clean an MMenu without destroying it? I do not know the number of items in the MMenu or their titles so I do not want to remove? Just clean the MMenu and fill it again. Kind or "RemoveAll".

Is this already possible? Could I otherwise ask you to build this functionality, so I can use it for LilBuilder? Thanks a lot.

Edit:
I do not want to destoy it, because it is a submenu.
Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
I understand. You put menu as submenu and you don't want to destroy it as it will change its handle so you would have to tweak its parent menu also. With remove all you keep handle and emtpy the menu.


I will see to create this for you.

I didn't do much on polishing MMenu as I am still thinking how to fix dynamic menu iterative slowdowns....

One thing will be there for shure. Item ID is unique across all menus, so I will drop menu parameter for it (that is, it will be dummy). This way to remove an item from the menu you will just have to know its ID, not its menu handle.

Other then that lot of cleaning is required. Some arrays are not needed etc..
Posted Image

toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
Could you add the functionality before doing the clean up?
Need it badly.
Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.