From b8f25955d0717a38068486fc8bee3c03eef2cae4 Mon Sep 17 00:00:00 2001 From: maxim Date: Mon, 30 Mar 2020 19:31:02 +0000 Subject: [PATCH] Merged revision(s) 62137 #193392dc98 from trunk: LCL-GTK3: Implement TrayIcon for GTK3 widgetset. Issue #36209, patch from David Bannon. ........ git-svn-id: branches/fixes_2_0@62822 - --- .gitattributes | 1 + lcl/interfaces/gtk3/gtk3wsfactory.pas | 8 +- lcl/interfaces/gtk3/gtk3wstrayicon.pas | 287 +++++++++++++++++++++++++ 3 files changed, 294 insertions(+), 2 deletions(-) create mode 100644 lcl/interfaces/gtk3/gtk3wstrayicon.pas diff --git a/.gitattributes b/.gitattributes index 30856aa49f..e320410062 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9675,6 +9675,7 @@ lcl/interfaces/gtk3/gtk3wsmenus.pp svneol=native#text/plain lcl/interfaces/gtk3/gtk3wsspin.pp svneol=native#text/plain lcl/interfaces/gtk3/gtk3wssplitter.pas svneol=native#text/pascal lcl/interfaces/gtk3/gtk3wsstdctrls.pp svneol=native#text/plain +lcl/interfaces/gtk3/gtk3wstrayicon.pas svneol=native#text/pascal lcl/interfaces/gtk3/interfaces.pp svneol=native#text/plain lcl/interfaces/gtk3/issues.xml svneol=native#text/plain lcl/interfaces/lcl.lpk svneol=native#text/plain diff --git a/lcl/interfaces/gtk3/gtk3wsfactory.pas b/lcl/interfaces/gtk3/gtk3wsfactory.pas index fdbd2e4435..60283f31f1 100644 --- a/lcl/interfaces/gtk3/gtk3wsfactory.pas +++ b/lcl/interfaces/gtk3/gtk3wsfactory.pas @@ -137,7 +137,7 @@ uses 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 @@ end; function RegisterCustomTrayIcon: Boolean; alias : 'WSRegisterCustomTrayIcon'; begin // RegisterWSComponent(TCustomTrayIcon, TGtk2WSCustomTrayIcon); - Result := False; + Result := True; + if Gtk3AppIndicatorInit then + RegisterWSComponent(TCustomTrayIcon, TGtk3WSTrayIcon) + else + Result := False; end; //ExtDlgs diff --git a/lcl/interfaces/gtk3/gtk3wstrayicon.pas b/lcl/interfaces/gtk3/gtk3wstrayicon.pas new file mode 100644 index 0000000000..61f803f082 --- /dev/null +++ b/lcl/interfaces/gtk3/gtk3wstrayicon.pas @@ -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.