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

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

Powered by Google App Engine
This is Rietveld 408576698