1 // Copyright 2019 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/views/status_icons/status_icon_linux_dbus.h"
6
7 #include <dbus/dbus-shared.h>
8
9 #include <memory>
10 #include <string>
11
12 #include "base/bind.h"
13 #include "base/check_op.h"
14 #include "base/environment.h"
15 #include "base/files/file_util.h"
16 #include "base/memory/ref_counted_memory.h"
17 #include "base/nix/xdg_util.h"
18 #include "base/notreached.h"
19 #include "base/numerics/checked_math.h"
20 #include "base/process/process.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/task/post_task.h"
24 #include "base/task/thread_pool.h"
25 #include "base/task_runner_util.h"
26 #include "components/dbus/menu/menu.h"
27 #include "components/dbus/properties/dbus_properties.h"
28 #include "components/dbus/properties/success_barrier_callback.h"
29 #include "components/dbus/properties/types.h"
30 #include "components/dbus/thread_linux/dbus_thread_linux.h"
31 #include "content/public/browser/browser_thread.h"
32 #include "dbus/bus.h"
33 #include "dbus/exported_object.h"
34 #include "dbus/message.h"
35 #include "dbus/object_path.h"
36 #include "dbus/object_proxy.h"
37 #include "third_party/skia/include/core/SkBitmap.h"
38 #include "ui/base/models/menu_model.h"
39 #include "ui/base/models/menu_separator_types.h"
40 #include "ui/base/models/simple_menu_model.h"
41 #include "ui/gfx/geometry/point.h"
42 #include "ui/gfx/geometry/rect.h"
43 #include "ui/gfx/geometry/size.h"
44 #include "ui/gfx/image/image_skia.h"
45 #include "ui/views/linux_ui/status_icon_linux.h"
46
47 namespace {
48
49 // Service names.
50 const char kServiceStatusNotifierWatcher[] = "org.kde.StatusNotifierWatcher";
51
52 // Interfaces.
53 // If/when the StatusNotifierItem spec gets accepted AND widely used, replace
54 // "kde" with "freedesktop".
55 const char kInterfaceStatusNotifierItem[] = "org.kde.StatusNotifierItem";
56 const char kInterfaceStatusNotifierWatcher[] = "org.kde.StatusNotifierWatcher";
57
58 // Object paths.
59 const char kPathStatusNotifierItem[] = "/StatusNotifierItem";
60 const char kPathStatusNotifierWatcher[] = "/StatusNotifierWatcher";
61 const char kPathDbusMenu[] = "/com/canonical/dbusmenu";
62
63 // Methods.
64 const char kMethodNameHasOwner[] = "NameHasOwner";
65 const char kMethodRegisterStatusNotifierItem[] = "RegisterStatusNotifierItem";
66 const char kMethodActivate[] = "Activate";
67 const char kMethodContextMenu[] = "ContextMenu";
68 const char kMethodScroll[] = "Scroll";
69 const char kMethodSecondaryActivate[] = "SecondaryActivate";
70 const char kMethodGet[] = "Get";
71
72 // Properties.
73 const char kPropertyIsStatusNotifierHostRegistered[] =
74 "IsStatusNotifierHostRegistered";
75 const char kPropertyItemIsMenu[] = "ItemIsMenu";
76 const char kPropertyWindowId[] = "WindowId";
77 const char kPropertyMenu[] = "Menu";
78 const char kPropertyAttentionIconName[] = "AttentionIconName";
79 const char kPropertyAttentionMovieName[] = "AttentionMovieName";
80 const char kPropertyCategory[] = "Category";
81 const char kPropertyIconName[] = "IconName";
82 const char kPropertyIconThemePath[] = "IconThemePath";
83 const char kPropertyId[] = "Id";
84 const char kPropertyOverlayIconName[] = "OverlayIconName";
85 const char kPropertyStatus[] = "Status";
86 const char kPropertyTitle[] = "Title";
87 const char kPropertyAttentionIconPixmap[] = "AttentionIconPixmap";
88 const char kPropertyIconPixmap[] = "IconPixmap";
89 const char kPropertyOverlayIconPixmap[] = "OverlayIconPixmap";
90 const char kPropertyToolTip[] = "ToolTip";
91
92 // Signals.
93 const char kSignalNewIcon[] = "NewIcon";
94 const char kSignalNewIconThemePath[] = "NewIconThemePath";
95 const char kSignalNewToolTip[] = "NewToolTip";
96
97 // Property values.
98 const char kPropertyValueCategory[] = "ApplicationStatus";
99 const char kPropertyValueStatus[] = "Active";
100
NextServiceId()101 int NextServiceId() {
102 static int status_icon_count = 0;
103 return ++status_icon_count;
104 }
105
ServiceNameFromId(int service_id)106 std::string ServiceNameFromId(int service_id) {
107 return std::string(kInterfaceStatusNotifierItem) + '-' +
108 base::NumberToString(base::Process::Current().Pid()) + '-' +
109 base::NumberToString(service_id);
110 }
111
PropertyIdFromId(int service_id)112 std::string PropertyIdFromId(int service_id) {
113 return "chrome_status_icon_" + base::NumberToString(service_id);
114 }
115
MakeDbusImage(const gfx::ImageSkia & image)116 auto MakeDbusImage(const gfx::ImageSkia& image) {
117 const SkBitmap* bitmap = image.bitmap();
118 int width = bitmap->width();
119 int height = bitmap->height();
120 std::vector<uint8_t> color_data;
121 auto size = base::CheckedNumeric<size_t>(4) * width * height;
122 color_data.reserve(size.ValueOrDie());
123 for (int y = 0; y < height; ++y) {
124 for (int x = 0; x < width; ++x) {
125 SkColor color = bitmap->getColor(x, y);
126 color_data.push_back(SkColorGetA(color));
127 color_data.push_back(SkColorGetR(color));
128 color_data.push_back(SkColorGetG(color));
129 color_data.push_back(SkColorGetB(color));
130 }
131 }
132 return MakeDbusArray(MakeDbusStruct(
133 DbusInt32(width), DbusInt32(height),
134 DbusByteArray(base::RefCountedBytes::TakeVector(&color_data))));
135 }
136
MakeDbusToolTip(const std::string & text)137 auto MakeDbusToolTip(const std::string& text) {
138 return MakeDbusStruct(
139 DbusString(""),
140 DbusArray<DbusStruct<DbusInt32, DbusInt32, DbusByteArray>>(),
141 DbusString(text), DbusString(""));
142 }
143
ShouldWriteIconToFile()144 bool ShouldWriteIconToFile() {
145 auto env = base::Environment::Create();
146 switch (base::nix::GetDesktopEnvironment(env.get())) {
147 case base::nix::DESKTOP_ENVIRONMENT_GNOME:
148 // gnome-shell-extension-appindicator doesn't downsize icons when they're
149 // given as DBus pixmaps. But it does when icons are given as files.
150 case base::nix::DESKTOP_ENVIRONMENT_PANTHEON:
151 // wingpanel-indicator-ayatana only supports file icons.
152 return true;
153 case base::nix::DESKTOP_ENVIRONMENT_OTHER:
154 case base::nix::DESKTOP_ENVIRONMENT_CINNAMON:
155 case base::nix::DESKTOP_ENVIRONMENT_KDE3:
156 case base::nix::DESKTOP_ENVIRONMENT_KDE4:
157 case base::nix::DESKTOP_ENVIRONMENT_KDE5:
158 case base::nix::DESKTOP_ENVIRONMENT_UNITY:
159 case base::nix::DESKTOP_ENVIRONMENT_XFCE:
160 return false;
161 }
162 NOTREACHED();
163 return false;
164 }
165
WriteIconFile(size_t icon_file_id,scoped_refptr<base::RefCountedMemory> data)166 base::FilePath WriteIconFile(size_t icon_file_id,
167 scoped_refptr<base::RefCountedMemory> data) {
168 // Some StatusNotifierHosts require both the theme directory and icon name to
169 // change in order to update, so we need a new temporary directory and a
170 // unique base name for the file.
171 base::FilePath temp_dir;
172 if (!base::CreateNewTempDirectory("", &temp_dir))
173 return {};
174
175 base::FilePath file_path = temp_dir.Append(
176 "status_icon_" + base::NumberToString(icon_file_id) + ".png");
177 if (!base::WriteFile(file_path, data->front_as<char>(), data->size())) {
178 base::DeletePathRecursively(temp_dir);
179 return {};
180 }
181
182 return file_path;
183 }
184
185 } // namespace
186
StatusIconLinuxDbus()187 StatusIconLinuxDbus::StatusIconLinuxDbus()
188 : should_write_icon_to_file_(ShouldWriteIconToFile()),
189 icon_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
190 {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
191 base::TaskShutdownBehavior::BLOCK_SHUTDOWN})) {
192 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
193 dbus::Bus::Options bus_options;
194 bus_options.bus_type = dbus::Bus::SESSION;
195 bus_options.connection_type = dbus::Bus::PRIVATE;
196 bus_options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
197 bus_ = base::MakeRefCounted<dbus::Bus>(bus_options);
198 CheckStatusNotifierWatcherHasOwner();
199 }
200
SetIcon(const gfx::ImageSkia & image)201 void StatusIconLinuxDbus::SetIcon(const gfx::ImageSkia& image) {
202 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
203 SetIconImpl(image, true);
204 }
205
SetToolTip(const base::string16 & tool_tip)206 void StatusIconLinuxDbus::SetToolTip(const base::string16& tool_tip) {
207 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
208 if (!properties_)
209 return;
210
211 UpdateMenuImpl(delegate_->GetMenuModel(), true);
212
213 properties_->SetProperty(
214 kInterfaceStatusNotifierItem, kPropertyToolTip,
215 MakeDbusToolTip(base::UTF16ToUTF8(delegate_->GetToolTip())));
216 dbus::Signal signal(kInterfaceStatusNotifierItem, kSignalNewToolTip);
217 item_->SendSignal(&signal);
218 }
219
UpdatePlatformContextMenu(ui::MenuModel * model)220 void StatusIconLinuxDbus::UpdatePlatformContextMenu(ui::MenuModel* model) {
221 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
222 UpdateMenuImpl(model, true);
223 }
224
RefreshPlatformContextMenu()225 void StatusIconLinuxDbus::RefreshPlatformContextMenu() {
226 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
227 // This codepath gets called for property changes like changed labels or
228 // icons, but also for layout changes like deleted items.
229 // TODO(thomasanderson): Split this into two methods so we can avoid
230 // rebuilding the menu for simple property changes.
231 UpdateMenuImpl(delegate_->GetMenuModel(), true);
232 }
233
ExecuteCommand(int command_id,int event_flags)234 void StatusIconLinuxDbus::ExecuteCommand(int command_id, int event_flags) {
235 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
236 DCHECK_EQ(command_id, 0);
237 delegate_->OnClick();
238 }
239
~StatusIconLinuxDbus()240 StatusIconLinuxDbus::~StatusIconLinuxDbus() {
241 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
242 bus_->GetDBusTaskRunner()->PostTask(
243 FROM_HERE, base::BindOnce(&dbus::Bus::ShutdownAndBlock, bus_));
244 CleanupIconFile();
245 }
246
CheckStatusNotifierWatcherHasOwner()247 void StatusIconLinuxDbus::CheckStatusNotifierWatcherHasOwner() {
248 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
249 dbus::ObjectProxy* bus_proxy =
250 bus_->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS));
251 dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, kMethodNameHasOwner);
252 dbus::MessageWriter writer(&method_call);
253 writer.AppendString(kServiceStatusNotifierWatcher);
254 bus_proxy->CallMethod(
255 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
256 base::BindOnce(&StatusIconLinuxDbus::OnNameHasOwnerResponse,
257 weak_factory_.GetWeakPtr()));
258 }
259
OnNameHasOwnerResponse(dbus::Response * response)260 void StatusIconLinuxDbus::OnNameHasOwnerResponse(dbus::Response* response) {
261 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
262 dbus::MessageReader reader(response);
263 bool owned = false;
264 if (!response || !reader.PopBool(&owned) || !owned) {
265 delegate_->OnImplInitializationFailed();
266 return;
267 }
268
269 watcher_ = bus_->GetObjectProxy(kServiceStatusNotifierWatcher,
270 dbus::ObjectPath(kPathStatusNotifierWatcher));
271
272 dbus::MethodCall method_call(DBUS_INTERFACE_PROPERTIES, kMethodGet);
273 dbus::MessageWriter writer(&method_call);
274 writer.AppendString(kInterfaceStatusNotifierWatcher);
275 writer.AppendString(kPropertyIsStatusNotifierHostRegistered);
276 watcher_->CallMethod(
277 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
278 base::BindOnce(&StatusIconLinuxDbus::OnHostRegisteredResponse,
279 weak_factory_.GetWeakPtr()));
280 }
281
OnHostRegisteredResponse(dbus::Response * response)282 void StatusIconLinuxDbus::OnHostRegisteredResponse(dbus::Response* response) {
283 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
284 if (!response) {
285 delegate_->OnImplInitializationFailed();
286 return;
287 }
288
289 dbus::MessageReader reader(response);
290 bool registered = false;
291 if (!reader.PopVariantOfBool(®istered) || !registered) {
292 delegate_->OnImplInitializationFailed();
293 return;
294 }
295
296 service_id_ = NextServiceId();
297 bus_->RequestOwnership(ServiceNameFromId(service_id_),
298 dbus::Bus::ServiceOwnershipOptions::REQUIRE_PRIMARY,
299 base::BindOnce(&StatusIconLinuxDbus::OnOwnership,
300 weak_factory_.GetWeakPtr()));
301 }
302
OnOwnership(const std::string & service_name,bool success)303 void StatusIconLinuxDbus::OnOwnership(const std::string& service_name,
304 bool success) {
305 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
306 if (!success) {
307 delegate_->OnImplInitializationFailed();
308 return;
309 }
310
311 static constexpr struct {
312 const char* name;
313 void (StatusIconLinuxDbus::*callback)(dbus::MethodCall*,
314 dbus::ExportedObject::ResponseSender);
315 } methods[4] = {
316 {kMethodActivate, &StatusIconLinuxDbus::OnActivate},
317 {kMethodContextMenu, &StatusIconLinuxDbus::OnContextMenu},
318 {kMethodScroll, &StatusIconLinuxDbus::OnScroll},
319 {kMethodSecondaryActivate, &StatusIconLinuxDbus::OnSecondaryActivate},
320 };
321
322 // The barrier requires base::size(methods) + 2 calls. base::size(methods)
323 // for each method exported, 1 for |properties_| initialization, and 1 for
324 // |menu_| initialization.
325 barrier_ =
326 SuccessBarrierCallback(base::size(methods) + 2,
327 base::BindOnce(&StatusIconLinuxDbus::OnInitialized,
328 weak_factory_.GetWeakPtr()));
329
330 item_ = bus_->GetExportedObject(dbus::ObjectPath(kPathStatusNotifierItem));
331 for (const auto& method : methods) {
332 item_->ExportMethod(
333 kInterfaceStatusNotifierItem, method.name,
334 base::BindRepeating(method.callback, weak_factory_.GetWeakPtr()),
335 base::BindOnce(&StatusIconLinuxDbus::OnExported,
336 weak_factory_.GetWeakPtr()));
337 }
338
339 menu_ = std::make_unique<DbusMenu>(
340 bus_->GetExportedObject(dbus::ObjectPath(kPathDbusMenu)), barrier_);
341 UpdateMenuImpl(delegate_->GetMenuModel(), false);
342
343 properties_ = std::make_unique<DbusProperties>(item_, barrier_);
344 properties_->RegisterInterface(kInterfaceStatusNotifierItem);
345 auto set_property = [&](const std::string& property_name, auto&& value) {
346 properties_->SetProperty(kInterfaceStatusNotifierItem, property_name,
347 std::forward<decltype(value)>(value), false);
348 };
349 set_property(kPropertyItemIsMenu, DbusBoolean(false));
350 set_property(kPropertyWindowId, DbusInt32(0));
351 set_property(kPropertyMenu, DbusObjectPath(dbus::ObjectPath(kPathDbusMenu)));
352 set_property(kPropertyAttentionIconName, DbusString(""));
353 set_property(kPropertyAttentionMovieName, DbusString(""));
354 set_property(kPropertyCategory, DbusString(kPropertyValueCategory));
355 set_property(kPropertyId, DbusString(PropertyIdFromId(service_id_)));
356 set_property(kPropertyOverlayIconName, DbusString(""));
357 set_property(kPropertyStatus, DbusString(kPropertyValueStatus));
358 set_property(kPropertyTitle, DbusString(""));
359 set_property(kPropertyAttentionIconPixmap,
360 DbusArray<DbusStruct<DbusInt32, DbusInt32, DbusByteArray>>());
361 set_property(kPropertyOverlayIconPixmap,
362 DbusArray<DbusStruct<DbusInt32, DbusInt32, DbusByteArray>>());
363 set_property(kPropertyToolTip,
364 MakeDbusToolTip(base::UTF16ToUTF8(delegate_->GetToolTip())));
365 SetIconImpl(delegate_->GetImage(), false);
366 }
367
OnExported(const std::string & interface_name,const std::string & method_name,bool success)368 void StatusIconLinuxDbus::OnExported(const std::string& interface_name,
369 const std::string& method_name,
370 bool success) {
371 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
372 barrier_.Run(success);
373 }
374
OnInitialized(bool success)375 void StatusIconLinuxDbus::OnInitialized(bool success) {
376 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
377 if (!success) {
378 delegate_->OnImplInitializationFailed();
379 return;
380 }
381
382 dbus::MethodCall method_call(kInterfaceStatusNotifierWatcher,
383 kMethodRegisterStatusNotifierItem);
384 dbus::MessageWriter writer(&method_call);
385 writer.AppendString(ServiceNameFromId(service_id_));
386 watcher_->CallMethod(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
387 base::BindOnce(&StatusIconLinuxDbus::OnRegistered,
388 weak_factory_.GetWeakPtr()));
389 }
390
OnRegistered(dbus::Response * response)391 void StatusIconLinuxDbus::OnRegistered(dbus::Response* response) {
392 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
393 if (!response)
394 delegate_->OnImplInitializationFailed();
395 }
396
OnActivate(dbus::MethodCall * method_call,dbus::ExportedObject::ResponseSender sender)397 void StatusIconLinuxDbus::OnActivate(
398 dbus::MethodCall* method_call,
399 dbus::ExportedObject::ResponseSender sender) {
400 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
401 delegate_->OnClick();
402 std::move(sender).Run(dbus::Response::FromMethodCall(method_call));
403 }
404
OnContextMenu(dbus::MethodCall * method_call,dbus::ExportedObject::ResponseSender sender)405 void StatusIconLinuxDbus::OnContextMenu(
406 dbus::MethodCall* method_call,
407 dbus::ExportedObject::ResponseSender sender) {
408 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
409 dbus::MessageReader reader(method_call);
410 int32_t x;
411 int32_t y;
412 if (!reader.PopInt32(&x) || !reader.PopInt32(&y)) {
413 std::move(sender).Run(nullptr);
414 return;
415 }
416
417 if (!menu_runner_) {
418 menu_runner_ = std::make_unique<views::MenuRunner>(
419 concat_menu_.get(), views::MenuRunner::HAS_MNEMONICS |
420 views::MenuRunner::CONTEXT_MENU |
421 views::MenuRunner::FIXED_ANCHOR);
422 }
423 menu_runner_->RunMenuAt(
424 nullptr, nullptr, gfx::Rect(gfx::Point(x, y), gfx::Size()),
425 views::MenuAnchorPosition::kTopRight, ui::MENU_SOURCE_MOUSE);
426 std::move(sender).Run(dbus::Response::FromMethodCall(method_call));
427 }
428
OnScroll(dbus::MethodCall * method_call,dbus::ExportedObject::ResponseSender sender)429 void StatusIconLinuxDbus::OnScroll(
430 dbus::MethodCall* method_call,
431 dbus::ExportedObject::ResponseSender sender) {
432 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
433 // Ignore scroll events.
434 std::move(sender).Run(dbus::Response::FromMethodCall(method_call));
435 }
436
OnSecondaryActivate(dbus::MethodCall * method_call,dbus::ExportedObject::ResponseSender sender)437 void StatusIconLinuxDbus::OnSecondaryActivate(
438 dbus::MethodCall* method_call,
439 dbus::ExportedObject::ResponseSender sender) {
440 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
441 // Intentionally ignore secondary activations. In the future, we may decide
442 // to run the same handler as regular activations.
443 std::move(sender).Run(dbus::Response::FromMethodCall(method_call));
444 }
445
UpdateMenuImpl(ui::MenuModel * model,bool send_signal)446 void StatusIconLinuxDbus::UpdateMenuImpl(ui::MenuModel* model,
447 bool send_signal) {
448 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
449 if (!menu_)
450 return;
451
452 if (!model) {
453 empty_menu_ = std::make_unique<ui::SimpleMenuModel>(nullptr);
454 model = empty_menu_.get();
455 }
456
457 click_action_menu_ = std::make_unique<ui::SimpleMenuModel>(this);
458 if (delegate_->HasClickAction() && !delegate_->GetToolTip().empty()) {
459 click_action_menu_->AddItem(0, delegate_->GetToolTip());
460 if (model->GetItemCount())
461 click_action_menu_->AddSeparator(ui::MenuSeparatorType::NORMAL_SEPARATOR);
462 }
463
464 concat_menu_ =
465 std::make_unique<ConcatMenuModel>(click_action_menu_.get(), model);
466 menu_->SetModel(concat_menu_.get(), send_signal);
467 menu_runner_.reset();
468 }
469
SetIconImpl(const gfx::ImageSkia & image,bool send_signals)470 void StatusIconLinuxDbus::SetIconImpl(const gfx::ImageSkia& image,
471 bool send_signals) {
472 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
473 if (!properties_)
474 return;
475
476 if (should_write_icon_to_file_) {
477 base::PostTaskAndReplyWithResult(
478 icon_task_runner_.get(), FROM_HERE,
479 base::BindOnce(WriteIconFile, icon_file_id_++,
480 gfx::Image(image).As1xPNGBytes()),
481 base::BindOnce(&StatusIconLinuxDbus::OnIconFileWritten, this));
482 } else {
483 properties_->SetProperty(kInterfaceStatusNotifierItem, kPropertyIconPixmap,
484 MakeDbusImage(image), send_signals, false);
485 if (send_signals) {
486 dbus::Signal signal(kInterfaceStatusNotifierItem, kSignalNewIcon);
487 item_->SendSignal(&signal);
488 }
489 }
490 }
491
OnIconFileWritten(const base::FilePath & icon_file)492 void StatusIconLinuxDbus::OnIconFileWritten(const base::FilePath& icon_file) {
493 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
494 CleanupIconFile();
495 icon_file_ = icon_file;
496 if (icon_file_.empty())
497 return;
498
499 properties_->SetProperty(kInterfaceStatusNotifierItem, kPropertyIconThemePath,
500 DbusString(icon_file_.DirName().value()), false);
501 properties_->SetProperty(
502 kInterfaceStatusNotifierItem, kPropertyIconName,
503 DbusString(icon_file_.BaseName().RemoveExtension().value()), false);
504
505 dbus::Signal new_icon_theme_path_signal(kInterfaceStatusNotifierItem,
506 kSignalNewIconThemePath);
507 dbus::MessageWriter writer(&new_icon_theme_path_signal);
508 writer.AppendString(icon_file_.DirName().value());
509 item_->SendSignal(&new_icon_theme_path_signal);
510 dbus::Signal new_icon_signal(kInterfaceStatusNotifierItem, kSignalNewIcon);
511 item_->SendSignal(&new_icon_signal);
512 }
513
CleanupIconFile()514 void StatusIconLinuxDbus::CleanupIconFile() {
515 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
516 if (!icon_file_.empty()) {
517 icon_task_runner_->PostTask(
518 FROM_HERE, (base::BindOnce(base::GetDeletePathRecursivelyCallback(),
519 icon_file_.DirName())));
520 }
521 }
522