Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add notification tray icon on windows (boss key) #6341

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions osu.Framework/Platform/IWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,5 +265,11 @@ public interface IWindow : IDisposable
/// The window title.
/// </summary>
string Title { get; set; }

IBindable<NotificationTrayIcon?> TrayIcon { get; }

public void CreateNotificationTrayIcon(string text, Action? onClick);

public void RemoveNotificationTrayIcon();
}
}
34 changes: 34 additions & 0 deletions osu.Framework/Platform/NotificationTrayIcon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;
using osu.Framework.Platform.Windows;

namespace osu.Framework.Platform
{
/// <summary>
/// Represents an icon located in the OS notification tray.
/// </summary>
public abstract class NotificationTrayIcon : IDisposable
{
/// <summary>
/// The hint text shown when hovering over the icon with the cursor
/// </summary>
public string Text { get; init; } = string.Empty;

/// <summary>
/// The action to perform when the icon gets clicked
/// </summary>
public Action? OnClick { get; init; }

public static NotificationTrayIcon Create(string text, Action? onClick, IWindow window)
{
switch (RuntimeInfo.OS)
{
case RuntimeInfo.Platform.Windows:
return new WindowsNotificationTrayIcon(text, onClick, window);

Check warning on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Build only (iOS)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Build only (iOS)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (macOS, macos-latest, Debug, MultiThreaded)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (macOS, macos-latest, Debug, MultiThreaded)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (macOS, macos-latest, Debug, SingleThread)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (macOS, macos-latest, Debug, SingleThread)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Release, MultiThreaded)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Release, MultiThreaded)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Debug, SingleThread)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Debug, SingleThread)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Release, SingleThread)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Release, SingleThread)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Debug, MultiThreaded)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Linux, ubuntu-latest, Debug, MultiThreaded)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, Debug, MultiThreaded)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, Debug, MultiThreaded)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, Debug, SingleThread)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check failure on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Test (Windows, windows-latest, Debug, SingleThread)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Build only (Android)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 26 in osu.Framework/Platform/NotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Build only (Android)

This call site is reachable on all platforms. 'WindowsNotificationTrayIcon' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
}

throw new PlatformNotSupportedException();
}

public abstract void Dispose();
}
}
21 changes: 21 additions & 0 deletions osu.Framework/Platform/SDL2/SDL2Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,27 @@ private unsafe void setSDLIcon(Image<Rgba32> image)
});
}

public IBindable<NotificationTrayIcon?> TrayIcon => trayIcon;

private Bindable<NotificationTrayIcon?> trayIcon = new Bindable<NotificationTrayIcon?>(null);

public virtual void CreateNotificationTrayIcon(string text, Action? onClick)
{
if (trayIcon.Value is not null)
{
throw new InvalidOperationException("a notification tray icon already exists!");
}

NotificationTrayIcon icon = NotificationTrayIcon.Create(text, onClick, this);
trayIcon.Value = icon;
}

public virtual void RemoveNotificationTrayIcon()
{
trayIcon.Value?.Dispose();
trayIcon.Value = null;
}

#region SDL Event Handling

/// <summary>
Expand Down
11 changes: 11 additions & 0 deletions osu.Framework/Platform/SDL3/SDL3MobileWindow.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using SDL;
using static SDL.SDL3;

Expand All @@ -20,5 +21,15 @@ protected override unsafe void UpdateWindowStateAndSize(WindowState state, Displ

// Don't run base logic at all. Let's keep things simple.
}

public override void CreateNotificationTrayIcon(string text, Action? onClick)
{
throw new PlatformNotSupportedException();
}

public override void RemoveNotificationTrayIcon()
{
throw new PlatformNotSupportedException();
}
}
}
21 changes: 21 additions & 0 deletions osu.Framework/Platform/SDL3/SDL3Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,27 @@ private void setSDLIcon(Image<Rgba32> image)
});
}

public IBindable<NotificationTrayIcon?> TrayIcon => trayIcon;

private Bindable<NotificationTrayIcon?> trayIcon = new Bindable<NotificationTrayIcon?>(null);

public virtual void CreateNotificationTrayIcon(string text, Action? onClick)
{
if (trayIcon.Value is not null)
{
throw new InvalidOperationException("a notification tray icon already exists!");
}

NotificationTrayIcon icon = NotificationTrayIcon.Create(text, onClick, this);
trayIcon.Value = icon;
}

public virtual void RemoveNotificationTrayIcon()
{
trayIcon.Value?.Dispose();
trayIcon.Value = null;
}

#region SDL Event Handling

/// <summary>
Expand Down
10 changes: 8 additions & 2 deletions osu.Framework/Platform/Windows/SDL2WindowsWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
private const int large_icon_size = 256;
private const int small_icon_size = 16;

private Icon? smallIcon;
private Icon? largeIcon;
internal Icon? smallIcon;

Check failure on line 28 in osu.Framework/Platform/Windows/SDL2WindowsWindow.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Naming rule violation: These words must begin with upper case characters: smallIcon (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)
internal Icon? largeIcon;

Check failure on line 29 in osu.Framework/Platform/Windows/SDL2WindowsWindow.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Naming rule violation: These words must begin with upper case characters: largeIcon (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)

private const int wm_killfocus = 8;

Expand Down Expand Up @@ -99,6 +99,12 @@

switch (m.msg)
{
case WindowsNotificationTrayIcon.TRAYICON:
if (WindowsNotificationTrayIcon.IsClick(m.lParam))
{
TrayIcon.Value?.OnClick?.Invoke();
}
break;
case wm_killfocus:
warpCursorFromFocusLoss();
break;
Expand Down
10 changes: 8 additions & 2 deletions osu.Framework/Platform/Windows/SDL3WindowsWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
private const int large_icon_size = 256;
private const int small_icon_size = 16;

private Icon? smallIcon;
private Icon? largeIcon;
internal Icon? smallIcon;

Check failure on line 32 in osu.Framework/Platform/Windows/SDL3WindowsWindow.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Naming rule violation: These words must begin with upper case characters: smallIcon (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)
internal Icon? largeIcon;

Check failure on line 33 in osu.Framework/Platform/Windows/SDL3WindowsWindow.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Naming rule violation: These words must begin with upper case characters: largeIcon (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)

/// <summary>
/// Whether to apply the <see cref="windows_borderless_width_hack"/>.
Expand Down Expand Up @@ -82,6 +82,12 @@
{
switch (msg.message)
{
case WindowsNotificationTrayIcon.TRAYICON:
if (WindowsNotificationTrayIcon.IsClick(msg.lParam))
{
TrayIcon.Value?.OnClick?.Invoke();
}
break;
case Imm.WM_IME_STARTCOMPOSITION:
case Imm.WM_IME_COMPOSITION:
case Imm.WM_IME_ENDCOMPOSITION:
Expand Down
168 changes: 168 additions & 0 deletions osu.Framework/Platform/Windows/WindowsNotificationTrayIcon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using osu.Framework.Logging;

namespace osu.Framework.Platform.Windows
{
/// <summary>
/// A windows specific notification tray icon,
/// </summary>
[SupportedOSPlatform("windows")]
internal partial class WindowsNotificationTrayIcon : NotificationTrayIcon
{
internal IWindowsWindow window = null!;

Check failure on line 17 in osu.Framework/Platform/Windows/WindowsNotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Naming rule violation: These words must begin with upper case characters: window (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)

private NOTIFYICONDATAW inner;

internal WindowsNotificationTrayIcon(string text, Action? onClick, IWindow win)
{

if (win is not IWindowsWindow w)
{
throw new PlatformNotSupportedException();
}

window = w;
Text = text;
OnClick = onClick;

NotifyIconFlags flags = NotifyIconFlags.NIF_MESSAGE | NotifyIconFlags.NIF_ICON | NotifyIconFlags.NIF_TIP | NotifyIconFlags.NIF_SHOWTIP;
IntPtr iconHandle = IntPtr.Zero;
IntPtr hwnd;

if (window is SDL3WindowsWindow w3)
{
hwnd = w3.WindowHandle;
if (w3.smallIcon is not null)
{
iconHandle = w3.smallIcon.Handle;
}
}
else if (window is SDL2WindowsWindow w2)
{
hwnd = w2.WindowHandle;
if (w2.smallIcon is not null)
{
iconHandle = w2.smallIcon.Handle;
}
}
else
{
throw new PlatformNotSupportedException("Invalid windowing backend");
}

inner = new NOTIFYICONDATAW
{
cbSize = Marshal.SizeOf(inner),
uFlags = flags,
hIcon = iconHandle,
hWnd = hwnd,
szTip = text,
uCallbackMessage = TRAYICON
};

bool ret = Shell_NotifyIconW(NotifyIconAction.NIM_ADD, ref inner);

inner.uTimeoutOrVersion = NOTIFYICON_VERSION_4;

Shell_NotifyIconW(NotifyIconAction.NIM_SETVERSION, ref inner);

if (!ret)
{
int err = Marshal.GetLastWin32Error();
Logger.Log($"Error {err} while creating notification tray icon", LoggingTarget.Runtime, LogLevel.Error);
}
}

public override void Dispose()
{
bool ret = Shell_NotifyIconW(NotifyIconAction.NIM_DELETE, ref inner);

if (!ret)
{
int err = Marshal.GetLastWin32Error();
Logger.Log($"Error {err} while removing notification tray icon", LoggingTarget.Runtime, LogLevel.Error);
}
}

private const int NOTIFYICON_VERSION_4 = 4;

Check failure on line 92 in osu.Framework/Platform/Windows/WindowsNotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Naming rule violation: These words cannot contain upper case characters: NOTIFYICON, VERSION (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)

internal const int TRAYICON = 0x0400 + 1024;
internal const int WM_LBUTTONUP = 0x0202;
internal const int WM_RBUTTONUP = 0x0205;
internal const int WM_MBUTTONUP = 0x0208;
internal const int NIN_SELECT = 0x400;

internal static bool IsClick(long lParam)
{
switch ((short)lParam)
{
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case NIN_SELECT:
return true;
default:
return false;
}
}

[Flags]
internal enum NotifyIconAction : uint
{
NIM_ADD = 0x00000000,
NIM_DELETE = 0x00000002,
NIM_SETVERSION = 0x00000004,
}

[DllImport("shell32.dll")]
internal static extern bool Shell_NotifyIconW(NotifyIconAction dwMessage, [In] ref NOTIFYICONDATAW pnid);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct NOTIFYICONDATAW
{
internal int cbSize;

Check failure on line 128 in osu.Framework/Platform/Windows/WindowsNotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Naming rule violation: These words must begin with upper case characters: cbSize (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)
internal IntPtr hWnd;

Check failure on line 129 in osu.Framework/Platform/Windows/WindowsNotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Naming rule violation: These words must begin with upper case characters: hWnd (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)
internal int uID;

Check failure on line 130 in osu.Framework/Platform/Windows/WindowsNotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Naming rule violation: These words must begin with upper case characters: uID (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)
internal NotifyIconFlags uFlags;

Check failure on line 131 in osu.Framework/Platform/Windows/WindowsNotificationTrayIcon.cs

View workflow job for this annotation

GitHub Actions / Code Quality

Naming rule violation: These words must begin with upper case characters: uFlags (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)
internal int uCallbackMessage;
internal IntPtr hIcon;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
internal string szTip;
internal int dwState;
internal int dwStateMask;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
internal string szInfo;
internal int uTimeoutOrVersion;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
internal string szInfoTitle;
internal int dwInfoFlags;
internal Guid guidItem;
internal IntPtr hBalloonIcon;
}

[Flags]
internal enum NotifyIconFlags : uint
{
NIF_MESSAGE = 0x00000001,
NIF_ICON = 0x00000002,
NIF_TIP = 0x00000004,
NIF_STATE = 0x00000008,
NIF_INFO = 0x00000010,
NIF_GUID = 0x00000020,
NIF_SHOWTIP = 0x00000080
}

internal enum ToolTipIcon
{
None = 0,
Info = 1,
Warning = 2,
Error = 3
}
}
}
Loading