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

Notification support #1788

Open
tonyho opened this issue Aug 24, 2022 · 28 comments
Open

Notification support #1788

tonyho opened this issue Aug 24, 2022 · 28 comments
Labels
TODO The issue is ready to be developed v3

Comments

@tonyho
Copy link

tonyho commented Aug 24, 2022

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
Copy link
Contributor

lyimmi commented Aug 24, 2022

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 leaanthony added the TODO The issue is ready to be developed label Aug 24, 2022
@leaanthony leaanthony added this to the v2.1.0 milestone Aug 24, 2022
@leaanthony
Copy link
Member

Added to Roadmap 👍

@leaanthony leaanthony moved this to 🆕 TODO in Wails Backlog Sep 22, 2022
@leaanthony leaanthony modified the milestones: v2.1.0, v2.2.0 Oct 1, 2022
@lyimmi
Copy link
Contributor

lyimmi commented Oct 15, 2022

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.

  • title
  • message
  • icon
  • image (only windows)
  • action buttons

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
Copy link
Member

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 👍

@leaanthony leaanthony modified the milestones: v2.2.0, v2.3.0 Oct 16, 2022
@lyimmi
Copy link
Contributor

lyimmi commented Oct 24, 2022

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
Copy link
Contributor

willdot commented Oct 24, 2022

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
Copy link
Contributor

lyimmi commented Nov 5, 2022

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
Copy link
Member

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
Copy link
Contributor

lyimmi commented Nov 5, 2022

@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
Copy link
Member

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

@lyimmi
Copy link
Contributor

lyimmi commented Nov 5, 2022

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
Copy link
Contributor

lyimmi commented Nov 5, 2022

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
Copy link
Member

leaanthony commented Nov 5, 2022

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
Copy link
Contributor

lyimmi commented Nov 5, 2022

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

@lyimmi
Copy link
Contributor

lyimmi commented Nov 6, 2022

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.

  • On macOS, I used @willdot's package gomacosnotify wrapped with the runtime. Unfortunately gomacosnotify cannot show icons on the notificaitons.
  • On Windows, I used go-toast/toast this package is quite limited. Same as on macOS it is wrapped by the runtime. This is more like a placeholder than a real solution.
  • On linux, notifications are handled with dbus and fallback to libnotify via os.Exec(). I tested it on PopOS 22.04, Ubuntu 22.04, Linux Mint 21 Cinnamon, I need to test it on some KDE distros as well.

@leaanthony
Copy link
Member

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
Copy link

brys0 commented Dec 7, 2022

@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
Copy link

brys0 commented Dec 7, 2022

@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
Copy link

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

@leaanthony leaanthony removed this from the v2.4.0 milestone Dec 29, 2022
@Swop
Copy link

Swop commented Nov 17, 2023

Hey folks :)

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

@leaanthony
Copy link
Member

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

@EmmanuelOga
Copy link

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.

func ShowNotification(title string, subtitle string, message string, sound string) error {

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

@imide
Copy link

imide commented Jul 10, 2024

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

@wksama
Copy link

wksama commented Jul 18, 2024

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
Copy link

swagftw commented Jul 19, 2024

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

@leaanthony leaanthony added the v3 label Nov 17, 2024
@JaneX8
Copy link

JaneX8 commented Dec 2, 2024

I'd like to see this feature too.

@brys0
Copy link

brys0 commented Dec 2, 2024

As things look currently this is probably better off being a plugin for wails rather than a core feature. Notifications vary between platforms and is likely to change often.

@daifiyum
Copy link

Windows Notification Feasible Solution:

ShellNotifyIcon + SetCurrentProcessExplicitAppUserModelID or .rc

example

Can anyone provide elegant solutions for Linux and macOS? Let's expedite the arrival of wails3!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
TODO The issue is ready to be developed v3
Projects
None yet
Development

No branches or pull requests

14 participants