1 {
2 *****************************************************************************
3 * gtk3WSTrayIcon.pas *
4 * ------------------ *
5 * *
6 * *
7 *****************************************************************************
8
9 *****************************************************************************
10 This file is part of the Lazarus Component Library (LCL)
11
12 See the file COPYING.modifiedLGPL.txt, included in this distribution,
13 for details about the license.
14 *****************************************************************************
15
16 A unit that uses LibAppIndicator3 to display a TrayIcon in GTK3. Based on a
17 GTK2 version by Anthony Walter, now works with many common Linux systems "out
18 of the box" and almost all of the remainder with addition of LibAppIndicator3
19 and TopIconsPlus or similar Gnome Extension.
20
21 See Wiki for details and Limitations (Menu only, one Icon only....)
22 Also refer to discussion in ../gtk2/UnityWSCtrls.pas
23 }
24
25
26 unit gtk3wstrayicon;
27
28 interface
29
30 {$mode delphi}
31 uses
32 GLib2, LazGtk3, LazGdkPixbuf2, gtk3widgets,
33 Classes, SysUtils, dynlibs,
34 Graphics, Controls, Forms, ExtCtrls, WSExtCtrls, LCLType, LazUTF8,
35 FileUtil;
36
37 type
38 TGtk3WSTrayIcon = class(TWSCustomTrayIcon)
39 published
Hidenull40 class function Hide(const ATrayIcon: TCustomTrayIcon): Boolean; override;
Shownull41 class function Show(const ATrayIcon: TCustomTrayIcon): Boolean; override;
42 class procedure InternalUpdate(const ATrayIcon: TCustomTrayIcon); override;
GetPositionnull43 class function GetPosition(const {%H-}ATrayIcon: TCustomTrayIcon): TPoint; override;
44 end;
45
46 { Gtk3AppIndicatorInit returns true if LibAppIndicator_3 library has been loaded }
Gtk3AppIndicatorInitnull47 function Gtk3AppIndicatorInit: Boolean;
48
49 implementation
50
51 uses gtk3objects; // TGtk3Image
52
53 const
54 libappindicator_3 = 'libappindicator3.so.1';
55
56 type
57 TAppIndicatorCategory = (
58 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
59 APP_INDICATOR_CATEGORY_COMMUNICATIONS,
60 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
61 APP_INDICATOR_CATEGORY_HARDWARE,
62 APP_INDICATOR_CATEGORY_OTHER
63 );
64
65 TAppIndicatorStatus = (
66 APP_INDICATOR_STATUS_PASSIVE,
67 APP_INDICATOR_STATUS_ACTIVE,
68 APP_INDICATOR_STATUS_ATTENTION
69 );
70
71 PAppIndicator = Pointer;
72
73 var
74 { GlobalAppIndicator creation routines }
GTypenull75 app_indicator_get_type: function: GType; cdecl;
dnull76 app_indicator_new: function(id, icon_name: PGChar; category: TAppIndicatorCategory): PAppIndicator; cdecl;
dnull77 app_indicator_new_with_path: function(id, icon_name: PGChar; category: TAppIndicatorCategory; icon_theme_path: PGChar): PAppIndicator; cdecl;
78 { Set properties }
79 app_indicator_set_status: procedure(self: PAppIndicator; status: TAppIndicatorStatus); cdecl;
80 app_indicator_set_attention_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
81 app_indicator_set_menu: procedure(self: PAppIndicator; menu: PGtkMenu); cdecl;
82 app_indicator_set_icon: procedure(self: PAppIndicator; icon_name: PGChar); cdecl;
83 app_indicator_set_label: procedure(self: PAppIndicator; _label, guide: PGChar); cdecl;
84 app_indicator_set_icon_theme_path: procedure(self: PAppIndicator; icon_theme_path: PGChar); cdecl;
85 app_indicator_set_ordering_index: procedure(self: PAppIndicator; ordering_index: guint32); cdecl;
86 { Get properties }
elfnull87 app_indicator_get_id: function(self: PAppIndicator): PGChar; cdecl;
elfnull88 app_indicator_get_category: function(self: PAppIndicator): TAppIndicatorCategory; cdecl;
elfnull89 app_indicator_get_status: function(self: PAppIndicator): TAppIndicatorStatus; cdecl;
elfnull90 app_indicator_get_icon: function(self: PAppIndicator): PGChar; cdecl;
elfnull91 app_indicator_get_icon_theme_path: function(self: PAppIndicator): PGChar; cdecl;
elfnull92 app_indicator_get_attention_icon: function(self: PAppIndicator): PGChar; cdecl;
elfnull93 app_indicator_get_menu: function(self: PAppIndicator): PGtkMenu; cdecl;
elfnull94 app_indicator_get_label: function(self: PAppIndicator): PGChar; cdecl;
elfnull95 app_indicator_get_label_guide: function(self: PAppIndicator): PGChar; cdecl;
elfnull96 app_indicator_get_ordering_index: function(self: PAppIndicator): guint32; cdecl;
97
98 { TAppIndTrayIconHandle }
99
100 type
101 TAppIndTrayIconHandle = class
102 private
103 FTrayIcon: TCustomTrayIcon;
104 FName: string;
105 FIconName: string;
106 public
107 constructor Create(TrayIcon: TCustomTrayIcon);
108 destructor Destroy; override;
109 procedure Update;
110 end;
111
112 const
113 IconThemePath = '/tmp/appindicators/'; // We must write our icon to a file.
114 IconType = 'png';
115
116 var
117 GlobalAppIndicator: PAppIndicator;
118 GlobalIcon: Pointer;
119 GlobalIconPath: string;
120
121 constructor TAppIndTrayIconHandle.Create(TrayIcon: TCustomTrayIcon);
122 var
123 NewIcon: Pointer;
124 begin
125 inherited Create;
126 FTrayIcon := TrayIcon;
127 FName := 'app-' + IntToHex(IntPtr(Application), SizeOf(IntPtr) * 2);
128 NewIcon := {%H-}Pointer(TGtk3Image(FTrayIcon.Icon.Handle).handle);
129 if NewIcon = nil then
130 NewIcon := {%H-}Pointer(Application.Icon.Handle);
131 if NewIcon <> GlobalIcon then
132 begin
133 GlobalIcon := NewIcon;
134 ForceDirectories(IconThemePath);
135 FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
136 if FileExists(GlobalIconPath) then
137 DeleteFile(GlobalIconPath);
138 GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
139 gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
140 if GlobalAppIndicator <> nil then
141 app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
142 end
143 else
144 FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
145 { Only the first created AppIndicator is functional }
146 if GlobalAppIndicator = nil then
147 { It seems that icons can only come from files :( }
148 GlobalAppIndicator := app_indicator_new_with_path(PChar(FName), PChar(FIconName),
149 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, IconThemePath);
150 Update;
151 end;
152
153 destructor TAppIndTrayIconHandle.Destroy;
154 begin
155 { Hide the global AppIndicator }
156 app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_PASSIVE);
157 inherited Destroy;
158 end;
159
160 procedure TAppIndTrayIconHandle.Update;
161 var
162 NewIcon: Pointer;
163 begin
164 NewIcon := {%H-}Pointer(TGTK3Image(FTrayIcon.Icon.Handle).Handle);
165 if NewIcon = nil then
166 NewIcon := {%H-}Pointer(Application.Icon.Handle);
167 if NewIcon <> GlobalIcon then
168 begin
169 GlobalIcon := NewIcon;
170 FIconName := FName + '-' + IntToHex({%H-}IntPtr(GlobalIcon), SizeOf(GlobalIcon) * 2);
171 ForceDirectories(IconThemePath);
172 if FileExists(GlobalIconPath) then
173 DeleteFile(GlobalIconPath);
174 GlobalIconPath := IconThemePath + FIconName + '.' + IconType;
175 gdk_pixbuf_save(GlobalIcon, PChar(GlobalIconPath), IconType, nil, [nil]);
176 { Again it seems that icons can only come from files }
177 app_indicator_set_icon(GlobalAppIndicator, PChar(FIconName));
178 end;
179 { It seems to me you can only set the menu once for an AppIndicator }
180 if (app_indicator_get_menu(GlobalAppIndicator) = nil) and (FTrayIcon.PopUpMenu <> nil) then
181 //app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(FTrayIcon.PopUpMenu.Handle));
182 app_indicator_set_menu(GlobalAppIndicator, {%H-}PGtkMenu(TGTK3Menu(FTrayIcon.PopUpMenu.Handle).Widget));
183 app_indicator_set_status(GlobalAppIndicator, APP_INDICATOR_STATUS_ACTIVE);
184 end;
185
186 { TAppIndWSCustomTrayIcon }
187
TGtk3WSTrayIcon.Hidenull188 class function TGtk3WSTrayIcon.Hide(const ATrayIcon: TCustomTrayIcon): Boolean;
189 var
190 T: TAppIndTrayIconHandle;
191 begin
192 if ATrayIcon.Handle <> 0 then
193 begin
194 T := TAppIndTrayIconHandle(ATrayIcon.Handle);
195 ATrayIcon.Handle := 0;
196 T.Free;
197 end;
198 Result := True;
199 end;
200
TGtk3WSTrayIcon.Shownull201 class function TGtk3WSTrayIcon.Show(const ATrayIcon: TCustomTrayIcon): Boolean;
202 var
203 T: TAppIndTrayIconHandle;
204 begin
205 if ATrayIcon.Handle = 0 then
206 begin
207 T := TAppIndTrayIconHandle.Create(ATrayIcon);
208 ATrayIcon.Handle := HWND(T);
209 end;
210 Result := True;
211 end;
212
213 class procedure TGtk3WSTrayIcon.InternalUpdate(const ATrayIcon: TCustomTrayIcon);
214 var
215 T: TAppIndTrayIconHandle;
216 begin
217 if ATrayIcon.Handle <> 0 then
218 begin
219 T := TAppIndTrayIconHandle(ATrayIcon.Handle);
220 T.Update;
221 end;
222 end;
223
TGtk3WSTrayIcon.GetPositionnull224 class function TGtk3WSTrayIcon.GetPosition(const ATrayIcon: TCustomTrayIcon): TPoint;
225 begin
226 Result := Point(0, 0);
227 end;
228
229 { AppIndicatorInit }
230
231 var
232 Loaded: Boolean;
233 Initialized: Boolean;
234
Gtk3AppIndicatorInitnull235 function Gtk3AppIndicatorInit: Boolean;
236 var
237 Module: HModule;
238
TryLoadnull239 function TryLoad(const ProcName: string; var Proc: Pointer): Boolean;
240 begin
241 Proc := GetProcAddress(Module, ProcName);
242 Result := Proc <> nil;
243 end;
244
245 begin
246 Result := False;
247 if Loaded then
248 Exit(Initialized);
249 Loaded := True;
250 if Initialized then
251 Exit(True);
252 Module := LoadLibrary(libappindicator_3); // might have several package names, see wiki
253 if Module = 0 then
254 Exit;
255 Result :=
256 TryLoad('app_indicator_get_type', @app_indicator_get_type) and
257 TryLoad('app_indicator_new', @app_indicator_new) and
258 TryLoad('app_indicator_new_with_path', @app_indicator_new_with_path) and
259 TryLoad('app_indicator_set_status', @app_indicator_set_status) and
260 TryLoad('app_indicator_set_attention_icon', @app_indicator_set_attention_icon) and
261 TryLoad('app_indicator_set_menu', @app_indicator_set_menu) and
262 TryLoad('app_indicator_set_icon', @app_indicator_set_icon) and
263 TryLoad('app_indicator_set_label', @app_indicator_set_label) and
264 TryLoad('app_indicator_set_icon_theme_path', @app_indicator_set_icon_theme_path) and
265 TryLoad('app_indicator_set_ordering_index', @app_indicator_set_ordering_index) and
266 TryLoad('app_indicator_get_id', @app_indicator_get_id) and
267 TryLoad('app_indicator_get_category', @app_indicator_get_category) and
268 TryLoad('app_indicator_get_status', @app_indicator_get_status) and
269 TryLoad('app_indicator_get_icon', @app_indicator_get_icon) and
270 TryLoad('app_indicator_get_icon_theme_path', @app_indicator_get_icon_theme_path) and
271 TryLoad('app_indicator_get_attention_icon', @app_indicator_get_attention_icon) and
272 TryLoad('app_indicator_get_menu', @app_indicator_get_menu) and
273 TryLoad('app_indicator_get_label', @app_indicator_get_label) and
274 TryLoad('app_indicator_get_label_guide', @app_indicator_get_label_guide) and
275 TryLoad('app_indicator_get_ordering_index', @app_indicator_get_ordering_index);
276 Initialized := Result;
277 end;
278
279 initialization
280 GlobalAppIndicator := nil;
281 GlobalIconPath := '';
282 finalization
283 if FileExists(GlobalIconPath) then
284 DeleteFile(GlobalIconPath);
285 if GlobalAppIndicator <> nil then
286 g_object_unref(GlobalAppIndicator);
287 end.
288