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(&registered) || !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