├── .gitignore ├── LICENSE ├── README.md ├── TrayIcon.cpp ├── TrayIcon.h ├── TrayWrapper.cpp ├── binding.gyp ├── index.js ├── package-lock.json ├── package.json ├── stdafx.cpp ├── stdafx.h ├── targetver.h └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .vscode 61 | build/ 62 | test/icon.ico -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 mceSystems 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # windows-trayicon 2 | Native addon to add a windows tray icon with menu, built on windows-native libraries (no .NET dependency) 3 | 4 | # Installation 5 | ``` 6 | npm install --save windows-trayicon 7 | ``` 8 | 9 | # Usage 10 | ``` 11 | const WindowsTrayicon = require("windows-trayicon"); 12 | const path = require("path"); 13 | const fs = require("fs"); 14 | 15 | const myTrayApp = new WindowsTrayicon({ 16 | title: "Trayicon Test", 17 | icon: path.resolve(__dirname, "icon.ico"), 18 | menu: [ 19 | { 20 | id: "item-1-id", 21 | caption: "First Item" 22 | }, 23 | { 24 | id: "item-2-id", 25 | caption: "Second Item" 26 | }, 27 | { 28 | id: "item-3-id-exit", 29 | caption: "Exit" 30 | } 31 | ] 32 | }); 33 | 34 | myTrayApp.item((id) => { 35 | console.log(`Menu id selected=${id}`); 36 | switch (id) { 37 | case "item-1-id": { 38 | console.log("First item selected..."); 39 | break; 40 | } 41 | case "item-2-id": { 42 | myTrayApp.balloon("Hello There!", "This is my message to you").then(() => { 43 | console.log("Balloon clicked"); 44 | }) 45 | break; 46 | } 47 | case "item-3-id-exit": { 48 | myTrayApp.exit(); 49 | process.exit(0) 50 | break; 51 | } 52 | } 53 | }); 54 | 55 | process.stdin.resume() 56 | 57 | ``` -------------------------------------------------------------------------------- /TrayIcon.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Istvan Pasztor 3 | * This source has been published on www.codeproject.com under the CPOL license. 4 | */ 5 | #include "stdafx.h" 6 | #include "TrayIcon.h" 7 | #include 8 | 9 | #define TRAY_WINDOW_MESSAGE (WM_USER + 100) 10 | 11 | namespace 12 | { 13 | // A map that never holds allocated memory when it is empty. This map will be created with placement new as a static variable, 14 | // and its destructor will be never called, and it shouldn't leak memory if it contains no items at program exit. 15 | // This dirty trick is useful when you create your trayicon object as static. In this case we can not control the 16 | // order of destruction of this map object and the static trayicon object. However this dirty trick ensures that 17 | // the map is freed exactly when the destructor of the last static trayicon is unregistering itself. 18 | class CIdToTrayIconMap 19 | { 20 | public: 21 | typedef UINT KeyType; 22 | typedef CTrayIcon *ValueType; 23 | 24 | // typedef didn't work with VC++6 25 | struct StdMap : public std::map 26 | { 27 | }; 28 | typedef StdMap::iterator iterator; 29 | 30 | CIdToTrayIconMap() : m_Empty(true) {} 31 | ValueType &operator[](KeyType k) 32 | { 33 | return GetOrCreateStdMap()[k]; 34 | } 35 | ValueType *find(KeyType k) 36 | { 37 | if (m_Empty) 38 | return false; 39 | StdMap::iterator it = GetStdMap().find(k); 40 | if (it == GetStdMap().end()) 41 | return NULL; 42 | return &it->second; 43 | } 44 | int erase(KeyType k) 45 | { 46 | if (m_Empty) 47 | return 0; 48 | StdMap &m = GetStdMap(); 49 | int res = (int)m.erase(k); 50 | if (m.empty()) 51 | { 52 | m.~StdMap(); 53 | m_Empty = true; 54 | } 55 | return res; 56 | } 57 | bool empty() const 58 | { 59 | return m_Empty; 60 | } 61 | // Call this only when the container is not empty!!! 62 | iterator begin() 63 | { 64 | assert(!m_Empty); // Call this only when the container is not empty!!! 65 | return m_Empty ? iterator() : GetStdMap().begin(); 66 | } 67 | // Call this only when the container is not empty!!! 68 | iterator end() 69 | { 70 | assert(!m_Empty); // Call this only when the container is not empty!!! 71 | return m_Empty ? iterator() : GetStdMap().end(); 72 | } 73 | 74 | private: 75 | StdMap &GetStdMap() 76 | { 77 | assert(!m_Empty); 78 | return (StdMap &)m_MapBuffer; 79 | } 80 | StdMap &GetOrCreateStdMap() 81 | { 82 | if (m_Empty) 83 | { 84 | new ((void *)&m_MapBuffer) StdMap(); 85 | m_Empty = false; 86 | } 87 | return (StdMap &)m_MapBuffer; 88 | } 89 | 90 | private: 91 | bool m_Empty; 92 | char m_MapBuffer[sizeof(StdMap)]; 93 | }; 94 | 95 | static CIdToTrayIconMap &GetIdToTrayIconMap() 96 | { 97 | // This hack prevents running the destructor of our map, so it isn't problem if someone tries to reach this from a static destructor. 98 | // Because of using MyMap this will not cause a memory leak if the user removes all items from the container before exiting. 99 | static char id_to_tray_icon_buffer[sizeof(CIdToTrayIconMap)]; 100 | static bool initialized = false; 101 | if (!initialized) 102 | { 103 | initialized = true; 104 | new ((void *)id_to_tray_icon_buffer) CIdToTrayIconMap(); 105 | } 106 | return (CIdToTrayIconMap &)id_to_tray_icon_buffer; 107 | } 108 | 109 | static UINT GetNextTrayIconId() 110 | { 111 | static UINT next_id = 1; 112 | return next_id++; 113 | } 114 | } 115 | 116 | void CallJsWithOptionalString(Napi::Env env, Function callback, Context *context, std::string* data) { 117 | if (env != nullptr) { 118 | if (callback != nullptr) { 119 | if (data) { 120 | callback.Call(context->Value(), { String::New(env, *data) }); 121 | } else { 122 | callback.Call(context->Value(), {}); 123 | } 124 | 125 | } 126 | } 127 | if (data != nullptr) { 128 | delete data; 129 | } 130 | } 131 | 132 | static const UINT g_WndMsgTaskbarCreated = RegisterWindowMessage(TEXT("TaskbarCreated")); 133 | LRESULT CALLBACK CTrayIcon::MessageProcessorWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 134 | { 135 | if (uMsg == TRAY_WINDOW_MESSAGE) 136 | { 137 | if (CTrayIcon **ppIcon = GetIdToTrayIconMap().find((UINT)wParam)) 138 | (*ppIcon)->OnMessage((UINT)lParam); 139 | return 0; 140 | } 141 | else if (uMsg == g_WndMsgTaskbarCreated) 142 | { 143 | CIdToTrayIconMap &id_to_tray = GetIdToTrayIconMap(); 144 | if (!id_to_tray.empty()) 145 | { 146 | for (std::map::const_iterator it = id_to_tray.begin(), eit = id_to_tray.end(); it != eit; ++it) 147 | { 148 | CTrayIcon *pTrayIcon = it->second; 149 | pTrayIcon->OnTaskbarCreated(); 150 | } 151 | } 152 | } 153 | return DefWindowProc(hWnd, uMsg, wParam, lParam); 154 | } 155 | 156 | HWND CTrayIcon::GetMessageProcessorHWND() 157 | { 158 | static HWND hWnd = NULL; 159 | if (!hWnd) 160 | { 161 | static const TCHAR TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASSNAME[] = TEXT("TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASS"); 162 | HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL); 163 | 164 | WNDCLASSEX wc; 165 | wc.cbSize = sizeof(wc); 166 | wc.cbClsExtra = 0; 167 | wc.cbWndExtra = 0; 168 | wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); 169 | wc.hCursor = LoadCursor(NULL, IDC_ARROW); 170 | wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); 171 | wc.hIconSm = NULL; 172 | wc.hInstance = hInstance; 173 | wc.lpfnWndProc = MessageProcessorWndProc; 174 | wc.lpszClassName = TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASSNAME; 175 | wc.lpszMenuName = NULL; 176 | wc.style = 0; 177 | if (!RegisterClassEx(&wc)) 178 | return NULL; 179 | 180 | hWnd = CreateWindowEx( 181 | 0, 182 | TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASSNAME, 183 | TEXT("TRAY_ICON_MESSAGE_PROCESSOR_WND"), 184 | WS_POPUP, 185 | 0, 0, 0, 0, 186 | NULL, 187 | NULL, 188 | hInstance, 189 | NULL); 190 | } 191 | return hWnd; 192 | } 193 | 194 | CTrayIcon::CTrayIcon(const char *name, bool visible, HICON hIcon, bool destroy_icon_in_destructor) 195 | : m_Id(GetNextTrayIconId()), m_Name(name), m_hIcon(hIcon), m_Visible(false), m_DestroyIconInDestructor(destroy_icon_in_destructor), m_pOnMessageFunc(NULL), m_pListener(NULL) 196 | { 197 | GetIdToTrayIconMap()[m_Id] = this; 198 | SetVisible(visible); 199 | } 200 | 201 | CTrayIcon::~CTrayIcon() 202 | { 203 | SetVisible(false); 204 | SetIcon(NULL, m_DestroyIconInDestructor); 205 | GetIdToTrayIconMap().erase(m_Id); 206 | } 207 | 208 | HICON CTrayIcon::InternalGetIcon() const 209 | { 210 | return m_hIcon ? m_hIcon : ::LoadIcon(NULL, IDI_APPLICATION); 211 | } 212 | 213 | bool CTrayIcon::AddIcon() 214 | { 215 | NOTIFYICONDATAA data; 216 | FillNotifyIconData(data); 217 | data.uFlags |= NIF_MESSAGE | NIF_ICON | NIF_TIP; 218 | data.uCallbackMessage = TRAY_WINDOW_MESSAGE; 219 | data.hIcon = InternalGetIcon(); 220 | 221 | size_t tip_len = max(sizeof(data.szTip) - 1, strlen(m_Name.c_str())); 222 | memcpy(data.szTip, m_Name.c_str(), tip_len); 223 | data.szTip[tip_len] = 0; 224 | 225 | return FALSE != Shell_NotifyIconA(NIM_ADD, &data); 226 | } 227 | 228 | bool CTrayIcon::RemoveIcon() 229 | { 230 | NOTIFYICONDATAA data; 231 | FillNotifyIconData(data); 232 | return FALSE != Shell_NotifyIconA(NIM_DELETE, &data); 233 | } 234 | 235 | void CTrayIcon::OnTaskbarCreated() 236 | { 237 | if (m_Visible) 238 | AddIcon(); 239 | } 240 | 241 | void CTrayIcon::SetName(const char *name) 242 | { 243 | m_Name = name; 244 | if (m_Visible) 245 | { 246 | NOTIFYICONDATAA data; 247 | FillNotifyIconData(data); 248 | data.uFlags |= NIF_TIP; 249 | 250 | size_t tip_len = max(sizeof(data.szTip) - 1, strlen(name)); 251 | memcpy(data.szTip, name, tip_len); 252 | data.szTip[tip_len] = 0; 253 | 254 | Shell_NotifyIconA(NIM_MODIFY, &data); 255 | } 256 | } 257 | 258 | bool CTrayIcon::SetVisible(bool visible) 259 | { 260 | if (m_Visible == visible) 261 | return true; 262 | m_Visible = visible; 263 | if (m_Visible) 264 | return AddIcon(); 265 | return RemoveIcon(); 266 | } 267 | 268 | void CTrayIcon::SetIcon(HICON hNewIcon, bool destroy_current_icon) 269 | { 270 | if (m_hIcon == hNewIcon) 271 | return; 272 | if (destroy_current_icon && m_hIcon) 273 | DestroyIcon(m_hIcon); 274 | m_hIcon = hNewIcon; 275 | 276 | if (m_Visible) 277 | { 278 | NOTIFYICONDATAA data; 279 | FillNotifyIconData(data); 280 | data.uFlags |= NIF_ICON; 281 | data.hIcon = InternalGetIcon(); 282 | Shell_NotifyIconA(NIM_MODIFY, &data); 283 | } 284 | } 285 | 286 | bool CTrayIcon::ShowBalloonTooltip(const char *title, const char *msg, ETooltipIcon icon) 287 | { 288 | #ifndef NOTIFYICONDATA_V2_SIZE 289 | return false; 290 | #else 291 | if (!m_Visible) 292 | return false; 293 | 294 | NOTIFYICONDATAA data; 295 | FillNotifyIconData(data); 296 | data.cbSize = NOTIFYICONDATAA_V2_SIZE; // win2k and later 297 | data.uFlags |= NIF_INFO; 298 | data.dwInfoFlags = icon; 299 | data.uTimeout = 10000; // deprecated as of Windows Vista, it has a min(10000) and max(30000) value on previous Windows versions. 300 | 301 | strcpy_s(data.szInfoTitle, title); 302 | strcpy_s(data.szInfo, msg); 303 | 304 | return FALSE != Shell_NotifyIconA(NIM_MODIFY, &data); 305 | #endif 306 | } 307 | 308 | void CTrayIcon::OnMessage(UINT uMsg) 309 | { 310 | if (m_pOnMessageFunc) 311 | m_pOnMessageFunc(this, uMsg); 312 | if (m_pListener) 313 | m_pListener->OnTrayIconMessage(this, uMsg); 314 | } 315 | 316 | void CTrayIcon::FillNotifyIconData(NOTIFYICONDATAA &data) 317 | { 318 | memset(&data, 0, sizeof(data)); 319 | // the basic functions need only V1 320 | #ifdef NOTIFYICONDATA_V1_SIZE 321 | data.cbSize = NOTIFYICONDATA_V1_SIZE; 322 | #else 323 | data.cbSize = sizeof(data); 324 | #endif 325 | data.hWnd = GetMessageProcessorHWND(); 326 | assert(data.hWnd); 327 | data.uID = m_Id; 328 | } 329 | 330 | void TrayOnMessage(CTrayIcon *pTrayIcon, UINT uMsg) 331 | { 332 | switch (uMsg) 333 | { 334 | case WM_LBUTTONUP: 335 | case WM_RBUTTONUP: 336 | ((CTrayIconContainer *)pTrayIcon->GetUserData())->PopupMenu(); 337 | break; 338 | 339 | case NIN_BALLOONUSERCLICK: 340 | ((CTrayIconContainer *)pTrayIcon->GetUserData())->BalloonClick(); 341 | break; 342 | } 343 | } 344 | 345 | HICON GetIconHandle(std::string iconPath) 346 | { 347 | return (HICON)LoadImage(NULL, iconPath.c_str(), IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_SHARED); 348 | } 349 | 350 | 351 | CTrayIconContainer::CTrayIconContainer(const CallbackInfo& info) : ObjectWrap(info), m_OnBalloonClick(nullptr), m_OnMenuItem(nullptr) {} 352 | 353 | Object CTrayIconContainer::Init(Napi::Env env, Object exports){ 354 | Function func = 355 | DefineClass(env, 356 | "CTrayIconContainer", 357 | { 358 | InstanceMethod("Start", &CTrayIconContainer::Start), 359 | InstanceMethod("SetIconPath", &CTrayIconContainer::SetIconPath), 360 | InstanceMethod("SetTitle", &CTrayIconContainer::SetTitle), 361 | InstanceMethod("Stop", &CTrayIconContainer::Stop), 362 | InstanceMethod("AddMenuItem", &CTrayIconContainer::AddMenuItem), 363 | InstanceMethod("OnMenuItem", &CTrayIconContainer::OnMenuItem), 364 | InstanceMethod("ShowBalloon", &CTrayIconContainer::ShowBalloon), 365 | } 366 | ); 367 | 368 | FunctionReference* constructor = new FunctionReference(); 369 | *constructor = Napi::Persistent(func); 370 | env.SetInstanceData(constructor); 371 | 372 | exports.Set("CTrayIconContainer", func); 373 | return exports; 374 | } 375 | 376 | void CTrayIconContainer::Stop(const CallbackInfo& info) 377 | { 378 | if (m_OnMenuItem) { 379 | m_OnMenuItem.Release(); 380 | m_OnMenuItem = nullptr; 381 | } 382 | if (m_OnBalloonClick) { 383 | m_OnBalloonClick.Release(); 384 | m_OnBalloonClick = nullptr; 385 | } 386 | PostThreadMessage(GetThreadId(m_worker->native_handle()), WM_QUIT, NULL, NULL); 387 | m_worker->join(); 388 | delete m_worker; 389 | m_worker = nullptr; 390 | m_tray.SetVisible(false); 391 | } 392 | 393 | LRESULT CALLBACK pWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 394 | { 395 | return DefWindowProc(hWnd, uMsg, wParam, lParam); 396 | } 397 | 398 | void CTrayIconContainer::SetIconPath(const CallbackInfo& info) 399 | { 400 | std::string iconPath = info[0].As(); 401 | m_tray.SetIcon(GetIconHandle(iconPath)); 402 | } 403 | void CTrayIconContainer::SetTitle(const CallbackInfo& info) 404 | { 405 | std::string title = info[0].As(); 406 | m_tray.SetName(title.c_str()); 407 | } 408 | void CTrayIconContainer::Start(const CallbackInfo& info) 409 | { 410 | HANDLE ready = CreateEvent(nullptr, true, false, nullptr); 411 | 412 | m_worker = new std::thread([this, ready] { 413 | m_tray.SetUserData(this); 414 | m_tray.SetVisible(true); 415 | m_tray.SetListener(TrayOnMessage); 416 | 417 | SetEvent(ready); 418 | 419 | static const TCHAR *class_name = TEXT("MCE_HWND_MESSAGE"); 420 | WNDCLASSEX wx = {}; 421 | wx.cbSize = sizeof(WNDCLASSEX); 422 | wx.lpfnWndProc = pWndProc; 423 | wx.hInstance = 0; 424 | wx.lpszClassName = class_name; 425 | RegisterClassEx(&wx); 426 | m_hwnd = CreateWindowEx(0, class_name, TEXT("MCE_HWND_MESSAGE"), 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL); 427 | 428 | MSG msg; 429 | while (GetMessage(&msg, NULL, 0, 0)) 430 | { 431 | TranslateMessage(&msg); 432 | DispatchMessage(&msg); 433 | } 434 | 435 | DestroyWindow(m_hwnd); 436 | 437 | return 0; 438 | }); 439 | 440 | WaitForSingleObject(ready, INFINITE); 441 | CloseHandle(ready); 442 | } 443 | 444 | void CTrayIconContainer::BalloonClick() 445 | { 446 | m_OnBalloonClick.BlockingCall(nullptr); 447 | } 448 | 449 | void CTrayIconContainer::PopupMenu() 450 | { 451 | POINT pt; 452 | if (GetCursorPos(&pt)) 453 | { 454 | HMENU menu = CreatePopupMenu(); 455 | int i = 0; 456 | for (auto const &item : m_menuItems) 457 | { 458 | AppendMenuA(menu, MF_STRING, i++, item.m_caption.c_str()); 459 | } 460 | UINT cmd = TrackPopupMenu(menu, TPM_RETURNCMD | TPM_RIGHTBUTTON, pt.x, pt.y, 0, m_hwnd, NULL); 461 | m_OnMenuItem.BlockingCall(new std::string(m_menuItems[cmd].m_id)); 462 | } 463 | } 464 | 465 | void CTrayIconContainer::AddMenuItem(const CallbackInfo& info) 466 | { 467 | std::string id = info[0].As(); 468 | std::string caption = info[1].As(); 469 | m_menuItems.push_back(CTrayIconMenuItem(id, caption)); 470 | } 471 | 472 | void CTrayIconContainer::OnMenuItem(const CallbackInfo& info) 473 | { 474 | Function cb = info[0].As(); 475 | Context *context = new Reference(Persistent(info.This())); 476 | if (m_OnMenuItem) { 477 | m_OnMenuItem.Release(); 478 | } 479 | m_OnMenuItem = TSFNOptString::New(info.Env(), cb, "wintrayicon_OnMenuItem", 0, 1, context); 480 | } 481 | 482 | void CTrayIconContainer::ShowBalloon(const CallbackInfo& info) 483 | { 484 | std::string title = info[0].As(); 485 | std::string text = info[1].As(); 486 | int timeout = info[2].As(); 487 | Function cb = info[3].As(); 488 | 489 | Context *context = new Reference(Persistent(info.This())); 490 | 491 | if (m_OnBalloonClick) { 492 | m_OnBalloonClick.Release(); 493 | } 494 | 495 | m_OnBalloonClick = TSFNOptString::New(info.Env(), cb, "wintrayicon_ShowBalloon", 0, 1, context); 496 | m_tray.ShowBalloonTooltip(title.c_str(), text.c_str()); 497 | } 498 | -------------------------------------------------------------------------------- /TrayIcon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Istvan Pasztor 3 | * This source has been published on www.codeproject.com under the CPOL license. 4 | */ 5 | #ifndef __TRAY_ICON_H__ 6 | #define __TRAY_ICON_H__ 7 | #pragma once 8 | 9 | // NOTE: include the following headers in your stdafx.h: 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "napi.h" 19 | 20 | using namespace Napi; 21 | 22 | using Context = Reference; 23 | void CallJsWithOptionalString(Napi::Env env, Function callback, Context *context, std::string* data); 24 | using TSFNOptString = TypedThreadSafeFunction; 25 | 26 | struct ITrayIconListener; 27 | 28 | class CTrayIconMenuItem 29 | { 30 | public: 31 | CTrayIconMenuItem(std::string id, std::string caption) : 32 | m_id(id), m_caption(caption) {} 33 | 34 | std::string m_id; 35 | std::string m_caption; 36 | }; 37 | 38 | 39 | // You can use this class either by inheriting from it and overriding the OnMessage() method, 40 | // or by instantiating this class directly and setting its listener object or function. 41 | class CTrayIcon 42 | { 43 | public: 44 | CTrayIcon(const char* name="tray_icon", bool visible=false, HICON hIcon=NULL, bool destroy_icon_in_destructor=false); 45 | // destroys the current m_hIcon if set 46 | virtual ~CTrayIcon(); 47 | 48 | virtual void SetName(const char* name); 49 | const char* GetName() const { return m_Name.c_str(); } 50 | 51 | virtual bool SetVisible(bool visible); 52 | bool IsVisible() const { return m_Visible; } 53 | 54 | // The destructor may destroy the specified hIcon. If you want to avoid that, call 55 | // SetIcon(NULL, false) or SetDestroyIconInDestructor(false). 56 | virtual void SetIcon(HICON hNewIcon, bool destroy_current_icon=true); 57 | HICON GetIcon() const { return m_hIcon; } 58 | 59 | void SetDestroyIconInDestructor(bool b) { m_DestroyIconInDestructor = b; } 60 | bool GetDestroyIconInDestructor() const { return m_DestroyIconInDestructor; } 61 | 62 | enum ETooltipIcon 63 | { 64 | eTI_None, // NIIF_NONE(0) 65 | eTI_Info, // NIIF_INFO(1) 66 | eTI_Warning, // NIIF_WARNING(2) 67 | eTI_Error // NIIF_ERROR(3) 68 | }; 69 | // ShowBalloonTooltip() works only on win2k and later 70 | bool ShowBalloonTooltip(const char* title, const char* msg, ETooltipIcon icon=eTI_None); 71 | 72 | typedef void (*POnMessageFunc)(CTrayIcon* pTrayIcon, UINT uMsg); 73 | void SetListener(POnMessageFunc pOnMessageFunc) { m_pOnMessageFunc = pOnMessageFunc; } 74 | void SetListener(ITrayIconListener *pListener) { m_pListener = pListener; } 75 | void SetUserData(const void* UserData) { m_userData = UserData; } 76 | const void* GetUserData() { return m_userData; } 77 | 78 | protected: 79 | // uMsg can be one of the following window messages: 80 | // - WM_MOUSEMOVE 81 | // - WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK 82 | // - WM_RBUTTONDOWN, WM_RBUTTONUP, WM_RBUTTONDBLCLK 83 | // - WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MBUTTONDBLCLK 84 | // WinXP and later: 85 | // - NIN_BALLOONXXX messages (eg.: NIN_BALLOONUSERCLICK) 86 | // 87 | // Use GetCursorPos() if you need the location of the cursor. 88 | // The default implementation calls the listener. 89 | virtual void OnMessage(UINT uMsg); 90 | 91 | private: 92 | void FillNotifyIconData(NOTIFYICONDATAA& data); 93 | // Never returns NULL! If GetIcon()==NULL, then this returns a system icon 94 | HICON InternalGetIcon() const; 95 | bool AddIcon(); 96 | bool RemoveIcon(); 97 | void OnTaskbarCreated(); 98 | 99 | private: 100 | UINT m_Id; 101 | std::string m_Name; 102 | bool m_Visible; 103 | HICON m_hIcon; 104 | bool m_DestroyIconInDestructor; 105 | POnMessageFunc m_pOnMessageFunc; 106 | ITrayIconListener* m_pListener; 107 | 108 | static LRESULT CALLBACK MessageProcessorWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 109 | static HWND GetMessageProcessorHWND(); 110 | const void* m_userData; 111 | }; 112 | 113 | 114 | //------------------------------------------------------------------------------------------------- 115 | 116 | 117 | struct ITrayIconListener 118 | { 119 | virtual void OnTrayIconMouseMove(CTrayIcon* pTrayIcon) {} 120 | 121 | virtual void OnTrayIconLButtonDown(CTrayIcon* pTrayIcon) {} 122 | virtual void OnTrayIconLButtonUp(CTrayIcon* pTrayIcon) {} 123 | virtual void OnTrayIconLButtonDblClk(CTrayIcon* pTrayIcon) {} 124 | 125 | virtual void OnTrayIconRButtonDown(CTrayIcon* pTrayIcon) {} 126 | virtual void OnTrayIconRButtonUp(CTrayIcon* pTrayIcon) {} 127 | virtual void OnTrayIconRButtonDblClk(CTrayIcon* pTrayIcon) {} 128 | 129 | virtual void OnTrayIconMButtonDown(CTrayIcon* pTrayIcon) {} 130 | virtual void OnTrayIconMButtonUp(CTrayIcon* pTrayIcon) {} 131 | virtual void OnTrayIconMButtonDblClk(CTrayIcon* pTrayIcon) {} 132 | 133 | // WinXP and later 134 | virtual void OnTrayIconSelect(CTrayIcon* pTrayIcon) {} 135 | virtual void OnTrayIconBalloonShow(CTrayIcon* pTrayIcon) {} 136 | virtual void OnTrayIconBalloonHide(CTrayIcon* pTrayIcon) {} 137 | virtual void OnTrayIconBalloonTimeout(CTrayIcon* pTrayIcon) {} 138 | virtual void OnTrayIconBalloonUserClick(CTrayIcon* pTrayIcon) {} 139 | 140 | // Use GetCursorPos() if you need the location of the cursor. 141 | virtual void OnTrayIconMessage(CTrayIcon* pTrayIcon, UINT uMsg) 142 | { 143 | switch (uMsg) 144 | { 145 | case WM_MOUSEMOVE: OnTrayIconMouseMove(pTrayIcon); break; 146 | case WM_LBUTTONDOWN: OnTrayIconLButtonDown(pTrayIcon); break; 147 | case WM_LBUTTONUP: OnTrayIconLButtonUp(pTrayIcon); break; 148 | case WM_LBUTTONDBLCLK: OnTrayIconLButtonDblClk(pTrayIcon); break; 149 | case WM_RBUTTONDOWN: OnTrayIconRButtonDown(pTrayIcon); break; 150 | case WM_RBUTTONUP: OnTrayIconRButtonUp(pTrayIcon); break; 151 | case WM_RBUTTONDBLCLK: OnTrayIconRButtonDblClk(pTrayIcon); break; 152 | case WM_MBUTTONDOWN: OnTrayIconMButtonDown(pTrayIcon); break; 153 | case WM_MBUTTONUP: OnTrayIconMButtonUp(pTrayIcon); break; 154 | case WM_MBUTTONDBLCLK: OnTrayIconMButtonDblClk(pTrayIcon); break; 155 | 156 | #ifdef NIN_SELECT 157 | case NIN_SELECT: OnTrayIconSelect(pTrayIcon); break; 158 | case NIN_BALLOONSHOW: OnTrayIconBalloonShow(pTrayIcon); break; 159 | case NIN_BALLOONHIDE: OnTrayIconBalloonHide(pTrayIcon); break; 160 | case NIN_BALLOONTIMEOUT: OnTrayIconBalloonTimeout(pTrayIcon); break; 161 | case NIN_BALLOONUSERCLICK: OnTrayIconBalloonUserClick(pTrayIcon); break; 162 | #endif 163 | } 164 | } 165 | }; 166 | 167 | 168 | 169 | class CTrayIconContainer : public ObjectWrap 170 | { 171 | public: 172 | CTrayIconContainer(const CallbackInfo& info); 173 | static Object Init(Napi::Env env, Object exports); 174 | void Start(const CallbackInfo& info); 175 | void SetIconPath(const CallbackInfo& info); 176 | void SetTitle(const CallbackInfo& info); 177 | void AddMenuItem(const CallbackInfo& info); 178 | void OnMenuItem(const CallbackInfo& info); 179 | void ShowBalloon(const CallbackInfo& info); 180 | void Stop(const CallbackInfo& info); 181 | void PopupMenu(); 182 | void BalloonClick(); 183 | 184 | private: 185 | CTrayIcon m_tray; 186 | std::thread* m_worker; 187 | HWND m_hwnd; 188 | std::vector m_menuItems; 189 | TSFNOptString m_OnMenuItem; 190 | TSFNOptString m_OnBalloonClick; 191 | }; 192 | 193 | #endif //!__TRAY_ICON_H__ 194 | -------------------------------------------------------------------------------- /TrayWrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "TrayIcon.h" 2 | 3 | Object Init(Env env, Object exports) { 4 | CTrayIconContainer::Init(env, exports); 5 | return exports; 6 | } 7 | 8 | NODE_API_MODULE(addon, Init) -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "addon", 5 | "conditions": [ 6 | ['OS=="win"', { 7 | "sources": [ 8 | "TrayWrapper.cpp", 9 | "TrayIcon.cpp" 10 | ], 11 | "include_dirs": [ 12 | " { 15 | for (const cb of this.__itemCallbacks) { 16 | cb(id); 17 | } 18 | }) 19 | this.__nativeTray.SetTitle(this.__trayTitle); 20 | if(this.__icon && "string" === typeof this.__icon){ 21 | this.__nativeTray.SetIconPath(this.__icon); 22 | } 23 | this.__nativeTray.Start(); 24 | } 25 | item(cb) { 26 | if ("function" === typeof cb) { 27 | this.__itemCallbacks.push(cb); 28 | } 29 | } 30 | balloon(title, text, timeout = 5000) { 31 | return new Promise((resolve) => { 32 | this.__nativeTray.ShowBalloon(title, text, timeout, () => { 33 | resolve(); 34 | }) 35 | }); 36 | } 37 | exit() { 38 | this.__nativeTray.Stop(); 39 | } 40 | } 41 | 42 | module.exports = WindowsTrayicon; 43 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "windows-trayicon", 3 | "version": "3.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "abbrev": { 8 | "version": "1.1.1", 9 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 10 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 11 | }, 12 | "ajv": { 13 | "version": "6.12.6", 14 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 15 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 16 | "requires": { 17 | "fast-deep-equal": "^3.1.1", 18 | "fast-json-stable-stringify": "^2.0.0", 19 | "json-schema-traverse": "^0.4.1", 20 | "uri-js": "^4.2.2" 21 | } 22 | }, 23 | "ansi-regex": { 24 | "version": "2.1.1", 25 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 26 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 27 | }, 28 | "aproba": { 29 | "version": "1.2.0", 30 | "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", 31 | "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" 32 | }, 33 | "are-we-there-yet": { 34 | "version": "1.1.5", 35 | "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", 36 | "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", 37 | "requires": { 38 | "delegates": "^1.0.0", 39 | "readable-stream": "^2.0.6" 40 | } 41 | }, 42 | "asn1": { 43 | "version": "0.2.4", 44 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", 45 | "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", 46 | "requires": { 47 | "safer-buffer": "~2.1.0" 48 | } 49 | }, 50 | "assert-plus": { 51 | "version": "1.0.0", 52 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 53 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 54 | }, 55 | "asynckit": { 56 | "version": "0.4.0", 57 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 58 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 59 | }, 60 | "aws-sign2": { 61 | "version": "0.7.0", 62 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 63 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 64 | }, 65 | "aws4": { 66 | "version": "1.11.0", 67 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", 68 | "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" 69 | }, 70 | "balanced-match": { 71 | "version": "1.0.0", 72 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 73 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 74 | }, 75 | "bcrypt-pbkdf": { 76 | "version": "1.0.2", 77 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 78 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 79 | "requires": { 80 | "tweetnacl": "^0.14.3" 81 | } 82 | }, 83 | "bindings": { 84 | "version": "1.5.0", 85 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 86 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 87 | "requires": { 88 | "file-uri-to-path": "1.0.0" 89 | } 90 | }, 91 | "brace-expansion": { 92 | "version": "1.1.11", 93 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 94 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 95 | "requires": { 96 | "balanced-match": "^1.0.0", 97 | "concat-map": "0.0.1" 98 | } 99 | }, 100 | "caseless": { 101 | "version": "0.12.0", 102 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 103 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 104 | }, 105 | "chownr": { 106 | "version": "2.0.0", 107 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 108 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" 109 | }, 110 | "code-point-at": { 111 | "version": "1.1.0", 112 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 113 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 114 | }, 115 | "combined-stream": { 116 | "version": "1.0.8", 117 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 118 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 119 | "requires": { 120 | "delayed-stream": "~1.0.0" 121 | } 122 | }, 123 | "concat-map": { 124 | "version": "0.0.1", 125 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 126 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 127 | }, 128 | "console-control-strings": { 129 | "version": "1.1.0", 130 | "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", 131 | "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" 132 | }, 133 | "core-util-is": { 134 | "version": "1.0.2", 135 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 136 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 137 | }, 138 | "dashdash": { 139 | "version": "1.14.1", 140 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 141 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 142 | "requires": { 143 | "assert-plus": "^1.0.0" 144 | } 145 | }, 146 | "delayed-stream": { 147 | "version": "1.0.0", 148 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 149 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 150 | }, 151 | "delegates": { 152 | "version": "1.0.0", 153 | "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", 154 | "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" 155 | }, 156 | "ecc-jsbn": { 157 | "version": "0.1.2", 158 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 159 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 160 | "requires": { 161 | "jsbn": "~0.1.0", 162 | "safer-buffer": "^2.1.0" 163 | } 164 | }, 165 | "env-paths": { 166 | "version": "2.2.0", 167 | "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", 168 | "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==" 169 | }, 170 | "extend": { 171 | "version": "3.0.2", 172 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 173 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 174 | }, 175 | "extsprintf": { 176 | "version": "1.3.0", 177 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 178 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 179 | }, 180 | "fast-deep-equal": { 181 | "version": "3.1.3", 182 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 183 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 184 | }, 185 | "fast-json-stable-stringify": { 186 | "version": "2.1.0", 187 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 188 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 189 | }, 190 | "file-uri-to-path": { 191 | "version": "1.0.0", 192 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 193 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" 194 | }, 195 | "forever-agent": { 196 | "version": "0.6.1", 197 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 198 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 199 | }, 200 | "form-data": { 201 | "version": "2.3.3", 202 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 203 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 204 | "requires": { 205 | "asynckit": "^0.4.0", 206 | "combined-stream": "^1.0.6", 207 | "mime-types": "^2.1.12" 208 | } 209 | }, 210 | "fs-minipass": { 211 | "version": "2.1.0", 212 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 213 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 214 | "requires": { 215 | "minipass": "^3.0.0" 216 | } 217 | }, 218 | "fs.realpath": { 219 | "version": "1.0.0", 220 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 221 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 222 | }, 223 | "gauge": { 224 | "version": "2.7.4", 225 | "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", 226 | "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", 227 | "requires": { 228 | "aproba": "^1.0.3", 229 | "console-control-strings": "^1.0.0", 230 | "has-unicode": "^2.0.0", 231 | "object-assign": "^4.1.0", 232 | "signal-exit": "^3.0.0", 233 | "string-width": "^1.0.1", 234 | "strip-ansi": "^3.0.1", 235 | "wide-align": "^1.1.0" 236 | } 237 | }, 238 | "getpass": { 239 | "version": "0.1.7", 240 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 241 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 242 | "requires": { 243 | "assert-plus": "^1.0.0" 244 | } 245 | }, 246 | "glob": { 247 | "version": "7.1.6", 248 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 249 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 250 | "requires": { 251 | "fs.realpath": "^1.0.0", 252 | "inflight": "^1.0.4", 253 | "inherits": "2", 254 | "minimatch": "^3.0.4", 255 | "once": "^1.3.0", 256 | "path-is-absolute": "^1.0.0" 257 | } 258 | }, 259 | "graceful-fs": { 260 | "version": "4.2.4", 261 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", 262 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" 263 | }, 264 | "har-schema": { 265 | "version": "2.0.0", 266 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 267 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 268 | }, 269 | "har-validator": { 270 | "version": "5.1.5", 271 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", 272 | "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", 273 | "requires": { 274 | "ajv": "^6.12.3", 275 | "har-schema": "^2.0.0" 276 | } 277 | }, 278 | "has-unicode": { 279 | "version": "2.0.1", 280 | "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", 281 | "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" 282 | }, 283 | "http-signature": { 284 | "version": "1.2.0", 285 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 286 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 287 | "requires": { 288 | "assert-plus": "^1.0.0", 289 | "jsprim": "^1.2.2", 290 | "sshpk": "^1.7.0" 291 | } 292 | }, 293 | "inflight": { 294 | "version": "1.0.6", 295 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 296 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 297 | "requires": { 298 | "once": "^1.3.0", 299 | "wrappy": "1" 300 | } 301 | }, 302 | "inherits": { 303 | "version": "2.0.4", 304 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 305 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 306 | }, 307 | "is-fullwidth-code-point": { 308 | "version": "1.0.0", 309 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 310 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 311 | "requires": { 312 | "number-is-nan": "^1.0.0" 313 | } 314 | }, 315 | "is-typedarray": { 316 | "version": "1.0.0", 317 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 318 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 319 | }, 320 | "isarray": { 321 | "version": "1.0.0", 322 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 323 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 324 | }, 325 | "isexe": { 326 | "version": "2.0.0", 327 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 328 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 329 | }, 330 | "isstream": { 331 | "version": "0.1.2", 332 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 333 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 334 | }, 335 | "jsbn": { 336 | "version": "0.1.1", 337 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 338 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 339 | }, 340 | "json-schema": { 341 | "version": "0.2.3", 342 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 343 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 344 | }, 345 | "json-schema-traverse": { 346 | "version": "0.4.1", 347 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 348 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 349 | }, 350 | "json-stringify-safe": { 351 | "version": "5.0.1", 352 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 353 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 354 | }, 355 | "jsprim": { 356 | "version": "1.4.1", 357 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 358 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 359 | "requires": { 360 | "assert-plus": "1.0.0", 361 | "extsprintf": "1.3.0", 362 | "json-schema": "0.2.3", 363 | "verror": "1.10.0" 364 | } 365 | }, 366 | "lru-cache": { 367 | "version": "6.0.0", 368 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 369 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 370 | "requires": { 371 | "yallist": "^4.0.0" 372 | } 373 | }, 374 | "mime-db": { 375 | "version": "1.45.0", 376 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", 377 | "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" 378 | }, 379 | "mime-types": { 380 | "version": "2.1.28", 381 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", 382 | "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", 383 | "requires": { 384 | "mime-db": "1.45.0" 385 | } 386 | }, 387 | "minimatch": { 388 | "version": "3.0.4", 389 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 390 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 391 | "requires": { 392 | "brace-expansion": "^1.1.7" 393 | } 394 | }, 395 | "minipass": { 396 | "version": "3.1.3", 397 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", 398 | "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", 399 | "requires": { 400 | "yallist": "^4.0.0" 401 | } 402 | }, 403 | "minizlib": { 404 | "version": "2.1.2", 405 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 406 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 407 | "requires": { 408 | "minipass": "^3.0.0", 409 | "yallist": "^4.0.0" 410 | } 411 | }, 412 | "mkdirp": { 413 | "version": "1.0.4", 414 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 415 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" 416 | }, 417 | "node-addon-api": { 418 | "version": "3.1.0", 419 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz", 420 | "integrity": "sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==" 421 | }, 422 | "node-gyp": { 423 | "version": "7.1.2", 424 | "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", 425 | "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==", 426 | "requires": { 427 | "env-paths": "^2.2.0", 428 | "glob": "^7.1.4", 429 | "graceful-fs": "^4.2.3", 430 | "nopt": "^5.0.0", 431 | "npmlog": "^4.1.2", 432 | "request": "^2.88.2", 433 | "rimraf": "^3.0.2", 434 | "semver": "^7.3.2", 435 | "tar": "^6.0.2", 436 | "which": "^2.0.2" 437 | } 438 | }, 439 | "nopt": { 440 | "version": "5.0.0", 441 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", 442 | "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", 443 | "requires": { 444 | "abbrev": "1" 445 | } 446 | }, 447 | "npmlog": { 448 | "version": "4.1.2", 449 | "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", 450 | "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", 451 | "requires": { 452 | "are-we-there-yet": "~1.1.2", 453 | "console-control-strings": "~1.1.0", 454 | "gauge": "~2.7.3", 455 | "set-blocking": "~2.0.0" 456 | } 457 | }, 458 | "number-is-nan": { 459 | "version": "1.0.1", 460 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 461 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 462 | }, 463 | "oauth-sign": { 464 | "version": "0.9.0", 465 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 466 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 467 | }, 468 | "object-assign": { 469 | "version": "4.1.1", 470 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 471 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 472 | }, 473 | "once": { 474 | "version": "1.4.0", 475 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 476 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 477 | "requires": { 478 | "wrappy": "1" 479 | } 480 | }, 481 | "path-is-absolute": { 482 | "version": "1.0.1", 483 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 484 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 485 | }, 486 | "performance-now": { 487 | "version": "2.1.0", 488 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 489 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 490 | }, 491 | "process-nextick-args": { 492 | "version": "2.0.1", 493 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 494 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" 495 | }, 496 | "psl": { 497 | "version": "1.8.0", 498 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", 499 | "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" 500 | }, 501 | "punycode": { 502 | "version": "2.1.1", 503 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 504 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 505 | }, 506 | "qs": { 507 | "version": "6.5.2", 508 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 509 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 510 | }, 511 | "readable-stream": { 512 | "version": "2.3.7", 513 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 514 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 515 | "requires": { 516 | "core-util-is": "~1.0.0", 517 | "inherits": "~2.0.3", 518 | "isarray": "~1.0.0", 519 | "process-nextick-args": "~2.0.0", 520 | "safe-buffer": "~5.1.1", 521 | "string_decoder": "~1.1.1", 522 | "util-deprecate": "~1.0.1" 523 | } 524 | }, 525 | "request": { 526 | "version": "2.88.2", 527 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", 528 | "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", 529 | "requires": { 530 | "aws-sign2": "~0.7.0", 531 | "aws4": "^1.8.0", 532 | "caseless": "~0.12.0", 533 | "combined-stream": "~1.0.6", 534 | "extend": "~3.0.2", 535 | "forever-agent": "~0.6.1", 536 | "form-data": "~2.3.2", 537 | "har-validator": "~5.1.3", 538 | "http-signature": "~1.2.0", 539 | "is-typedarray": "~1.0.0", 540 | "isstream": "~0.1.2", 541 | "json-stringify-safe": "~5.0.1", 542 | "mime-types": "~2.1.19", 543 | "oauth-sign": "~0.9.0", 544 | "performance-now": "^2.1.0", 545 | "qs": "~6.5.2", 546 | "safe-buffer": "^5.1.2", 547 | "tough-cookie": "~2.5.0", 548 | "tunnel-agent": "^0.6.0", 549 | "uuid": "^3.3.2" 550 | } 551 | }, 552 | "rimraf": { 553 | "version": "3.0.2", 554 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 555 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 556 | "requires": { 557 | "glob": "^7.1.3" 558 | } 559 | }, 560 | "safe-buffer": { 561 | "version": "5.1.2", 562 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 563 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 564 | }, 565 | "safer-buffer": { 566 | "version": "2.1.2", 567 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 568 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 569 | }, 570 | "semver": { 571 | "version": "7.3.4", 572 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", 573 | "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", 574 | "requires": { 575 | "lru-cache": "^6.0.0" 576 | } 577 | }, 578 | "set-blocking": { 579 | "version": "2.0.0", 580 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 581 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 582 | }, 583 | "signal-exit": { 584 | "version": "3.0.3", 585 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 586 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 587 | }, 588 | "sshpk": { 589 | "version": "1.16.1", 590 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", 591 | "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", 592 | "requires": { 593 | "asn1": "~0.2.3", 594 | "assert-plus": "^1.0.0", 595 | "bcrypt-pbkdf": "^1.0.0", 596 | "dashdash": "^1.12.0", 597 | "ecc-jsbn": "~0.1.1", 598 | "getpass": "^0.1.1", 599 | "jsbn": "~0.1.0", 600 | "safer-buffer": "^2.0.2", 601 | "tweetnacl": "~0.14.0" 602 | } 603 | }, 604 | "string-width": { 605 | "version": "1.0.2", 606 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 607 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 608 | "requires": { 609 | "code-point-at": "^1.0.0", 610 | "is-fullwidth-code-point": "^1.0.0", 611 | "strip-ansi": "^3.0.0" 612 | } 613 | }, 614 | "string_decoder": { 615 | "version": "1.1.1", 616 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 617 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 618 | "requires": { 619 | "safe-buffer": "~5.1.0" 620 | } 621 | }, 622 | "strip-ansi": { 623 | "version": "3.0.1", 624 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 625 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 626 | "requires": { 627 | "ansi-regex": "^2.0.0" 628 | } 629 | }, 630 | "tar": { 631 | "version": "6.0.5", 632 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", 633 | "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", 634 | "requires": { 635 | "chownr": "^2.0.0", 636 | "fs-minipass": "^2.0.0", 637 | "minipass": "^3.0.0", 638 | "minizlib": "^2.1.1", 639 | "mkdirp": "^1.0.3", 640 | "yallist": "^4.0.0" 641 | } 642 | }, 643 | "tough-cookie": { 644 | "version": "2.5.0", 645 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 646 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 647 | "requires": { 648 | "psl": "^1.1.28", 649 | "punycode": "^2.1.1" 650 | } 651 | }, 652 | "tunnel-agent": { 653 | "version": "0.6.0", 654 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 655 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 656 | "requires": { 657 | "safe-buffer": "^5.0.1" 658 | } 659 | }, 660 | "tweetnacl": { 661 | "version": "0.14.5", 662 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 663 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 664 | }, 665 | "uri-js": { 666 | "version": "4.4.0", 667 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", 668 | "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", 669 | "requires": { 670 | "punycode": "^2.1.0" 671 | } 672 | }, 673 | "util-deprecate": { 674 | "version": "1.0.2", 675 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 676 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 677 | }, 678 | "uuid": { 679 | "version": "3.4.0", 680 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 681 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" 682 | }, 683 | "verror": { 684 | "version": "1.10.0", 685 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 686 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 687 | "requires": { 688 | "assert-plus": "^1.0.0", 689 | "core-util-is": "1.0.2", 690 | "extsprintf": "^1.2.0" 691 | } 692 | }, 693 | "which": { 694 | "version": "2.0.2", 695 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 696 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 697 | "requires": { 698 | "isexe": "^2.0.0" 699 | } 700 | }, 701 | "wide-align": { 702 | "version": "1.1.3", 703 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 704 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 705 | "requires": { 706 | "string-width": "^1.0.2 || 2" 707 | } 708 | }, 709 | "wrappy": { 710 | "version": "1.0.2", 711 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 712 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 713 | }, 714 | "yallist": { 715 | "version": "4.0.0", 716 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 717 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 718 | } 719 | } 720 | } 721 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "windows-trayicon", 3 | "version": "3.0.0", 4 | "description": "Create tray icon for windows", 5 | "main": "index.js", 6 | "scripts": { 7 | "install": "node-gyp configure build" 8 | }, 9 | "author": "mce", 10 | "license": "MIT", 11 | "dependencies": { 12 | "node-gyp": "^7.1.2", 13 | "bindings": "^1.5.0", 14 | "node-addon-api": "^3.1.0" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/mceSystems/node-windows-trayicon" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | // ConsoleApplication1.pch will be the pre-compiled header 3 | // stdafx.obj will contain the pre-compiled type information 4 | 5 | #include "stdafx.h" 6 | 7 | // TODO: reference any additional headers you need in STDAFX.H 8 | // and not in this file 9 | -------------------------------------------------------------------------------- /stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #include 11 | #include 12 | 13 | 14 | 15 | // TODO: reference additional headers your program requires here 16 | -------------------------------------------------------------------------------- /targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | 8 | #include 9 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const WindowsTrayicon = require(".."); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | 5 | const myTrayApp = new WindowsTrayicon({ 6 | title: "Trayicon Test", 7 | icon: path.resolve(__dirname, "icon.ico"), 8 | menu: [ 9 | { 10 | id: "item-1-id", 11 | caption: "First Item" 12 | }, 13 | { 14 | id: "item-2-id", 15 | caption: "Second Item" 16 | }, 17 | { 18 | id: "item-3-id-exit", 19 | caption: "Exit" 20 | } 21 | ] 22 | }); 23 | 24 | myTrayApp.item((id) => { 25 | console.log(`Menu id selected=${id}`); 26 | switch (id) { 27 | case "item-1-id": { 28 | console.log("First item selected..."); 29 | break; 30 | } 31 | case "item-2-id": { 32 | myTrayApp.balloon("Hello There!", "This is my message to you").then(() => { 33 | console.log("Balloon clicked"); 34 | }) 35 | break; 36 | } 37 | case "item-3-id-exit": { 38 | myTrayApp.exit(); 39 | process.exit(0) 40 | break; 41 | } 42 | } 43 | }); 44 | 45 | process.stdin.resume() --------------------------------------------------------------------------------