[WFX] Icon update (again)

Discuss and announce Total Commander plugins, addons and other useful tools here, both their usage and their development.

Moderators: Hacker, petermad, Stefan2, white

Post Reply
User avatar
Dalai
Power Member
Power Member
Posts: 9948
Joined: 2005-01-28, 22:17 UTC
Location: Meiningen (Südthüringen)

[WFX] Icon update (again)

Post by *Dalai »

Hey there :),

I know that this was discussed a couple of times before (e.g. WFX custom icons update and How to change the file icon?), but I guess I need some advice.

It's about my Services2 file-system plugin: If the plugin's option "Show service state in the extension column of TC's panel" is enabled all is well. However, if it's disabled and a service's state changes (doesn't matter if that happens inside the plugin or it's done externally) the icon does not reflect this change on a refresh (Ctrl+R), i.e. the icon stays the same. So far so bad.

My plugin returns 7 different icons in total (one for each state) like this:

Code: Select all

function FsExtractCustomIcon(RemoteName: PChar; ExtractFlags:integer; var TheIcon: hicon):integer; stdcall;
[...]
  case svc.State of
    ssStopped: begin
          typ:= ICON_STATE_STOPPED;
        end;
    ssStartPending: begin
          typ:= ICON_STATE_STARTING;
        end;
    [...]
  end;
  TheIcon:= LoadIcon(hInstance, PChar(typ));
  lstrcpyn(RemoteName, PChar(typ), MAX_PATH);
  if TheIcon <> 0 then
      Result:= FS_ICON_EXTRACTED;
end;
So, every service state has its own icon. My question is: When is FsExtractCustomIcon called? I guess it's called if there is a change in file name, timestamp, size and/or attributes. If that's true, there's nothing I could do inside the function FsExtractCustomIcon, but I would need to change information about the file in FsFindFirst/FsFindNext. The question is: Which kind of information should I change? And how should I change that information? Currently the list of services is rebuilt whenever FsFindFirst is called, so I don't know if there was a change in any of the services.

Thanks in advance and kind regards
Dalai
#101164 Personal licence
Ryzen 5 2600, 16 GiB RAM, ASUS Prime X370-A, Win7 x64

Plugins: Services2, Startups, CertificateInfo, SignatureInfo, LineBreakInfo - Download-Mirror
User avatar
ghisler(Author)
Site Admin
Site Admin
Posts: 50390
Joined: 2003-02-04, 09:46 UTC
Location: Switzerland
Contact:

Post by *ghisler(Author) »

Unfortunately the icons are cached, and there is no function right now to tell TC to refresh the icon. Maybe you solve it by reporting a different file name, e.g.
Service x (stopped)
Author of Total Commander
https://www.ghisler.com
User avatar
Dalai
Power Member
Power Member
Posts: 9948
Joined: 2005-01-28, 22:17 UTC
Location: Meiningen (Südthüringen)

Post by *Dalai »

ghisler(Author) wrote:Maybe you solve it by reporting a different file name, e.g.
Service x (stopped)
Since this is not what I want, I looked a little deeper into the issue and found a kind of "hacky" solution. The plugin now caches what it returns to TC. That includes the service name, the service state and the timestamp of its executable file, or in code:

Code: Select all

type TCachedService = class
    private
      FName: string;
      FState: TServiceState;
      FTimestamp: Cardinal;
    end;

  TCachedServicesList = class(TObjectList)
    protected
      function GetItem(AIndex: Integer): TCachedService; virtual;
      procedure SetItem(AIndex: Integer; AObjekt: TCachedService); virtual;
    public
      function Add(AObjekt: TCachedService): Integer; virtual;
      function Remove(AObjekt: TCachedService): Integer; virtual;
      function IndexOf(AObjekt: TCachedService): Integer; virtual;
      function IndexOfName(const AName: string): integer;
      procedure Insert(AIndex: Integer; AObjekt: TCachedService); virtual;
      function First: TCachedService; virtual;
      function Last: TCachedService; virtual;
      property Items[Index: Integer]: TCachedService read GetItem write SetItem; default;
end;
When the service list is refreshed by the user using Ctrl+R (or by the plugin sending cm_RereadSource to TC), the plugin checks if the service is in the cache list and if its state has changed. If so, it returns a different timestamp, if not, it returns the same timestamp:

Code: Select all

[...]
    LState:= svc.State;
[...]
        LsvcIndex:= CachedServicesList.IndexOfName(LsvcName);
        if LsvcIndex > -1 then
        begin
            // If the service's state changed we must return a different timestamp
            // to force TC to call FsExtractCustomIcon (for that service at least)
            LsvcCached:= CachedServicesList[LsvcIndex];
            if (LsvcCached.FState <> LState) then
            begin
                if (LsvcCached.FTimestamp = Lwfd.ftLastWriteTime.dwLowDateTime) then
                    Inc(FindData.ftLastWriteTime.dwLowDateTime);
            end
            else
[...]
(This code is called in FsFindFirst and FsFindNext)
Returning a different timestamp seems to force TC to call FsExtractCustomIcon which is exactly what I want it to do.

If I understand the MSDN article about the FILETIME structure correctly it has a resolution of 100 nanoseconds, so the different timestamp should not be visible to the user.

Nevertheless it would be nice to have a function in the plugin interface (or an additional optional argument to an existing function like FsFindFirst/FsFindNext) to force TC to call FsExtractCustomIcon.

Regards
Dalai
#101164 Personal licence
Ryzen 5 2600, 16 GiB RAM, ASUS Prime X370-A, Win7 x64

Plugins: Services2, Startups, CertificateInfo, SignatureInfo, LineBreakInfo - Download-Mirror
Post Reply