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

Feature/systray #2812

Merged
merged 14 commits into from
Mar 10, 2022
Merged
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 app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,9 @@ func newAppWithDriver(d fyne.Driver, id string) fyne.App {

return newApp
}

// marker interface to pass system tray to supporting drivers
type systrayDriver interface {
SetSystemTrayMenu(*fyne.Menu)
SetSystemTrayIcon(resource fyne.Resource)
}
12 changes: 12 additions & 0 deletions app/app_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ func (a *fyneApp) SendNotification(n *fyne.Notification) {
fallbackNotification(n.Title, n.Content)
}

// SetSystemTrayMenu creates a system tray item and attaches the specified menu.
// By default this will use the application icon.
func (a *fyneApp) SetSystemTrayMenu(menu *fyne.Menu) {
a.Driver().(systrayDriver).SetSystemTrayMenu(menu)
}

// SetSystemTrayIcon sets a custom image for the system tray icon.
// You should have previously called `SetSystemTrayMenu` to initialise the menu icon.
func (a *fyneApp) SetSystemTrayIcon(icon fyne.Resource) {
a.Driver().(systrayDriver).SetSystemTrayIcon(icon)
}

func escapeNotificationString(in string) string {
noSlash := strings.ReplaceAll(in, "\\", "\\\\")
return strings.ReplaceAll(noSlash, "\"", "\\\"")
Expand Down
12 changes: 12 additions & 0 deletions app/app_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ func (a *fyneApp) SendNotification(n *fyne.Notification) {
go runScript("notify", script)
}

// SetSystemTrayMenu creates a system tray item and attaches the specified menu.
// By default this will use the application icon.
func (a *fyneApp) SetSystemTrayMenu(menu *fyne.Menu) {
a.Driver().(systrayDriver).SetSystemTrayMenu(menu)
}

// SetSystemTrayIcon sets a custom image for the system tray icon.
// You should have previously called `SetSystemTrayMenu` to initialise the menu icon.
func (a *fyneApp) SetSystemTrayIcon(icon fyne.Resource) {
a.Driver().(systrayDriver).SetSystemTrayIcon(icon)
}

func escapeNotificationString(in string) string {
noSlash := strings.ReplaceAll(in, "`", "``")
return strings.ReplaceAll(noSlash, "\"", "`\"")
Expand Down
12 changes: 12 additions & 0 deletions app/app_xdg.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ func (a *fyneApp) SendNotification(n *fyne.Notification) {
}
}

// SetSystemTrayMenu creates a system tray item and attaches the specified menu.
// By default this will use the application icon.
func (a *fyneApp) SetSystemTrayMenu(menu *fyne.Menu) {
a.Driver().(systrayDriver).SetSystemTrayMenu(menu)
}

// SetSystemTrayIcon sets a custom image for the system tray icon.
// You should have previously called `SetSystemTrayMenu` to initialise the menu icon.
func (a *fyneApp) SetSystemTrayIcon(icon fyne.Resource) {
a.Driver().(systrayDriver).SetSystemTrayIcon(icon)
}

func rootConfigDir() string {
desktopConfig, _ := os.UserConfigDir()
return filepath.Join(desktopConfig, "fyne")
Expand Down
11 changes: 11 additions & 0 deletions cmd/fyne_demo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var topWindow fyne.Window
func main() {
a := app.NewWithID("io.fyne.demo")
a.SetIcon(theme.FyneLogo())
makeTray(a)
logLifecycle(a)
w := a.NewWindow("Fyne Demo")
topWindow = w
Expand Down Expand Up @@ -161,6 +162,16 @@ func makeMenu(a fyne.App, w fyne.Window) *fyne.MainMenu {
)
}

func makeTray(a fyne.App) {
if desk, ok := a.(desktop.App); ok {
menu := fyne.NewMenu("Hello World",
fyne.NewMenuItem("Hello", func() {
log.Println("System tray menu tapped")
}))
desk.SetSystemTrayMenu(menu)
}
}

func unsupportedTutorial(t tutorials.Tutorial) bool {
return !t.SupportWeb && fyne.CurrentDevice().IsBrowser()
}
Expand Down
11 changes: 11 additions & 0 deletions driver/desktop/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package desktop

import "fyne.io/fyne/v2"

// App defines the desktop specific extensions to a fyne.App.
//
// Since: 2.2
type App interface {
SetSystemTrayMenu(menu *fyne.Menu)
SetSystemTrayIcon(icon fyne.Resource)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module fyne.io/fyne/v2
go 1.14

require (
fyne.io/systray v1.1.1-0.20220307102710-0121a6d9ce01
github.com/BurntSushi/toml v1.0.0
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9
github.com/akavel/rsrc v0.8.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
fyne.io/systray v1.1.1-0.20220307102710-0121a6d9ce01 h1:kKqpTktxesuGT4+Dv1aYiVse688saMLeWPDRihIw+zI=
fyne.io/systray v1.1.1-0.20220307102710-0121a6d9ce01/go.mod h1:N4ZU0i34X+n8soFRlBNkmJTunw9wD+9jIP19fSZpjSI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
Expand Down
28 changes: 27 additions & 1 deletion internal/driver/glfw/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
package glfw

import (
"bytes"
"image"
"os"
"runtime"
"strconv"
"strings"
"sync"

ico "github.com/Kodeworks/golang-image-ico"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/animation"
intapp "fyne.io/fyne/v2/internal/app"
Expand Down Expand Up @@ -38,7 +42,26 @@ type gLDriver struct {

animation *animation.Runner

drawOnMainThread bool // A workaround on Apple M1, just use 1 thread until fixed upstream
drawOnMainThread bool // A workaround on Apple M1, just use 1 thread until fixed upstream
trayStart, trayStop func() // shut down the system tray, if used
}

func toOSIcon(icon fyne.Resource) ([]byte, error) {
if runtime.GOOS != "windows" {
return icon.Content(), nil
}

img, _, err := image.Decode(bytes.NewReader(icon.Content()))
if err != nil {
return nil, err
}

buf := &bytes.Buffer{}
err = ico.Encode(buf, img)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

func (d *gLDriver) RenderedTextSize(text string, textSize float32, style fyne.TextStyle) (size fyne.Size, baseline float32) {
Expand Down Expand Up @@ -70,6 +93,9 @@ func (d *gLDriver) Device() fyne.Device {
func (d *gLDriver) Quit() {
if curWindow != nil {
curWindow = nil
if d.trayStop != nil {
d.trayStop()
}
fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).TriggerExitedForeground()
}
defer func() {
Expand Down
65 changes: 63 additions & 2 deletions internal/driver/glfw/driver_desktop.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,72 @@
//go:build !js
// +build !js
//go:build !js && !wasm && !test_web_driver
// +build !js,!wasm,!test_web_driver

package glfw

import (
"fyne.io/systray"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
)

func (d *gLDriver) Run() {
if goroutineID() != mainGoroutineID {
panic("Run() or ShowAndRun() must be called from main goroutine")
}
d.runGL()
}

func (d *gLDriver) SetSystemTrayMenu(m *fyne.Menu) {
d.trayStart, d.trayStop = systray.RunWithExternalLoop(func() {
if fyne.CurrentApp().Icon() != nil {
img, err := toOSIcon(fyne.CurrentApp().Icon())
if err == nil {
systray.SetIcon(img)
}
} else {
img, err := toOSIcon(theme.FyneLogo())
if err == nil {
systray.SetIcon(img)
}
}

for _, i := range m.Items {
if i.IsSeparator {
systray.AddSeparator()
continue
}

var item *systray.MenuItem
fn := i.Action

if i.Checked {
item = systray.AddMenuItemCheckbox(i.Label, i.Label, true)
} else {
item = systray.AddMenuItem(i.Label, i.Label)
}
if i.Disabled {
item.Disable()
}

go func() {
for range item.ClickedCh {
fn()
}
}()
}

systray.AddSeparator()
quit := systray.AddMenuItem("Quit", "Quit application")
go func() {
<-quit.ClickedCh
d.Quit()
}()
}, func() {
// anything required for tear-down
})
}

func (d *gLDriver) SetSystemTrayIcon(resource fyne.Resource) {
systray.SetIcon(resource.Content())
}
10 changes: 10 additions & 0 deletions internal/driver/glfw/driver_mobile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build js || wasm || test_web_driver
// +build js wasm test_web_driver

package glfw

import "fyne.io/fyne/v2"

func (d *gLDriver) SetSystemTrayMenu(m *fyne.Menu) {
// no-op for mobile apps using this driver
}
3 changes: 3 additions & 0 deletions internal/driver/glfw/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ func (d *gLDriver) runGL() {
run.cond.Broadcast()

d.initGLFW()
if d.trayStart != nil {
d.trayStart()
}
fyne.CurrentApp().Lifecycle().(*app.Lifecycle).TriggerStarted()
for {
select {
Expand Down
12 changes: 12 additions & 0 deletions vendor/fyne.io/systray/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading