commit
98aba2aa12
28 changed files with 1892 additions and 0 deletions
22
atom.gyp
22
atom.gyp
|
@ -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': [
|
||||
|
|
|
@ -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();
|
||||
|
|
83
atom/browser/api/atom_api_tray.cc
Normal file
83
atom/browser/api/atom_api_tray.cc
Normal 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)
|
56
atom/browser/api/atom_api_tray.h
Normal file
56
atom/browser/api/atom_api_tray.h
Normal 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_
|
10
atom/browser/api/lib/tray.coffee
Normal file
10
atom/browser/api/lib/tray.coffee
Normal 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
|
258
atom/browser/ui/gtk/app_indicator_icon.cc
Normal file
258
atom/browser/ui/gtk/app_indicator_icon.cc
Normal 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
|
55
atom/browser/ui/gtk/app_indicator_icon.h
Normal file
55
atom/browser/ui/gtk/app_indicator_icon.h
Normal 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_
|
56
atom/browser/ui/gtk/status_icon.cc
Normal file
56
atom/browser/ui/gtk/status_icon.cc
Normal 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
|
47
atom/browser/ui/gtk/status_icon.h
Normal file
47
atom/browser/ui/gtk/status_icon.h
Normal 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_
|
19
atom/browser/ui/tray_icon.cc
Normal file
19
atom/browser/ui/tray_icon.cc
Normal 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
|
52
atom/browser/ui/tray_icon.h
Normal file
52
atom/browser/ui/tray_icon.h
Normal 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_
|
43
atom/browser/ui/tray_icon_cocoa.h
Normal file
43
atom/browser/ui/tray_icon_cocoa.h
Normal 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_
|
81
atom/browser/ui/tray_icon_cocoa.mm
Normal file
81
atom/browser/ui/tray_icon_cocoa.mm
Normal 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
|
18
atom/browser/ui/tray_icon_gtk.cc
Normal file
18
atom/browser/ui/tray_icon_gtk.cc
Normal 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
|
20
atom/browser/ui/tray_icon_observer.h
Normal file
20
atom/browser/ui/tray_icon_observer.h
Normal 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_
|
16
atom/browser/ui/tray_icon_win.cc
Normal file
16
atom/browser/ui/tray_icon_win.cc
Normal 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
|
30
atom/browser/ui/tray_icon_win.h
Normal file
30
atom/browser/ui/tray_icon_win.h
Normal 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_
|
133
atom/browser/ui/win/notify_icon.cc
Normal file
133
atom/browser/ui/win/notify_icon.cc
Normal 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
|
79
atom/browser/ui/win/notify_icon.h
Normal file
79
atom/browser/ui/win/notify_icon.h
Normal 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_
|
226
atom/browser/ui/win/notify_icon_host.cc
Normal file
226
atom/browser/ui/win/notify_icon_host.cc
Normal 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
|
79
atom/browser/ui/win/notify_icon_host.h
Normal file
79
atom/browser/ui/win/notify_icon_host.h
Normal 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_
|
|
@ -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
|
||||
|
|
45
atom/common/native_mate_converters/image_converter.cc
Normal file
45
atom/common/native_mate_converters/image_converter.cc
Normal 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
|
25
atom/common/native_mate_converters/image_converter.h
Normal file
25
atom/common/native_mate_converters/image_converter.h
Normal 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_
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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_
|
|
@ -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
66
docs/api/tray.md
Normal 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
|
Loading…
Reference in a new issue