tray_icon.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. #include "tray_icon.h"
  2. #define TRAY_WINDOW_MESSAGE (WM_USER + 101)
  3. namespace {
  4. // A map that never holds allocated memory when it is empty. This map will be created with placement new as a static variable,
  5. // and its destructor will be never called, and it shouldn't leak memory if it contains no items at program exit.
  6. // This dirty trick is useful when you create your trayicon object as static. In this case we can not control the
  7. // order of destruction of this map object and the static trayicon object. However this dirty trick ensures that
  8. // the map is freed exactly when the destructor of the last static trayicon is unregistering itself.
  9. class TrayIconMap {
  10. public:
  11. typedef UINT KeyType;
  12. typedef TrayIcon *ValueType;
  13. //typedef StdMap std::map<KeyType, ValueType>;
  14. struct StdMap : public std::map<KeyType, ValueType>{};
  15. typedef StdMap::iterator iterator;
  16. TrayIconMap() : m_empty(true) {}
  17. ValueType &operator[](KeyType k) {
  18. return getOrCreateStdMap()[k];
  19. }
  20. ValueType *find(KeyType k) {
  21. if (m_empty) {
  22. return false;
  23. }
  24. StdMap::iterator it = getStdMap().find(k);
  25. if (it == getStdMap().end()) {
  26. return NULL;
  27. }
  28. return &it->second;
  29. }
  30. int erase(KeyType k) {
  31. if (m_empty) {
  32. return 0;
  33. }
  34. StdMap &m = getStdMap();
  35. int res = (int)m.erase(k);
  36. if (m.empty()) {
  37. m.~StdMap();
  38. m_empty = true;
  39. }
  40. return res;
  41. }
  42. bool empty() const {
  43. return m_empty;
  44. }
  45. iterator begin() {
  46. assert(!m_empty);
  47. return m_empty ? iterator() : getStdMap().begin();
  48. }
  49. iterator end() {
  50. assert(!m_empty);
  51. return m_empty ? iterator() : getStdMap().end();
  52. }
  53. private:
  54. StdMap &getStdMap() {
  55. assert(!m_empty);
  56. return (StdMap &)m_mapBuffer;
  57. }
  58. StdMap &getOrCreateStdMap() {
  59. if (m_empty) {
  60. new ((void *)&m_mapBuffer) StdMap();
  61. m_empty = false;
  62. }
  63. return (StdMap &)m_mapBuffer;
  64. }
  65. private:
  66. bool m_empty;
  67. char m_mapBuffer[sizeof(StdMap)];
  68. };
  69. static TrayIconMap &getTrayIconMap() {
  70. // This hack prevents running the destructor of our map, so it isn't problem if someone tries to reach this from a static destructor.
  71. // Because of using MyMap this will not cause a memory leak if the user removes all items from the container before exiting.
  72. static char tray_icon_buffer[sizeof(TrayIconMap)];
  73. static bool initialized = false;
  74. if (!initialized) {
  75. initialized = true;
  76. new ((void *)tray_icon_buffer) TrayIconMap();
  77. }
  78. return (TrayIconMap &)tray_icon_buffer;
  79. }
  80. static UINT getNextTrayIconId() {
  81. static UINT next_id = 1;
  82. return next_id++;
  83. }
  84. }
  85. static const UINT g_WndMsgTaskbarCreated = RegisterWindowMessage(TEXT("TaskbarCreated"));
  86. LRESULT CALLBACK TrayIcon::MessageProcessorWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  87. {
  88. if (uMsg == TRAY_WINDOW_MESSAGE) {
  89. if (TrayIcon **ppIcon = getTrayIconMap().find((UINT)wParam)) {
  90. (*ppIcon)->onMessage((UINT)lParam);
  91. }
  92. return 0;
  93. } else if (uMsg == g_WndMsgTaskbarCreated) {
  94. TrayIconMap &m = getTrayIconMap();
  95. if (!m.empty()) {
  96. for (std::map<UINT, TrayIcon *>::const_iterator it = m.begin(), eit = m.end(); it != eit; ++it) {
  97. TrayIcon *pTrayIcon = it->second;
  98. pTrayIcon->onTaskbarCreated();
  99. }
  100. }
  101. }
  102. return DefWindowProc(hWnd, uMsg, wParam, lParam);
  103. }
  104. HWND TrayIcon::GetMessageProcessorHWND()
  105. {
  106. static HWND hWnd = NULL;
  107. if (!hWnd) {
  108. static const TCHAR TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASSNAME[] = TEXT("TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASS");
  109. HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);
  110. WNDCLASSEX wc;
  111. wc.cbSize = sizeof(wc);
  112. wc.cbClsExtra = 0;
  113. wc.cbWndExtra = 0;
  114. wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  115. wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  116. wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
  117. wc.hIconSm = NULL;
  118. wc.hInstance = hInstance;
  119. wc.lpfnWndProc = MessageProcessorWndProc;
  120. wc.lpszClassName = TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASSNAME;
  121. wc.lpszMenuName = NULL;
  122. wc.style = 0;
  123. if (!RegisterClassEx(&wc)) {
  124. return NULL;
  125. }
  126. hWnd = CreateWindowEx(
  127. 0,
  128. TRAY_ICON_MESSAGE_PROCESSOR_WND_CLASSNAME,
  129. TEXT("TRAY_ICON_MESSAGE_PROCESSOR_WND"),
  130. WS_POPUP,
  131. 0, 0, 0, 0,
  132. NULL,
  133. NULL,
  134. hInstance,
  135. NULL);
  136. }
  137. return hWnd;
  138. }
  139. TrayIcon::TrayIcon(LPCWSTR name, bool visible, HICON hIcon, bool destroyIconInDestructor)
  140. : m_id(getNextTrayIconId())
  141. , m_name(name)
  142. , m_hIcon(hIcon)
  143. , m_visible(false)
  144. , m_destroyIconInDestructor(destroyIconInDestructor)
  145. , m_messageFunc(NULL)
  146. , m_listener(NULL)
  147. , m_userData(NULL)
  148. {
  149. getTrayIconMap()[m_id] = this;
  150. setVisible(visible);
  151. }
  152. TrayIcon::~TrayIcon()
  153. {
  154. setVisible(false);
  155. setIcon(NULL, m_destroyIconInDestructor);
  156. getTrayIconMap().erase(m_id);
  157. }
  158. HICON TrayIcon::internalGetIcon() const
  159. {
  160. return m_hIcon ? m_hIcon : ::LoadIcon(NULL, IDI_APPLICATION);
  161. }
  162. bool TrayIcon::addIcon()
  163. {
  164. NOTIFYICONDATAW data;
  165. fillNotifyIconData(data);
  166. data.uFlags |= NIF_MESSAGE | NIF_ICON | NIF_TIP;
  167. data.uCallbackMessage = TRAY_WINDOW_MESSAGE;
  168. data.hIcon = internalGetIcon();
  169. size_t tip_len = std::max(sizeof(data.szTip) - 1, wcslen(m_name.c_str()));
  170. wmemcpy(data.szTip, m_name.c_str(), tip_len);
  171. data.szTip[tip_len] = 0;
  172. return FALSE != Shell_NotifyIcon(NIM_ADD, &data);
  173. }
  174. bool TrayIcon::removeIcon()
  175. {
  176. NOTIFYICONDATAW data;
  177. fillNotifyIconData(data);
  178. return FALSE != Shell_NotifyIcon(NIM_DELETE, &data);
  179. }
  180. void TrayIcon::onTaskbarCreated()
  181. {
  182. if (m_visible) {
  183. addIcon();
  184. }
  185. }
  186. void TrayIcon::setName(LPCWSTR name)
  187. {
  188. m_name = name;
  189. if (m_visible) {
  190. NOTIFYICONDATAW data;
  191. fillNotifyIconData(data);
  192. data.uFlags |= NIF_TIP;
  193. size_t tip_len = std::max(sizeof(data.szTip) - 1, wcslen(name));
  194. wmemcpy(data.szTip, name, tip_len);
  195. data.szTip[tip_len] = 0;
  196. Shell_NotifyIcon(NIM_MODIFY, &data);
  197. }
  198. }
  199. bool TrayIcon::setVisible(bool visible)
  200. {
  201. if (m_visible == visible) {
  202. return true;
  203. }
  204. m_visible = visible;
  205. if (m_visible) {
  206. return addIcon();
  207. }
  208. return removeIcon();
  209. }
  210. void TrayIcon::setIcon(HICON hNewIcon, bool destroyCurrentIcon)
  211. {
  212. if (m_hIcon == hNewIcon) {
  213. return;
  214. }
  215. if (destroyCurrentIcon && m_hIcon) {
  216. DestroyIcon(m_hIcon);
  217. }
  218. m_hIcon = hNewIcon;
  219. if (m_visible) {
  220. NOTIFYICONDATAW data;
  221. fillNotifyIconData(data);
  222. data.uFlags |= NIF_ICON;
  223. data.hIcon = internalGetIcon();
  224. Shell_NotifyIcon(NIM_MODIFY, &data);
  225. }
  226. }
  227. bool TrayIcon::notify(LPCWSTR title, LPCWSTR msg, NotificationIcon icon)
  228. {
  229. #ifndef NOTIFYICONDATA_V2_SIZE
  230. return false;
  231. #else
  232. if (!m_visible) {
  233. return false;
  234. }
  235. NOTIFYICONDATAW data;
  236. fillNotifyIconData(data);
  237. data.cbSize = NOTIFYICONDATAW_V2_SIZE;
  238. data.uFlags |= NIF_INFO;
  239. data.dwInfoFlags = icon;
  240. data.uTimeout = 10000;
  241. wcscpy_s(data.szInfoTitle, title);
  242. wcscpy_s(data.szInfo, msg);
  243. return FALSE != Shell_NotifyIcon(NIM_MODIFY, &data);
  244. #endif
  245. }
  246. void TrayIcon::onMessage(UINT uMsg)
  247. {
  248. if (m_messageFunc) {
  249. m_messageFunc(this, uMsg);
  250. }
  251. if (m_listener) {
  252. m_listener->onTrayIconMessage(this, uMsg);
  253. }
  254. }
  255. void TrayIcon::fillNotifyIconData(NOTIFYICONDATAW &data)
  256. {
  257. memset(&data, 0, sizeof(data));
  258. // the basic functions need only V1
  259. #ifdef NOTIFYICONDATA_V1_SIZE
  260. data.cbSize = NOTIFYICONDATA_V1_SIZE;
  261. #else
  262. data.cbSize = sizeof(data);
  263. #endif
  264. data.hWnd = GetMessageProcessorHWND();
  265. assert(data.hWnd);
  266. data.uID = m_id;
  267. }