SHGetFileInfo performance issues - c++

Here is an excerpt from the Windows documentation about the SHGetFileInfo() function:
You should call this function from a background thread. Failure to do so could cause the UI to stop responding.
Does this apply to extracting the folder icons as well?
One of our applications resembles Windows Explorer, and we pursue two antagonistic goals: to support as many Windows Explorer's features as possible, and to be as fast as possible. Having the latter in mind, I assigned to each folder the default icon (obtained with the help of the SHGFI_USEFILEATTRIBUTES flag). But after some time it turned out, that a few of our customers use custom folder icons.
So, should I create new threads to calculate the icon for each folder, or is there a way to quickly extract a folder icon in the main thread, given the fact that the number of custom folder icons is negligible?
For example, a way to retrieve only cached icons could be a solution, I think. There is a function IShellItemImageFactory::GetImage(), which allows to obtain only cached icons, but it unfortunately returns HBITMAP instead of HICON.

It seems that I found an appropriate solution.
First of all, I prepare and store a pair containing the default folder icon and its index in the system image list. Then, when the request for a folder icon arrives, I immediately return the default icon, and launch a thread from the Windows thread pool.
In the launched thread I calculate the actual folder icon and its index with the help of the SHGetFileInfo API. If the index is the same as index of the default icon, I destroy the icon. Only if the obtained index differs from the default icon index, I post notification to the control in the main thread.
In this way the control is notified only about folders that really have custom icons, and the main thread is not overburdened with redundant notifications.

Related

Is it possible to add an icon to the Windows System Image List?

I display objects, some of which are files and folders in TTreeView, TListView and a few other custom controls.
When a folder is selected, I display its contents (files and folders) in TListViewand I use the Windows System Image List to assign an icon. I call SHGetFileInfoW to obtain an icon index. SHGetFileInfoW is also used to obtain the System Image List Handle:
SHFILEINFOW info ;
DWORD Flags = (SHGFI_SMALLICON | SHGFI_SYSICONINDEX | SHGFI_OVERLAYINDEX) // for instance
ImageList->Handle = SHGetFileInfoW( L"",
0,
&info,
sizeof(SHFILEINFOW),
Flags
) ;
My program also keeps its own ImageList with icons, since it displays more than just files and folders. In TreeView for instance a combination of 'other objects' and folder objects will be shown:
To keep folder icons consistent (same) between TreeView and ListView on different versions of the Windows OS, I copy the Windows System folder icon to my own ImageList during program start.
This way both TreeView and ListView use the same folder icon, and the icon is that of the OS the software is running on.
This has worked well for many years across many OS, from Win2K to W11. However I'd like to try the reverse approach now IF at all possible. I'd like to use my own folder icon in both ListView and TreeView. For TreeView this is not a problem, but for ListView it is an issue in every situation where files and folders are displayed together, since I use the Windows System Image List to display the file icons and hence also folder icons.
I can think of a workaround where I create the needed registry entries to register a very unique file extension that is associated with a folder icon in my executable. I could then use that for folders. But it's a workaround that may not always work perfectly if Windows doesn't refresh immediately etc.
So, the question, is it at all possible to add an icon to the System Image List during program execution ? Is there a Windows API call that does this ? If so I could use that Index then to display folder icons in ListView.
Yes and no.
First off, in Windows NT based systems, the system image list is per-process and is not a full copy of the real list. In Windows 95 it really was global. This means the design never intended for people to add extra HICONs on their own directly into the list.
While it is called the system image list, the only thing it stores are file type icons and the shell extension overlays, it is not a general icon list.
There are two ways to add icons to the list:
The common way is to use one of the shell functions (like SHGetFileInfo) to ask for information about a file path or extension.
The other way is to call Shell_GetCachedImageIndex. This function used to be undocumented and was only documented because of the DOJ trial.
My suggestion: Use your own custom imagelist with your custom images. In slot 0 place an empty icon that you draw on top of with custom draw (not owner-draw).

How do you code merging different executables into a single window frame? Like browsers do?

OK, so a noob question here: How do you code this functionality that browsers have for example? You open a chrome browser for example and you can open multiple tabs open. Then you can move one tab out of the window and it becomes another window, having its own separate process. Then you can drag that tab into another window and they become one frame? Similar to docking in windows applications, but how do you do it with executables?
Windows-specific answer, though I think other OSs work pretty much the same: the HWND handle that you get for a window is global. If you send its numeric value to a different process, that process can use it to do things with the window: get its information, resize it, even draw on it. What it can't do is replace its event handler function.
To get process separation like browsers have nowadays, the key is to create a container window and send the handle to the child. The child then creates its own window as a child of the container. The child window simply fills out the entire content area of the container.
This way, the content process is contained within the parent's window, but can handle events.
Now, if you want to drag out a tab into its own top-level window, the parent process creates a new top-level window with all the UI inside, and then re-parents the content container to this new top-level window. The content child follows along.
I can't tell you how to code it, you should search the feature inside the chromium code to know how it's coded but I can tell you how it works:
Inside chromium every tab, extension, utility, etc is a process, each one of these processes is child of the "Browser" process, the "Browser" process manages everything (creating new windows, opening new tabs, closing tabs, destroying windows etc) so, for example, whenever you open a new instance of chromium you are telling the "Browser" process to create a new tab and put it inside a new window.
Every window is managed by the "Browser" process and every tab is managed by a process that is child of the main process.
Now to reply your question: when you drag & drop a tab outside a window you're triggering an event that is caught by the "Browser" process which then create a new window and assign the tab to the new window.
Those informations should give you a hint on how you could develop this feature yourself.
If you want to know more about the chromium architecture I suggest you to read how chromium is designed at https://www.chromium.org/developers/design-documents

Save Workspace MFC C++ MDI

I have a MFC MDI application. The app can have 2 or more dialogs open. I want to implement the "Save Workspace" feature so that the user, when opening the workspace next time, opens the dialogs that were opened when he saved and closed the workspace. How to do this?
The functionality is built into the CMDIFrameWndEx implementation. SaveMDIState stores the current setup, while LoadMDIState restores it. The documentation also explains, how to persist the position and size of the frame window itself, in case that's something you want, too.
Generally this is accomplished by storing the current state of the application into some sort of persistent store. Depending on your architecture this could be a local configuration file (xml, json, etc) stored under the Windows User's profile directory (you could also use the Windows Registry), a remote store such as a database could be used or perhaps such state could be stored into cloud storage. Just depends on how the application currently handles configuration and how universal you want to make the state storage.
You will want to implement some sort or running history of UI state. Storing the currently open windows and their locations when they are loaded, closed and moved. Inject a history tracker into each MDI child that is created. This could be a singleton type in the MDI parent. It should have facility to look up a window by its handle and should store the relevant information in a structure that makes sense for serialization. In each of the afore mentioned events add calls to the tracker with the appropriate changes. The tracker should persist along whatever rules you determine are appropriate.
The load process then requires that the MDI parent interrogate the tracker after it has initialized with what ever stored state existed. Using the stored information (window type and location (and data?)) the MDI parent then opens up the appropriate children at the appropriate locations.

How to move the cursor to the last opened window (possibly popup) in c++

I need to move the mouse to the last opened window. This last window will be a popup created by whatever website.
I guess all I need is to get the position of the last opened window and use SetMousePos, right?
I'm not really familiar with the windows API and any help is welcome - Thanks!
Edit:
To answer the questions, we are writing a program that gets malware data. Unfortunately some malware only start working after the mouse moves to a popup they opened. Its a research-based application
I haven't tested this but I believe you could try the following:
Enumerate running processes and order by PID.
The highest number PID should be the "newest" process.
For the newest process enumerate its windows (use GetWindowThreadProcessId)
At this point I guess you'd have to pick which window you think is the "main" window, for example if the malware opens two windows I don't know how you're going to choose which one to give focus to?
Of your picked HWND get its position on the desktop.
Use SetMousePos to move the mouse to the position of the window.
I haven't included all the API's you'll need for these tasks as its generally quite easy to find on here :)
One way to track recently opened windows is to use SetWinEventHook to listen to the EVENT_OBJECT_CREATE and EVENT_OBJECT_SHOW events. In the callback, filter:
just events with a non-null HWND where idObject==OBJID_WINDOW to get just window creation events (vs other creation events such as for items within a listbox)
for top-level only windows, also filter by checking GetAncestor(hwnd, GA_PARENT) is GetDesktopWindow()
And check that the window is indeed currently visible (WS_VISIBLE style is set in GetWindowLong(GWL_STYLE)).
Also filter by GetWindowThreadProcessId() and via the thread/process you pass into SetWinEventHook if you only care about HWNDs from a specific app.
The reason for checking both of these events is that some windows are created hidden and then shown, others are created fully visible, while others are created once, then shown/hidden many times over their lifetime.
You can then cache this 'last known created hwnd' in a global and check it as needed, using GetWindowRect() to get its location, and SetCursorPos() to move the mouse to that location.
--
If the most recent popup is an active window which takes focus - as is the case with dialogs, but not usually the case with 'pop-under' windows - you can use GetGUIThreadInfo(NULL, ...) to determine the currently active HWND, which might be the one you are looking for, returned in the GUITHREADINFO.hwndActive member of the struct you pass it.

can I set the AppUserModelID of an arbitrary process launched through a jumplist?

I have a simple console application written in C++ that acts as a stub for launching another application through it's jumplist. Purpose is to add jumplist abilities to applications that do not support this. Call it stub.exe. When running stub.exe it creates a custom jumplist using these steps (taken right form the MS samples):
create an ICustomDestinationList
ICustomDestinationList::BeginList()
create an IObjectCollection
for_each item_to_add
create an IShellLink, set its path/arguments/title/icon
add IShellLink to the IObjectCollection
get the IObjectArray interface from the IObjectCollection
call ICustomDestinationList::AddUserTasks( IObjectArray interface )
ICustomDestinationList::CommitList()
When pinning stub.exe to the taskbar and right-clicking it, the jumpilst appears and it contains all IShellLinks added. When clicking an item, it will launch the corresponding process.
Now I'd like a process launched through this jumplist have it's window(s) grouped under stub.exe's taskbar icon, instead of having it's own group. They key to get this working seems to be the AppUsermodelID. This is what I tried so far:
just for testing, create a couple of shortcuts and set the id through IPropertyStore->SetValue( PKEY_AppUserModel_ID, "id" ). Indeed, when launching these shortcuts, they will all group under the same taskbar icon.
since the shortcuts do what I want, I tried adding shortcuts to stub.exe's jumplist: no effect. The shortcuts don't even show up in the jumplist (maybe one cannot have a shortcut to a shortcut?), yet all methods return S_OK
setting the PKEY_AppUserModel_ID on each of the IShellLinks that get added to the jumplist: no effect
calling ICustomDestinationList->SetAppID(): no effect
instead of using SubTasks, tried with SHAddToRecentDocs: no effect. The recent doc list does not show up. But now things get messy. After setting the AppUserModelID on the shortcut that is responsible for the pinned taskbar item (the one in %APPDATA%/Roaming/Microsoft/Internet Explorer/Quick Launch/User Pinned/TaskBar), the jumplist changed: it does not show the 'Tasks' item anymore, but does show 'Recent' and the items I added using SHAddToRecentDocs. Now when clicking them I get a dialog box with a title that starts with 'd:\desktop' followed by Chinese characters. Hovering the items in the jumplist also shows Chinese characters instead of the descirption I set.
Questions:
What's with the Chinese characters in the jumplist?
How come setting the app id on the taskbar shortcut toggles between 'Tasks' and 'Recent' sections, why are they not both there?
What would be the way, if even possible, to achive what I actually want: a custom jump list of which the items launched will group under it's taskbar icon? (note that the processes I plan to laucnh their do not have their app id set currently)
not much reactions here ;]
In the meantime I managed to solve the main problem myself; it's not quite a straightforward solution but it fullfills the requirements: a program runs in the backround and installs a CBT hook. Each time an application creates a window (HookProc code = HCBT_CREATEWND), the hook checks the application's path against a map containing paths and desired application ids. If a match is found, the application id of the HWND is set. Since this occurs before the window is actually shown and is combined with the custom task list, from a user's point of view the application behaves just like one that does support a recent/pinned document list.