Anyway, I did it. That was easy, thanks to you.
The script should now run fine under XP (as well as Vista and Win7), although I have tested it only under Win7 x64.
Note that it doesn't work well (the icons are missing) if it runs under AHK_L x64, or if it is compiled with AHK_L x64, even under an x64 platform. But it works fine under my Win7 x64 when using the 32bit version of AHK_L. I haven't enough time to search for the problem right now.
I have also added 2 new parameters in the INI file: the folder location of the "Recent" items, and the folder of the recent apps maintained by Anvir task manager. I did it because there is no hardcoded variable pointing to the Recent folder (something like %A_Recent%), and of course, the same problem exists for the Anvir folder. So, if you compile the script and it cannot find the right folder(s) (the menus are empty), you just have to edit the INI file.
Anyway, here is RecentMenu.ahk v1.1:
Code:
#NoTrayIcon
#NoEnv
#SingleInstance force
#UseHook off
#Persistent
; Recent Menu v1.1 by r0lZ (January 2011)
; AutoHotkey script Developed with AHK_L v1.0.48.05.L51 x32 Unicode (W) version, under Windows 7 x64.
; The script MUST use the x32 version of AutoHotkey_L, or no icons will be displayed in the menus.
;
; Version history:
; V1.0:
; - First release, compatible only with Vista and Win7
; V1.1:
; - The original file has been modified by DataLife to adapt it to Windows XP.
; See http://www.autohotkey.com/forum/viewtopic.php?t=65704&postdays=0&postorder=asc&start=73
; The original Vista/7 version v1.0 and the modified version for XP have been merged by r0lZ
; to form v1.1, that should be compatible with Vista, Win 7 and XP.
; - Added two options in the INI file to specify the "Recent" and the Anvir "Tray" folder locations,
; necessary only if the script cannot find the folders automatically.
;
; Purpose:
; Displays a popup menu with the recently used documents and folders as sub-menus.
; If Anvir Task Manager is installed and running, it displays also a sub menu with the recently executed programs.
; Additionally, a menu item is added in each sub-menu to clear the history.
;
; The script accepts the following arguments:
; /trayicon=1 ; stays resident in the tray. Default: 0 (Advantage of tray mode: much more rapid to display the menu)
; /iconsize=32 ; use large icons in menus. Default: 16 (Other values between 16 and 64 accepted but not recommended)
; /maxitems=30 ; Maximum number of items in each menu. Default: 20 (The most recent items are always on top of the menus)
; /folderfullpath=1 ; Display the full path in the Folders menu. Default: 0 = folder name only.
;
; The same parameters can be specified in an INI file. The INI file MUST be "%appdata%\Recent Menu.ini",
; "%appdata%\Recent Menu\Recent Menu.ini" or "<same folder as application>\Recent Menu.ini".
; The INI file must have the following structure:
; [Config]
; TrayIcon=0
; IconSize=16
; MaxItems=20
; FolderFullPath=0
; ; Additional INI constants, added in v1.1: (Leave them blank or commented out if the program works fine)
; RecentFolder=
; AnVirRecentAppsFolder=
; defaults for the user modifiable constants
trayicon = 0 ; boolean: stay resident in the tray?
iconsize = 16 ; size of the icons in the menu: 16 or 13 (other sizes accepted but not recommended)
maxitems = 20 ; maximum number of items in the menu (not including the "Clear MRU History!" item)
folderfullpath = 0 ; boolean: display the full path of the folder items?
if A_OSVersion in WIN_XP,WIN_2000,WIN_2003,WIN_NT4,WIN_95,WIN_98,WIN_ME
xp = 1
else
xp = 0
SplitPath, A_AppData, , realappdata
if (xp) {
Splitpath, A_StartMenu, , recent
recent = %recent%\Recent
recentapps = %realappdata%\Local Settings\Application Data\AnVir\Tray
} else {
recent = %A_AppData%\Microsoft\Windows\Recent
recentapps = %realappdata%\Local\AnVir\Tray
}
; Read INI file if it exists in %A_AppData%\Recent Menu\Recent Menu.ini,
; %A_AppData%\Recent Menu.ini, or in <script directory>\Recent Menu.ini
ini = %A_AppData%\Recent Menu\Recent Menu.ini
if (NOT FileExist(ini))
ini = %A_AppData%\Recent Menu.ini
if (NOT FileExist(ini))
ini = %A_ScriptDir%\Recent Menu.ini
IniRead, trayicon, %ini%, Config, TrayIcon, %trayicon%
IniRead, iconsize, %ini%, Config, IconSize, %iconsize%
IniRead, maxitems, %ini%, Config, MaxItems, %maxitems%
IniRead, folderfullpath, %ini%, Config, FolderFullPath, %folderfullpath%
IniRead, tmp_recent, %ini%, Config, RecentFolder
IniRead, tmp_recentapps, %ini%, Config, AnVirRecentAppsFolder
if (tmp_recent != "" && tmp_recent != "ERROR")
recent = %tmp_recent%
if (tmp_recentapps != "" && tmp_recentapps != "ERROR")
recentapps = %tmp_recentapps%
; overwrite defaults if arguments are specified via the command line
Loop, %0% ; For each parameter:
{
param := %A_Index% ; Fetch the contents of the variable whose name is contained in A_Index.
firstchar = SubStr(param,1,1)
if (firstchar == "/" || firstchar == "-")
param := SubStr(param, 2)
idx := InStr(param,"=")
if (idx == 0)
continue
var = SubStr(param, 1, idx-1)
val = SubStr(param, idx+1)
if val is integer
{
if (var == "trayicon" && (val == 0 || val == 1))
trayicon := val
if (var == "iconsize" && val >= 16 && val <= 64)
iconsize := val
if (var == "maxitems" && val >= 1)
maxitems := val
if (var == "folderfullpath" && (val == 0 || val == 1))
folderfullpath := val
}
}
; Main job begins here
IconsArray := Object()
; Stay resident in the tray if requested
if (trayicon)
{
Menu, Tray, NoStandard
Menu, Tray, Add, MRU menu, MRUMenu
Menu, Tray, Add
Menu, Tray, Add, Exit, Exit
Menu, Tray, Default, MRU menu
Menu, Tray, Click, 1
if (xp) {
Menu, Tray, Icon, MRU menu, %A_WinDir%\system32\shell32.dll, 214
Menu, Tray, Icon, Exit, %A_WinDir%\system32\shell32.dll, 110
Menu, Tray, Icon, %A_WinDir%\system32\shell32.dll, 214
} else {
Menu, Tray, Icon, MRU menu, %A_WinDir%\system32\imageres.dll, 113
Menu, Tray, Icon, Exit, %A_WinDir%\system32\shell32.dll, 220
Menu, Tray, Icon, %A_WinDir%\system32\imageres.dll, 113
}
Menu, Tray, Icon
}
else
{
GoSub, MRUMenu
ExitApp
}
Return
; Main function: build Recent menus
MRUMenu:
dirs =
docs =
loop, %recent%\*
{
SplitPath, A_LoopFileName, , , , filenoext
FileGetShortcut, %A_LoopFileLongPath%, target
if (! FileExist(target))
continue
if (target == A_ScriptFullPath)
continue
if (InStr(FileExist(target),"D"))
{
if (folderfullpath)
dirs = %dirs%%A_LoopFileTimeModified% %target%`n
else
dirs = %dirs%%A_LoopFileTimeModified% %filenoext%`n
}
else
docs = %docs%%A_LoopFileTimeModified% %filenoext%`n
}
Sort, dirs, CL R
StringTrimRight, dirs, dirs, 1
Sort, docs, CL R
StringTrimRight, docs, docs, 1
recentappsexist = 0
Process, Exist, anvir.exe
if (ErrorLevel && InStr(FileExist(recentapps),"D"))
recentappsexist = 1
if (recentappsexist)
{
apps =
loop, %recentapps%\*
{
SplitPath, A_LoopFileName, , , , filenoext
FileGetShortcut, %A_LoopFileLongPath%, target
if (! FileExist(target))
continue
if (target == A_ScriptFullPath)
continue
apps = %apps%%A_LoopFileTimeModified% %filenoext%`n
}
Sort, apps, CL R
StringTrimRight, apps, apps, 1
}
Menu, docsmenu, Add
Menu, docsmenu, DeleteAll
if (docs == "")
{
Menu, docsmenu, Add, (empty), Exit
Menu, docsmenu, Disable, (empty)
}
else
{
loop, parse, docs, `n
{
lab := SubStr(A_LoopField, 16)
Menu, docsmenu, Add, %lab%, ItemClick
file = %recent%\%lab%.lnk
hicon := GetAssociatedIcon(file,iconsize)
MI_SetMenuItemIcon("docsmenu", A_Index, hicon, iconsize)
if (A_Index == maxitems)
Break
}
menu, docsmenu, Add
menu, docsmenu, Add, Clear MRU Documents History!, ClearHistory
if (xp)
menu, docsmenu, Icon, Clear MRU Documents History!, %A_WinDir%\system32\shell32.dll, 132, %iconsize%
else
menu, docsmenu, Icon, Clear MRU Documents History!, %A_WinDir%\system32\shell32.dll, 235, %iconsize%
}
hicondir =
Menu, dirsmenu, Add
Menu, dirsmenu, DeleteAll
if (dirs == "")
{
Menu, dirsmenu, Add, (empty), Exit
Menu, dirsmenu, Disable, (empty)
}
else
{
loop, parse, dirs, `n
{
lab := SubStr(A_LoopField, 16)
Menu, dirsmenu, Add, %lab%, ItemClick
if (hicondir == "")
{
if (folderfullpath)
dir = %lab%
else
dir = %recent%\%lab%.lnk
if (xp)
hicondir := GetAssociatedIcon(dir,iconsize)
else
hicondir := GetFolderIcon(dir,iconsize)
}
MI_SetMenuItemIcon("dirsmenu", A_Index, hicondir, iconsize)
if (A_Index == maxitems)
Break
}
menu, dirsmenu, Add
menu, dirsmenu, Add, Clear MRU Folders History!, ClearHistory
if (xp)
menu, dirsmenu, Icon, Clear MRU Folders History!, %A_WinDir%\system32\shell32.dll, 132, %iconsize%
else
menu, dirsmenu, Icon, Clear MRU Folders History!, %A_WinDir%\system32\shell32.dll, 235, %iconsize%
}
if (recentappsexist)
{
Menu, appsmenu, Add
Menu, appsmenu, DeleteAll
if (apps == "")
{
Menu, appsmenu, Add, (empty), Exit
Menu, appsmenu, Disable, (empty)
}
else
{
loop, parse, apps, `n
{
lab := SubStr(A_LoopField, 16)
Menu, appsmenu, Add, %lab%, ItemClick
file = %recentapps%\%lab%.lnk
hicon := GetAssociatedIcon(file,iconsize)
MI_SetMenuItemIcon("appsmenu", A_Index, hicon, iconsize)
if (A_Index == maxitems)
Break
}
menu, appsmenu, Add
menu, appsmenu, Add, Clear MRU Applications History!, ClearHistory
if (xp)
menu, appsmenu, Icon, Clear MRU Applications History!, %A_WinDir%\system32\shell32.dll, 132, %iconsize%
else
menu, appsmenu, Icon, Clear MRU Applications History!, %A_WinDir%\system32\shell32.dll, 235, %iconsize%
}
}
menu, mainmenu, Add
Menu, mainmenu, DeleteAll
menu, mainmenu, Add, Recently used..., Exit
menu, mainmenu, Disable, Recently used...
menu, mainmenu, Add, Documents, :docsmenu
menu, mainmenu, Add, Folders, :dirsmenu
if (xp) {
Menu, mainmenu, Icon, Recently used..., %A_WinDir%\system32\shell32.dll, 214, 16
menu, mainmenu, Icon, Documents, %A_WinDir%\system32\shell32.dll, 214, 32
menu, mainmenu, Icon, Folders, %A_WinDir%\system32\shell32.dll, 111, 32
} else {
Menu, mainmenu, Icon, Recently used..., %A_WinDir%\system32\imageres.dll, 113, 16
menu, mainmenu, Icon, Documents, %A_WinDir%\system32\imageres.dll, 113, 32
menu, mainmenu, Icon, Folders, %A_WinDir%\system32\imageres.dll, 154, 32
}
if (recentappsexist)
{
menu, mainmenu, Add, Programs, :appsmenu
menu, mainmenu, Icon, Programs, %A_WinDir%\system32\shell32.dll, 20, 32
}
Menu, mainmenu, Show
Return
; Menu functions:
Exit:
ExitApp
ItemClick:
if (A_ThisMenu == "docsmenu")
Run, %recent%\%A_ThisMenuItem%.lnk
else if (A_ThisMenu == "dirsmenu")
{
if (folderfullpath)
dir = %A_ThisMenuItem%
else
dir = %recent%\%A_ThisMenuItem%.lnk
Run, %dir%
}
else if (A_ThisMenu == "appsmenu")
Run, %recentapps%\%A_ThisMenuItem%.lnk
Return
ClearHistory:
if (A_ThisMenu == "docsmenu")
{
MsgBox, 292, Recent Documents Menu, Are you sure you want to clear the Recent Documents items?
IfMsgBox No
Return
loop, %recent%\*
{
SplitPath, A_LoopFileName, , , , filenoext
FileGetShortcut, %A_LoopFileLongPath%, target
if (NOT InStr(FileExist(target),"D"))
FileDelete, %recent%\%A_LoopFileName%
}
Return
}
else if (A_ThisMenu == "dirsmenu")
{
MsgBox, 292, Recent Documents Menu, Are you sure you want to clear the Recent Folders items?
IfMsgBox No
Return
loop, %recent%\*
{
SplitPath, A_LoopFileName, , , , filenoext
FileGetShortcut, %A_LoopFileLongPath%, target
if (InStr(FileExist(target),"D"))
FileDelete, %recent%\%A_LoopFileName%
}
Return
}
else if (A_ThisMenu == "appsmenu")
{
MsgBox, 292, Recent Documents Menu, Are you sure you want to clear the Recent Applications items?
IfMsgBox No
Return
FileDelete, %recentapps%\*.lnk
Return
}
Return
; Utility functions to retrieve the icons and associate them with the menu items
GetFolderIcon(dir, iconsize = 16)
{
global IconsArray
old := IconsArray[iconsize, "directory"]
if old =
{
FileIcon = %A_WinDir%\system32\shell32.dll
hIcon := MI_ExtractIcon(FileIcon,4,iconsize)
IconsArray[iconsize, "directory"] := hicon
} else {
hicon := old
}
return hIcon
}
GetAssociatedIcon(File, iconsize = 16)
{
static
sfi_size:=352
global IconsArray
local hIcon, Ext, Fileto, FileIcon, FileIconNum, old, programsx86
EnvGet, programsx86, ProgramFiles(x86)
If not sfi
VarSetCapacity(sfi, sfi_size)
SplitPath, File,,, Ext
If ext=LNK
{
FileGetShortcut,%File%,Fileto,,,,FileIcon,FileIconNum
if (FileIcon) {
hIcon := MI_ExtractIcon(FileIcon,FileIconNum,iconsize)
if (hIcon)
return hIcon
} else {
File := Fileto
SplitPath, File,,, Ext
}
}
if (! FileExist(File))
StringReplace, File, File, %Programx86%, %ProgramW6432%
if Ext in ICO,ANI,CUR,EXE
{
SplitPath, File,,, Ext
hIcon := MI_ExtractIcon(File,1,iconsize)
if (hIcon)
return hIcon
}
old := IconsArray[iconsize, ext]
if old =
{
If (DllCall("Shell32\SHGetFileInfoA", "astr", File, "uint", 0, "str", sfi, "uint", sfi_size, "uint", 0x101))
{
hIcon = 0
Loop 4
hIcon += *(&sfi + A_Index-1) << 8*(A_Index-1)
}
IconsArray[iconsize, ext] := hicon
} else {
hicon := old
}
return hIcon
}
MI_SetMenuItemIcon(MenuNameOrHandle, ItemPos, h_icon, IconSize=0)
{
; Set for compatibility with older scripts:
unused1=0
unused2=0
if MenuNameOrHandle is integer
h_menu := MenuNameOrHandle
else
h_menu := MI_GetMenuHandle(MenuNameOrHandle)
if !h_menu
return false
h_icon := DllCall("CopyImage","uint",h_icon,"uint",1
,"int",IconSize,"int",IconSize,"uint",0)
; Get the previous bitmap or icon handle.
VarSetCapacity(mii,48,0), NumPut(48,mii), NumPut(0xA0,mii,4)
if DllCall("GetMenuItemInfo","uint",h_menu,"uint",ItemPos-1,"uint",1,"uint",&mii)
h_previous := NumGet(mii,44,"int")
h_bitmap := MI_GetBitmapFromIcon32Bit(h_icon, IconSize, IconSize)
if loaded_icon
{
; The icon we loaded is no longer needed.
DllCall("DestroyIcon","uint",loaded_icon)
; Don't try to destroy the now invalid handle again:
loaded_icon := 0
}
if !h_bitmap
return false
NumPut(0x80,mii,4) ; fMask: Set hbmpItem only, not dwItemData.
, NumPut(h_bitmap,mii,44) ; hbmpItem = h_bitmap
if DllCall("SetMenuItemInfo","uint",h_menu,"uint",ItemPos-1,"uint",1,"uint",&mii)
{
; Only now that we know it's a success, delete the previous icon or bitmap.
if (h_previous < -1 || h_previous > 11)
DllCall("DeleteObject","uint",h_previous)
return true
}
; ELSE FAIL
if loaded_icon
DllCall("DestroyIcon","uint",loaded_icon)
return false
}
MI_ExtractIcon(Filename, IconNumber, IconSize)
{
DllCall("PrivateExtractIcons", "wStr", Filename, "Int", IconNumber-1, "Int", IconSize, "Int", IconSize, "UInt*", hIcon, "UInt*", 0, "UInt", 1, "UInt", 0, "Int")
If !ErrorLevel
Return hIcon
If DllCall("shell32.dll\ExtractIconExA", "wStr", Filename, "Int", IconNumber-1, "UInt*", hIcon, "UInt*", hIcon_Small, "UInt", 1)
{
SysGet, SmallIconSize, 49
If (IconSize <= SmallIconSize) {
DllCall("DeStroyIcon", "UInt", hIcon)
hIcon := hIcon_Small
}
Else
DllCall("DeStroyIcon", "UInt", hIcon_Small)
If (hIcon && IconSize)
hIcon := DllCall("CopyImage", "UInt", hIcon, "UInt", 1, "Int", IconSize, "Int", IconSize, "UInt", 4|8)
}
Return, hIcon ? hIcon : 0
}
MI_GetMenuHandle(menu_name)
{
static h_menuDummy
; v2.2: Check for !h_menuDummy instead of h_menuDummy="" in case init failed last time.
If !h_menuDummy
{
Menu, menuDummy, Add
Menu, menuDummy, DeleteAll
Gui, 99:Menu, menuDummy
; v2.2: Use LastFound method instead of window title. [Thanks animeaime.]
Gui, 99:+LastFound
h_menuDummy := DllCall("GetMenu", "uint", WinExist())
Gui, 99:Menu
Gui, 99:Destroy
; v2.2: Return only after cleaning up. [Thanks animeaime.]
if !h_menuDummy
return 0
}
Menu, menuDummy, Add, :%menu_name%
h_menu := DllCall( "GetSubMenu", "uint", h_menuDummy, "int", 0 )
DllCall( "RemoveMenu", "uint", h_menuDummy, "uint", 0, "uint", 0x400 )
Menu, menuDummy, Delete, :%menu_name%
return h_menu
}
MI_GetBitmapFromIcon32Bit(h_icon, width=0, height=0)
{
VarSetCapacity(buf,40) ; used as ICONINFO (20), BITMAP (24), BITMAPINFO (40)
if DllCall("GetIconInfo","uint",h_icon,"uint",&buf) {
hbmColor := NumGet(buf,16) ; used to measure the icon
hbmMask := NumGet(buf,12) ; used to generate alpha data (if necessary)
}
if !(width && height) {
if !hbmColor or !DllCall("GetObject","uint",hbmColor,"int",24,"uint",&buf)
return 0
width := NumGet(buf,4,"int"), height := NumGet(buf,8,"int")
}
; Create a device context compatible with the screen.
if (hdcDest := DllCall("CreateCompatibleDC","uint",0))
{
; Create a 32-bit bitmap to draw the icon onto.
VarSetCapacity(buf,40,0), NumPut(40,buf), NumPut(1,buf,12,"ushort")
NumPut(width,buf,4), NumPut(height,buf,8), NumPut(32,buf,14,"ushort")
if (bm := DllCall("CreateDIBSection","uint",hdcDest,"uint",&buf,"uint",0
,"uint*",pBits,"uint",0,"uint",0))
{
; SelectObject -- use hdcDest to draw onto bm
if (bmOld := DllCall("SelectObject","uint",hdcDest,"uint",bm))
{
; Draw the icon onto the 32-bit bitmap.
DllCall("DrawIconEx","uint",hdcDest,"int",0,"int",0,"uint",h_icon
,"uint",width,"uint",height,"uint",0,"uint",0,"uint",3)
DllCall("SelectObject","uint",hdcDest,"uint",bmOld)
}
; Check for alpha data.
has_alpha_data := false
Loop, % height*width
if NumGet(pBits+0,(A_Index-1)*4) & 0xFF000000 {
has_alpha_data := true
break
}
if !has_alpha_data
{
; Ensure the mask is the right size.
hbmMask := DllCall("CopyImage","uint",hbmMask,"uint",0
,"int",width,"int",height,"uint",4|8)
VarSetCapacity(mask_bits, width*height*4, 0)
if DllCall("GetDIBits","uint",hdcDest,"uint",hbmMask,"uint",0
,"uint",height,"uint",&mask_bits,"uint",&buf,"uint",0)
{ ; Use icon mask to generate alpha data.
Loop, % height*width
if (NumGet(mask_bits, (A_Index-1)*4))
NumPut(0, pBits+(A_Index-1)*4)
else
NumPut(NumGet(pBits+(A_Index-1)*4) | 0xFF000000, pBits+(A_Index-1)*4)
} else { ; Make the bitmap entirely opaque.
Loop, % height*width
NumPut(NumGet(pBits+(A_Index-1)*4) | 0xFF000000, pBits+(A_Index-1)*4)
}
}
}
; Done using the device context.
DllCall("DeleteDC","uint",hdcDest)
}
if hbmColor
DllCall("DeleteObject","uint",hbmColor)
if hbmMask
DllCall("DeleteObject","uint",hbmMask)
return bm
}
And the INI file (that should be placed in the same folder):
Code:
[Config]
; Boolean: Stay resident in the tray? Default: 0 (Advantage of tray mode: much more rapid to display the menu)
TrayIcon=0
; Size of icons in sub-menus. Default: 16 (Use 16 or 32. Other values between 16 and 64 accepted but not recommended)
IconSize=16
; Maximum number of items in each sub-menu. Default: 20 (The most recent items are always on top of the menus)
MaxItems=20
; Boolean: Display the full path in the Folders menu. Default: 0 = folder name only.
FolderFullPath=0
; The program tries to find the Recent folder. If the Recent menus are blank, try to put here the path to your Recent folder.
; Under XP, it should be: C:\Documents and Settings\<you>\Recent
; Under Win7, it should be: C:\Users\<you>\AppData\Roaming\Microsoft\Windows\Recent
RecentFolder=
; Same thing for the Anvir Tray folder (only available if you have "Anvir Task Manager" running):
; Under XP: C:\Documents and Settings\<you>\Local Settings\Application Data\AnVir\Tray
; Under Win 7: C:\Users\<you>\AppData\Local\AnVir\Tray
AnVirRecentAppsFolder=
Note that you don't need the INI file if you are happy with the default settings.