Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(211)

Side by Side Diff: chrome/browser/ui/libgtk2ui/app_indicator_icon.cc

Issue 18334003: Linux status icon for Ubuntu Unity (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/libgtk2ui/app_indicator_icon.h"
6
7 #include <gtk/gtk.h>
8 #include <dlfcn.h>
9
10 #include "base/bind.h"
11 #include "base/file_util.h"
12 #include "base/memory/ref_counted_memory.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/ui/libgtk2ui/menu_util.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "ui/base/models/menu_model.h"
18 #include "ui/gfx/image/image_skia.h"
19
20 namespace {
21
22 typedef enum {/*< prefix=APP_INDICATOR_CATEGORY >*/
Elliot Glaysher 2013/07/10 22:55:03 Please remove /* style comments.
sidharthms 2013/07/11 05:15:12 Done.
23 APP_INDICATOR_CATEGORY_APPLICATION_STATUS, /*< nick=ApplicationStatus >*/
24 APP_INDICATOR_CATEGORY_COMMUNICATIONS, /*< nick=Communications >*/
25 APP_INDICATOR_CATEGORY_SYSTEM_SERVICES, /*< nick=SystemServices >*/
26 APP_INDICATOR_CATEGORY_HARDWARE, /*< nick=Hardware >*/
27 APP_INDICATOR_CATEGORY_OTHER /*< nick=Other >*/
28 } AppIndicatorCategory;
29
30 typedef enum { /*< prefix=APP_INDICATOR_STATUS >*/
31 APP_INDICATOR_STATUS_PASSIVE, /*< nick=Passive >*/
32 APP_INDICATOR_STATUS_ACTIVE, /*< nick=Active >*/
33 APP_INDICATOR_STATUS_ATTENTION /*< nick=NeedsAttention >*/
34 } AppIndicatorStatus;
35
36 typedef AppIndicator* (*app_indicator_new_func)(const gchar* id,
37 const gchar* icon_name,
38 AppIndicatorCategory category);
39
40 typedef AppIndicator* (*app_indicator_new_with_path_func)(
41 const gchar* id,
42 const gchar* icon_name,
43 AppIndicatorCategory category,
44 const gchar* icon_theme_path);
45
46 typedef void (*app_indicator_set_status_func)(AppIndicator* self,
47 AppIndicatorStatus status);
48
49 typedef void (*app_indicator_set_attention_icon_full_func)(
50 AppIndicator* self,
51 const gchar* icon_name,
52 const gchar* icon_desc);
53
54 typedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu);
55
56 typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self,
57 const gchar* icon_name,
58 const gchar* icon_desc);
59
60 typedef void (*app_indicator_set_icon_theme_path_func)(
61 AppIndicator* self,
62 const gchar* icon_theme_path);
63
64 bool attempted_load = false;
65 bool opened = false;
66
67 // Retrieved functions from libappindicator.
68 app_indicator_new_func app_indicator_new = NULL;
69 app_indicator_new_with_path_func app_indicator_new_with_path = NULL;
70 app_indicator_set_status_func app_indicator_set_status = NULL;
71 app_indicator_set_attention_icon_full_func
72 app_indicator_set_attention_icon_full = NULL;
73 app_indicator_set_menu_func app_indicator_set_menu = NULL;
74 app_indicator_set_icon_full_func app_indicator_set_icon_full = NULL;
75 app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path = NULL;
76
77 void EnsureMethodsLoaded() {
78
79 if (attempted_load)
80 return;
81 attempted_load = true;
82
83 void* indicator_lib = dlopen("libappindicator.so", RTLD_LAZY);
84 if (!indicator_lib) {
85 indicator_lib = dlopen("libappindicator.so.1", RTLD_LAZY);
86 }
87 if (!indicator_lib) {
88 indicator_lib = dlopen("libappindicator.so.0", RTLD_LAZY);
89 }
90 if (!indicator_lib) {
91 return;
92 }
93
94 opened = true;
95
96 app_indicator_new = reinterpret_cast<app_indicator_new_func>(
97 dlsym(indicator_lib, "app_indicator_new"));
98
99 app_indicator_new_with_path =
100 reinterpret_cast<app_indicator_new_with_path_func>(
101 dlsym(indicator_lib, "app_indicator_new_with_path"));
102
103 app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
104 dlsym(indicator_lib, "app_indicator_set_status"));
105
106 app_indicator_set_attention_icon_full =
107 reinterpret_cast<app_indicator_set_attention_icon_full_func>(
108 dlsym(indicator_lib, "app_indicator_set_attention_icon_full"));
109
110 app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
111 dlsym(indicator_lib, "app_indicator_set_menu"));
112
113 app_indicator_set_icon_full =
114 reinterpret_cast<app_indicator_set_icon_full_func>(
115 dlsym(indicator_lib, "app_indicator_set_icon_full"));
116
117 app_indicator_set_icon_theme_path =
118 reinterpret_cast<app_indicator_set_icon_theme_path_func>(
119 dlsym(indicator_lib, "app_indicator_set_icon_theme_path"));
120 }
121
122 } // namespace
123
124 namespace libgtk2ui {
125
126 using content::BrowserThread;
127
128 AppIndicatorIcon::AppIndicatorIcon(std::string id)
129 : id_(id),
130 icon_(NULL),
131 gtk_menu_(NULL),
132 menu_model_(NULL),
133 icon_change_count_(0),
134 block_activation_(false),
135 has_click_action_replacement_(false) {
136 EnsureMethodsLoaded();
137 }
138 AppIndicatorIcon::~AppIndicatorIcon() {
139 if (icon_) {
140 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
141 if (menu_model_)
142 menu_model_->MenuClosed();
143 if (gtk_menu_)
144 DestroyMenu();
145 g_object_unref(icon_);
146 BrowserThread::PostTask(
147 BrowserThread::FILE,
148 FROM_HERE,
149 base::Bind(&AppIndicatorIcon::DeletePath, icon_file_path_.DirName()));
150 }
151 }
152
153 bool AppIndicatorIcon::CouldOpen() {
154 EnsureMethodsLoaded();
155 return opened;
156 }
157
158 void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) {
159 if (opened) {
160 ++icon_change_count_;
161 BrowserThread::PostTaskAndReplyWithResult(
162 BrowserThread::FILE,
Elliot Glaysher 2013/07/10 22:55:03 I thought that we weren't supposed to do things di
sidharthms 2013/07/11 05:15:12 Done.
163 FROM_HERE,
164 base::Bind(&AppIndicatorIcon::CreateTempImageFile,
165 image,
166 icon_change_count_,
167 id_),
168 base::Bind(&AppIndicatorIcon::SetImageFromFile,
169 base::Unretained(this)));
170 }
171 }
172
173 void AppIndicatorIcon::SetPressedImage(const gfx::ImageSkia& image) {
174 // Ignore pressed images, since the standard on Linux is to not highlight
175 // pressed status icons.
176 }
177
178 void AppIndicatorIcon::SetToolTip(const string16& tool_tip) {
179 // App-indicators don't support tool-tips. Ignore call.
180 }
181
182 void AppIndicatorIcon::SetClickActionLabel(const string16& label) {
183 click_action_label_ = UTF16ToUTF8(label);
184
185 // If the menu item has already been created, then find the menu item and
186 // change it's label.
187 if (has_click_action_replacement_) {
188 GList* children = gtk_container_get_children(GTK_CONTAINER(gtk_menu_));
189 for (GList* child = children; child; child = g_list_next(child))
190 if (g_object_get_data(G_OBJECT(child->data), "click-action-item") !=
191 NULL) {
192 gtk_menu_item_set_label(GTK_MENU_ITEM(child->data),
193 click_action_label_.c_str());
194 break;
195 }
196 g_list_free(children);
197 } else if (icon_) {
198 CreateClickActionReplacement();
199 app_indicator_set_menu(icon_, GTK_MENU(gtk_menu_));
200 }
201 }
202
203 void AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
204 if (!opened)
205 return;
206
207 if (gtk_menu_) {
208 DestroyMenu();
209 has_click_action_replacement_ = false;
210 }
211 menu_model_ = model;
212
213 // If icon doesn't exist now it's okay, the menu will be set later along with
214 // the image. Both an icon and a menu are required to show an app indicator.
215 if (model && icon_)
216 SetMenu();
217 }
218
219 void AppIndicatorIcon::SetImageFromFile(base::FilePath icon_file_path) {
220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
221 if (!icon_file_path.empty()) {
222 base::FilePath old_path = icon_file_path_;
223 icon_file_path_ = icon_file_path;
224
225 std::string icon_name =
226 icon_file_path_.BaseName().RemoveExtension().value();
227 std::string icon_dir = icon_file_path_.DirName().value();
228 if (!icon_) {
229 icon_ =
230 app_indicator_new_with_path(id_.c_str(),
231 icon_name.c_str(),
232 APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
233 icon_dir.c_str());
234 app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
235 if (menu_model_) {
236 SetMenu();
237 } else if (!click_action_label_.empty()) {
238 CreateClickActionReplacement();
239 app_indicator_set_menu(icon_, GTK_MENU(gtk_menu_));
240 }
241 } else {
242 // Currently we are creating a new temp directory every time the icon is
243 // set. So we need to set the directory each time.
244 app_indicator_set_icon_theme_path(icon_, icon_dir.c_str());
245 app_indicator_set_icon_full(icon_, icon_name.c_str(), "icon");
246
247 // Delete previous icon directory.
248 content::BrowserThread::PostTask(
249 content::BrowserThread::FILE,
250 FROM_HERE,
251 base::Bind(&AppIndicatorIcon::DeletePath, old_path.DirName()));
252 }
253 }
254 }
255
256 void AppIndicatorIcon::SetMenu() {
257 gtk_menu_ = gtk_menu_new();
258 BuildSubmenuFromModel(menu_model_,
259 gtk_menu_,
260 G_CALLBACK(OnMenuItemActivatedThunk),
261 &block_activation_,
262 this);
263 if (!click_action_label_.empty())
264 CreateClickActionReplacement();
265 UpdateMenu();
266 menu_model_->MenuWillShow();
267 app_indicator_set_menu(icon_, GTK_MENU(gtk_menu_));
268 }
269
270 void AppIndicatorIcon::CreateClickActionReplacement() {
271 GtkWidget* menu_item = NULL;
272
273 // If a menu doesn't exist create one just for the click action replacement.
274 if (!gtk_menu_) {
275 gtk_menu_ = gtk_menu_new();
276 } else {
277 // Add separator before the other menu items.
278 menu_item = gtk_separator_menu_item_new();
279 gtk_widget_show(menu_item);
280 gtk_menu_shell_prepend(GTK_MENU_SHELL(gtk_menu_), menu_item);
281 }
282
283 // Add "click replacement menu item".
284 menu_item = gtk_menu_item_new_with_mnemonic(click_action_label_.c_str());
285 g_object_set_data(
286 G_OBJECT(menu_item), "click-action-item", GINT_TO_POINTER(1));
287 g_signal_connect(menu_item, "activate", G_CALLBACK(OnClickThunk), this);
288 gtk_widget_show(menu_item);
289 gtk_menu_shell_prepend(GTK_MENU_SHELL(gtk_menu_), menu_item);
290
291 has_click_action_replacement_ = true;
292 }
293
294 void AppIndicatorIcon::DestroyMenu() {
295 if (menu_model_)
296 menu_model_->MenuClosed();
297 gtk_widget_destroy(gtk_menu_);
298 gtk_menu_ = NULL;
299 menu_model_ = NULL;
300 }
301
302 base::FilePath AppIndicatorIcon::CreateTempImageFile(gfx::ImageSkia image,
303 int icon_change_count,
304 std::string id) {
305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
306
307 scoped_refptr<base::RefCountedMemory> png_data =
308 gfx::Image(image).As1xPNGBytes();
309 if (png_data->size() == 0) {
310 // If the bitmap could not be encoded to PNG format, skip it.
311 LOG(WARNING) << "Could not encode icon";
312 return base::FilePath();
313 }
314
315 base::FilePath temp_dir;
316 base::FilePath new_file_path;
317
318 // Create a new temporary directory for each image since using a single
319 // temporary directory seems to have issues when changing icons in quick
320 // succession.
321 if (!file_util::CreateNewTempDirectory("", &temp_dir))
322 return base::FilePath();
323 new_file_path =
324 temp_dir.Append(id + base::StringPrintf("_%d.png", icon_change_count));
325 int bytes_written =
326 file_util::WriteFile(new_file_path,
327 reinterpret_cast<const char*>(png_data->front()),
328 png_data->size());
329
330 if (bytes_written != static_cast<int>(png_data->size())) {
331 return base::FilePath();
332 }
333
334 return new_file_path;
335 }
336
337 void AppIndicatorIcon::DeletePath(base::FilePath icon_file_path) {
338 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
339 if (!icon_file_path.empty()) {
340 base::Delete(icon_file_path, true);
341 }
342 }
343
344 void AppIndicatorIcon::UpdateMenu() {
345 gtk_container_foreach(
346 GTK_CONTAINER(gtk_menu_), SetMenuItemInfo, &block_activation_);
347 }
348
349 void AppIndicatorIcon::OnClick(GtkWidget* menu_item) {
350 if (delegate())
351 delegate()->OnClick();
352 }
353
354 void AppIndicatorIcon::OnMenuItemActivated(GtkWidget* menu_item) {
355 if (block_activation_)
356 return;
357
358 ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item));
359
360 if (!model) {
361 // There won't be a model for "native" submenus like the "Input Methods"
362 // context menu. We don't need to handle activation messages for submenus
363 // anyway, so we can just return here.
364 DCHECK(gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)));
365 return;
366 }
367
368 // The activate signal is sent to radio items as they get deselected;
369 // ignore it in this case.
370 if (GTK_IS_RADIO_MENU_ITEM(menu_item) &&
371 !gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item))) {
372 return;
373 }
374
375 int id;
376 if (!GetMenuItemID(menu_item, &id))
377 return;
378
379 // The menu item can still be activated by hotkeys even if it is disabled.
380 if (menu_model_->IsEnabledAt(id))
381 ExecuteCommand(model, id);
382 UpdateMenu();
383 }
384
385 } // namespace libgtk2ui
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698