1ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch// Copyright 2013 The Chromium Authors. All rights reserved.
2ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch// Use of this source code is governed by a BSD-style license that can be
3ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch// found in the LICENSE file.
4ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
5ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "chrome/browser/ui/libgtk2ui/app_indicator_icon.h"
6ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
7ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include <gtk/gtk.h>
8ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include <dlfcn.h>
9ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
10ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "base/bind.h"
1146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "base/environment.h"
12ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "base/file_util.h"
1346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "base/md5.h"
14ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "base/memory/ref_counted_memory.h"
1546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "base/nix/xdg_util.h"
16ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "base/strings/stringprintf.h"
17ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "base/strings/utf_string_conversions.h"
18ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "base/threading/sequenced_worker_pool.h"
1946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "chrome/browser/ui/libgtk2ui/app_indicator_icon_menu.h"
20ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "content/public/browser/browser_thread.h"
21ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "ui/base/models/menu_model.h"
2246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "ui/gfx/image/image.h"
23ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch#include "ui/gfx/image/image_skia.h"
24ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
25ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochnamespace {
26ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
27ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochtypedef enum {
28ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
29ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  APP_INDICATOR_CATEGORY_COMMUNICATIONS,
30ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
31ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  APP_INDICATOR_CATEGORY_HARDWARE,
32ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  APP_INDICATOR_CATEGORY_OTHER
33ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch} AppIndicatorCategory;
34ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
35ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochtypedef enum {
36ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  APP_INDICATOR_STATUS_PASSIVE,
37ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  APP_INDICATOR_STATUS_ACTIVE,
38ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  APP_INDICATOR_STATUS_ATTENTION
39ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch} AppIndicatorStatus;
40ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
41ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochtypedef AppIndicator* (*app_indicator_new_func)(const gchar* id,
42ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                                                const gchar* icon_name,
43ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                                                AppIndicatorCategory category);
44ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
45ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochtypedef AppIndicator* (*app_indicator_new_with_path_func)(
46ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    const gchar* id,
47ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    const gchar* icon_name,
48ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    AppIndicatorCategory category,
49ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    const gchar* icon_theme_path);
50ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
51ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochtypedef void (*app_indicator_set_status_func)(AppIndicator* self,
52ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                                              AppIndicatorStatus status);
53ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
54ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochtypedef void (*app_indicator_set_attention_icon_full_func)(
55ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    AppIndicator* self,
56ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    const gchar* icon_name,
57ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    const gchar* icon_desc);
58ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
59ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochtypedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu);
60ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
61ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochtypedef void (*app_indicator_set_icon_full_func)(AppIndicator* self,
62ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                                                 const gchar* icon_name,
63ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch                                                 const gchar* icon_desc);
64ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
65ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochtypedef void (*app_indicator_set_icon_theme_path_func)(
66ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    AppIndicator* self,
67ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    const gchar* icon_theme_path);
68ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)bool g_attempted_load = false;
704e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)bool g_opened = false;
71ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
72ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch// Retrieved functions from libappindicator.
73ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochapp_indicator_new_func app_indicator_new = NULL;
74ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochapp_indicator_new_with_path_func app_indicator_new_with_path = NULL;
75ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochapp_indicator_set_status_func app_indicator_set_status = NULL;
76ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochapp_indicator_set_attention_icon_full_func
77ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    app_indicator_set_attention_icon_full = NULL;
78ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochapp_indicator_set_menu_func app_indicator_set_menu = NULL;
79ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochapp_indicator_set_icon_full_func app_indicator_set_icon_full = NULL;
80ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochapp_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path = NULL;
81ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
82ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochvoid EnsureMethodsLoaded() {
834e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (g_attempted_load)
84ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return;
854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
864e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  g_attempted_load = true;
87ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
88f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // Only use libappindicator where it is needed to support dbus based status
89f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  // icons. In particular, libappindicator does not support a click action.
90f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  scoped_ptr<base::Environment> env(base::Environment::Create());
91f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  base::nix::DesktopEnvironment environment =
92f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      base::nix::GetDesktopEnvironment(env.get());
93f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  if (environment != base::nix::DESKTOP_ENVIRONMENT_KDE4 &&
94f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)      environment != base::nix::DESKTOP_ENVIRONMENT_UNITY) {
95f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    return;
96f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  }
97f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)
98ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  void* indicator_lib = dlopen("libappindicator.so", RTLD_LAZY);
99ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  if (!indicator_lib) {
100ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    indicator_lib = dlopen("libappindicator.so.1", RTLD_LAZY);
101ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  }
102ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  if (!indicator_lib) {
103ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    indicator_lib = dlopen("libappindicator.so.0", RTLD_LAZY);
104ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  }
105ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  if (!indicator_lib) {
106ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return;
107ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  }
108ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
1094e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  g_opened = true;
110ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
111ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  app_indicator_new = reinterpret_cast<app_indicator_new_func>(
112ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      dlsym(indicator_lib, "app_indicator_new"));
113ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
114ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  app_indicator_new_with_path =
115ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      reinterpret_cast<app_indicator_new_with_path_func>(
116ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch          dlsym(indicator_lib, "app_indicator_new_with_path"));
117ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
118ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
119ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      dlsym(indicator_lib, "app_indicator_set_status"));
120ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
121ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  app_indicator_set_attention_icon_full =
122ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      reinterpret_cast<app_indicator_set_attention_icon_full_func>(
123ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch          dlsym(indicator_lib, "app_indicator_set_attention_icon_full"));
124ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
125ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
126ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      dlsym(indicator_lib, "app_indicator_set_menu"));
127ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
128ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  app_indicator_set_icon_full =
129ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      reinterpret_cast<app_indicator_set_icon_full_func>(
130ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch          dlsym(indicator_lib, "app_indicator_set_icon_full"));
131ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
132ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  app_indicator_set_icon_theme_path =
133ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      reinterpret_cast<app_indicator_set_icon_theme_path_func>(
134ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch          dlsym(indicator_lib, "app_indicator_set_icon_theme_path"));
135ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
136ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
13746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// Returns whether a temporary directory should be created for each app
13846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// indicator image.
13946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)bool ShouldCreateTempDirectoryPerImage(bool using_kde4) {
14046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // Create a new temporary directory for each image on Unity since using a
14146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // single temporary directory seems to have issues when changing icons in
14246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // quick succession.
14346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  return !using_kde4;
14446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
14546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
14646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// Returns the subdirectory of |temp_dir| in which the app indicator image
14746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// should be saved.
14846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)base::FilePath GetImageDirectoryPath(bool using_kde4,
14946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                     const base::FilePath& temp_dir) {
15046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // On KDE4, an image located in a directory ending with
15146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // "icons/hicolor/16x16/apps" can be used as the app indicator image because
15246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // "/usr/share/icons/hicolor/16x16/apps" exists.
15346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  return using_kde4 ?
15446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      temp_dir.AppendASCII("icons").AppendASCII("hicolor").AppendASCII("16x16").
15546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          AppendASCII("apps") :
15646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      temp_dir;
15746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
15846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
15946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)std::string GetImageFileNameForKDE4(
16046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    const scoped_refptr<base::RefCountedMemory>& png_data) {
16146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // On KDE4, the name of the image file for each different looking bitmap must
16246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // be unique. It must also be unique across runs of Chrome.
16346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  base::MD5Digest digest;
16446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  base::MD5Sum(png_data->front_as<char>(), png_data->size(), &digest);
16546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  return base::StringPrintf("chrome_app_indicator_%s.png",
16646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                            base::MD5DigestToBase16(digest).c_str());
16746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
16846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
16946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)std::string GetImageFileNameForNonKDE4(int icon_change_count,
17046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                       const std::string& id) {
17146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  return base::StringPrintf("%s_%d.png", id.c_str(), icon_change_count);
17246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
17346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
17446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// Returns the "icon theme path" given the file path of the app indicator image.
17546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)std::string GetIconThemePath(bool using_kde4,
17646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                             const base::FilePath& image_path) {
17746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  return using_kde4 ?
17846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      image_path.DirName().DirName().DirName().DirName().value() :
17946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      image_path.DirName().value();
18046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
18146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
18246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)base::FilePath CreateTempImageFile(bool using_kde4,
18346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                   gfx::ImageSkia* image_ptr,
1844e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                   int icon_change_count,
18546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                   std::string id,
18646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                   const base::FilePath& previous_file_path) {
1874e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  scoped_ptr<gfx::ImageSkia> image(image_ptr);
1884e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1894e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  scoped_refptr<base::RefCountedMemory> png_data =
1904e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      gfx::Image(*image.get()).As1xPNGBytes();
1914e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (png_data->size() == 0) {
1924e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // If the bitmap could not be encoded to PNG format, skip it.
1934e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    LOG(WARNING) << "Could not encode icon";
1944e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return base::FilePath();
1954e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
1964e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1974e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  base::FilePath new_file_path;
19846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (previous_file_path.empty() ||
19946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      ShouldCreateTempDirectoryPerImage(using_kde4)) {
20046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    base::FilePath tmp_dir;
20146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (!base::CreateNewTempDirectory(base::FilePath::StringType(), &tmp_dir))
20246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      return base::FilePath();
20346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    new_file_path = GetImageDirectoryPath(using_kde4, tmp_dir);
20446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (new_file_path != tmp_dir) {
20546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if (!base::CreateDirectory(new_file_path))
20646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        return base::FilePath();
20746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
20846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  } else {
20946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    new_file_path = previous_file_path.DirName();
21046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
21146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
21246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  new_file_path = new_file_path.Append(using_kde4 ?
21346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      GetImageFileNameForKDE4(png_data) :
21446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      GetImageFileNameForNonKDE4(icon_change_count, id));
2154e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
2164e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  int bytes_written =
217a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      base::WriteFile(new_file_path,
218a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)                      png_data->front_as<char>(), png_data->size());
2194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
2204e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (bytes_written != static_cast<int>(png_data->size()))
2214e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return base::FilePath();
2224e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  return new_file_path;
2234e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
2244e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
22546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void DeleteTempDirectory(const base::FilePath& dir_path) {
22646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (dir_path.empty())
2274e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return;
22846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  base::DeleteFile(dir_path, true);
2294e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
2304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
231ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}  // namespace
232ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
233ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochnamespace libgtk2ui {
234ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
235a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)AppIndicatorIcon::AppIndicatorIcon(std::string id,
236a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)                                   const gfx::ImageSkia& image,
237a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)                                   const base::string16& tool_tip)
238ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    : id_(id),
23946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      using_kde4_(false),
240ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      icon_(NULL),
241ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      menu_model_(NULL),
242ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      icon_change_count_(0),
2431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      weak_factory_(this) {
24446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  scoped_ptr<base::Environment> env(base::Environment::Create());
24546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  using_kde4_ = base::nix::GetDesktopEnvironment(env.get()) ==
24646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      base::nix::DESKTOP_ENVIRONMENT_KDE4;
24746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
248ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  EnsureMethodsLoaded();
2495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  tool_tip_ = base::UTF16ToUTF8(tool_tip);
250a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  SetImage(image);
251ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
252ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben MurdochAppIndicatorIcon::~AppIndicatorIcon() {
253ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  if (icon_) {
254ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
255ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    g_object_unref(icon_);
256ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    content::BrowserThread::GetBlockingPool()->PostTask(
257ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        FROM_HERE,
25846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        base::Bind(&DeleteTempDirectory, icon_file_path_.DirName()));
259ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  }
260ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
261ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
2624e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// static
263ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochbool AppIndicatorIcon::CouldOpen() {
264ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  EnsureMethodsLoaded();
2654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  return g_opened;
266ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
267ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
268ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochvoid AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) {
2694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (!g_opened)
270a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)    return;
271a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
272a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  ++icon_change_count_;
273a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)
274a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // We create a deep copy of the image since it may have been freed by the time
275a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // it's accessed in the other thread.
276a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  scoped_ptr<gfx::ImageSkia> safe_image(image.DeepCopy());
277a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  base::PostTaskAndReplyWithResult(
278a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      content::BrowserThread::GetBlockingPool()
279a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)          ->GetTaskRunnerWithShutdownBehavior(
280a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)                base::SequencedWorkerPool::SKIP_ON_SHUTDOWN).get(),
281a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      FROM_HERE,
2824e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      base::Bind(&CreateTempImageFile,
28346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                 using_kde4_,
284a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)                 safe_image.release(),
285a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)                 icon_change_count_,
28646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                 id_,
28746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                 icon_file_path_),
288a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)      base::Bind(&AppIndicatorIcon::SetImageFromFile,
2891e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)                 weak_factory_.GetWeakPtr()));
290ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
291ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
292ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochvoid AppIndicatorIcon::SetPressedImage(const gfx::ImageSkia& image) {
293ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  // Ignore pressed images, since the standard on Linux is to not highlight
294ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  // pressed status icons.
295ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
296ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
297a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)void AppIndicatorIcon::SetToolTip(const base::string16& tool_tip) {
298a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  DCHECK(!tool_tip_.empty());
2995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  tool_tip_ = base::UTF16ToUTF8(tool_tip);
30046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  UpdateClickActionReplacementMenuItem();
301ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
302ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
303ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochvoid AppIndicatorIcon::UpdatePlatformContextMenu(ui::MenuModel* model) {
3044e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (!g_opened)
305ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return;
306ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
307ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  menu_model_ = model;
308ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
309a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // The icon is created asynchronously so it might not exist when the menu is
310a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  // set.
311a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles)  if (icon_)
312ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    SetMenu();
313ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
314ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
315424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)void AppIndicatorIcon::RefreshPlatformContextMenu() {
31646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  menu_->Refresh();
317424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)}
318424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
3194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)void AppIndicatorIcon::SetImageFromFile(const base::FilePath& icon_file_path) {
320e5d81f57cb97b3b6b7fccc9c5610d21eb81db09dBen Murdoch  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
3214e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (icon_file_path.empty())
3224e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    return;
3234e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
3244e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  base::FilePath old_path = icon_file_path_;
3254e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  icon_file_path_ = icon_file_path;
3264e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
3274e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  std::string icon_name =
3284e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      icon_file_path_.BaseName().RemoveExtension().value();
32946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  std::string icon_dir = GetIconThemePath(using_kde4_, icon_file_path);
3304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (!icon_) {
3314e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    icon_ =
3324e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)        app_indicator_new_with_path(id_.c_str(),
3334e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                    icon_name.c_str(),
3344e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                    APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
3354e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                    icon_dir.c_str());
3364e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
3374e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    SetMenu();
3384e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  } else {
3394e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // Currently we are creating a new temp directory every time the icon is
3404e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // set. So we need to set the directory each time.
3414e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    app_indicator_set_icon_theme_path(icon_, icon_dir.c_str());
3424e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    app_indicator_set_icon_full(icon_, icon_name.c_str(), "icon");
3434e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
34446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (ShouldCreateTempDirectoryPerImage(using_kde4_)) {
34546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      // Delete previous icon directory.
34646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      content::BrowserThread::GetBlockingPool()->PostTask(
34746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          FROM_HERE,
34846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          base::Bind(&DeleteTempDirectory, old_path.DirName()));
34946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
350ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  }
351ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
352ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
353ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdochvoid AppIndicatorIcon::SetMenu() {
35446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  menu_.reset(new AppIndicatorIconMenu(menu_model_));
35546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  UpdateClickActionReplacementMenuItem();
35646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  app_indicator_set_menu(icon_, menu_->GetGtkMenu());
357ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
358ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
35946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void AppIndicatorIcon::UpdateClickActionReplacementMenuItem() {
36046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // The menu may not have been created yet.
36146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (!menu_.get())
36246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return;
363ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
36446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (!delegate()->HasClickAction() && menu_model_)
36546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return;
366ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
36746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  DCHECK(!tool_tip_.empty());
36846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  menu_->UpdateClickActionReplacementMenuItem(
36946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      tool_tip_.c_str(),
37046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      base::Bind(&AppIndicatorIcon::OnClickActionReplacementMenuItemActivated,
37146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                 base::Unretained(this)));
372ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
373ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
37446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void AppIndicatorIcon::OnClickActionReplacementMenuItemActivated() {
375ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  if (delegate())
376ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    delegate()->OnClick();
377ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}
378ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
379ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch}  // namespace libgtk2ui
380