View Issue Details

IDProjectCategoryView StatusLast Update
0036209LazarusLCLpublic2019-11-13 07:12
ReporterDavidAssigned ToJuha Manninen 
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
PlatformOSLinuxOS VersionVarious
Product Version2.1 (SVN)Product Build 
Target VersionFixed in Version 
Summary0036209: Patch to make TrayIcon work in GTK3
DescriptionAt present, Lazarus on GTK3 does not support the TrayIcon at all, in lcl/interfaces/gtk3/gtk3wsfactory.pas the function RegisterCustomTrayIcon() just returns false.

My patch has a slightly modified version of UnityWSCtrls.pas from GTK2 that implements AppIndicator3, a dumbed down version of the older System Tray Icon. All you can use an AppIndicator TrayIcon for is to pop up a menu but that is the way many Linux Desktops are heading anyway.

I have tested this patch on older distros such as Ubuntu16.04, newer (GTK3 only) ones such as Ubuntu19.10. On Mageia 7.1 and Linux Mint 19.2. On KDE, Xfce and Enlightenment desktops.

Steps To ReproduceThe patch is an svn patch against Lazarus Trunk r62111
Additional InformationBe aware that this is still not the approach that the Gnome Developers would like us to take. That involves their App Notifier via dbus approach. Maybe some time in the distant future ....

Note that unlike gtk2, when running on gtk3 and, for example, the LibAppIndicator3 is not installed, it will not fall back to old System Tray mode (GTK3 does not have an old System Tray mode). Programmers may well be advised to check for presence of libappindicator3 at startup or set it as a dependency. I will document this on the wiki.
TagsNo tags attached.
Fixed in Revisionr62137
LazTarget-
WidgetsetGTK 3
Attached Files
  • appind.patch (14,892 bytes)
    Index: lcl/interfaces/gtk2/unitywsctrls.pas
    ===================================================================
    --- lcl/interfaces/gtk2/unitywsctrls.pas	(revision 62111)
    +++ lcl/interfaces/gtk2/unitywsctrls.pas	(working copy)
    @@ -260,41 +260,23 @@
       Initialized: Boolean;
     
     function UnityAppIndicatorInit: Boolean;
    +var
    +  Module: HModule;
    +  UseAppInd : string;
    +
    +  function NeedAppIndicator: boolean;
       var
    -    Module: HModule;
    -    UseAppInd : string;
    +    DeskTop : String;
    +  begin
    +    DeskTop := GetEnvironmentVariableUTF8('XDG_CURRENT_DESKTOP');
    +    // See the wiki for details of what extras these desktops require !!
    +    if (Desktop = 'GNOME')
    +      or (DeskTop = 'Unity')
    +      or (Desktop = 'Enlightenment')
    +      or (Desktop = 'ubuntu:GNOME') then exit(True);
    +    Result := False;
    +  end;
     
    -    function NeedAppIndicator: boolean;
    -    var
    -      DeskTop,  VersionSt : String;
    -      ProcFile: TextFile;
    -    begin
    -      DeskTop := GetEnvironmentVariableUTF8('XDG_CURRENT_DESKTOP');
    -      // See the wiki for details of what extras these desktops require !!
    -      if (DeskTop = 'Unity')
    -         or (Desktop = 'Enlightenment')
    -            then exit(True);
    -      if (DeskTop = 'GNOME') then begin
    -          {$PUSH}
    -          {$IOChecks off}
    -          AssignFile(ProcFile, '/proc/version');
    -          reset(ProcFile);
    -          if IOResult<>0 then exit(false);
    -          {$POP}
    -          readln(ProcFile, VersionSt);
    -          CloseFile(ProcFile);
    -          if ( (pos('mageia', VersionSt) > 0) or
    -            (pos('Debian', VersionSt) > 0) or
    -            (pos('Red Hat', VersionSt) > 0) or
    -            (pos('SUSE', VersionSt) > 0) )
    -            // 19.04 and earlier Ubuntu Gnome does not need LibAppIndicator3
    -            then exit(True);
    -      end;
    -      Result := False;
    -    end;
    -
    -
    -
       function TryLoad(const ProcName: string; var Proc: Pointer): Boolean;
       begin
         Proc := GetProcAddress(Module, ProcName);
    Index: lcl/interfaces/gtk3/appindicator.pas
    ===================================================================
    --- lcl/interfaces/gtk3/appindicator.pas	(nonexistent)
    +++ lcl/interfaces/gtk3/appindicator.pas	(working copy)
    @@ -0,0 +1,312 @@
    +{ Copyleft implementation of TTrayIcon for
    +  Unity applications indicators
    +  Created 2015 by Anthony Walter sysrpl@gmail.com
    +
    +  Ported (minimally) to GTK3 by David Bannon, October 2019, Thanks Anthony !
    +  Renamed from unitywsctrls to AppIndicator reflecting the fact that
    +  LibAppIndicator3 has survived beyond Unity and is useful on current Linuxes.
    +  Works out of the box on Ubuntu based systems including ones using Gnome Desktop.
    +  On 'strict' Gnome (RedHat, Suse, Mageia) will require the addition of
    +  LibAppIndicator3 and a Gnome Shell Extension such as TopIcons Plus.
    +
    +  Read the wiki for (lots) more info.
    +  }
    +
    +unit appindicator;
    +
    +interface
    +
    +{$mode delphi}
    +uses
    +  GLib2, LazGtk3, LazGdkPixbuf2, gtk3widgets,
    +  Classes, SysUtils, dynlibs,
    +  Graphics, Controls, Forms, ExtCtrls, WSExtCtrls, LCLType, LazUTF8,
    +  FileUtil;
    +
    +{ TUnityWSCustomTrayIcon (aka AppIndicator) is the class for tray icons on
    +  systems running the Unity desktop environment.
    +
    +  LibAppIndicator3 allows only AppIndicator objects in its tray. These objects
    +  have the following reduced functionality:
    +
    +  Tooltips are not allowed
    +  Icons do not receive mouse events
    +  Indicators display a menu when clicked by any mouse button
    +
    +  See also: http://www.markshuttleworth.com/archives/347
    +  "Clicking on an indicator will open its menu..."
    +  "There’ll be no ability for arbitrary applications to define arbitrary
    +   behaviours to arbitrary events on indicators"
    +
    +  Personal observations:
    +
    +  A popup menu is required always
    +  You can only create one AppIndicator per appplication
    +  You cannot use a different popupmenu once one has been used }
    +
    +type
    +  TAppIndWSCustomTrayIcon = class(TWSCustomTrayIcon)
    +  published
    +    class function Hide(const ATrayIcon: TCustomTrayIcon): Boolean; override;
    +    class function Show(const ATrayIcon: TCustomTrayIcon): Boolean; override;
    +    class procedure InternalUpdate(const ATrayIcon: TCustomTrayIcon); override;
    +    class function GetPosition(const {%H-}ATrayIcon: TCustomTrayIcon): TPoint; override;
    +  end;
    +
    +{ UnityAppIndicatorInit returns true if libappindicator_3 library can be loaded }
    +
    +function AppIndicatorInit: Boolean;
    +
    +implementation
    +
    +uses gtk3objects;     // TGtk3Image
    +
    +const
    +  libappindicator_3 = 'libappindicator3.so.1';
    +
    +{const
    +  APP_INDICATOR_SIGNAL_NEW_ICON = 'new-icon';
    +  APP_INDICATOR_SIGNAL_NEW_ATTENTION_ICON = 'new-attention-icon';
    +  APP_INDICATOR_SIGNAL_NEW_STATUS = 'new-status';
    +  APP_INDICATOR_SIGNAL_NEW_LABEL = 'new-label';
    +  APP_INDICATOR_SIGNAL_CONNECTION_CHANGED = 'connection-changed';
    +  APP_INDICATOR_SIGNAL_NEW_ICON_THEME_PATH = 'new-icon-theme-path';
    +}
    +type
    +  TAppIndicatorCategory = (
    +    APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
    +    APP_INDICATOR_CATEGORY_COMMUNICATIONS,
    +    APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
    +    APP_INDICATOR_CATEGORY_HARDWARE,
    +    APP_INDICATOR_CATEGORY_OTHER
    +  );
    +
    +  TAppIndicatorStatus = (
    +    APP_INDICATOR_STATUS_PASSIVE,
    +    APP_INDICATOR_STATUS_ACTIVE,
    +    APP_INDICATOR_STATUS_ATTENTION
    +  );
    +
    +  PAppIndicator = Pointer;
    +
    +var
    +  { GlobalAppIndicator creation routines }
    +  app_indicator_get_type: function: GType; cdecl;
    +  app_indicator_new: function(id, icon_name: PGChar; category: TAppIndicatorCategory): PAppIndicator; cdecl;
    +  app_indicator_new_with_path: function(id, icon_name: PGChar; category: TAppIndicatorCategory; icon_theme_path: PGChar): PAppIndicator; cdecl;
    +  { Set properties }
    +  app_indicator_set_status: procedure(self: PAppIndicator; status: TAppIndicatorStatus); cdecl;
    +  app_indicator_set_attention_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
    +  app_indicator_set_menu: procedure(self: PAppIndicator; menu: PGtkMenu); cdecl;
    +  app_indicator_set_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
    +  app_indicator_set_label: procedure(self: PAppIndicator; _label, guide: PGChar); cdecl;
    +  app_indicator_set_icon_theme_path: procedure(self: PAppIndicator; icon_theme_path: PGChar); cdecl;
    +  app_indicator_set_ordering_index: procedure(self: PAppIndicator; ordering_index: guint32); cdecl;
    +  { Get properties }
    +  app_indicator_get_id: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_category: function(self: PAppIndicator): TAppIndicatorCategory; cdecl;
    +  app_indicator_get_status: function(self: PAppIndicator): TAppIndicatorStatus; cdecl;
    +  app_indicator_get_icon: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_icon_theme_path: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_attention_icon: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_menu: function(self: PAppIndicator): PGtkMenu; cdecl;
    +  app_indicator_get_label: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_label_guide: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_ordering_index: function(self: PAppIndicator): guint32; cdecl;
    +
    +{ TAppIndTrayIconHandle }
    +
    +type
    +  TAppIndTrayIconHandle = class
    +  private
    +    FTrayIcon: TCustomTrayIcon;
    +    FName: string;
    +    FIconName: string;
    +  public
    +    constructor Create(TrayIcon: TCustomTrayIcon);
    +    destructor Destroy; override;
    +    procedure Update;
    +  end;
    +
    +{ It seems to me, and please tell me otherwise if untrue, that the only way
    +  to load icons for AppIndicator is through files }
    +
    +const
    +  IconThemePath = '/tmp/appindicators/';
    +  IconType = 'png';
    +
    +{ It seems to me, and please tell me otherwise if untrue, that you can only
    +  create one working AppIndicator for your program over its lifetime }
    +
    +var
    +  GlobalAppIndicator: PAppIndicator;
    +  GlobalIcon: Pointer;
    +  GlobalIconPath: string;
    +
    +constructor TAppIndTrayIconHandle.Create(TrayIcon: TCustomTrayIcon);
    +var
    +  NewIcon: Pointer;
    +begin
    +  inherited Create;
    +  FTrayIcon := TrayIcon;
    +  FName := 'app-' + IntToHex(IntPtr(Application), SizeOf(IntPtr) * 2);
    +  NewIcon := {%H-}Pointer(TGtk3Image(FTrayIcon.Icon.Handle).handle);
    +  if NewIcon = nil then
    +    NewIcon := {%H-}Pointer(Application.Icon.Handle);
    +  if NewIcon <> GlobalIcon then
    +  begin
    +    GlobalIcon := NewIcon;
    +    ForceDirectories(IconThemePath);
    +    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
    +    if FileExists(GlobalIconPath) then
    +      DeleteFile(GlobalIconPath);
    +    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
    +    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
    +    if GlobalAppIndicator <> nil then
    +        app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
    +  end
    +  else
    +    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
    +  { Only the first created AppIndicator is functional }
    +  if GlobalAppIndicator = nil then
    +    { It seems that icons can only come from files :( }
    +    GlobalAppIndicator := app_indicator_new_with_path(PChar(FName), PChar(FIconName),
    +      APP_INDICATOR_CATEGORY_APPLICATION_STATUS, IconThemePath);
    +  Update;
    +end;
    +
    +destructor TAppIndTrayIconHandle.Destroy;
    +begin
    +  { Hide the global AppIndicator }
    +  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_PASSIVE);
    +  inherited Destroy;
    +end;
    +
    +procedure TAppIndTrayIconHandle.Update;
    +var
    +  NewIcon: Pointer;
    +begin
    +  NewIcon := {%H-}Pointer(TGTK3Image(FTrayIcon.Icon.Handle).Handle);
    +  if NewIcon = nil then
    +    NewIcon := {%H-}Pointer(Application.Icon.Handle);
    +  if NewIcon <> GlobalIcon then
    +  begin
    +    GlobalIcon := NewIcon;
    +    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
    +    ForceDirectories(IconThemePath);
    +    if FileExists(GlobalIconPath) then
    +      DeleteFile(GlobalIconPath);
    +    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
    +    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
    +    { Again it seems that icons can only come from files }
    +    app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
    +  end;
    +  { It seems to me you can only set the menu once for an AppIndicator }
    +  if (app_indicator_get_menu(GlobalAppIndicator) = nil) and (FTrayIcon.PopUpMenu <> nil) then
    +    //app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(FTrayIcon.PopUpMenu.Handle));
    +    app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(TGTK3Menu(FTrayIcon.PopUpMenu.Handle).Widget));
    +  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_ACTIVE);
    +end;
    +
    +{ TAppIndWSCustomTrayIcon }
    +
    +class function TAppIndWSCustomTrayIcon.Hide(const ATrayIcon: TCustomTrayIcon): Boolean;
    +var
    +  T: TAppIndTrayIconHandle;
    +begin
    +  if ATrayIcon.Handle <> 0 then
    +  begin
    +    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
    +    ATrayIcon.Handle := 0;
    +    T.Free;
    +  end;
    +  Result := True;
    +end;
    +
    +class function TAppIndWSCustomTrayIcon.Show(const ATrayIcon: TCustomTrayIcon): Boolean;
    +var
    +  T: TAppIndTrayIconHandle;
    +begin
    +  if ATrayIcon.Handle = 0 then
    +  begin
    +    T := TAppIndTrayIconHandle.Create(ATrayIcon);
    +    ATrayIcon.Handle := HWND(T);
    +  end;
    +  Result := True;
    +end;
    +
    +class procedure TAppIndWSCustomTrayIcon.InternalUpdate(const ATrayIcon: TCustomTrayIcon);
    +var
    +  T: TAppIndTrayIconHandle;
    +begin
    +  if ATrayIcon.Handle <> 0 then
    +  begin
    +    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
    +    T.Update;
    +  end;
    +end;
    +
    +class function TAppIndWSCustomTrayIcon.GetPosition(const ATrayIcon: TCustomTrayIcon): TPoint;
    +begin
    +  Result := Point(0, 0);
    +end;
    +
    +{ AppIndicatorInit }
    +
    +var
    +  Loaded: Boolean;
    +  Initialized: Boolean;
    +
    +function AppIndicatorInit: Boolean;
    +var
    +  Module: HModule;
    +
    +  function TryLoad(const ProcName: string; var Proc: Pointer): Boolean;
    +  begin
    +    Proc := GetProcAddress(Module, ProcName);
    +    Result := Proc <> nil;
    +  end;
    +
    +begin
    +  Result := False;
    +  if Loaded then
    +    Exit(Initialized);
    +  Loaded := True;
    +  if Initialized then
    +    Exit(True);
    +  Module := LoadLibrary(libappindicator_3);        // might have several package names, see wiki
    +  if Module = 0 then
    +     Exit;
    +  Result :=
    +    TryLoad('app_indicator_get_type', @app_indicator_get_type) and
    +    TryLoad('app_indicator_new', @app_indicator_new) and
    +    TryLoad('app_indicator_new_with_path', @app_indicator_new_with_path) and
    +    TryLoad('app_indicator_set_status', @app_indicator_set_status) and
    +    TryLoad('app_indicator_set_attention_icon', @app_indicator_set_attention_icon) and
    +    TryLoad('app_indicator_set_menu', @app_indicator_set_menu) and
    +    TryLoad('app_indicator_set_icon', @app_indicator_set_icon) and
    +    TryLoad('app_indicator_set_label', @app_indicator_set_label) and
    +    TryLoad('app_indicator_set_icon_theme_path', @app_indicator_set_icon_theme_path) and
    +    TryLoad('app_indicator_set_ordering_index', @app_indicator_set_ordering_index) and
    +    TryLoad('app_indicator_get_id', @app_indicator_get_id) and
    +    TryLoad('app_indicator_get_category', @app_indicator_get_category) and
    +    TryLoad('app_indicator_get_status', @app_indicator_get_status) and
    +    TryLoad('app_indicator_get_icon', @app_indicator_get_icon) and
    +    TryLoad('app_indicator_get_icon_theme_path', @app_indicator_get_icon_theme_path) and
    +    TryLoad('app_indicator_get_attention_icon', @app_indicator_get_attention_icon) and
    +    TryLoad('app_indicator_get_menu', @app_indicator_get_menu) and
    +    TryLoad('app_indicator_get_label', @app_indicator_get_label) and
    +    TryLoad('app_indicator_get_label_guide', @app_indicator_get_label_guide) and
    +    TryLoad('app_indicator_get_ordering_index', @app_indicator_get_ordering_index);
    +  Initialized := Result;
    +end;
    +
    +initialization
    +  GlobalAppIndicator := nil;
    +  GlobalIconPath := '';
    +finalization
    +  if FileExists(GlobalIconPath) then
    +    DeleteFile(GlobalIconPath);
    +  if GlobalAppIndicator <> nil then
    +    g_object_unref(GlobalAppIndicator);
    +end.
    Index: lcl/interfaces/gtk3/gtk3wsfactory.pas
    ===================================================================
    --- lcl/interfaces/gtk3/gtk3wsfactory.pas	(revision 62111)
    +++ lcl/interfaces/gtk3/gtk3wsfactory.pas	(working copy)
    @@ -21,7 +21,7 @@
     uses
       Classes, Controls, ComCtrls, Calendar, StdCtrls, Arrow, Spin,
       Dialogs, ExtCtrls, ExtDlgs, Buttons, CheckLst, Forms, Grids, Menus,
    -  ImgList, PairSplitter, WSLCLClasses;
    +  ImgList, PairSplitter, WSLCLClasses, AppIndicator;
     
     
     // imglist
    @@ -457,7 +457,11 @@
     function RegisterCustomTrayIcon: Boolean; alias : 'WSRegisterCustomTrayIcon';
     begin
       // RegisterWSComponent(TCustomTrayIcon, TGtk2WSCustomTrayIcon);
    -  Result := False;
    +  Result := True;
    +  if AppIndicatorInit then
    +     RegisterWSComponent(TCustomTrayIcon, TAppIndWSCustomTrayIcon)
    +  else
    +     Result := False;
     end;
     
     //ExtDlgs
    
    appind.patch (14,892 bytes)
  • 36209.patch (12,929 bytes)
    Index: lcl/interfaces/gtk3/appindicator.pas
    ===================================================================
    --- lcl/interfaces/gtk3/appindicator.pas	(nonexistent)
    +++ lcl/interfaces/gtk3/appindicator.pas	(working copy)
    @@ -0,0 +1,312 @@
    +{ Copyleft implementation of TTrayIcon for
    +  Unity applications indicators
    +  Created 2015 by Anthony Walter sysrpl@gmail.com
    +
    +  Ported (minimally) to GTK3 by David Bannon, October 2019, Thanks Anthony !
    +  Renamed from unitywsctrls to AppIndicator reflecting the fact that
    +  LibAppIndicator3 has survived beyond Unity and is useful on current Linuxes.
    +  Works out of the box on Ubuntu based systems including ones using Gnome Desktop.
    +  On 'strict' Gnome (RedHat, Suse, Mageia) will require the addition of
    +  LibAppIndicator3 and a Gnome Shell Extension such as TopIcons Plus.
    +
    +  Read the wiki for (lots) more info.
    +  }
    +
    +unit appindicator;
    +
    +interface
    +
    +{$mode delphi}
    +uses
    +  GLib2, LazGtk3, LazGdkPixbuf2, gtk3widgets,
    +  Classes, SysUtils, dynlibs,
    +  Graphics, Controls, Forms, ExtCtrls, WSExtCtrls, LCLType, LazUTF8,
    +  FileUtil;
    +
    +{ TUnityWSCustomTrayIcon (aka AppIndicator) is the class for tray icons on
    +  systems running the Unity desktop environment.
    +
    +  LibAppIndicator3 allows only AppIndicator objects in its tray. These objects
    +  have the following reduced functionality:
    +
    +  Tooltips are not allowed
    +  Icons do not receive mouse events
    +  Indicators display a menu when clicked by any mouse button
    +
    +  See also: http://www.markshuttleworth.com/archives/347
    +  "Clicking on an indicator will open its menu..."
    +  "There’ll be no ability for arbitrary applications to define arbitrary
    +   behaviours to arbitrary events on indicators"
    +
    +  Personal observations:
    +
    +  A popup menu is required always
    +  You can only create one AppIndicator per appplication
    +  You cannot use a different popupmenu once one has been used }
    +
    +type
    +  TAppIndWSCustomTrayIcon = class(TWSCustomTrayIcon)
    +  published
    +    class function Hide(const ATrayIcon: TCustomTrayIcon): Boolean; override;
    +    class function Show(const ATrayIcon: TCustomTrayIcon): Boolean; override;
    +    class procedure InternalUpdate(const ATrayIcon: TCustomTrayIcon); override;
    +    class function GetPosition(const {%H-}ATrayIcon: TCustomTrayIcon): TPoint; override;
    +  end;
    +
    +{ UnityAppIndicatorInit returns true if libappindicator_3 library can be loaded }
    +
    +function AppIndicatorInit: Boolean;
    +
    +implementation
    +
    +uses gtk3objects;     // TGtk3Image
    +
    +const
    +  libappindicator_3 = 'libappindicator3.so.1';
    +
    +{const
    +  APP_INDICATOR_SIGNAL_NEW_ICON = 'new-icon';
    +  APP_INDICATOR_SIGNAL_NEW_ATTENTION_ICON = 'new-attention-icon';
    +  APP_INDICATOR_SIGNAL_NEW_STATUS = 'new-status';
    +  APP_INDICATOR_SIGNAL_NEW_LABEL = 'new-label';
    +  APP_INDICATOR_SIGNAL_CONNECTION_CHANGED = 'connection-changed';
    +  APP_INDICATOR_SIGNAL_NEW_ICON_THEME_PATH = 'new-icon-theme-path';
    +}
    +type
    +  TAppIndicatorCategory = (
    +    APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
    +    APP_INDICATOR_CATEGORY_COMMUNICATIONS,
    +    APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
    +    APP_INDICATOR_CATEGORY_HARDWARE,
    +    APP_INDICATOR_CATEGORY_OTHER
    +  );
    +
    +  TAppIndicatorStatus = (
    +    APP_INDICATOR_STATUS_PASSIVE,
    +    APP_INDICATOR_STATUS_ACTIVE,
    +    APP_INDICATOR_STATUS_ATTENTION
    +  );
    +
    +  PAppIndicator = Pointer;
    +
    +var
    +  { GlobalAppIndicator creation routines }
    +  app_indicator_get_type: function: GType; cdecl;
    +  app_indicator_new: function(id, icon_name: PGChar; category: TAppIndicatorCategory): PAppIndicator; cdecl;
    +  app_indicator_new_with_path: function(id, icon_name: PGChar; category: TAppIndicatorCategory; icon_theme_path: PGChar): PAppIndicator; cdecl;
    +  { Set properties }
    +  app_indicator_set_status: procedure(self: PAppIndicator; status: TAppIndicatorStatus); cdecl;
    +  app_indicator_set_attention_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
    +  app_indicator_set_menu: procedure(self: PAppIndicator; menu: PGtkMenu); cdecl;
    +  app_indicator_set_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
    +  app_indicator_set_label: procedure(self: PAppIndicator; _label, guide: PGChar); cdecl;
    +  app_indicator_set_icon_theme_path: procedure(self: PAppIndicator; icon_theme_path: PGChar); cdecl;
    +  app_indicator_set_ordering_index: procedure(self: PAppIndicator; ordering_index: guint32); cdecl;
    +  { Get properties }
    +  app_indicator_get_id: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_category: function(self: PAppIndicator): TAppIndicatorCategory; cdecl;
    +  app_indicator_get_status: function(self: PAppIndicator): TAppIndicatorStatus; cdecl;
    +  app_indicator_get_icon: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_icon_theme_path: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_attention_icon: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_menu: function(self: PAppIndicator): PGtkMenu; cdecl;
    +  app_indicator_get_label: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_label_guide: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_ordering_index: function(self: PAppIndicator): guint32; cdecl;
    +
    +{ TAppIndTrayIconHandle }
    +
    +type
    +  TAppIndTrayIconHandle = class
    +  private
    +    FTrayIcon: TCustomTrayIcon;
    +    FName: string;
    +    FIconName: string;
    +  public
    +    constructor Create(TrayIcon: TCustomTrayIcon);
    +    destructor Destroy; override;
    +    procedure Update;
    +  end;
    +
    +{ It seems to me, and please tell me otherwise if untrue, that the only way
    +  to load icons for AppIndicator is through files }
    +
    +const
    +  IconThemePath = '/tmp/appindicators/';
    +  IconType = 'png';
    +
    +{ It seems to me, and please tell me otherwise if untrue, that you can only
    +  create one working AppIndicator for your program over its lifetime }
    +
    +var
    +  GlobalAppIndicator: PAppIndicator;
    +  GlobalIcon: Pointer;
    +  GlobalIconPath: string;
    +
    +constructor TAppIndTrayIconHandle.Create(TrayIcon: TCustomTrayIcon);
    +var
    +  NewIcon: Pointer;
    +begin
    +  inherited Create;
    +  FTrayIcon := TrayIcon;
    +  FName := 'app-' + IntToHex(IntPtr(Application), SizeOf(IntPtr) * 2);
    +  NewIcon := {%H-}Pointer(TGtk3Image(FTrayIcon.Icon.Handle).handle);
    +  if NewIcon = nil then
    +    NewIcon := {%H-}Pointer(Application.Icon.Handle);
    +  if NewIcon <> GlobalIcon then
    +  begin
    +    GlobalIcon := NewIcon;
    +    ForceDirectories(IconThemePath);
    +    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
    +    if FileExists(GlobalIconPath) then
    +      DeleteFile(GlobalIconPath);
    +    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
    +    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
    +    if GlobalAppIndicator <> nil then
    +        app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
    +  end
    +  else
    +    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
    +  { Only the first created AppIndicator is functional }
    +  if GlobalAppIndicator = nil then
    +    { It seems that icons can only come from files :( }
    +    GlobalAppIndicator := app_indicator_new_with_path(PChar(FName), PChar(FIconName),
    +      APP_INDICATOR_CATEGORY_APPLICATION_STATUS, IconThemePath);
    +  Update;
    +end;
    +
    +destructor TAppIndTrayIconHandle.Destroy;
    +begin
    +  { Hide the global AppIndicator }
    +  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_PASSIVE);
    +  inherited Destroy;
    +end;
    +
    +procedure TAppIndTrayIconHandle.Update;
    +var
    +  NewIcon: Pointer;
    +begin
    +  NewIcon := {%H-}Pointer(TGTK3Image(FTrayIcon.Icon.Handle).Handle);
    +  if NewIcon = nil then
    +    NewIcon := {%H-}Pointer(Application.Icon.Handle);
    +  if NewIcon <> GlobalIcon then
    +  begin
    +    GlobalIcon := NewIcon;
    +    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
    +    ForceDirectories(IconThemePath);
    +    if FileExists(GlobalIconPath) then
    +      DeleteFile(GlobalIconPath);
    +    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
    +    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
    +    { Again it seems that icons can only come from files }
    +    app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
    +  end;
    +  { It seems to me you can only set the menu once for an AppIndicator }
    +  if (app_indicator_get_menu(GlobalAppIndicator) = nil) and (FTrayIcon.PopUpMenu <> nil) then
    +    //app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(FTrayIcon.PopUpMenu.Handle));
    +    app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(TGTK3Menu(FTrayIcon.PopUpMenu.Handle).Widget));
    +  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_ACTIVE);
    +end;
    +
    +{ TAppIndWSCustomTrayIcon }
    +
    +class function TAppIndWSCustomTrayIcon.Hide(const ATrayIcon: TCustomTrayIcon): Boolean;
    +var
    +  T: TAppIndTrayIconHandle;
    +begin
    +  if ATrayIcon.Handle <> 0 then
    +  begin
    +    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
    +    ATrayIcon.Handle := 0;
    +    T.Free;
    +  end;
    +  Result := True;
    +end;
    +
    +class function TAppIndWSCustomTrayIcon.Show(const ATrayIcon: TCustomTrayIcon): Boolean;
    +var
    +  T: TAppIndTrayIconHandle;
    +begin
    +  if ATrayIcon.Handle = 0 then
    +  begin
    +    T := TAppIndTrayIconHandle.Create(ATrayIcon);
    +    ATrayIcon.Handle := HWND(T);
    +  end;
    +  Result := True;
    +end;
    +
    +class procedure TAppIndWSCustomTrayIcon.InternalUpdate(const ATrayIcon: TCustomTrayIcon);
    +var
    +  T: TAppIndTrayIconHandle;
    +begin
    +  if ATrayIcon.Handle <> 0 then
    +  begin
    +    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
    +    T.Update;
    +  end;
    +end;
    +
    +class function TAppIndWSCustomTrayIcon.GetPosition(const ATrayIcon: TCustomTrayIcon): TPoint;
    +begin
    +  Result := Point(0, 0);
    +end;
    +
    +{ AppIndicatorInit }
    +
    +var
    +  Loaded: Boolean;
    +  Initialized: Boolean;
    +
    +function AppIndicatorInit: Boolean;
    +var
    +  Module: HModule;
    +
    +  function TryLoad(const ProcName: string; var Proc: Pointer): Boolean;
    +  begin
    +    Proc := GetProcAddress(Module, ProcName);
    +    Result := Proc <> nil;
    +  end;
    +
    +begin
    +  Result := False;
    +  if Loaded then
    +    Exit(Initialized);
    +  Loaded := True;
    +  if Initialized then
    +    Exit(True);
    +  Module := LoadLibrary(libappindicator_3);        // might have several package names, see wiki
    +  if Module = 0 then
    +     Exit;
    +  Result :=
    +    TryLoad('app_indicator_get_type', @app_indicator_get_type) and
    +    TryLoad('app_indicator_new', @app_indicator_new) and
    +    TryLoad('app_indicator_new_with_path', @app_indicator_new_with_path) and
    +    TryLoad('app_indicator_set_status', @app_indicator_set_status) and
    +    TryLoad('app_indicator_set_attention_icon', @app_indicator_set_attention_icon) and
    +    TryLoad('app_indicator_set_menu', @app_indicator_set_menu) and
    +    TryLoad('app_indicator_set_icon', @app_indicator_set_icon) and
    +    TryLoad('app_indicator_set_label', @app_indicator_set_label) and
    +    TryLoad('app_indicator_set_icon_theme_path', @app_indicator_set_icon_theme_path) and
    +    TryLoad('app_indicator_set_ordering_index', @app_indicator_set_ordering_index) and
    +    TryLoad('app_indicator_get_id', @app_indicator_get_id) and
    +    TryLoad('app_indicator_get_category', @app_indicator_get_category) and
    +    TryLoad('app_indicator_get_status', @app_indicator_get_status) and
    +    TryLoad('app_indicator_get_icon', @app_indicator_get_icon) and
    +    TryLoad('app_indicator_get_icon_theme_path', @app_indicator_get_icon_theme_path) and
    +    TryLoad('app_indicator_get_attention_icon', @app_indicator_get_attention_icon) and
    +    TryLoad('app_indicator_get_menu', @app_indicator_get_menu) and
    +    TryLoad('app_indicator_get_label', @app_indicator_get_label) and
    +    TryLoad('app_indicator_get_label_guide', @app_indicator_get_label_guide) and
    +    TryLoad('app_indicator_get_ordering_index', @app_indicator_get_ordering_index);
    +  Initialized := Result;
    +end;
    +
    +initialization
    +  GlobalAppIndicator := nil;
    +  GlobalIconPath := '';
    +finalization
    +  if FileExists(GlobalIconPath) then
    +    DeleteFile(GlobalIconPath);
    +  if GlobalAppIndicator <> nil then
    +    g_object_unref(GlobalAppIndicator);
    +end.
    Index: lcl/interfaces/gtk3/gtk3wsfactory.pas
    ===================================================================
    --- lcl/interfaces/gtk3/gtk3wsfactory.pas	(revision 62114)
    +++ lcl/interfaces/gtk3/gtk3wsfactory.pas	(working copy)
    @@ -21,7 +21,7 @@
     uses
       Classes, Controls, ComCtrls, Calendar, StdCtrls, Arrow, Spin,
       Dialogs, ExtCtrls, ExtDlgs, Buttons, CheckLst, Forms, Grids, Menus,
    -  ImgList, PairSplitter, WSLCLClasses;
    +  ImgList, PairSplitter, WSLCLClasses, AppIndicator;
     
     
     // imglist
    @@ -457,7 +457,11 @@
     function RegisterCustomTrayIcon: Boolean; alias : 'WSRegisterCustomTrayIcon';
     begin
       // RegisterWSComponent(TCustomTrayIcon, TGtk2WSCustomTrayIcon);
    -  Result := False;
    +  Result := True;
    +  if AppIndicatorInit then
    +     RegisterWSComponent(TCustomTrayIcon, TAppIndWSCustomTrayIcon)
    +  else
    +     Result := False;
     end;
     
     //ExtDlgs
    
    36209.patch (12,929 bytes)
  • Oct28.patch (12,255 bytes)
    Index: lcl/interfaces/gtk3/gtk3wsfactory.pas
    ===================================================================
    --- lcl/interfaces/gtk3/gtk3wsfactory.pas	(revision 62136)
    +++ lcl/interfaces/gtk3/gtk3wsfactory.pas	(working copy)
    @@ -137,7 +137,7 @@
     uses
       Gtk3WSImgList, Gtk3WSControls, Gtk3WSForms, Gtk3WSButtons, Gtk3WSStdCtrls,
       Gtk3WSComCtrls, Gtk3WSExtCtrls, Gtk3WSSpin, Gtk3WSMenus, Gtk3WSCalendar,
    -  Gtk3WSDialogs, Gtk3WSCheckLst, Gtk3WSExtDlgs, gtk3wssplitter;
    +  Gtk3WSDialogs, Gtk3WSCheckLst, Gtk3WSExtDlgs, gtk3wssplitter, Gtk3WSTrayIcon;
     
     // imglist
     function RegisterCustomImageListResolution: Boolean; alias : 'WSRegisterCustomImageListResolution';
    @@ -457,7 +457,11 @@
     function RegisterCustomTrayIcon: Boolean; alias : 'WSRegisterCustomTrayIcon';
     begin
       // RegisterWSComponent(TCustomTrayIcon, TGtk2WSCustomTrayIcon);
    -  Result := False;
    +  Result := True;
    +  if Gtk3AppIndicatorInit then
    +     RegisterWSComponent(TCustomTrayIcon, TGtk3WSTrayIcon)
    +  else
    +     Result := False;
     end;
     
     //ExtDlgs
    Index: lcl/interfaces/gtk3/gtk3wstrayicon.pas
    ===================================================================
    --- lcl/interfaces/gtk3/gtk3wstrayicon.pas	(nonexistent)
    +++ lcl/interfaces/gtk3/gtk3wstrayicon.pas	(working copy)
    @@ -0,0 +1,287 @@
    +{ 
    + *****************************************************************************
    + *                               gtk3WSTrayIcon.pas                          *
    + *                               ------------------                          *
    + *                                                                           *
    + *                                                                           *
    + *****************************************************************************
    +
    + *****************************************************************************
    +  This file is part of the Lazarus Component Library (LCL)
    +
    +  See the file COPYING.modifiedLGPL.txt, included in this distribution,
    +  for details about the license.
    + *****************************************************************************
    +
    +A unit that uses LibAppIndicator3 to display a TrayIcon in GTK3.   Based on a
    +GTK2 version by Anthony Walter, now works with many common Linux systems "out 
    +of the box" and almost all of the remainder with addition of LibAppIndicator3
    +and TopIconsPlus or similar Gnome Extension. 
    +
    +See Wiki for details and Limitations (Menu only, one Icon only....)
    +Also refer to discussion in ../gtk2/UnityWSCtrls.pas
    +}
    +
    +
    +unit gtk3wstrayicon;
    +
    +interface
    +
    +{$mode delphi}
    +uses
    +  GLib2, LazGtk3, LazGdkPixbuf2, gtk3widgets,
    +  Classes, SysUtils, dynlibs,
    +  Graphics, Controls, Forms, ExtCtrls, WSExtCtrls, LCLType, LazUTF8,
    +  FileUtil;
    +
    +type
    +  TGtk3WSTrayIcon = class(TWSCustomTrayIcon)
    +  published
    +    class function Hide(const ATrayIcon: TCustomTrayIcon): Boolean; override;
    +    class function Show(const ATrayIcon: TCustomTrayIcon): Boolean; override;
    +    class procedure InternalUpdate(const ATrayIcon: TCustomTrayIcon); override;
    +    class function GetPosition(const {%H-}ATrayIcon: TCustomTrayIcon): TPoint; override;
    +  end;
    +
    +{ Gtk3AppIndicatorInit returns true if LibAppIndicator_3 library has been loaded }
    +function Gtk3AppIndicatorInit: Boolean;
    +
    +implementation
    +
    +uses gtk3objects;     // TGtk3Image
    +
    +const
    +  libappindicator_3 = 'libappindicator3.so.1';
    +
    +type
    +  TAppIndicatorCategory = (
    +    APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
    +    APP_INDICATOR_CATEGORY_COMMUNICATIONS,
    +    APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
    +    APP_INDICATOR_CATEGORY_HARDWARE,
    +    APP_INDICATOR_CATEGORY_OTHER
    +  );
    +
    +  TAppIndicatorStatus = (
    +    APP_INDICATOR_STATUS_PASSIVE,
    +    APP_INDICATOR_STATUS_ACTIVE,
    +    APP_INDICATOR_STATUS_ATTENTION
    +  );
    +
    +  PAppIndicator = Pointer;
    +
    +var
    +  { GlobalAppIndicator creation routines }
    +  app_indicator_get_type: function: GType; cdecl;
    +  app_indicator_new: function(id, icon_name: PGChar; category: TAppIndicatorCategory): PAppIndicator; cdecl;
    +  app_indicator_new_with_path: function(id, icon_name: PGChar; category: TAppIndicatorCategory; icon_theme_path: PGChar): PAppIndicator; cdecl;
    +  { Set properties }
    +  app_indicator_set_status: procedure(self: PAppIndicator; status: TAppIndicatorStatus); cdecl;
    +  app_indicator_set_attention_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
    +  app_indicator_set_menu: procedure(self: PAppIndicator; menu: PGtkMenu); cdecl;
    +  app_indicator_set_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
    +  app_indicator_set_label: procedure(self: PAppIndicator; _label, guide: PGChar); cdecl;
    +  app_indicator_set_icon_theme_path: procedure(self: PAppIndicator; icon_theme_path: PGChar); cdecl;
    +  app_indicator_set_ordering_index: procedure(self: PAppIndicator; ordering_index: guint32); cdecl;
    +  { Get properties }
    +  app_indicator_get_id: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_category: function(self: PAppIndicator): TAppIndicatorCategory; cdecl;
    +  app_indicator_get_status: function(self: PAppIndicator): TAppIndicatorStatus; cdecl;
    +  app_indicator_get_icon: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_icon_theme_path: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_attention_icon: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_menu: function(self: PAppIndicator): PGtkMenu; cdecl;
    +  app_indicator_get_label: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_label_guide: function(self: PAppIndicator): PGChar; cdecl;
    +  app_indicator_get_ordering_index: function(self: PAppIndicator): guint32; cdecl;
    +
    +{ TAppIndTrayIconHandle }
    +
    +type
    +  TAppIndTrayIconHandle = class
    +  private
    +    FTrayIcon: TCustomTrayIcon;
    +    FName: string;
    +    FIconName: string;
    +  public
    +    constructor Create(TrayIcon: TCustomTrayIcon);
    +    destructor Destroy; override;
    +    procedure Update;
    +  end;
    +
    +const
    +  IconThemePath = '/tmp/appindicators/'; // We must write our icon to a file.
    +  IconType = 'png';
    +
    +var
    +  GlobalAppIndicator: PAppIndicator;
    +  GlobalIcon: Pointer;
    +  GlobalIconPath: string;
    +
    +constructor TAppIndTrayIconHandle.Create(TrayIcon: TCustomTrayIcon);
    +var
    +  NewIcon: Pointer;
    +begin
    +  inherited Create;
    +  FTrayIcon := TrayIcon;
    +  FName := 'app-' + IntToHex(IntPtr(Application), SizeOf(IntPtr) * 2);
    +  NewIcon := {%H-}Pointer(TGtk3Image(FTrayIcon.Icon.Handle).handle);
    +  if NewIcon = nil then
    +    NewIcon := {%H-}Pointer(Application.Icon.Handle);
    +  if NewIcon <> GlobalIcon then
    +  begin
    +    GlobalIcon := NewIcon;
    +    ForceDirectories(IconThemePath);
    +    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
    +    if FileExists(GlobalIconPath) then
    +      DeleteFile(GlobalIconPath);
    +    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
    +    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
    +    if GlobalAppIndicator <> nil then
    +        app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
    +  end
    +  else
    +    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
    +  { Only the first created AppIndicator is functional }
    +  if GlobalAppIndicator = nil then
    +    { It seems that icons can only come from files :( }
    +    GlobalAppIndicator := app_indicator_new_with_path(PChar(FName), PChar(FIconName),
    +      APP_INDICATOR_CATEGORY_APPLICATION_STATUS, IconThemePath);
    +  Update;
    +end;
    +
    +destructor TAppIndTrayIconHandle.Destroy;
    +begin
    +  { Hide the global AppIndicator }
    +  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_PASSIVE);
    +  inherited Destroy;
    +end;
    +
    +procedure TAppIndTrayIconHandle.Update;
    +var
    +  NewIcon: Pointer;
    +begin
    +  NewIcon := {%H-}Pointer(TGTK3Image(FTrayIcon.Icon.Handle).Handle);
    +  if NewIcon = nil then
    +    NewIcon := {%H-}Pointer(Application.Icon.Handle);
    +  if NewIcon <> GlobalIcon then
    +  begin
    +    GlobalIcon := NewIcon;
    +    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
    +    ForceDirectories(IconThemePath);
    +    if FileExists(GlobalIconPath) then
    +      DeleteFile(GlobalIconPath);
    +    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
    +    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
    +    { Again it seems that icons can only come from files }
    +    app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
    +  end;
    +  { It seems to me you can only set the menu once for an AppIndicator }
    +  if (app_indicator_get_menu(GlobalAppIndicator) = nil) and (FTrayIcon.PopUpMenu <> nil) then
    +    //app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(FTrayIcon.PopUpMenu.Handle));
    +    app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(TGTK3Menu(FTrayIcon.PopUpMenu.Handle).Widget));
    +  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_ACTIVE);
    +end;
    +
    +{ TAppIndWSCustomTrayIcon }
    +
    +class function TGtk3WSTrayIcon.Hide(const ATrayIcon: TCustomTrayIcon): Boolean;
    +var
    +  T: TAppIndTrayIconHandle;
    +begin
    +  if ATrayIcon.Handle <> 0 then
    +  begin
    +    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
    +    ATrayIcon.Handle := 0;
    +    T.Free;
    +  end;
    +  Result := True;
    +end;
    +
    +class function TGtk3WSTrayIcon.Show(const ATrayIcon: TCustomTrayIcon): Boolean;
    +var
    +  T: TAppIndTrayIconHandle;
    +begin
    +  if ATrayIcon.Handle = 0 then
    +  begin
    +    T := TAppIndTrayIconHandle.Create(ATrayIcon);
    +    ATrayIcon.Handle := HWND(T);
    +  end;
    +  Result := True;
    +end;
    +
    +class procedure TGtk3WSTrayIcon.InternalUpdate(const ATrayIcon: TCustomTrayIcon);
    +var
    +  T: TAppIndTrayIconHandle;
    +begin
    +  if ATrayIcon.Handle <> 0 then
    +  begin
    +    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
    +    T.Update;
    +  end;
    +end;
    +
    +class function TGtk3WSTrayIcon.GetPosition(const ATrayIcon: TCustomTrayIcon): TPoint;
    +begin
    +  Result := Point(0, 0);
    +end;
    +
    +{ AppIndicatorInit }
    +
    +var
    +  Loaded: Boolean;
    +  Initialized: Boolean;
    +
    +function Gtk3AppIndicatorInit: Boolean;
    +var
    +  Module: HModule;
    +
    +  function TryLoad(const ProcName: string; var Proc: Pointer): Boolean;
    +  begin
    +    Proc := GetProcAddress(Module, ProcName);
    +    Result := Proc <> nil;
    +  end;
    +
    +begin
    +  Result := False;
    +  if Loaded then
    +    Exit(Initialized);
    +  Loaded := True;
    +  if Initialized then
    +    Exit(True);
    +  Module := LoadLibrary(libappindicator_3);        // might have several package names, see wiki
    +  if Module = 0 then
    +     Exit;
    +  Result :=
    +    TryLoad('app_indicator_get_type', @app_indicator_get_type) and
    +    TryLoad('app_indicator_new', @app_indicator_new) and
    +    TryLoad('app_indicator_new_with_path', @app_indicator_new_with_path) and
    +    TryLoad('app_indicator_set_status', @app_indicator_set_status) and
    +    TryLoad('app_indicator_set_attention_icon', @app_indicator_set_attention_icon) and
    +    TryLoad('app_indicator_set_menu', @app_indicator_set_menu) and
    +    TryLoad('app_indicator_set_icon', @app_indicator_set_icon) and
    +    TryLoad('app_indicator_set_label', @app_indicator_set_label) and
    +    TryLoad('app_indicator_set_icon_theme_path', @app_indicator_set_icon_theme_path) and
    +    TryLoad('app_indicator_set_ordering_index', @app_indicator_set_ordering_index) and
    +    TryLoad('app_indicator_get_id', @app_indicator_get_id) and
    +    TryLoad('app_indicator_get_category', @app_indicator_get_category) and
    +    TryLoad('app_indicator_get_status', @app_indicator_get_status) and
    +    TryLoad('app_indicator_get_icon', @app_indicator_get_icon) and
    +    TryLoad('app_indicator_get_icon_theme_path', @app_indicator_get_icon_theme_path) and
    +    TryLoad('app_indicator_get_attention_icon', @app_indicator_get_attention_icon) and
    +    TryLoad('app_indicator_get_menu', @app_indicator_get_menu) and
    +    TryLoad('app_indicator_get_label', @app_indicator_get_label) and
    +    TryLoad('app_indicator_get_label_guide', @app_indicator_get_label_guide) and
    +    TryLoad('app_indicator_get_ordering_index', @app_indicator_get_ordering_index);
    +  Initialized := Result;
    +end;
    +
    +initialization
    +  GlobalAppIndicator := nil;
    +  GlobalIconPath := '';
    +finalization
    +  if FileExists(GlobalIconPath) then
    +    DeleteFile(GlobalIconPath);
    +  if GlobalAppIndicator <> nil then
    +    g_object_unref(GlobalAppIndicator);
    +end.
    
    Oct28.patch (12,255 bytes)

Relationships

related to 0036199 resolvedJuha Manninen Futher fixes for TrayIcon 

Activities

David

2019-10-24 13:57

reporter  

appind.patch (14,892 bytes)
Index: lcl/interfaces/gtk2/unitywsctrls.pas
===================================================================
--- lcl/interfaces/gtk2/unitywsctrls.pas	(revision 62111)
+++ lcl/interfaces/gtk2/unitywsctrls.pas	(working copy)
@@ -260,41 +260,23 @@
   Initialized: Boolean;
 
 function UnityAppIndicatorInit: Boolean;
+var
+  Module: HModule;
+  UseAppInd : string;
+
+  function NeedAppIndicator: boolean;
   var
-    Module: HModule;
-    UseAppInd : string;
+    DeskTop : String;
+  begin
+    DeskTop := GetEnvironmentVariableUTF8('XDG_CURRENT_DESKTOP');
+    // See the wiki for details of what extras these desktops require !!
+    if (Desktop = 'GNOME')
+      or (DeskTop = 'Unity')
+      or (Desktop = 'Enlightenment')
+      or (Desktop = 'ubuntu:GNOME') then exit(True);
+    Result := False;
+  end;
 
-    function NeedAppIndicator: boolean;
-    var
-      DeskTop,  VersionSt : String;
-      ProcFile: TextFile;
-    begin
-      DeskTop := GetEnvironmentVariableUTF8('XDG_CURRENT_DESKTOP');
-      // See the wiki for details of what extras these desktops require !!
-      if (DeskTop = 'Unity')
-         or (Desktop = 'Enlightenment')
-            then exit(True);
-      if (DeskTop = 'GNOME') then begin
-          {$PUSH}
-          {$IOChecks off}
-          AssignFile(ProcFile, '/proc/version');
-          reset(ProcFile);
-          if IOResult<>0 then exit(false);
-          {$POP}
-          readln(ProcFile, VersionSt);
-          CloseFile(ProcFile);
-          if ( (pos('mageia', VersionSt) > 0) or
-            (pos('Debian', VersionSt) > 0) or
-            (pos('Red Hat', VersionSt) > 0) or
-            (pos('SUSE', VersionSt) > 0) )
-            // 19.04 and earlier Ubuntu Gnome does not need LibAppIndicator3
-            then exit(True);
-      end;
-      Result := False;
-    end;
-
-
-
   function TryLoad(const ProcName: string; var Proc: Pointer): Boolean;
   begin
     Proc := GetProcAddress(Module, ProcName);
Index: lcl/interfaces/gtk3/appindicator.pas
===================================================================
--- lcl/interfaces/gtk3/appindicator.pas	(nonexistent)
+++ lcl/interfaces/gtk3/appindicator.pas	(working copy)
@@ -0,0 +1,312 @@
+{ Copyleft implementation of TTrayIcon for
+  Unity applications indicators
+  Created 2015 by Anthony Walter sysrpl@gmail.com
+
+  Ported (minimally) to GTK3 by David Bannon, October 2019, Thanks Anthony !
+  Renamed from unitywsctrls to AppIndicator reflecting the fact that
+  LibAppIndicator3 has survived beyond Unity and is useful on current Linuxes.
+  Works out of the box on Ubuntu based systems including ones using Gnome Desktop.
+  On 'strict' Gnome (RedHat, Suse, Mageia) will require the addition of
+  LibAppIndicator3 and a Gnome Shell Extension such as TopIcons Plus.
+
+  Read the wiki for (lots) more info.
+  }
+
+unit appindicator;
+
+interface
+
+{$mode delphi}
+uses
+  GLib2, LazGtk3, LazGdkPixbuf2, gtk3widgets,
+  Classes, SysUtils, dynlibs,
+  Graphics, Controls, Forms, ExtCtrls, WSExtCtrls, LCLType, LazUTF8,
+  FileUtil;
+
+{ TUnityWSCustomTrayIcon (aka AppIndicator) is the class for tray icons on
+  systems running the Unity desktop environment.
+
+  LibAppIndicator3 allows only AppIndicator objects in its tray. These objects
+  have the following reduced functionality:
+
+  Tooltips are not allowed
+  Icons do not receive mouse events
+  Indicators display a menu when clicked by any mouse button
+
+  See also: http://www.markshuttleworth.com/archives/347
+  "Clicking on an indicator will open its menu..."
+  "There’ll be no ability for arbitrary applications to define arbitrary
+   behaviours to arbitrary events on indicators"
+
+  Personal observations:
+
+  A popup menu is required always
+  You can only create one AppIndicator per appplication
+  You cannot use a different popupmenu once one has been used }
+
+type
+  TAppIndWSCustomTrayIcon = class(TWSCustomTrayIcon)
+  published
+    class function Hide(const ATrayIcon: TCustomTrayIcon): Boolean; override;
+    class function Show(const ATrayIcon: TCustomTrayIcon): Boolean; override;
+    class procedure InternalUpdate(const ATrayIcon: TCustomTrayIcon); override;
+    class function GetPosition(const {%H-}ATrayIcon: TCustomTrayIcon): TPoint; override;
+  end;
+
+{ UnityAppIndicatorInit returns true if libappindicator_3 library can be loaded }
+
+function AppIndicatorInit: Boolean;
+
+implementation
+
+uses gtk3objects;     // TGtk3Image
+
+const
+  libappindicator_3 = 'libappindicator3.so.1';
+
+{const
+  APP_INDICATOR_SIGNAL_NEW_ICON = 'new-icon';
+  APP_INDICATOR_SIGNAL_NEW_ATTENTION_ICON = 'new-attention-icon';
+  APP_INDICATOR_SIGNAL_NEW_STATUS = 'new-status';
+  APP_INDICATOR_SIGNAL_NEW_LABEL = 'new-label';
+  APP_INDICATOR_SIGNAL_CONNECTION_CHANGED = 'connection-changed';
+  APP_INDICATOR_SIGNAL_NEW_ICON_THEME_PATH = 'new-icon-theme-path';
+}
+type
+  TAppIndicatorCategory = (
+    APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
+    APP_INDICATOR_CATEGORY_COMMUNICATIONS,
+    APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
+    APP_INDICATOR_CATEGORY_HARDWARE,
+    APP_INDICATOR_CATEGORY_OTHER
+  );
+
+  TAppIndicatorStatus = (
+    APP_INDICATOR_STATUS_PASSIVE,
+    APP_INDICATOR_STATUS_ACTIVE,
+    APP_INDICATOR_STATUS_ATTENTION
+  );
+
+  PAppIndicator = Pointer;
+
+var
+  { GlobalAppIndicator creation routines }
+  app_indicator_get_type: function: GType; cdecl;
+  app_indicator_new: function(id, icon_name: PGChar; category: TAppIndicatorCategory): PAppIndicator; cdecl;
+  app_indicator_new_with_path: function(id, icon_name: PGChar; category: TAppIndicatorCategory; icon_theme_path: PGChar): PAppIndicator; cdecl;
+  { Set properties }
+  app_indicator_set_status: procedure(self: PAppIndicator; status: TAppIndicatorStatus); cdecl;
+  app_indicator_set_attention_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
+  app_indicator_set_menu: procedure(self: PAppIndicator; menu: PGtkMenu); cdecl;
+  app_indicator_set_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
+  app_indicator_set_label: procedure(self: PAppIndicator; _label, guide: PGChar); cdecl;
+  app_indicator_set_icon_theme_path: procedure(self: PAppIndicator; icon_theme_path: PGChar); cdecl;
+  app_indicator_set_ordering_index: procedure(self: PAppIndicator; ordering_index: guint32); cdecl;
+  { Get properties }
+  app_indicator_get_id: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_category: function(self: PAppIndicator): TAppIndicatorCategory; cdecl;
+  app_indicator_get_status: function(self: PAppIndicator): TAppIndicatorStatus; cdecl;
+  app_indicator_get_icon: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_icon_theme_path: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_attention_icon: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_menu: function(self: PAppIndicator): PGtkMenu; cdecl;
+  app_indicator_get_label: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_label_guide: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_ordering_index: function(self: PAppIndicator): guint32; cdecl;
+
+{ TAppIndTrayIconHandle }
+
+type
+  TAppIndTrayIconHandle = class
+  private
+    FTrayIcon: TCustomTrayIcon;
+    FName: string;
+    FIconName: string;
+  public
+    constructor Create(TrayIcon: TCustomTrayIcon);
+    destructor Destroy; override;
+    procedure Update;
+  end;
+
+{ It seems to me, and please tell me otherwise if untrue, that the only way
+  to load icons for AppIndicator is through files }
+
+const
+  IconThemePath = '/tmp/appindicators/';
+  IconType = 'png';
+
+{ It seems to me, and please tell me otherwise if untrue, that you can only
+  create one working AppIndicator for your program over its lifetime }
+
+var
+  GlobalAppIndicator: PAppIndicator;
+  GlobalIcon: Pointer;
+  GlobalIconPath: string;
+
+constructor TAppIndTrayIconHandle.Create(TrayIcon: TCustomTrayIcon);
+var
+  NewIcon: Pointer;
+begin
+  inherited Create;
+  FTrayIcon := TrayIcon;
+  FName := 'app-' + IntToHex(IntPtr(Application), SizeOf(IntPtr) * 2);
+  NewIcon := {%H-}Pointer(TGtk3Image(FTrayIcon.Icon.Handle).handle);
+  if NewIcon = nil then
+    NewIcon := {%H-}Pointer(Application.Icon.Handle);
+  if NewIcon <> GlobalIcon then
+  begin
+    GlobalIcon := NewIcon;
+    ForceDirectories(IconThemePath);
+    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
+    if FileExists(GlobalIconPath) then
+      DeleteFile(GlobalIconPath);
+    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
+    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
+    if GlobalAppIndicator <> nil then
+        app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
+  end
+  else
+    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
+  { Only the first created AppIndicator is functional }
+  if GlobalAppIndicator = nil then
+    { It seems that icons can only come from files :( }
+    GlobalAppIndicator := app_indicator_new_with_path(PChar(FName), PChar(FIconName),
+      APP_INDICATOR_CATEGORY_APPLICATION_STATUS, IconThemePath);
+  Update;
+end;
+
+destructor TAppIndTrayIconHandle.Destroy;
+begin
+  { Hide the global AppIndicator }
+  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_PASSIVE);
+  inherited Destroy;
+end;
+
+procedure TAppIndTrayIconHandle.Update;
+var
+  NewIcon: Pointer;
+begin
+  NewIcon := {%H-}Pointer(TGTK3Image(FTrayIcon.Icon.Handle).Handle);
+  if NewIcon = nil then
+    NewIcon := {%H-}Pointer(Application.Icon.Handle);
+  if NewIcon <> GlobalIcon then
+  begin
+    GlobalIcon := NewIcon;
+    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
+    ForceDirectories(IconThemePath);
+    if FileExists(GlobalIconPath) then
+      DeleteFile(GlobalIconPath);
+    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
+    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
+    { Again it seems that icons can only come from files }
+    app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
+  end;
+  { It seems to me you can only set the menu once for an AppIndicator }
+  if (app_indicator_get_menu(GlobalAppIndicator) = nil) and (FTrayIcon.PopUpMenu <> nil) then
+    //app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(FTrayIcon.PopUpMenu.Handle));
+    app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(TGTK3Menu(FTrayIcon.PopUpMenu.Handle).Widget));
+  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_ACTIVE);
+end;
+
+{ TAppIndWSCustomTrayIcon }
+
+class function TAppIndWSCustomTrayIcon.Hide(const ATrayIcon: TCustomTrayIcon): Boolean;
+var
+  T: TAppIndTrayIconHandle;
+begin
+  if ATrayIcon.Handle <> 0 then
+  begin
+    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
+    ATrayIcon.Handle := 0;
+    T.Free;
+  end;
+  Result := True;
+end;
+
+class function TAppIndWSCustomTrayIcon.Show(const ATrayIcon: TCustomTrayIcon): Boolean;
+var
+  T: TAppIndTrayIconHandle;
+begin
+  if ATrayIcon.Handle = 0 then
+  begin
+    T := TAppIndTrayIconHandle.Create(ATrayIcon);
+    ATrayIcon.Handle := HWND(T);
+  end;
+  Result := True;
+end;
+
+class procedure TAppIndWSCustomTrayIcon.InternalUpdate(const ATrayIcon: TCustomTrayIcon);
+var
+  T: TAppIndTrayIconHandle;
+begin
+  if ATrayIcon.Handle <> 0 then
+  begin
+    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
+    T.Update;
+  end;
+end;
+
+class function TAppIndWSCustomTrayIcon.GetPosition(const ATrayIcon: TCustomTrayIcon): TPoint;
+begin
+  Result := Point(0, 0);
+end;
+
+{ AppIndicatorInit }
+
+var
+  Loaded: Boolean;
+  Initialized: Boolean;
+
+function AppIndicatorInit: Boolean;
+var
+  Module: HModule;
+
+  function TryLoad(const ProcName: string; var Proc: Pointer): Boolean;
+  begin
+    Proc := GetProcAddress(Module, ProcName);
+    Result := Proc <> nil;
+  end;
+
+begin
+  Result := False;
+  if Loaded then
+    Exit(Initialized);
+  Loaded := True;
+  if Initialized then
+    Exit(True);
+  Module := LoadLibrary(libappindicator_3);        // might have several package names, see wiki
+  if Module = 0 then
+     Exit;
+  Result :=
+    TryLoad('app_indicator_get_type', @app_indicator_get_type) and
+    TryLoad('app_indicator_new', @app_indicator_new) and
+    TryLoad('app_indicator_new_with_path', @app_indicator_new_with_path) and
+    TryLoad('app_indicator_set_status', @app_indicator_set_status) and
+    TryLoad('app_indicator_set_attention_icon', @app_indicator_set_attention_icon) and
+    TryLoad('app_indicator_set_menu', @app_indicator_set_menu) and
+    TryLoad('app_indicator_set_icon', @app_indicator_set_icon) and
+    TryLoad('app_indicator_set_label', @app_indicator_set_label) and
+    TryLoad('app_indicator_set_icon_theme_path', @app_indicator_set_icon_theme_path) and
+    TryLoad('app_indicator_set_ordering_index', @app_indicator_set_ordering_index) and
+    TryLoad('app_indicator_get_id', @app_indicator_get_id) and
+    TryLoad('app_indicator_get_category', @app_indicator_get_category) and
+    TryLoad('app_indicator_get_status', @app_indicator_get_status) and
+    TryLoad('app_indicator_get_icon', @app_indicator_get_icon) and
+    TryLoad('app_indicator_get_icon_theme_path', @app_indicator_get_icon_theme_path) and
+    TryLoad('app_indicator_get_attention_icon', @app_indicator_get_attention_icon) and
+    TryLoad('app_indicator_get_menu', @app_indicator_get_menu) and
+    TryLoad('app_indicator_get_label', @app_indicator_get_label) and
+    TryLoad('app_indicator_get_label_guide', @app_indicator_get_label_guide) and
+    TryLoad('app_indicator_get_ordering_index', @app_indicator_get_ordering_index);
+  Initialized := Result;
+end;
+
+initialization
+  GlobalAppIndicator := nil;
+  GlobalIconPath := '';
+finalization
+  if FileExists(GlobalIconPath) then
+    DeleteFile(GlobalIconPath);
+  if GlobalAppIndicator <> nil then
+    g_object_unref(GlobalAppIndicator);
+end.
Index: lcl/interfaces/gtk3/gtk3wsfactory.pas
===================================================================
--- lcl/interfaces/gtk3/gtk3wsfactory.pas	(revision 62111)
+++ lcl/interfaces/gtk3/gtk3wsfactory.pas	(working copy)
@@ -21,7 +21,7 @@
 uses
   Classes, Controls, ComCtrls, Calendar, StdCtrls, Arrow, Spin,
   Dialogs, ExtCtrls, ExtDlgs, Buttons, CheckLst, Forms, Grids, Menus,
-  ImgList, PairSplitter, WSLCLClasses;
+  ImgList, PairSplitter, WSLCLClasses, AppIndicator;
 
 
 // imglist
@@ -457,7 +457,11 @@
 function RegisterCustomTrayIcon: Boolean; alias : 'WSRegisterCustomTrayIcon';
 begin
   // RegisterWSComponent(TCustomTrayIcon, TGtk2WSCustomTrayIcon);
-  Result := False;
+  Result := True;
+  if AppIndicatorInit then
+     RegisterWSComponent(TCustomTrayIcon, TAppIndWSCustomTrayIcon)
+  else
+     Result := False;
 end;
 
 //ExtDlgs
appind.patch (14,892 bytes)

Zeljan Rikalo

2019-10-24 18:10

developer   ~0118788

There are 2 patches actually. One for gtk2 and one for gtk3. Please distinct those patches, also I will not commit gtk2 part.

Juha Manninen

2019-10-24 23:29

developer   ~0118791

Last edited: 2019-10-24 23:33

View 3 revisions

Sorry Zeljko, I will take over and study it more tomorrow Friday. This is related to the GTK2 version which I was involved in.
David, does the change for GTK2 part replace the patch in the related issue? Why did you include it here? They should be applied separately for clarity.
You should remove Anthony Walter's original comment header with "Unity" and all. Just mention your work is based on his work.
Why is the class name TAppIndWSCustomTrayIcon instead of TGtk3WSCustomTrayIcon?
Is the unit name good? The Unity addition by Walter was kind of a hack. You should follow the naming etc. conventions of other controls in the widgetset.
I will look at this more tomorrow...

David

2019-10-25 00:30

reporter   ~0118792

Er, sorry, there should not be any gtk2 component in there, even I know better than combine such different things in one patch.

I did my development in fixes branch and then, when I was ready, moved it over to trunk. While in trunk I made no changes to the gtk2 branch so I am at a loss to understand how it produced a different patch than I initially did with Fixes.

We could just delete the gtk2 section but I would prefer to back track a little and try and work out what actually happened.

Juha Manninen

2019-10-25 00:47

developer   ~0118794

> I did my development in fixes branch and then, when I was ready, moved it over to trunk.

Moved by copy/pasting files?
No, you should do your development in trunk only! By copying files from fixes branch you risk overwriting recent changes in trunk.
Besides, copying files around without any benefit is just plain dummy.

David

2019-10-25 01:23

reporter   ~0118795

Last edited: 2019-10-25 01:24

View 2 revisions

OK, I understand what happened. Early on I accidentally edited my UnityWSCtrls.pas in GTK2 tree of trunk. I reverted that change using SVN and viewing the file in Lazarus, building with it etc all seemed OK. However, when generating a patch, svn apparently remembered the history of my edit and used the edited content rather than the actual content in the tree.

A search indicates that I should have used -
svn cat -r 62111 lcl/interfaces/gtk2/unitywsctrls.pas > lcl/interfaces/gtk2/unitywsctrls.pas
rather than svn revert filename. I have done it the 'correct way' now, here is a new patch, 36209.patch, that I have carefully checked for no unexpected content. It does only two things, firstly putting the appindicator.pas file in the GTK3 tree and secondly, it hooks into lcl/interfaces/gtk3/gtk3wsfactory.pas to call appindicator.pas

Juha and Zeljan, I am really sorry for that carelessness.



36209.patch (12,929 bytes)
Index: lcl/interfaces/gtk3/appindicator.pas
===================================================================
--- lcl/interfaces/gtk3/appindicator.pas	(nonexistent)
+++ lcl/interfaces/gtk3/appindicator.pas	(working copy)
@@ -0,0 +1,312 @@
+{ Copyleft implementation of TTrayIcon for
+  Unity applications indicators
+  Created 2015 by Anthony Walter sysrpl@gmail.com
+
+  Ported (minimally) to GTK3 by David Bannon, October 2019, Thanks Anthony !
+  Renamed from unitywsctrls to AppIndicator reflecting the fact that
+  LibAppIndicator3 has survived beyond Unity and is useful on current Linuxes.
+  Works out of the box on Ubuntu based systems including ones using Gnome Desktop.
+  On 'strict' Gnome (RedHat, Suse, Mageia) will require the addition of
+  LibAppIndicator3 and a Gnome Shell Extension such as TopIcons Plus.
+
+  Read the wiki for (lots) more info.
+  }
+
+unit appindicator;
+
+interface
+
+{$mode delphi}
+uses
+  GLib2, LazGtk3, LazGdkPixbuf2, gtk3widgets,
+  Classes, SysUtils, dynlibs,
+  Graphics, Controls, Forms, ExtCtrls, WSExtCtrls, LCLType, LazUTF8,
+  FileUtil;
+
+{ TUnityWSCustomTrayIcon (aka AppIndicator) is the class for tray icons on
+  systems running the Unity desktop environment.
+
+  LibAppIndicator3 allows only AppIndicator objects in its tray. These objects
+  have the following reduced functionality:
+
+  Tooltips are not allowed
+  Icons do not receive mouse events
+  Indicators display a menu when clicked by any mouse button
+
+  See also: http://www.markshuttleworth.com/archives/347
+  "Clicking on an indicator will open its menu..."
+  "There’ll be no ability for arbitrary applications to define arbitrary
+   behaviours to arbitrary events on indicators"
+
+  Personal observations:
+
+  A popup menu is required always
+  You can only create one AppIndicator per appplication
+  You cannot use a different popupmenu once one has been used }
+
+type
+  TAppIndWSCustomTrayIcon = class(TWSCustomTrayIcon)
+  published
+    class function Hide(const ATrayIcon: TCustomTrayIcon): Boolean; override;
+    class function Show(const ATrayIcon: TCustomTrayIcon): Boolean; override;
+    class procedure InternalUpdate(const ATrayIcon: TCustomTrayIcon); override;
+    class function GetPosition(const {%H-}ATrayIcon: TCustomTrayIcon): TPoint; override;
+  end;
+
+{ UnityAppIndicatorInit returns true if libappindicator_3 library can be loaded }
+
+function AppIndicatorInit: Boolean;
+
+implementation
+
+uses gtk3objects;     // TGtk3Image
+
+const
+  libappindicator_3 = 'libappindicator3.so.1';
+
+{const
+  APP_INDICATOR_SIGNAL_NEW_ICON = 'new-icon';
+  APP_INDICATOR_SIGNAL_NEW_ATTENTION_ICON = 'new-attention-icon';
+  APP_INDICATOR_SIGNAL_NEW_STATUS = 'new-status';
+  APP_INDICATOR_SIGNAL_NEW_LABEL = 'new-label';
+  APP_INDICATOR_SIGNAL_CONNECTION_CHANGED = 'connection-changed';
+  APP_INDICATOR_SIGNAL_NEW_ICON_THEME_PATH = 'new-icon-theme-path';
+}
+type
+  TAppIndicatorCategory = (
+    APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
+    APP_INDICATOR_CATEGORY_COMMUNICATIONS,
+    APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
+    APP_INDICATOR_CATEGORY_HARDWARE,
+    APP_INDICATOR_CATEGORY_OTHER
+  );
+
+  TAppIndicatorStatus = (
+    APP_INDICATOR_STATUS_PASSIVE,
+    APP_INDICATOR_STATUS_ACTIVE,
+    APP_INDICATOR_STATUS_ATTENTION
+  );
+
+  PAppIndicator = Pointer;
+
+var
+  { GlobalAppIndicator creation routines }
+  app_indicator_get_type: function: GType; cdecl;
+  app_indicator_new: function(id, icon_name: PGChar; category: TAppIndicatorCategory): PAppIndicator; cdecl;
+  app_indicator_new_with_path: function(id, icon_name: PGChar; category: TAppIndicatorCategory; icon_theme_path: PGChar): PAppIndicator; cdecl;
+  { Set properties }
+  app_indicator_set_status: procedure(self: PAppIndicator; status: TAppIndicatorStatus); cdecl;
+  app_indicator_set_attention_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
+  app_indicator_set_menu: procedure(self: PAppIndicator; menu: PGtkMenu); cdecl;
+  app_indicator_set_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
+  app_indicator_set_label: procedure(self: PAppIndicator; _label, guide: PGChar); cdecl;
+  app_indicator_set_icon_theme_path: procedure(self: PAppIndicator; icon_theme_path: PGChar); cdecl;
+  app_indicator_set_ordering_index: procedure(self: PAppIndicator; ordering_index: guint32); cdecl;
+  { Get properties }
+  app_indicator_get_id: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_category: function(self: PAppIndicator): TAppIndicatorCategory; cdecl;
+  app_indicator_get_status: function(self: PAppIndicator): TAppIndicatorStatus; cdecl;
+  app_indicator_get_icon: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_icon_theme_path: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_attention_icon: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_menu: function(self: PAppIndicator): PGtkMenu; cdecl;
+  app_indicator_get_label: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_label_guide: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_ordering_index: function(self: PAppIndicator): guint32; cdecl;
+
+{ TAppIndTrayIconHandle }
+
+type
+  TAppIndTrayIconHandle = class
+  private
+    FTrayIcon: TCustomTrayIcon;
+    FName: string;
+    FIconName: string;
+  public
+    constructor Create(TrayIcon: TCustomTrayIcon);
+    destructor Destroy; override;
+    procedure Update;
+  end;
+
+{ It seems to me, and please tell me otherwise if untrue, that the only way
+  to load icons for AppIndicator is through files }
+
+const
+  IconThemePath = '/tmp/appindicators/';
+  IconType = 'png';
+
+{ It seems to me, and please tell me otherwise if untrue, that you can only
+  create one working AppIndicator for your program over its lifetime }
+
+var
+  GlobalAppIndicator: PAppIndicator;
+  GlobalIcon: Pointer;
+  GlobalIconPath: string;
+
+constructor TAppIndTrayIconHandle.Create(TrayIcon: TCustomTrayIcon);
+var
+  NewIcon: Pointer;
+begin
+  inherited Create;
+  FTrayIcon := TrayIcon;
+  FName := 'app-' + IntToHex(IntPtr(Application), SizeOf(IntPtr) * 2);
+  NewIcon := {%H-}Pointer(TGtk3Image(FTrayIcon.Icon.Handle).handle);
+  if NewIcon = nil then
+    NewIcon := {%H-}Pointer(Application.Icon.Handle);
+  if NewIcon <> GlobalIcon then
+  begin
+    GlobalIcon := NewIcon;
+    ForceDirectories(IconThemePath);
+    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
+    if FileExists(GlobalIconPath) then
+      DeleteFile(GlobalIconPath);
+    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
+    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
+    if GlobalAppIndicator <> nil then
+        app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
+  end
+  else
+    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
+  { Only the first created AppIndicator is functional }
+  if GlobalAppIndicator = nil then
+    { It seems that icons can only come from files :( }
+    GlobalAppIndicator := app_indicator_new_with_path(PChar(FName), PChar(FIconName),
+      APP_INDICATOR_CATEGORY_APPLICATION_STATUS, IconThemePath);
+  Update;
+end;
+
+destructor TAppIndTrayIconHandle.Destroy;
+begin
+  { Hide the global AppIndicator }
+  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_PASSIVE);
+  inherited Destroy;
+end;
+
+procedure TAppIndTrayIconHandle.Update;
+var
+  NewIcon: Pointer;
+begin
+  NewIcon := {%H-}Pointer(TGTK3Image(FTrayIcon.Icon.Handle).Handle);
+  if NewIcon = nil then
+    NewIcon := {%H-}Pointer(Application.Icon.Handle);
+  if NewIcon <> GlobalIcon then
+  begin
+    GlobalIcon := NewIcon;
+    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
+    ForceDirectories(IconThemePath);
+    if FileExists(GlobalIconPath) then
+      DeleteFile(GlobalIconPath);
+    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
+    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
+    { Again it seems that icons can only come from files }
+    app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
+  end;
+  { It seems to me you can only set the menu once for an AppIndicator }
+  if (app_indicator_get_menu(GlobalAppIndicator) = nil) and (FTrayIcon.PopUpMenu <> nil) then
+    //app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(FTrayIcon.PopUpMenu.Handle));
+    app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(TGTK3Menu(FTrayIcon.PopUpMenu.Handle).Widget));
+  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_ACTIVE);
+end;
+
+{ TAppIndWSCustomTrayIcon }
+
+class function TAppIndWSCustomTrayIcon.Hide(const ATrayIcon: TCustomTrayIcon): Boolean;
+var
+  T: TAppIndTrayIconHandle;
+begin
+  if ATrayIcon.Handle <> 0 then
+  begin
+    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
+    ATrayIcon.Handle := 0;
+    T.Free;
+  end;
+  Result := True;
+end;
+
+class function TAppIndWSCustomTrayIcon.Show(const ATrayIcon: TCustomTrayIcon): Boolean;
+var
+  T: TAppIndTrayIconHandle;
+begin
+  if ATrayIcon.Handle = 0 then
+  begin
+    T := TAppIndTrayIconHandle.Create(ATrayIcon);
+    ATrayIcon.Handle := HWND(T);
+  end;
+  Result := True;
+end;
+
+class procedure TAppIndWSCustomTrayIcon.InternalUpdate(const ATrayIcon: TCustomTrayIcon);
+var
+  T: TAppIndTrayIconHandle;
+begin
+  if ATrayIcon.Handle <> 0 then
+  begin
+    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
+    T.Update;
+  end;
+end;
+
+class function TAppIndWSCustomTrayIcon.GetPosition(const ATrayIcon: TCustomTrayIcon): TPoint;
+begin
+  Result := Point(0, 0);
+end;
+
+{ AppIndicatorInit }
+
+var
+  Loaded: Boolean;
+  Initialized: Boolean;
+
+function AppIndicatorInit: Boolean;
+var
+  Module: HModule;
+
+  function TryLoad(const ProcName: string; var Proc: Pointer): Boolean;
+  begin
+    Proc := GetProcAddress(Module, ProcName);
+    Result := Proc <> nil;
+  end;
+
+begin
+  Result := False;
+  if Loaded then
+    Exit(Initialized);
+  Loaded := True;
+  if Initialized then
+    Exit(True);
+  Module := LoadLibrary(libappindicator_3);        // might have several package names, see wiki
+  if Module = 0 then
+     Exit;
+  Result :=
+    TryLoad('app_indicator_get_type', @app_indicator_get_type) and
+    TryLoad('app_indicator_new', @app_indicator_new) and
+    TryLoad('app_indicator_new_with_path', @app_indicator_new_with_path) and
+    TryLoad('app_indicator_set_status', @app_indicator_set_status) and
+    TryLoad('app_indicator_set_attention_icon', @app_indicator_set_attention_icon) and
+    TryLoad('app_indicator_set_menu', @app_indicator_set_menu) and
+    TryLoad('app_indicator_set_icon', @app_indicator_set_icon) and
+    TryLoad('app_indicator_set_label', @app_indicator_set_label) and
+    TryLoad('app_indicator_set_icon_theme_path', @app_indicator_set_icon_theme_path) and
+    TryLoad('app_indicator_set_ordering_index', @app_indicator_set_ordering_index) and
+    TryLoad('app_indicator_get_id', @app_indicator_get_id) and
+    TryLoad('app_indicator_get_category', @app_indicator_get_category) and
+    TryLoad('app_indicator_get_status', @app_indicator_get_status) and
+    TryLoad('app_indicator_get_icon', @app_indicator_get_icon) and
+    TryLoad('app_indicator_get_icon_theme_path', @app_indicator_get_icon_theme_path) and
+    TryLoad('app_indicator_get_attention_icon', @app_indicator_get_attention_icon) and
+    TryLoad('app_indicator_get_menu', @app_indicator_get_menu) and
+    TryLoad('app_indicator_get_label', @app_indicator_get_label) and
+    TryLoad('app_indicator_get_label_guide', @app_indicator_get_label_guide) and
+    TryLoad('app_indicator_get_ordering_index', @app_indicator_get_ordering_index);
+  Initialized := Result;
+end;
+
+initialization
+  GlobalAppIndicator := nil;
+  GlobalIconPath := '';
+finalization
+  if FileExists(GlobalIconPath) then
+    DeleteFile(GlobalIconPath);
+  if GlobalAppIndicator <> nil then
+    g_object_unref(GlobalAppIndicator);
+end.
Index: lcl/interfaces/gtk3/gtk3wsfactory.pas
===================================================================
--- lcl/interfaces/gtk3/gtk3wsfactory.pas	(revision 62114)
+++ lcl/interfaces/gtk3/gtk3wsfactory.pas	(working copy)
@@ -21,7 +21,7 @@
 uses
   Classes, Controls, ComCtrls, Calendar, StdCtrls, Arrow, Spin,
   Dialogs, ExtCtrls, ExtDlgs, Buttons, CheckLst, Forms, Grids, Menus,
-  ImgList, PairSplitter, WSLCLClasses;
+  ImgList, PairSplitter, WSLCLClasses, AppIndicator;
 
 
 // imglist
@@ -457,7 +457,11 @@
 function RegisterCustomTrayIcon: Boolean; alias : 'WSRegisterCustomTrayIcon';
 begin
   // RegisterWSComponent(TCustomTrayIcon, TGtk2WSCustomTrayIcon);
-  Result := False;
+  Result := True;
+  if AppIndicatorInit then
+     RegisterWSComponent(TCustomTrayIcon, TAppIndWSCustomTrayIcon)
+  else
+     Result := False;
 end;
 
 //ExtDlgs
36209.patch (12,929 bytes)

Juha Manninen

2019-10-25 15:02

developer   ~0118803

David, did you read my other notions and questions in #c118791 ?

David

2019-10-26 01:57

reporter   ~0118816

Last edited: 2019-10-26 08:31

View 2 revisions

> David, did you read my other notions and questions in #c118791 ?
No, was so cutup about my stuff up with the patch ....

>You should remove Anthony Walter's original comment header with "Unity" and all. Just mention your work is based on his work.
I would consider it still Anthony's work with some minor changes by me.

Why is the class name TAppIndWSCustomTrayIcon instead of TGtk3WSCustomTrayIcon?
> Consistent with how Anthony named the original. But I can see how the the GTK3 label would make more sense. Will rename and post another patch later today, it does now have specific GTK3 things in there.

> The Unity addition by Walter was kind of a hack.
I am not absolutely sure my work is not also a hack. It does get us a working TrayIcon on almost all systems for now. But many of those systems will require extra stuff be installed and that is only going to get worse. This particular hack may buy us a couple of years.....
-------------
EDIT: No, on further consideration, I do not think it should be renamed TGtk3WSCustomTrayIcon. That name should be reserved for the 'proper" version that will have to come a little later. Juha, I'll make changes if you insist but I'd rather stick with the last patch I posted if you don't mind.
--------------

Juha Manninen

2019-10-26 18:46

developer   ~0118856

> "Consistent with how Anthony named the original."
Anthony chose some lousy names and you must do the same thing? No, that is a recipe for so called "code rot" effect. Hacks and poor design are copied around.
This is not to mock Anthony's work. His TrayIcon unit was then a special case for _only_ Unity desktop. The unit was going to be history soon.
You on the other hand are implementing a TrayIcon for GTK3 bindings. Sure you can copy from other LGPL code but you don't need to act like a dummy. Use your judgement in naming conventions and code structure! Code always improves incrementally in cycles. In this cycle your task is to remove quick hacks and create consistent high quality code.
You copied as-is a comment saying: "TTrayIcon for Unity applications indicators" and don't see anything wrong with it. Strange!
Please look at other units in GTK3 and other bindings. Usually they don't mention the author at all. This is a community effort and the author is kind of irrelevant. It can always be checked from revision control.
So, please add a license block to your unit like the other units have:
 *****************************************************************************
  This file is part of the Lazarus Component Library (LCL)

  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************

It is actually a requirement of (L)GPL although it was missing from Anthony's unit.
Then add a short description of what the unit does. Many units don't have any description but it would be nice.
Then you can add the author (yourself) and mention it was based on Anthony's work.
Study also the naming convention of GTK3 binding unit files.

> I do not think it should be renamed TGtk3WSCustomTrayIcon. That name should
> be reserved for the 'proper" version that will have to come a little later.

I am not sure if I understand. The LCL TTrayIcon component can map to only one GTK3 implementation at a time. It means the future proper version will replace this one. This code will be in revision history only.
TGtk3WSCustomTrayIcon is the right name in both cases. The name does not imply how "proper" the code is. Or maybe you should name this TGtk3WSLousyTrayIcon to separate it from the proper version. Anyway it is part of GTK3 binding code thus "Gtk3" should logically be included.
Maybe you meant to create a whole new LCL component for the "proper" version. Then you would have to implement bindings for all widgetsets. This is a cross-platform library, you know.
Please explain your idea.

David

2019-10-27 00:30

reporter   ~0118864

OK, OK, Juha stay calm ! This is very minor compared to getting 2.0.6 out. I will tidy up the header and rename it TGtk3WSCustomTrayIcon.

(I was under the impression that the "Custom" label was used for virtual classes, that will be used as ancestor of eg TGTK3WSTrayIcon. But your call.)

When it comes to replacing it with something the dreaded Gnome Devs approve of, we'll isolate the two versions with Defines and maybe even another env var during the transition stage.

Davo

Juha Manninen

2019-10-27 09:20

developer   ~0118866

Last edited: 2019-10-27 09:27

View 2 revisions

> I was under the impression that the "Custom" label was used for virtual classes, that will be used as ancestor of eg TGTK3WSTrayIcon.
Good point. I was looking only at the GTK2 stuff. There "Custom" is used in WS classes sometimes but sometimes not. It may well be a code rot effect, somebody just copied the "Custom" without thinking further. For some reason there is TGtk2WSCustomTrayIcon but no TGtk2WSTrayIcon.
So yes, TGTK3WSTrayIcon is a better name.

I also understand your idea of 2 classes if they are needed in different systems. Like now we select the class based on UnityAppIndicatorInit test:
 function RegisterCustomTrayIcon: Boolean; alias : 'WSRegisterCustomTrayIcon';
 begin
   if UnityAppIndicatorInit then
     RegisterWSComponent(TCustomTrayIcon, TUnityWSCustomTrayIcon)
   else
     RegisterWSComponent(TCustomTrayIcon, TGtk2WSCustomTrayIcon);
   Result := True;
 end;
Is such a test needed also in the future or will the "proper" version work everywhere?
Remember, Gnome is not the only desktop under which a GTK3 app can be run. I can run it under KDE for example.
Although I recommend things here, I am not an expert in this topic. You must use your judgement.

A note about GTK2 versus GTK3 bindings:
GTK1 was the first GUI binding and it became a bit messy over time. It already had hacks to support GTK2. Then it was copied as-is for GTK2 bindings, modified further and became more messy.
GTK3 bindings on the other hand is a fresh start made by Zeljko. It is clean of the old hacks. Please keep that in mind when you study the code.
Zeljko is also the best person to answer any detailed technical question that comes up.

BTW, I added license info for unit UnityWSCtrls in r62126.

David

2019-10-28 10:54

reporter   ~0118893

> Is such a test needed also in the future or will the "proper" version work everywhere?
Might depend on how long we take to get the "proper" version. Right now, no it would not work with all current systems likely to be around. A couple of years and hopefully a lot of those older systems may be out of service (but not all) and its also possible that someone will have come up with yet another model. I believe we will need that dual system for quite some time to come.

Yep, Zeljko was very quick (and accurate) with some help via the forum.

Attached is what I call Oct28.patch. The file and component now called Gtk3WSTrayIcon. Internally, that is below "implementation", things are still called AppIndicator because thats what they are. we are using AppIndicator to implement a System TrayIcon. But from outside, its all consistently named. I have cleaned up the comments and put the license stamp on. Removed a lot of the discussion, its still available in the GTK2 version of course. And its useful too.

I have checked to ensure no unexpected extras in the patch :-) I have tested with several systems and desktops, all good except for the issue I have logged under 36220 - Fedora (*&!!)

Davo

Oct28.patch (12,255 bytes)
Index: lcl/interfaces/gtk3/gtk3wsfactory.pas
===================================================================
--- lcl/interfaces/gtk3/gtk3wsfactory.pas	(revision 62136)
+++ lcl/interfaces/gtk3/gtk3wsfactory.pas	(working copy)
@@ -137,7 +137,7 @@
 uses
   Gtk3WSImgList, Gtk3WSControls, Gtk3WSForms, Gtk3WSButtons, Gtk3WSStdCtrls,
   Gtk3WSComCtrls, Gtk3WSExtCtrls, Gtk3WSSpin, Gtk3WSMenus, Gtk3WSCalendar,
-  Gtk3WSDialogs, Gtk3WSCheckLst, Gtk3WSExtDlgs, gtk3wssplitter;
+  Gtk3WSDialogs, Gtk3WSCheckLst, Gtk3WSExtDlgs, gtk3wssplitter, Gtk3WSTrayIcon;
 
 // imglist
 function RegisterCustomImageListResolution: Boolean; alias : 'WSRegisterCustomImageListResolution';
@@ -457,7 +457,11 @@
 function RegisterCustomTrayIcon: Boolean; alias : 'WSRegisterCustomTrayIcon';
 begin
   // RegisterWSComponent(TCustomTrayIcon, TGtk2WSCustomTrayIcon);
-  Result := False;
+  Result := True;
+  if Gtk3AppIndicatorInit then
+     RegisterWSComponent(TCustomTrayIcon, TGtk3WSTrayIcon)
+  else
+     Result := False;
 end;
 
 //ExtDlgs
Index: lcl/interfaces/gtk3/gtk3wstrayicon.pas
===================================================================
--- lcl/interfaces/gtk3/gtk3wstrayicon.pas	(nonexistent)
+++ lcl/interfaces/gtk3/gtk3wstrayicon.pas	(working copy)
@@ -0,0 +1,287 @@
+{ 
+ *****************************************************************************
+ *                               gtk3WSTrayIcon.pas                          *
+ *                               ------------------                          *
+ *                                                                           *
+ *                                                                           *
+ *****************************************************************************
+
+ *****************************************************************************
+  This file is part of the Lazarus Component Library (LCL)
+
+  See the file COPYING.modifiedLGPL.txt, included in this distribution,
+  for details about the license.
+ *****************************************************************************
+
+A unit that uses LibAppIndicator3 to display a TrayIcon in GTK3.   Based on a
+GTK2 version by Anthony Walter, now works with many common Linux systems "out 
+of the box" and almost all of the remainder with addition of LibAppIndicator3
+and TopIconsPlus or similar Gnome Extension. 
+
+See Wiki for details and Limitations (Menu only, one Icon only....)
+Also refer to discussion in ../gtk2/UnityWSCtrls.pas
+}
+
+
+unit gtk3wstrayicon;
+
+interface
+
+{$mode delphi}
+uses
+  GLib2, LazGtk3, LazGdkPixbuf2, gtk3widgets,
+  Classes, SysUtils, dynlibs,
+  Graphics, Controls, Forms, ExtCtrls, WSExtCtrls, LCLType, LazUTF8,
+  FileUtil;
+
+type
+  TGtk3WSTrayIcon = class(TWSCustomTrayIcon)
+  published
+    class function Hide(const ATrayIcon: TCustomTrayIcon): Boolean; override;
+    class function Show(const ATrayIcon: TCustomTrayIcon): Boolean; override;
+    class procedure InternalUpdate(const ATrayIcon: TCustomTrayIcon); override;
+    class function GetPosition(const {%H-}ATrayIcon: TCustomTrayIcon): TPoint; override;
+  end;
+
+{ Gtk3AppIndicatorInit returns true if LibAppIndicator_3 library has been loaded }
+function Gtk3AppIndicatorInit: Boolean;
+
+implementation
+
+uses gtk3objects;     // TGtk3Image
+
+const
+  libappindicator_3 = 'libappindicator3.so.1';
+
+type
+  TAppIndicatorCategory = (
+    APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
+    APP_INDICATOR_CATEGORY_COMMUNICATIONS,
+    APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
+    APP_INDICATOR_CATEGORY_HARDWARE,
+    APP_INDICATOR_CATEGORY_OTHER
+  );
+
+  TAppIndicatorStatus = (
+    APP_INDICATOR_STATUS_PASSIVE,
+    APP_INDICATOR_STATUS_ACTIVE,
+    APP_INDICATOR_STATUS_ATTENTION
+  );
+
+  PAppIndicator = Pointer;
+
+var
+  { GlobalAppIndicator creation routines }
+  app_indicator_get_type: function: GType; cdecl;
+  app_indicator_new: function(id, icon_name: PGChar; category: TAppIndicatorCategory): PAppIndicator; cdecl;
+  app_indicator_new_with_path: function(id, icon_name: PGChar; category: TAppIndicatorCategory; icon_theme_path: PGChar): PAppIndicator; cdecl;
+  { Set properties }
+  app_indicator_set_status: procedure(self: PAppIndicator; status: TAppIndicatorStatus); cdecl;
+  app_indicator_set_attention_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
+  app_indicator_set_menu: procedure(self: PAppIndicator; menu: PGtkMenu); cdecl;
+  app_indicator_set_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
+  app_indicator_set_label: procedure(self: PAppIndicator; _label, guide: PGChar); cdecl;
+  app_indicator_set_icon_theme_path: procedure(self: PAppIndicator; icon_theme_path: PGChar); cdecl;
+  app_indicator_set_ordering_index: procedure(self: PAppIndicator; ordering_index: guint32); cdecl;
+  { Get properties }
+  app_indicator_get_id: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_category: function(self: PAppIndicator): TAppIndicatorCategory; cdecl;
+  app_indicator_get_status: function(self: PAppIndicator): TAppIndicatorStatus; cdecl;
+  app_indicator_get_icon: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_icon_theme_path: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_attention_icon: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_menu: function(self: PAppIndicator): PGtkMenu; cdecl;
+  app_indicator_get_label: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_label_guide: function(self: PAppIndicator): PGChar; cdecl;
+  app_indicator_get_ordering_index: function(self: PAppIndicator): guint32; cdecl;
+
+{ TAppIndTrayIconHandle }
+
+type
+  TAppIndTrayIconHandle = class
+  private
+    FTrayIcon: TCustomTrayIcon;
+    FName: string;
+    FIconName: string;
+  public
+    constructor Create(TrayIcon: TCustomTrayIcon);
+    destructor Destroy; override;
+    procedure Update;
+  end;
+
+const
+  IconThemePath = '/tmp/appindicators/'; // We must write our icon to a file.
+  IconType = 'png';
+
+var
+  GlobalAppIndicator: PAppIndicator;
+  GlobalIcon: Pointer;
+  GlobalIconPath: string;
+
+constructor TAppIndTrayIconHandle.Create(TrayIcon: TCustomTrayIcon);
+var
+  NewIcon: Pointer;
+begin
+  inherited Create;
+  FTrayIcon := TrayIcon;
+  FName := 'app-' + IntToHex(IntPtr(Application), SizeOf(IntPtr) * 2);
+  NewIcon := {%H-}Pointer(TGtk3Image(FTrayIcon.Icon.Handle).handle);
+  if NewIcon = nil then
+    NewIcon := {%H-}Pointer(Application.Icon.Handle);
+  if NewIcon <> GlobalIcon then
+  begin
+    GlobalIcon := NewIcon;
+    ForceDirectories(IconThemePath);
+    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
+    if FileExists(GlobalIconPath) then
+      DeleteFile(GlobalIconPath);
+    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
+    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
+    if GlobalAppIndicator <> nil then
+        app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
+  end
+  else
+    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
+  { Only the first created AppIndicator is functional }
+  if GlobalAppIndicator = nil then
+    { It seems that icons can only come from files :( }
+    GlobalAppIndicator := app_indicator_new_with_path(PChar(FName), PChar(FIconName),
+      APP_INDICATOR_CATEGORY_APPLICATION_STATUS, IconThemePath);
+  Update;
+end;
+
+destructor TAppIndTrayIconHandle.Destroy;
+begin
+  { Hide the global AppIndicator }
+  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_PASSIVE);
+  inherited Destroy;
+end;
+
+procedure TAppIndTrayIconHandle.Update;
+var
+  NewIcon: Pointer;
+begin
+  NewIcon := {%H-}Pointer(TGTK3Image(FTrayIcon.Icon.Handle).Handle);
+  if NewIcon = nil then
+    NewIcon := {%H-}Pointer(Application.Icon.Handle);
+  if NewIcon <> GlobalIcon then
+  begin
+    GlobalIcon := NewIcon;
+    FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
+    ForceDirectories(IconThemePath);
+    if FileExists(GlobalIconPath) then
+      DeleteFile(GlobalIconPath);
+    GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
+    gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
+    { Again it seems that icons can only come from files }
+    app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
+  end;
+  { It seems to me you can only set the menu once for an AppIndicator }
+  if (app_indicator_get_menu(GlobalAppIndicator) = nil) and (FTrayIcon.PopUpMenu <> nil) then
+    //app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(FTrayIcon.PopUpMenu.Handle));
+    app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(TGTK3Menu(FTrayIcon.PopUpMenu.Handle).Widget));
+  app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_ACTIVE);
+end;
+
+{ TAppIndWSCustomTrayIcon }
+
+class function TGtk3WSTrayIcon.Hide(const ATrayIcon: TCustomTrayIcon): Boolean;
+var
+  T: TAppIndTrayIconHandle;
+begin
+  if ATrayIcon.Handle <> 0 then
+  begin
+    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
+    ATrayIcon.Handle := 0;
+    T.Free;
+  end;
+  Result := True;
+end;
+
+class function TGtk3WSTrayIcon.Show(const ATrayIcon: TCustomTrayIcon): Boolean;
+var
+  T: TAppIndTrayIconHandle;
+begin
+  if ATrayIcon.Handle = 0 then
+  begin
+    T := TAppIndTrayIconHandle.Create(ATrayIcon);
+    ATrayIcon.Handle := HWND(T);
+  end;
+  Result := True;
+end;
+
+class procedure TGtk3WSTrayIcon.InternalUpdate(const ATrayIcon: TCustomTrayIcon);
+var
+  T: TAppIndTrayIconHandle;
+begin
+  if ATrayIcon.Handle <> 0 then
+  begin
+    T := TAppIndTrayIconHandle(ATrayIcon.Handle);
+    T.Update;
+  end;
+end;
+
+class function TGtk3WSTrayIcon.GetPosition(const ATrayIcon: TCustomTrayIcon): TPoint;
+begin
+  Result := Point(0, 0);
+end;
+
+{ AppIndicatorInit }
+
+var
+  Loaded: Boolean;
+  Initialized: Boolean;
+
+function Gtk3AppIndicatorInit: Boolean;
+var
+  Module: HModule;
+
+  function TryLoad(const ProcName: string; var Proc: Pointer): Boolean;
+  begin
+    Proc := GetProcAddress(Module, ProcName);
+    Result := Proc <> nil;
+  end;
+
+begin
+  Result := False;
+  if Loaded then
+    Exit(Initialized);
+  Loaded := True;
+  if Initialized then
+    Exit(True);
+  Module := LoadLibrary(libappindicator_3);        // might have several package names, see wiki
+  if Module = 0 then
+     Exit;
+  Result :=
+    TryLoad('app_indicator_get_type', @app_indicator_get_type) and
+    TryLoad('app_indicator_new', @app_indicator_new) and
+    TryLoad('app_indicator_new_with_path', @app_indicator_new_with_path) and
+    TryLoad('app_indicator_set_status', @app_indicator_set_status) and
+    TryLoad('app_indicator_set_attention_icon', @app_indicator_set_attention_icon) and
+    TryLoad('app_indicator_set_menu', @app_indicator_set_menu) and
+    TryLoad('app_indicator_set_icon', @app_indicator_set_icon) and
+    TryLoad('app_indicator_set_label', @app_indicator_set_label) and
+    TryLoad('app_indicator_set_icon_theme_path', @app_indicator_set_icon_theme_path) and
+    TryLoad('app_indicator_set_ordering_index', @app_indicator_set_ordering_index) and
+    TryLoad('app_indicator_get_id', @app_indicator_get_id) and
+    TryLoad('app_indicator_get_category', @app_indicator_get_category) and
+    TryLoad('app_indicator_get_status', @app_indicator_get_status) and
+    TryLoad('app_indicator_get_icon', @app_indicator_get_icon) and
+    TryLoad('app_indicator_get_icon_theme_path', @app_indicator_get_icon_theme_path) and
+    TryLoad('app_indicator_get_attention_icon', @app_indicator_get_attention_icon) and
+    TryLoad('app_indicator_get_menu', @app_indicator_get_menu) and
+    TryLoad('app_indicator_get_label', @app_indicator_get_label) and
+    TryLoad('app_indicator_get_label_guide', @app_indicator_get_label_guide) and
+    TryLoad('app_indicator_get_ordering_index', @app_indicator_get_ordering_index);
+  Initialized := Result;
+end;
+
+initialization
+  GlobalAppIndicator := nil;
+  GlobalIconPath := '';
+finalization
+  if FileExists(GlobalIconPath) then
+    DeleteFile(GlobalIconPath);
+  if GlobalAppIndicator <> nil then
+    g_object_unref(GlobalAppIndicator);
+end.
Oct28.patch (12,255 bytes)

Juha Manninen

2019-10-28 11:43

developer   ~0118895

Applied, thanks.
OnClick does not work in my KDE system but the popup menu does.
For possible further patches please either open a new issue or reopen this one.

Issue History

Date Modified Username Field Change
2019-10-24 13:57 David New Issue
2019-10-24 13:57 David File Added: appind.patch
2019-10-24 18:10 Zeljan Rikalo Assigned To => Zeljan Rikalo
2019-10-24 18:10 Zeljan Rikalo Status new => feedback
2019-10-24 18:10 Zeljan Rikalo LazTarget => -
2019-10-24 18:10 Zeljan Rikalo Note Added: 0118788
2019-10-24 23:10 Juha Manninen Relationship added related to 0036199
2019-10-24 23:11 Juha Manninen Assigned To Zeljan Rikalo => Juha Manninen
2019-10-24 23:29 Juha Manninen Note Added: 0118791
2019-10-24 23:30 Juha Manninen Note Edited: 0118791 View Revisions
2019-10-24 23:33 Juha Manninen Note Edited: 0118791 View Revisions
2019-10-25 00:30 David Note Added: 0118792
2019-10-25 00:30 David Status feedback => assigned
2019-10-25 00:47 Juha Manninen Note Added: 0118794
2019-10-25 01:23 David File Added: 36209.patch
2019-10-25 01:23 David Note Added: 0118795
2019-10-25 01:24 David Note Edited: 0118795 View Revisions
2019-10-25 15:02 Juha Manninen Note Added: 0118803
2019-10-26 01:57 David Note Added: 0118816
2019-10-26 08:31 David Note Edited: 0118816 View Revisions
2019-10-26 18:46 Juha Manninen Note Added: 0118856
2019-10-27 00:30 David Note Added: 0118864
2019-10-27 09:20 Juha Manninen Note Added: 0118866
2019-10-27 09:27 Juha Manninen Note Edited: 0118866 View Revisions
2019-10-28 10:54 David File Added: Oct28.patch
2019-10-28 10:54 David Note Added: 0118893
2019-10-28 11:43 Juha Manninen Status assigned => resolved
2019-10-28 11:43 Juha Manninen Resolution open => fixed
2019-10-28 11:43 Juha Manninen Fixed in Revision => r62137
2019-10-28 11:43 Juha Manninen Widgetset GTK 3 => GTK 3
2019-10-28 11:43 Juha Manninen Note Added: 0118895