Skip to content

Commit

Permalink
🎨 Implement left click and double click on NotifyIcon.
Browse files Browse the repository at this point in the history
  • Loading branch information
hexawyz committed Sep 3, 2024
1 parent 9c55765 commit 9727f26
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 16 deletions.
12 changes: 12 additions & 0 deletions src/Exo/Ui/Exo.Overlay/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,14 @@ public static unsafe uint RegisterWindowMessage(string name)
public const int WmNull = 0x0000;
public const int WmCreate = 0x0001;
public const int WmDestroy = 0x0002;
public const int WmSettingChange = 0x001A;
public const int WmWindowsPosChanging = 0x0046;
public const int WmClose = 0x0010;
public const int WmContextMenu = 0x007B;
public const int WmCommand = 0x0111;
public const int WmTimer = 0x0113;
public const int WmMenuCommand = 0x0126;
public const int WmLeftButtonUp = 0x0202;
public const int WmRightButtonUp = 0x0205;

[DllImport("user32", EntryPoint = "GetWindowLongW", ExactSpelling = true, SetLastError = true)]
Expand Down Expand Up @@ -448,4 +451,13 @@ public enum MenuStyles : uint

[DllImport("uxtheme", EntryPoint = "#135", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int SetPreferredAppMode(int preferredAppMode);

[DllImport("user32", EntryPoint = "SetTimer", SetLastError = true)]
public static extern nuint SetTimer(nint windowHandle, nuint eventId, uint time, nint timerCallback);

[DllImport("user32", EntryPoint = "KillTimer", SetLastError = true)]
public static extern uint KillTimer(nint windowHandle, nuint eventId);

[DllImport("user32", EntryPoint = "GetDoubleClickTime", SetLastError = true)]
public static extern uint GetDoubleClickTime();
}
77 changes: 61 additions & 16 deletions src/Exo/Ui/Exo.Overlay/NotificationWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ public static ValueTask<NotificationWindow> GetOrCreateAsync()
private readonly Dictionary<nint, WeakReference<PopupMenu>> _registeredMenus;
private nint _handle;
private int _isDisposed;
private int _doubleClickDelay;
private int _clickTimerNotifyIconId;
private uint _clickTimerClickPosition;

public nint Handle => _handle;

Expand Down Expand Up @@ -363,6 +366,7 @@ private void TryCreateWindowAndRunMessageLoop(TaskCompletionSource<NotificationW
{
var previous = Current;
SetSynchronizationContext(this);
_doubleClickDelay = (int)GetDoubleClickTime();
try
{
if (!TryCreateWindow(taskCompletionSource)) return;
Expand Down Expand Up @@ -422,34 +426,75 @@ private static void ProcessCallback(RegisteredCallback callback)

private unsafe nint WindowProcedure(uint message, nint wParam, nint lParam)
{
WeakReference<NotifyIcon>? wr;
NotifyIcon? icon;
switch (message)
{
case WmSettingChange:
_doubleClickDelay = (int)GetDoubleClickTime();
goto MessageProcessed;
case WmTimer:
if (wParam == _clickTimerNotifyIconId)
{
if (!_registeredIcons.TryGetValue((ushort)wParam, out wr) || !wr.TryGetTarget(out icon)) break;
_clickTimerNotifyIconId = -1;
wParam = (nint)(nuint)_clickTimerClickPosition;
_clickTimerClickPosition = 0;
goto HandleMenu;
}
goto MessageProcessed;
case WmMenuCommand:
HandleMenuCommand(lParam, (int)wParam);
return 0;
goto MessageProcessed;
case IconMessageId:
if (!_registeredIcons.TryGetValue((ushort)(lParam >>> 16), out var wr) || !wr.TryGetTarget(out var icon)) break;
if (!_registeredIcons.TryGetValue((ushort)(lParam >>> 16), out wr) || !wr.TryGetTarget(out icon)) break;
switch ((ushort)lParam)
{
case WmLeftButtonUp:
if (_clickTimerNotifyIconId >= 0)
{
KillTimer(_handle, (uint)_clickTimerNotifyIconId);
if (icon.IconId == _clickTimerNotifyIconId)
{
_clickTimerNotifyIconId = -1;
_clickTimerClickPosition = 0;
icon.OnDoubleClick();
goto MessageProcessed;
}
}
_clickTimerNotifyIconId = icon.IconId;
_clickTimerClickPosition = (uint)wParam;
SetTimer(_handle, (uint)_clickTimerNotifyIconId, (uint)_doubleClickDelay, 0);
goto MessageProcessed;
case WmContextMenu:
case WmRightButtonUp:
SetForegroundWindow(_handle);
var result = TrackPopupMenuEx
(
icon.ContextMenu.Handle,
TrackPopupMenuOptions.RightAlign | TrackPopupMenuOptions.BottomAlign | TrackPopupMenuOptions.RightButton,
(short)(ushort)wParam,
(short)(ushort)(wParam >>> 16),
_handle,
null
);
PostMessage(_handle, WmNull, 0, 0);
return 0;
if (_clickTimerNotifyIconId >= 0)
{
KillTimer(_handle, (uint)_clickTimerNotifyIconId);
_clickTimerNotifyIconId = -1;
_clickTimerClickPosition = 0;
}
goto HandleMenu;
}
return 0;
goto MessageProcessed;
}

return DefWindowProc(_handle, message, wParam, lParam);

HandleMenu:;
SetForegroundWindow(_handle);
var result = TrackPopupMenuEx
(
icon.ContextMenu.Handle,
TrackPopupMenuOptions.RightAlign | TrackPopupMenuOptions.BottomAlign | TrackPopupMenuOptions.RightButton,
(short)(ushort)wParam,
(short)(ushort)(wParam >>> 16),
_handle,
null
);
PostMessage(_handle, WmNull, 0, 0);

MessageProcessed:;
return 0;
}

private void HandleMenuCommand(nint menuHandle, int itemIndex)
Expand Down
4 changes: 4 additions & 0 deletions src/Exo/Ui/Exo.Overlay/NotifyIcon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ internal sealed class NotifyIcon : NotificationControl
#pragma warning restore IDE0044 // Add readonly modifier
private bool _isCreated;

public event EventHandler DoubleClick;

internal ushort IconId => _iconId;

internal NotifyIcon(NotificationWindow notificationWindow, ushort iconId, int iconResourceId, string tooltipText)
Expand Down Expand Up @@ -120,4 +122,6 @@ private unsafe void UpdateIcon(NotifyIconFeatures changedFeatures)
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
}

internal void OnDoubleClick() => DoubleClick?.Invoke(this, EventArgs.Empty);
}
1 change: 1 addition & 0 deletions src/Exo/Ui/Exo.Overlay/NotifyIconService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public NotifyIconService(NotificationWindow window, ServiceConnectionManager ser
_window = window;
_icon = window.CreateNotifyIcon(0, 32512, "Exo");
var menu = _icon.ContextMenu;
_icon.DoubleClick += OnSettingsMenuItemClick;
var settingsMenuItem = new TextMenuItem("&Settings…");
settingsMenuItem.Click += OnSettingsMenuItemClick;
settingsMenuItem.IsEnabled = App.SettingsUiExecutablePath is not null;
Expand Down

0 comments on commit 9727f26

Please sign in to comment.