electron_api_tray.cc 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. // Copyright (c) 2014 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/api/electron_api_tray.h"
  5. #include <string>
  6. #include "base/containers/fixed_flat_map.h"
  7. #include "gin/dictionary.h"
  8. #include "gin/object_template_builder.h"
  9. #include "shell/browser/api/electron_api_menu.h"
  10. #include "shell/browser/api/ui_event.h"
  11. #include "shell/browser/browser.h"
  12. #include "shell/browser/javascript_environment.h"
  13. #include "shell/common/api/electron_api_native_image.h"
  14. #include "shell/common/gin_converters/file_path_converter.h"
  15. #include "shell/common/gin_converters/gfx_converter.h"
  16. #include "shell/common/gin_converters/guid_converter.h"
  17. #include "shell/common/gin_converters/image_converter.h"
  18. #include "shell/common/gin_helper/dictionary.h"
  19. #include "shell/common/gin_helper/function_template_extensions.h"
  20. #include "shell/common/node_includes.h"
  21. #include "ui/gfx/image/image.h"
  22. namespace gin {
  23. template <>
  24. struct Converter<electron::TrayIcon::IconType> {
  25. static bool FromV8(v8::Isolate* isolate,
  26. v8::Local<v8::Value> val,
  27. electron::TrayIcon::IconType* out) {
  28. using Val = electron::TrayIcon::IconType;
  29. static constexpr auto Lookup =
  30. base::MakeFixedFlatMapSorted<base::StringPiece, Val>({
  31. {"custom", Val::kCustom},
  32. {"error", Val::kError},
  33. {"info", Val::kInfo},
  34. {"none", Val::kNone},
  35. {"warning", Val::kWarning},
  36. });
  37. return FromV8WithLookup(isolate, val, Lookup, out);
  38. }
  39. };
  40. } // namespace gin
  41. namespace electron::api {
  42. gin::WrapperInfo Tray::kWrapperInfo = {gin::kEmbedderNativeGin};
  43. Tray::Tray(v8::Isolate* isolate,
  44. v8::Local<v8::Value> image,
  45. absl::optional<UUID> guid)
  46. : tray_icon_(TrayIcon::Create(guid)) {
  47. SetImage(isolate, image);
  48. tray_icon_->AddObserver(this);
  49. }
  50. Tray::~Tray() = default;
  51. // static
  52. gin::Handle<Tray> Tray::New(gin_helper::ErrorThrower thrower,
  53. v8::Local<v8::Value> image,
  54. absl::optional<UUID> guid,
  55. gin::Arguments* args) {
  56. if (!Browser::Get()->is_ready()) {
  57. thrower.ThrowError("Cannot create Tray before app is ready");
  58. return gin::Handle<Tray>();
  59. }
  60. #if BUILDFLAG(IS_WIN)
  61. if (!guid.has_value() && args->Length() > 1) {
  62. thrower.ThrowError("Invalid GUID format");
  63. return gin::Handle<Tray>();
  64. }
  65. #endif
  66. auto handle = gin::CreateHandle(args->isolate(),
  67. new Tray(args->isolate(), image, guid));
  68. handle->Pin(args->isolate());
  69. return handle;
  70. }
  71. void Tray::OnClicked(const gfx::Rect& bounds,
  72. const gfx::Point& location,
  73. int modifiers) {
  74. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  75. v8::HandleScope scope(isolate);
  76. EmitWithoutEvent("click", CreateEventFromFlags(modifiers), bounds, location);
  77. }
  78. void Tray::OnDoubleClicked(const gfx::Rect& bounds, int modifiers) {
  79. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  80. v8::HandleScope scope(isolate);
  81. EmitWithoutEvent("double-click", CreateEventFromFlags(modifiers), bounds);
  82. }
  83. void Tray::OnRightClicked(const gfx::Rect& bounds, int modifiers) {
  84. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  85. v8::HandleScope scope(isolate);
  86. EmitWithoutEvent("right-click", CreateEventFromFlags(modifiers), bounds);
  87. }
  88. void Tray::OnBalloonShow() {
  89. Emit("balloon-show");
  90. }
  91. void Tray::OnBalloonClicked() {
  92. Emit("balloon-click");
  93. }
  94. void Tray::OnBalloonClosed() {
  95. Emit("balloon-closed");
  96. }
  97. void Tray::OnDrop() {
  98. Emit("drop");
  99. }
  100. void Tray::OnDropFiles(const std::vector<std::string>& files) {
  101. Emit("drop-files", files);
  102. }
  103. void Tray::OnDropText(const std::string& text) {
  104. Emit("drop-text", text);
  105. }
  106. void Tray::OnMouseEntered(const gfx::Point& location, int modifiers) {
  107. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  108. v8::HandleScope scope(isolate);
  109. EmitWithoutEvent("mouse-enter", CreateEventFromFlags(modifiers), location);
  110. }
  111. void Tray::OnMouseExited(const gfx::Point& location, int modifiers) {
  112. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  113. v8::HandleScope scope(isolate);
  114. EmitWithoutEvent("mouse-leave", CreateEventFromFlags(modifiers), location);
  115. }
  116. void Tray::OnMouseMoved(const gfx::Point& location, int modifiers) {
  117. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  118. v8::HandleScope scope(isolate);
  119. EmitWithoutEvent("mouse-move", CreateEventFromFlags(modifiers), location);
  120. }
  121. void Tray::OnMouseUp(const gfx::Point& location, int modifiers) {
  122. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  123. v8::HandleScope scope(isolate);
  124. EmitWithoutEvent("mouse-up", CreateEventFromFlags(modifiers), location);
  125. }
  126. void Tray::OnMouseDown(const gfx::Point& location, int modifiers) {
  127. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  128. v8::HandleScope scope(isolate);
  129. EmitWithoutEvent("mouse-down", CreateEventFromFlags(modifiers), location);
  130. }
  131. void Tray::OnDragEntered() {
  132. Emit("drag-enter");
  133. }
  134. void Tray::OnDragExited() {
  135. Emit("drag-leave");
  136. }
  137. void Tray::OnDragEnded() {
  138. Emit("drag-end");
  139. }
  140. void Tray::Destroy() {
  141. Unpin();
  142. menu_.Reset();
  143. tray_icon_.reset();
  144. }
  145. bool Tray::IsDestroyed() {
  146. return !tray_icon_;
  147. }
  148. void Tray::SetImage(v8::Isolate* isolate, v8::Local<v8::Value> image) {
  149. if (!CheckAlive())
  150. return;
  151. NativeImage* native_image = nullptr;
  152. if (!NativeImage::TryConvertNativeImage(isolate, image, &native_image))
  153. return;
  154. #if BUILDFLAG(IS_WIN)
  155. tray_icon_->SetImage(native_image->GetHICON(GetSystemMetrics(SM_CXSMICON)));
  156. #else
  157. tray_icon_->SetImage(native_image->image());
  158. #endif
  159. }
  160. void Tray::SetPressedImage(v8::Isolate* isolate, v8::Local<v8::Value> image) {
  161. if (!CheckAlive())
  162. return;
  163. NativeImage* native_image = nullptr;
  164. if (!NativeImage::TryConvertNativeImage(isolate, image, &native_image))
  165. return;
  166. #if BUILDFLAG(IS_WIN)
  167. tray_icon_->SetPressedImage(
  168. native_image->GetHICON(GetSystemMetrics(SM_CXSMICON)));
  169. #else
  170. tray_icon_->SetPressedImage(native_image->image());
  171. #endif
  172. }
  173. void Tray::SetToolTip(const std::string& tool_tip) {
  174. if (!CheckAlive())
  175. return;
  176. tray_icon_->SetToolTip(tool_tip);
  177. }
  178. void Tray::SetTitle(const std::string& title,
  179. const absl::optional<gin_helper::Dictionary>& options,
  180. gin::Arguments* args) {
  181. if (!CheckAlive())
  182. return;
  183. #if BUILDFLAG(IS_MAC)
  184. TrayIcon::TitleOptions title_options;
  185. if (options) {
  186. if (options->Get("fontType", &title_options.font_type)) {
  187. // Validate the font type if it's passed in
  188. if (title_options.font_type != "monospaced" &&
  189. title_options.font_type != "monospacedDigit") {
  190. args->ThrowTypeError(
  191. "fontType must be one of 'monospaced' or 'monospacedDigit'");
  192. return;
  193. }
  194. } else if (options->Has("fontType")) {
  195. args->ThrowTypeError(
  196. "fontType must be one of 'monospaced' or 'monospacedDigit'");
  197. return;
  198. }
  199. } else if (args->Length() >= 2) {
  200. args->ThrowTypeError("setTitle options must be an object");
  201. return;
  202. }
  203. tray_icon_->SetTitle(title, title_options);
  204. #endif
  205. }
  206. std::string Tray::GetTitle() {
  207. if (!CheckAlive())
  208. return std::string();
  209. #if BUILDFLAG(IS_MAC)
  210. return tray_icon_->GetTitle();
  211. #else
  212. return "";
  213. #endif
  214. }
  215. void Tray::SetIgnoreDoubleClickEvents(bool ignore) {
  216. if (!CheckAlive())
  217. return;
  218. #if BUILDFLAG(IS_MAC)
  219. tray_icon_->SetIgnoreDoubleClickEvents(ignore);
  220. #endif
  221. }
  222. bool Tray::GetIgnoreDoubleClickEvents() {
  223. if (!CheckAlive())
  224. return false;
  225. #if BUILDFLAG(IS_MAC)
  226. return tray_icon_->GetIgnoreDoubleClickEvents();
  227. #else
  228. return false;
  229. #endif
  230. }
  231. void Tray::DisplayBalloon(gin_helper::ErrorThrower thrower,
  232. const gin_helper::Dictionary& options) {
  233. if (!CheckAlive())
  234. return;
  235. TrayIcon::BalloonOptions balloon_options;
  236. if (!options.Get("title", &balloon_options.title) ||
  237. !options.Get("content", &balloon_options.content)) {
  238. thrower.ThrowError("'title' and 'content' must be defined");
  239. return;
  240. }
  241. v8::Local<v8::Value> icon_value;
  242. NativeImage* icon = nullptr;
  243. if (options.Get("icon", &icon_value) &&
  244. !NativeImage::TryConvertNativeImage(thrower.isolate(), icon_value,
  245. &icon)) {
  246. return;
  247. }
  248. options.Get("iconType", &balloon_options.icon_type);
  249. options.Get("largeIcon", &balloon_options.large_icon);
  250. options.Get("noSound", &balloon_options.no_sound);
  251. options.Get("respectQuietTime", &balloon_options.respect_quiet_time);
  252. if (icon) {
  253. #if BUILDFLAG(IS_WIN)
  254. balloon_options.icon = icon->GetHICON(
  255. GetSystemMetrics(balloon_options.large_icon ? SM_CXICON : SM_CXSMICON));
  256. #else
  257. balloon_options.icon = icon->image();
  258. #endif
  259. }
  260. tray_icon_->DisplayBalloon(balloon_options);
  261. }
  262. void Tray::RemoveBalloon() {
  263. if (!CheckAlive())
  264. return;
  265. tray_icon_->RemoveBalloon();
  266. }
  267. void Tray::Focus() {
  268. if (!CheckAlive())
  269. return;
  270. tray_icon_->Focus();
  271. }
  272. void Tray::PopUpContextMenu(gin::Arguments* args) {
  273. if (!CheckAlive())
  274. return;
  275. gin::Handle<Menu> menu;
  276. gfx::Point pos;
  277. v8::Local<v8::Value> first_arg;
  278. if (args->GetNext(&first_arg)) {
  279. if (!gin::ConvertFromV8(args->isolate(), first_arg, &menu)) {
  280. if (!gin::ConvertFromV8(args->isolate(), first_arg, &pos)) {
  281. args->ThrowError();
  282. return;
  283. }
  284. } else if (args->Length() >= 2) {
  285. if (!args->GetNext(&pos)) {
  286. args->ThrowError();
  287. return;
  288. }
  289. }
  290. }
  291. tray_icon_->PopUpContextMenu(
  292. pos, menu.IsEmpty() ? nullptr : menu->model()->GetWeakPtr());
  293. }
  294. void Tray::CloseContextMenu() {
  295. if (!CheckAlive())
  296. return;
  297. tray_icon_->CloseContextMenu();
  298. }
  299. void Tray::SetContextMenu(gin_helper::ErrorThrower thrower,
  300. v8::Local<v8::Value> arg) {
  301. if (!CheckAlive())
  302. return;
  303. gin::Handle<Menu> menu;
  304. if (arg->IsNull()) {
  305. menu_.Reset();
  306. tray_icon_->SetContextMenu(nullptr);
  307. } else if (gin::ConvertFromV8(thrower.isolate(), arg, &menu)) {
  308. menu_.Reset(thrower.isolate(), menu.ToV8());
  309. tray_icon_->SetContextMenu(menu->model());
  310. } else {
  311. thrower.ThrowTypeError("Must pass Menu or null");
  312. }
  313. }
  314. gfx::Rect Tray::GetBounds() {
  315. if (!CheckAlive())
  316. return gfx::Rect();
  317. return tray_icon_->GetBounds();
  318. }
  319. bool Tray::CheckAlive() {
  320. if (!tray_icon_) {
  321. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  322. v8::HandleScope scope(isolate);
  323. gin_helper::ErrorThrower(isolate).ThrowError("Tray is destroyed");
  324. return false;
  325. }
  326. return true;
  327. }
  328. // static
  329. void Tray::FillObjectTemplate(v8::Isolate* isolate,
  330. v8::Local<v8::ObjectTemplate> templ) {
  331. gin::ObjectTemplateBuilder(isolate, GetClassName(), templ)
  332. .SetMethod("destroy", &Tray::Destroy)
  333. .SetMethod("isDestroyed", &Tray::IsDestroyed)
  334. .SetMethod("setImage", &Tray::SetImage)
  335. .SetMethod("setPressedImage", &Tray::SetPressedImage)
  336. .SetMethod("setToolTip", &Tray::SetToolTip)
  337. .SetMethod("setTitle", &Tray::SetTitle)
  338. .SetMethod("getTitle", &Tray::GetTitle)
  339. .SetMethod("setIgnoreDoubleClickEvents",
  340. &Tray::SetIgnoreDoubleClickEvents)
  341. .SetMethod("getIgnoreDoubleClickEvents",
  342. &Tray::GetIgnoreDoubleClickEvents)
  343. .SetMethod("displayBalloon", &Tray::DisplayBalloon)
  344. .SetMethod("removeBalloon", &Tray::RemoveBalloon)
  345. .SetMethod("focus", &Tray::Focus)
  346. .SetMethod("popUpContextMenu", &Tray::PopUpContextMenu)
  347. .SetMethod("closeContextMenu", &Tray::CloseContextMenu)
  348. .SetMethod("setContextMenu", &Tray::SetContextMenu)
  349. .SetMethod("getBounds", &Tray::GetBounds)
  350. .Build();
  351. }
  352. const char* Tray::GetTypeName() {
  353. return GetClassName();
  354. }
  355. } // namespace electron::api
  356. namespace {
  357. using electron::api::Tray;
  358. void Initialize(v8::Local<v8::Object> exports,
  359. v8::Local<v8::Value> unused,
  360. v8::Local<v8::Context> context,
  361. void* priv) {
  362. v8::Isolate* isolate = context->GetIsolate();
  363. gin::Dictionary dict(isolate, exports);
  364. dict.Set("Tray", Tray::GetConstructor(context));
  365. }
  366. } // namespace
  367. NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_tray, Initialize)