FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?) Topic is solved

Get help with using AutoHotkey and its commands and hotkeys
tamo
Posts: 7
Joined: 11 Jul 2019, 04:23

FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

11 Jul 2019, 09:56

Hi,

I'm new here, so I must be doing something wrong to which you can easily point. ;)
Please help me.

I want to open FileSelectFile with RootDir set to "MyVideo" folder.
And I CAN do it as long as I use AHK U32.

Code: Select all

VarSetCapacity(myvideos, 520, 0)
SetGUID(rfid, "{18989B1D-99B5-455B-841C-AB7C74E4DDFC}")
DllCall("Shell32\SHGetKnownFolderPath", "UInt", &rfid, "UInt", 0, "UInt", 0, "UIntP", myvideos)
vdir := StrGet(myvideos)

FileSelectFile, s, M3, %vdir%\Radeon Relive, open, rec (2019*.mp4)

return

SetGUID(ByRef GUID, String) {
    VarSetCapacity(GUID, 16, 0)
    StringReplace,String,String,-,,All
    NumPut("0x" . SubStr(String, 2,  8), GUID, 0,  "UInt")   ; DWORD Data1
    NumPut("0x" . SubStr(String, 10, 4), GUID, 4,  "UShort") ; WORD  Data2
    NumPut("0x" . SubStr(String, 14, 4), GUID, 6,  "UShort") ; WORD  Data3
    Loop, 8
        NumPut("0x" . SubStr(String, 16+(A_Index*2), 2), GUID, 7+A_Index,  "UChar")  ; BYTE  Data4[A_Index]
}
cf. https://github.com/shajul/Autohotkey/blob/master/Scriplets/KnownFolders.ahk


But the same script doesn't work when it has been converted to EXE.
How to reproduce:
  1. Convert the AHK file to an EXE.
  2. Run the EXE. The first run opens "MyVideo" successfully. :)
  3. Click another folder in the FileSelectFile window.
  4. Select some file(s) and finish the window. The first run ends happily. :bravo:
  5. Run the same EXE again. I expect to see "MyVideo" again.
  6. But you see the folder you clicked in the previous run. :crazy:
  7. Rename the EXE and run it.
  8. You see "MyVideo" again. :wtf:
Whenever you run the AHK file, you will see "MyVideo".
But when you run the EXE file more than once, you will see the last selected folder.
Renaming the EXE resets the RootDir.

Is the above-mentioned behavior expected?
Can I make EXE more forgetful? (as forgetful as AHK)


Moreover, I cannot make the code work with U64.
I don't know whether it is relevant to the real cause, though.

Thanks in advance,
Tamo
pneumatic
Posts: 287
Joined: 05 Dec 2016, 01:51

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

11 Jul 2019, 21:30

This was my workaround for 64-bit Unicode compiled .exe

Code: Select all

FileSelectFile , SelectedFile ,, % GetReliablePath( Path . "\Folder" ) , Select File , Videos (*.mp4)

GetReliablePath(Path){ 
static LastPath

	If (Path = LastPath){	
		StringRight , LastChar , Path , 1	
		If (LastChar = "\")
			StringTrimRight , Path , Path , 1
		Else
			Path .= "\"	
	}
	LastPath := Path
	return Path	
}
tamo
Posts: 7
Joined: 11 Jul 2019, 04:23

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

12 Jul 2019, 00:51

pneumatic wrote:
11 Jul 2019, 21:30
This was my workaround for 64-bit Unicode compiled .exe

Code: Select all

FileSelectFile , SelectedFile ,, % GetReliablePath( Path . "\Folder" ) , Select File , Videos (*.mp4)

GetReliablePath(Path){ 
static LastPath

	If (Path = LastPath){	
		StringRight , LastChar , Path , 1	
		If (LastChar = "\")
			StringTrimRight , Path , Path , 1
		Else
			Path .= "\"	
	}
	LastPath := Path
	return Path	
}
Wow, it works! Thank you, pneumatic!

With some experiments and in hindsight, I just need the trailing "\".
(You don't need GetReliablePath, pneumatic.)

That's strange because the official document says "C:\My Pictures" is OK (not "C:\My Pictures\"). :eh:
https://www.autohotkey.com/docs/commands/FileSelectFile.htm

Anyway, my script now works. Thank you!
pneumatic
Posts: 287
Joined: 05 Dec 2016, 01:51

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

12 Jul 2019, 02:19

tamo wrote:
12 Jul 2019, 00:51
With some experiments and in hindsight, I just need the trailing "\".
(You don't need GetReliablePath, pneumatic.)
I found that it can still fail if you don't change the starting directory every time. The purpose is to stop Windows from trying to be clever:
https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamea wrote: The algorithm for selecting the initial directory varies on different platforms.
Windows 7:
If lpstrInitialDir has the same value as was passed the first time the application used an Open or Save As dialog box, the path most recently selected by the user is used as the initial directory.
edit: actually it seems my workaround would fail under the following scenario:

Launch script
FileSelectFile C:\Folder1 -> user manually navigates to C:\Folder2 -> OK
FileSelectFile C:\Folder3 -> OK
FileSelectfile C:\Folder1 -> Windows sees it "has the same value as was passed the first time the application used an Open or Save As dialog box" and gives you instead "the path most recently selected" which is actually Folder3

edit2: tested the above with compiled .exe and it works fine, no idea why :wtf:
tamo
Posts: 7
Joined: 11 Jul 2019, 04:23

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

12 Jul 2019, 08:42

pneumatic wrote:
12 Jul 2019, 02:19
tamo wrote:
12 Jul 2019, 00:51
With some experiments and in hindsight, I just need the trailing "\".
(You don't need GetReliablePath, pneumatic.)
I found that it can still fail if you don't change the starting directory every time. The purpose is to stop Windows from trying to be clever:
https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamea wrote: The algorithm for selecting the initial directory varies on different platforms.
Windows 7:
If lpstrInitialDir has the same value as was passed the first time the application used an Open or Save As dialog box, the path most recently selected by the user is used as the initial directory.
You are right, again. ;) Just adding "\" sometimes works, sometimes fails.
And oh, your quote from Microsoft document is very inspiring. Thanks!
edit: actually it seems my workaround would fail under the following scenario:

Launch script
FileSelectFile C:\Folder1 -> user manually navigates to C:\Folder2 -> OK
FileSelectFile C:\Folder3 -> OK
FileSelectfile C:\Folder1 -> Windows sees it "has the same value as was passed the first time the application used an Open or Save As dialog box" and gives you instead "the path most recently selected" which is actually Folder3

edit2: tested the above with compiled .exe and it works fine, no idea why :wtf:
Hehehe, I even found that an EXE which seems reliably working can stop working if it is just copied to another folder. :headwall:

I'm on Windows 10 Home 64bit 1903 (18362.239).
Autohotkey 1.1.30.03.
tamo
Posts: 7
Joined: 11 Jul 2019, 04:23

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

12 Jul 2019, 09:21

Other useful information sources: I think it's too much work for me to introduce IFileDialog just in order to use SetFolder.
So I'm trying "Folder\Filename" instead of "Folder" or "Folder\". For example, "\Radeon Relive\xxxx.xx.xx.mp4"

edit:
No! Specifying filename doesn't fix it.
If there is a complete solution, it should have something to do with \HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\*. :facepalm:
Helgef
Posts: 4031
Joined: 17 Jul 2016, 01:02
Contact:

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)  Topic is solved

12 Jul 2019, 10:43

In the first post, the code should be something like this,

Code: Select all

SetGUID(rfid, "{18989B1D-99B5-455B-841C-AB7C74E4DDFC}")

; function documentation:
;	- https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath

pStr := 0
if ret := DllCall("Shell32\SHGetKnownFolderPath", "ptr", &rfid, "UInt", 0, "ptr", 0, "ptr*", pStr, "int")
	throw exception("SHGetKnownFolderPath fail: " . ret)
msgbox % strget(pStr, "UTF-16")
CoTaskMemFree(pStr)

;FileSelectFile, s, M3, %vdir%\Radeon Relive, open, rec (2019*.mp4)

return

CoTaskMemFree(pv){
	return dllcall("Ole32.dll\CoTaskMemFree", "ptr", pv)
}

SetGUID(ByRef GUID, String) {
    VarSetCapacity(GUID, 16, 0)
    StringReplace,String,String,-,,All
    NumPut("0x" . SubStr(String, 2,  8), GUID, 0,  "UInt")   ; DWORD Data1
    NumPut("0x" . SubStr(String, 10, 4), GUID, 4,  "UShort") ; WORD  Data2
    NumPut("0x" . SubStr(String, 14, 4), GUID, 6,  "UShort") ; WORD  Data3
    Loop, 8
        NumPut("0x" . SubStr(String, 16+(A_Index*2), 2), GUID, 7+A_Index,  "UChar")  ; BYTE  Data4[A_Index]
}
Cheers.
tamo
Posts: 7
Joined: 11 Jul 2019, 04:23

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

12 Jul 2019, 11:02

Helgef wrote:
12 Jul 2019, 10:43

Code: Select all

; function documentation:
;	- https://docs.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath

pStr := 0
if ret := DllCall("Shell32\SHGetKnownFolderPath", "ptr", &rfid, "UInt", 0, "ptr", 0, "ptr*", pStr, "int")
	throw exception("SHGetKnownFolderPath fail: " . ret)
msgbox % strget(pStr, "UTF-16")
CoTaskMemFree(pStr)
Thanks, you are clean. ;)
I don't mind memleaks so much when writing in AHK. But appropriate types for DllCalls are important. Appreciated! :D

Also I've found I can get GUID values using ole32. So I can reduce the number of lines removing SetGUID function.

Code: Select all

VarSetCapacity(rfid, 16, 0)
DllCall("ole32\CLSIDFromString", "WStr", "{18989B1D-99B5-455B-841C-AB7C74E4DDFC}", "Ptr", &rfid)
Well, ANSI version has to prepare the Wstr rather than directly giving to DllCall? I'm not clean enough. ;)
Helgef
Posts: 4031
Joined: 17 Jul 2016, 01:02
Contact:

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

12 Jul 2019, 11:45

Dllcall will convert your ANSI string to Unicode when you specify wstr, it should work as is.
tamo
Posts: 7
Joined: 11 Jul 2019, 04:23

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

12 Jul 2019, 18:39

Helgef wrote:
12 Jul 2019, 11:45
Dllcall will convert your ANSI string to Unicode when you specify wstr, it should work as is.
Thanks, Helgef. You are cool, kind, and smart. I've come to like AutoHotkey better than before.
pneumatic
Posts: 287
Joined: 05 Dec 2016, 01:51

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

15 Oct 2019, 07:39

tamo wrote:
12 Jul 2019, 08:42
Just adding "\" sometimes works, sometimes fails.

I finally got to the bottom of this.

So the first time an application opens a FileSelectFile, Windows saves the starting directory parameter to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\FirstFolder

Then, if your app opens a FileSelectFile with the above starting directory, Windows will instead give you the most recent path that the user previously selected, which is stored in HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU

I am able to reproduce this behaviour in ahk, so it appears to function exactly as Windows describes in the documentation.

Appending "\" or other things like "\\\" or ".\" to the starting directory alone won't work because it can't always circumvent this logic, regardless of how clever you try to be, because there will always be a possible scenario where the application could select the registry's "FirstFolder" value, since the value gets stored between app launches. i.e even your app's very first FileSelectFile starting directory will be compared to the registry value from the previous run, so it doesn't "start fresh" each time, and so you can't know for sure what the FirstFolder value is without keeping a copy between each time the application runs.

Therefore the only ways to overcome it require read/write/deleting external files on disk, which is quite disappointing, but appears to be necessary to get around this problem.

Possible solutions:

Solution 1.
The first time your app opens a FileSelectFile, store your own private copy of the starting directory on disk. Every time you call FileSelectFile, check if it the starting directory matches the one you saved to disk, and if it does, append "\" to it.

Solution 2.
Same as above, but instead of reading it from your own private copy, read it straight from the registry. Iterate through each value inside the FirstFolder regkey and find the one which contains the full path to your script's executable, and that should be the one for your app.

Solution 3.
Same as above, but just delete your app's registry value instead of checking to see if it matches.

Solution 4.
If you are feeling lazy, just delete the entire "FirstFolder" key every time you use FileSelectFile. This will delete "FirstFolder" for all apps on the system. But who cares? The behaviour is retarded and shouldn't exist in the first place :lol: However it won't fix it properly for other apps because they could open and close their own dialogues and then the problem returns for those apps until your app happens to delete the key again.


I was leaning towards solution 3 because it's quite easy, but solution 1 should be more reliable since you have your own copy of FirstFolder and aren't relying on assumptions about Windows registry. eg. different versions of Windows or languages might change the formatting of the key value, or antivirus software might block you from deleting reg keys altogether, or a future version of Windows might change the regkey path.

So I will probably go with solution 1.

If anyone can figure out a clever way to fudge the FileSelectFile starting directory without relying on any disk read/write/delete operations, I'd love to hear it!

Note: I am aware that you can pass FileSelectFile a file path instead of a directory, but that's not quite good enough for me as I want the dialogue's text input to start off blank (especially when the user just wants to open a file; for saving a file, pre-inputting something like "untitled.ext" might be acceptable). i.e I want a complete solution, not half-measure, and it has to be 100% reliable!

edit: and I've just realised Solution 1 won't be 100% reliable because the user could move the app to another location on disk, which would then get its own reg value, which might not match your private value that you previously stored. So you would need to store a copy of the previous A_ScriptDir and if it changes then do what exactly? What if the user had moved it to that location before, then it might still be wrong since we can't assume that it started fresh. Maybe interacting with the registry might be necessary after all...

edit2: I will probably go with solution 1 but append to file a series of records "A_ScriptDir has FirstFolder" so we know which location has which firstfolder. Essentially making our own copy of what Windows itself stores in the registry so we can avoid having to interact with the registry and whatever problems that might entail.
pneumatic
Posts: 287
Joined: 05 Dec 2016, 01:51

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

18 Oct 2019, 14:28

I've encountered some inconsistent behaviour in the way Windows writes the FirstFolder value to registry.

i.e

Delete registry's FirstFolder key -> run app -> FileSelectFile , , , C:\Path1 -> C:\Path1 gets written to registry FirstFolder value.

FileSelectFile , , , (no path) -> select C:\Path2\File.ext -> OK -> C:\Path2 gets written to registry LastVisitedPidlMRU value.

At this point if you do another FileSelectFile , , , C:\Path1 , per Windows documentation, it will give you C:\Path2, which is consistent with Windows documentation.

But then if I do this:

FileSelectFile , , , C:\Path3 -> the script's exe path gets written to the reg key, and from that point on the issue appears to disappear completely, even if you specify script's exe path as starting directory.

I tried this with compiled and non-compiled, and behaviour was the same.

edit2: after some time thinking about the logic of this, it seems that unfortunately we STILL need to use an external file on disk to remember the value between app launches :x otherwise we can't circumvent the Windows behaviour under all possible scenarios.
pneumatic
Posts: 287
Joined: 05 Dec 2016, 01:51

Re: FileSelectFile sometimes ignores my Rootdir (ahk2exe or 32/64bit issue?)

19 Oct 2019, 20:04

Here is my workaround if anyone's interested. Please see the "important" note in the comment, otherwise it may not work.

Code: Select all

FileSelectFile ,,, % GetFileSelectFileDir( "C:\StartingDirectory" )

GetFileSelectFileDir( ThisPath:="" ){ 

/*
Workaround issue where Windows ignores your specified starting directory of FileSelectFile:

"If lpstrInitialDir has the same value as was passed the first time the application used an Open or Save As
dialog box, the path most recently selected by the user is used as the initial directory."

https://docs.microsoft.com/en-us/windows/win32/api/commdlg/ns-commdlg-openfilenamea

Windows stores "first folder" in:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\FirstFolder

"Most recently selected" is stored in:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU

IMPORTANT: if your script has already opened a FileSelectFile dialogue with a non-null starting folder prior
to using this function, you will need to delete the "FirstFolder" regkey above, otherwise this function will
not be in sync with the regkey value.

This function should be effective even after moving your script to another location, or to another Windows
installation, since it stores a "FirstFolder" for each A_ScriptDir, and is stored inside %A_AppDataCommon%
which is unique to each Windows installation.

Note: Windows also appears to have a bug where if the regkey "FirstFolder" is non-null, not equal to the
script's executable path, and the first file select dialogue opened by the app has a starting directory
different to that stored in the regkey, Windows sets the regkey to the script's executable path, and from
that point onwards Windows will always obey the starting directory you specify.  This does not affect the
reliability of this function, nor does this function assume this behaviour.
*/

static IsFirstDialogue := 1
	
	;Remove any trailing backslashes which would interfere with this function, since it works
	;by appending a backslash to make the path different to the "FirstFolder" regkey value, while still
	;pointing to the same location.	
	loop % StrLen(ThisPath)
	{
		StringRight , LastChar , ThisPath, 1
		If (LastChar = "\")
			StringTrimRight , ThisPath , ThisPath, 1
		Else
			break	
	}
	
	If (ThisPath = "")
		return  ;In this case, Windows will give you the "most recently selected" value in the regkey,
				;and will not modify the "FirstFolder" regkey
			
	IniRead , FirstFolder , %A_AppDataCommon%\AutoHotkey\FileSelectDialogue.ini , %A_ScriptDir% , FirstFolder
	(FirstFolder = "ERROR") ? (FirstFolder := "")

	
	If (ThisPath = FirstFolder){
		StringRight , LastChar , ThisPath, 1		
		If (LastChar = "\")
			StringTrimRight , ThisPath , ThisPath, 1
		Else
			ThisPath .= "\"
	}
		
	If (IsFirstDialogue){	
		IniWrite , %ThisPath%, %A_AppDataCommon%\AutoHotkey\FileSelectDialogue.ini , %A_ScriptDir% , FirstFolder
		IsFirstDialogue := 0	
	}

	return ThisPath

}

Return to “Ask For Help”

Who is online

Users browsing this forum: Bing [Bot], MIRKOSOFT, Tom Harding and 248 guests