Page 1 of 1

DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 10 Apr 2017, 11:56
by programlearner
Hi

Could someone show me how to use ahk to add icons to the explorer context menu please?

It's been done here in C++

https://superuser.com/questions/649168/ ... it/1021873

I can do some basic ahk stuff - but DllCall is beyond my skills.

Thanks

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 11 Apr 2017, 09:07
by programlearner
I'm still struggling with this - but found this that may help:

https://autohotkey.com/board/topic/1975 ... text-menu/

I think I need to use MenuItemInfo to find the text "&Copy"
Then somehow use this as an identifier for SetMenuItemInfo to set an icon.

Any help?

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 12 Apr 2017, 10:53
by programlearner
Any help at all please? Third comment in "ask for help" but no replies :? :cry:

I've been trying for days and it's driving me crazy...keep getting up in the night to try new ideas, but DLL is just too complicated for me.

Could someone just give me a pointer please?

I've found a few CallDlll() tutorials (message box, etc) - but still can't get them to work for:

Menuiteminfo Structure
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

and

SetMenuItemInfo Function
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

I'm sure the outcome will be of use to lots of people - ability to add icons to menu items (I'm dyslexic and pictures speed me up and prevent mistakes).

There's a CPP version for people to see that it is possible.
https://superuser.com/questions/649168/ ... it/1021873

Is anyone able to help please? I really need to get some sleep again.

I'd be grateful for just a point in the right direction.

:begging:

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 12 Apr 2017, 17:17
by jeeswg
There is a post by the author of the software, at the Super User link, that states:
I have developed a 64-bit shell extension that gets registered as a context menu handler.
...
Put the DLL somewhere accessible to all users, that folder you just made is a good choice. Open an admin prompt in the folder containing the DLL and do regsvr32 ContextIcons.dll.
Thus, what I see is that you have to install the shell extension. Once installed the shell extension will 'do all the work'. So that's it, you install the shell extension, and it works, and no code is required after that point.

However, it may be possible to rewrite that shell extension using AutoHotkey. I'd be interested to know if anyone on the forum has done much with shell extensions. It's one of the few key issues I haven't seen much progress on, e.g. select multiple icons in an Explorer window, and apply a right-click context menu action to them.

Btw apologies but I don't like playing around with things that affect the shell, unless I have a duff computer for testing, which I don't, unfortunately.

Btw thanks for the post, I didn't know that these icons can be set.

Btw have you installed this software yourself?

Yeah, I know what it's like when you get one of these burning questions.

==================================================

SHELL EXTENSIONS, ANYONE?

Also even this recent question, relates to a shell extension, getting a 'Properties' dialog for multiple selected files (or if possible directly getting a 'Properties' dialog for a list of files).
summarize total size property box - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=30483

DOES ANYBODY KNOW ABOUT AHK + SHELL EXTENSIONS? PRETTY PLEASE?
(I don't have any experience with this.)
No responses for:
Shell extension to show cover icons of media files? - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?t=11533
Creating a DLL Shell Extension with AutoHotkey? - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?t=11895

==================================================

NOTES ON MENUS

[EDIT:] (I'll check for code to see if internal/external menu icons can be changed 'live'.)

I would recommend starting off by trying to set menu icons for internal menus (i.e. menus that are created by AutoHotkey). Try the example in the link below (try AHK x32 if you normally use AHK x64 and it doesn't work.)

Menu Icons v2 - Scripts and Functions - AutoHotkey Community
https://autohotkey.com/board/topic/20253-menu-icons-v2/

==================================================

RETRIEVING ICONS/IMAGES FROM FILES

Apologies for such a basic starting point, I will post more when I have the chance:
[EDIT: added a bit more since]

Code: Select all

q::
;LR_DEFAULTSIZE := 0x40 ;LR_LOADFROMFILE := 0x10

;IMAGE_ICON := 1
;vPath = %A_Desktop%\MyIcon.ico
;hIcon := DllCall("LoadImage", Ptr,0, Str,vPath, UInt,1, Int,0, Int,0, UInt,0x50, Ptr)

;IMAGE_BITMAP := 0
;vPath = %A_Desktop%\MyImage.bmp
;hBitmap := DllCall("LoadImage", Ptr,0, Str,vPath, UInt,0, Int,0, Int,0, UInt,0x50, Ptr)

;oddly, LoadPicture needs a ByRef output var in order to set an hIcon rather than an hBitmap
hIcon := LoadPicture(A_AhkPath, "", vType)

;set window icons
WinGet, hWnd, ID, A
SendMessage, 0x80, 0, % hIcon, , % "ahk_id " hWnd ;WM_SETICON ;sets title bar icon + taskbar icon
SendMessage, 0x80, 1, % hIcon, , % "ahk_id " hWnd ;WM_SETICON ;sets alt+tab icon
return
I couldn't get AutoHotkey's LoadPicture function to work in the past. I searched for 'LoadPicture(A_AhkPath' just in case, got lucky, and now I realise that it needs a ByRef output variable to be specified, to allow it to not assume an hBitmap is wanted, and instead allow it to autodetect the image type, in this case an icon. No wonder I didn't realise what was going on!
hIcon := LoadPicture(A_AhkPath, "", vType)

Code: Select all

q:: ;various ways to retrieve/set icons
WinGet, hWnd, ID, A
;use NirSoft IconsExtract to retrieve icon numbers
vPath := "shell32.dll", vNum := 28 ;shutdown icon
vPath := A_AhkPath, vNum := 160 ;ahk file icon
hModule := DllCall("LoadLibrary", Str,vPath)
;LR_DEFAULTSIZE := 0x40 ;LR_LOADFROMFILE := 0x10
;IMAGE_CURSOR := 2 ;IMAGE_ICON := 1 ;IMAGE_BITMAP := 0
hIcon := DllCall("LoadImage", Ptr,hModule, Ptr,vNum, UInt,1, Int,0, Int,0, UInt,0x40, Ptr)

;LoadImage supersedes LoadIcon, however, I'm not sure if this is doable with LoadImage instead:
;IDI_APPLICATION := 32512 ;standard exe icon
;hIcon := DllCall("LoadIcon", Ptr,0, Ptr,32512)

;use hIcon := 0 to restore the window's original icon
;hIcon := 0

;create a blank icon
;classic AutoHotkey moment:
;Remove titlebar icon without using -SysMenu or +ToolWindow - Ask for Help - AutoHotkey Community
;https://autohotkey.com/board/topic/30783-remove-titlebar-icon-without-using-sysmenu-or-toolwindow/#entry207998
;VarSetCapacity(AndMask, 32*4, 0xFF), VarSetCapacity(XorMask, 32*4, 0)
;hIcon := DllCall("CreateCursor", Ptr,0, Int,0, Int,0, Int,32, Int,32, UInt,&AndMask, UInt,&XorMask)

SendMessage, 0x80, 0, % hIcon, , % "ahk_id " hWnd ;WM_SETICON ;sets title bar icon + taskbar icon
SendMessage, 0x80, 1, % hIcon, , % "ahk_id " hWnd ;WM_SETICON ;sets alt+tab icon
DllCall("FreeLibrary", Ptr,hModule)
return
==================================================

ALT+SPACE MENU (SYSTEM MENU) ICONS AND TITLE BAR BUTTONS

[EDIT:] I couldn't remember last night where the alt+space menu icons (restore/minimize/maximize/close) were kept, couldn't find it online easily, then today I remembered, it was a font beginning with 'm', Marlett, on Windows 7: you can view it in Character Map, or open C:\Windows\Fonts\marlett.ttf.

It's possible to do text to image via Gdip_TextToGraphics, but if I wanted to clone the alt+space menu (system menu) icons, I'd want it to be perfect, so I'd probably need some other knowledge to do it. Ideally I'd do it in the same way Windows does.

I'm not sure if there was/is a way to set a menu item to have one of the default alt+space menu icons.
[EDIT:]
MENUITEMINFO structure (Windows)
https://msdn.microsoft.com/en-us/librar ... s.85).aspx

;HBMMENU_SYSTEM := 1
;HBMMENU_MBAR_RESTORE := 2
;HBMMENU_MBAR_MINIMIZE := 3
;HBMMENU_MBAR_CLOSE := 5
;HBMMENU_MBAR_CLOSE_D := 6
;HBMMENU_MBAR_MINIMIZE_D := 7
;HBMMENU_POPUP_CLOSE := 8
;HBMMENU_POPUP_RESTORE := 9
;HBMMENU_POPUP_MAXIMIZE := 10
;HBMMENU_POPUP_MINIMIZE := 11

This script allows you to change which menu items the alt+space menu has (add in custom menu items):
System Menu : Add custom and/or remove standard items - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=1060

In Windows XP, the main title bar button images are kept here:
C:\WINDOWS\Resources\Themes\Luna\luna.msstyles
You can view the images inside the file with Resource Hacker.

[EDIT:] This is great, I've recaptured all my old knowledge about icons, and it wasn't easy!

==================================================

CONVERTING BITMAPS TO ICONS, ICONS TO BITMAPS
(AND EXTRACTING ICON IMAGES FROM THE SYSTRAY)

[the function here converts bitmaps to icons]
[Function] Resize and Convert Images - AutoHotkey Community
https://autohotkey.com/boards/viewtopic ... 093#p14093

[this link covers icons to bitmaps, and extracting icons from the systray]
[they may not be needed for this script, but experience dictates you should investigate related problems as you go along]
[SOLVED]Extract programs icon in systray - Ask for Help - AutoHotkey Community
https://autohotkey.com/board/topic/7624 ... n-systray/

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 13 Apr 2017, 08:23
by programlearner
Thank you jeeswg - I really appreciate you taking an interest.

I'm no closer with the cut/copy/paste/delete - but have found it is really easy to add icons to most shells using ahk to write registry entries to:

HKEY_CLASSES_ROOT\*\shell\MyEntry
and adding a string called icon, pointing to any ico/exe/dll

I've also found it's just as easy to make a nestled context menu, with all it's entries having their own icons by writing to:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell

and creating a nestled file title, pointing to them all, here:

HKEY_CLASSES_ROOT\*shell\

If the cut/copy/paste/delete had their own registry places - I'd have it working now...but it doesn't look like they do.

I'll keep going, and thanks again for the help.

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 13 Apr 2017, 15:51
by jeeswg
This script sets all the icons in the active context menu, to the 'restore icon'.

Use at your own risk. Try it with a new Notepad instance first. Certain actions such as this, can potentially crash a program.

Code: Select all

q:: ;set context menu icons to 'restore icon'
;hBitmap := LoadPicture(A_AhkPath)
hBitmap := 2 ;HBMMENU_MBAR_RESTORE := 2
WinGet, hWnd, ID, ahk_class #32768
if !hWnd
	return
;WinGet, vPName, ProcessName, % "ahk_id " hWnd
;if !(vPName = "notepad.exe") && !(vPName = "explorer.exe")
;	return
SendMessage, 0x1E1, 0, 0, , ahk_class #32768 ;MN_GETHMENU
if !hMenu := ErrorLevel
	return
WinGet, vPID, PID, % "ahk_id " hWnd
;OpenProcess may not be needed to set an external menu item's icon to HBMMENU_MBAR_RESTORE
if !hProc := DllCall("OpenProcess", UInt,0x1F0FFF, Int,0, UInt,vPID, Ptr)
	return
Loop, % DllCall("GetMenuItemCount", Ptr,hMenu)
{
	vPos := A_Index-1
	vSize := A_PtrSize=8?80:48
	VarSetCapacity(MENUITEMINFO, vSize, 0)
	NumPut(vSize, MENUITEMINFO, 0, "UInt") ;cbSize
	NumPut(0x80, MENUITEMINFO, 4, "UInt") ;fMask
	NumPut(hBitmap, MENUITEMINFO, A_PtrSize=8?72:44, "Ptr") ;hBitmap
	DllCall("SetMenuItemInfo", Ptr,hMenu, UInt,vPos, Int,1, Ptr,&MENUITEMINFO)
}

DllCall("CloseHandle", Ptr,hProc)
return
I am not sure about setting external context menu icons to an arbitrary image, there may be issues regarding trying to use an hBitmap from one process, in another process, although there may be workarounds (and hopefully not requiring dll injection). That leaves the key issue of shell extensions, which is something I'm not sure about. Best of luck. I hope someone can chip in with some further ideas.

[Btw I added some info here, regarding the structs: MENUBARINFO, MENUINFO, MENUITEMINFO, which may be useful.]
list of structs with parameters (sizes and types) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=30497

Btw interesting info about icons and the registry, that I didn't know all of. And, many thanks for this interesting set of posts.

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 13 Apr 2017, 17:11
by programlearner
Wow - that is ace!
Completely stable, and works perfectly on every menu (I've spent ages just playing with it).
I'm not sure if it'll do what I've been after...but just this is further that I'd managed to get, and it's also loads of fun.
Thank you!

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 13 Apr 2017, 18:51
by programlearner
If it's of use to anyone, here's how to make a nested context menu with icons (I couldn't find anything doing it in the forum).
I've only tested it on Windows 7 and 10 - and it needs to be run as an administrator to work.

Image

Code: Select all

;Create a nested context menu with icons
Icon0 = %SystemRoot%\System32\shell32.dll,146
Icon1 = %SystemRoot%\System32\shell32.dll,147
Icon2 = %SystemRoot%\System32\shell32.dll,148

RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo, MUIVerb, AHK_Demo
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo, Icon, %Icon0%
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo, SubCommands, {AHK_Demo-Item1}`;{AHK_Demo-Item2}

RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item1}, Icon, %Icon1%
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item1}, MUIVerb, Open Script 1
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item1}, , %A_ScriptFullPath%

RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item2}, Icon, %Icon2%
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item2}, MUIVerb, Open Script 2
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item2}, , %A_ScriptFullPath%
Obviously use the icons and links you need - and also add as many items as you need.

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 14 Apr 2017, 05:39
by programlearner
First of all - please accept my apologies for the method I've used here.

I've taken two excellent scripts, and combined them in a crude, untalented manner - which now looks like an insult to the original creators...sorry micha and jeeswg.

However, it does kind of work - and can serve as a starting point :)

Code: Select all

;https://autohotkey.com/board/topic/19754-get-info-from-context-menu/
;
; AutoHotkey Version: 1.1
; Language:       English
; Platform:       Win9x/NT
; Author:         micha
;
; Script Function:
;	Demonstrates how to retrieve infos from a context/ popup menu
;

/*
  This is the struct we are using.

  typedef struct tagMENUITEMINFO {
  UINT    cbSize;
  UINT    fMask;
  UINT    fType;
  UINT    fState;
  UINT    wID;
  HMENU   hSubMenu;
  HBITMAP hbmpChecked;
  HBITMAP hbmpUnchecked;
  ULONG_PTR dwItemData;
  LPTSTR  dwTypeData;
  UINT    cch;
  HBITMAP hbmpItem;
} MENUITEMINFO, *LPMENUITEMINFO;


*/

#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
#Persistent
SetTimer, Demo, 500
return

Demo:
;constants
  MFS_ENABLED = 0
  MFS_CHECKED = 8
  MFS_DEFAULT = 0x1000
  MFS_DISABLED = 2
  MFS_GRAYED = 1
  MFS_HILITE = 0x80
  ;MFS_UNCHECKED = 0
  ;MFS_UNHILITE = 0

  ;Get mouse position and handle to wnd under the mouse cursor
  MouseGetPos, MouseScreenX, MouseScreenY, MouseWindowUID, MouseControlID
  WinGet,ControlHwnd, ID,ahk_id %MouseWindowUID%
  ;Get count of menu items
  ContextMenCnt := GetContextMenuCount(ControlHwnd)
  if ContextMenCnt < 1
  {
   Tooltip,
   return
  }
  TooltipText =
  TextText =
  loop, %ContextMenCnt%
  {
    StrSize := GetContextMenuText(ControlHwnd, a_index-1)
	If StrSize = Cu&t
		ChangeIconImage(a_index, 2)
	If StrSize = &Copy
		ChangeIconImage(a_index, 3)
   ; TextText = %TextText%%a_index%:%StrSize%`n
  }
  CoordMode, Tooltip, Screen
  Tooltip, %TextText%, 0, 0
return

/***************************************************************
 * returns the count of menu items
 ***************************************************************
*/
GetContextMenuCount(hWnd)
{
  WinGetClass, WindowClass, ahk_id %hWnd%
  ;All popups should have the window class #32768
  if WindowClass <> #32768
  {
   return 0
  }
  ;Retrieve menu handle from window
  SendMessage, 0x01E1, , , , ahk_id %hWnd%
  ;Errorlevel is set by SendMessage. It contains the handle to the menu
  hMenu := errorlevel
  menuitemcount:=DllCall("GetMenuItemCount",UInt,hMenu)
  Return, menuitemcount
}

/***************************************************************
 * returns the text of a menu entry (standard windows context menus only!!!)
 ***************************************************************
*/
GetContextMenuText(hWnd, Position)
{
  WinGetClass, WindowClass, ahk_id %hWnd%
  if WindowClass <> #32768
  {
   return -1
  }
  SendMessage, 0x01E1, , , , ahk_id %hWnd%
  ;Errorlevel is set by SendMessage. It contains the handle to the menu
  hMenu := errorlevel

  ;We need to allocate a struct
  VarSetCapacity(MenuItemInfo, 200, 0)
  ;Set Size of Struct (48) to the first member
  InsertInteger(48, MenuItemInfo, 0, 4)
  ;Retrieve string MIIM_STRING = 0x40 = 64 (/ MIIM_TYPE = 0x10 = 16)
  InsertInteger(64, MenuItemInfo, 4, 4)
  ;Set type - Get only size of string we need to allocate
  ;InsertInteger(0, MenuItemInfo, 8, 4)
  ;GetMenuItemInfo: Handle to Menu, Index of Position, 0=Menu identifier / 1=Index
  InfoRes := DllCall("user32.dll\GetMenuItemInfo",UInt,hMenu, Uint, Position, uint, 1, "int", &MenuItemInfo)
  if InfoRes = 0
     return -1

  InfoResError := errorlevel
  LastErrorRes := DllCall("GetLastError")
  if InfoResError <> 0
     return -1
  if LastErrorRes <> 0
     return -1

  ;Get size of string from struct
  GetMenuItemInfoRes := ExtractInteger(MenuItemInfo, 40, false, 4)
  ;If menu is empty return
  If GetMenuItemInfoRes = 0
     return "{Empty String}"

  ;+1 should be enough, we'll use 2
  GetMenuItemInfoRes += 2
  ;Set capacity of string that will be filled by windows
  VarSetCapacity(PopupText, GetMenuItemInfoRes, 0)
  ;Set Size plus 0 terminator + security ;-)
  InsertInteger(GetMenuItemInfoRes, MenuItemInfo, 40, 4)
  InsertInteger(&PopupText, MenuItemInfo, 36, 4)

  InfoRes := DllCall("user32.dll\GetMenuItemInfo",UInt,hMenu, Uint, Position, uint, 1, "int", &MenuItemInfo)
  if InfoRes = 0
     return -1

  InfoResError := errorlevel
  LastErrorRes := DllCall("GetLastError")
  if InfoResError <> 0
     return -1
  if LastErrorRes <> 0
     return -1

  return PopupText
}

; *********************************
; *********************************
; Original versions of ExtractInteger and InsertInteger provided by Chris
; - from the AutoHotkey help file - Version 1.0.37.04
; *********************************
; *********************************
ExtractInteger(ByRef pSource, pOffset = 0, pIsSigned = false, pSize = 4)
; pSource is a string (buffer) whose memory area contains a raw/binary integer at pOffset.
; The caller should pass true for pSigned to interpret the result as signed vs. unsigned.
; pSize is the size of PSource's integer in bytes (e.g. 4 bytes for a DWORD or Int).
; pSource must be ByRef to avoid corruption during the formal-to-actual copying process
; (since pSource might contain valid data beyond its first binary zero).
{
   SourceAddress := &pSource + pOffset  ; Get address and apply the caller's offset.
   result := 0  ; Init prior to accumulation in the loop.
   Loop %pSize%  ; For each byte in the integer:
   {
      result := result | (*SourceAddress << 8 * (A_Index - 1))  ; Build the integer from its bytes.
      SourceAddress += 1  ; Move on to the next byte.
   }
   if (!pIsSigned OR pSize > 4 OR result < 0x80000000)
      return result  ; Signed vs. unsigned doesn't matter in these cases.
   ; Otherwise, convert the value (now known to be 32-bit) to its signed counterpart:
   return -(0xFFFFFFFF - result + 1)
}

InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; To preserve any existing contents in pDest, only pSize number of bytes starting at pOffset
; are altered in it. The caller must ensure that pDest has sufficient capacity.
{
   mask := 0xFF  ; This serves to isolate each byte, one by one.
   Loop %pSize%  ; Copy each byte in the integer into the structure as raw binary data.
   {
      DllCall("RtlFillMemory", UInt, &pDest + pOffset + A_Index - 1, UInt, 1  ; Write one byte.
         , UChar, (pInteger & mask) >> 8 * (A_Index - 1))  ; This line is auto-merged with above at load-time.
      mask := mask << 8  ; Set it up for isolation of the next byte.
   }
}
; *********************************
; *********************************

PrintScreen::reload

ChangeIconImage(IconPosition, IconImage)
{
IconPosition--
hBitmap := IconImage
WinGet, hWnd, ID, ahk_class #32768
if !hWnd
	return
;WinGet, vPName, ProcessName, % "ahk_id " hWnd
;if !(vPName = "notepad.exe") && !(vPName = "explorer.exe")
;	return
SendMessage, 0x1E1, 0, 0, , ahk_class #32768 ;MN_GETHMENU
if !hMenu := ErrorLevel
	return
WinGet, vPID, PID, % "ahk_id " hWnd
;OpenProcess may not be needed to set an external menu item's icon to HBMMENU_MBAR_RESTORE
if !hProc := DllCall("OpenProcess", UInt,0x1F0FFF, Int,0, UInt,vPID, Ptr)
	return
Loop, % DllCall("GetMenuItemCount", Ptr,hMenu)
{
	vPos := IconPosition
	vSize := A_PtrSize=8?80:48
	VarSetCapacity(MENUITEMINFO, vSize, 0)
	NumPut(vSize, MENUITEMINFO, 0, "UInt") ;cbSize
	NumPut(0x80, MENUITEMINFO, 4, "UInt") ;fMask
	NumPut(hBitmap, MENUITEMINFO, A_PtrSize=8?72:44, "Ptr") ;hBitmap
	DllCall("SetMenuItemInfo", Ptr,hMenu, UInt,vPos, Int,1, Ptr,&MENUITEMINFO)
}

DllCall("CloseHandle", Ptr,hProc)
return
}
Return
Thanks

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 14 Apr 2017, 08:12
by jeeswg
I thought it wasn't working, then I realised, it was setting Cut to have the Restore icon, and Copy to have the Minimize icon, this is really good. So yes, this was the best workaround idea, that I had intended ... unless we can figure out a shell extension. Obviously, if we can find a way to specify *any* icon image (any hBitmap), that would also be an advantage.

I've written a script below, similar to my one above, that sets a menu item's icon to the Restore icon if its text is Cut, the Min icon if its text is Copy, or the Max icon otherwise.

Note: InsertInteger and ExtractInteger, are some old-school functions that have since been replaced with NumPut and NumGet. Also, older scripts were x32-compatible only, I try to make mine compatible with x32 and x64.

[Btw this article on shell extensions was interesting suggesting we need to create our own object:]
The Complete Idiot's Guide to Writing Shell Extensions - Part I - CodeProject
https://www.codeproject.com/Articles/44 ... ell-Extens

Code: Select all

q:: ;set context menu icons to Max icon, (Restore icon if Cut, Min icon if Copy)
;hBitmap := LoadPicture(A_AhkPath)
hBitmap := 2 ;HBMMENU_MBAR_RESTORE := 2
WinGet, hWnd, ID, ahk_class #32768
if !hWnd
	return
;WinGet, vPName, ProcessName, % "ahk_id " hWnd
;if !(vPName = "notepad.exe") && !(vPName = "explorer.exe")
;	return
SendMessage, 0x1E1, 0, 0, , ahk_class #32768 ;MN_GETHMENU
if !hMenu := ErrorLevel
	return
WinGet, vPID, PID, % "ahk_id " hWnd
;OpenProcess may not be needed to set an external menu item's icon to HBMMENU_MBAR_RESTORE
if !hProc := DllCall("OpenProcess", UInt,0x1F0FFF, Int,0, UInt,vPID, Ptr)
	return

vOutput := ""
Loop, % DllCall("GetMenuItemCount", Ptr,hMenu)
{
	vPos := A_Index-1
	vSize := A_PtrSize=8?80:48

	;MIIM_STRING := 0x40
	VarSetCapacity(MENUITEMINFO, vSize, 0)
	NumPut(vSize, MENUITEMINFO, 0, "UInt") ;cbSize
	NumPut(0x40, MENUITEMINFO, 4, "UInt") ;fMask
	DllCall("GetMenuItemInfo", Ptr,hMenu, UInt,vPos, Int,1, Ptr,&MENUITEMINFO)
	vChars := NumGet(MENUITEMINFO, A_PtrSize=8?64:40, "UInt") ;cch
	if (vChars > 0)
	{
		VarSetCapacity(vText, (vChars+1)*(A_IsUnicode+1), 0)
		NumPut(&vText, MENUITEMINFO, A_PtrSize=8?56:36, "Ptr") ;dwTypeData
		NumPut(vChars+1, MENUITEMINFO, A_PtrSize=8?64:40, "UInt") ;cch
		DllCall("GetMenuItemInfo", Ptr,hMenu, UInt,vPos, Int,1, Ptr,&MENUITEMINFO)
		VarSetCapacity(vText, -1)
	}
	else
		vText := ""
	vOutput .= vText "`r`n"

	;0 will remove an icon
	;4 has some weird slight effect on the menu appearance (tested on Windows 7)
	;HBMMENU_SYSTEM := 1
	;HBMMENU_MBAR_RESTORE := 2
	;HBMMENU_MBAR_MINIMIZE := 3
	;HBMMENU_MBAR_CLOSE := 5
	;HBMMENU_MBAR_CLOSE_D := 6
	;HBMMENU_MBAR_MINIMIZE_D := 7
	;HBMMENU_POPUP_CLOSE := 8
	;HBMMENU_POPUP_RESTORE := 9
	;HBMMENU_POPUP_MAXIMIZE := 10
	;HBMMENU_POPUP_MINIMIZE := 11
	hBitmap := 10
	(vText = "Cu&t") ? (hBitmap := 2) : ""
	(vText = "&Copy") ? (hBitmap := 3) : ""

	if !(hBitmap = 0)
	{
		;MIIM_BITMAP := 0x80
		VarSetCapacity(MENUITEMINFO, vSize, 0)
		NumPut(vSize, MENUITEMINFO, 0, "UInt") ;cbSize
		NumPut(0x80, MENUITEMINFO, 4, "UInt") ;fMask
		NumPut(hBitmap, MENUITEMINFO, A_PtrSize=8?72:44, "Ptr") ;hBitmap
		DllCall("SetMenuItemInfo", Ptr,hMenu, UInt,vPos, Int,1, Ptr,&MENUITEMINFO)
	}
}
;Clipboard := vOutput
;MsgBox, % vOutput

DllCall("CloseHandle", Ptr,hProc)
return
Btw one thing that we're hoping to do is set an external menu item's hBitmap. One thing I've been interested to do in the past, was to get the image of an external hBitmap, from a menu item or from a systray icon, to possibly compare it with another image, in order to retrieve some information from the program (if it changes the icon image to indicate a state). Possibly this isn't too hard, just that the last time I tried was a long time ago, I couldn't get it to work back then. Note: checking if an item is checked/unchecked (ticked/unticked) or enabled/disabled, is a different task, and is fairly easy, you check the menu item's state.

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 14 Apr 2017, 12:13
by programlearner
Just adding to the fun I've been having with context menus...
If you use the zip versions of ahk, and create your nestled context menu here:
HKEY_CLASSES_ROOT\Applications\AutoHotkeyA32.exe\shell
Then you can add a host of open/compile options for your scripts:

Image

Command for open is:
"C:\LOCATION_OF_YOUR_ZIP\AutoHotkey\Compiler\AutoHotkey.exe" "%1"

Command for compile is:
"C:\LOCATION_OF_YOUR_ZIP\AutoHotkey\Compiler\Ahk2Exe.exe" /in "

Command for Compile with Options is:
"C:\LOCATION_OF_YOUR_ZIP\AutoHotkey\Compiler\Compile_AHK.exe" "%1"

Hope it's of use to someone.

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 14 Apr 2017, 21:22
by coffee
programlearner wrote:If it's of use to anyone, here's how to make a nested context menu with icons (I couldn't find anything doing it in the forum).
I've only tested it on Windows 7 and 10 - and it needs to be run as an administrator to work.

Image

Code: Select all

;Create a nested context menu with icons
Icon0 = %SystemRoot%\System32\shell32.dll,146
Icon1 = %SystemRoot%\System32\shell32.dll,147
Icon2 = %SystemRoot%\System32\shell32.dll,148

RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo, MUIVerb, AHK_Demo
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo, Icon, %Icon0%
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo, SubCommands, {AHK_Demo-Item1}`;{AHK_Demo-Item2}

RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item1}, Icon, %Icon1%
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item1}, MUIVerb, Open Script 1
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item1}, , %A_ScriptFullPath%

RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item2}, Icon, %Icon2%
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item2}, MUIVerb, Open Script 2
RegWrite, REG_SZ, HKEY_LOCAL_MACHINE, SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\{AHK_Demo-Item2}, , %A_ScriptFullPath%
Obviously use the icons and links you need - and also add as many items as you need.
You don't really need to mess with the explorer command store if you want unique nesting. It just spread things unnecessarily for simple stuff. It only makes sense if you are going to be using the same nested submenu with the same options in multiple places (other classes). If that nesting is unique to directory\background, just make the submenu inside the directory\background key without using the command store.

You can just create a subkey in the software\class you want, add a value called subcommands and leave it empty, then add subkey called shell under the subkey you created, then add the items that will be in the menu. I'm going to assume this is either win8+ or win7+, not sure, so it may not work if you are running a dinosaur OS.

HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo
Create the muiverb value and the data will be name that will appear in the context menu.
Create the value for the icon
Create a reg_sz value called subcommands, leave it empty

Then create a shell subkey, under AHK Demo which will contain the items/commands for that submenu.
HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo\shell\

Add the item "entry name"
HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo\shell\entry name\
The (default) value here is what you want to show on the submenu
You can add the icon value here.

HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo\shell\entry name\command\
The (default) value here is what the item will perform

You can do the same for the autohotkeyscript class under shell. Create a key that will contain the submenu, add the muiverb and the empty subcommands value, create the shell subkey, then add the different run options for each version of autohotkey you may have downloaded. Or whatever other options you may want, keeping it contained to that class.

Image
Image

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 15 Apr 2017, 02:21
by programlearner
Thanks - that's even easier.

It looks to me like there's an opportunity for someone create a beautiful context-menu editor in ahk.

If they could add an icon changer for cut/copy/paste/delete...I'd be problem free ;)

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 16 Apr 2017, 04:44
by Noesis
Another alternative is using the extendedsubcommandskey key method, for future stuff. For backwards compatibility perhaps not so much.

There has been a bug fix in windows 10 creators update, in that if you had multiple menus called from the * (all files) or Folder/Directory keys, and tried to use one from a shortcut, they would all display correctly but regardless of which custom entry you actually selected, you'd ALWAYS get the same entry executing (some said it was last entered/listed - but for me not the case, it was always the same one). Anyway it's fixed in Win 10 but I don't know about other versions.

It's similar to the subcommands method, but these can be nested (i.e. submenu in submenu) or called from anywhere. The actual "menu keys" can be anywhere in the HKCR, (which is a made from a combo of HKLM\Software\Classes & HKU\{userid}\Software\Classes), they don't have to be subkeys of the calling keys or be under a shell key themselves, but they must have a shell subkey with items containing either menus/commands. The "calling keys" just need to reference the location of a "menu key". For example,

HKEY_LOCAL_MACHINE, Software\Classes\Directory\Background\shell\AHK_Demo
Create the muiverb value and the data will be name that will appear in the context menu.
Create the value for the icon
Create a reg_sz value called "ExtendedSubCommandsKey" and the data points to the menu entry eg "AHK_Menu_Demo\AHK_Demo1"

Then create a shell key, AHK_Menu_Demo and subkey AHK_Demo1 with subkey shell which will contain the items/commands for that submenu.
HKEY_LOCAL_MACHINE, Software\Classes\AHK_Menu_Demo
HKEY_LOCAL_MACHINE, Software\Classes\AHK_Menu_Demo\AHK_Demo1
HKEY_LOCAL_MACHINE, Software\Classes\AHK_Menu_Demo\AHK_Demo1\shell\

Add the item(s) - just like you would above.
HKEY_LOCAL_MACHINE, Software\Classes\AHK_Menu_Demo\AHK_Demo1\shell\item1
Create a muiverb value and the data will be name that will appear in the context menu (or don't & set default value data as name, or leave default blank to use keyname).
Create the value for the icon (optional)
Create a subkey "command" if it's an item that does something (The "default" value within the command subkey is what the item will perform) OR
, Create another reg_sz value called "ExtendedSubCommandsKey" with data pointing to a menu entry if it's to be a menu.

Note: You don't need a key like "AHK_Menu_Demo" for this to work, but including a key like this helps to keep all your custom menus & items in one place.

Re: DllCall - Assign icon for “Copy/Cut/Paste/Delete” Windows default context menu items

Posted: 28 Apr 2017, 19:22
by jeeswg
One thing that didn't work: I tried setting the hBitmap of one menu icon to the icon of another menu icon, in the same menu. However, using the window 'Restore' icon did work on those (all) menus.

I don't know why this wouldn't work, if anyone has any ideas.

If it did work, or could be made to work, then perhaps any hBitmap owned by Explorer, could be used as a menu icon. And thus all you would need to do is to get your hBitmaps into Explorer. If there is not an easy way to do this then perhaps dll injection could work as a method of last resort.

Code: Select all

q:: ;setting the hBitmap of one menu icon to the icon of another menu icon (in the same menu) not working
SendMessage, 0x1E1, 0, 0,, ahk_class #32768 ;MN_GETHMENU
if !(hMenu := ErrorLevel)
	return
vCount := DllCall("GetMenuItemCount", Ptr,hMenu)
oArray := []
Loop, % vCount
	if (hBitmap := JEE_MenuItemPosGetBitmap(hMenu, A_Index))
		oArray.push(hBitmap)
	hBitmap := 2 ;HBMMENU_MBAR_RESTORE := 2 ;always works
		oArray.push(hBitmap)
Loop, % oArray.MaxIndex()
{
	hBitmap := oArray[A_Index]
	Loop, % vCount
		JEE_MenuItemPosSetBitmap(hMenu, A_Index, hBitmap)
	Sleep 800
	ToolTip, % A_Index
}
Sleep 2000
ToolTip
oArray := ""
MsgBox, % "done"
return

;==================================================

;works on internal + external menus
;vPos is 1-based index
JEE_MenuItemPosGetBitmap(hMenu, vPos)
{
	;MIIM_BITMAP := 0x80
	vSize := A_PtrSize=8?80:48
	VarSetCapacity(MENUITEMINFO, vSize, 0)
	NumPut(vSize, MENUITEMINFO, 0, "UInt") ;cbSize
	NumPut(0x80, MENUITEMINFO, 4, "UInt") ;fMask
	DllCall("GetMenuItemInfo", Ptr,hMenu, UInt,vPos-1, Int,1, Ptr,&MENUITEMINFO)
	return NumGet(MENUITEMINFO, A_PtrSize=8?72:44, "Ptr") ;hBitmap
}

;==================================================

;works on internal + external menus
JEE_MenuItemIDGetBitmap(hMenu, vID)
{
	;MIIM_BITMAP := 0x80
	vSize := A_PtrSize=8?80:48
	VarSetCapacity(MENUITEMINFO, vSize, 0)
	NumPut(vSize, MENUITEMINFO, 0, "UInt") ;cbSize
	NumPut(0x80, MENUITEMINFO, 4, "UInt") ;fMask
	DllCall("GetMenuItemInfo", Ptr,hMenu, UInt,vID, Int,0, Ptr,&MENUITEMINFO)
	return NumGet(MENUITEMINFO, A_PtrSize=8?72:44, "Ptr") ;hBitmap
}

;==================================================

;works on internal + external menus (but the hBitmap must belong to the process)
;vPos is 1-based index
JEE_MenuItemPosSetBitmap(hMenu, vPos, hBitmap)
{
	;MIIM_BITMAP := 0x80
	vSize := A_PtrSize=8?80:48
	VarSetCapacity(MENUITEMINFO, vSize, 0)
	NumPut(vSize, MENUITEMINFO, 0, "UInt") ;cbSize
	NumPut(0x80, MENUITEMINFO, 4, "UInt") ;fMask
	NumPut(hBitmap, MENUITEMINFO, A_PtrSize=8?72:44, "Ptr") ;hBitmap
	DllCall("SetMenuItemInfo", Ptr,hMenu, UInt,vPos-1, Int,1, Ptr,&MENUITEMINFO)
	return
}

;==================================================

;works on internal + external menus (but the hBitmap must belong to the process)
JEE_MenuItemIDSetBitmap(hMenu, vID, hBitmap)
{
	;MIIM_BITMAP := 0x80
	vSize := A_PtrSize=8?80:48
	VarSetCapacity(MENUITEMINFO, vSize, 0)
	NumPut(vSize, MENUITEMINFO, 0, "UInt") ;cbSize
	NumPut(0x80, MENUITEMINFO, 4, "UInt") ;fMask
	NumPut(hBitmap, MENUITEMINFO, A_PtrSize=8?72:44, "Ptr") ;hBitmap
	DllCall("SetMenuItemInfo", Ptr,hMenu, UInt,vID, Int,0, Ptr,&MENUITEMINFO)
	return
}

;==================================================