wailsapp / wails

Create beautiful applications using Go
https://wails.io
MIT License
24.56k stars 1.18k forks source link

Notification support #1788

Open tonyho opened 2 years ago

tonyho commented 2 years ago

Is your feature request related to a problem? Please describe.

We want to show a notification toast to hint information to users, but it seems no such function in Wails and its roadmap.

Describe the solution you'd like

Notification should be added.

Describe alternatives you've considered

Toast function which is similiar to Android Toas.

Additional context

No response

lyimmi commented 2 years ago

I don't know if there is a plan for notifications, but beeep is a good package for native notifications IMO: https://github.com/gen2brain/beeep .

leaanthony commented 2 years ago

Added to Roadmap 👍

lyimmi commented 1 year ago

As @leaanthony suggested, I'm trying to put together an API proposal and I'm looking at the notification capabilities of Windows, macOs and linux. Windows has the most extensive functionality, linux and macOS has a "simpler" API.

My question would be, what is the goal, a common API with functions that work everywhere, or one where each OS can have its own settings, or some form of hybrid from the two with an override style solution.

Case one: A common API for all supported OS with "limited" functionality.

We may lose some functionality on windows, but when you create a notification it would behave the same way on every OS.

Case two: An API with separate options for each OS.

One would have to configure every notification for each OS they want to support, probably the only common options for a notification would be the title and a message (and maybe action buttons).

Case three: An API with a "limited" common functionality but with separate options for each OS.

The main API would work as described in the first case with the basic functionality, but has OS specific options like timing, sound, position, or for windows status/download bars, etc.

leaanthony commented 1 year ago

Awesome @Lyimmi ! As far as API goes, it's ok to have a single struct of options for all platforms where options are ignored if not supported 👍

lyimmi commented 1 year ago

Here is my first draft of the Notification API. I think if we want to have more than a title, message and an icon, we need to have OS specific options. As far as I can tell Electron only supports these three options as default.

Even the timeout is different on each os, on linux it's set in milliseconds, on windows it's a dateTime, on macOs its not available. Handling the actions / buttons is totally different as well.

Maybe the click on the notification could be handled commonly too.

It is more of a conversation starter than a complete API.


// LinuxActionInvokedHandler handles signals returned by activating actions
type LinuxActionInvokedHandler func(signal *LinuxNotificationActionInvokedSignal, options ...interface{})

// LinuxNotificationAction represents a Notification action for a notification
type LinuxNotificationAction struct {

    // Key is the actions's identifier
    Key string

    // Label is shown on the notification
    Label string
}

// LinuxNotificationActionInvokedSignal holds data from any signal received regarding Actions invoked
type LinuxNotificationActionInvokedSignal struct {

    // ID of the Notification the action was invoked for
    NotificationID uint32

    // Key from the activated action
    ActionKey string
}

type LinuxNotificationOptions struct {

    // ReplacesID is used to replace an existing notification.
    ReplacesID uint32

    // Actions to be shown.
    Actions []LinuxNotificationAction

    // OnAction handles the activated action's signal.
    OnAction LinuxActionInvokedHandler

    // Timeout of the notification
    Timeout time.Duration
}

// NotificationProgressBar shows a progress bar. (Windows only)
type NotificationProgressBar struct {

    // Gets or sets an optional title string. Supports data binding.
    Title string

    // Gets or sets the value of the progress bar. Supports data binding.
    // Defaults to 0. Can either be a double between 0.0 and 1.0, AdaptiveProgressBarValue.
    // Indeterminate, or new BindableProgressBarValue("myProgressValue").
    Value float32

    // Gets or sets an optional string to be displayed instead of the default percentage string.
    // If this isn't provided, something like "70%" will be displayed.
    ValueStringOverride string

    // Gets or sets a status string (required), which is displayed underneath the progress bar on the left.
    // This string should reflect the status of the operation, like "Downloading..." or "Installing..."
    Status string
}

// NotificationHeroImage A featured "hero" image that is displayed on the toast and within Action Center. (Windows only)
type NotificationHeroImage struct {

    // The URL to the image. ms-appx, ms-appdata, and http are supported.
    // Http images must be 200 KB or less in size.
    Source string

    // Alternate text describing the image, used for accessibility purposes.
    AlternateText string

    // Set to "true" to allow Windows to append a query string to the image URL
    // supplied in the toast notification. Use this attribute if your server hosts
    // images and can handle query strings, either by retrieving an image
    // variant based on the query strings or by ignoring the query string and
    // returning the image as specified without the query string.
    //
    // This query string specifies scale, contrast setting, and language; for instance,
    // a value of "www.website.com/images/hello.png" given in the notification becomes
    // "www.website.com/images/hello.png?ms-scale=100&ms-contrast=standard&ms-lang=en-us"
    AddImageQuery bool
}

type WindowsNotificationOptions struct {

    // HeroImage shows a full size image.
    HeroImage *NotificationHeroImage

    // ProgressBar shows a progress bar style notification.
    ProgressBar *NotificationProgressBar

        // ExpirationTime sets the expiration time for the notification.
        ExpirationTime time.Time
}

// NotificationOptions sets up a notification to be sent.
type NotificationOptions struct {

    // Applications identifier.
    AppID string

    // AppIcon used to show an icon on the notification. Absolute path to the icon.
    AppIcon string

    // Title is a summary for the notification.
    Title string

    // Message is the notification's message.
    Message string

    // LinuxOptions holds the linux specific options for a notification.
    LinuxOptions *LinuxNotificationOptions

    // WindowsOptions holds the windows specific options for a notification.
    WindowsOptions *WindowsNotificationOptions

    // Timeout is the duration for how long a notification is shown.
    Timeout time.Duration
}

Usage would look something like this:

no := NotificationOptions{
        AppID:      "Wails test",
        AppIcon:    "/wails/build/appicon.png",
        Title:      "wails test",
        Message:    name,
        LinuxOptions: &LinuxNotificationOptions{
            ReplacesID: 0,
            Actions: []LinuxNotificationAction{
                {Key: "btn-1", Label: "Button 1"},
                {Key: "btn-2", Label: "Button 2"},
            },
            OnAction: func(signal *LinuxNotificationActionInvokedSignal, options ...interface{}) {
                fmt.Printf("key: %v, label: %v", signal.NotificationID, signal.ActionKey)
            },
            Timeout: time.Second * 10,
        },
        WindowsOptions: &WindowsNotificationOptions{
            HeroImage: &NotificationHeroImage{
                Source:        "/path/to/image.jpg",
                AlternateText: "alternate text",
                AddImageQuery: false,
            },
            ProgressBar: &NotificationProgressBar{
                Title:               "title",
                Value:               0.5,
                ValueStringOverride: "10 out of a 100",
                Status:              "progress...",
            },
                        ExpirationTime: time.Now().Add(24 * time.Hour),
        },
    }

    runtime.SendNotification(ctx, no)
willdot commented 1 year ago

For a work side project, I needed something to send notifications on macOS. All of the existing libraries I found were either deprecated or no longer maintained (and so features didn't work on recent versions of macOS), or the beeep one suggested earlier in this issue would cause script manager to launch when used, which isn't nice UX.

However I found a library / binary (written in Objective-C) that is maintained and fairly feature complete but needed a Go library to work with it. So I created one that I'm using.

https://github.com/willdot/gomacosnotify

It does require the Alterer binary to be embedded and then installed into a temp location on the users machine, however I'm sure there's a way to stop that so that it's opt in (ie you want notifications and so the binary then gets used), possibly via build tags?

Happy to work together to get something working for notifications working (on macOS) using the library I've created and open to API changes etc.

lyimmi commented 1 year ago

Hi, @leaanthony, @stffabi, @willdot

I started implementing the notification support on this branch: https://github.com/Lyimmi/wails/tree/feature/1788_notification_support

It is still in early stages and currently only supports linux/dbus (started the nofiy-send and kdialog fallbacks but with a minimal functionality).

Should I make a draft pull request, or wait for a more complete stage? It would be nice if someone else looked at it. Whether it's going in the right direction at all.

Thanks!

Edit: Made some changes to the API, currently this is how it looks like.

res, err := runtime.SendNotification(a.ctx, runtime.NotificationOptions{
        AppIcon: "/wailst/build/appicon.png",
        Title:   "This is a title",
        Message: "This is a message",
        Timeout: 60 * time.Second,
        LinuxOptions: &runtime.LinuxNotificationOptions{
            Actions: []runtime.LinuxNotificationAction{
                {
                    Key:   "maximize",
                    Label: "Maximize",
                    OnAction: func(signal *runtime.LinuxNotificationActionInvokedSignal) {
                        runtime.WindowMaximise(a.ctx)
                    },
                },
                {
                    Key:   "minimize",
                    Label: "Minimize",
                    OnAction: func(signal *runtime.LinuxNotificationActionInvokedSignal) {
                        runtime.WindowMinimise(a.ctx)
                    },
                },
            },
        },
    })
leaanthony commented 1 year ago

Thanks for the work on this @Lyimmi! I'm happy for you to push on with this. One question: is the path to the icon something that'll work at runtime or should we be using a []byte?

lyimmi commented 1 year ago

@leaanthony all supported OSes are using absolute paths as default and all of them have some optional unique way too. This is one of my pain points as well, because we cannot use embedded resources.

One way could be to use []byte as input and save the images to tmp if its not supported and then point to it?

leaanthony commented 1 year ago

I think there's no option because how does it work from a distribution point of view?

lyimmi commented 1 year ago

Well, yes. But it raises a few of questions.

  1. Should all notification generate a tmp image or use some sort of hash to create images only once? eg. md5 the image check if a file exists in tmp, if exists use that?
  2. Should we impose a size limit?
  3. Should we check for mime-type, jpg,jpeg,png,gif etc?
  4. When should we clean up these files, or leave it to the OS? If a program shows 10 notifications with the same image, does it break when we delete the image that is showing?

Another advantage of the []byte route is that we could create a standard api for showing images via http.

lyimmi commented 1 year ago

Another subject. For windows I can't figure out, how to listen to toast action signals... Probably we'll need a direct winapi call and the last time I poked at that was about 8-10 years ago.

The documentation is rather confusing to me. https://learn.microsoft.com/en-us/windows/apps/design/shell/tiles-and-notifications/toast-notifications-overview To be fair this is even wors: https://specifications.freedesktop.org/notification-spec/latest/index.html#introduction

As a simple drop in for windows I used this https://github.com/martinlindhe/notify, but this package only handles opening other applications and web pages.

leaanthony commented 1 year ago

Hey there 👋

Regarding the Q's, My approach would be to generate a random filename prefix at startup so icons are unique to the process. Before issuing the notification just write the file again. It sounds inefficient but notifications will be few and far between and the cost of writing a 32k icon will be miniscule vs the overhead of reading, hashing and potentially writing anyway, plus writing and testing code for all that. We can defer a delete but it's not even critical to do that as temp files will be vacuumed at some point by the OS. Regarding file type, the approach I've been taking is to use PNGs as source and generate OS appropriate file types from that if needed. I wouldn't bother with file limit unless you are required to by the OS.

I can probably sort out the windows side. Managed to get in the zone over the last 6 months.

lyimmi commented 1 year ago

Hi @leaanthony, it was a good idea, works nicely on Windows and linux as well.

lyimmi commented 1 year ago

Hi, am at a stage where Windows, macOS and Linux can show notifications. Windows and macOS needs more work. Linux is almost there I think. I wrote the docs that represent the current state of things.

leaanthony commented 1 year ago

This is awesome! I may be able to help with the MacOS notification icons by implementing it in Objective-C. I'll be moving to mac in the next week or so to do tray menus so can fit this in at the same time 👍

brys0 commented 1 year ago

@Lyimmi amazing work, I had the same issue trying to work for actually listening to toast events for Windows. While it's cool.. the actual underlying API for toasts doesn't seem very thought out in terms of other apps, other than uwp using toast notifications. The few times I did get the listener working for the notification, (which I had to do with c# mind you) it was almost impossible to get the related action data. Like what button was clicked. What text was entered, what radio button was selected.. I think it's crucial to get events from notifications but in terms of a alpha feature.. don't focus too much on getting the events working. It'll only give you headaches. I'm willing to help, but my knowledge of windows apis, and c++/c in general is very very limited..

brys0 commented 1 year ago

@Lyimmi amazing work, I had the same issue trying to work for actually listening to toast events for Windows. While it's cool.. the actual underlying API for toasts doesn't seem very thought out in terms of other apps, other than uwp using toast notifications. The few times I did get the listener working for the notification, (which I had to do with c# mind you) it was almost impossible to get the related action data. Like what button was clicked. What text was entered, what radio button was selected.. I think it's crucial to get events from notifications but in terms of a alpha feature.. don't focus too much on getting the events working. It'll only give you headaches. I'm willing to help, but my knowledge of windows apis, and c++/c in general is very very limited..

It might be worth actually contacting a Microsoft developer about this. It doesn't seem like even their website explains how to get data from toasts/receive events?

Here's a developer support email: premdevinfo@microsoft.com

mullender commented 1 year ago

For OSX it is possible to schedule and cancel notifications: https://developer.apple.com/documentation/usernotifications/scheduling_a_notification_locally_from_your_app

Swop commented 10 months ago

Hey folks :)

Do you have any news about this feature? Is it still on-going?

leaanthony commented 10 months ago

No updates. I believe it's pretty difficult beyond very basic notifications. I'm hoping we can get a basic plugin working for v3

ultimateshadsform commented 8 months ago

What about https://github.com/gen2brain/beeep ?

Or should we just use native apis directly instead of a library?

EmmanuelOga commented 8 months ago

No updates. I believe it's pretty difficult beyond very basic notifications. I'm hoping we can get a basic plugin working for v3

I found this function that should work at least in Mac, although it just uses the osascript mac binary.

https://github.com/wailsapp/wails/blob/12d633642115f077a67dac5a65180581062eb5e0/v2/pkg/mac/notification_darwin.go#L14

I'm guessing the difficult part to come up with a more-or-less uniform API across systems?

imide commented 2 months ago

any updates on the progress of this? looks like this is the only blocker for v3

wksama commented 2 months ago

any updates on the progress of this? looks like this is the only blocker for v3

Maybe this can be postponed to v3.1

swagftw commented 2 months ago

How do I open, the app back again with the context, after clicking notification?