How to get the "shortcut keys" of a shortcut (.lnk file)?

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Awannaknow
Posts: 78
Joined: 01 Oct 2013, 08:07

How to get the "shortcut keys" of a shortcut (.lnk file)?

28 Jan 2017, 18:07

______
02- February 01 2017 Edited

[Answer]: just me's code is a better answer, from qwerty12's last post:
qwerty12 wrote:Use just me's code. It's simpler, safer (no pointer arithmetic) and doesn't have the Unicode-only restriction I imposed.

Code: Select all

#NoEnv
Target := A_ScriptDir . "\TestLnk.txt"
LnkFile := A_Desktop . "\TestLnk.lnk"
FileOpen(Target, "w").Close()
FileCreateShortcut, %Target%, %LnkFile%, , , LnkFile Test, , b
MsgBox, 0, Hotkey, % ShortcutGetHotkey(LnkFile)
ShortcutSetHotkey(LnkFile, "A", "Ctrl+Alt+Shift")
MsgBox, 0, Hotkey, % ShortcutGetHotkey(LnkFile)
ExitApp
; ======================================================================================================================
ShortcutGetHotkey(ShortcutPath) {
   SplitPath, ShortcutPath, Name, Dir
   Shell := ComObjCreate("Shell.Application")
   ShellLink := Shell.NameSpace(Dir).ParseName(Name).GetLink
   HK := ShellLink.Hotkey
   Key := GetKeyName(Format("VK{:02X}", HK & 0xFF))
   HK >>= 8
   Mods := (HK & 2 ? "Ctrl+" : "") . (HK & 4 ? "Alt+" : "") . (HK & 1 ? "Shift+" : "")
   Return (Mods . Key)
}
; ======================================================================================================================
ShortcutSetHotkey(ShortcutPath, Key, Modifiers := "") {
   Static ModValues := {Alt: 4, Ctrl: 2, Shift: 1}
   Key := GetKeyVK(Key)
   If (Key = 0)
      Return False
   ModArray := StrSplit(Modifiers, "+")
   If (ModArray.MaxIndex() < 2) || (ModArray.MaxIndex() >  3)
      Return False
   Mods := 0
   For Each, Mod In ModArray
      Mods |= (M := ModValues[Mod]) ? M : 0
   If Mods In 0,1,2,4
      Return False
   SplitPath, ShortcutPath, Name, Dir
   Shell := ComObjCreate("Shell.Application")
   ShellLink := Shell.NameSpace(Dir).ParseName(Name).GetLink
   ShellLink.Hotkey := (Key | (Mods << 8))
   ShellLink.Save
   Return True
}
______
______
01- January 30 2017 Edited

[Answer]: qwerty12:

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

; From deliberately using IID_ShellLinkW to the string manipulation, this assumes a Unicode build of AutoHotkey throughout.
; Credits:
; * https://github.com/maul-esel/AHK-Util-Funcs/blob/master/SUCCEEDED.ahk
; * https://msdn.microsoft.com/en-us/library/windows/desktop/bb774926(v=vs.85).aspx
; * the AutoHotkey source: https://github.com/Lexikos/AutoHotkey_L/blob/HEAD/source/script_autoit.cpp#L1240
; * comctl32.dll

SUCCEEDED(hr) {
	return hr != "" && hr >= 0x00
}

LOBYTE(w) {
	return w & 0xff
}

HIBYTE(w) {
	return (w >> 8) & 0xff
}

GetShortcutHotkey(lnkFile)
{
	wHotkey := 0

	if (FileExist(lnkFile)) {
		try ShellLinkW := ComObjCreate(CLSID_ShellLink := "{00021401-0000-0000-C000-000000000046}", IID_ShellLinkW := "{000214F9-0000-0000-C000-000000000046}")
		if (ShellLinkW) {
			try PersistFile := ComObjQuery(ShellLinkW, IID_IPersistFile := "{0000010b-0000-0000-C000-000000000046}") ; IPersistFile::Load
			if (PersistFile) {
				if (SUCCEEDED(DllCall(NumGet(NumGet(PersistFile+0)+5*A_PtrSize), "Ptr", PersistFile, "WStr", lnkFile, "UInt", 0)))
					DllCall(NumGet(NumGet(ShellLinkW+0)+12*A_PtrSize), "Ptr", ShellLinkW, "UShort*", wHotkey) ; IShellLink::GetHotkey
				ObjRelease(PersistFile)
			}
			ObjRelease(ShellLinkW)
		}
	}

	return wHotkey
}

HotkeyToString(hk)
{
	if (!hk) {
		msg := "None"
	} else {
		HOTKEYF_ALT := 0x04, HOTKEYF_CONTROL := 0x02, HOTKEYF_EXT := 0x08, HOTKEYF_SHIFT := 0x01

		cbMsg := VarSetCapacity(msg, 128 * 2, 0)
		wMod := HIBYTE(hk)
		if (wMod & HOTKEYF_CONTROL)
			msg .= GetKeyName("Ctrl")
		if (wMod & HOTKEYF_SHIFT)
			msg .= (msg ? " + " : "") . GetKeyName("Shift")
		if (wMod & HOTKEYF_ALT)
			msg .= (msg ? " + " : "") . GetKeyName("Alt")

		if ((wVk := DllCall("MapVirtualKeyW", "UInt", LOBYTE(hk), "UInt", 0) << 16)) {
			if (wMod & HOTKEYF_EXT)
				wVk |= 0x01000000
			if (msg)
				msg .= " + "
			currCchMsg := StrLen(msg)
			if ((ret := DllCall("GetKeyNameTextW", "Int", wVk, "Ptr", &msg+(currCchMsg*2), "Int", (cbMsg/2) - currCchMsg))) {
				if ((offset := (currCchMsg+ret) * 2) > cbMsg)
					ExitApp 1 ; die
				NumPut(0, msg, offset, "UChar"), VarSetCapacity(msg, -1)
			}
		}
	}
	return msg
}

shortcutHk := GetShortcutHotkey("<shortcut goes here>")
if (False) {
	; Using the hotkey GUI control to show the result:
	Gui, Add, Hotkey, vChosenHotkey hwndhwndChosenHotkey
	SendMessage 0x0401, %shortcutHk%, 0,, ahk_id %hwndChosenHotkey% ; HKM_SETHOTKEY
	Gui, show
} else {
	MsgBox % HotkeyToString(shortcutHk)
}

How to test it:
Fill "<shortcut goes here>" with your link, so you can test qwerty12's code, like this "C:\Path to the link file\that\you want to test\my lnk test file.lnk"
qwerty12 wrote:

Code: Select all

shortcutHk := GetShortcutHotkey("<shortcut goes here>")
______
I found something on autoit but it is way above my knowledge.
There is nothing simple to do that, really ?
Anyway I didn't found much help on it searching google: there is almost nothing!
(Or maybe, I didn't succeed at it . . .)
Here it is:
1- The autoit page:
autoit page: get the "shortcut keys" of a shortcut (.lnk file)
2-The Google search pages:
Google search: get the "shortcut keys" of a .lnk file?
Google search: how to retrieve keyboard shortcut keys from windows .lnk file?
This could be helpful to some of you.
Maybe some skilled coders could give an answer or tracks to follow.
Last edited by Awannaknow on 01 Feb 2017, 08:50, edited 3 times in total.
User avatar
Exaskryz
Posts: 2882
Joined: 17 Oct 2015, 20:28

Re: How to get the "shortcut keys" of a shortcut (.lnk file)?

29 Jan 2017, 06:21

I can only get halfway there. I tried translating the AutoIt code, but I'm getting lost. I don't understand the bitshifting - let alone if I can even skip the convert to binary steps, which needs an external function I believe.

Code: Select all

file:="C:\Users\" A_UserName "\Google Drive\Notepads\01-30.txt - Shortcut.lnk"
FileRead, thing, *m68 %file%
;Number:=NumGet(binary)
;Msgbox % Number "`n" Asc("U")
;return
MsgBox % thing
MsgBox % shortcut:=SubStr(thing,65,4) ; If the non-modifier is a letter, it is displayed here, with a suffix...
return
This got as far for me as telling me a shortcut of CTRL+SHIFT+ALT+U is U• ... The • is produced by typing alt+numpad7 come to think of it. So if each modifier is values 1, 2, and 4, those add up to 7, which could be why that character gets displayed. Could otherwise be coincidence.

Now, if you try to use something like CTRL+ALT+RIGHT, I get '♫ , which isn't helpful without the conversions that the AutoIt code is doing. They're splitting bytes in some way, and then remapping one half to the modifiers and the other half to the name of the key. (For possible inspiration, the music note is produced with Alt+14 on the numpad.)

Of interesting note, those two characters (two for each shortcut) I have in the post, could not be copied and pasted from the MsgBox. Only the first character posts, so AHK is doing its own interpretation of the final "half" character and just rendering that as full character. If that makes sense. I explained it poorly, but you can just try out that behavior for yourself.
Awannaknow
Posts: 78
Joined: 01 Oct 2013, 08:07

Re: How to get the "shortcut keys" of a shortcut (.lnk file)?

29 Jan 2017, 09:05

:bravo: Exaskryz, I hope you didn't start to get an headeach . . .
I tried your piece of code to see if it'll blow my PC up, but it could digest it by chance.
Of course I only vaguely got an idea of what you're talking about . . .
:clap:
And in no way can help, except by applauding to your performance that I would love to be able to have done myself . . .
:bravo:
It is so intriguing as why simple things like that are so difficult to accomplish? (of course it is not really a question that needs an answer).
Last edited by Awannaknow on 30 Jan 2017, 02:25, edited 1 time in total.
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: How to get the "shortcut keys" of a shortcut (.lnk file)?

29 Jan 2017, 13:26

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

; From deliberately choosing to use IID_ShellLinkW only to the string manipulation, this assumes a Unicode build of AutoHotkey is used throughout.

; Definitions of standard WINAPI macros follow
; https://github.com/maul-esel/AHK-Util-Funcs/blob/master/SUCCEEDED.ahk
SUCCEEDED(hr) {
	return hr != "" && hr >= 0x00
}

LOBYTE(w) {
	return w & 0xff
}

HIBYTE(w) {
	return (w >> 8) & 0xff
}

GetShortcutHotkey(lnkFile)
{
	; Derived from the AutoHotkey source: https://github.com/Lexikos/AutoHotkey_L/blob/HEAD/source/script_autoit.cpp#L1240
	wHotkey := 0

	if (FileExist(lnkFile)) {
		; https://msdn.microsoft.com/en-us/library/windows/desktop/bb774926(v=vs.85).aspx
		; IShellLink appears to be the canonical - and it's *documented* - interface for reading & writing to/from shortcuts,
		; but it's accessible over COM only, so create an instance of the interface that lets me use the GetHotkey method linked above.
		; Read https://maul-esel.github.io/tutorials/COM-Interfaces.html for good information on the COM-specific stuff
		try ShellLinkW := ComObjCreate(CLSID_ShellLink := "{00021401-0000-0000-C000-000000000046}", IID_ShellLinkW := "{000214F9-0000-0000-C000-000000000046}")
		if (ShellLinkW) {
			; if you look at the methods of IShellLink, it doesn't have any methods to point it to an existing shortcut file
			; but it is mentioned somewhere on MSDN that it implements the IPersistFile interface, which does allow it to load an existing file, so ask for that iface
			try PersistFile := ComObjQuery(ShellLinkW, IID_IPersistFile := "{0000010b-0000-0000-C000-000000000046}")
			if (PersistFile) {
				if (SUCCEEDED(DllCall(NumGet(NumGet(PersistFile+0)+5*A_PtrSize), "Ptr", PersistFile, "WStr", lnkFile, "UInt", 0))) ; IPersistFile::Load
					DllCall(NumGet(NumGet(ShellLinkW+0)+12*A_PtrSize), "Ptr", ShellLinkW, "UShort*", wHotkey) ; IShellLink::GetHotkey - the result is stored in the wHotkey variable
				ObjRelease(PersistFile) ; Clean up: release our references on the COM objects so that they eventually get released and return the memory used back
			}
			ObjRelease(ShellLinkW)
		}
	}

	return wHotkey
}

; Convert the number representation of a set hotkey in a shortcut file to the actual keys in text form, 
; mostly like how the hotkey control of comctl32.dll does it
HotkeyToString(hk)
{
	if (!hk) {
		msg := "None"
	} else {
		; Constants from the MSDN page linked above - see there for the descriptions
		HOTKEYF_ALT := 0x04, HOTKEYF_CONTROL := 0x02, HOTKEYF_EXT := 0x08, HOTKEYF_SHIFT := 0x01

		; Like I said, this is Unicode only. There's nothing stopping this working for ANSI builds, I just don't want to do the extra work
		; for a build that in 2017 nobody really uses
		if (!A_IsUnicode)
			return ""

		; Reserve space for the msg variable to store 126 actual characters (two characters are needed for the string's null terminator)
		; Multiply the size by two because each character occupies two bytes
		cbMsg := VarSetCapacity(msg, 128 * 2, 0)
		wMod := HIBYTE(hk) ; from the MSDN page: "the modifier flags are in the high-order byte". Use a standard WINAPI macro (function here) to get it
		; Test the listed modifier keys on the MSDN page to determine which are used in the shortcut:
		; GetKeyName doesn't really work the same as GetKeyNameTextW below (note how the shortcut properties show "CTRL + ALT" while this shows "Control + Alt").
		; Maybe consider removing the really superfluous uses of GetKeyName here and just use hardcoded text constants here of "CTRL" etc.?
		if (wMod & HOTKEYF_CONTROL)
			msg := GetKeyName("Ctrl")
		if (wMod & HOTKEYF_SHIFT)
			msg .= (msg ? " + " : "") . GetKeyName("Shift") ; Add the textual result of GetKeyName to the msg string, but if msg already had text in it (i.e. by it also using the Ctrl modifier), then also add " + " before adding the GKN result
		if (wMod & HOTKEYF_ALT)
			msg .= (msg ? " + " : "") . GetKeyName("Alt")

		; Read https://msdn.microsoft.com/en-us/library/windows/desktop/ms646306(v=vs.85).aspx for proper information
		; Use the MapVirtualKeyW function to turn the virtual key code stored in the "low-order byte [of the number returned by IShellLink::GetHotkey]"
		; into a scan code - needed for GetKeyNameTextW below to work
		if ((sc := DllCall("MapVirtualKeyW", "UInt", LOBYTE(hk), "UInt", 0, "UInt") << 16)) { ; MAPVK_VK_TO_VSC
			if (wMod & HOTKEYF_EXT)
				sc |= 0x01000000 ; if it's an extended keycode, OR-ing this is apparently required
			if (msg)
				msg .= " + "
			currCchMsg := StrLen(msg) ; get the amount of characters currently in the msg string
			; https://msdn.microsoft.com/en-us/library/windows/desktop/ms646300(v=vs.85).aspx - if you look here, this function expects the scan code from bit 16 - hence the shift above
			; Write the result of this function directly into the msg string - &msg+(currCchMsg*2) puts the pointer at the null terminator character of what's already in there,
			; which GetKeyNameTextW is free to overwrite with the name of the shortcut key. The second parameter is the available amount of characters for GKNT to write - we made space for 128
			; of them, subtract the amount of characters used already in msg to reflect the actual remaining space
			if ((ret := DllCall("GetKeyNameTextW", "Int", sc, "Ptr", &msg+(currCchMsg*2), "Int", (cbMsg/2) - currCchMsg))) {
				; A lame (and possibly incorrect - I'm not very experienced at this sort of thing) buffer overflow check
				; it's unlikely to happen since GetKeyNameTextW checks itself, but just in case, see if GKNT wrote beyond the space we set aside for the msg variable
				offset := (currCchMsg+ret) * 2
				if (offset > cbMsg)
					ExitApp 1 ; die
				; Not needed: null-terminate the string ourselves just in case and tell AutoHotkey the string has been changed by a DllCall
				if (offset + 4 < cbMsg)
					NumPut(0, msg, offset, "UShort")
				VarSetCapacity(msg, -1)
			}
		}
	}
	return msg
}

shortcutHk := GetShortcutHotkey("<shortcut goes here>")
if (True) {
	MsgBox % HotkeyToString(shortcutHk)
} else {
	; Using the hotkey GUI control instead to show the result:
	Gui, Add, Hotkey, vChosenHotkey hwndhwndChosenHotkey
	SendMessage, 0x0401, %shortcutHk%, 0,, ahk_id %hwndChosenHotkey% ; HKM_SETHOTKEY
	Gui, Show	
}

EDIT:
Awannaknow wrote:Hi qwerty12,
Veeeeryyyy coooool ! ! !
:P
Of course I don't understand anything at all: but it works ! ! !
Hey,

No problem. While I can't say that I'm experienced here, I've added comments of what I do understand in the hope that it helps a little :-)
Last edited by qwerty12 on 30 Jan 2017, 08:36, edited 2 times in total.
Awannaknow
Posts: 78
Joined: 01 Oct 2013, 08:07

Re: How to get the "shortcut keys" of a shortcut (.lnk file)?

30 Jan 2017, 03:18

Hi qwerty12,
Veeeeryyyy coooool ! ! !
:P
Of course I don't understand anything at all: but it works ! ! !
Seems you did a lot of work to get it working: a big thank you for your help.
How to test it:
Fill "<shortcut goes here>" with your link, so you can test qwerty12's code, like this "C:\Path to the link file\that\you want to test\my lnk test file.lnk"
qwerty12 wrote:

Code: Select all

shortcutHk := GetShortcutHotkey("<shortcut goes here>")
just me
Posts: 9442
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: How to get the "shortcut keys" of a shortcut (.lnk file)?

30 Jan 2017, 10:53

Awannaknow wrote:Of course I don't understand anything at all: but it works ! ! !
Maybe it's a bit more easy when using the ShellLinkObject:

Code: Select all

#NoEnv
Target := A_ScriptDir . "\TestLnk.txt"
LnkFile := A_Desktop . "\TestLnk.lnk"
FileOpen(Target, "w").Close()
FileCreateShortcut, %Target%, %LnkFile%, , , LnkFile Test, , b
MsgBox, 0, Hotkey, % ShortcutGetHotkey(LnkFile)
ShortcutSetHotkey(LnkFile, "A", "Ctrl+Alt+Shift")
MsgBox, 0, Hotkey, % ShortcutGetHotkey(LnkFile)
ExitApp
; ======================================================================================================================
ShortcutGetHotkey(ShortcutPath) {
   SplitPath, ShortcutPath, Name, Dir
   Shell := ComObjCreate("Shell.Application")
   ShellLink := Shell.NameSpace(Dir).ParseName(Name).GetLink
   HK := ShellLink.Hotkey
   Key := GetKeyName(Format("VK{:02X}", HK & 0xFF))
   HK >>= 8
   Mods := (HK & 2 ? "Ctrl+" : "") . (HK & 4 ? "Alt+" : "") . (HK & 1 ? "Shift+" : "")
   Return (Mods . Key)
}
; ======================================================================================================================
ShortcutSetHotkey(ShortcutPath, Key, Modifiers := "") {
   Static ModValues := {Alt: 4, Ctrl: 2, Shift: 1}
   Key := GetKeyVK(Key)
   If (Key = 0)
      Return False
   ModArray := StrSplit(Modifiers, "+")
   If (ModArray.MaxIndex() < 2) || (ModArray.MaxIndex() >  3)
      Return False
   Mods := 0
   For Each, Mod In ModArray
      Mods |= (M := ModValues[Mod]) ? M : 0
   If Mods In 0,1,2,4
      Return False
   SplitPath, ShortcutPath, Name, Dir
   Shell := ComObjCreate("Shell.Application")
   ShellLink := Shell.NameSpace(Dir).ParseName(Name).GetLink
   ShellLink.Hotkey := (Key | (Mods << 8))
   ShellLink.Save
   Return True
}
Awannaknow
Posts: 78
Joined: 01 Oct 2013, 08:07

Re: How to get the "shortcut keys" of a shortcut (.lnk file)?

31 Jan 2017, 07:30

Thanks to both of you, I'll "decode" that in details later on, I already had a quick look at it, when I got time enough, because I know I'll need a lot of time to try to understand all that.
qwerty12 wrote: EDIT:
Awannaknow wrote:Hi qwerty12,
Veeeeryyyy coooool ! ! !
:P
Of course I don't understand anything at all: but it works ! ! !
Hey,
No problem. While I can't say that I'm experienced here, I've added comments of what I do understand in the hope that it helps a little :-)
just me wrote: Maybe it's a bit more easy when using the ShellLinkObject:
Last edited by Awannaknow on 01 Feb 2017, 08:52, edited 1 time in total.
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
Contact:

Re: How to get the "shortcut keys" of a shortcut (.lnk file)?

31 Jan 2017, 17:33

Use just me's code. It's simpler, safer (no pointer arithmetic) and doesn't have the Unicode-only restriction I imposed. (But thanks for making this topic, it was fun finding out how to do it!)
Awannaknow
Posts: 78
Joined: 01 Oct 2013, 08:07

Re: How to get the "shortcut keys" of a shortcut (.lnk file)?

01 Feb 2017, 08:37

Thanks for the info qwerty12, now anyone will know which one to use.
Awannaknow
Posts: 78
Joined: 01 Oct 2013, 08:07

Re: How to get the "shortcut keys" of a shortcut (.lnk file)?

02 Feb 2017, 03:23

Just me, or anyone, any chance of having any piece of code or pointers to the right direction for doing this:
Filling the fields of an existing .lnk properties, rather than deleting it and recreating a new one.
Mainly Target, but others fields are welcome too (keyboard shortcut or start in fields for example).
Here is the code I made, but it is more a trick than a real solid coding solution . . .:

Code: Select all

SetTitleMatchMode, 2 ; for  an approximate matching title name
TargetName= The properties partial title
Run, properties "%1%", %OutDir%, Hide ; hide is not hiding anything as it works only on "top" windows
WinWait, %TargetName% ahk_class #32770
WinHide, %TargetName% ahk_class #32770 ; it hide the properties window, but we can see it when it is created, there is a delay 
;before it gets hidden
modified_path=%A_AhkPath% "%A_ScriptFullPath%" "%OutTarget%" ;The target field needs big changes: it goes from something like: 
;"a/path/to/a/file.exe" up to: Path/to/autohotkey.exe "path/to/script.ahk" "a/path/to/a/file.exe"
t:=Clipboard ; save clipboard before using it
Clipboard:=modified_path
WinActivate, %TargetName% ahk_class #32770
SendInput  ^v ; fill in the target field . . . not the most reliable method
Clipboard:=t ; restore clipboard
WinActivate, %TargetName% ahk_class #32770
ControlClick, OK, %TargetName% ahk_class #32770 ; click the ok button . . .
just me wrote: Maybe it's a bit more easy when using the ShellLinkObject:

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: mcd, ReyAHK and 244 guests