osu-framework icon indicating copy to clipboard operation
osu-framework copied to clipboard

Add notification tray icon (boss key)

Open adryzz opened this issue 1 year ago • 7 comments

This PR adds support for creating/removing notification tray icons, needed for boss key support.

This implementation is windows-only, i have a linux one half working but it'll be its own separate patch.

On windows, notification tray icons are closely intertwined with windowing logic, so i had to add 2 methods to IWindow to handle that.

osu!-side PR: https://github.com/ppy/osu/pull/28972

https://github.com/user-attachments/assets/27f1c4f2-09d9-4742-bf0b-641dc3a94fd4

In my testing there's a problem with hiding the game window, and it looks like there's 2 windows, one that gets hidden and the other that remains, but i don't know if that's a framework issue or an osu! side issue.

https://github.com/user-attachments/assets/254eb751-b93b-483a-905d-779fe20a3098

EDIT: this only happens in fullscreen, borderless and windowed work just fine.

image

adryzz avatar Jul 20 '24 14:07 adryzz

SDL recently added tray icon support for windows, unix and macos. Documentation here: https://wiki.libsdl.org/SDL3/CategoryTray.

Using this API gets us the other two platforms for free.

Susko3 avatar Dec 25 '24 16:12 Susko3

that's awesome, i'll start using it then

adryzz avatar Dec 27 '24 10:12 adryzz

fixed conflicts, will now switch to the SDL3 API

adryzz avatar Dec 27 '24 10:12 adryzz

I think the API should be declarative, and make use of the SDL tray menu nesting.

public interface IDesktopWindow : IWindow
{
    // dispose to remove the tray icon
    IDisposable CreateTrayIcon(TrayIcon icon);
}


static void Main()
{
    IWindow window;
    if (window is IDesktopWindow desktopWindow)
    {
        var icon = new TrayIcon
        {
            Label = "osu!",
            Icon = imageSharpImage,
            Menu = new TrayMenuEntry[]
            {
                new TrayButton
                {
                    Label = "Open osu!",
                    Default = true, // label should be in bold and double-clicking the icon will invoke this action
                    Action = () => openOsuAndDisposeTray(),
                },
                new TraySubmenu
                {
                    Label = "Example submenu",
                    Entries = new TrayMenuEntry[]
                    {
                        new TrayCheckbox
                        {
                            Checked = true,
                            Action = b => Logger.Log(b ? "checked" : "unchecked"),
                        }
                    }
                },
                new TraySeparator(),
                new TrayButton
                {
                    Label = "Exit",
                    Action = () => closeTheGameAndDisposeTray(),
                }
            }
        }

        var activeTrayIcon = desktopWindow.CreateTrayIcon(icon)
    }
}

Example tray from above definition

We just walk the tree and call the appropriate SDL functions in order. The created tray icon and menu are readonly, a game can delete the old menu and create a new one if updates are needed.

I might take a stab at it next year.

Susko3 avatar Dec 31 '24 02:12 Susko3

yes, actually it's exactly the design i first had in mind when i started working on this many months ago.

when i get home tomorrow i'll get it all working and up to snuff

adryzz avatar Dec 31 '24 16:12 adryzz

alright got it to mostly work in a local branch, but the new version of ppy.SDL3-CS isn't released yet so it only works with a local nuget package

adryzz avatar Jan 02 '25 10:01 adryzz

alright, rebased on master and made use of the SDL3 API. things are working (tested on windows and linux), but still in a state of flux (the icon isn't set at the moment, and to make things work i had to make ISDLWindow public, which is very much not ideal

adryzz avatar Feb 21 '25 13:02 adryzz