Added a wrapper for the NotifyIconAdv to use the normal NotifyIcon class on Linux systems and the (fixed) custom implementation on Windows systems.
3 This Source Code Form is subject to the terms of the Mozilla Public
4 License, v. 2.0. If a copy of the MPL was not distributed with this
5 file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 Copyright (C) 2012 Michael Möller <mmoeller@openhardwaremonitor.org>
12 using System.ComponentModel;
14 using System.Runtime.InteropServices;
15 using System.Reflection;
16 using System.Windows.Forms;
18 namespace OpenHardwareMonitor.GUI {
20 public class NotifyIconAdv : IDisposable {
22 private NotifyIcon genericNotifyIcon;
23 private NotifyIconWindowsImplementation windowsNotifyIcon;
25 public NotifyIconAdv() {
26 int p = (int)Environment.OSVersion.Platform;
27 if ((p == 4) || (p == 128)) { // Unix
28 genericNotifyIcon = new NotifyIcon();
30 windowsNotifyIcon = new NotifyIconWindowsImplementation();
34 public event EventHandler BalloonTipClicked {
36 if (genericNotifyIcon != null)
37 genericNotifyIcon.BalloonTipClicked += value;
39 windowsNotifyIcon.BalloonTipClicked += value;
42 if (genericNotifyIcon != null)
43 genericNotifyIcon.BalloonTipClicked -= value;
45 windowsNotifyIcon.BalloonTipClicked -= value;
49 public event EventHandler BalloonTipClosed {
51 if (genericNotifyIcon != null)
52 genericNotifyIcon.BalloonTipClosed += value;
54 windowsNotifyIcon.BalloonTipClosed += value;
57 if (genericNotifyIcon != null)
58 genericNotifyIcon.BalloonTipClosed -= value;
60 windowsNotifyIcon.BalloonTipClosed -= value;
64 public event EventHandler BalloonTipShown {
66 if (genericNotifyIcon != null)
67 genericNotifyIcon.BalloonTipShown += value;
69 windowsNotifyIcon.BalloonTipShown += value;
72 if (genericNotifyIcon != null)
73 genericNotifyIcon.BalloonTipShown -= value;
75 windowsNotifyIcon.BalloonTipShown -= value;
79 public event EventHandler Click {
81 if (genericNotifyIcon != null)
82 genericNotifyIcon.Click += value;
84 windowsNotifyIcon.Click += value;
87 if (genericNotifyIcon != null)
88 genericNotifyIcon.Click -= value;
90 windowsNotifyIcon.Click -= value;
94 public event EventHandler DoubleClick {
96 if (genericNotifyIcon != null)
97 genericNotifyIcon.DoubleClick += value;
99 windowsNotifyIcon.DoubleClick += value;
102 if (genericNotifyIcon != null)
103 genericNotifyIcon.DoubleClick -= value;
105 windowsNotifyIcon.DoubleClick -= value;
109 public event MouseEventHandler MouseClick {
111 if (genericNotifyIcon != null)
112 genericNotifyIcon.MouseClick += value;
114 windowsNotifyIcon.MouseClick += value;
117 if (genericNotifyIcon != null)
118 genericNotifyIcon.MouseClick -= value;
120 windowsNotifyIcon.MouseClick -= value;
124 public event MouseEventHandler MouseDoubleClick {
126 if (genericNotifyIcon != null)
127 genericNotifyIcon.MouseDoubleClick += value;
129 windowsNotifyIcon.MouseDoubleClick += value;
132 if (genericNotifyIcon != null)
133 genericNotifyIcon.MouseDoubleClick -= value;
135 windowsNotifyIcon.MouseDoubleClick -= value;
139 public event MouseEventHandler MouseDown {
141 if (genericNotifyIcon != null)
142 genericNotifyIcon.MouseDown += value;
144 windowsNotifyIcon.MouseDown += value;
147 if (genericNotifyIcon != null)
148 genericNotifyIcon.MouseDown -= value;
150 windowsNotifyIcon.MouseDown -= value;
154 public event MouseEventHandler MouseMove {
156 if (genericNotifyIcon != null)
157 genericNotifyIcon.MouseMove += value;
159 windowsNotifyIcon.MouseMove += value;
162 if (genericNotifyIcon != null)
163 genericNotifyIcon.MouseMove -= value;
165 windowsNotifyIcon.MouseMove -= value;
169 public event MouseEventHandler MouseUp {
171 if (genericNotifyIcon != null)
172 genericNotifyIcon.MouseUp += value;
174 windowsNotifyIcon.MouseUp += value;
177 if (genericNotifyIcon != null)
178 genericNotifyIcon.MouseUp -= value;
180 windowsNotifyIcon.MouseUp -= value;
184 public string BalloonTipText {
186 if (genericNotifyIcon != null)
187 return genericNotifyIcon.BalloonTipText;
189 return windowsNotifyIcon.BalloonTipText;
192 if (genericNotifyIcon != null)
193 genericNotifyIcon.BalloonTipText = value;
195 windowsNotifyIcon.BalloonTipText = value;
199 public ToolTipIcon BalloonTipIcon {
201 if (genericNotifyIcon != null)
202 return genericNotifyIcon.BalloonTipIcon;
204 return windowsNotifyIcon.BalloonTipIcon;
207 if (genericNotifyIcon != null)
208 genericNotifyIcon.BalloonTipIcon = value;
210 windowsNotifyIcon.BalloonTipIcon = value;
214 public string BalloonTipTitle {
216 if (genericNotifyIcon != null)
217 return genericNotifyIcon.BalloonTipTitle;
219 return windowsNotifyIcon.BalloonTipTitle;
222 if (genericNotifyIcon != null)
223 genericNotifyIcon.BalloonTipTitle = value;
225 windowsNotifyIcon.BalloonTipTitle = value;
229 public ContextMenu ContextMenu {
231 if (genericNotifyIcon != null)
232 return genericNotifyIcon.ContextMenu;
234 return windowsNotifyIcon.ContextMenu;
237 if (genericNotifyIcon != null)
238 genericNotifyIcon.ContextMenu = value;
240 windowsNotifyIcon.ContextMenu = value;
244 public ContextMenuStrip ContextMenuStrip {
246 if (genericNotifyIcon != null)
247 return genericNotifyIcon.ContextMenuStrip;
249 return windowsNotifyIcon.ContextMenuStrip;
252 if (genericNotifyIcon != null)
253 genericNotifyIcon.ContextMenuStrip = value;
255 windowsNotifyIcon.ContextMenuStrip = value;
259 public object Tag { get; set; }
263 if (genericNotifyIcon != null)
264 return genericNotifyIcon.Icon;
266 return windowsNotifyIcon.Icon;
269 if (genericNotifyIcon != null)
270 genericNotifyIcon.Icon = value;
272 windowsNotifyIcon.Icon = value;
278 if (genericNotifyIcon != null)
279 return genericNotifyIcon.Text;
281 return windowsNotifyIcon.Text;
284 if (genericNotifyIcon != null)
285 genericNotifyIcon.Text = value;
287 windowsNotifyIcon.Text = value;
291 public bool Visible {
293 if (genericNotifyIcon != null)
294 return genericNotifyIcon.Visible;
296 return windowsNotifyIcon.Visible;
299 if (genericNotifyIcon != null)
300 genericNotifyIcon.Visible = value;
302 windowsNotifyIcon.Visible = value;
306 public void Dispose() {
307 if (genericNotifyIcon != null)
308 genericNotifyIcon.Dispose();
310 windowsNotifyIcon.Dispose();
313 public void ShowBalloonTip(int timeout) {
314 ShowBalloonTip(timeout, BalloonTipTitle, BalloonTipText, BalloonTipIcon);
317 public void ShowBalloonTip(int timeout, string tipTitle, string tipText,
318 ToolTipIcon tipIcon) {
319 if (genericNotifyIcon != null)
320 genericNotifyIcon.ShowBalloonTip(timeout, tipTitle, tipText, tipIcon);
322 windowsNotifyIcon.ShowBalloonTip(timeout, tipTitle, tipText, tipIcon);
325 private class NotifyIconWindowsImplementation : Component {
327 private static int nextId = 0;
329 private object syncObj = new object();
331 private string text = "";
333 private bool created;
334 private NotifyIconNativeWindow window;
335 private bool doubleClickDown;
336 private bool visible;
337 private MethodInfo commandDispatch;
339 public event EventHandler BalloonTipClicked;
340 public event EventHandler BalloonTipClosed;
341 public event EventHandler BalloonTipShown;
342 public event EventHandler Click;
343 public event EventHandler DoubleClick;
344 public event MouseEventHandler MouseClick;
345 public event MouseEventHandler MouseDoubleClick;
346 public event MouseEventHandler MouseDown;
347 public event MouseEventHandler MouseMove;
348 public event MouseEventHandler MouseUp;
350 public string BalloonTipText { get; set; }
351 public ToolTipIcon BalloonTipIcon { get; set; }
352 public string BalloonTipTitle { get; set; }
353 public ContextMenu ContextMenu { get; set; }
354 public ContextMenuStrip ContextMenuStrip { get; set; }
355 public object Tag { get; set; }
364 UpdateNotifyIcon(visible);
377 if (value.Length > 63)
378 throw new ArgumentOutOfRangeException();
380 if (!value.Equals(text)) {
384 UpdateNotifyIcon(visible);
389 public bool Visible {
394 if (visible != value) {
396 UpdateNotifyIcon(visible);
401 public NotifyIconWindowsImplementation() {
403 BalloonTipTitle = "";
405 commandDispatch = typeof(Form).Assembly.
406 GetType("System.Windows.Forms.Command").GetMethod("DispatchID",
407 BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
408 null, new Type[] { typeof(int) }, null);
410 id = ++NotifyIconWindowsImplementation.nextId;
411 window = new NotifyIconNativeWindow(this);
412 UpdateNotifyIcon(visible);
415 protected override void Dispose(bool disposing) {
417 if (window != null) {
420 UpdateNotifyIcon(false);
421 window.DestroyHandle();
424 ContextMenuStrip = null;
427 if (window != null && window.Handle != IntPtr.Zero) {
428 NativeMethods.PostMessage(
429 new HandleRef(window, window.Handle), WM_CLOSE, 0, 0);
430 window.ReleaseHandle();
433 base.Dispose(disposing);
436 public void ShowBalloonTip(int timeout) {
437 ShowBalloonTip(timeout, BalloonTipTitle, BalloonTipText, BalloonTipIcon);
440 public void ShowBalloonTip(int timeout, string tipTitle, string tipText,
441 ToolTipIcon tipIcon) {
443 throw new ArgumentOutOfRangeException("timeout");
445 if (string.IsNullOrEmpty(tipText))
446 throw new ArgumentException("tipText");
452 NativeMethods.NotifyIconData data = new NativeMethods.NotifyIconData();
453 if (window.Handle == IntPtr.Zero)
454 window.CreateHandle(new CreateParams());
456 data.Window = window.Handle;
458 data.Flags = NativeMethods.NotifyIconDataFlags.Info;
459 data.TimeoutOrVersion = timeout;
460 data.InfoTitle = tipTitle;
462 data.InfoFlags = (int)tipIcon;
464 NativeMethods.Shell_NotifyIcon(
465 NativeMethods.NotifyIconMessage.Modify, data);
469 private void ShowContextMenu() {
470 if (ContextMenu == null && ContextMenuStrip == null)
473 NativeMethods.Point p = new NativeMethods.Point();
474 NativeMethods.GetCursorPos(ref p);
475 NativeMethods.SetForegroundWindow(
476 new HandleRef(window, window.Handle));
478 if (ContextMenu != null) {
479 ContextMenu.GetType().InvokeMember("OnPopup",
480 BindingFlags.NonPublic | BindingFlags.InvokeMethod |
481 BindingFlags.Instance, null, ContextMenu,
482 new Object[] { System.EventArgs.Empty });
484 NativeMethods.TrackPopupMenuEx(
485 new HandleRef(ContextMenu, ContextMenu.Handle), 72,
486 p.x, p.y, new HandleRef(window, window.Handle),
489 NativeMethods.PostMessage(
490 new HandleRef(window, window.Handle), WM_NULL, 0, 0);
494 if (ContextMenuStrip != null)
495 ContextMenuStrip.GetType().InvokeMember("ShowInTaskbar",
496 BindingFlags.NonPublic | BindingFlags.InvokeMethod |
497 BindingFlags.Instance, null, ContextMenuStrip,
498 new Object[] { p.x, p.y });
501 private void UpdateNotifyIcon(bool showNotifyIcon) {
506 window.LockReference(showNotifyIcon);
508 NativeMethods.NotifyIconData data = new NativeMethods.NotifyIconData();
509 data.CallbackMessage = WM_TRAYMOUSEMESSAGE;
510 data.Flags = NativeMethods.NotifyIconDataFlags.Message;
512 if (showNotifyIcon && window.Handle == IntPtr.Zero)
513 window.CreateHandle(new CreateParams());
515 data.Window = window.Handle;
519 data.Flags |= NativeMethods.NotifyIconDataFlags.Icon;
520 data.Icon = icon.Handle;
523 data.Flags |= NativeMethods.NotifyIconDataFlags.Tip;
526 if (showNotifyIcon && icon != null) {
530 created = NativeMethods.Shell_NotifyIcon(
531 NativeMethods.NotifyIconMessage.Add, data);
533 System.Threading.Thread.Sleep(200);
536 } while (!created && i < 40);
538 NativeMethods.Shell_NotifyIcon(
539 NativeMethods.NotifyIconMessage.Modify, data);
544 bool deleted = false;
546 deleted = NativeMethods.Shell_NotifyIcon(
547 NativeMethods.NotifyIconMessage.Delete, data);
549 System.Threading.Thread.Sleep(200);
552 } while (!deleted && i < 40);
559 private void ProcessMouseDown(ref Message message, MouseButtons button,
562 if (DoubleClick != null)
563 DoubleClick(this, new MouseEventArgs(button, 2, 0, 0, 0));
565 if (MouseDoubleClick != null)
566 MouseDoubleClick(this, new MouseEventArgs(button, 2, 0, 0, 0));
568 doubleClickDown = true;
571 if (MouseDown != null)
573 new MouseEventArgs(button, doubleClick ? 2 : 1, 0, 0, 0));
576 private void ProcessMouseUp(ref Message message, MouseButtons button) {
578 MouseUp(this, new MouseEventArgs(button, 0, 0, 0, 0));
580 if (!doubleClickDown) {
582 Click(this, new MouseEventArgs(button, 0, 0, 0, 0));
584 if (MouseClick != null)
585 MouseClick(this, new MouseEventArgs(button, 0, 0, 0, 0));
587 doubleClickDown = false;
590 private void ProcessInitMenuPopup(ref Message message) {
591 if (ContextMenu != null &&
592 (bool)ContextMenu.GetType().InvokeMember("ProcessInitMenuPopup",
593 BindingFlags.NonPublic | BindingFlags.InvokeMethod |
594 BindingFlags.Instance, null, ContextMenu,
595 new Object[] { message.WParam })) {
598 window.DefWndProc(ref message);
601 private void WndProc(ref Message message) {
602 switch (message.Msg) {
604 UpdateNotifyIcon(false);
607 if (message.LParam != IntPtr.Zero) {
608 window.DefWndProc(ref message);
611 commandDispatch.Invoke(null, new object[] {
612 message.WParam.ToInt32() & 0xFFFF });
614 case WM_INITMENUPOPUP:
615 ProcessInitMenuPopup(ref message);
617 case WM_TRAYMOUSEMESSAGE:
618 switch ((int)message.LParam) {
620 if (MouseMove != null)
622 new MouseEventArgs(Control.MouseButtons, 0, 0, 0, 0));
625 ProcessMouseDown(ref message, MouseButtons.Left, false);
628 ProcessMouseUp(ref message, MouseButtons.Left);
630 case WM_LBUTTONDBLCLK:
631 ProcessMouseDown(ref message, MouseButtons.Left, true);
634 ProcessMouseDown(ref message, MouseButtons.Right, false);
637 if (ContextMenu != null || ContextMenuStrip != null)
639 ProcessMouseUp(ref message, MouseButtons.Right);
641 case WM_RBUTTONDBLCLK:
642 ProcessMouseDown(ref message, MouseButtons.Right, true);
645 ProcessMouseDown(ref message, MouseButtons.Middle, false);
648 ProcessMouseUp(ref message, MouseButtons.Middle);
650 case WM_MBUTTONDBLCLK:
651 ProcessMouseDown(ref message, MouseButtons.Middle, true);
653 case NIN_BALLOONSHOW:
654 if (BalloonTipShown != null)
655 BalloonTipShown(this, EventArgs.Empty);
657 case NIN_BALLOONHIDE:
658 case NIN_BALLOONTIMEOUT:
659 if (BalloonTipClosed != null)
660 BalloonTipClosed(this, EventArgs.Empty);
662 case NIN_BALLOONUSERCLICK:
663 if (BalloonTipClicked != null)
664 BalloonTipClicked(this, EventArgs.Empty);
671 if (message.Msg == NotifyIconWindowsImplementation.WM_TASKBARCREATED) {
675 UpdateNotifyIcon(visible);
678 window.DefWndProc(ref message);
681 private class NotifyIconNativeWindow : NativeWindow {
682 private NotifyIconWindowsImplementation reference;
683 private GCHandle referenceHandle;
685 internal NotifyIconNativeWindow(NotifyIconWindowsImplementation component) {
686 this.reference = component;
689 ~NotifyIconNativeWindow() {
690 if (base.Handle != IntPtr.Zero)
691 NativeMethods.PostMessage(
692 new HandleRef(this, base.Handle), WM_CLOSE, 0, 0);
695 public void LockReference(bool locked) {
697 if (!referenceHandle.IsAllocated) {
698 referenceHandle = GCHandle.Alloc(reference, GCHandleType.Normal);
702 if (referenceHandle.IsAllocated)
703 referenceHandle.Free();
707 protected override void OnThreadException(Exception e) {
708 Application.OnThreadException(e);
711 protected override void WndProc(ref Message m) {
712 reference.WndProc(ref m);
716 private const int WM_NULL = 0x00;
717 private const int WM_DESTROY = 0x02;
718 private const int WM_CLOSE = 0x10;
719 private const int WM_COMMAND = 0x111;
720 private const int WM_INITMENUPOPUP = 0x117;
721 private const int WM_MOUSEMOVE = 0x200;
722 private const int WM_LBUTTONDOWN = 0x201;
723 private const int WM_LBUTTONUP = 0x202;
724 private const int WM_LBUTTONDBLCLK = 0x203;
725 private const int WM_RBUTTONDOWN = 0x204;
726 private const int WM_RBUTTONUP = 0x205;
727 private const int WM_RBUTTONDBLCLK = 0x206;
728 private const int WM_MBUTTONDOWN = 0x207;
729 private const int WM_MBUTTONUP = 0x208;
730 private const int WM_MBUTTONDBLCLK = 0x209;
731 private const int WM_TRAYMOUSEMESSAGE = 0x800;
733 private const int NIN_BALLOONSHOW = 0x402;
734 private const int NIN_BALLOONHIDE = 0x403;
735 private const int NIN_BALLOONTIMEOUT = 0x404;
736 private const int NIN_BALLOONUSERCLICK = 0x405;
738 private static int WM_TASKBARCREATED =
739 NativeMethods.RegisterWindowMessage("TaskbarCreated");
741 private static class NativeMethods {
742 [DllImport("user32.dll", CharSet = CharSet.Auto)]
743 public static extern IntPtr PostMessage(HandleRef hwnd, int msg,
744 int wparam, int lparam);
746 [DllImport("user32.dll", CharSet = CharSet.Auto)]
747 public static extern int RegisterWindowMessage(string msg);
750 public enum NotifyIconDataFlags : int {
758 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
759 public class NotifyIconData {
760 private int Size = Marshal.SizeOf(typeof(NotifyIconData));
761 public IntPtr Window;
763 public NotifyIconDataFlags Flags;
764 public int CallbackMessage;
766 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
769 public int StateMask;
770 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
772 public int TimeoutOrVersion;
773 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
774 public string InfoTitle;
775 public int InfoFlags;
778 public enum NotifyIconMessage : int {
784 [DllImport("shell32.dll", CharSet = CharSet.Auto)]
785 [return: MarshalAs(UnmanagedType.Bool)]
786 public static extern bool Shell_NotifyIcon(NotifyIconMessage message,
787 NotifyIconData pnid);
789 [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
790 public static extern bool TrackPopupMenuEx(HandleRef hmenu, int fuFlags,
791 int x, int y, HandleRef hwnd, IntPtr tpm);
793 [StructLayout(LayoutKind.Sequential)]
794 public struct Point {
799 [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
800 public static extern bool GetCursorPos(ref Point point);
802 [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
803 public static extern bool SetForegroundWindow(HandleRef hWnd);