Merge pull request #375 from atom/status-icon

Tray module
This commit is contained in:
Cheng Zhao 2014-06-03 14:27:21 +08:00
commit 98aba2aa12
28 changed files with 1892 additions and 0 deletions

View file

@ -24,6 +24,7 @@
'atom/browser/api/lib/menu-item.coffee',
'atom/browser/api/lib/power-monitor.coffee',
'atom/browser/api/lib/protocol.coffee',
'atom/browser/api/lib/tray.coffee',
'atom/browser/api/lib/web-contents.coffee',
'atom/browser/lib/init.coffee',
'atom/browser/lib/objects-registry.coffee',
@ -62,6 +63,8 @@
'atom/browser/api/atom_api_power_monitor.h',
'atom/browser/api/atom_api_protocol.cc',
'atom/browser/api/atom_api_protocol.h',
'atom/browser/api/atom_api_tray.cc',
'atom/browser/api/atom_api_tray.h',
'atom/browser/api/atom_api_web_contents.cc',
'atom/browser/api/atom_api_web_contents.h',
'atom/browser/api/atom_api_window.cc',
@ -127,14 +130,29 @@
'atom/browser/ui/file_dialog_gtk.cc',
'atom/browser/ui/file_dialog_mac.mm',
'atom/browser/ui/file_dialog_win.cc',
'atom/browser/ui/gtk/app_indicator_icon.cc',
'atom/browser/ui/gtk/app_indicator_icon.h',
'atom/browser/ui/gtk/status_icon.cc',
'atom/browser/ui/gtk/status_icon.h',
'atom/browser/ui/message_box.h',
'atom/browser/ui/message_box_gtk.cc',
'atom/browser/ui/message_box_mac.mm',
'atom/browser/ui/message_box_win.cc',
'atom/browser/ui/tray_icon.cc',
'atom/browser/ui/tray_icon.h',
'atom/browser/ui/tray_icon_gtk.cc',
'atom/browser/ui/tray_icon_cocoa.h',
'atom/browser/ui/tray_icon_cocoa.mm',
'atom/browser/ui/tray_icon_observer.h',
'atom/browser/ui/tray_icon_win.cc',
'atom/browser/ui/win/menu_2.cc',
'atom/browser/ui/win/menu_2.h',
'atom/browser/ui/win/native_menu_win.cc',
'atom/browser/ui/win/native_menu_win.h',
'atom/browser/ui/win/notify_icon_host.cc',
'atom/browser/ui/win/notify_icon_host.h',
'atom/browser/ui/win/notify_icon.cc',
'atom/browser/ui/win/notify_icon.h',
'atom/browser/window_list.cc',
'atom/browser/window_list.h',
'atom/browser/window_list_observer.h',
@ -176,6 +194,8 @@
'atom/common/native_mate_converters/file_path_converter.h',
'atom/common/native_mate_converters/function_converter.h',
'atom/common/native_mate_converters/gurl_converter.h',
'atom/common/native_mate_converters/image_converter.cc',
'atom/common/native_mate_converters/image_converter.h',
'atom/common/native_mate_converters/string16_converter.h',
'atom/common/native_mate_converters/v8_value_converter.cc',
'atom/common/native_mate_converters/v8_value_converter.h',
@ -215,6 +235,8 @@
'chrome/browser/ui/gtk/gtk_window_util.h',
'chrome/browser/ui/gtk/menu_gtk.cc',
'chrome/browser/ui/gtk/menu_gtk.h',
'chrome/browser/ui/views/status_icons/status_tray_state_changer_win.cc',
'chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h',
'<@(native_mate_files)',
],
'framework_sources': [

View file

@ -34,6 +34,8 @@ class Menu : public mate::Wrappable,
static void SendActionToFirstResponder(const std::string& action);
#endif
ui::SimpleMenuModel* model() const { return model_.get(); }
protected:
Menu();
virtual ~Menu();

View file

@ -0,0 +1,83 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/api/atom_api_tray.h"
#include <string>
#include "atom/browser/api/atom_api_menu.h"
#include "atom/browser/ui/tray_icon.h"
#include "atom/common/native_mate_converters/image_converter.h"
#include "native_mate/constructor.h"
#include "native_mate/dictionary.h"
#include "atom/common/node_includes.h"
namespace atom {
namespace api {
Tray::Tray(const gfx::ImageSkia& image)
: tray_icon_(TrayIcon::Create()) {
tray_icon_->SetImage(image);
tray_icon_->AddObserver(this);
}
Tray::~Tray() {
}
// static
mate::Wrappable* Tray::New(const gfx::ImageSkia& image) {
return new Tray(image);
}
void Tray::OnClicked() {
Emit("clicked");
}
void Tray::SetImage(const gfx::ImageSkia& image) {
tray_icon_->SetImage(image);
}
void Tray::SetPressedImage(const gfx::ImageSkia& image) {
tray_icon_->SetPressedImage(image);
}
void Tray::SetToolTip(const std::string& tool_tip) {
tray_icon_->SetToolTip(tool_tip);
}
void Tray::SetContextMenu(Menu* menu) {
tray_icon_->SetContextMenu(menu->model());
}
// static
void Tray::BuildPrototype(v8::Isolate* isolate,
v8::Handle<v8::ObjectTemplate> prototype) {
mate::ObjectTemplateBuilder(isolate, prototype)
.SetMethod("setImage", &Tray::SetImage)
.SetMethod("setPressedImage", &Tray::SetPressedImage)
.SetMethod("setToolTip", &Tray::SetToolTip)
.SetMethod("_setContextMenu", &Tray::SetContextMenu);
}
} // namespace api
} // namespace atom
namespace {
void Initialize(v8::Handle<v8::Object> exports) {
using atom::api::Tray;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Handle<v8::Function> constructor = mate::CreateConstructor<Tray>(
isolate, "Tray", base::Bind(&Tray::New));
mate::Dictionary dict(isolate, exports);
dict.Set("Tray", static_cast<v8::Handle<v8::Value>>(constructor));
}
} // namespace
NODE_MODULE(atom_browser_tray, Initialize)

View file

@ -0,0 +1,56 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_API_ATOM_API_TRAY_H_
#define ATOM_BROWSER_API_ATOM_API_TRAY_H_
#include <string>
#include "atom/browser/api/event_emitter.h"
#include "atom/browser/ui/tray_icon_observer.h"
#include "base/memory/scoped_ptr.h"
namespace gfx {
class ImageSkia;
}
namespace atom {
class TrayIcon;
namespace api {
class Menu;
class Tray : public mate::EventEmitter,
public TrayIconObserver {
public:
static mate::Wrappable* New(const gfx::ImageSkia& image);
static void BuildPrototype(v8::Isolate* isolate,
v8::Handle<v8::ObjectTemplate> prototype);
protected:
explicit Tray(const gfx::ImageSkia& image);
virtual ~Tray();
// TrayIcon implementations:
virtual void OnClicked() OVERRIDE;
void SetImage(const gfx::ImageSkia& image);
void SetPressedImage(const gfx::ImageSkia& image);
void SetToolTip(const std::string& tool_tip);
void SetContextMenu(Menu* menu);
private:
scoped_ptr<TrayIcon> tray_icon_;
DISALLOW_COPY_AND_ASSIGN(Tray);
};
} // namespace api
} // namespace atom
#endif // ATOM_BROWSER_API_ATOM_API_TRAY_H_

View file

@ -0,0 +1,10 @@
EventEmitter = require('events').EventEmitter
bindings = process.atomBinding 'tray'
Tray = bindings.Tray
Tray::__proto__ = EventEmitter.prototype
Tray::setContextMenu = (menu) ->
@_setContextMenu menu
@menu = menu # Keep a strong reference of menu.
module.exports = Tray

View file

@ -0,0 +1,258 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/gtk/app_indicator_icon.h"
#include <gtk/gtk.h>
#include <dlfcn.h>
#include "base/file_util.h"
#include "base/guid.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/stringprintf.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/ui/gtk/menu_gtk.h"
#include "content/public/browser/browser_thread.h"
#include "ui/gfx/image/image.h"
namespace {
typedef enum {
APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
APP_INDICATOR_CATEGORY_COMMUNICATIONS,
APP_INDICATOR_CATEGORY_SYSTEM_SERVICES,
APP_INDICATOR_CATEGORY_HARDWARE,
APP_INDICATOR_CATEGORY_OTHER
} AppIndicatorCategory;
typedef enum {
APP_INDICATOR_STATUS_PASSIVE,
APP_INDICATOR_STATUS_ACTIVE,
APP_INDICATOR_STATUS_ATTENTION
} AppIndicatorStatus;
typedef AppIndicator* (*app_indicator_new_func)(const gchar* id,
const gchar* icon_name,
AppIndicatorCategory category);
typedef AppIndicator* (*app_indicator_new_with_path_func)(
const gchar* id,
const gchar* icon_name,
AppIndicatorCategory category,
const gchar* icon_theme_path);
typedef void (*app_indicator_set_status_func)(AppIndicator* self,
AppIndicatorStatus status);
typedef void (*app_indicator_set_attention_icon_full_func)(
AppIndicator* self,
const gchar* icon_name,
const gchar* icon_desc);
typedef void (*app_indicator_set_menu_func)(AppIndicator* self, GtkMenu* menu);
typedef void (*app_indicator_set_icon_full_func)(AppIndicator* self,
const gchar* icon_name,
const gchar* icon_desc);
typedef void (*app_indicator_set_icon_theme_path_func)(
AppIndicator* self,
const gchar* icon_theme_path);
bool g_attempted_load = false;
bool g_opened = false;
// Retrieved functions from libappindicator.
app_indicator_new_func app_indicator_new = NULL;
app_indicator_new_with_path_func app_indicator_new_with_path = NULL;
app_indicator_set_status_func app_indicator_set_status = NULL;
app_indicator_set_attention_icon_full_func
app_indicator_set_attention_icon_full = NULL;
app_indicator_set_menu_func app_indicator_set_menu = NULL;
app_indicator_set_icon_full_func app_indicator_set_icon_full = NULL;
app_indicator_set_icon_theme_path_func app_indicator_set_icon_theme_path = NULL;
void EnsureMethodsLoaded() {
if (g_attempted_load)
return;
g_attempted_load = true;
void* indicator_lib = dlopen("libappindicator.so", RTLD_LAZY);
if (!indicator_lib) {
indicator_lib = dlopen("libappindicator.so.1", RTLD_LAZY);
}
if (!indicator_lib) {
indicator_lib = dlopen("libappindicator.so.0", RTLD_LAZY);
}
if (!indicator_lib) {
return;
}
g_opened = true;
app_indicator_new = reinterpret_cast<app_indicator_new_func>(
dlsym(indicator_lib, "app_indicator_new"));
app_indicator_new_with_path =
reinterpret_cast<app_indicator_new_with_path_func>(
dlsym(indicator_lib, "app_indicator_new_with_path"));
app_indicator_set_status = reinterpret_cast<app_indicator_set_status_func>(
dlsym(indicator_lib, "app_indicator_set_status"));
app_indicator_set_attention_icon_full =
reinterpret_cast<app_indicator_set_attention_icon_full_func>(
dlsym(indicator_lib, "app_indicator_set_attention_icon_full"));
app_indicator_set_menu = reinterpret_cast<app_indicator_set_menu_func>(
dlsym(indicator_lib, "app_indicator_set_menu"));
app_indicator_set_icon_full =
reinterpret_cast<app_indicator_set_icon_full_func>(
dlsym(indicator_lib, "app_indicator_set_icon_full"));
app_indicator_set_icon_theme_path =
reinterpret_cast<app_indicator_set_icon_theme_path_func>(
dlsym(indicator_lib, "app_indicator_set_icon_theme_path"));
}
base::FilePath CreateTempImageFile(gfx::ImageSkia* image_ptr,
int icon_change_count,
std::string id) {
scoped_ptr<gfx::ImageSkia> image(image_ptr);
scoped_refptr<base::RefCountedMemory> png_data =
gfx::Image(*image.get()).As1xPNGBytes();
if (png_data->size() == 0) {
// If the bitmap could not be encoded to PNG format, skip it.
LOG(WARNING) << "Could not encode icon";
return base::FilePath();
}
base::FilePath temp_dir;
base::FilePath new_file_path;
// Create a new temporary directory for each image since using a single
// temporary directory seems to have issues when changing icons in quick
// succession.
if (!file_util::CreateNewTempDirectory(base::FilePath::StringType(),
&temp_dir))
return base::FilePath();
new_file_path =
temp_dir.Append(id + base::StringPrintf("_%d.png", icon_change_count));
int bytes_written =
file_util::WriteFile(
new_file_path,
reinterpret_cast<const char*>(png_data->front()),
png_data->size());
if (bytes_written != static_cast<int>(png_data->size()))
return base::FilePath();
return new_file_path;
}
void DeleteTempImagePath(const base::FilePath& icon_file_path) {
if (icon_file_path.empty())
return;
base::DeleteFile(icon_file_path, true);
}
} // namespace
namespace atom {
AppIndicatorIcon::AppIndicatorIcon()
: icon_(NULL),
id_(base::GenerateGUID()),
icon_change_count_(0),
weak_factory_(this) {
}
AppIndicatorIcon::~AppIndicatorIcon() {
if (icon_) {
app_indicator_set_status(icon_, APP_INDICATOR_STATUS_PASSIVE);
// if (gtk_menu_)
// DestroyMenu();
g_object_unref(icon_);
content::BrowserThread::GetBlockingPool()->PostTask(
FROM_HERE,
base::Bind(&DeleteTempImagePath, icon_file_path_.DirName()));
}
}
bool AppIndicatorIcon::CouldOpen() {
EnsureMethodsLoaded();
return g_opened;
}
void AppIndicatorIcon::SetImage(const gfx::ImageSkia& image) {
if (!g_opened)
return;
++icon_change_count_;
// We create a deep copy of the image since it may have been freed by the time
// it's accessed in the other thread.
scoped_ptr<gfx::ImageSkia> safe_image(image.DeepCopy());
base::PostTaskAndReplyWithResult(
content::BrowserThread::GetBlockingPool()
->GetTaskRunnerWithShutdownBehavior(
base::SequencedWorkerPool::SKIP_ON_SHUTDOWN).get(),
FROM_HERE,
base::Bind(&CreateTempImageFile,
safe_image.release(),
icon_change_count_,
id_),
base::Bind(&AppIndicatorIcon::SetImageFromFile,
weak_factory_.GetWeakPtr()));
}
void AppIndicatorIcon::SetPressedImage(const gfx::ImageSkia& image) {
// Ignore pressed images, since the standard on Linux is to not highlight
// pressed status icons.
}
void AppIndicatorIcon::SetToolTip(const std::string& tool_tip) {
// App indicator doesn't have tooltips:
// https://bugs.launchpad.net/indicator-application/+bug/527458
}
void AppIndicatorIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) {
menu_.reset(new MenuGtk(NULL, menu_model));
app_indicator_set_menu(icon_, GTK_MENU(menu_->widget()));
}
void AppIndicatorIcon::SetImageFromFile(const base::FilePath& icon_file_path) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
if (icon_file_path.empty())
return;
base::FilePath old_path = icon_file_path_;
icon_file_path_ = icon_file_path;
std::string icon_name =
icon_file_path_.BaseName().RemoveExtension().value();
std::string icon_dir = icon_file_path_.DirName().value();
if (!icon_) {
icon_ =
app_indicator_new_with_path(id_.c_str(),
icon_name.c_str(),
APP_INDICATOR_CATEGORY_APPLICATION_STATUS,
icon_dir.c_str());
app_indicator_set_status(icon_, APP_INDICATOR_STATUS_ACTIVE);
} else {
// Currently we are creating a new temp directory every time the icon is
// set. So we need to set the directory each time.
app_indicator_set_icon_theme_path(icon_, icon_dir.c_str());
app_indicator_set_icon_full(icon_, icon_name.c_str(), "icon");
// Delete previous icon directory.
content::BrowserThread::GetBlockingPool()->PostTask(
FROM_HERE,
base::Bind(&DeleteTempImagePath, old_path.DirName()));
}
}
} // namespace atom

View file

@ -0,0 +1,55 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
#define ATOM_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_
#include <string>
#include "atom/browser/ui/tray_icon.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "ui/base/gtk/gtk_signal.h"
typedef struct _AppIndicator AppIndicator;
typedef struct _GtkWidget GtkWidget;
class MenuGtk;
namespace atom {
class AppIndicatorIcon : public TrayIcon {
public:
AppIndicatorIcon();
virtual ~AppIndicatorIcon();
// Indicates whether libappindicator so could be opened.
static bool CouldOpen();
virtual void SetImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetPressedImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetToolTip(const std::string& tool_tip) OVERRIDE;
virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) OVERRIDE;
private:
void SetImageFromFile(const base::FilePath& icon_file_path);
// Gtk status icon wrapper
AppIndicator* icon_;
// The context menu for this icon (if any).
scoped_ptr<MenuGtk> menu_;
std::string id_;
base::FilePath icon_file_path_;
int icon_change_count_;
base::WeakPtrFactory<AppIndicatorIcon> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(AppIndicatorIcon);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_

View file

@ -0,0 +1,56 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/gtk/status_icon.h"
#include "chrome/browser/ui/gtk/menu_gtk.h"
#include "ui/gfx/gtk_util.h"
namespace atom {
StatusIcon::StatusIcon() : icon_(gtk_status_icon_new()) {
gtk_status_icon_set_visible(icon_, TRUE);
g_signal_connect(icon_, "activate", G_CALLBACK(OnActivateThunk), this);
g_signal_connect(icon_, "popup-menu", G_CALLBACK(OnPopupMenuThunk), this);
}
StatusIcon::~StatusIcon() {
gtk_status_icon_set_visible(icon_, FALSE);
g_object_unref(icon_);
}
void StatusIcon::SetImage(const gfx::ImageSkia& image) {
if (image.isNull())
return;
GdkPixbuf* pixbuf = gfx::GdkPixbufFromSkBitmap(*image.bitmap());
gtk_status_icon_set_from_pixbuf(icon_, pixbuf);
g_object_unref(pixbuf);
}
void StatusIcon::SetPressedImage(const gfx::ImageSkia& image) {
// Ignore pressed images, since the standard on Linux is to not highlight
// pressed status icons.
}
void StatusIcon::SetToolTip(const std::string& tool_tip) {
gtk_status_icon_set_tooltip_text(icon_, tool_tip.c_str());
}
void StatusIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) {
menu_.reset(new MenuGtk(NULL, menu_model));
}
void StatusIcon::OnPopupMenu(GtkWidget* widget, guint button, guint time) {
// If we have a menu - display it.
if (menu_.get())
menu_->PopupAsContextForStatusIcon(time, button, icon_);
}
void StatusIcon::OnActivate(GtkWidget* widget) {
NotifyClicked();
}
} // namespace atom

View file

@ -0,0 +1,47 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_UI_GTK_STATUS_ICON_H_
#define ATOM_BROWSER_UI_GTK_STATUS_ICON_H_
#include <gtk/gtk.h>
#include <string>
#include "atom/browser/ui/tray_icon.h"
#include "ui/base/gtk/gtk_signal.h"
class MenuGtk;
namespace atom {
class StatusIcon : public TrayIcon {
public:
StatusIcon();
virtual ~StatusIcon();
virtual void SetImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetPressedImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetToolTip(const std::string& tool_tip) OVERRIDE;
virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) OVERRIDE;
private:
// Callback invoked when user right-clicks on the status icon.
CHROMEGTK_CALLBACK_2(StatusIcon, void, OnPopupMenu, guint, guint);
// Callback invoked when the icon is clicked.
CHROMEGTK_CALLBACK_0(StatusIcon, void, OnActivate);
// The currently-displayed icon for the window.
GtkStatusIcon* icon_;
// The context menu for this icon (if any).
scoped_ptr<MenuGtk> menu_;
DISALLOW_COPY_AND_ASSIGN(StatusIcon);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_GTK_STATUS_ICON_H_

View file

@ -0,0 +1,19 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/tray_icon.h"
namespace atom {
TrayIcon::TrayIcon() {
}
TrayIcon::~TrayIcon() {
}
void TrayIcon::NotifyClicked() {
FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnClicked());
}
} // namespace atom

View file

@ -0,0 +1,52 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_UI_TRAY_ICON_H_
#define ATOM_BROWSER_UI_TRAY_ICON_H_
#include <string>
#include "atom/browser/ui/tray_icon_observer.h"
#include "base/observer_list.h"
#include "ui/base/models/simple_menu_model.h"
namespace atom {
class TrayIcon {
public:
static TrayIcon* Create();
virtual ~TrayIcon();
// Sets the image associated with this status icon.
virtual void SetImage(const gfx::ImageSkia& image) = 0;
// Sets the image associated with this status icon when pressed.
virtual void SetPressedImage(const gfx::ImageSkia& image) = 0;
// Sets the hover text for this status icon. This is also used as the label
// for the menu item which is created as a replacement for the status icon
// click action on platforms that do not support custom click actions for the
// status icon (e.g. Ubuntu Unity).
virtual void SetToolTip(const std::string& tool_tip) = 0;
// Set the context menu for this icon.
virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) = 0;
void AddObserver(TrayIconObserver* obs) { observers_.AddObserver(obs); }
void RemoveObserver(TrayIconObserver* obs) { observers_.RemoveObserver(obs); }
void NotifyClicked();
protected:
TrayIcon();
private:
ObserverList<TrayIconObserver> observers_;
DISALLOW_COPY_AND_ASSIGN(TrayIcon);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_TRAY_ICON_H_

View file

@ -0,0 +1,43 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_
#define ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_
#import <Cocoa/Cocoa.h>
#include <string>
#include "atom/browser/ui/tray_icon.h"
#include "base/mac/scoped_nsobject.h"
@class AtomMenuController;
@class StatusItemController;
namespace atom {
class TrayIconCocoa : public TrayIcon {
public:
TrayIconCocoa();
virtual ~TrayIconCocoa();
virtual void SetImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetPressedImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetToolTip(const std::string& tool_tip) OVERRIDE;
virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) OVERRIDE;
private:
base::scoped_nsobject<NSStatusItem> item_;
base::scoped_nsobject<StatusItemController> controller_;
// Status menu shown when right-clicking the system icon.
base::scoped_nsobject<AtomMenuController> menu_;
DISALLOW_COPY_AND_ASSIGN(TrayIconCocoa);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_

View file

@ -0,0 +1,81 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/tray_icon_cocoa.h"
#include "atom/browser/ui/cocoa/atom_menu_controller.h"
#include "base/strings/sys_string_conversions.h"
#include "skia/ext/skia_utils_mac.h"
@interface StatusItemController : NSObject {
atom::TrayIconCocoa* trayIcon_; // weak
}
- (id)initWithIcon:(atom::TrayIconCocoa*)icon;
- (void)handleClick:(id)sender;
@end // @interface StatusItemController
@implementation StatusItemController
- (id)initWithIcon:(atom::TrayIconCocoa*)icon {
trayIcon_ = icon;
return self;
}
- (void)handleClick:(id)sender {
DCHECK(trayIcon_);
trayIcon_->NotifyClicked();
}
@end
namespace atom {
TrayIconCocoa::TrayIconCocoa() {
controller_.reset([[StatusItemController alloc] initWithIcon:this]);
item_.reset([[[NSStatusBar systemStatusBar]
statusItemWithLength:NSVariableStatusItemLength] retain]);
[item_ setEnabled:YES];
[item_ setTarget:controller_];
[item_ setAction:@selector(handleClick:)];
[item_ setHighlightMode:YES];
}
TrayIconCocoa::~TrayIconCocoa() {
// Remove the status item from the status bar.
[[NSStatusBar systemStatusBar] removeStatusItem:item_];
}
void TrayIconCocoa::SetImage(const gfx::ImageSkia& image) {
if (!image.isNull()) {
NSImage* ns_image = gfx::SkBitmapToNSImage(*image.bitmap());
if (ns_image)
[item_ setImage:ns_image];
}
}
void TrayIconCocoa::SetPressedImage(const gfx::ImageSkia& image) {
if (!image.isNull()) {
NSImage* ns_image = gfx::SkBitmapToNSImage(*image.bitmap());
if (ns_image)
[item_ setAlternateImage:ns_image];
}
}
void TrayIconCocoa::SetToolTip(const std::string& tool_tip) {
[item_ setToolTip:base::SysUTF8ToNSString(tool_tip)];
}
void TrayIconCocoa::SetContextMenu(ui::SimpleMenuModel* menu_model) {
menu_.reset([[AtomMenuController alloc] initWithModel:menu_model]);
[item_ setMenu:[menu_ menu]];
}
// static
TrayIcon* TrayIcon::Create() {
return new TrayIconCocoa;
}
} // namespace atom

View file

@ -0,0 +1,18 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/gtk/status_icon.h"
#include "atom/browser/ui/gtk/app_indicator_icon.h"
namespace atom {
// static
TrayIcon* TrayIcon::Create() {
if (AppIndicatorIcon::CouldOpen())
return new AppIndicatorIcon;
else
return new StatusIcon;
}
} // namespace atom

View file

@ -0,0 +1,20 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_
#define ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_
namespace atom {
class TrayIconObserver {
public:
virtual void OnClicked() {}
protected:
virtual ~TrayIconObserver() {}
};
} // namespace atom
#endif // ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_

View file

@ -0,0 +1,16 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/win/notify_icon.h"
#include "atom/browser/ui/win/notify_icon_host.h"
namespace atom {
// static
TrayIcon* TrayIcon::Create() {
static NotifyIconHost host;
return host.CreateNotifyIcon();
}
} // namespace atom

View file

@ -0,0 +1,30 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_UI_TRAY_ICON_WIN_H_
#define ATOM_BROWSER_UI_TRAY_ICON_WIN_H_
#include <string>
#include "atom/browser/ui/tray_icon.h"
namespace atom {
class TrayIconWin : public TrayIcon {
public:
TrayIconWin();
virtual ~TrayIconWin();
virtual void SetImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetPressedImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetToolTip(const std::string& tool_tip) OVERRIDE;
virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) OVERRIDE;
private:
DISALLOW_COPY_AND_ASSIGN(TrayIconWin);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_TRAY_ICON_WIN_H_

View file

@ -0,0 +1,133 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/win/notify_icon.h"
#include "atom/browser/ui/win/notify_icon_host.h"
#include "atom/browser/ui/win/menu_2.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/windows_version.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/icon_util.h"
#include "ui/gfx/point.h"
#include "ui/gfx/rect.h"
namespace atom {
NotifyIcon::NotifyIcon(NotifyIconHost* host,
UINT id,
HWND window,
UINT message)
: host_(host),
icon_id_(id),
window_(window),
message_id_(message),
menu_model_(NULL) {
NOTIFYICONDATA icon_data;
InitIconData(&icon_data);
icon_data.uFlags = NIF_MESSAGE;
icon_data.uCallbackMessage = message_id_;
BOOL result = Shell_NotifyIcon(NIM_ADD, &icon_data);
// This can happen if the explorer process isn't running when we try to
// create the icon for some reason (for example, at startup).
if (!result)
LOG(WARNING) << "Unable to create status tray icon.";
}
NotifyIcon::~NotifyIcon() {
// Remove our icon.
host_->Remove(this);
NOTIFYICONDATA icon_data;
InitIconData(&icon_data);
Shell_NotifyIcon(NIM_DELETE, &icon_data);
}
void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos,
bool left_mouse_click) {
// Pass to the observer if appropriate.
if (left_mouse_click) {
NotifyClicked();
return;
}
if (!menu_model_)
return;
// Set our window as the foreground window, so the context menu closes when
// we click away from it.
if (!SetForegroundWindow(window_))
return;
menu_.reset(new Menu2(menu_model_));
menu_->RunContextMenuAt(cursor_pos);
}
void NotifyIcon::ResetIcon() {
NOTIFYICONDATA icon_data;
InitIconData(&icon_data);
// Delete any previously existing icon.
Shell_NotifyIcon(NIM_DELETE, &icon_data);
InitIconData(&icon_data);
icon_data.uFlags = NIF_MESSAGE;
icon_data.uCallbackMessage = message_id_;
icon_data.hIcon = icon_.Get();
// If we have an image, then set the NIF_ICON flag, which tells
// Shell_NotifyIcon() to set the image for the status icon it creates.
if (icon_data.hIcon)
icon_data.uFlags |= NIF_ICON;
// Re-add our icon.
BOOL result = Shell_NotifyIcon(NIM_ADD, &icon_data);
if (!result)
LOG(WARNING) << "Unable to re-create status tray icon.";
}
void NotifyIcon::SetImage(const gfx::ImageSkia& image) {
// Create the icon.
NOTIFYICONDATA icon_data;
InitIconData(&icon_data);
icon_data.uFlags = NIF_ICON;
icon_.Set(IconUtil::CreateHICONFromSkBitmap(*image.bitmap()));
icon_data.hIcon = icon_.Get();
BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data);
if (!result)
LOG(WARNING) << "Error setting status tray icon image";
else
host_->UpdateIconVisibilityInBackground(this);
}
void NotifyIcon::SetPressedImage(const gfx::ImageSkia& image) {
// Ignore pressed images, since the standard on Windows is to not highlight
// pressed status icons.
}
void NotifyIcon::SetToolTip(const std::string& tool_tip) {
// Create the icon.
NOTIFYICONDATA icon_data;
InitIconData(&icon_data);
icon_data.uFlags = NIF_TIP;
wcscpy_s(icon_data.szTip, UTF8ToUTF16(tool_tip).c_str());
BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data);
if (!result)
LOG(WARNING) << "Unable to set tooltip for status tray icon";
}
void NotifyIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) {
menu_model_ = menu_model;
}
void NotifyIcon::InitIconData(NOTIFYICONDATA* icon_data) {
if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
memset(icon_data, 0, sizeof(NOTIFYICONDATA));
icon_data->cbSize = sizeof(NOTIFYICONDATA);
} else {
memset(icon_data, 0, NOTIFYICONDATA_V3_SIZE);
icon_data->cbSize = NOTIFYICONDATA_V3_SIZE;
}
icon_data->hWnd = window_;
icon_data->uID = icon_id_;
}
} // namespace atom

View file

@ -0,0 +1,79 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_UI_WIN_NOTIFY_ICON_H_
#define ATOM_BROWSER_UI_WIN_NOTIFY_ICON_H_
#include <windows.h>
#include <shellapi.h>
#include <string>
#include "atom/browser/ui/tray_icon.h"
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "base/win/scoped_gdi_object.h"
namespace gfx {
class Point;
}
namespace atom {
class Menu2;
class NotifyIconHost;
class NotifyIcon : public TrayIcon {
public:
// Constructor which provides this icon's unique ID and messaging window.
NotifyIcon(NotifyIconHost* host, UINT id, HWND window, UINT message);
virtual ~NotifyIcon();
// Handles a click event from the user - if |left_button_click| is true and
// there is a registered observer, passes the click event to the observer,
// otherwise displays the context menu if there is one.
void HandleClickEvent(const gfx::Point& cursor_pos, bool left_button_click);
// Re-creates the status tray icon now after the taskbar has been created.
void ResetIcon();
UINT icon_id() const { return icon_id_; }
HWND window() const { return window_; }
UINT message_id() const { return message_id_; }
// Overridden from TrayIcon:
virtual void SetImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetPressedImage(const gfx::ImageSkia& image) OVERRIDE;
virtual void SetToolTip(const std::string& tool_tip) OVERRIDE;
virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) OVERRIDE;
private:
void InitIconData(NOTIFYICONDATA* icon_data);
// The tray that owns us. Weak.
NotifyIconHost* host_;
// The unique ID corresponding to this icon.
UINT icon_id_;
// Window used for processing messages from this icon.
HWND window_;
// The message identifier used for status icon messages.
UINT message_id_;
// The currently-displayed icon for the window.
base::win::ScopedHICON icon_;
// The context menu.
ui::SimpleMenuModel* menu_model_;
scoped_ptr<atom::Menu2> menu_;
DISALLOW_COPY_AND_ASSIGN(NotifyIcon);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_WIN_NOTIFY_ICON_H_

View file

@ -0,0 +1,226 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/ui/win/notify_icon_host.h"
#include <commctrl.h>
#include "atom/browser/ui/win/notify_icon.h"
#include "base/bind.h"
#include "base/stl_util.h"
#include "base/threading/non_thread_safe.h"
#include "base/threading/thread.h"
#include "base/win/wrapped_window_proc.h"
#include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h"
#include "ui/gfx/screen.h"
#include "ui/gfx/win/hwnd_util.h"
namespace atom {
namespace {
const UINT kNotifyIconMessage = WM_APP + 1;
// |kBaseIconId| is 2 to avoid conflicts with plugins that hard-code id 1.
const UINT kBaseIconId = 2;
const wchar_t kNotifyIconHostWindowClass[] = L"AtomShell_NotifyIconHostWindow";
} // namespace
// Default implementation for NotifyIconHostStateChangerProxy that communicates
// to Exporer.exe via COM. It spawns a background thread with a fresh COM
// apartment and requests that the visibility be increased unless the user
// has explicitly set the icon to be hidden.
class NotifyIconHostStateChangerProxyImpl
: public NotifyIconHostStateChangerProxy,
public base::NonThreadSafe {
public:
NotifyIconHostStateChangerProxyImpl()
: pending_requests_(0),
worker_thread_("NotifyIconCOMWorkerThread"),
weak_factory_(this) {
worker_thread_.init_com_with_mta(false);
}
virtual void EnqueueChange(UINT icon_id, HWND window) OVERRIDE {
DCHECK(CalledOnValidThread());
if (pending_requests_ == 0)
worker_thread_.Start();
++pending_requests_;
worker_thread_.message_loop_proxy()->PostTaskAndReply(
FROM_HERE,
base::Bind(
&NotifyIconHostStateChangerProxyImpl::EnqueueChangeOnWorkerThread,
icon_id,
window),
base::Bind(&NotifyIconHostStateChangerProxyImpl::ChangeDone,
weak_factory_.GetWeakPtr()));
}
private:
// Must be called only on |worker_thread_|, to ensure the correct COM
// apartment.
static void EnqueueChangeOnWorkerThread(UINT icon_id, HWND window) {
// It appears that IUnknowns are coincidentally compatible with
// scoped_refptr. Normally I wouldn't depend on that but it seems that
// base::win::IUnknownImpl itself depends on that coincidence so it's
// already being assumed elsewhere.
scoped_refptr<StatusTrayStateChangerWin> status_tray_state_changer(
new StatusTrayStateChangerWin(icon_id, window));
status_tray_state_changer->EnsureTrayIconVisible();
}
// Called on UI thread.
void ChangeDone() {
DCHECK(CalledOnValidThread());
DCHECK_GT(pending_requests_, 0);
if (--pending_requests_ == 0)
worker_thread_.Stop();
}
private:
int pending_requests_;
base::Thread worker_thread_;
base::WeakPtrFactory<NotifyIconHostStateChangerProxyImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(NotifyIconHostStateChangerProxyImpl);
};
NotifyIconHost::NotifyIconHost()
: next_icon_id_(1),
atom_(0),
instance_(NULL),
window_(NULL) {
// Register our window class
WNDCLASSEX window_class;
base::win::InitializeWindowClass(
kNotifyIconHostWindowClass,
&base::win::WrappedWindowProc<NotifyIconHost::WndProcStatic>,
0, 0, 0, NULL, NULL, NULL, NULL, NULL,
&window_class);
instance_ = window_class.hInstance;
atom_ = RegisterClassEx(&window_class);
CHECK(atom_);
// If the taskbar is re-created after we start up, we have to rebuild all of
// our icons.
taskbar_created_message_ = RegisterWindowMessage(TEXT("TaskbarCreated"));
// Create an offscreen window for handling messages for the status icons. We
// create a hidden WS_POPUP window instead of an HWND_MESSAGE window, because
// only top-level windows such as popups can receive broadcast messages like
// "TaskbarCreated".
window_ = CreateWindow(MAKEINTATOM(atom_),
0, WS_POPUP, 0, 0, 0, 0, 0, 0, instance_, 0);
gfx::CheckWindowCreated(window_);
gfx::SetWindowUserData(window_, this);
}
NotifyIconHost::~NotifyIconHost() {
if (window_)
DestroyWindow(window_);
if (atom_)
UnregisterClass(MAKEINTATOM(atom_), instance_);
NotifyIcons copied_container(notify_icons_);
STLDeleteContainerPointers(copied_container.begin(), copied_container.end());
}
NotifyIcon* NotifyIconHost::CreateNotifyIcon() {
NotifyIcon* notify_icon =
new NotifyIcon(this, NextIconId(), window_, kNotifyIconMessage);
notify_icons_.push_back(notify_icon);
return notify_icon;
}
void NotifyIconHost::Remove(NotifyIcon* icon) {
NotifyIcons::iterator i(
std::find(notify_icons_.begin(), notify_icons_.end(), icon));
if (i == notify_icons_.end()) {
NOTREACHED();
return;
}
notify_icons_.erase(i);
}
void NotifyIconHost::UpdateIconVisibilityInBackground(
NotifyIcon* notify_icon) {
if (!state_changer_proxy_.get())
state_changer_proxy_.reset(new NotifyIconHostStateChangerProxyImpl);
state_changer_proxy_->EnqueueChange(notify_icon->icon_id(),
notify_icon->window());
}
LRESULT CALLBACK NotifyIconHost::WndProcStatic(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
NotifyIconHost* msg_wnd = reinterpret_cast<NotifyIconHost*>(
GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (msg_wnd)
return msg_wnd->WndProc(hwnd, message, wparam, lparam);
else
return ::DefWindowProc(hwnd, message, wparam, lparam);
}
LRESULT CALLBACK NotifyIconHost::WndProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
if (message == taskbar_created_message_) {
// We need to reset all of our icons because the taskbar went away.
for (NotifyIcons::const_iterator i(notify_icons_.begin());
i != notify_icons_.end(); ++i) {
NotifyIcon* win_icon = static_cast<NotifyIcon*>(*i);
win_icon->ResetIcon();
}
return TRUE;
} else if (message == kNotifyIconMessage) {
NotifyIcon* win_icon = NULL;
// Find the selected status icon.
for (NotifyIcons::const_iterator i(notify_icons_.begin());
i != notify_icons_.end(); ++i) {
NotifyIcon* current_win_icon = static_cast<NotifyIcon*>(*i);
if (current_win_icon->icon_id() == wparam) {
win_icon = current_win_icon;
break;
}
}
// It is possible for this procedure to be called with an obsolete icon
// id. In that case we should just return early before handling any
// actions.
if (!win_icon)
return TRUE;
switch (lparam) {
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_CONTEXTMENU:
// Walk our icons, find which one was clicked on, and invoke its
// HandleClickEvent() method.
gfx::Point cursor_pos(
gfx::Screen::GetNativeScreen()->GetCursorScreenPoint());
win_icon->HandleClickEvent(cursor_pos, lparam == WM_LBUTTONDOWN);
return TRUE;
}
}
return ::DefWindowProc(hwnd, message, wparam, lparam);
}
UINT NotifyIconHost::NextIconId() {
UINT icon_id = next_icon_id_++;
return kBaseIconId + icon_id;
}
} // namespace atom

View file

@ -0,0 +1,79 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_UI_WIN_NOTIFY_ICON_HOST_H_
#define ATOM_BROWSER_UI_WIN_NOTIFY_ICON_HOST_H_
#include <windows.h>
#include <vector>
#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
namespace atom {
class NotifyIcon;
// A class that's responsible for increasing, if possible, the visibility
// of a status tray icon on the taskbar. The default implementation sends
// a task to a worker thread each time EnqueueChange is called.
class NotifyIconHostStateChangerProxy {
public:
// Called by NotifyIconHost to request upgraded visibility on the icon
// represented by the |icon_id|, |window| pair.
virtual void EnqueueChange(UINT icon_id, HWND window) = 0;
};
class NotifyIconHost {
public:
NotifyIconHost();
~NotifyIconHost();
NotifyIcon* CreateNotifyIcon();
void Remove(NotifyIcon* notify_icon);
void UpdateIconVisibilityInBackground(NotifyIcon* notify_icon);
private:
typedef std::vector<NotifyIcon*> NotifyIcons;
// Static callback invoked when a message comes in to our messaging window.
static LRESULT CALLBACK
WndProcStatic(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
LRESULT CALLBACK
WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
UINT NextIconId();
// The unique icon ID we will assign to the next icon.
UINT next_icon_id_;
// List containing all active NotifyIcons.
NotifyIcons notify_icons_;
// The window class of |window_|.
ATOM atom_;
// The handle of the module that contains the window procedure of |window_|.
HMODULE instance_;
// The window used for processing events.
HWND window_;
// The message ID of the "TaskbarCreated" message, sent to us when we need to
// reset our status icons.
UINT taskbar_created_message_;
// Manages changes performed on a background thread to manipulate visibility
// of notification icons.
scoped_ptr<NotifyIconHostStateChangerProxy> state_changer_proxy_;
DISALLOW_COPY_AND_ASSIGN(NotifyIconHost);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_WIN_NOTIFY_ICON_HOST_H_

View file

@ -15,6 +15,7 @@ NODE_EXT_LIST_ITEM(atom_browser_dialog)
NODE_EXT_LIST_ITEM(atom_browser_menu)
NODE_EXT_LIST_ITEM(atom_browser_power_monitor)
NODE_EXT_LIST_ITEM(atom_browser_protocol)
NODE_EXT_LIST_ITEM(atom_browser_tray)
NODE_EXT_LIST_ITEM(atom_browser_window)
// Module names start with `atom_renderer_` can only be used by renderer

View file

@ -0,0 +1,45 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/common/native_mate_converters/image_converter.h"
#include <string>
#include "atom/common/native_mate_converters/file_path_converter.h"
#include "base/file_util.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image_skia.h"
namespace mate {
bool Converter<gfx::ImageSkia>::FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
gfx::ImageSkia* out) {
base::FilePath path;
if (Converter<base::FilePath>::FromV8(isolate, val, &path)) {
std::string file_contents;
if (!base::ReadFileToString(path, &file_contents))
return false;
const unsigned char* data =
reinterpret_cast<const unsigned char*>(file_contents.data());
size_t size = file_contents.size();
scoped_ptr<SkBitmap> decoded(new SkBitmap());
// Try PNG first.
if (!gfx::PNGCodec::Decode(data, size, decoded.get()))
// Try JPEG.
decoded.reset(gfx::JPEGCodec::Decode(data, size));
if (decoded) {
*out = gfx::ImageSkia::CreateFrom1xBitmap(*decoded.release());
return true;
}
}
return false;
}
} // namespace mate

View file

@ -0,0 +1,25 @@
// Copyright (c) 2014 GitHub, Inc. All rights reserved.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_IMAGE_CONVERTER_H_
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_IMAGE_CONVERTER_H_
#include "native_mate/converter.h"
namespace gfx {
class ImageSkia;
}
namespace mate {
template<>
struct Converter<gfx::ImageSkia> {
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
gfx::ImageSkia* out);
};
} // namespace mate
#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_IMAGE_CONVERTER_H_

View file

@ -0,0 +1,236 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h"
namespace {
////////////////////////////////////////////////////////////////////////////////
// Status Tray API
// The folowing describes the interface to the undocumented Windows Exporer APIs
// for manipulating with the status tray area. This code should be used with
// care as it can change with versions (even minor versions) of Windows.
// ITrayNotify is an interface describing the API for manipulating the state of
// the Windows notification area, as well as for registering for change
// notifications.
class __declspec(uuid("FB852B2C-6BAD-4605-9551-F15F87830935")) ITrayNotify
: public IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE
RegisterCallback(INotificationCB* callback) = 0;
virtual HRESULT STDMETHODCALLTYPE
SetPreference(const NOTIFYITEM* notify_item) = 0;
virtual HRESULT STDMETHODCALLTYPE EnableAutoTray(BOOL enabled) = 0;
};
// ITrayNotifyWin8 is the interface that replaces ITrayNotify for newer versions
// of Windows.
class __declspec(uuid("D133CE13-3537-48BA-93A7-AFCD5D2053B4")) ITrayNotifyWin8
: public IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE
RegisterCallback(INotificationCB* callback, unsigned long*) = 0;
virtual HRESULT STDMETHODCALLTYPE UnregisterCallback(unsigned long*) = 0;
virtual HRESULT STDMETHODCALLTYPE SetPreference(NOTIFYITEM const*) = 0;
virtual HRESULT STDMETHODCALLTYPE EnableAutoTray(BOOL) = 0;
virtual HRESULT STDMETHODCALLTYPE DoAction(BOOL) = 0;
};
const CLSID CLSID_TrayNotify = {
0x25DEAD04,
0x1EAC,
0x4911,
{0x9E, 0x3A, 0xAD, 0x0A, 0x4A, 0xB5, 0x60, 0xFD}};
} // namespace
StatusTrayStateChangerWin::StatusTrayStateChangerWin(UINT icon_id, HWND window)
: interface_version_(INTERFACE_VERSION_UNKNOWN),
icon_id_(icon_id),
window_(window) {
wchar_t module_name[MAX_PATH];
::GetModuleFileName(NULL, module_name, MAX_PATH);
file_name_ = module_name;
}
void StatusTrayStateChangerWin::EnsureTrayIconVisible() {
DCHECK(CalledOnValidThread());
if (!CreateTrayNotify()) {
VLOG(1) << "Unable to create COM object for ITrayNotify.";
return;
}
scoped_ptr<NOTIFYITEM> notify_item = RegisterCallback();
// If the user has already hidden us explicitly, try to honor their choice by
// not changing anything.
if (notify_item->preference == PREFERENCE_SHOW_NEVER)
return;
// If we are already on the taskbar, return since nothing needs to be done.
if (notify_item->preference == PREFERENCE_SHOW_ALWAYS)
return;
notify_item->preference = PREFERENCE_SHOW_ALWAYS;
SendNotifyItemUpdate(notify_item.Pass());
}
STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::AddRef() {
DCHECK(CalledOnValidThread());
return base::win::IUnknownImpl::AddRef();
}
STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::Release() {
DCHECK(CalledOnValidThread());
return base::win::IUnknownImpl::Release();
}
STDMETHODIMP StatusTrayStateChangerWin::QueryInterface(REFIID riid,
PVOID* ptr_void) {
DCHECK(CalledOnValidThread());
if (riid == __uuidof(INotificationCB)) {
*ptr_void = static_cast<INotificationCB*>(this);
AddRef();
return S_OK;
}
return base::win::IUnknownImpl::QueryInterface(riid, ptr_void);
}
STDMETHODIMP StatusTrayStateChangerWin::Notify(ULONG event,
NOTIFYITEM* notify_item) {
DCHECK(CalledOnValidThread());
DCHECK(notify_item);
if (notify_item->hwnd != window_ || notify_item->id != icon_id_ ||
base::string16(notify_item->exe_name) != file_name_) {
return S_OK;
}
notify_item_.reset(new NOTIFYITEM(*notify_item));
return S_OK;
}
StatusTrayStateChangerWin::~StatusTrayStateChangerWin() {
DCHECK(CalledOnValidThread());
}
bool StatusTrayStateChangerWin::CreateTrayNotify() {
DCHECK(CalledOnValidThread());
tray_notify_.Release(); // Release so this method can be called more than
// once.
HRESULT hr = tray_notify_.CreateInstance(CLSID_TrayNotify);
if (FAILED(hr))
return false;
base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;
hr = tray_notify_win8.QueryFrom(tray_notify_);
if (SUCCEEDED(hr)) {
interface_version_ = INTERFACE_VERSION_WIN8;
return true;
}
base::win::ScopedComPtr<ITrayNotify> tray_notify_legacy;
hr = tray_notify_legacy.QueryFrom(tray_notify_);
if (SUCCEEDED(hr)) {
interface_version_ = INTERFACE_VERSION_LEGACY;
return true;
}
return false;
}
scoped_ptr<NOTIFYITEM> StatusTrayStateChangerWin::RegisterCallback() {
// |notify_item_| is used to store the result of the callback from
// Explorer.exe, which happens synchronously during
// RegisterCallbackWin8 or RegisterCallbackLegacy.
DCHECK(notify_item_.get() == NULL);
// TODO(dewittj): Add UMA logging here to report if either of our strategies
// has a tendency to fail on particular versions of Windows.
switch (interface_version_) {
case INTERFACE_VERSION_WIN8:
if (!RegisterCallbackWin8())
VLOG(1) << "Unable to successfully run RegisterCallbackWin8.";
break;
case INTERFACE_VERSION_LEGACY:
if (!RegisterCallbackLegacy())
VLOG(1) << "Unable to successfully run RegisterCallbackLegacy.";
break;
default:
NOTREACHED();
}
// Adding an intermediate scoped pointer here so that |notify_item_| is reset
// to NULL.
scoped_ptr<NOTIFYITEM> rv(notify_item_.release());
return rv.Pass();
}
bool StatusTrayStateChangerWin::RegisterCallbackWin8() {
base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;
HRESULT hr = tray_notify_win8.QueryFrom(tray_notify_);
if (FAILED(hr))
return false;
// The following two lines cause Windows Explorer to call us back with all the
// existing tray icons and their preference. It would also presumably notify
// us if changes were made in realtime while we registered as a callback, but
// we just want to modify our own entry so we immediately unregister.
unsigned long callback_id = 0;
hr = tray_notify_win8->RegisterCallback(this, &callback_id);
tray_notify_win8->UnregisterCallback(&callback_id);
if (FAILED(hr)) {
return false;
}
return true;
}
bool StatusTrayStateChangerWin::RegisterCallbackLegacy() {
base::win::ScopedComPtr<ITrayNotify> tray_notify;
HRESULT hr = tray_notify.QueryFrom(tray_notify_);
if (FAILED(hr)) {
return false;
}
// The following two lines cause Windows Explorer to call us back with all the
// existing tray icons and their preference. It would also presumably notify
// us if changes were made in realtime while we registered as a callback. In
// this version of the API, there can be only one registered callback so it is
// better to unregister as soon as possible.
// TODO(dewittj): Try to notice if the notification area icon customization
// window is open and postpone this call until the user closes it;
// registering the callback while the window is open can cause stale data to
// be displayed to the user.
hr = tray_notify->RegisterCallback(this);
tray_notify->RegisterCallback(NULL);
if (FAILED(hr)) {
return false;
}
return true;
}
void StatusTrayStateChangerWin::SendNotifyItemUpdate(
scoped_ptr<NOTIFYITEM> notify_item) {
if (interface_version_ == INTERFACE_VERSION_LEGACY) {
base::win::ScopedComPtr<ITrayNotify> tray_notify;
HRESULT hr = tray_notify.QueryFrom(tray_notify_);
if (SUCCEEDED(hr))
tray_notify->SetPreference(notify_item.get());
} else if (interface_version_ == INTERFACE_VERSION_WIN8) {
base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify;
HRESULT hr = tray_notify.QueryFrom(tray_notify_);
if (SUCCEEDED(hr))
tray_notify->SetPreference(notify_item.get());
}
}

View file

@ -0,0 +1,133 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_
#define CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_
#include "base/memory/scoped_ptr.h"
#include "base/strings/string16.h"
#include "base/threading/non_thread_safe.h"
#include "base/win/iunknown_impl.h"
#include "base/win/scoped_comptr.h"
// The known values for NOTIFYITEM's dwPreference member.
enum NOTIFYITEM_PREFERENCE {
// In Windows UI: "Only show notifications."
PREFERENCE_SHOW_WHEN_ACTIVE = 0,
// In Windows UI: "Hide icon and notifications."
PREFERENCE_SHOW_NEVER = 1,
// In Windows UI: "Show icon and notifications."
PREFERENCE_SHOW_ALWAYS = 2
};
// NOTIFYITEM describes an entry in Explorer's registry of status icons.
// Explorer keeps entries around for a process even after it exits.
struct NOTIFYITEM {
PWSTR exe_name; // The file name of the creating executable.
PWSTR tip; // The last hover-text value associated with this status
// item.
HICON icon; // The icon associated with this status item.
HWND hwnd; // The HWND associated with the status item.
DWORD preference; // Determines the behavior of the icon with respect to
// the taskbar. Values taken from NOTIFYITEM_PREFERENCE.
UINT id; // The ID specified by the application. (hWnd, uID) is
// unique.
GUID guid; // The GUID specified by the application, alternative to
// uID.
};
// INotificationCB is an interface that applications can implement in order to
// receive notifications about the state of the notification area manager.
class __declspec(uuid("D782CCBA-AFB0-43F1-94DB-FDA3779EACCB")) INotificationCB
: public IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE
Notify(ULONG event, NOTIFYITEM* notify_item) = 0;
};
// A class that is capable of reading and writing the state of the notification
// area in the Windows taskbar. It is used to promote a tray icon from the
// overflow area to the taskbar, and refuses to do anything if the user has
// explicitly marked an icon to be always hidden.
class StatusTrayStateChangerWin : public INotificationCB,
public base::win::IUnknownImpl,
public base::NonThreadSafe {
public:
StatusTrayStateChangerWin(UINT icon_id, HWND window);
// Call this method to move the icon matching |icon_id| and |window| to the
// taskbar from the overflow area. This will not make any changes if the
// icon has been set to |PREFERENCE_SHOW_NEVER|, in order to comply with
// the explicit wishes/configuration of the user.
void EnsureTrayIconVisible();
// IUnknown.
virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE;
virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE;
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, PVOID*) OVERRIDE;
// INotificationCB.
// Notify is called in response to RegisterCallback for each current
// entry in Explorer's list of notification area icons, and ever time
// one of them changes, until UnregisterCallback is called or |this|
// is destroyed.
virtual HRESULT STDMETHODCALLTYPE Notify(ULONG, NOTIFYITEM*);
protected:
virtual ~StatusTrayStateChangerWin();
private:
friend class StatusTrayStateChangerWinTest;
enum InterfaceVersion {
INTERFACE_VERSION_LEGACY = 0,
INTERFACE_VERSION_WIN8,
INTERFACE_VERSION_UNKNOWN
};
// Creates an instance of TrayNotify, and ensures that it supports either
// ITrayNotify or ITrayNotifyWin8. Returns true on success.
bool CreateTrayNotify();
// Returns the NOTIFYITEM that corresponds to this executable and the
// HWND/ID pair that were used to create the StatusTrayStateChangerWin.
// Internally it calls the appropriate RegisterCallback{Win8,Legacy}.
scoped_ptr<NOTIFYITEM> RegisterCallback();
// Calls RegisterCallback with the appropriate interface required by
// different versions of Windows. This will result in |notify_item_| being
// updated when a matching item is passed into
// StatusTrayStateChangerWin::Notify.
bool RegisterCallbackWin8();
bool RegisterCallbackLegacy();
// Sends an update to Explorer with the passed NOTIFYITEM.
void SendNotifyItemUpdate(scoped_ptr<NOTIFYITEM> notify_item);
// Storing IUnknown since we will need to use different interfaces
// for different versions of Windows.
base::win::ScopedComPtr<IUnknown> tray_notify_;
InterfaceVersion interface_version_;
// The ID assigned to the notification area icon that we want to manipulate.
const UINT icon_id_;
// The HWND associated with the notification area icon that we want to
// manipulate. This is an unretained pointer, do not dereference.
const HWND window_;
// Executable name of the current program. Along with |icon_id_| and
// |window_|, this uniquely identifies a notification area entry to Explorer.
base::string16 file_name_;
// Temporary storage for the matched NOTIFYITEM. This is necessary because
// Notify doesn't return anything. The call flow looks like this:
// TrayNotify->RegisterCallback()
// ... other COM stack frames ..
// StatusTrayStateChangerWin->Notify(NOTIFYITEM);
// so we can't just return the notifyitem we're looking for.
scoped_ptr<NOTIFYITEM> notify_item_;
DISALLOW_COPY_AND_ASSIGN(StatusTrayStateChangerWin);
};
#endif // CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_

View file

@ -20,6 +20,7 @@ Modules for browser side:
* [menu-item](api/menu-item.md)
* [power-monitor](api/power-monitor.md)
* [protocol](api/protocol.md)
* [tray](api/tray.md)
Modules for web page:

66
docs/api/tray.md Normal file
View file

@ -0,0 +1,66 @@
# tray
A `Tray` represents an icon in operating system's notification area, it is
usually attached with a context menu.
```javascript
var Menu = require('menu');
var Tray = require('tray');
var appIcon = new Tray('/path/to/my/icon');
var contextMenu = Menu.buildFromTemplate([
{ label: 'Item1', type: 'radio' },
{ label: 'Item2', type: 'radio' },
{ label: 'Item3', type: 'radio', clicked: true },
{ label: 'Item4', type: 'radio' },
]);
appIcon.setToolTip('This is my application.');
appIcon.setContextMenu(contextMenu);
```
__Platform limitations:__
* On OS X `clicked` event will be ignored if the tray icon has context menu.
* On Linux app indicator will be used if it is supported, otherwise
`GtkStatusIcon` will be used instead.
* App indicator will only be showed when it has context menu.
* When app indicator is used on Linux, `clicked` event is ignored.
So if you want to keep exact same behaviors on all platforms, you should not
rely on `clicked` event and always attach a context menu to the tray icon.
## Class: Tray
`Tray` is an [EventEmitter](event-emitter).
### new Tray(image)
* `image` String
Creates a new tray icon associated with the `image`.
### Event: 'clicked'
Emitted when the tray icon is clicked.
### Tray.setImage(image)
* `image` String
Sets the `image` associated with this tray icon.
### Tray.setPressedImage(image)
* `image` String
Sets the `image` associated with this tray icon when pressed.
### Tray.setToolTip(toolTip)
* `toolTip` String
### Tray.setContextMenu(menu)
* `menu` Menu
[event-emitter]: http://nodejs.org/api/events.html#events_class_events_eventemitter