From 80fb79daac95a89d29b6a991fbd288a2a40d0bac Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 30 May 2014 10:31:27 +0800 Subject: [PATCH 01/16] Add dummy implementations for TrayIcon. --- atom.gyp | 8 ++++++ atom/browser/ui/tray_icon.cc | 19 +++++++++++++ atom/browser/ui/tray_icon.h | 45 ++++++++++++++++++++++++++++++ atom/browser/ui/tray_icon_cocoa.h | 29 +++++++++++++++++++ atom/browser/ui/tray_icon_cocoa.mm | 24 ++++++++++++++++ atom/browser/ui/tray_icon_gtk.cc | 24 ++++++++++++++++ atom/browser/ui/tray_icon_gtk.h | 29 +++++++++++++++++++ atom/browser/ui/tray_icon_win.cc | 24 ++++++++++++++++ atom/browser/ui/tray_icon_win.h | 29 +++++++++++++++++++ 9 files changed, 231 insertions(+) create mode 100644 atom/browser/ui/tray_icon.cc create mode 100644 atom/browser/ui/tray_icon.h create mode 100644 atom/browser/ui/tray_icon_cocoa.h create mode 100644 atom/browser/ui/tray_icon_cocoa.mm create mode 100644 atom/browser/ui/tray_icon_gtk.cc create mode 100644 atom/browser/ui/tray_icon_gtk.h create mode 100644 atom/browser/ui/tray_icon_win.cc create mode 100644 atom/browser/ui/tray_icon_win.h diff --git a/atom.gyp b/atom.gyp index 3c95bf0904a..426d9bf0543 100644 --- a/atom.gyp +++ b/atom.gyp @@ -131,6 +131,14 @@ '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_gtk.h', + 'atom/browser/ui/tray_icon_cocoa.h', + 'atom/browser/ui/tray_icon_cocoa.mm', + 'atom/browser/ui/tray_icon_win.cc', + 'atom/browser/ui/tray_icon_win.h', 'atom/browser/ui/win/menu_2.cc', 'atom/browser/ui/win/menu_2.h', 'atom/browser/ui/win/native_menu_win.cc', diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc new file mode 100644 index 00000000000..5b707756d9d --- /dev/null +++ b/atom/browser/ui/tray_icon.cc @@ -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() : model_(NULL) { +} + +TrayIcon::~TrayIcon() { +} + +void TrayIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) { + model_ = menu_model; +} + +} // namespace atom diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h new file mode 100644 index 00000000000..69cdba2ab5a --- /dev/null +++ b/atom/browser/ui/tray_icon.h @@ -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. + +#ifndef ATOM_BROWSER_UI_TRAY_ICON_H_ +#define ATOM_BROWSER_UI_TRAY_ICON_H_ + +#include + +#include "ui/base/models/simple_menu_model.h" + +namespace atom { + +class TrayIcon { + public: + static TrayIcon* Create(); + + // 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. + void SetContextMenu(ui::SimpleMenuModel* menu_model); + + protected: + TrayIcon(); + virtual ~TrayIcon(); + + private: + ui::SimpleMenuModel* model_; // Weak reference. + + DISALLOW_COPY_AND_ASSIGN(TrayIcon); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_TRAY_ICON_H_ diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h new file mode 100644 index 00000000000..73da462351c --- /dev/null +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -0,0 +1,29 @@ +// 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_ + +#include + +#include "atom/browser/ui/tray_icon.h" + +namespace atom { + +class TrayIconCocoa : public TrayIcon { + public: + 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; + + private: + TrayIconCocoa(); + virtual ~TrayIconCocoa(); + + DISALLOW_COPY_AND_ASSIGN(TrayIconCocoa); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_ diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm new file mode 100644 index 00000000000..ff3aa469bfb --- /dev/null +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -0,0 +1,24 @@ +// 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" + +namespace atom { + +TrayIconCocoa::TrayIconCocoa() { +} + +TrayIconCocoa::~TrayIconCocoa() { +} + +void TrayIconCocoa::SetImage(const gfx::ImageSkia& image) { +} + +void TrayIconCocoa::SetPressedImage(const gfx::ImageSkia& image) { +} + +void TrayIconCocoa::SetToolTip(const std::string& tool_tip) { +} + +} // namespace atom diff --git a/atom/browser/ui/tray_icon_gtk.cc b/atom/browser/ui/tray_icon_gtk.cc new file mode 100644 index 00000000000..1a61b2f462a --- /dev/null +++ b/atom/browser/ui/tray_icon_gtk.cc @@ -0,0 +1,24 @@ +// 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_gtk.h" + +namespace atom { + +TrayIconGtk::TrayIconGtk() { +} + +TrayIconGtk::~TrayIconGtk() { +} + +void TrayIconGtk::SetImage(const gfx::ImageSkia& image) { +} + +void TrayIconGtk::SetPressedImage(const gfx::ImageSkia& image) { +} + +void TrayIconGtk::SetToolTip(const std::string& tool_tip) { +} + +} // namespace atom diff --git a/atom/browser/ui/tray_icon_gtk.h b/atom/browser/ui/tray_icon_gtk.h new file mode 100644 index 00000000000..b4845ad7810 --- /dev/null +++ b/atom/browser/ui/tray_icon_gtk.h @@ -0,0 +1,29 @@ +// 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_GTK_H_ +#define ATOM_BROWSER_UI_TRAY_ICON_GTK_H_ + +#include + +#include "atom/browser/ui/tray_icon.h" + +namespace atom { + +class TrayIconGtk : public TrayIcon { + public: + 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; + + private: + TrayIconGtk(); + virtual ~TrayIconGtk(); + + DISALLOW_COPY_AND_ASSIGN(TrayIconGtk); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_TRAY_ICON_GTK_H_ diff --git a/atom/browser/ui/tray_icon_win.cc b/atom/browser/ui/tray_icon_win.cc new file mode 100644 index 00000000000..21bf2f6ad29 --- /dev/null +++ b/atom/browser/ui/tray_icon_win.cc @@ -0,0 +1,24 @@ +// 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_win.h" + +namespace atom { + +TrayIconWin::TrayIconWin() { +} + +TrayIconWin::~TrayIconWin() { +} + +void TrayIconWin::SetImage(const gfx::ImageSkia& image) { +} + +void TrayIconWin::SetPressedImage(const gfx::ImageSkia& image) { +} + +void TrayIconWin::SetToolTip(const std::string& tool_tip) { +} + +} // namespace atom diff --git a/atom/browser/ui/tray_icon_win.h b/atom/browser/ui/tray_icon_win.h new file mode 100644 index 00000000000..4724e49b434 --- /dev/null +++ b/atom/browser/ui/tray_icon_win.h @@ -0,0 +1,29 @@ +// 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 + +#include "atom/browser/ui/tray_icon.h" + +namespace atom { + +class TrayIconWin : public TrayIcon { + public: + 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; + + private: + TrayIconWin(); + virtual ~TrayIconWin(); + + DISALLOW_COPY_AND_ASSIGN(TrayIconWin); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_TRAY_ICON_WIN_H_ From 6f5184f001199d40734021fabd8fb12b47f10213 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 30 May 2014 14:37:53 +0800 Subject: [PATCH 02/16] mac: Simple implementation of tray icon. --- atom/browser/ui/tray_icon.cc | 6 +----- atom/browser/ui/tray_icon.h | 4 +--- atom/browser/ui/tray_icon_cocoa.h | 11 +++++++++++ atom/browser/ui/tray_icon_cocoa.mm | 25 +++++++++++++++++++++++++ atom/browser/ui/tray_icon_gtk.cc | 3 +++ atom/browser/ui/tray_icon_gtk.h | 1 + atom/browser/ui/tray_icon_win.cc | 3 +++ atom/browser/ui/tray_icon_win.h | 1 + 8 files changed, 46 insertions(+), 8 deletions(-) diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index 5b707756d9d..c67c9bbcf56 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -6,14 +6,10 @@ namespace atom { -TrayIcon::TrayIcon() : model_(NULL) { +TrayIcon::TrayIcon() { } TrayIcon::~TrayIcon() { } -void TrayIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) { - model_ = menu_model; -} - } // namespace atom diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index 69cdba2ab5a..ed96653bcda 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -28,15 +28,13 @@ class TrayIcon { virtual void SetToolTip(const std::string& tool_tip) = 0; // Set the context menu for this icon. - void SetContextMenu(ui::SimpleMenuModel* menu_model); + virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) = 0; protected: TrayIcon(); virtual ~TrayIcon(); private: - ui::SimpleMenuModel* model_; // Weak reference. - DISALLOW_COPY_AND_ASSIGN(TrayIcon); }; diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index 73da462351c..eb18c15db1e 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -5,9 +5,14 @@ #ifndef ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_ #define ATOM_BROWSER_UI_TRAY_ICON_COCOA_H_ +#import + #include #include "atom/browser/ui/tray_icon.h" +#include "base/mac/scoped_nsobject.h" + +@class AtomMenuController; namespace atom { @@ -16,11 +21,17 @@ class TrayIconCocoa : public 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: TrayIconCocoa(); virtual ~TrayIconCocoa(); + base::scoped_nsobject item_; + + // Status menu shown when right-clicking the system icon. + base::scoped_nsobject menu_; + DISALLOW_COPY_AND_ASSIGN(TrayIconCocoa); }; diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index ff3aa469bfb..ee42c69e44d 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -4,21 +4,46 @@ #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" + namespace atom { TrayIconCocoa::TrayIconCocoa() { + item_.reset([[[NSStatusBar systemStatusBar] + statusItemWithLength:NSVariableStatusItemLength] retain]); + [item_ setEnabled:YES]; + [item_ setHighlightMode:YES]; } TrayIconCocoa::~TrayIconCocoa() { } 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) { + TrayIcon::SetContextMenu(menu_model); + menu_.reset([[AtomMenuController alloc] initWithModel:menu_model]); + [item_ setMenu:[menu_ menu]]; } } // namespace atom diff --git a/atom/browser/ui/tray_icon_gtk.cc b/atom/browser/ui/tray_icon_gtk.cc index 1a61b2f462a..40c5bbbb4ab 100644 --- a/atom/browser/ui/tray_icon_gtk.cc +++ b/atom/browser/ui/tray_icon_gtk.cc @@ -21,4 +21,7 @@ void TrayIconGtk::SetPressedImage(const gfx::ImageSkia& image) { void TrayIconGtk::SetToolTip(const std::string& tool_tip) { } +void TrayIconGtk::SetContextMenu(ui::SimpleMenuModel* menu_model) { +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon_gtk.h b/atom/browser/ui/tray_icon_gtk.h index b4845ad7810..da398e4eee9 100644 --- a/atom/browser/ui/tray_icon_gtk.h +++ b/atom/browser/ui/tray_icon_gtk.h @@ -16,6 +16,7 @@ class TrayIconGtk : public 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: TrayIconGtk(); diff --git a/atom/browser/ui/tray_icon_win.cc b/atom/browser/ui/tray_icon_win.cc index 21bf2f6ad29..0ef5b0fdef7 100644 --- a/atom/browser/ui/tray_icon_win.cc +++ b/atom/browser/ui/tray_icon_win.cc @@ -21,4 +21,7 @@ void TrayIconWin::SetPressedImage(const gfx::ImageSkia& image) { void TrayIconWin::SetToolTip(const std::string& tool_tip) { } +void TrayIconWin::SetContextMenu(ui::SimpleMenuModel* menu_model) { +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon_win.h b/atom/browser/ui/tray_icon_win.h index 4724e49b434..b7077037d40 100644 --- a/atom/browser/ui/tray_icon_win.h +++ b/atom/browser/ui/tray_icon_win.h @@ -16,6 +16,7 @@ class TrayIconWin : public 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: TrayIconWin(); From 52d8d6fdb381037e5b28a7701cd94917917f7ced Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 30 May 2014 23:05:24 +0800 Subject: [PATCH 03/16] Add native_mate converters for ImageSkia. --- .../native_mate_converters/image_converter.cc | 43 +++++++++++++++++++ .../native_mate_converters/image_converter.h | 25 +++++++++++ 2 files changed, 68 insertions(+) create mode 100644 atom/common/native_mate_converters/image_converter.cc create mode 100644 atom/common/native_mate_converters/image_converter.h diff --git a/atom/common/native_mate_converters/image_converter.cc b/atom/common/native_mate_converters/image_converter.cc new file mode 100644 index 00000000000..c0dde067248 --- /dev/null +++ b/atom/common/native_mate_converters/image_converter.cc @@ -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. + +#include "atom/common/native_mate_converters/image_converter.h" + +#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::FromV8(v8::Isolate* isolate, + v8::Handle val, + gfx::ImageSkia* out) { + base::FilePath path; + if (Converter::FromV8(isolate, val, &path)) { + std::string file_contents; + if (!base::ReadFileToString(path, &file_contents)) + return false; + + const unsigned char* data = + reinterpret_cast(file_contents.data()); + size_t size = file_contents.size(); + scoped_ptr 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 diff --git a/atom/common/native_mate_converters/image_converter.h b/atom/common/native_mate_converters/image_converter.h new file mode 100644 index 00000000000..c5032a8729d --- /dev/null +++ b/atom/common/native_mate_converters/image_converter.h @@ -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 { + static bool FromV8(v8::Isolate* isolate, + v8::Handle val, + gfx::ImageSkia* out); +}; + +} // namespace mate + +#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_IMAGE_CONVERTER_H_ From 6c7fe80ec5846bd17e5ff29e05a5061f1d110bec Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Fri, 30 May 2014 23:57:54 +0800 Subject: [PATCH 04/16] Bind TrayIcon to JS. --- atom.gyp | 5 ++ atom/browser/api/atom_api_menu.h | 2 + atom/browser/api/atom_api_tray.cc | 76 ++++++++++++++++++++++++++++++ atom/browser/api/atom_api_tray.h | 49 +++++++++++++++++++ atom/browser/api/lib/tray.coffee | 10 ++++ atom/browser/ui/tray_icon.h | 3 +- atom/browser/ui/tray_icon_cocoa.h | 6 +-- atom/browser/ui/tray_icon_cocoa.mm | 6 ++- atom/browser/ui/tray_icon_gtk.cc | 5 ++ atom/browser/ui/tray_icon_gtk.h | 6 +-- atom/browser/ui/tray_icon_win.cc | 5 ++ atom/browser/ui/tray_icon_win.h | 6 +-- atom/common/api/atom_extensions.h | 1 + 13 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 atom/browser/api/atom_api_tray.cc create mode 100644 atom/browser/api/atom_api_tray.h create mode 100644 atom/browser/api/lib/tray.coffee diff --git a/atom.gyp b/atom.gyp index 426d9bf0543..3e011363245 100644 --- a/atom.gyp +++ b/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', @@ -184,6 +187,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', diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index a309e1b1eb4..2a212d0145a 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -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(); diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc new file mode 100644 index 00000000000..41e1d180be3 --- /dev/null +++ b/atom/browser/api/atom_api_tray.cc @@ -0,0 +1,76 @@ +// 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 "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::~Tray() { +} + +// static +mate::Wrappable* Tray::New(const gfx::ImageSkia& image) { + return new Tray(image); +} + +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 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 exports) { + using atom::api::Tray; + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + v8::Handle constructor = mate::CreateConstructor( + isolate, "Tray", base::Bind(&Tray::New)); + mate::Dictionary dict(isolate, exports); + dict.Set("Tray", static_cast>(constructor)); +} + +} // namespace + +NODE_MODULE(atom_browser_tray, Initialize) diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h new file mode 100644 index 00000000000..1ea95d92a3e --- /dev/null +++ b/atom/browser/api/atom_api_tray.h @@ -0,0 +1,49 @@ +// 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 "atom/browser/api/event_emitter.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: + static mate::Wrappable* New(const gfx::ImageSkia& image); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Handle prototype); + + protected: + Tray(const gfx::ImageSkia& image); + virtual ~Tray(); + + 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 tray_icon_; + + DISALLOW_COPY_AND_ASSIGN(Tray); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_TRAY_H_ diff --git a/atom/browser/api/lib/tray.coffee b/atom/browser/api/lib/tray.coffee new file mode 100644 index 00000000000..7d158a9a010 --- /dev/null +++ b/atom/browser/api/lib/tray.coffee @@ -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 diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index ed96653bcda..67f3150d40e 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -15,6 +15,8 @@ class TrayIcon { public: static TrayIcon* Create(); + virtual ~TrayIcon(); + // Sets the image associated with this status icon. virtual void SetImage(const gfx::ImageSkia& image) = 0; @@ -32,7 +34,6 @@ class TrayIcon { protected: TrayIcon(); - virtual ~TrayIcon(); private: DISALLOW_COPY_AND_ASSIGN(TrayIcon); diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index eb18c15db1e..c030a1f9844 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -18,15 +18,15 @@ 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: - TrayIconCocoa(); - virtual ~TrayIconCocoa(); - base::scoped_nsobject item_; // Status menu shown when right-clicking the system icon. diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index ee42c69e44d..f490015c97e 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -41,9 +41,13 @@ void TrayIconCocoa::SetToolTip(const std::string& tool_tip) { } void TrayIconCocoa::SetContextMenu(ui::SimpleMenuModel* menu_model) { - TrayIcon::SetContextMenu(menu_model); menu_.reset([[AtomMenuController alloc] initWithModel:menu_model]); [item_ setMenu:[menu_ menu]]; } +// static +TrayIcon* TrayIcon::Create() { + return new TrayIconCocoa; +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon_gtk.cc b/atom/browser/ui/tray_icon_gtk.cc index 40c5bbbb4ab..9069c0749b3 100644 --- a/atom/browser/ui/tray_icon_gtk.cc +++ b/atom/browser/ui/tray_icon_gtk.cc @@ -24,4 +24,9 @@ void TrayIconGtk::SetToolTip(const std::string& tool_tip) { void TrayIconGtk::SetContextMenu(ui::SimpleMenuModel* menu_model) { } +// static +TrayIcon* TrayIcon::Create() { + return new TrayIconGtk; +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon_gtk.h b/atom/browser/ui/tray_icon_gtk.h index da398e4eee9..cc66819cab2 100644 --- a/atom/browser/ui/tray_icon_gtk.h +++ b/atom/browser/ui/tray_icon_gtk.h @@ -13,15 +13,15 @@ namespace atom { class TrayIconGtk : public TrayIcon { public: + TrayIconGtk(); + virtual ~TrayIconGtk(); + 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: - TrayIconGtk(); - virtual ~TrayIconGtk(); - DISALLOW_COPY_AND_ASSIGN(TrayIconGtk); }; diff --git a/atom/browser/ui/tray_icon_win.cc b/atom/browser/ui/tray_icon_win.cc index 0ef5b0fdef7..3c8e36ebc91 100644 --- a/atom/browser/ui/tray_icon_win.cc +++ b/atom/browser/ui/tray_icon_win.cc @@ -24,4 +24,9 @@ void TrayIconWin::SetToolTip(const std::string& tool_tip) { void TrayIconWin::SetContextMenu(ui::SimpleMenuModel* menu_model) { } +// static +TrayIcon* TrayIcon::Create() { + return new TrayIconWin; +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon_win.h b/atom/browser/ui/tray_icon_win.h index b7077037d40..dcff1c48079 100644 --- a/atom/browser/ui/tray_icon_win.h +++ b/atom/browser/ui/tray_icon_win.h @@ -13,15 +13,15 @@ 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: - TrayIconWin(); - virtual ~TrayIconWin(); - DISALLOW_COPY_AND_ASSIGN(TrayIconWin); }; diff --git a/atom/common/api/atom_extensions.h b/atom/common/api/atom_extensions.h index 6d0a9166c19..39a4159c555 100644 --- a/atom/common/api/atom_extensions.h +++ b/atom/common/api/atom_extensions.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 From 065185baead13c81921c713be4e97386fe283254 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sat, 31 May 2014 10:27:07 +0800 Subject: [PATCH 05/16] gtk: Add TrayIcon implementation for GtkStatusIcon. --- atom.gyp | 3 +- atom/browser/ui/gtk/status_icon.cc | 51 ++++++++++++++++++++++++++++++ atom/browser/ui/gtk/status_icon.h | 44 ++++++++++++++++++++++++++ atom/browser/ui/tray_icon_gtk.cc | 22 ++----------- atom/browser/ui/tray_icon_gtk.h | 30 ------------------ 5 files changed, 99 insertions(+), 51 deletions(-) create mode 100644 atom/browser/ui/gtk/status_icon.cc create mode 100644 atom/browser/ui/gtk/status_icon.h delete mode 100644 atom/browser/ui/tray_icon_gtk.h diff --git a/atom.gyp b/atom.gyp index 3e011363245..dd41d5c48ea 100644 --- a/atom.gyp +++ b/atom.gyp @@ -130,6 +130,8 @@ '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/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', @@ -137,7 +139,6 @@ 'atom/browser/ui/tray_icon.cc', 'atom/browser/ui/tray_icon.h', 'atom/browser/ui/tray_icon_gtk.cc', - 'atom/browser/ui/tray_icon_gtk.h', 'atom/browser/ui/tray_icon_cocoa.h', 'atom/browser/ui/tray_icon_cocoa.mm', 'atom/browser/ui/tray_icon_win.cc', diff --git a/atom/browser/ui/gtk/status_icon.cc b/atom/browser/ui/gtk/status_icon.cc new file mode 100644 index 00000000000..3de2a8f6c58 --- /dev/null +++ b/atom/browser/ui/gtk/status_icon.cc @@ -0,0 +1,51 @@ +// 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_, "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_); +} + +} // namespace atom diff --git a/atom/browser/ui/gtk/status_icon.h b/atom/browser/ui/gtk/status_icon.h new file mode 100644 index 00000000000..2e35744391a --- /dev/null +++ b/atom/browser/ui/gtk/status_icon.h @@ -0,0 +1,44 @@ +// 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 + +#include + +#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); + + // The currently-displayed icon for the window. + GtkStatusIcon* icon_; + + // The context menu for this icon (if any). + scoped_ptr menu_; + + DISALLOW_COPY_AND_ASSIGN(StatusIcon); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_GTK_STATUS_ICON_H_ diff --git a/atom/browser/ui/tray_icon_gtk.cc b/atom/browser/ui/tray_icon_gtk.cc index 9069c0749b3..ada6cef3420 100644 --- a/atom/browser/ui/tray_icon_gtk.cc +++ b/atom/browser/ui/tray_icon_gtk.cc @@ -2,31 +2,13 @@ // 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_gtk.h" +#include "atom/browser/ui/gtk/status_icon.h" namespace atom { -TrayIconGtk::TrayIconGtk() { -} - -TrayIconGtk::~TrayIconGtk() { -} - -void TrayIconGtk::SetImage(const gfx::ImageSkia& image) { -} - -void TrayIconGtk::SetPressedImage(const gfx::ImageSkia& image) { -} - -void TrayIconGtk::SetToolTip(const std::string& tool_tip) { -} - -void TrayIconGtk::SetContextMenu(ui::SimpleMenuModel* menu_model) { -} - // static TrayIcon* TrayIcon::Create() { - return new TrayIconGtk; + return new StatusIcon; } } // namespace atom diff --git a/atom/browser/ui/tray_icon_gtk.h b/atom/browser/ui/tray_icon_gtk.h deleted file mode 100644 index cc66819cab2..00000000000 --- a/atom/browser/ui/tray_icon_gtk.h +++ /dev/null @@ -1,30 +0,0 @@ -// 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_GTK_H_ -#define ATOM_BROWSER_UI_TRAY_ICON_GTK_H_ - -#include - -#include "atom/browser/ui/tray_icon.h" - -namespace atom { - -class TrayIconGtk : public TrayIcon { - public: - TrayIconGtk(); - virtual ~TrayIconGtk(); - - 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(TrayIconGtk); -}; - -} // namespace atom - -#endif // ATOM_BROWSER_UI_TRAY_ICON_GTK_H_ From 9699dbb71f29ef239e5ddfbc05efe0260ea8fbbb Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Sun, 1 Jun 2014 10:20:06 +0800 Subject: [PATCH 06/16] gtk: Add app indicator support as tray icon. --- atom.gyp | 2 + atom/browser/api/atom_api_tray.cc | 2 + atom/browser/api/atom_api_tray.h | 4 +- atom/browser/ui/gtk/app_indicator_icon.cc | 258 ++++++++++++++++++ atom/browser/ui/gtk/app_indicator_icon.h | 55 ++++ atom/browser/ui/gtk/status_icon.h | 4 +- atom/browser/ui/tray_icon_gtk.cc | 6 +- .../native_mate_converters/image_converter.cc | 2 + 8 files changed, 329 insertions(+), 4 deletions(-) create mode 100644 atom/browser/ui/gtk/app_indicator_icon.cc create mode 100644 atom/browser/ui/gtk/app_indicator_icon.h diff --git a/atom.gyp b/atom.gyp index dd41d5c48ea..a00a487fb4f 100644 --- a/atom.gyp +++ b/atom.gyp @@ -130,6 +130,8 @@ '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', diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 41e1d180be3..51fb2d95457 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -4,6 +4,8 @@ #include "atom/browser/api/atom_api_tray.h" +#include + #include "atom/browser/api/atom_api_menu.h" #include "atom/browser/ui/tray_icon.h" #include "atom/common/native_mate_converters/image_converter.h" diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index 1ea95d92a3e..8040f0f9975 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -5,6 +5,8 @@ #ifndef ATOM_BROWSER_API_ATOM_API_TRAY_H_ #define ATOM_BROWSER_API_ATOM_API_TRAY_H_ +#include + #include "atom/browser/api/event_emitter.h" #include "base/memory/scoped_ptr.h" @@ -28,7 +30,7 @@ class Tray : public mate::EventEmitter { v8::Handle prototype); protected: - Tray(const gfx::ImageSkia& image); + explicit Tray(const gfx::ImageSkia& image); virtual ~Tray(); void SetImage(const gfx::ImageSkia& image); diff --git a/atom/browser/ui/gtk/app_indicator_icon.cc b/atom/browser/ui/gtk/app_indicator_icon.cc new file mode 100644 index 00000000000..3b39ea0fc9a --- /dev/null +++ b/atom/browser/ui/gtk/app_indicator_icon.cc @@ -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 +#include + +#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( + dlsym(indicator_lib, "app_indicator_new")); + + app_indicator_new_with_path = + reinterpret_cast( + dlsym(indicator_lib, "app_indicator_new_with_path")); + + app_indicator_set_status = reinterpret_cast( + dlsym(indicator_lib, "app_indicator_set_status")); + + app_indicator_set_attention_icon_full = + reinterpret_cast( + dlsym(indicator_lib, "app_indicator_set_attention_icon_full")); + + app_indicator_set_menu = reinterpret_cast( + dlsym(indicator_lib, "app_indicator_set_menu")); + + app_indicator_set_icon_full = + reinterpret_cast( + dlsym(indicator_lib, "app_indicator_set_icon_full")); + + app_indicator_set_icon_theme_path = + reinterpret_cast( + 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 image(image_ptr); + + scoped_refptr 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(png_data->front()), + png_data->size()); + + if (bytes_written != static_cast(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 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 diff --git a/atom/browser/ui/gtk/app_indicator_icon.h b/atom/browser/ui/gtk/app_indicator_icon.h new file mode 100644 index 00000000000..1799dc1b38e --- /dev/null +++ b/atom/browser/ui/gtk/app_indicator_icon.h @@ -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 + +#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 menu_; + + std::string id_; + base::FilePath icon_file_path_; + int icon_change_count_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(AppIndicatorIcon); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_GTK_APP_INDICATOR_ICON_H_ diff --git a/atom/browser/ui/gtk/status_icon.h b/atom/browser/ui/gtk/status_icon.h index 2e35744391a..60e6dd3566e 100644 --- a/atom/browser/ui/gtk/status_icon.h +++ b/atom/browser/ui/gtk/status_icon.h @@ -5,10 +5,10 @@ #ifndef ATOM_BROWSER_UI_GTK_STATUS_ICON_H_ #define ATOM_BROWSER_UI_GTK_STATUS_ICON_H_ -#include - #include +#include + #include "atom/browser/ui/tray_icon.h" #include "ui/base/gtk/gtk_signal.h" diff --git a/atom/browser/ui/tray_icon_gtk.cc b/atom/browser/ui/tray_icon_gtk.cc index ada6cef3420..fa9e82859cc 100644 --- a/atom/browser/ui/tray_icon_gtk.cc +++ b/atom/browser/ui/tray_icon_gtk.cc @@ -3,12 +3,16 @@ // 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() { - return new StatusIcon; + if (AppIndicatorIcon::CouldOpen()) + return new AppIndicatorIcon; + else + return new StatusIcon; } } // namespace atom diff --git a/atom/common/native_mate_converters/image_converter.cc b/atom/common/native_mate_converters/image_converter.cc index c0dde067248..5564f1e5dfc 100644 --- a/atom/common/native_mate_converters/image_converter.cc +++ b/atom/common/native_mate_converters/image_converter.cc @@ -4,6 +4,8 @@ #include "atom/common/native_mate_converters/image_converter.h" +#include + #include "atom/common/native_mate_converters/file_path_converter.h" #include "base/file_util.h" #include "ui/gfx/codec/jpeg_codec.h" From cbd9366898d4f73775b9258ffcfd691963c6976a Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 2 Jun 2014 11:08:29 +0800 Subject: [PATCH 07/16] Add "click" event for TrayIcon. --- atom.gyp | 1 + atom/browser/api/atom_api_tray.cc | 4 ++++ atom/browser/api/atom_api_tray.h | 7 ++++++- atom/browser/ui/tray_icon.cc | 4 ++++ atom/browser/ui/tray_icon.h | 8 ++++++++ atom/browser/ui/tray_icon_observer.h | 20 ++++++++++++++++++++ 6 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 atom/browser/ui/tray_icon_observer.h diff --git a/atom.gyp b/atom.gyp index a00a487fb4f..d0503227386 100644 --- a/atom.gyp +++ b/atom.gyp @@ -143,6 +143,7 @@ '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/tray_icon_win.h', 'atom/browser/ui/win/menu_2.cc', diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 51fb2d95457..1fc6aa936af 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -31,6 +31,10 @@ 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); } diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index 8040f0f9975..5dce0cbe954 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -8,6 +8,7 @@ #include #include "atom/browser/api/event_emitter.h" +#include "atom/browser/ui/tray_icon_observer.h" #include "base/memory/scoped_ptr.h" namespace gfx { @@ -22,7 +23,8 @@ namespace api { class Menu; -class Tray : public mate::EventEmitter { +class Tray : public mate::EventEmitter, + public TrayIconObserver { public: static mate::Wrappable* New(const gfx::ImageSkia& image); @@ -33,6 +35,9 @@ class Tray : public mate::EventEmitter { 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); diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index c67c9bbcf56..fdbdf7a5395 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -12,4 +12,8 @@ TrayIcon::TrayIcon() { TrayIcon::~TrayIcon() { } +void TrayIcon::NotifyClicked() { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnClicked()); +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index 67f3150d40e..80de248810b 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -7,6 +7,8 @@ #include +#include "atom/browser/ui/tray_icon_observer.h" +#include "base/observer_list.h" #include "ui/base/models/simple_menu_model.h" namespace atom { @@ -32,10 +34,16 @@ class TrayIcon { // 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 observers_; + DISALLOW_COPY_AND_ASSIGN(TrayIcon); }; diff --git a/atom/browser/ui/tray_icon_observer.h b/atom/browser/ui/tray_icon_observer.h new file mode 100644 index 00000000000..621530f7dd0 --- /dev/null +++ b/atom/browser/ui/tray_icon_observer.h @@ -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_ From da31588e0d254b3659887b1e09cf77dd975b6c44 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 2 Jun 2014 11:28:23 +0800 Subject: [PATCH 08/16] mac: Implement the "click" event of TrayIcon. --- atom/browser/api/atom_api_tray.cc | 1 + atom/browser/ui/tray_icon_cocoa.h | 3 +++ atom/browser/ui/tray_icon_cocoa.mm | 28 ++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 1fc6aa936af..49d95402443 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -21,6 +21,7 @@ namespace api { Tray::Tray(const gfx::ImageSkia& image) : tray_icon_(TrayIcon::Create()) { tray_icon_->SetImage(image); + tray_icon_->AddObserver(this); } Tray::~Tray() { diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index c030a1f9844..b6a710ce127 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -13,6 +13,7 @@ #include "base/mac/scoped_nsobject.h" @class AtomMenuController; +@class StatusItemController; namespace atom { @@ -29,6 +30,8 @@ class TrayIconCocoa : public TrayIcon { private: base::scoped_nsobject item_; + base::scoped_nsobject controller_; + // Status menu shown when right-clicking the system icon. base::scoped_nsobject menu_; diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index f490015c97e..03461634378 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -8,16 +8,44 @@ #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) { From 884f30010c4f58b62bf1bebef76d4377ac16d7ae Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 2 Jun 2014 11:47:22 +0800 Subject: [PATCH 09/16] :memo: Document the tray module. --- docs/README.md | 1 + docs/api/tray.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 docs/api/tray.md diff --git a/docs/README.md b/docs/README.md index 82ab7c5228b..69358530fbe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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: diff --git a/docs/api/tray.md b/docs/api/tray.md new file mode 100644 index 00000000000..02350a68c90 --- /dev/null +++ b/docs/api/tray.md @@ -0,0 +1,59 @@ +# 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. + +## 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 From 4647efd22f8d1c71f274e07ac0d1da5b72de9dfd Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 2 Jun 2014 11:47:37 +0800 Subject: [PATCH 10/16] :lipstick: Fix cpplint warning. --- atom/browser/ui/tray_icon_observer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/tray_icon_observer.h b/atom/browser/ui/tray_icon_observer.h index 621530f7dd0..f28bfd6cc9a 100644 --- a/atom/browser/ui/tray_icon_observer.h +++ b/atom/browser/ui/tray_icon_observer.h @@ -9,10 +9,10 @@ namespace atom { class TrayIconObserver { public: - virtual void OnClicked() {}; + virtual void OnClicked() {} protected: - virtual ~TrayIconObserver() {}; + virtual ~TrayIconObserver() {} }; } // namespace atom From 540e6ff01b4d780deec5ef116bd943162e19d293 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 2 Jun 2014 23:10:36 +0800 Subject: [PATCH 11/16] gtk: Implement click event for status icon. --- atom/browser/ui/gtk/status_icon.cc | 5 +++++ atom/browser/ui/gtk/status_icon.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/atom/browser/ui/gtk/status_icon.cc b/atom/browser/ui/gtk/status_icon.cc index 3de2a8f6c58..433e4af13e5 100644 --- a/atom/browser/ui/gtk/status_icon.cc +++ b/atom/browser/ui/gtk/status_icon.cc @@ -12,6 +12,7 @@ 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); } @@ -48,4 +49,8 @@ void StatusIcon::OnPopupMenu(GtkWidget* widget, guint button, guint time) { menu_->PopupAsContextForStatusIcon(time, button, icon_); } +void StatusIcon::OnActivate(GtkWidget* widget) { + NotifyClicked(); +} + } // namespace atom diff --git a/atom/browser/ui/gtk/status_icon.h b/atom/browser/ui/gtk/status_icon.h index 60e6dd3566e..9c8aa1f5abf 100644 --- a/atom/browser/ui/gtk/status_icon.h +++ b/atom/browser/ui/gtk/status_icon.h @@ -30,6 +30,9 @@ class StatusIcon : public TrayIcon { // 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_; From 01e93c108c6dd1d36b4af75e36f45d87416a312e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Mon, 2 Jun 2014 23:18:14 +0800 Subject: [PATCH 12/16] :memo: Mention platform limitations on Linux. --- docs/api/tray.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api/tray.md b/docs/api/tray.md index 02350a68c90..f35fc4bdcf6 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -21,6 +21,13 @@ 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 From 08e7c07c574c9db2e7f11ca8ad40301e5d83b66e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 3 Jun 2014 11:24:40 +0800 Subject: [PATCH 13/16] Add StatusTrayStateChangerWindow from chrome. --- .../status_tray_state_changer_win.cc | 236 ++++++++++++++++++ .../status_tray_state_changer_win.h | 133 ++++++++++ 2 files changed, 369 insertions(+) create mode 100644 chrome/browser/ui/views/status_icons/status_tray_state_changer_win.cc create mode 100644 chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h diff --git a/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.cc b/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.cc new file mode 100644 index 00000000000..410d31db62d --- /dev/null +++ b/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.cc @@ -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 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(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 tray_notify_win8; + hr = tray_notify_win8.QueryFrom(tray_notify_); + if (SUCCEEDED(hr)) { + interface_version_ = INTERFACE_VERSION_WIN8; + return true; + } + + base::win::ScopedComPtr tray_notify_legacy; + hr = tray_notify_legacy.QueryFrom(tray_notify_); + if (SUCCEEDED(hr)) { + interface_version_ = INTERFACE_VERSION_LEGACY; + return true; + } + + return false; +} + +scoped_ptr 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 rv(notify_item_.release()); + return rv.Pass(); +} + +bool StatusTrayStateChangerWin::RegisterCallbackWin8() { + base::win::ScopedComPtr 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 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 notify_item) { + if (interface_version_ == INTERFACE_VERSION_LEGACY) { + base::win::ScopedComPtr 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 tray_notify; + HRESULT hr = tray_notify.QueryFrom(tray_notify_); + if (SUCCEEDED(hr)) + tray_notify->SetPreference(notify_item.get()); + } +} + diff --git a/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h b/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h new file mode 100644 index 00000000000..963792b9807 --- /dev/null +++ b/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h @@ -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 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 notify_item); + + // Storing IUnknown since we will need to use different interfaces + // for different versions of Windows. + base::win::ScopedComPtr 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 notify_item_; + + DISALLOW_COPY_AND_ASSIGN(StatusTrayStateChangerWin); +}; + +#endif // CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_ \ No newline at end of file From 1cb135f7f2a262e385881dd1cd9a695db1303517 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 3 Jun 2014 11:25:09 +0800 Subject: [PATCH 14/16] win: Implement tray icon API. --- atom.gyp | 7 +- atom/browser/ui/tray_icon_win.cc | 24 +-- atom/browser/ui/win/notify_icon.cc | 138 +++++++++++++++ atom/browser/ui/win/notify_icon.h | 74 ++++++++ atom/browser/ui/win/notify_icon_host.cc | 226 ++++++++++++++++++++++++ atom/browser/ui/win/notify_icon_host.h | 79 +++++++++ 6 files changed, 527 insertions(+), 21 deletions(-) create mode 100644 atom/browser/ui/win/notify_icon.cc create mode 100644 atom/browser/ui/win/notify_icon.h create mode 100644 atom/browser/ui/win/notify_icon_host.cc create mode 100644 atom/browser/ui/win/notify_icon_host.h diff --git a/atom.gyp b/atom.gyp index d0503227386..e3075b4d985 100644 --- a/atom.gyp +++ b/atom.gyp @@ -145,11 +145,14 @@ 'atom/browser/ui/tray_icon_cocoa.mm', 'atom/browser/ui/tray_icon_observer.h', 'atom/browser/ui/tray_icon_win.cc', - 'atom/browser/ui/tray_icon_win.h', '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', @@ -232,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': [ diff --git a/atom/browser/ui/tray_icon_win.cc b/atom/browser/ui/tray_icon_win.cc index 3c8e36ebc91..f3b7581c2db 100644 --- a/atom/browser/ui/tray_icon_win.cc +++ b/atom/browser/ui/tray_icon_win.cc @@ -2,31 +2,15 @@ // 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_win.h" +#include "atom/browser/ui/win/notify_icon.h" +#include "atom/browser/ui/win/notify_icon_host.h" namespace atom { -TrayIconWin::TrayIconWin() { -} - -TrayIconWin::~TrayIconWin() { -} - -void TrayIconWin::SetImage(const gfx::ImageSkia& image) { -} - -void TrayIconWin::SetPressedImage(const gfx::ImageSkia& image) { -} - -void TrayIconWin::SetToolTip(const std::string& tool_tip) { -} - -void TrayIconWin::SetContextMenu(ui::SimpleMenuModel* menu_model) { -} - // static TrayIcon* TrayIcon::Create() { - return new TrayIconWin; + static NotifyIconHost host; + return host.CreateNotifyIcon(); } } // namespace atom diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc new file mode 100644 index 00000000000..b44791fe90a --- /dev/null +++ b/atom/browser/ui/win/notify_icon.cc @@ -0,0 +1,138 @@ +// 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 "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) { + 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_runner_.reset(new views::MenuRunner(menu_model_)); + + ignore_result(menu_runner_->RunMenuAt(NULL, + NULL, + gfx::Rect(cursor_pos, gfx::Size()), + views::MENU_ANCHOR_TOPLEFT, + ui::MENU_SOURCE_MOUSE, + views::MenuRunner::HAS_MNEMONICS)); + */ +} + +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) { +} + +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 diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h new file mode 100644 index 00000000000..850b3961b47 --- /dev/null +++ b/atom/browser/ui/win/notify_icon.h @@ -0,0 +1,74 @@ +// 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 +#include + +#include + +#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 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_; + + DISALLOW_COPY_AND_ASSIGN(NotifyIcon); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_NOTIFY_ICON_H_ diff --git a/atom/browser/ui/win/notify_icon_host.cc b/atom/browser/ui/win/notify_icon_host.cc new file mode 100644 index 00000000000..7f2b54113b4 --- /dev/null +++ b/atom/browser/ui/win/notify_icon_host.cc @@ -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 + +#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 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 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, + 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( + 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(*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(*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 diff --git a/atom/browser/ui/win/notify_icon_host.h b/atom/browser/ui/win/notify_icon_host.h new file mode 100644 index 00000000000..c4757ffd971 --- /dev/null +++ b/atom/browser/ui/win/notify_icon_host.h @@ -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 + +#include + +#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 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 state_changer_proxy_; + + DISALLOW_COPY_AND_ASSIGN(NotifyIconHost); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_NOTIFY_ICON_HOST_H_ From 9ef7ecbdaf03fe382ed8d13c467d6b9a7894653e Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 3 Jun 2014 11:52:57 +0800 Subject: [PATCH 15/16] Win: Implement context menu for tray icon. --- atom/browser/ui/win/notify_icon.cc | 17 ++++++----------- atom/browser/ui/win/notify_icon.h | 3 +++ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index b44791fe90a..86ce15d698e 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -4,6 +4,7 @@ #include "atom/browser/ui/win/notify_icon.h" +#include "atom/browser/ui/win/menu_2.h" #include "atom/browser/ui/win/notify_icon_host.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" @@ -22,7 +23,8 @@ NotifyIcon::NotifyIcon(NotifyIconHost* host, : host_(host), icon_id_(id), window_(window), - message_id_(message) { + message_id_(message), + menu_model_(NULL) { NOTIFYICONDATA icon_data; InitIconData(&icon_data); icon_data.uFlags = NIF_MESSAGE; @@ -50,7 +52,6 @@ void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, return; } - /* if (!menu_model_) return; @@ -59,15 +60,8 @@ void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, if (!SetForegroundWindow(window_)) return; - menu_runner_.reset(new views::MenuRunner(menu_model_)); - - ignore_result(menu_runner_->RunMenuAt(NULL, - NULL, - gfx::Rect(cursor_pos, gfx::Size()), - views::MENU_ANCHOR_TOPLEFT, - ui::MENU_SOURCE_MOUSE, - views::MenuRunner::HAS_MNEMONICS)); - */ + scoped_ptr menu(new Menu2(menu_model_)); + menu->RunContextMenuAt(cursor_pos); } void NotifyIcon::ResetIcon() { @@ -120,6 +114,7 @@ void NotifyIcon::SetToolTip(const std::string& tool_tip) { } void NotifyIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) { + menu_model_ = menu_model; } void NotifyIcon::InitIconData(NOTIFYICONDATA* icon_data) { diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h index 850b3961b47..d76960ad492 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -66,6 +66,9 @@ class NotifyIcon : public TrayIcon { // The currently-displayed icon for the window. base::win::ScopedHICON icon_; + // The context menu. + ui::SimpleMenuModel* menu_model_; + DISALLOW_COPY_AND_ASSIGN(NotifyIcon); }; From 53092cfb7cde675c4723a00ab171550b63eb36fe Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 3 Jun 2014 14:11:12 +0800 Subject: [PATCH 16/16] win: Fix context menu clicking in notify icon. --- atom/browser/ui/win/notify_icon.cc | 6 +++--- atom/browser/ui/win/notify_icon.h | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index 86ce15d698e..fddee1cea40 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -4,8 +4,8 @@ #include "atom/browser/ui/win/notify_icon.h" -#include "atom/browser/ui/win/menu_2.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" @@ -60,8 +60,8 @@ void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, if (!SetForegroundWindow(window_)) return; - scoped_ptr menu(new Menu2(menu_model_)); - menu->RunContextMenuAt(cursor_pos); + menu_.reset(new Menu2(menu_model_)); + menu_->RunContextMenuAt(cursor_pos); } void NotifyIcon::ResetIcon() { diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h index d76960ad492..51e854b802e 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -22,6 +22,7 @@ class Point; namespace atom { +class Menu2; class NotifyIconHost; class NotifyIcon : public TrayIcon { @@ -68,6 +69,7 @@ class NotifyIcon : public TrayIcon { // The context menu. ui::SimpleMenuModel* menu_model_; + scoped_ptr menu_; DISALLOW_COPY_AND_ASSIGN(NotifyIcon); };