1 // Written by Christopher E. Miller 2 // See the included license.txt for copyright and license details. 3 4 module dfl.notifyicon; 5 import core.sys.windows.windows; 6 7 import dfl.application; 8 import dfl.base; 9 import dfl.control; 10 import dfl.drawing; 11 import dfl.event; 12 import dfl.exception; 13 import dfl.form; 14 import dfl.internal.dlib; 15 import dfl.internal.utf; 16 17 version (DFL_NO_MENUS) { 18 } else { 19 private import dfl.menu; 20 } 21 22 class NotifyIcon { 23 version (DFL_NO_MENUS) { 24 } else { 25 26 final @property void contextMenu(ContextMenu menu) { 27 this.cmenu = menu; 28 } 29 30 final @property ContextMenu contextMenu() { 31 return cmenu; 32 } 33 } 34 35 final @property void icon(Icon ico) { 36 _icon = ico; 37 nid.hIcon = ico ? ico.handle : null; 38 39 if (visible) { 40 nid.uFlags = NIF_ICON; 41 Shell_NotifyIcon(NIM_MODIFY, &nid); 42 } 43 } 44 45 final @property Icon icon() { 46 return _icon; 47 } 48 49 // Must be less than 64 chars. 50 // To-do: hold reference to setter's string, use that for getter.. ? 51 final @property void text(Dstring txt) { 52 if (txt.length >= nid.szTip.length) { 53 throw new DflException("Notify icon text too long"); 54 } 55 56 // To-do: support Unicode. 57 58 txt = unsafeAnsi(txt); // ... 59 nid.szTip[txt.length] = 0; 60 // FIX: nid.szTip[0 .. txt.length] = txt[]; 61 tipLen = txt.length; 62 63 if (visible) { 64 nid.uFlags = NIF_TIP; 65 Shell_NotifyIcon(NIM_MODIFY, &nid); 66 } 67 } 68 69 final @property Dstring text() { 70 //return nid.szTip[0 .. tipLen]; // Returning possibly mutated text! 71 //return nid.szTip[0 .. tipLen].dup; 72 //return nid.szTip[0 .. tipLen].idup; // Needed in D2. Doesn't work in D1. 73 return cast(Dstring) nid.szTip[0 .. tipLen].dup; // Needed in D2. Doesn't work in D1. 74 } 75 76 final @property void visible(bool byes) { 77 if (byes) { 78 if (!nid.uID) { 79 nid.uID = allocNotifyIconID(); 80 assert(nid.uID); 81 allNotifyIcons[nid.uID] = this; 82 } 83 84 _forceAdd(); 85 } else if (nid.uID) { 86 _forceDelete(); 87 88 //delete allNotifyIcons[nid.uID]; 89 allNotifyIcons.remove(nid.uID); 90 nid.uID = 0; 91 } 92 } 93 94 final @property bool visible() { 95 return nid.uID != 0; 96 } 97 98 final void show() { 99 visible = true; 100 } 101 102 final void hide() { 103 visible = false; 104 } 105 106 //EventHandler click; 107 Event!(NotifyIcon, EventArgs) click; 108 //EventHandler doubleClick; 109 Event!(NotifyIcon, EventArgs) doubleClick; 110 //MouseEventHandler mouseDown; 111 Event!(NotifyIcon, MouseEventArgs) mouseDown; 112 //MouseEventHandler mouseUp; 113 Event!(NotifyIcon, MouseEventArgs) mouseUp; 114 //MouseEventHandler mouseMove; 115 Event!(NotifyIcon, MouseEventArgs) mouseMove; 116 117 this() { 118 if (!ctrlNotifyIcon) { 119 _init(); 120 } 121 122 nid.cbSize = nid.sizeof; 123 nid.hWnd = ctrlNotifyIcon.handle; 124 nid.uID = 0; 125 nid.uCallbackMessage = WM_NOTIFYICON; 126 nid.hIcon = null; 127 nid.szTip[0] = '\0'; 128 } 129 130 ~this() { 131 if (nid.uID) { 132 _forceDelete(); 133 //delete allNotifyIcons[nid.uID]; 134 allNotifyIcons.remove(nid.uID); 135 } 136 137 //delete allNotifyIcons[nid.uID]; 138 //allNotifyIcons.remove(nid.uID); 139 140 /+ 141 if(!allNotifyIcons.length) { 142 delete ctrlNotifyIcon; 143 ctrlNotifyIcon = null; 144 } 145 +/ 146 } 147 148 // Extra. 149 void minimize(IWindow win) { 150 LONG style; 151 HWND hwnd; 152 153 hwnd = win.handle; 154 style = GetWindowLongA(hwnd, GWL_STYLE); 155 156 if (style & WS_VISIBLE) { 157 ShowOwnedPopups(hwnd, FALSE); 158 159 if (!(style & WS_MINIMIZE) && _animation()) { 160 RECT myRect, areaRect; 161 162 GetWindowRect(hwnd, &myRect); 163 _area(areaRect); 164 DrawAnimatedRects(hwnd, 3, &myRect, &areaRect); 165 } 166 167 ShowWindow(hwnd, SW_HIDE); 168 } 169 } 170 171 // Extra. 172 void restore(IWindow win) { 173 LONG style; 174 HWND hwnd; 175 176 hwnd = win.handle; 177 style = GetWindowLongA(hwnd, GWL_STYLE); 178 179 if (!(style & WS_VISIBLE)) { 180 if (style & WS_MINIMIZE) { 181 ShowWindow(hwnd, SW_RESTORE); 182 } else { 183 if (_animation()) { 184 RECT myRect, areaRect; 185 186 GetWindowRect(hwnd, &myRect); 187 _area(areaRect); 188 DrawAnimatedRects(hwnd, 3, &areaRect, &myRect); 189 } 190 191 ShowWindow(hwnd, SW_SHOW); 192 193 ShowOwnedPopups(hwnd, TRUE); 194 } 195 } else { 196 if (style & WS_MINIMIZE) { 197 ShowWindow(hwnd, SW_RESTORE); 198 } 199 } 200 201 SetForegroundWindow(hwnd); 202 } 203 204 private: 205 206 NOTIFYICONDATA nid; 207 int tipLen = 0; 208 version (DFL_NO_MENUS) { 209 } else { 210 ContextMenu cmenu; 211 } 212 Icon _icon; 213 214 package final void _forceAdd() { 215 nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; 216 Shell_NotifyIcon(NIM_ADD, &nid); 217 } 218 219 package final void _forceDelete() { 220 Shell_NotifyIcon(NIM_DELETE, &nid); 221 } 222 223 // Returns true if min/restore animation is on. 224 static bool _animation() { 225 ANIMATIONINFO ai; 226 227 ai.cbSize = ai.sizeof; 228 SystemParametersInfoA(SPI_GETANIMATION, ai.sizeof, &ai, 0); 229 230 return ai.iMinAnimate ? true : false; 231 } 232 233 // Gets the tray area. 234 static void _area(out RECT rect) { 235 HWND hwTaskbar, hw; 236 237 hwTaskbar = FindWindowExA(null, null, "Shell_TrayWnd", null); 238 if (hwTaskbar) { 239 hw = FindWindowExA(hwTaskbar, null, "TrayNotifyWnd", null); 240 if (hw) { 241 GetWindowRect(hw, &rect); 242 return; 243 } 244 } 245 246 APPBARDATA abd; 247 248 abd.cbSize = abd.sizeof; 249 if (SHAppBarMessage(ABM_GETTASKBARPOS, &abd)) { 250 switch (abd.uEdge) { 251 case ABE_LEFT: 252 case ABE_RIGHT: 253 rect.top = abd.rc.bottom - 100; 254 rect.bottom = abd.rc.bottom - 16; 255 rect.left = abd.rc.left; 256 rect.right = abd.rc.right; 257 break; 258 259 case ABE_TOP: 260 case ABE_BOTTOM: 261 rect.top = abd.rc.top; 262 rect.bottom = abd.rc.bottom; 263 rect.left = abd.rc.right - 100; 264 rect.right = abd.rc.right - 16; 265 break; 266 267 default: 268 } 269 } else if (hwTaskbar) { 270 GetWindowRect(hwTaskbar, &rect); 271 if (rect.right - rect.left > 150) { 272 rect.left = rect.right - 150; 273 } 274 if (rect.bottom - rect.top > 30) { 275 rect.top = rect.bottom - 30; 276 } 277 } else { 278 SystemParametersInfoA(SPI_GETWORKAREA, 0, &rect, 0); 279 rect.left = rect.right - 150; 280 rect.top = rect.bottom - 30; 281 } 282 } 283 } 284 285 package: 286 287 enum UINT WM_NOTIFYICON = WM_USER + 34; // -wparam- is id, -lparam- is the mouse message such as WM_LBUTTONDBLCLK. 288 UINT wmTaskbarCreated; 289 NotifyIcon[UINT] allNotifyIcons; // Indexed by ID. 290 UINT lastId = 1; 291 NotifyIconControl ctrlNotifyIcon; 292 293 class NotifyIconControl : Control { 294 override void createHandle() { 295 //if(created) 296 if (isHandleCreated) { 297 return; 298 } 299 300 if (killing) { 301 create_err: 302 throw new DflException("Notify icon initialization failure"); 303 } 304 305 Application.creatingControl(this); 306 hwnd = CreateWindowExA(wexstyle, CONTROL_CLASSNAME.ptr, "NotifyIcon", 0, 307 0, 0, 0, 0, null, null, Application.getInstance(), null); 308 if (!hwnd) { 309 goto create_err; 310 } 311 } 312 313 protected override void wndProc(ref Message msg) { 314 if (msg.msg == WM_NOTIFYICON) { 315 if (cast(UINT) msg.wParam in allNotifyIcons) { 316 NotifyIcon ni; 317 Point pt; 318 319 ni = allNotifyIcons[cast(UINT) msg.wParam]; 320 321 switch (cast(UINT) msg.lParam) { // msg. 322 case WM_MOUSEMOVE: 323 pt = Cursor.position; 324 ni.mouseMove(ni, new MouseEventArgs(Control.mouseButtons(), 0, pt.x, 325 pt.y, 0)); 326 break; 327 328 case WM_LBUTTONUP: 329 pt = Cursor.position; 330 ni.mouseUp(ni, new MouseEventArgs(MouseButtons.LEFT, 1, pt.x, pt.y, 331 0)); 332 333 ni.click(ni, EventArgs.empty); 334 break; 335 336 case WM_RBUTTONUP: 337 pt = Cursor.position; 338 ni.mouseUp(ni, new MouseEventArgs(MouseButtons.RIGHT, 1, pt.x, pt.y, 339 0)); 340 341 version (DFL_NO_MENUS) { 342 } else { 343 if (ni.cmenu) { 344 ni.cmenu.show(ctrlNotifyIcon, pt); 345 } 346 } 347 break; 348 349 case WM_LBUTTONDOWN: 350 pt = Cursor.position; 351 ni.mouseDown(ni, new MouseEventArgs(MouseButtons.LEFT, 0, pt.x, pt.y, 352 0)); 353 break; 354 355 case WM_RBUTTONDOWN: 356 pt = Cursor.position; 357 ni.mouseDown(ni, new MouseEventArgs(MouseButtons.RIGHT, 0, pt.x, pt.y, 358 0)); 359 break; 360 361 case WM_LBUTTONDBLCLK: 362 ni.doubleClick(ni, EventArgs.empty); 363 break; 364 365 default: 366 } 367 } 368 } else if (msg.msg == wmTaskbarCreated) { 369 // Show all visible NotifyIcon's. 370 foreach (NotifyIcon ni; allNotifyIcons) { 371 if (ni.visible) { 372 ni._forceAdd(); 373 } 374 } 375 } 376 377 super.wndProc(msg); 378 } 379 } 380 381 static ~this() { 382 // Due to all items not being destructed at program exit, 383 // remove all visible notify icons because the OS won't. 384 foreach (NotifyIcon ni; allNotifyIcons) { 385 if (ni.visible) { 386 ni._forceDelete(); 387 } 388 } 389 390 allNotifyIcons = null; 391 } 392 393 UINT allocNotifyIconID() { 394 UINT prev; 395 prev = lastId; 396 for (;;) { 397 lastId++; 398 if (lastId == ushort.max) { 399 lastId = 1; 400 } 401 if (lastId == prev) { 402 throw new DflException("Too many notify icons"); 403 } 404 405 if (!(lastId in allNotifyIcons)) { 406 break; 407 } 408 } 409 return lastId; 410 } 411 412 void _init() { 413 wmTaskbarCreated = RegisterWindowMessageA("TaskbarCreated"); 414 415 ctrlNotifyIcon = new NotifyIconControl; 416 ctrlNotifyIcon.visible = false; 417 }