diff --git a/app/app.go b/app/app.go index 50f241db07..69cf2130d8 100644 --- a/app/app.go +++ b/app/app.go @@ -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) +} diff --git a/app/app_darwin.go b/app/app_darwin.go index 61f44452c2..b8f3ab3105 100644 --- a/app/app_darwin.go +++ b/app/app_darwin.go @@ -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, "\"", "\\\"") diff --git a/app/app_windows.go b/app/app_windows.go index 202f371412..3e4a09b1f5 100644 --- a/app/app_windows.go +++ b/app/app_windows.go @@ -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, "\"", "`\"") diff --git a/app/app_xdg.go b/app/app_xdg.go index a7b6ee815c..66daf57bd4 100644 --- a/app/app_xdg.go +++ b/app/app_xdg.go @@ -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") diff --git a/cmd/fyne_demo/main.go b/cmd/fyne_demo/main.go index f3dfb10c4d..ae1744a79b 100644 --- a/cmd/fyne_demo/main.go +++ b/cmd/fyne_demo/main.go @@ -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 @@ -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() } diff --git a/driver/desktop/app.go b/driver/desktop/app.go new file mode 100644 index 0000000000..e4edcec137 --- /dev/null +++ b/driver/desktop/app.go @@ -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) +} diff --git a/go.mod b/go.mod index 3f8bcaafaa..18a1e64b42 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index c9fddf39ed..750f82ab3a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/driver/glfw/driver.go b/internal/driver/glfw/driver.go index c06c89e617..2095820371 100644 --- a/internal/driver/glfw/driver.go +++ b/internal/driver/glfw/driver.go @@ -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" @@ -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) { @@ -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() { diff --git a/internal/driver/glfw/driver_desktop.go b/internal/driver/glfw/driver_desktop.go index 9a23696037..364b056972 100644 --- a/internal/driver/glfw/driver_desktop.go +++ b/internal/driver/glfw/driver_desktop.go @@ -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()) +} diff --git a/internal/driver/glfw/driver_mobile.go b/internal/driver/glfw/driver_mobile.go new file mode 100644 index 0000000000..d8f56780a0 --- /dev/null +++ b/internal/driver/glfw/driver_mobile.go @@ -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 +} diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index 0382cb37d7..78f6164a86 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -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 { diff --git a/vendor/fyne.io/systray/.gitignore b/vendor/fyne.io/systray/.gitignore new file mode 100644 index 0000000000..2e85ef697e --- /dev/null +++ b/vendor/fyne.io/systray/.gitignore @@ -0,0 +1,12 @@ +example/example +webview_example/webview_example +*~ +*.swp +**/*.exe +Release +Debug +*.sdf +dll/systray_unsigned.dll +out.txt +.vs +on_exit*.txt diff --git a/vendor/fyne.io/systray/CHANGELOG.md b/vendor/fyne.io/systray/CHANGELOG.md new file mode 100644 index 0000000000..58e7fc8b33 --- /dev/null +++ b/vendor/fyne.io/systray/CHANGELOG.md @@ -0,0 +1,125 @@ +# Changelog + +## [v1.1.0](https://github.com/getlantern/systray/tree/v1.1.0) (2020-11-18) + +[Full Changelog](https://github.com/getlantern/systray/compare/v1.0.5...v1.1.0) + +**Merged pull requests:** + +- Add submenu support for Linux [\#183](https://github.com/getlantern/systray/pull/183) ([fbrinker](https://github.com/fbrinker)) +- Add checkbox support for Linux [\#181](https://github.com/getlantern/systray/pull/181) ([fbrinker](https://github.com/fbrinker)) +- fix SetTitle documentation [\#179](https://github.com/getlantern/systray/pull/179) ([delthas](https://github.com/delthas)) + +## [v1.0.5](https://github.com/getlantern/systray/tree/v1.0.5) (2020-10-19) + +[Full Changelog](https://github.com/getlantern/systray/compare/v1.0.4...v1.0.5) + +**Merged pull requests:** + +- start menu ID with positive, and change the type to uint32 [\#173](https://github.com/getlantern/systray/pull/173) ([joesis](https://github.com/joesis)) +- Allows disabling items in submenu on macOS [\#172](https://github.com/getlantern/systray/pull/172) ([joesis](https://github.com/joesis)) +- Does not use the template icon for regular icons [\#171](https://github.com/getlantern/systray/pull/171) ([sithembiso](https://github.com/sithembiso)) + +## [v1.0.4](https://github.com/getlantern/systray/tree/v1.0.4) (2020-07-21) + +[Full Changelog](https://github.com/getlantern/systray/compare/1.0.3...v1.0.4) + +**Merged pull requests:** + +- protect shared data structures with proper mutexes [\#162](https://github.com/getlantern/systray/pull/162) ([joesis](https://github.com/joesis)) + +## [1.0.3](https://github.com/getlantern/systray/tree/1.0.3) (2020-06-11) + +[Full Changelog](https://github.com/getlantern/systray/compare/v1.0.3...1.0.3) + +## [v1.0.3](https://github.com/getlantern/systray/tree/v1.0.3) (2020-06-11) + +[Full Changelog](https://github.com/getlantern/systray/compare/v1.0.2...v1.0.3) + +**Merged pull requests:** + +- have a workaround to avoid crash on old macOS versions [\#153](https://github.com/getlantern/systray/pull/153) ([joesis](https://github.com/joesis)) +- Fix bug on darwin of setting icon for menu [\#147](https://github.com/getlantern/systray/pull/147) ([mangalaman93](https://github.com/mangalaman93)) + +## [v1.0.2](https://github.com/getlantern/systray/tree/v1.0.2) (2020-05-19) + +[Full Changelog](https://github.com/getlantern/systray/compare/v1.0.1...v1.0.2) + +**Merged pull requests:** + +- remove unused dependencies [\#145](https://github.com/getlantern/systray/pull/145) ([joesis](https://github.com/joesis)) + +## [v1.0.1](https://github.com/getlantern/systray/tree/v1.0.1) (2020-05-18) + +[Full Changelog](https://github.com/getlantern/systray/compare/1.0.1...v1.0.1) + +## [1.0.1](https://github.com/getlantern/systray/tree/1.0.1) (2020-05-18) + +[Full Changelog](https://github.com/getlantern/systray/compare/1.0.0...1.0.1) + +**Merged pull requests:** + +- Unlock menuItemsLock before changing UI [\#144](https://github.com/getlantern/systray/pull/144) ([joesis](https://github.com/joesis)) + +## [1.0.0](https://github.com/getlantern/systray/tree/1.0.0) (2020-05-18) + +[Full Changelog](https://github.com/getlantern/systray/compare/v1.0.0...1.0.0) + +## [v1.0.0](https://github.com/getlantern/systray/tree/v1.0.0) (2020-05-18) + +[Full Changelog](https://github.com/getlantern/systray/compare/0.9.0...v1.0.0) + +**Merged pull requests:** + +- Check if the menu item is nil [\#137](https://github.com/getlantern/systray/pull/137) ([myleshorton](https://github.com/myleshorton)) + +## [0.9.0](https://github.com/getlantern/systray/tree/0.9.0) (2020-03-24) + +[Full Changelog](https://github.com/getlantern/systray/compare/v0.9.0...0.9.0) + +## [v0.9.0](https://github.com/getlantern/systray/tree/v0.9.0) (2020-03-24) + +[Full Changelog](https://github.com/getlantern/systray/compare/8e63b37ef27d94f6db79c4ffb941608e8f0dc2f9...v0.9.0) + +**Merged pull requests:** + +- Backport all features and fixes from master [\#140](https://github.com/getlantern/systray/pull/140) ([joesis](https://github.com/joesis)) +- Nested menu windows [\#132](https://github.com/getlantern/systray/pull/132) ([joesis](https://github.com/joesis)) +- Support for nested sub-menus on OS X [\#131](https://github.com/getlantern/systray/pull/131) ([oxtoacart](https://github.com/oxtoacart)) +- Use temp directory for walk resource manager [\#129](https://github.com/getlantern/systray/pull/129) ([max-b](https://github.com/max-b)) +- Added support for template icons on macOS [\#119](https://github.com/getlantern/systray/pull/119) ([oxtoacart](https://github.com/oxtoacart)) +- When launching app window on macOS, make application a foreground app… [\#118](https://github.com/getlantern/systray/pull/118) ([oxtoacart](https://github.com/oxtoacart)) +- Include stdlib.h in systray\_browser\_linux to explicitly declare funct… [\#114](https://github.com/getlantern/systray/pull/114) ([oxtoacart](https://github.com/oxtoacart)) +- Fix panic when resources root path is not the working directory [\#112](https://github.com/getlantern/systray/pull/112) ([ksubileau](https://github.com/ksubileau)) +- Don't print close reason to console [\#111](https://github.com/getlantern/systray/pull/111) ([ksubileau](https://github.com/ksubileau)) +- Systray icon could not be changed dynamically [\#110](https://github.com/getlantern/systray/pull/110) ([ksubileau](https://github.com/ksubileau)) +- Preventing deadlock on menu item ClickeCh when no one is listening, c… [\#105](https://github.com/getlantern/systray/pull/105) ([oxtoacart](https://github.com/oxtoacart)) +- Reverted deadlock fix \(Affected other receivers\) [\#104](https://github.com/getlantern/systray/pull/104) ([ldstein](https://github.com/ldstein)) +- Fix Deadlock and item ordering in Windows [\#103](https://github.com/getlantern/systray/pull/103) ([ldstein](https://github.com/ldstein)) +- Minor README improvements \(go modules, example app, screenshot\) [\#98](https://github.com/getlantern/systray/pull/98) ([tstromberg](https://github.com/tstromberg)) +- Add support for app window [\#97](https://github.com/getlantern/systray/pull/97) ([oxtoacart](https://github.com/oxtoacart)) +- systray\_darwin.m: Compare Mac OS min version with value instead of macro [\#94](https://github.com/getlantern/systray/pull/94) ([teddywing](https://github.com/teddywing)) +- Attempt to fix https://github.com/getlantern/systray/issues/75 [\#92](https://github.com/getlantern/systray/pull/92) ([mikeschinkel](https://github.com/mikeschinkel)) +- Fix application path for MacOS in README [\#91](https://github.com/getlantern/systray/pull/91) ([zereraz](https://github.com/zereraz)) +- Document cross-platform console window details [\#81](https://github.com/getlantern/systray/pull/81) ([michaelsanford](https://github.com/michaelsanford)) +- Fix bad-looking system tray icon in Windows [\#78](https://github.com/getlantern/systray/pull/78) ([juja256](https://github.com/juja256)) +- Add the separator to the visible items [\#76](https://github.com/getlantern/systray/pull/76) ([meskio](https://github.com/meskio)) +- keep track of hidden items [\#74](https://github.com/getlantern/systray/pull/74) ([kalikaneko](https://github.com/kalikaneko)) +- Support macOS older than 10.13 [\#73](https://github.com/getlantern/systray/pull/73) ([swznd](https://github.com/swznd)) +- define ERROR\_SUCCESS as syscall.Errno [\#69](https://github.com/getlantern/systray/pull/69) ([joesis](https://github.com/joesis)) +- Bug/fix broken menuitem show [\#68](https://github.com/getlantern/systray/pull/68) ([kalikaneko](https://github.com/kalikaneko)) +- Fix mac deprecations [\#66](https://github.com/getlantern/systray/pull/66) ([jefvel](https://github.com/jefvel)) +- Made it possible to add icons to menu items on Mac [\#65](https://github.com/getlantern/systray/pull/65) ([jefvel](https://github.com/jefvel)) +- linux: delete temp files as soon as they are not needed [\#63](https://github.com/getlantern/systray/pull/63) ([meskio](https://github.com/meskio)) +- Merge changes from amkulikov to remove DLL for windows [\#56](https://github.com/getlantern/systray/pull/56) ([oxtoacart](https://github.com/oxtoacart)) +- Revert "Use templated icons for the menu bar in macOS" [\#51](https://github.com/getlantern/systray/pull/51) ([stoggi](https://github.com/stoggi)) +- Use templated icons for the menu bar in macOS [\#46](https://github.com/getlantern/systray/pull/46) ([stoggi](https://github.com/stoggi)) +- Syscalls instead of custom DLLs [\#44](https://github.com/getlantern/systray/pull/44) ([amkulikov](https://github.com/amkulikov)) +- On quit exit main loop on linux [\#41](https://github.com/getlantern/systray/pull/41) ([meskio](https://github.com/meskio)) +- Fixed hide show in linux \(\#37\) [\#39](https://github.com/getlantern/systray/pull/39) ([meskio](https://github.com/meskio)) +- fix: linux compilation warning [\#36](https://github.com/getlantern/systray/pull/36) ([novln](https://github.com/novln)) +- Added separator functionality [\#32](https://github.com/getlantern/systray/pull/32) ([oxtoacart](https://github.com/oxtoacart)) + + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/vendor/fyne.io/systray/LICENSE b/vendor/fyne.io/systray/LICENSE new file mode 100644 index 0000000000..3ee01626e1 --- /dev/null +++ b/vendor/fyne.io/systray/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014 Brave New Software Project, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/fyne.io/systray/Makefile b/vendor/fyne.io/systray/Makefile new file mode 100644 index 0000000000..12f3d221f0 --- /dev/null +++ b/vendor/fyne.io/systray/Makefile @@ -0,0 +1,18 @@ +tag-changelog: require-version require-gh-token + echo "Tagging..." && \ + git tag -a "$$VERSION" -f --annotate -m"Tagged $$VERSION" && \ + git push --tags -f && \ + git checkout master && \ + git pull && \ + github_changelog_generator --no-issues --max-issues 100 --token "${GH_TOKEN}" --user getlantern --project systray && \ + git add CHANGELOG.md && \ + git commit -m "Updated changelog for $$VERSION" && \ + git push origin HEAD && \ + git checkout - + +guard-%: + @ if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fi + +require-version: guard-VERSION + +require-gh-token: guard-GH_TOKEN diff --git a/vendor/fyne.io/systray/README.md b/vendor/fyne.io/systray/README.md new file mode 100644 index 0000000000..a65449ea49 --- /dev/null +++ b/vendor/fyne.io/systray/README.md @@ -0,0 +1,122 @@ +# Systray + +systray is a cross-platform Go library to place an icon and menu in the notification area. +This repository is a fork of [getlantern/systray](https://github.com/getlantern/systray) +removing the GTK dependency and support for legacy linux system tray. + +## Features + +* Supported on Windows, macOS and Linux +* Menu items can be checked and/or disabled +* Methods may be called from any Goroutine + +## API + +```go +package main + +import "fyne.io/systray" + +func main() { + systray.Run(onReady, onExit) +} + +func onReady() { + systray.SetIcon(icon.Data) + systray.SetTitle("Awesome App") + systray.SetTooltip("Pretty awesome超级棒") + mQuit := systray.AddMenuItem("Quit", "Quit the whole app") + + // Sets the icon of a menu item. Only available on Mac and Windows. + mQuit.SetIcon(icon.Data) +} + +func onExit() { + // clean up here +} +``` + +### Run in another toolkit + +Most graphical toolkits will grab the main loop so the `Run` code above is not possible. +For this reason there is another entry point `RunWithExternalLoop`. +This function of the library returns a start and end function that should be called +when the application has started and will end, to loop in appropriate features. + +See [full API](https://pkg.go.dev/fyne.io/systray?tab=doc) as well as [CHANGELOG](https://github.com/fyne-io/systray/tree/master/CHANGELOG.md). + +## Try the example app! + +Have go v1.12+ or higher installed? Here's an example to get started on macOS: + +```sh +git clone https://github.com/fyne-io/systray +cd systray/example +env GO111MODULE=on go build +./example +``` + +On Windows, you should build like this: + +``` +env GO111MODULE=on go build -ldflags "-H=windowsgui" +``` + +The following text will then appear on the console: + + +```sh +go: finding github.com/fyne-io/systray latest +``` + +Now look for *Awesome App* in your menu bar! + +![Awesome App screenshot](example/screenshot.png) + +## Platform notes + +### Linux + +This implementation uses DBus to communicate through the SystemNotifier/AppIndicator spec, older tray implementations may not load the icon. + +### Windows + +* To avoid opening a console at application startup, use "fyne package" for your app or manually use these compile flags: + +```sh +go build -ldflags -H=windowsgui +``` + +### macOS + +On macOS, you will need to create an application bundle to wrap the binary; simply use "fyne package" or add folders with the following minimal structure and assets: + +``` +SystrayApp.app/ + Contents/ + Info.plist + MacOS/ + go-executable + Resources/ + SystrayApp.icns +``` + +When running as an app bundle, you may want to add one or both of the following to your Info.plist: + +```xml + + NSHighResolutionCapable + True + + + LSUIElement + 1 +``` + +Consult the [Official Apple Documentation here](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1). + +## Credits + +- https://github.com/getlantern/systray +- https://github.com/xilp/systray +- https://github.com/cratonica/trayhost diff --git a/vendor/fyne.io/systray/go.mod b/vendor/fyne.io/systray/go.mod new file mode 100644 index 0000000000..3edf71cae1 --- /dev/null +++ b/vendor/fyne.io/systray/go.mod @@ -0,0 +1,7 @@ +module fyne.io/systray + +go 1.13 + +require golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 + +require github.com/godbus/dbus/v5 v5.0.4 diff --git a/vendor/fyne.io/systray/go.sum b/vendor/fyne.io/systray/go.sum new file mode 100644 index 0000000000..ee3fb7e910 --- /dev/null +++ b/vendor/fyne.io/systray/go.sum @@ -0,0 +1,4 @@ +github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/fyne.io/systray/internal/generated/menu/dbus_menu.go b/vendor/fyne.io/systray/internal/generated/menu/dbus_menu.go new file mode 100644 index 0000000000..1b896d3e06 --- /dev/null +++ b/vendor/fyne.io/systray/internal/generated/menu/dbus_menu.go @@ -0,0 +1,484 @@ +// Code generated by dbus-codegen-go DO NOT EDIT. +package menu + +import ( + "context" + "errors" + "fmt" + + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" +) + +var ( + // Introspection for com.canonical.dbusmenu + IntrospectDataDbusmenu = introspect.Interface{ + Name: "com.canonical.dbusmenu", + Methods: []introspect.Method{{Name: "GetLayout", Args: []introspect.Arg{ + {Name: "parentId", Type: "i", Direction: "in"}, + {Name: "recursionDepth", Type: "i", Direction: "in"}, + {Name: "propertyNames", Type: "as", Direction: "in"}, + {Name: "revision", Type: "u", Direction: "out"}, + {Name: "layout", Type: "(ia{sv}av)", Direction: "out"}, + }}, + {Name: "GetGroupProperties", Args: []introspect.Arg{ + {Name: "ids", Type: "ai", Direction: "in"}, + {Name: "propertyNames", Type: "as", Direction: "in"}, + {Name: "properties", Type: "a(ia{sv})", Direction: "out"}, + }}, + {Name: "GetProperty", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "name", Type: "s", Direction: "in"}, + {Name: "value", Type: "v", Direction: "out"}, + }}, + {Name: "Event", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "eventId", Type: "s", Direction: "in"}, + {Name: "data", Type: "v", Direction: "in"}, + {Name: "timestamp", Type: "u", Direction: "in"}, + }}, + {Name: "EventGroup", Args: []introspect.Arg{ + {Name: "events", Type: "a(isvu)", Direction: "in"}, + {Name: "idErrors", Type: "ai", Direction: "out"}, + }}, + {Name: "AboutToShow", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "in"}, + {Name: "needUpdate", Type: "b", Direction: "out"}, + }}, + {Name: "AboutToShowGroup", Args: []introspect.Arg{ + {Name: "ids", Type: "ai", Direction: "in"}, + {Name: "updatesNeeded", Type: "ai", Direction: "out"}, + {Name: "idErrors", Type: "ai", Direction: "out"}, + }}, + }, + Signals: []introspect.Signal{{Name: "ItemsPropertiesUpdated", Args: []introspect.Arg{ + {Name: "updatedProps", Type: "a(ia{sv})", Direction: "out"}, + {Name: "removedProps", Type: "a(ias)", Direction: "out"}, + }}, + {Name: "LayoutUpdated", Args: []introspect.Arg{ + {Name: "revision", Type: "u", Direction: "out"}, + {Name: "parent", Type: "i", Direction: "out"}, + }}, + {Name: "ItemActivationRequested", Args: []introspect.Arg{ + {Name: "id", Type: "i", Direction: "out"}, + {Name: "timestamp", Type: "u", Direction: "out"}, + }}, + }, + Properties: []introspect.Property{{Name: "Version", Type: "u", Access: "read"}, + {Name: "TextDirection", Type: "s", Access: "read"}, + {Name: "Status", Type: "s", Access: "read"}, + {Name: "IconThemePath", Type: "as", Access: "read"}, + }, + Annotations: []introspect.Annotation{}, + } +) + +// Signal is a common interface for all signals. +type Signal interface { + Name() string + Interface() string + Sender() string + + path() dbus.ObjectPath + values() []interface{} +} + +// Emit sends the given signal to the bus. +func Emit(conn *dbus.Conn, s Signal) error { + return conn.Emit(s.path(), s.Interface()+"."+s.Name(), s.values()...) +} + +// ErrUnknownSignal is returned by LookupSignal when a signal cannot be resolved. +var ErrUnknownSignal = errors.New("unknown signal") + +// LookupSignal converts the given raw D-Bus signal with variable body +// into one with typed structured body or returns ErrUnknownSignal error. +func LookupSignal(signal *dbus.Signal) (Signal, error) { + switch signal.Name { + case InterfaceDbusmenu + "." + "ItemsPropertiesUpdated": + v0, ok := signal.Body[0].([]struct { + V0 int32 + V1 map[string]dbus.Variant + }) + if !ok { + return nil, fmt.Errorf("prop .UpdatedProps is %T, not []struct {V0 int32;V1 map[string]dbus.Variant}", signal.Body[0]) + } + v1, ok := signal.Body[1].([]struct { + V0 int32 + V1 []string + }) + if !ok { + return nil, fmt.Errorf("prop .RemovedProps is %T, not []struct {V0 int32;V1 []string}", signal.Body[1]) + } + return &Dbusmenu_ItemsPropertiesUpdatedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_ItemsPropertiesUpdatedSignalBody{ + UpdatedProps: v0, + RemovedProps: v1, + }, + }, nil + case InterfaceDbusmenu + "." + "LayoutUpdated": + v0, ok := signal.Body[0].(uint32) + if !ok { + return nil, fmt.Errorf("prop .Revision is %T, not uint32", signal.Body[0]) + } + v1, ok := signal.Body[1].(int32) + if !ok { + return nil, fmt.Errorf("prop .Parent is %T, not int32", signal.Body[1]) + } + return &Dbusmenu_LayoutUpdatedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_LayoutUpdatedSignalBody{ + Revision: v0, + Parent: v1, + }, + }, nil + case InterfaceDbusmenu + "." + "ItemActivationRequested": + v0, ok := signal.Body[0].(int32) + if !ok { + return nil, fmt.Errorf("prop .Id is %T, not int32", signal.Body[0]) + } + v1, ok := signal.Body[1].(uint32) + if !ok { + return nil, fmt.Errorf("prop .Timestamp is %T, not uint32", signal.Body[1]) + } + return &Dbusmenu_ItemActivationRequestedSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &Dbusmenu_ItemActivationRequestedSignalBody{ + Id: v0, + Timestamp: v1, + }, + }, nil + default: + return nil, ErrUnknownSignal + } +} + +// AddMatchSignal registers a match rule for the given signal, +// opts are appended to the automatically generated signal's rules. +func AddMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.AddMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// RemoveMatchSignal unregisters the previously registered subscription. +func RemoveMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.RemoveMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// Interface name constants. +const ( + InterfaceDbusmenu = "com.canonical.dbusmenu" +) + +// Dbusmenuer is com.canonical.dbusmenu interface. +type Dbusmenuer interface { + // GetLayout is com.canonical.dbusmenu.GetLayout method. + GetLayout(parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant + }, err *dbus.Error) + // GetGroupProperties is com.canonical.dbusmenu.GetGroupProperties method. + GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant + }, err *dbus.Error) + // GetProperty is com.canonical.dbusmenu.GetProperty method. + GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) + // Event is com.canonical.dbusmenu.Event method. + Event(id int32, eventId string, data dbus.Variant, timestamp uint32) (err *dbus.Error) + // EventGroup is com.canonical.dbusmenu.EventGroup method. + EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 + }) (idErrors []int32, err *dbus.Error) + // AboutToShow is com.canonical.dbusmenu.AboutToShow method. + AboutToShow(id int32) (needUpdate bool, err *dbus.Error) + // AboutToShowGroup is com.canonical.dbusmenu.AboutToShowGroup method. + AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) +} + +// ExportDbusmenu exports the given object that implements com.canonical.dbusmenu on the bus. +func ExportDbusmenu(conn *dbus.Conn, path dbus.ObjectPath, v Dbusmenuer) error { + return conn.ExportSubtreeMethodTable(map[string]interface{}{ + "GetLayout": v.GetLayout, + "GetGroupProperties": v.GetGroupProperties, + "GetProperty": v.GetProperty, + "Event": v.Event, + "EventGroup": v.EventGroup, + "AboutToShow": v.AboutToShow, + "AboutToShowGroup": v.AboutToShowGroup, + }, path, InterfaceDbusmenu) +} + +// UnexportDbusmenu unexports com.canonical.dbusmenu interface on the named path. +func UnexportDbusmenu(conn *dbus.Conn, path dbus.ObjectPath) error { + return conn.Export(nil, path, InterfaceDbusmenu) +} + +// UnimplementedDbusmenu can be embedded to have forward compatible server implementations. +type UnimplementedDbusmenu struct{} + +func (*UnimplementedDbusmenu) iface() string { + return InterfaceDbusmenu +} + +func (*UnimplementedDbusmenu) GetLayout(parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant +}, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) Event(id int32, eventId string, data dbus.Variant, timestamp uint32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) AboutToShow(id int32) (needUpdate bool, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedDbusmenu) AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +// NewDbusmenu creates and allocates com.canonical.dbusmenu. +func NewDbusmenu(object dbus.BusObject) *Dbusmenu { + return &Dbusmenu{object} +} + +// Dbusmenu implements com.canonical.dbusmenu D-Bus interface. +type Dbusmenu struct { + object dbus.BusObject +} + +// GetLayout calls com.canonical.dbusmenu.GetLayout method. +func (o *Dbusmenu) GetLayout(ctx context.Context, parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout struct { + V0 int32 + V1 map[string]dbus.Variant + V2 []dbus.Variant +}, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetLayout", 0, parentId, recursionDepth, propertyNames).Store(&revision, &layout) + return +} + +// GetGroupProperties calls com.canonical.dbusmenu.GetGroupProperties method. +func (o *Dbusmenu) GetGroupProperties(ctx context.Context, ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetGroupProperties", 0, ids, propertyNames).Store(&properties) + return +} + +// GetProperty calls com.canonical.dbusmenu.GetProperty method. +func (o *Dbusmenu) GetProperty(ctx context.Context, id int32, name string) (value dbus.Variant, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".GetProperty", 0, id, name).Store(&value) + return +} + +// Event calls com.canonical.dbusmenu.Event method. +func (o *Dbusmenu) Event(ctx context.Context, id int32, eventId string, data dbus.Variant, timestamp uint32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".Event", 0, id, eventId, data, timestamp).Store() + return +} + +// EventGroup calls com.canonical.dbusmenu.EventGroup method. +func (o *Dbusmenu) EventGroup(ctx context.Context, events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".EventGroup", 0, events).Store(&idErrors) + return +} + +// AboutToShow calls com.canonical.dbusmenu.AboutToShow method. +func (o *Dbusmenu) AboutToShow(ctx context.Context, id int32) (needUpdate bool, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".AboutToShow", 0, id).Store(&needUpdate) + return +} + +// AboutToShowGroup calls com.canonical.dbusmenu.AboutToShowGroup method. +func (o *Dbusmenu) AboutToShowGroup(ctx context.Context, ids []int32) (updatesNeeded []int32, idErrors []int32, err error) { + err = o.object.CallWithContext(ctx, InterfaceDbusmenu+".AboutToShowGroup", 0, ids).Store(&updatesNeeded, &idErrors) + return +} + +// GetVersion gets com.canonical.dbusmenu.Version property. +func (o *Dbusmenu) GetVersion(ctx context.Context) (version uint32, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "Version").Store(&version) + return +} + +// GetTextDirection gets com.canonical.dbusmenu.TextDirection property. +func (o *Dbusmenu) GetTextDirection(ctx context.Context) (textDirection string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "TextDirection").Store(&textDirection) + return +} + +// GetStatus gets com.canonical.dbusmenu.Status property. +func (o *Dbusmenu) GetStatus(ctx context.Context) (status string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "Status").Store(&status) + return +} + +// GetIconThemePath gets com.canonical.dbusmenu.IconThemePath property. +func (o *Dbusmenu) GetIconThemePath(ctx context.Context) (iconThemePath []string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceDbusmenu, "IconThemePath").Store(&iconThemePath) + return +} + +// Dbusmenu_ItemsPropertiesUpdatedSignal represents com.canonical.dbusmenu.ItemsPropertiesUpdated signal. +type Dbusmenu_ItemsPropertiesUpdatedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_ItemsPropertiesUpdatedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Name() string { + return "ItemsPropertiesUpdated" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_ItemsPropertiesUpdatedSignal) values() []interface{} { + return []interface{}{s.Body.UpdatedProps, s.Body.RemovedProps} +} + +// Dbusmenu_ItemsPropertiesUpdatedSignalBody is body container. +type Dbusmenu_ItemsPropertiesUpdatedSignalBody struct { + UpdatedProps []struct { + V0 int32 + V1 map[string]dbus.Variant + } + RemovedProps []struct { + V0 int32 + V1 []string + } +} + +// Dbusmenu_LayoutUpdatedSignal represents com.canonical.dbusmenu.LayoutUpdated signal. +type Dbusmenu_LayoutUpdatedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_LayoutUpdatedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_LayoutUpdatedSignal) Name() string { + return "LayoutUpdated" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_LayoutUpdatedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_LayoutUpdatedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_LayoutUpdatedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_LayoutUpdatedSignal) values() []interface{} { + return []interface{}{s.Body.Revision, s.Body.Parent} +} + +// Dbusmenu_LayoutUpdatedSignalBody is body container. +type Dbusmenu_LayoutUpdatedSignalBody struct { + Revision uint32 + Parent int32 +} + +// Dbusmenu_ItemActivationRequestedSignal represents com.canonical.dbusmenu.ItemActivationRequested signal. +type Dbusmenu_ItemActivationRequestedSignal struct { + sender string + Path dbus.ObjectPath + Body *Dbusmenu_ItemActivationRequestedSignalBody +} + +// Name returns the signal's name. +func (s *Dbusmenu_ItemActivationRequestedSignal) Name() string { + return "ItemActivationRequested" +} + +// Interface returns the signal's interface. +func (s *Dbusmenu_ItemActivationRequestedSignal) Interface() string { + return InterfaceDbusmenu +} + +// Sender returns the signal's sender unique name. +func (s *Dbusmenu_ItemActivationRequestedSignal) Sender() string { + return s.sender +} + +func (s *Dbusmenu_ItemActivationRequestedSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *Dbusmenu_ItemActivationRequestedSignal) values() []interface{} { + return []interface{}{s.Body.Id, s.Body.Timestamp} +} + +// Dbusmenu_ItemActivationRequestedSignalBody is body container. +type Dbusmenu_ItemActivationRequestedSignalBody struct { + Id int32 + Timestamp uint32 +} diff --git a/vendor/fyne.io/systray/internal/generated/notifier/status_notifier_item.go b/vendor/fyne.io/systray/internal/generated/notifier/status_notifier_item.go new file mode 100644 index 0000000000..6afbdb0854 --- /dev/null +++ b/vendor/fyne.io/systray/internal/generated/notifier/status_notifier_item.go @@ -0,0 +1,612 @@ +// Code generated by dbus-codegen-go DO NOT EDIT. +package notifier + +import ( + "context" + "errors" + "fmt" + + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" +) + +var ( + // Introspection for org.kde.StatusNotifierItem + IntrospectDataStatusNotifierItem = introspect.Interface{ + Name: "org.kde.StatusNotifierItem", + Methods: []introspect.Method{{Name: "ContextMenu", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "Activate", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "SecondaryActivate", Args: []introspect.Arg{ + {Name: "x", Type: "i", Direction: "in"}, + {Name: "y", Type: "i", Direction: "in"}, + }}, + {Name: "Scroll", Args: []introspect.Arg{ + {Name: "delta", Type: "i", Direction: "in"}, + {Name: "orientation", Type: "s", Direction: "in"}, + }}, + }, + Signals: []introspect.Signal{{Name: "NewTitle"}, + {Name: "NewIcon"}, + {Name: "NewAttentionIcon"}, + {Name: "NewOverlayIcon"}, + {Name: "NewStatus", Args: []introspect.Arg{ + {Name: "status", Type: "s", Direction: ""}, + }}, + {Name: "NewIconThemePath", Args: []introspect.Arg{ + {Name: "icon_theme_path", Type: "s", Direction: "out"}, + }}, + {Name: "NewMenu"}, + }, + Properties: []introspect.Property{{Name: "Category", Type: "s", Access: "read"}, + {Name: "Id", Type: "s", Access: "read"}, + {Name: "Title", Type: "s", Access: "read"}, + {Name: "Status", Type: "s", Access: "read"}, + {Name: "WindowId", Type: "i", Access: "read"}, + {Name: "IconThemePath", Type: "s", Access: "read"}, + {Name: "Menu", Type: "o", Access: "read"}, + {Name: "ItemIsMenu", Type: "b", Access: "read"}, + {Name: "IconName", Type: "s", Access: "read"}, + {Name: "IconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "OverlayIconName", Type: "s", Access: "read"}, + {Name: "OverlayIconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "AttentionIconName", Type: "s", Access: "read"}, + {Name: "AttentionIconPixmap", Type: "a(iiay)", Access: "read", Annotations: []introspect.Annotation{ + {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusImageVector"}, + }}, + {Name: "AttentionMovieName", Type: "s", Access: "read"}, + }, + Annotations: []introspect.Annotation{}, + } +) + +// Signal is a common interface for all signals. +type Signal interface { + Name() string + Interface() string + Sender() string + + path() dbus.ObjectPath + values() []interface{} +} + +// Emit sends the given signal to the bus. +func Emit(conn *dbus.Conn, s Signal) error { + return conn.Emit(s.path(), s.Interface()+"."+s.Name(), s.values()...) +} + +// ErrUnknownSignal is returned by LookupSignal when a signal cannot be resolved. +var ErrUnknownSignal = errors.New("unknown signal") + +// LookupSignal converts the given raw D-Bus signal with variable body +// into one with typed structured body or returns ErrUnknownSignal error. +func LookupSignal(signal *dbus.Signal) (Signal, error) { + switch signal.Name { + case InterfaceStatusNotifierItem + "." + "NewTitle": + return &StatusNotifierItem_NewTitleSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewTitleSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewIcon": + return &StatusNotifierItem_NewIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewAttentionIcon": + return &StatusNotifierItem_NewAttentionIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewAttentionIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewOverlayIcon": + return &StatusNotifierItem_NewOverlayIconSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewOverlayIconSignalBody{}, + }, nil + case InterfaceStatusNotifierItem + "." + "NewStatus": + v0, ok := signal.Body[0].(string) + if !ok { + return nil, fmt.Errorf("prop .Status is %T, not string", signal.Body[0]) + } + return &StatusNotifierItem_NewStatusSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewStatusSignalBody{ + Status: v0, + }, + }, nil + case InterfaceStatusNotifierItem + "." + "NewIconThemePath": + v0, ok := signal.Body[0].(string) + if !ok { + return nil, fmt.Errorf("prop .IconThemePath is %T, not string", signal.Body[0]) + } + return &StatusNotifierItem_NewIconThemePathSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewIconThemePathSignalBody{ + IconThemePath: v0, + }, + }, nil + case InterfaceStatusNotifierItem + "." + "NewMenu": + return &StatusNotifierItem_NewMenuSignal{ + sender: signal.Sender, + Path: signal.Path, + Body: &StatusNotifierItem_NewMenuSignalBody{}, + }, nil + default: + return nil, ErrUnknownSignal + } +} + +// AddMatchSignal registers a match rule for the given signal, +// opts are appended to the automatically generated signal's rules. +func AddMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.AddMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// RemoveMatchSignal unregisters the previously registered subscription. +func RemoveMatchSignal(conn *dbus.Conn, s Signal, opts ...dbus.MatchOption) error { + return conn.RemoveMatchSignal(append([]dbus.MatchOption{ + dbus.WithMatchInterface(s.Interface()), + dbus.WithMatchMember(s.Name()), + }, opts...)...) +} + +// Interface name constants. +const ( + InterfaceStatusNotifierItem = "org.kde.StatusNotifierItem" +) + +// StatusNotifierItemer is org.kde.StatusNotifierItem interface. +type StatusNotifierItemer interface { + // ContextMenu is org.kde.StatusNotifierItem.ContextMenu method. + ContextMenu(x int32, y int32) (err *dbus.Error) + // Activate is org.kde.StatusNotifierItem.Activate method. + Activate(x int32, y int32) (err *dbus.Error) + // SecondaryActivate is org.kde.StatusNotifierItem.SecondaryActivate method. + SecondaryActivate(x int32, y int32) (err *dbus.Error) + // Scroll is org.kde.StatusNotifierItem.Scroll method. + Scroll(delta int32, orientation string) (err *dbus.Error) +} + +// ExportStatusNotifierItem exports the given object that implements org.kde.StatusNotifierItem on the bus. +func ExportStatusNotifierItem(conn *dbus.Conn, path dbus.ObjectPath, v StatusNotifierItemer) error { + return conn.ExportSubtreeMethodTable(map[string]interface{}{ + "ContextMenu": v.ContextMenu, + "Activate": v.Activate, + "SecondaryActivate": v.SecondaryActivate, + "Scroll": v.Scroll, + }, path, InterfaceStatusNotifierItem) +} + +// UnexportStatusNotifierItem unexports org.kde.StatusNotifierItem interface on the named path. +func UnexportStatusNotifierItem(conn *dbus.Conn, path dbus.ObjectPath) error { + return conn.Export(nil, path, InterfaceStatusNotifierItem) +} + +// UnimplementedStatusNotifierItem can be embedded to have forward compatible server implementations. +type UnimplementedStatusNotifierItem struct{} + +func (*UnimplementedStatusNotifierItem) iface() string { + return InterfaceStatusNotifierItem +} + +func (*UnimplementedStatusNotifierItem) ContextMenu(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) Activate(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) SecondaryActivate(x int32, y int32) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +func (*UnimplementedStatusNotifierItem) Scroll(delta int32, orientation string) (err *dbus.Error) { + err = &dbus.ErrMsgUnknownMethod + return +} + +// NewStatusNotifierItem creates and allocates org.kde.StatusNotifierItem. +func NewStatusNotifierItem(object dbus.BusObject) *StatusNotifierItem { + return &StatusNotifierItem{object} +} + +// StatusNotifierItem implements org.kde.StatusNotifierItem D-Bus interface. +type StatusNotifierItem struct { + object dbus.BusObject +} + +// ContextMenu calls org.kde.StatusNotifierItem.ContextMenu method. +func (o *StatusNotifierItem) ContextMenu(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".ContextMenu", 0, x, y).Store() + return +} + +// Activate calls org.kde.StatusNotifierItem.Activate method. +func (o *StatusNotifierItem) Activate(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".Activate", 0, x, y).Store() + return +} + +// SecondaryActivate calls org.kde.StatusNotifierItem.SecondaryActivate method. +func (o *StatusNotifierItem) SecondaryActivate(ctx context.Context, x int32, y int32) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".SecondaryActivate", 0, x, y).Store() + return +} + +// Scroll calls org.kde.StatusNotifierItem.Scroll method. +func (o *StatusNotifierItem) Scroll(ctx context.Context, delta int32, orientation string) (err error) { + err = o.object.CallWithContext(ctx, InterfaceStatusNotifierItem+".Scroll", 0, delta, orientation).Store() + return +} + +// GetCategory gets org.kde.StatusNotifierItem.Category property. +func (o *StatusNotifierItem) GetCategory(ctx context.Context) (category string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Category").Store(&category) + return +} + +// GetId gets org.kde.StatusNotifierItem.Id property. +func (o *StatusNotifierItem) GetId(ctx context.Context) (id string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Id").Store(&id) + return +} + +// GetTitle gets org.kde.StatusNotifierItem.Title property. +func (o *StatusNotifierItem) GetTitle(ctx context.Context) (title string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Title").Store(&title) + return +} + +// GetStatus gets org.kde.StatusNotifierItem.Status property. +func (o *StatusNotifierItem) GetStatus(ctx context.Context) (status string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Status").Store(&status) + return +} + +// GetWindowId gets org.kde.StatusNotifierItem.WindowId property. +func (o *StatusNotifierItem) GetWindowId(ctx context.Context) (windowId int32, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "WindowId").Store(&windowId) + return +} + +// GetIconThemePath gets org.kde.StatusNotifierItem.IconThemePath property. +func (o *StatusNotifierItem) GetIconThemePath(ctx context.Context) (iconThemePath string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconThemePath").Store(&iconThemePath) + return +} + +// GetMenu gets org.kde.StatusNotifierItem.Menu property. +func (o *StatusNotifierItem) GetMenu(ctx context.Context) (menu dbus.ObjectPath, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "Menu").Store(&menu) + return +} + +// GetItemIsMenu gets org.kde.StatusNotifierItem.ItemIsMenu property. +func (o *StatusNotifierItem) GetItemIsMenu(ctx context.Context) (itemIsMenu bool, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "ItemIsMenu").Store(&itemIsMenu) + return +} + +// GetIconName gets org.kde.StatusNotifierItem.IconName property. +func (o *StatusNotifierItem) GetIconName(ctx context.Context) (iconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconName").Store(&iconName) + return +} + +// GetIconPixmap gets org.kde.StatusNotifierItem.IconPixmap property. +// +// Annotations: +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetIconPixmap(ctx context.Context) (iconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "IconPixmap").Store(&iconPixmap) + return +} + +// GetOverlayIconName gets org.kde.StatusNotifierItem.OverlayIconName property. +func (o *StatusNotifierItem) GetOverlayIconName(ctx context.Context) (overlayIconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "OverlayIconName").Store(&overlayIconName) + return +} + +// GetOverlayIconPixmap gets org.kde.StatusNotifierItem.OverlayIconPixmap property. +// +// Annotations: +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetOverlayIconPixmap(ctx context.Context) (overlayIconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "OverlayIconPixmap").Store(&overlayIconPixmap) + return +} + +// GetAttentionIconName gets org.kde.StatusNotifierItem.AttentionIconName property. +func (o *StatusNotifierItem) GetAttentionIconName(ctx context.Context) (attentionIconName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionIconName").Store(&attentionIconName) + return +} + +// GetAttentionIconPixmap gets org.kde.StatusNotifierItem.AttentionIconPixmap property. +// +// Annotations: +// @org.qtproject.QtDBus.QtTypeName = KDbusImageVector +func (o *StatusNotifierItem) GetAttentionIconPixmap(ctx context.Context) (attentionIconPixmap []struct { + V0 int32 + V1 int32 + V2 []byte +}, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionIconPixmap").Store(&attentionIconPixmap) + return +} + +// GetAttentionMovieName gets org.kde.StatusNotifierItem.AttentionMovieName property. +func (o *StatusNotifierItem) GetAttentionMovieName(ctx context.Context) (attentionMovieName string, err error) { + err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "AttentionMovieName").Store(&attentionMovieName) + return +} + +// StatusNotifierItem_NewTitleSignal represents org.kde.StatusNotifierItem.NewTitle signal. +type StatusNotifierItem_NewTitleSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewTitleSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewTitleSignal) Name() string { + return "NewTitle" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewTitleSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewTitleSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewTitleSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewTitleSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewTitleSignalBody is body container. +type StatusNotifierItem_NewTitleSignalBody struct { +} + +// StatusNotifierItem_NewIconSignal represents org.kde.StatusNotifierItem.NewIcon signal. +type StatusNotifierItem_NewIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewIconSignal) Name() string { + return "NewIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewIconSignalBody is body container. +type StatusNotifierItem_NewIconSignalBody struct { +} + +// StatusNotifierItem_NewAttentionIconSignal represents org.kde.StatusNotifierItem.NewAttentionIcon signal. +type StatusNotifierItem_NewAttentionIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewAttentionIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewAttentionIconSignal) Name() string { + return "NewAttentionIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewAttentionIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewAttentionIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewAttentionIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewAttentionIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewAttentionIconSignalBody is body container. +type StatusNotifierItem_NewAttentionIconSignalBody struct { +} + +// StatusNotifierItem_NewOverlayIconSignal represents org.kde.StatusNotifierItem.NewOverlayIcon signal. +type StatusNotifierItem_NewOverlayIconSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewOverlayIconSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewOverlayIconSignal) Name() string { + return "NewOverlayIcon" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewOverlayIconSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewOverlayIconSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewOverlayIconSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewOverlayIconSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewOverlayIconSignalBody is body container. +type StatusNotifierItem_NewOverlayIconSignalBody struct { +} + +// StatusNotifierItem_NewStatusSignal represents org.kde.StatusNotifierItem.NewStatus signal. +type StatusNotifierItem_NewStatusSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewStatusSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewStatusSignal) Name() string { + return "NewStatus" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewStatusSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewStatusSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewStatusSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewStatusSignal) values() []interface{} { + return []interface{}{s.Body.Status} +} + +// StatusNotifierItem_NewStatusSignalBody is body container. +type StatusNotifierItem_NewStatusSignalBody struct { + Status string +} + +// StatusNotifierItem_NewIconThemePathSignal represents org.kde.StatusNotifierItem.NewIconThemePath signal. +type StatusNotifierItem_NewIconThemePathSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewIconThemePathSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewIconThemePathSignal) Name() string { + return "NewIconThemePath" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewIconThemePathSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewIconThemePathSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewIconThemePathSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewIconThemePathSignal) values() []interface{} { + return []interface{}{s.Body.IconThemePath} +} + +// StatusNotifierItem_NewIconThemePathSignalBody is body container. +type StatusNotifierItem_NewIconThemePathSignalBody struct { + IconThemePath string +} + +// StatusNotifierItem_NewMenuSignal represents org.kde.StatusNotifierItem.NewMenu signal. +type StatusNotifierItem_NewMenuSignal struct { + sender string + Path dbus.ObjectPath + Body *StatusNotifierItem_NewMenuSignalBody +} + +// Name returns the signal's name. +func (s *StatusNotifierItem_NewMenuSignal) Name() string { + return "NewMenu" +} + +// Interface returns the signal's interface. +func (s *StatusNotifierItem_NewMenuSignal) Interface() string { + return InterfaceStatusNotifierItem +} + +// Sender returns the signal's sender unique name. +func (s *StatusNotifierItem_NewMenuSignal) Sender() string { + return s.sender +} + +func (s *StatusNotifierItem_NewMenuSignal) path() dbus.ObjectPath { + return s.Path +} + +func (s *StatusNotifierItem_NewMenuSignal) values() []interface{} { + return []interface{}{} +} + +// StatusNotifierItem_NewMenuSignalBody is body container. +type StatusNotifierItem_NewMenuSignalBody struct { +} diff --git a/vendor/fyne.io/systray/systray.go b/vendor/fyne.io/systray/systray.go new file mode 100644 index 0000000000..903cffee8e --- /dev/null +++ b/vendor/fyne.io/systray/systray.go @@ -0,0 +1,240 @@ +// Package systray is a cross-platform Go library to place an icon and menu in the notification area. +package systray + +import ( + "fmt" + "runtime" + "sync" + "sync/atomic" +) + +var ( + systrayReady func() + systrayExit func() + menuItems = make(map[uint32]*MenuItem) + menuItemsLock sync.RWMutex + + currentID = uint32(0) + quitOnce sync.Once +) + +func init() { + runtime.LockOSThread() +} + +// MenuItem is used to keep track each menu item of systray. +// Don't create it directly, use the one systray.AddMenuItem() returned +type MenuItem struct { + // ClickedCh is the channel which will be notified when the menu item is clicked + ClickedCh chan struct{} + + // id uniquely identify a menu item, not supposed to be modified + id uint32 + // title is the text shown on menu item + title string + // tooltip is the text shown when pointing to menu item + tooltip string + // disabled menu item is grayed out and has no effect when clicked + disabled bool + // checked menu item has a tick before the title + checked bool + // has the menu item a checkbox (Linux) + isCheckable bool + // parent item, for sub menus + parent *MenuItem +} + +func (item *MenuItem) String() string { + if item.parent == nil { + return fmt.Sprintf("MenuItem[%d, %q]", item.id, item.title) + } + return fmt.Sprintf("MenuItem[%d, parent %d, %q]", item.id, item.parent.id, item.title) +} + +// newMenuItem returns a populated MenuItem object +func newMenuItem(title string, tooltip string, parent *MenuItem) *MenuItem { + return &MenuItem{ + ClickedCh: make(chan struct{}), + id: atomic.AddUint32(¤tID, 1), + title: title, + tooltip: tooltip, + disabled: false, + checked: false, + isCheckable: false, + parent: parent, + } +} + +// Run initializes GUI and starts the event loop, then invokes the onReady +// callback. It blocks until systray.Quit() is called. +func Run(onReady, onExit func()) { + setInternalLoop(true) + Register(onReady, onExit) + + nativeLoop() +} + +// RunWithExternalLoop allows the systemtray module to operate with other tookits. +// The returned start and end functions should be called by the toolkit when the application has started and will end. +func RunWithExternalLoop(onReady, onExit func()) (start, end func()) { + Register(onReady, onExit) + + return nativeStart, nativeEnd +} + +// Register initializes GUI and registers the callbacks but relies on the +// caller to run the event loop somewhere else. It's useful if the program +// needs to show other UI elements, for example, webview. +// To overcome some OS weirdness, On macOS versions before Catalina, calling +// this does exactly the same as Run(). +func Register(onReady func(), onExit func()) { + if onReady == nil { + systrayReady = func() {} + } else { + // Run onReady on separate goroutine to avoid blocking event loop + readyCh := make(chan interface{}) + go func() { + <-readyCh + onReady() + }() + systrayReady = func() { + close(readyCh) + } + } + // unlike onReady, onExit runs in the event loop to make sure it has time to + // finish before the process terminates + if onExit == nil { + onExit = func() {} + } + systrayExit = onExit + registerSystray() +} + +// Quit the systray +func Quit() { + quitOnce.Do(quit) +} + +// AddMenuItem adds a menu item with the designated title and tooltip. +// It can be safely invoked from different goroutines. +// Created menu items are checkable on Windows and OSX by default. For Linux you have to use AddMenuItemCheckbox +func AddMenuItem(title string, tooltip string) *MenuItem { + item := newMenuItem(title, tooltip, nil) + item.update() + return item +} + +// AddMenuItemCheckbox adds a menu item with the designated title and tooltip and a checkbox for Linux. +// It can be safely invoked from different goroutines. +// On Windows and OSX this is the same as calling AddMenuItem +func AddMenuItemCheckbox(title string, tooltip string, checked bool) *MenuItem { + item := newMenuItem(title, tooltip, nil) + item.isCheckable = true + item.checked = checked + item.update() + return item +} + +// AddSeparator adds a separator bar to the menu +func AddSeparator() { + addSeparator(atomic.AddUint32(¤tID, 1)) +} + +// AddSubMenuItem adds a nested sub-menu item with the designated title and tooltip. +// It can be safely invoked from different goroutines. +// Created menu items are checkable on Windows and OSX by default. For Linux you have to use AddSubMenuItemCheckbox +func (item *MenuItem) AddSubMenuItem(title string, tooltip string) *MenuItem { + child := newMenuItem(title, tooltip, item) + child.update() + return child +} + +// AddSubMenuItemCheckbox adds a nested sub-menu item with the designated title and tooltip and a checkbox for Linux. +// It can be safely invoked from different goroutines. +// On Windows and OSX this is the same as calling AddSubMenuItem +func (item *MenuItem) AddSubMenuItemCheckbox(title string, tooltip string, checked bool) *MenuItem { + child := newMenuItem(title, tooltip, item) + child.isCheckable = true + child.checked = checked + child.update() + return child +} + +// SetTitle set the text to display on a menu item +func (item *MenuItem) SetTitle(title string) { + item.title = title + item.update() +} + +// SetTooltip set the tooltip to show when mouse hover +func (item *MenuItem) SetTooltip(tooltip string) { + item.tooltip = tooltip + item.update() +} + +// Disabled checks if the menu item is disabled +func (item *MenuItem) Disabled() bool { + return item.disabled +} + +// Enable a menu item regardless if it's previously enabled or not +func (item *MenuItem) Enable() { + item.disabled = false + item.update() +} + +// Disable a menu item regardless if it's previously disabled or not +func (item *MenuItem) Disable() { + item.disabled = true + item.update() +} + +// Hide hides a menu item +func (item *MenuItem) Hide() { + hideMenuItem(item) +} + +// Show shows a previously hidden menu item +func (item *MenuItem) Show() { + showMenuItem(item) +} + +// Checked returns if the menu item has a check mark +func (item *MenuItem) Checked() bool { + return item.checked +} + +// Check a menu item regardless if it's previously checked or not +func (item *MenuItem) Check() { + item.checked = true + item.update() +} + +// Uncheck a menu item regardless if it's previously unchecked or not +func (item *MenuItem) Uncheck() { + item.checked = false + item.update() +} + +// update propagates changes on a menu item to systray +func (item *MenuItem) update() { + menuItemsLock.Lock() + menuItems[item.id] = item + menuItemsLock.Unlock() + addOrUpdateMenuItem(item) +} + +func systrayMenuItemSelected(id uint32) { + menuItemsLock.RLock() + item, ok := menuItems[id] + menuItemsLock.RUnlock() + if !ok { + fmt.Printf("No menu item with ID %v", id) + return + } + select { + case item.ClickedCh <- struct{}{}: + // in case no one waiting for the channel + default: + } +} diff --git a/vendor/fyne.io/systray/systray.h b/vendor/fyne.io/systray/systray.h new file mode 100644 index 0000000000..9a4848eb33 --- /dev/null +++ b/vendor/fyne.io/systray/systray.h @@ -0,0 +1,19 @@ +#include "stdbool.h" + +extern void systray_ready(); +extern void systray_on_exit(); +extern void systray_menu_item_selected(int menu_id); +void registerSystray(void); +void nativeEnd(void); +int nativeLoop(void); +void nativeStart(void); + +void setIcon(const char* iconBytes, int length, bool template); +void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template); +void setTitle(char* title); +void setTooltip(char* tooltip); +void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked, short isCheckable); +void add_separator(int menuId); +void hide_menu_item(int menuId); +void show_menu_item(int menuId); +void quit(); diff --git a/vendor/fyne.io/systray/systray_darwin.go b/vendor/fyne.io/systray/systray_darwin.go new file mode 100644 index 0000000000..d80f986b71 --- /dev/null +++ b/vendor/fyne.io/systray/systray_darwin.go @@ -0,0 +1,143 @@ +package systray + +/* +#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc +#cgo darwin LDFLAGS: -framework Cocoa + +#include +#include "systray.h" + +void setInternalLoop(bool); +*/ +import "C" + +import ( + "unsafe" +) + +// SetTemplateIcon sets the systray icon as a template icon (on Mac), falling back +// to a regular icon on other platforms. +// templateIconBytes and regularIconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { + cstr := (*C.char)(unsafe.Pointer(&templateIconBytes[0])) + C.setIcon(cstr, (C.int)(len(templateIconBytes)), true) +} + +// SetIcon sets the icon of a menu item. Only works on macOS and Windows. +// iconBytes should be the content of .ico/.jpg/.png +func (item *MenuItem) SetIcon(iconBytes []byte) { + cstr := (*C.char)(unsafe.Pointer(&iconBytes[0])) + C.setMenuItemIcon(cstr, (C.int)(len(iconBytes)), C.int(item.id), false) +} + +// SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it +// falls back to the regular icon bytes and on Linux it does nothing. +// templateIconBytes and regularIconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { + cstr := (*C.char)(unsafe.Pointer(&templateIconBytes[0])) + C.setMenuItemIcon(cstr, (C.int)(len(templateIconBytes)), C.int(item.id), true) +} + +func registerSystray() { + C.registerSystray() +} + +func nativeLoop() { + C.nativeLoop() +} + +func nativeEnd() { + C.nativeEnd() +} + +func nativeStart() { + C.nativeStart() +} + +func quit() { + C.quit() +} + +func setInternalLoop(internal bool) { + C.setInternalLoop(C.bool(internal)) +} + +// SetIcon sets the systray icon. +// iconBytes should be the content of .ico for windows and .ico/.jpg/.png +// for other platforms. +func SetIcon(iconBytes []byte) { + cstr := (*C.char)(unsafe.Pointer(&iconBytes[0])) + C.setIcon(cstr, (C.int)(len(iconBytes)), false) +} + +// SetTitle sets the systray title, only available on Mac and Linux. +func SetTitle(title string) { + C.setTitle(C.CString(title)) +} + +// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon, +// only available on Mac and Windows. +func SetTooltip(tooltip string) { + C.setTooltip(C.CString(tooltip)) +} + +func addOrUpdateMenuItem(item *MenuItem) { + var disabled C.short + if item.disabled { + disabled = 1 + } + var checked C.short + if item.checked { + checked = 1 + } + var isCheckable C.short + if item.isCheckable { + isCheckable = 1 + } + var parentID uint32 = 0 + if item.parent != nil { + parentID = item.parent.id + } + C.add_or_update_menu_item( + C.int(item.id), + C.int(parentID), + C.CString(item.title), + C.CString(item.tooltip), + disabled, + checked, + isCheckable, + ) +} + +func addSeparator(id uint32) { + C.add_separator(C.int(id)) +} + +func hideMenuItem(item *MenuItem) { + C.hide_menu_item( + C.int(item.id), + ) +} + +func showMenuItem(item *MenuItem) { + C.show_menu_item( + C.int(item.id), + ) +} + +//export systray_ready +func systray_ready() { + systrayReady() +} + +//export systray_on_exit +func systray_on_exit() { + systrayExit() +} + +//export systray_menu_item_selected +func systray_menu_item_selected(cID C.int) { + systrayMenuItemSelected(uint32(cID)) +} diff --git a/vendor/fyne.io/systray/systray_darwin.m b/vendor/fyne.io/systray/systray_darwin.m new file mode 100644 index 0000000000..14f6a50fb0 --- /dev/null +++ b/vendor/fyne.io/systray/systray_darwin.m @@ -0,0 +1,317 @@ +#import +#include "systray.h" + +#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101400 + + #ifndef NSControlStateValueOff + #define NSControlStateValueOff NSOffState + #endif + + #ifndef NSControlStateValueOn + #define NSControlStateValueOn NSOnState + #endif + +#endif + +@interface MenuItem : NSObject +{ + @public + NSNumber* menuId; + NSNumber* parentMenuId; + NSString* title; + NSString* tooltip; + short disabled; + short checked; +} +-(id) initWithId: (int)theMenuId +withParentMenuId: (int)theParentMenuId + withTitle: (const char*)theTitle + withTooltip: (const char*)theTooltip + withDisabled: (short)theDisabled + withChecked: (short)theChecked; + @end + @implementation MenuItem + -(id) initWithId: (int)theMenuId + withParentMenuId: (int)theParentMenuId + withTitle: (const char*)theTitle + withTooltip: (const char*)theTooltip + withDisabled: (short)theDisabled + withChecked: (short)theChecked +{ + menuId = [NSNumber numberWithInt:theMenuId]; + parentMenuId = [NSNumber numberWithInt:theParentMenuId]; + title = [[NSString alloc] initWithCString:theTitle + encoding:NSUTF8StringEncoding]; + tooltip = [[NSString alloc] initWithCString:theTooltip + encoding:NSUTF8StringEncoding]; + disabled = theDisabled; + checked = theChecked; + return self; +} +@end + +@interface AppDelegate: NSObject + - (void) add_or_update_menu_item:(MenuItem*) item; + - (IBAction)menuHandler:(id)sender; + @property (assign) IBOutlet NSWindow *window; + @end + + @implementation AppDelegate +{ + NSStatusItem *statusItem; + NSMenu *menu; + NSCondition* cond; +} + +@synthesize window = _window; + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + self->statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; + self->menu = [[NSMenu alloc] init]; + [self->menu setAutoenablesItems: FALSE]; + [self->statusItem setMenu:self->menu]; + systray_ready(); +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification +{ + systray_on_exit(); +} + +- (void)setIcon:(NSImage *)image { + statusItem.button.image = image; + [self updateTitleButtonStyle]; +} + +- (void)setTitle:(NSString *)title { + statusItem.button.title = title; + [self updateTitleButtonStyle]; +} + +-(void)updateTitleButtonStyle { + if (statusItem.button.image != nil) { + if ([statusItem.button.title length] == 0) { + statusItem.button.imagePosition = NSImageOnly; + } else { + statusItem.button.imagePosition = NSImageLeft; + } + } else { + statusItem.button.imagePosition = NSNoImage; + } +} + + +- (void)setTooltip:(NSString *)tooltip { + statusItem.button.toolTip = tooltip; +} + +- (IBAction)menuHandler:(id)sender +{ + NSNumber* menuId = [sender representedObject]; + systray_menu_item_selected(menuId.intValue); +} + +- (void)add_or_update_menu_item:(MenuItem *)item { + NSMenu *theMenu = self->menu; + NSMenuItem *parentItem; + if ([item->parentMenuId integerValue] > 0) { + parentItem = find_menu_item(menu, item->parentMenuId); + if (parentItem.hasSubmenu) { + theMenu = parentItem.submenu; + } else { + theMenu = [[NSMenu alloc] init]; + [theMenu setAutoenablesItems:NO]; + [parentItem setSubmenu:theMenu]; + } + } + + NSMenuItem *menuItem; + menuItem = find_menu_item(theMenu, item->menuId); + if (menuItem == NULL) { + menuItem = [theMenu addItemWithTitle:item->title + action:@selector(menuHandler:) + keyEquivalent:@""]; + [menuItem setRepresentedObject:item->menuId]; + } + [menuItem setTitle:item->title]; + [menuItem setTag:[item->menuId integerValue]]; + [menuItem setTarget:self]; + [menuItem setToolTip:item->tooltip]; + if (item->disabled == 1) { + menuItem.enabled = FALSE; + } else { + menuItem.enabled = TRUE; + } + if (item->checked == 1) { + menuItem.state = NSControlStateValueOn; + } else { + menuItem.state = NSControlStateValueOff; + } +} + +NSMenuItem *find_menu_item(NSMenu *ourMenu, NSNumber *menuId) { + NSMenuItem *foundItem = [ourMenu itemWithTag:[menuId integerValue]]; + if (foundItem != NULL) { + return foundItem; + } + NSArray *menu_items = ourMenu.itemArray; + int i; + for (i = 0; i < [menu_items count]; i++) { + NSMenuItem *i_item = [menu_items objectAtIndex:i]; + if (i_item.hasSubmenu) { + foundItem = find_menu_item(i_item.submenu, menuId); + if (foundItem != NULL) { + return foundItem; + } + } + } + + return NULL; +}; + +- (void) add_separator:(NSNumber*) menuId +{ + [menu addItem: [NSMenuItem separatorItem]]; +} + +- (void) hide_menu_item:(NSNumber*) menuId +{ + NSMenuItem* menuItem = find_menu_item(menu, menuId); + if (menuItem != NULL) { + [menuItem setHidden:TRUE]; + } +} + +- (void) setMenuItemIcon:(NSArray*)imageAndMenuId { + NSImage* image = [imageAndMenuId objectAtIndex:0]; + NSNumber* menuId = [imageAndMenuId objectAtIndex:1]; + + NSMenuItem* menuItem; + menuItem = find_menu_item(menu, menuId); + if (menuItem == NULL) { + return; + } + menuItem.image = image; +} + +- (void) show_menu_item:(NSNumber*) menuId +{ + NSMenuItem* menuItem = find_menu_item(menu, menuId); + if (menuItem != NULL) { + [menuItem setHidden:FALSE]; + } +} + +- (void) quit +{ + [NSApp terminate:self]; +} + +@end + +bool internalLoop = false; +AppDelegate *owner; + +void setInternalLoop(bool i) { + internalLoop = i; +} + +void registerSystray(void) { + if (!internalLoop) { // with an external loop we don't take ownership of the app + return; + } + + owner = [[AppDelegate alloc] init]; + [[NSApplication sharedApplication] setDelegate:owner]; + + // A workaround to avoid crashing on macOS versions before Catalina. Somehow + // SIGSEGV would happen inside AppKit if [NSApp run] is called from a + // different function, even if that function is called right after this. + if (floor(NSAppKitVersionNumber) <= /*NSAppKitVersionNumber10_14*/ 1671){ + [NSApp run]; + } +} + +void nativeEnd(void) { + systray_on_exit(); +} + +int nativeLoop(void) { + if (floor(NSAppKitVersionNumber) > /*NSAppKitVersionNumber10_14*/ 1671){ + [NSApp run]; + } + return EXIT_SUCCESS; +} + +void nativeStart(void) { + owner = [[AppDelegate alloc] init]; + + NSNotification *launched = [NSNotification notificationWithName:NSApplicationDidFinishLaunchingNotification + object:[NSApplication sharedApplication]]; + [owner applicationDidFinishLaunching:launched]; +} + +void runInMainThread(SEL method, id object) { + [owner + performSelectorOnMainThread:method + withObject:object + waitUntilDone: YES]; +} + +void setIcon(const char* iconBytes, int length, bool template) { + NSData* buffer = [NSData dataWithBytes: iconBytes length:length]; + NSImage *image = [[NSImage alloc] initWithData:buffer]; + [image setSize:NSMakeSize(16, 16)]; + image.template = template; + runInMainThread(@selector(setIcon:), (id)image); +} + +void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template) { + NSData* buffer = [NSData dataWithBytes: iconBytes length:length]; + NSImage *image = [[NSImage alloc] initWithData:buffer]; + [image setSize:NSMakeSize(16, 16)]; + image.template = template; + NSNumber *mId = [NSNumber numberWithInt:menuId]; + runInMainThread(@selector(setMenuItemIcon:), @[image, (id)mId]); +} + +void setTitle(char* ctitle) { + NSString* title = [[NSString alloc] initWithCString:ctitle + encoding:NSUTF8StringEncoding]; + free(ctitle); + runInMainThread(@selector(setTitle:), (id)title); +} + +void setTooltip(char* ctooltip) { + NSString* tooltip = [[NSString alloc] initWithCString:ctooltip + encoding:NSUTF8StringEncoding]; + free(ctooltip); + runInMainThread(@selector(setTooltip:), (id)tooltip); +} + +void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked, short isCheckable) { + MenuItem* item = [[MenuItem alloc] initWithId: menuId withParentMenuId: parentMenuId withTitle: title withTooltip: tooltip withDisabled: disabled withChecked: checked]; + free(title); + free(tooltip); + runInMainThread(@selector(add_or_update_menu_item:), (id)item); +} + +void add_separator(int menuId) { + NSNumber *mId = [NSNumber numberWithInt:menuId]; + runInMainThread(@selector(add_separator:), (id)mId); +} + +void hide_menu_item(int menuId) { + NSNumber *mId = [NSNumber numberWithInt:menuId]; + runInMainThread(@selector(hide_menu_item:), (id)mId); +} + +void show_menu_item(int menuId) { + NSNumber *mId = [NSNumber numberWithInt:menuId]; + runInMainThread(@selector(show_menu_item:), (id)mId); +} + +void quit() { + runInMainThread(@selector(quit), nil); +} diff --git a/vendor/fyne.io/systray/systray_linux.go b/vendor/fyne.io/systray/systray_linux.go new file mode 100644 index 0000000000..bb1a391741 --- /dev/null +++ b/vendor/fyne.io/systray/systray_linux.go @@ -0,0 +1,296 @@ +//Note that you need to have github.com/knightpp/dbus-codegen-go installed from "custom" branch +//go:generate dbus-codegen-go -prefix org.kde -package notifier -output internal/generated/notifier/status_notifier_item.go internal/StatusNotifierItem.xml +//go:generate dbus-codegen-go -prefix com.canonical -package menu -output internal/generated/menu/dbus_menu.go internal/DbusMenu.xml + +package systray + +import ( + "bytes" + "fmt" + "image" + _ "image/png" + "log" + "os" + + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" + "github.com/godbus/dbus/v5/prop" + + "fyne.io/systray/internal/generated/menu" + "fyne.io/systray/internal/generated/notifier" +) + +const ( + path = "/StatusNotifierItem" + menuPath = "/StatusNotifierMenu" +) + +var ( + // to signal quitting the internal main loop + quitChan = make(chan struct{}) + + // icon data for the main systray icon + iconData []byte + + // the title of our system tray icon + title string + + // instance is the current instance of our DBus tray server + instance *tray +) + +// SetTemplateIcon sets the systray icon as a template icon (on macOS), falling back +// to a regular icon on other platforms. +// templateIconBytes and iconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { + // TODO handle the templateIconBytes? + SetIcon(regularIconBytes) +} + +// SetIcon sets the systray icon. +// iconBytes should be the content of .ico for windows and .ico/.jpg/.png +// for other platforms. +func SetIcon(iconBytes []byte) { + iconData = iconBytes + + if instance != nil && instance.props != nil { + instance.props["org.kde.StatusNotifierItem"]["IconPixmap"].Value = []PX{convertToPixels(iconData)} + + if instance.conn != nil { + notifier.Emit(instance.conn, ¬ifier.StatusNotifierItem_NewIconSignal{ + Path: path, + Body: ¬ifier.StatusNotifierItem_NewIconSignalBody{}, + }) + } + } +} + +// SetTitle sets the systray title, only available on Mac and Linux. +func SetTitle(t string) { + title = t +} + +// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon, +// only available on Mac and Windows. +func SetTooltip(tooltip string) { +} + +// SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it +// falls back to the regular icon bytes and on Linux it does nothing. +// templateIconBytes and regularIconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { +} + +func setInternalLoop(_ bool) { + // nothing to action on Linux +} + +func registerSystray() { +} + +func nativeLoop() int { + nativeStart() + select { + case <-quitChan: + break + } + nativeEnd() + return 0 +} + +func nativeEnd() { + systrayExit() + instance.conn.Close() +} + +func quit() { + close(quitChan) +} + +func nativeStart() { + systrayReady() + instance = &tray{menu: &menuLayout{}} + + conn, _ := dbus.ConnectSessionBus() + instance.conn = conn + notifier.ExportStatusNotifierItem(conn, path, instance) + menu.ExportDbusmenu(conn, menuPath, instance) + + name := fmt.Sprintf("org.kde.StatusNotifierItem-%d-1", os.Getpid()) // register id 1 for this process + _, err := conn.RequestName(name, dbus.NameFlagDoNotQueue) + if err != nil { + // fall back to existing name + name = conn.Names()[0] + } + + _, err = prop.Export(conn, path, createPropSpec()) + if err != nil { + log.Printf("Failed to export notifier item properties to bus") + return + } + _, err = prop.Export(conn, menuPath, createMenuPropSpec()) + if err != nil { + log.Printf("Failed to export notifier menu properties to bus") + return + } + + node := introspect.Node{ + Name: path, + Interfaces: []introspect.Interface{ + introspect.IntrospectData, + prop.IntrospectData, + notifier.IntrospectDataStatusNotifierItem, + }, + } + err = conn.Export(introspect.NewIntrospectable(&node), path, + "org.freedesktop.DBus.Introspectable") + if err != nil { + log.Printf("Failed to export introspection") + return + } + menuNode := introspect.Node{ + Name: menuPath, + Interfaces: []introspect.Interface{ + introspect.IntrospectData, + prop.IntrospectData, + menu.IntrospectDataDbusmenu, + }, + } + err = conn.Export(introspect.NewIntrospectable(&menuNode), menuPath, + "org.freedesktop.DBus.Introspectable") + if err != nil { + log.Printf("Failed to export introspection") + return + } + + obj := conn.Object("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher") + call := obj.Call("org.kde.StatusNotifierWatcher.RegisterStatusNotifierItem", 0, path) + if call.Err != nil { + log.Printf("Failed to register our icon with the notifier watcher, maybe no tray running?") + } +} + +// tray is a basic type that handles the dbus functionality +type tray struct { + conn *dbus.Conn + menu *menuLayout + props map[string]map[string]*prop.Prop +} + +// ContextMenu method is called when the user has right-clicked on our icon. +func (t *tray) ContextMenu(x, y int32) *dbus.Error { + // not supported for systray lib + return nil +} + +// Activate requests that we perform the primary action, such as showing a menu. +func (t *tray) Activate(x, y int32) *dbus.Error { + // TODO show menu, or have it handled in the dbus? + return nil +} + +// SecondaryActivate is alternative non-context click, such as middle mouse button. +func (t *tray) SecondaryActivate(x, y int32) *dbus.Error { + return nil +} + +// Scroll is called when the mouse wheel scrolls over the icon. +func (t *tray) Scroll(delta int32, orient string) *dbus.Error { + return nil +} + +type PX struct { + W, H int + Pix []byte +} + +func convertToPixels(data []byte) PX { + if len(iconData) == 0 { + return PX{} + } + + img, _, err := image.Decode(bytes.NewReader(iconData)) + if err != nil { + log.Printf("Failed to read icon format %v", err) + return PX{} + } + + return PX{ + img.Bounds().Dx(), img.Bounds().Dy(), + argbForImage(img), + } +} + +func argbForImage(img image.Image) []byte { + w, h := img.Bounds().Dx(), img.Bounds().Dy() + data := make([]byte, w*h*4) + i := 0 + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + r, g, b, a := img.At(x, y).RGBA() + data[i] = byte(a) + data[i+1] = byte(r) + data[i+2] = byte(g) + data[i+3] = byte(b) + i += 4 + } + } + return data +} + +func createPropSpec() map[string]map[string]*prop.Prop { + instance.props = map[string]map[string]*prop.Prop{ + "org.kde.StatusNotifierItem": { + "Status": { + "Active", // Passive, Active or NeedsAttention + false, + prop.EmitTrue, + nil, + }, + "Title": { + title, + false, + prop.EmitTrue, + nil, + }, + "Id": { + "1", + false, + prop.EmitTrue, + nil, + }, + "Category": { + "ApplicationStatus", + false, + prop.EmitTrue, + nil, + }, + "IconName": { + "", + false, + prop.EmitTrue, + nil, + }, + "IconPixmap": { + []PX{convertToPixels(iconData)}, + false, + prop.EmitTrue, + nil, + }, + "IconThemePath": { + "", + false, + prop.EmitTrue, + nil, + }, + "Menu": { + menuPath, + false, + prop.EmitTrue, + nil, + }, + }} + return instance.props +} diff --git a/vendor/fyne.io/systray/systray_menu_linux.go b/vendor/fyne.io/systray/systray_menu_linux.go new file mode 100644 index 0000000000..727ef23d25 --- /dev/null +++ b/vendor/fyne.io/systray/systray_menu_linux.go @@ -0,0 +1,207 @@ +package systray + +import ( + "log" + + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/prop" + + "fyne.io/systray/internal/generated/menu" +) + +// SetIcon sets the icon of a menu item. Only works on macOS and Windows. +// iconBytes should be the content of .ico/.jpg/.png +func (item *MenuItem) SetIcon(iconBytes []byte) { +} + +func (t *tray) GetLayout(parentId int32, recursionDepth int32, propertyNames []string) (revision uint32, layout menuLayout, err *dbus.Error) { + return 1, *instance.menu, nil +} + +// GetGroupProperties is com.canonical.dbusmenu.GetGroupProperties method. +func (t *tray) GetGroupProperties(ids []int32, propertyNames []string) (properties []struct { + V0 int32 + V1 map[string]dbus.Variant +}, err *dbus.Error) { + return +} + +// GetProperty is com.canonical.dbusmenu.GetProperty method. +func (t *tray) GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) { + return +} + +// Event is com.canonical.dbusmenu.Event method. +func (t *tray) Event(id int32, eventId string, data dbus.Variant, timestamp uint32) (err *dbus.Error) { + if eventId == "clicked" { + item, ok := menuItems[uint32(id)] + if !ok { + log.Printf("Failed to look up clicked menu item") + return + } + + item.ClickedCh <- struct{}{} + } + return +} + +// EventGroup is com.canonical.dbusmenu.EventGroup method. +func (t *tray) EventGroup(events []struct { + V0 int32 + V1 string + V2 dbus.Variant + V3 uint32 +}) (idErrors []int32, err *dbus.Error) { + return +} + +// AboutToShow is com.canonical.dbusmenu.AboutToShow method. +func (t *tray) AboutToShow(id int32) (needUpdate bool, err *dbus.Error) { + return +} + +// AboutToShowGroup is com.canonical.dbusmenu.AboutToShowGroup method. +func (t *tray) AboutToShowGroup(ids []int32) (updatesNeeded []int32, idErrors []int32, err *dbus.Error) { + return +} + +func createMenuPropSpec() map[string]map[string]*prop.Prop { + return map[string]map[string]*prop.Prop{ + "com.canonical.dbusmenu": { + "Version": { + uint32(1), + false, + prop.EmitTrue, + nil, + }, + "TextDirection": { + "ltr", + false, + prop.EmitTrue, + nil, + }, + "Status": { + "active", + false, + prop.EmitTrue, + nil, + }, + "IconThemePath": { + []string{}, + false, + prop.EmitTrue, + nil, + }, + }, + } +} + +// menuLayout is a named struct to map into generated bindings. It represents the layout of a menu item +type menuLayout = struct { + V0 int32 // the unique ID of this item + V1 map[string]dbus.Variant // properties for this menu item layout + V2 []dbus.Variant // child menu item layouts +} + +func addOrUpdateMenuItem(item *MenuItem) { + var layout *menuLayout + m, exists := findLayout(int32(item.id)) + if exists { + layout = m + } else { + layout = &menuLayout{ + V0: int32(item.id), + V1: map[string]dbus.Variant{}, + V2: []dbus.Variant{}, + } + + parent := instance.menu + if item.parent != nil { + m, ok := findLayout(int32(item.parent.id)) + if ok { + parent = m + parent.V1["children-display"] = dbus.MakeVariant("submenu") + } + } + parent.V2 = append(parent.V2, dbus.MakeVariant(layout)) + } + + applyItemToLayout(item, layout) + if exists { + refresh() + } +} + +func addSeparator(id uint32) { + layout := &menuLayout{ + V0: int32(id), + V1: map[string]dbus.Variant{ + "type": dbus.MakeVariant("separator"), + }, + V2: []dbus.Variant{}, + } + + instance.menu.V2 = append(instance.menu.V2, dbus.MakeVariant(layout)) +} + +func applyItemToLayout(in *MenuItem, out *menuLayout) { + out.V1["enabled"] = dbus.MakeVariant(!in.disabled) + out.V1["label"] = dbus.MakeVariant(in.title) + + if in.isCheckable { + out.V1["toggle-type"] = dbus.MakeVariant("checkmark") + if in.checked { + out.V1["toggle-state"] = dbus.MakeVariant(1) + } else { + out.V1["toggle-state"] = dbus.MakeVariant(0) + } + } else { + out.V1["toggle-type"] = dbus.MakeVariant("") + out.V1["toggle-state"] = dbus.MakeVariant(0) + } +} + +func findLayout(id int32) (*menuLayout, bool) { + return findSubLayout(id, instance.menu.V2) +} + +func findSubLayout(id int32, vals []dbus.Variant) (*menuLayout, bool) { + for _, i := range vals { + item := i.Value().(*menuLayout) + if item.V0 == id { + return item, true + } + + if len(item.V2) > 0 { + child, ok := findSubLayout(id, item.V2) + if ok { + return child, true + } + } + } + + return nil, false +} + +func hideMenuItem(item *MenuItem) { + m, exists := findLayout(int32(item.id)) + if exists { + m.V1["visible"] = dbus.MakeVariant(false) + refresh() + } +} + +func showMenuItem(item *MenuItem) { + m, exists := findLayout(int32(item.id)) + if exists { + m.V1["visible"] = dbus.MakeVariant(true) + refresh() + } +} + +func refresh() { + menu.Emit(instance.conn, &menu.Dbusmenu_LayoutUpdatedSignal{ + Path: menuPath, + Body: &menu.Dbusmenu_LayoutUpdatedSignalBody{}, + }) +} diff --git a/vendor/fyne.io/systray/systray_windows.go b/vendor/fyne.io/systray/systray_windows.go new file mode 100644 index 0000000000..ac3d722258 --- /dev/null +++ b/vendor/fyne.io/systray/systray_windows.go @@ -0,0 +1,957 @@ +// +build windows + +package systray + +import ( + "crypto/md5" + "encoding/hex" + "io/ioutil" + "log" + "os" + "path/filepath" + "sort" + "sync" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +// Helpful sources: https://github.com/golang/exp/blob/master/shiny/driver/internal/win32 + +var ( + g32 = windows.NewLazySystemDLL("Gdi32.dll") + pCreateCompatibleBitmap = g32.NewProc("CreateCompatibleBitmap") + pCreateCompatibleDC = g32.NewProc("CreateCompatibleDC") + pDeleteDC = g32.NewProc("DeleteDC") + pSelectObject = g32.NewProc("SelectObject") + + k32 = windows.NewLazySystemDLL("Kernel32.dll") + pGetModuleHandle = k32.NewProc("GetModuleHandleW") + + s32 = windows.NewLazySystemDLL("Shell32.dll") + pShellNotifyIcon = s32.NewProc("Shell_NotifyIconW") + + u32 = windows.NewLazySystemDLL("User32.dll") + pCreateMenu = u32.NewProc("CreateMenu") + pCreatePopupMenu = u32.NewProc("CreatePopupMenu") + pCreateWindowEx = u32.NewProc("CreateWindowExW") + pDefWindowProc = u32.NewProc("DefWindowProcW") + pDeleteMenu = u32.NewProc("DeleteMenu") + pDestroyWindow = u32.NewProc("DestroyWindow") + pDispatchMessage = u32.NewProc("DispatchMessageW") + pDrawIconEx = u32.NewProc("DrawIconEx") + pGetCursorPos = u32.NewProc("GetCursorPos") + pGetDC = u32.NewProc("GetDC") + pGetMenuItemID = u32.NewProc("GetMenuItemID") + pGetMessage = u32.NewProc("GetMessageW") + pGetSystemMetrics = u32.NewProc("GetSystemMetrics") + pInsertMenuItem = u32.NewProc("InsertMenuItemW") + pLoadCursor = u32.NewProc("LoadCursorW") + pLoadIcon = u32.NewProc("LoadIconW") + pLoadImage = u32.NewProc("LoadImageW") + pPostMessage = u32.NewProc("PostMessageW") + pPostQuitMessage = u32.NewProc("PostQuitMessage") + pRegisterClass = u32.NewProc("RegisterClassExW") + pRegisterWindowMessage = u32.NewProc("RegisterWindowMessageW") + pReleaseDC = u32.NewProc("ReleaseDC") + pSetForegroundWindow = u32.NewProc("SetForegroundWindow") + pSetMenuInfo = u32.NewProc("SetMenuInfo") + pSetMenuItemInfo = u32.NewProc("SetMenuItemInfoW") + pShowWindow = u32.NewProc("ShowWindow") + pTrackPopupMenu = u32.NewProc("TrackPopupMenu") + pTranslateMessage = u32.NewProc("TranslateMessage") + pUnregisterClass = u32.NewProc("UnregisterClassW") + pUpdateWindow = u32.NewProc("UpdateWindow") +) + +// Contains window class information. +// It is used with the RegisterClassEx and GetClassInfoEx functions. +// https://msdn.microsoft.com/en-us/library/ms633577.aspx +type wndClassEx struct { + Size, Style uint32 + WndProc uintptr + ClsExtra, WndExtra int32 + Instance, Icon, Cursor, Background windows.Handle + MenuName, ClassName *uint16 + IconSm windows.Handle +} + +// Registers a window class for subsequent use in calls to the CreateWindow or CreateWindowEx function. +// https://msdn.microsoft.com/en-us/library/ms633587.aspx +func (w *wndClassEx) register() error { + w.Size = uint32(unsafe.Sizeof(*w)) + res, _, err := pRegisterClass.Call(uintptr(unsafe.Pointer(w))) + if res == 0 { + return err + } + return nil +} + +// Unregisters a window class, freeing the memory required for the class. +// https://msdn.microsoft.com/en-us/library/ms644899.aspx +func (w *wndClassEx) unregister() error { + res, _, err := pUnregisterClass.Call( + uintptr(unsafe.Pointer(w.ClassName)), + uintptr(w.Instance), + ) + if res == 0 { + return err + } + return nil +} + +// Contains information that the system needs to display notifications in the notification area. +// Used by Shell_NotifyIcon. +// https://msdn.microsoft.com/en-us/library/windows/desktop/bb773352(v=vs.85).aspx +// https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159 +type notifyIconData struct { + Size uint32 + Wnd windows.Handle + ID, Flags, CallbackMessage uint32 + Icon windows.Handle + Tip [128]uint16 + State, StateMask uint32 + Info [256]uint16 + Timeout, Version uint32 + InfoTitle [64]uint16 + InfoFlags uint32 + GuidItem windows.GUID + BalloonIcon windows.Handle +} + +func (nid *notifyIconData) add() error { + const NIM_ADD = 0x00000000 + res, _, err := pShellNotifyIcon.Call( + uintptr(NIM_ADD), + uintptr(unsafe.Pointer(nid)), + ) + if res == 0 { + return err + } + return nil +} + +func (nid *notifyIconData) modify() error { + const NIM_MODIFY = 0x00000001 + res, _, err := pShellNotifyIcon.Call( + uintptr(NIM_MODIFY), + uintptr(unsafe.Pointer(nid)), + ) + if res == 0 { + return err + } + return nil +} + +func (nid *notifyIconData) delete() error { + const NIM_DELETE = 0x00000002 + res, _, err := pShellNotifyIcon.Call( + uintptr(NIM_DELETE), + uintptr(unsafe.Pointer(nid)), + ) + if res == 0 { + return err + } + return nil +} + +// Contains information about a menu item. +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx +type menuItemInfo struct { + Size, Mask, Type, State uint32 + ID uint32 + SubMenu, Checked, Unchecked windows.Handle + ItemData uintptr + TypeData *uint16 + Cch uint32 + BMPItem windows.Handle +} + +// The POINT structure defines the x- and y- coordinates of a point. +// https://msdn.microsoft.com/en-us/library/windows/desktop/dd162805(v=vs.85).aspx +type point struct { + X, Y int32 +} + +// Contains information about loaded resources +type winTray struct { + instance, + icon, + cursor, + window windows.Handle + + loadedImages map[string]windows.Handle + muLoadedImages sync.RWMutex + // menus keeps track of the submenus keyed by the menu item ID, plus 0 + // which corresponds to the main popup menu. + menus map[uint32]windows.Handle + muMenus sync.RWMutex + // menuOf keeps track of the menu each menu item belongs to. + menuOf map[uint32]windows.Handle + muMenuOf sync.RWMutex + // menuItemIcons maintains the bitmap of each menu item (if applies). It's + // needed to show the icon correctly when showing a previously hidden menu + // item again. + menuItemIcons map[uint32]windows.Handle + muMenuItemIcons sync.RWMutex + visibleItems map[uint32][]uint32 + muVisibleItems sync.RWMutex + + nid *notifyIconData + muNID sync.RWMutex + wcex *wndClassEx + + wmSystrayMessage, + wmTaskbarCreated uint32 +} + +// Loads an image from file and shows it in tray. +// Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx +func (t *winTray) setIcon(src string) error { + const NIF_ICON = 0x00000002 + + h, err := t.loadIconFrom(src) + if err != nil { + return err + } + + t.muNID.Lock() + defer t.muNID.Unlock() + t.nid.Icon = h + t.nid.Flags |= NIF_ICON + t.nid.Size = uint32(unsafe.Sizeof(*t.nid)) + + return t.nid.modify() +} + +// Sets tooltip on icon. +// Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx +func (t *winTray) setTooltip(src string) error { + const NIF_TIP = 0x00000004 + b, err := windows.UTF16FromString(src) + if err != nil { + return err + } + + t.muNID.Lock() + defer t.muNID.Unlock() + copy(t.nid.Tip[:], b[:]) + t.nid.Flags |= NIF_TIP + t.nid.Size = uint32(unsafe.Sizeof(*t.nid)) + + return t.nid.modify() +} + +var wt winTray + +// WindowProc callback function that processes messages sent to a window. +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx +func (t *winTray) wndProc(hWnd windows.Handle, message uint32, wParam, lParam uintptr) (lResult uintptr) { + const ( + WM_RBUTTONUP = 0x0205 + WM_LBUTTONUP = 0x0202 + WM_COMMAND = 0x0111 + WM_ENDSESSION = 0x0016 + WM_CLOSE = 0x0010 + WM_DESTROY = 0x0002 + WM_CREATE = 0x0001 + ) + switch message { + case WM_CREATE: + systrayReady() + case WM_COMMAND: + menuItemId := int32(wParam) + // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-command#menus + if menuItemId != -1 { + systrayMenuItemSelected(uint32(wParam)) + } + case WM_CLOSE: + pDestroyWindow.Call(uintptr(t.window)) + t.wcex.unregister() + case WM_DESTROY: + // same as WM_ENDSESSION, but throws 0 exit code after all + defer pPostQuitMessage.Call(uintptr(int32(0))) + fallthrough + case WM_ENDSESSION: + t.muNID.Lock() + if t.nid != nil { + t.nid.delete() + } + t.muNID.Unlock() + systrayExit() + case t.wmSystrayMessage: + switch lParam { + case WM_RBUTTONUP, WM_LBUTTONUP: + t.showMenu() + } + case t.wmTaskbarCreated: // on explorer.exe restarts + t.muNID.Lock() + t.nid.add() + t.muNID.Unlock() + default: + // Calls the default window procedure to provide default processing for any window messages that an application does not process. + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms633572(v=vs.85).aspx + lResult, _, _ = pDefWindowProc.Call( + uintptr(hWnd), + uintptr(message), + uintptr(wParam), + uintptr(lParam), + ) + } + return +} + +func (t *winTray) initInstance() error { + const IDI_APPLICATION = 32512 + const IDC_ARROW = 32512 // Standard arrow + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx + const SW_HIDE = 0 + const CW_USEDEFAULT = 0x80000000 + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms632600(v=vs.85).aspx + const ( + WS_CAPTION = 0x00C00000 + WS_MAXIMIZEBOX = 0x00010000 + WS_MINIMIZEBOX = 0x00020000 + WS_OVERLAPPED = 0x00000000 + WS_SYSMENU = 0x00080000 + WS_THICKFRAME = 0x00040000 + + WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX + ) + // https://msdn.microsoft.com/en-us/library/windows/desktop/ff729176 + const ( + CS_HREDRAW = 0x0002 + CS_VREDRAW = 0x0001 + ) + const NIF_MESSAGE = 0x00000001 + + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644931(v=vs.85).aspx + const WM_USER = 0x0400 + + const ( + className = "SystrayClass" + windowName = "" + ) + + t.wmSystrayMessage = WM_USER + 1 + t.visibleItems = make(map[uint32][]uint32) + t.menus = make(map[uint32]windows.Handle) + t.menuOf = make(map[uint32]windows.Handle) + t.menuItemIcons = make(map[uint32]windows.Handle) + + taskbarEventNamePtr, _ := windows.UTF16PtrFromString("TaskbarCreated") + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644947 + res, _, err := pRegisterWindowMessage.Call( + uintptr(unsafe.Pointer(taskbarEventNamePtr)), + ) + t.wmTaskbarCreated = uint32(res) + + t.loadedImages = make(map[string]windows.Handle) + + instanceHandle, _, err := pGetModuleHandle.Call(0) + if instanceHandle == 0 { + return err + } + t.instance = windows.Handle(instanceHandle) + + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648072(v=vs.85).aspx + iconHandle, _, err := pLoadIcon.Call(0, uintptr(IDI_APPLICATION)) + if iconHandle == 0 { + return err + } + t.icon = windows.Handle(iconHandle) + + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx + cursorHandle, _, err := pLoadCursor.Call(0, uintptr(IDC_ARROW)) + if cursorHandle == 0 { + return err + } + t.cursor = windows.Handle(cursorHandle) + + classNamePtr, err := windows.UTF16PtrFromString(className) + if err != nil { + return err + } + + windowNamePtr, err := windows.UTF16PtrFromString(windowName) + if err != nil { + return err + } + + t.wcex = &wndClassEx{ + Style: CS_HREDRAW | CS_VREDRAW, + WndProc: windows.NewCallback(t.wndProc), + Instance: t.instance, + Icon: t.icon, + Cursor: t.cursor, + Background: windows.Handle(6), // (COLOR_WINDOW + 1) + ClassName: classNamePtr, + IconSm: t.icon, + } + if err := t.wcex.register(); err != nil { + return err + } + + windowHandle, _, err := pCreateWindowEx.Call( + uintptr(0), + uintptr(unsafe.Pointer(classNamePtr)), + uintptr(unsafe.Pointer(windowNamePtr)), + uintptr(WS_OVERLAPPEDWINDOW), + uintptr(CW_USEDEFAULT), + uintptr(CW_USEDEFAULT), + uintptr(CW_USEDEFAULT), + uintptr(CW_USEDEFAULT), + uintptr(0), + uintptr(0), + uintptr(t.instance), + uintptr(0), + ) + if windowHandle == 0 { + return err + } + t.window = windows.Handle(windowHandle) + + pShowWindow.Call( + uintptr(t.window), + uintptr(SW_HIDE), + ) + + pUpdateWindow.Call( + uintptr(t.window), + ) + + t.muNID.Lock() + defer t.muNID.Unlock() + t.nid = ¬ifyIconData{ + Wnd: windows.Handle(t.window), + ID: 100, + Flags: NIF_MESSAGE, + CallbackMessage: t.wmSystrayMessage, + } + t.nid.Size = uint32(unsafe.Sizeof(*t.nid)) + + return t.nid.add() +} + +func (t *winTray) createMenu() error { + const MIM_APPLYTOSUBMENUS = 0x80000000 // Settings apply to the menu and all of its submenus + + menuHandle, _, err := pCreatePopupMenu.Call() + if menuHandle == 0 { + return err + } + t.menus[0] = windows.Handle(menuHandle) + + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647575(v=vs.85).aspx + mi := struct { + Size, Mask, Style, Max uint32 + Background windows.Handle + ContextHelpID uint32 + MenuData uintptr + }{ + Mask: MIM_APPLYTOSUBMENUS, + } + mi.Size = uint32(unsafe.Sizeof(mi)) + + res, _, err := pSetMenuInfo.Call( + uintptr(t.menus[0]), + uintptr(unsafe.Pointer(&mi)), + ) + if res == 0 { + return err + } + return nil +} + +func (t *winTray) convertToSubMenu(menuItemId uint32) (windows.Handle, error) { + const MIIM_SUBMENU = 0x00000004 + + res, _, err := pCreateMenu.Call() + if res == 0 { + return 0, err + } + menu := windows.Handle(res) + + mi := menuItemInfo{Mask: MIIM_SUBMENU, SubMenu: menu} + mi.Size = uint32(unsafe.Sizeof(mi)) + t.muMenuOf.RLock() + hMenu := t.menuOf[menuItemId] + t.muMenuOf.RUnlock() + res, _, err = pSetMenuItemInfo.Call( + uintptr(hMenu), + uintptr(menuItemId), + 0, + uintptr(unsafe.Pointer(&mi)), + ) + if res == 0 { + return 0, err + } + t.muMenus.Lock() + t.menus[menuItemId] = menu + t.muMenus.Unlock() + return menu, nil +} + +func (t *winTray) addOrUpdateMenuItem(menuItemId uint32, parentId uint32, title string, disabled, checked bool) error { + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx + const ( + MIIM_FTYPE = 0x00000100 + MIIM_BITMAP = 0x00000080 + MIIM_STRING = 0x00000040 + MIIM_SUBMENU = 0x00000004 + MIIM_ID = 0x00000002 + MIIM_STATE = 0x00000001 + ) + const MFT_STRING = 0x00000000 + const ( + MFS_CHECKED = 0x00000008 + MFS_DISABLED = 0x00000003 + ) + titlePtr, err := windows.UTF16PtrFromString(title) + if err != nil { + return err + } + + mi := menuItemInfo{ + Mask: MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_STATE, + Type: MFT_STRING, + ID: uint32(menuItemId), + TypeData: titlePtr, + Cch: uint32(len(title)), + } + mi.Size = uint32(unsafe.Sizeof(mi)) + if disabled { + mi.State |= MFS_DISABLED + } + if checked { + mi.State |= MFS_CHECKED + } + t.muMenuItemIcons.RLock() + hIcon := t.menuItemIcons[menuItemId] + t.muMenuItemIcons.RUnlock() + if hIcon > 0 { + mi.Mask |= MIIM_BITMAP + mi.BMPItem = hIcon + } + + var res uintptr + t.muMenus.RLock() + menu, exists := t.menus[parentId] + t.muMenus.RUnlock() + if !exists { + menu, err = t.convertToSubMenu(parentId) + if err != nil { + return err + } + t.muMenus.Lock() + t.menus[parentId] = menu + t.muMenus.Unlock() + } else if t.getVisibleItemIndex(parentId, menuItemId) != -1 { + // We set the menu item info based on the menuID + res, _, err = pSetMenuItemInfo.Call( + uintptr(menu), + uintptr(menuItemId), + 0, + uintptr(unsafe.Pointer(&mi)), + ) + } + + if res == 0 { + t.addToVisibleItems(parentId, menuItemId) + position := t.getVisibleItemIndex(parentId, menuItemId) + res, _, err = pInsertMenuItem.Call( + uintptr(menu), + uintptr(position), + 1, + uintptr(unsafe.Pointer(&mi)), + ) + if res == 0 { + t.delFromVisibleItems(parentId, menuItemId) + return err + } + t.muMenuOf.Lock() + t.menuOf[menuItemId] = menu + t.muMenuOf.Unlock() + } + + return nil +} + +func (t *winTray) addSeparatorMenuItem(menuItemId, parentId uint32) error { + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx + const ( + MIIM_FTYPE = 0x00000100 + MIIM_ID = 0x00000002 + MIIM_STATE = 0x00000001 + ) + const MFT_SEPARATOR = 0x00000800 + + mi := menuItemInfo{ + Mask: MIIM_FTYPE | MIIM_ID | MIIM_STATE, + Type: MFT_SEPARATOR, + ID: uint32(menuItemId), + } + + mi.Size = uint32(unsafe.Sizeof(mi)) + + t.addToVisibleItems(parentId, menuItemId) + position := t.getVisibleItemIndex(parentId, menuItemId) + t.muMenus.RLock() + menu := uintptr(t.menus[parentId]) + t.muMenus.RUnlock() + res, _, err := pInsertMenuItem.Call( + menu, + uintptr(position), + 1, + uintptr(unsafe.Pointer(&mi)), + ) + if res == 0 { + return err + } + + return nil +} + +func (t *winTray) hideMenuItem(menuItemId, parentId uint32) error { + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647629(v=vs.85).aspx + const MF_BYCOMMAND = 0x00000000 + const ERROR_SUCCESS syscall.Errno = 0 + + t.muMenus.RLock() + menu := uintptr(t.menus[parentId]) + t.muMenus.RUnlock() + res, _, err := pDeleteMenu.Call( + menu, + uintptr(menuItemId), + MF_BYCOMMAND, + ) + if res == 0 && err.(syscall.Errno) != ERROR_SUCCESS { + return err + } + t.delFromVisibleItems(parentId, menuItemId) + + return nil +} + +func (t *winTray) showMenu() error { + const ( + TPM_BOTTOMALIGN = 0x0020 + TPM_LEFTALIGN = 0x0000 + ) + p := point{} + res, _, err := pGetCursorPos.Call(uintptr(unsafe.Pointer(&p))) + if res == 0 { + return err + } + pSetForegroundWindow.Call(uintptr(t.window)) + + res, _, err = pTrackPopupMenu.Call( + uintptr(t.menus[0]), + TPM_BOTTOMALIGN|TPM_LEFTALIGN, + uintptr(p.X), + uintptr(p.Y), + 0, + uintptr(t.window), + 0, + ) + if res == 0 { + return err + } + + return nil +} + +func (t *winTray) delFromVisibleItems(parent, val uint32) { + t.muVisibleItems.Lock() + defer t.muVisibleItems.Unlock() + visibleItems := t.visibleItems[parent] + for i, itemval := range visibleItems { + if val == itemval { + visibleItems = append(visibleItems[:i], visibleItems[i+1:]...) + break + } + } +} + +func (t *winTray) addToVisibleItems(parent, val uint32) { + t.muVisibleItems.Lock() + defer t.muVisibleItems.Unlock() + if visibleItems, exists := t.visibleItems[parent]; !exists { + t.visibleItems[parent] = []uint32{val} + } else { + newvisible := append(visibleItems, val) + sort.Slice(newvisible, func(i, j int) bool { return newvisible[i] < newvisible[j] }) + t.visibleItems[parent] = newvisible + } +} + +func (t *winTray) getVisibleItemIndex(parent, val uint32) int { + t.muVisibleItems.RLock() + defer t.muVisibleItems.RUnlock() + for i, itemval := range t.visibleItems[parent] { + if val == itemval { + return i + } + } + return -1 +} + +// Loads an image from file to be shown in tray or menu item. +// LoadImage: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx +func (t *winTray) loadIconFrom(src string) (windows.Handle, error) { + const IMAGE_ICON = 1 // Loads an icon + const LR_LOADFROMFILE = 0x00000010 // Loads the stand-alone image from the file + const LR_DEFAULTSIZE = 0x00000040 // Loads default-size icon for windows(SM_CXICON x SM_CYICON) if cx, cy are set to zero + + // Save and reuse handles of loaded images + t.muLoadedImages.RLock() + h, ok := t.loadedImages[src] + t.muLoadedImages.RUnlock() + if !ok { + srcPtr, err := windows.UTF16PtrFromString(src) + if err != nil { + return 0, err + } + res, _, err := pLoadImage.Call( + 0, + uintptr(unsafe.Pointer(srcPtr)), + IMAGE_ICON, + 0, + 0, + LR_LOADFROMFILE|LR_DEFAULTSIZE, + ) + if res == 0 { + return 0, err + } + h = windows.Handle(res) + t.muLoadedImages.Lock() + t.loadedImages[src] = h + t.muLoadedImages.Unlock() + } + return h, nil +} + +func (t *winTray) iconToBitmap(hIcon windows.Handle) (windows.Handle, error) { + const SM_CXSMICON = 49 + const SM_CYSMICON = 50 + const DI_NORMAL = 0x3 + hDC, _, err := pGetDC.Call(uintptr(0)) + if hDC == 0 { + return 0, err + } + defer pReleaseDC.Call(uintptr(0), hDC) + hMemDC, _, err := pCreateCompatibleDC.Call(hDC) + if hMemDC == 0 { + return 0, err + } + defer pDeleteDC.Call(hMemDC) + cx, _, _ := pGetSystemMetrics.Call(SM_CXSMICON) + cy, _, _ := pGetSystemMetrics.Call(SM_CYSMICON) + hMemBmp, _, err := pCreateCompatibleBitmap.Call(hDC, cx, cy) + if hMemBmp == 0 { + return 0, err + } + hOriginalBmp, _, _ := pSelectObject.Call(hMemDC, hMemBmp) + defer pSelectObject.Call(hMemDC, hOriginalBmp) + res, _, err := pDrawIconEx.Call(hMemDC, 0, 0, uintptr(hIcon), cx, cy, 0, uintptr(0), DI_NORMAL) + if res == 0 { + return 0, err + } + return windows.Handle(hMemBmp), nil +} + +func registerSystray() { + if err := wt.initInstance(); err != nil { + log.Printf("Unable to init instance: %v", err) + return + } + + if err := wt.createMenu(); err != nil { + log.Printf("Unable to create menu: %v", err) + return + } + +} + +var m = &struct { + WindowHandle windows.Handle + Message uint32 + Wparam uintptr + Lparam uintptr + Time uint32 + Pt point +}{} + +func nativeLoop() { + for doNativeTick() { + } +} + +func nativeEnd() { +} + +func nativeStart() { + go func() { + for doNativeTick() { + } + }() +} + +func doNativeTick() bool { + ret, _, err := pGetMessage.Call(uintptr(unsafe.Pointer(m)), 0, 0, 0) + + // If the function retrieves a message other than WM_QUIT, the return value is nonzero. + // If the function retrieves the WM_QUIT message, the return value is zero. + // If there is an error, the return value is -1 + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx + switch int32(ret) { + case -1: + log.Printf("Error at message loop: %v", err) + return false + case 0: + return false + default: + pTranslateMessage.Call(uintptr(unsafe.Pointer(m))) + pDispatchMessage.Call(uintptr(unsafe.Pointer(m))) + } + return true +} + +func quit() { + const WM_CLOSE = 0x0010 + + pPostMessage.Call( + uintptr(wt.window), + WM_CLOSE, + 0, + 0, + ) +} + +func setInternalLoop(bool) { +} + +func iconBytesToFilePath(iconBytes []byte) (string, error) { + bh := md5.Sum(iconBytes) + dataHash := hex.EncodeToString(bh[:]) + iconFilePath := filepath.Join(os.TempDir(), "systray_temp_icon_"+dataHash) + + if _, err := os.Stat(iconFilePath); os.IsNotExist(err) { + if err := ioutil.WriteFile(iconFilePath, iconBytes, 0644); err != nil { + return "", err + } + } + return iconFilePath, nil +} + +// SetIcon sets the systray icon. +// iconBytes should be the content of .ico for windows and .ico/.jpg/.png +// for other platforms. +func SetIcon(iconBytes []byte) { + iconFilePath, err := iconBytesToFilePath(iconBytes) + if err != nil { + log.Printf("Unable to write icon data to temp file: %v", err) + return + } + if err := wt.setIcon(iconFilePath); err != nil { + log.Printf("Unable to set icon: %v", err) + return + } +} + +// SetTemplateIcon sets the systray icon as a template icon (on macOS), falling back +// to a regular icon on other platforms. +// templateIconBytes and iconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { + SetIcon(regularIconBytes) +} + +// SetTitle sets the systray title, only available on Mac and Linux. +func SetTitle(title string) { + // do nothing +} + +func (item *MenuItem) parentId() uint32 { + if item.parent != nil { + return uint32(item.parent.id) + } + return 0 +} + +// SetIcon sets the icon of a menu item. Only works on macOS and Windows. +// iconBytes should be the content of .ico/.jpg/.png +func (item *MenuItem) SetIcon(iconBytes []byte) { + iconFilePath, err := iconBytesToFilePath(iconBytes) + if err != nil { + log.Printf("Unable to write icon data to temp file: %v", err) + return + } + + h, err := wt.loadIconFrom(iconFilePath) + if err != nil { + log.Printf("Unable to load icon from temp file: %v", err) + return + } + + h, err = wt.iconToBitmap(h) + if err != nil { + log.Printf("Unable to convert icon to bitmap: %v", err) + return + } + wt.muMenuItemIcons.Lock() + wt.menuItemIcons[uint32(item.id)] = h + wt.muMenuItemIcons.Unlock() + + err = wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked) + if err != nil { + log.Printf("Unable to addOrUpdateMenuItem: %v", err) + return + } +} + +// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon, +// only available on Mac and Windows. +func SetTooltip(tooltip string) { + if err := wt.setTooltip(tooltip); err != nil { + log.Printf("Unable to set tooltip: %v", err) + return + } +} + +func addOrUpdateMenuItem(item *MenuItem) { + err := wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked) + if err != nil { + log.Printf("Unable to addOrUpdateMenuItem: %v", err) + return + } +} + +// SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it +// falls back to the regular icon bytes and on Linux it does nothing. +// templateIconBytes and regularIconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { + item.SetIcon(regularIconBytes) +} + +func addSeparator(id uint32) { + err := wt.addSeparatorMenuItem(id, 0) + if err != nil { + log.Printf("Unable to addSeparator: %v", err) + return + } +} + +func hideMenuItem(item *MenuItem) { + err := wt.hideMenuItem(uint32(item.id), item.parentId()) + if err != nil { + log.Printf("Unable to hideMenuItem: %v", err) + return + } +} + +func showMenuItem(item *MenuItem) { + addOrUpdateMenuItem(item) +} diff --git a/vendor/github.com/godbus/dbus/v5/introspect/call.go b/vendor/github.com/godbus/dbus/v5/introspect/call.go new file mode 100644 index 0000000000..a50f3e662d --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/introspect/call.go @@ -0,0 +1,28 @@ +package introspect + +import ( + "encoding/xml" + "strings" + + "github.com/godbus/dbus/v5" +) + +// Call calls org.freedesktop.Introspectable.Introspect on a remote object +// and returns the introspection data. +func Call(o dbus.BusObject) (*Node, error) { + var xmldata string + var node Node + + err := o.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&xmldata) + if err != nil { + return nil, err + } + err = xml.NewDecoder(strings.NewReader(xmldata)).Decode(&node) + if err != nil { + return nil, err + } + if node.Name == "" { + node.Name = string(o.Path()) + } + return &node, nil +} diff --git a/vendor/github.com/godbus/dbus/v5/introspect/introspect.go b/vendor/github.com/godbus/dbus/v5/introspect/introspect.go new file mode 100644 index 0000000000..8ee61055ac --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/introspect/introspect.go @@ -0,0 +1,86 @@ +// Package introspect provides some utilities for dealing with the DBus +// introspection format. +package introspect + +import "encoding/xml" + +// The introspection data for the org.freedesktop.DBus.Introspectable interface. +var IntrospectData = Interface{ + Name: "org.freedesktop.DBus.Introspectable", + Methods: []Method{ + { + Name: "Introspect", + Args: []Arg{ + {"out", "s", "out"}, + }, + }, + }, +} + +// XML document type declaration of the introspection format version 1.0 +const IntrospectDeclarationString = ` + +` + +// The introspection data for the org.freedesktop.DBus.Introspectable interface, +// as a string. +const IntrospectDataString = ` + + + + + +` + +// Node is the root element of an introspection. +type Node struct { + XMLName xml.Name `xml:"node"` + Name string `xml:"name,attr,omitempty"` + Interfaces []Interface `xml:"interface"` + Children []Node `xml:"node,omitempty"` +} + +// Interface describes a DBus interface that is available on the message bus. +type Interface struct { + Name string `xml:"name,attr"` + Methods []Method `xml:"method"` + Signals []Signal `xml:"signal"` + Properties []Property `xml:"property"` + Annotations []Annotation `xml:"annotation"` +} + +// Method describes a Method on an Interface as returned by an introspection. +type Method struct { + Name string `xml:"name,attr"` + Args []Arg `xml:"arg"` + Annotations []Annotation `xml:"annotation"` +} + +// Signal describes a Signal emitted on an Interface. +type Signal struct { + Name string `xml:"name,attr"` + Args []Arg `xml:"arg"` + Annotations []Annotation `xml:"annotation"` +} + +// Property describes a property of an Interface. +type Property struct { + Name string `xml:"name,attr"` + Type string `xml:"type,attr"` + Access string `xml:"access,attr"` + Annotations []Annotation `xml:"annotation"` +} + +// Arg represents an argument of a method or a signal. +type Arg struct { + Name string `xml:"name,attr,omitempty"` + Type string `xml:"type,attr"` + Direction string `xml:"direction,attr,omitempty"` +} + +// Annotation is an annotation in the introspection format. +type Annotation struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} diff --git a/vendor/github.com/godbus/dbus/v5/introspect/introspectable.go b/vendor/github.com/godbus/dbus/v5/introspect/introspectable.go new file mode 100644 index 0000000000..a032802bbd --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/introspect/introspectable.go @@ -0,0 +1,77 @@ +package introspect + +import ( + "encoding/xml" + "reflect" + "strings" + + "github.com/godbus/dbus/v5" +) + +// Introspectable implements org.freedesktop.Introspectable. +// +// You can create it by converting the XML-formatted introspection data from a +// string to an Introspectable or call NewIntrospectable with a Node. Then, +// export it as org.freedesktop.Introspectable on you object. +type Introspectable string + +// NewIntrospectable returns an Introspectable that returns the introspection +// data that corresponds to the given Node. If n.Interfaces doesn't contain the +// data for org.freedesktop.DBus.Introspectable, it is added automatically. +func NewIntrospectable(n *Node) Introspectable { + found := false + for _, v := range n.Interfaces { + if v.Name == "org.freedesktop.DBus.Introspectable" { + found = true + break + } + } + if !found { + n.Interfaces = append(n.Interfaces, IntrospectData) + } + b, err := xml.Marshal(n) + if err != nil { + panic(err) + } + return Introspectable(strings.TrimSpace(IntrospectDeclarationString) + string(b)) +} + +// Introspect implements org.freedesktop.Introspectable.Introspect. +func (i Introspectable) Introspect() (string, *dbus.Error) { + return string(i), nil +} + +// Methods returns the description of the methods of v. This can be used to +// create a Node which can be passed to NewIntrospectable. +func Methods(v interface{}) []Method { + t := reflect.TypeOf(v) + ms := make([]Method, 0, t.NumMethod()) + for i := 0; i < t.NumMethod(); i++ { + if t.Method(i).PkgPath != "" { + continue + } + mt := t.Method(i).Type + if mt.NumOut() == 0 || + mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{}) { + + continue + } + var m Method + m.Name = t.Method(i).Name + m.Args = make([]Arg, 0, mt.NumIn()+mt.NumOut()-2) + for j := 1; j < mt.NumIn(); j++ { + if mt.In(j) != reflect.TypeOf((*dbus.Sender)(nil)).Elem() && + mt.In(j) != reflect.TypeOf((*dbus.Message)(nil)).Elem() { + arg := Arg{"", dbus.SignatureOfType(mt.In(j)).String(), "in"} + m.Args = append(m.Args, arg) + } + } + for j := 0; j < mt.NumOut()-1; j++ { + arg := Arg{"", dbus.SignatureOfType(mt.Out(j)).String(), "out"} + m.Args = append(m.Args, arg) + } + m.Annotations = make([]Annotation, 0) + ms = append(ms, m) + } + return ms +} diff --git a/vendor/github.com/godbus/dbus/v5/prop/prop.go b/vendor/github.com/godbus/dbus/v5/prop/prop.go new file mode 100644 index 0000000000..2c2a61321e --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/prop/prop.go @@ -0,0 +1,348 @@ +// Package prop provides the Properties struct which can be used to implement +// org.freedesktop.DBus.Properties. +package prop + +import ( + "reflect" + "sync" + + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" +) + +// EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is +// emitted for a property. If it is EmitTrue, the signal is emitted. If it is +// EmitInvalidates, the signal is also emitted, but the new value of the property +// is not disclosed. If it is EmitConst, the property never changes value during +// the lifetime of the object it belongs to, and hence the signal is never emitted +// for it. +type EmitType byte + +const ( + EmitFalse EmitType = iota + EmitTrue + EmitInvalidates + EmitConst +) + +func (e EmitType) String() (str string) { + switch e { + case EmitFalse: + str = "false" + case EmitTrue: + str = "true" + case EmitInvalidates: + str = "invalidates" + case EmitConst: + str = "const" + default: + panic("invalid value for EmitType") + } + return +} + +// ErrIfaceNotFound is the error returned to peers who try to access properties +// on interfaces that aren't found. +var ErrIfaceNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil) + +// ErrPropNotFound is the error returned to peers trying to access properties +// that aren't found. +var ErrPropNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil) + +// ErrReadOnly is the error returned to peers trying to set a read-only +// property. +var ErrReadOnly = dbus.NewError("org.freedesktop.DBus.Properties.Error.ReadOnly", nil) + +// ErrInvalidArg is returned to peers if the type of the property that is being +// changed and the argument don't match. +var ErrInvalidArg = dbus.NewError("org.freedesktop.DBus.Properties.Error.InvalidArg", nil) + +// The introspection data for the org.freedesktop.DBus.Properties interface. +var IntrospectData = introspect.Interface{ + Name: "org.freedesktop.DBus.Properties", + Methods: []introspect.Method{ + { + Name: "Get", + Args: []introspect.Arg{ + {Name: "interface", Type: "s", Direction: "in"}, + {Name: "property", Type: "s", Direction: "in"}, + {Name: "value", Type: "v", Direction: "out"}, + }, + }, + { + Name: "GetAll", + Args: []introspect.Arg{ + {Name: "interface", Type: "s", Direction: "in"}, + {Name: "props", Type: "a{sv}", Direction: "out"}, + }, + }, + { + Name: "Set", + Args: []introspect.Arg{ + {Name: "interface", Type: "s", Direction: "in"}, + {Name: "property", Type: "s", Direction: "in"}, + {Name: "value", Type: "v", Direction: "in"}, + }, + }, + }, + Signals: []introspect.Signal{ + { + Name: "PropertiesChanged", + Args: []introspect.Arg{ + {Name: "interface", Type: "s", Direction: "out"}, + {Name: "changed_properties", Type: "a{sv}", Direction: "out"}, + {Name: "invalidates_properties", Type: "as", Direction: "out"}, + }, + }, + }, +} + +// The introspection data for the org.freedesktop.DBus.Properties interface, as +// a string. +const IntrospectDataString = ` + + + + + + + + + + + + + + + + + + + + + +` + +// Prop represents a single property. It is used for creating a Properties +// value. +type Prop struct { + // Initial value. Must be a DBus-representable type. This is not modified + // after Properties has been initialized; use Get or GetMust to access the + // value. + Value interface{} + + // If true, the value can be modified by calls to Set. + Writable bool + + // Controls how org.freedesktop.DBus.Properties.PropertiesChanged is + // emitted if this property changes. + Emit EmitType + + // If not nil, anytime this property is changed by Set, this function is + // called with an appropriate Change as its argument. If the returned error + // is not nil, it is sent back to the caller of Set and the property is not + // changed. + Callback func(*Change) *dbus.Error +} + +// Introspection returns the introspection data for p. +// The "name" argument is used as the property's name in the resulting data. +func (p *Prop) Introspection(name string) introspect.Property { + var result = introspect.Property{Name: name, Type: dbus.SignatureOf(p.Value).String()} + if p.Writable { + result.Access = "readwrite" + } else { + result.Access = "read" + } + result.Annotations = []introspect.Annotation{ + { + Name: "org.freedesktop.DBus.Property.EmitsChangedSignal", + Value: p.Emit.String(), + }, + } + return result +} + +// Change represents a change of a property by a call to Set. +type Change struct { + Props *Properties + Iface string + Name string + Value interface{} +} + +// Properties is a set of values that can be made available to the message bus +// using the org.freedesktop.DBus.Properties interface. It is safe for +// concurrent use by multiple goroutines. +type Properties struct { + m Map + mut sync.RWMutex + conn *dbus.Conn + path dbus.ObjectPath +} + +// New falls back to Export, but it returns nil if properties export fails, +// swallowing the error, shouldn't be used. +// +// Deprecated: use Export instead. +func New(conn *dbus.Conn, path dbus.ObjectPath, props Map) *Properties { + p, err := Export(conn, path, props) + if err != nil { + return nil + } + return p +} + +// Export returns a new Properties structure that manages the given properties. +// The key for the first-level map of props is the name of the interface; the +// second-level key is the name of the property. The returned structure will be +// exported as org.freedesktop.DBus.Properties on path. +func Export( + conn *dbus.Conn, path dbus.ObjectPath, props Map, +) (*Properties, error) { + p := &Properties{m: copyProps(props), conn: conn, path: path} + if err := conn.Export(p, path, "org.freedesktop.DBus.Properties"); err != nil { + return nil, err + } + return p, nil +} + +// Map is a helper type for supplying the configuration of properties to be handled. +type Map = map[string]map[string]*Prop + +func copyProps(in Map) Map { + out := make(Map, len(in)) + for intf, props := range in { + out[intf] = make(map[string]*Prop) + for name, prop := range props { + out[intf][name] = new(Prop) + *out[intf][name] = *prop + val := reflect.New(reflect.TypeOf(prop.Value)) + val.Elem().Set(reflect.ValueOf(prop.Value)) + out[intf][name].Value = val.Interface() + } + } + return out +} + +// Get implements org.freedesktop.DBus.Properties.Get. +func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) { + p.mut.RLock() + defer p.mut.RUnlock() + m, ok := p.m[iface] + if !ok { + return dbus.Variant{}, ErrIfaceNotFound + } + prop, ok := m[property] + if !ok { + return dbus.Variant{}, ErrPropNotFound + } + return dbus.MakeVariant(reflect.ValueOf(prop.Value).Elem().Interface()), nil +} + +// GetAll implements org.freedesktop.DBus.Properties.GetAll. +func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) { + p.mut.RLock() + defer p.mut.RUnlock() + m, ok := p.m[iface] + if !ok { + return nil, ErrIfaceNotFound + } + rm := make(map[string]dbus.Variant, len(m)) + for k, v := range m { + rm[k] = dbus.MakeVariant(reflect.ValueOf(v.Value).Elem().Interface()) + } + return rm, nil +} + +// GetMust returns the value of the given property and panics if either the +// interface or the property name are invalid. +func (p *Properties) GetMust(iface, property string) interface{} { + p.mut.RLock() + defer p.mut.RUnlock() + return reflect.ValueOf(p.m[iface][property].Value).Elem().Interface() +} + +// Introspection returns the introspection data that represents the properties +// of iface. +func (p *Properties) Introspection(iface string) []introspect.Property { + p.mut.RLock() + defer p.mut.RUnlock() + m := p.m[iface] + s := make([]introspect.Property, 0, len(m)) + for name, prop := range m { + s = append(s, prop.Introspection(name)) + } + return s +} + +// set sets the given property and emits PropertyChanged if appropriate. p.mut +// must already be locked. +func (p *Properties) set(iface, property string, v interface{}) error { + prop := p.m[iface][property] + err := dbus.Store([]interface{}{v}, prop.Value) + if err != nil { + return err + } + return p.emitChange(iface, property) +} + +func (p *Properties) emitChange(iface, property string) error { + prop := p.m[iface][property] + switch prop.Emit { + case EmitFalse: + return nil // do nothing + case EmitInvalidates: + return p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", + iface, map[string]dbus.Variant{}, []string{property}) + case EmitTrue: + return p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", + iface, map[string]dbus.Variant{property: dbus.MakeVariant(prop.Value)}, + []string{}) + case EmitConst: + return nil + default: + panic("invalid value for EmitType") + } +} + +// Set implements org.freedesktop.Properties.Set. +func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error { + p.mut.Lock() + defer p.mut.Unlock() + m, ok := p.m[iface] + if !ok { + return ErrIfaceNotFound + } + prop, ok := m[property] + if !ok { + return ErrPropNotFound + } + if !prop.Writable { + return ErrReadOnly + } + if newv.Signature() != dbus.SignatureOf(prop.Value) { + return ErrInvalidArg + } + if prop.Callback != nil { + err := prop.Callback(&Change{p, iface, property, newv.Value()}) + if err != nil { + return err + } + } + if err := p.set(iface, property, newv.Value()); err != nil { + return dbus.MakeFailedError(err) + } + return nil +} + +// SetMust sets the value of the given property and panics if the interface or +// the property name are invalid. +func (p *Properties) SetMust(iface, property string, v interface{}) { + p.mut.Lock() + defer p.mut.Unlock() // unlock in case of panic + err := p.set(iface, property, v) + if err != nil { + panic(err) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 76bf4dbfb5..5660f86d5b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,8 @@ +# fyne.io/systray v1.1.1-0.20220307102710-0121a6d9ce01 +## explicit +fyne.io/systray +fyne.io/systray/internal/generated/menu +fyne.io/systray/internal/generated/notifier # github.com/BurntSushi/toml v1.0.0 ## explicit github.com/BurntSushi/toml @@ -50,6 +55,8 @@ github.com/go-ole/go-ole/oleutil # github.com/godbus/dbus/v5 v5.1.0 ## explicit github.com/godbus/dbus/v5 +github.com/godbus/dbus/v5/introspect +github.com/godbus/dbus/v5/prop # github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff ## explicit github.com/goki/freetype