diff --git a/doc/thirdparty.txt b/doc/thirdparty.txt
index 9a7848d..6f85170 100644
--- a/doc/thirdparty.txt
+++ b/doc/thirdparty.txt
@@ -16,11 +16,6 @@ The licenses are included, and the authorship comments are left intact.
- allowed uri schemes
- added svg whitelist
-- systray
- https://github.com/getlantern/systray (commit:2c0986d) Apache 2.0
-
- removed golog dependency
-
- fixconsole
https://github.com/apenwarr/fixconsole (commit:5a9f648) Apache 2.0
diff --git a/go.mod b/go.mod
index 14576d7..8fde179 100644
--- a/go.mod
+++ b/go.mod
@@ -5,9 +5,13 @@ go 1.23.0
toolchain go1.23.5
require (
+ fyne.io/systray v1.12.0
github.com/mattn/go-sqlite3 v1.14.24
golang.org/x/net v0.38.0
golang.org/x/sys v0.31.0
)
-require golang.org/x/text v0.23.0 // indirect
+require (
+ github.com/godbus/dbus/v5 v5.1.0 // indirect
+ golang.org/x/text v0.23.0 // indirect
+)
diff --git a/go.sum b/go.sum
index a1a31c8..cade117 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,7 @@
+fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM=
+fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
+github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
+github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
diff --git a/src/platform/gui.go b/src/platform/gui.go
index 4e2e6e7..8a27af1 100644
--- a/src/platform/gui.go
+++ b/src/platform/gui.go
@@ -3,8 +3,8 @@
package platform
import (
+ "fyne.io/systray"
"github.com/nkanaev/yarr/src/server"
- "github.com/nkanaev/yarr/src/systray"
)
func Start(s *server.Server) {
diff --git a/src/systray/NOTES.txt b/src/systray/NOTES.txt
deleted file mode 100644
index e5e817f..0000000
--- a/src/systray/NOTES.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-taken from:
-
-repo:
-https://github.com/getlantern/systray
-
-hash:
-2c0986dda9aea361e925f90e848d9036be7b5367
-
-changes:
-
--removed `getlantern/golog` dependency
--prevent from compiling in linux
diff --git a/src/systray/README.md b/src/systray/README.md
deleted file mode 100644
index 543acfd..0000000
--- a/src/systray/README.md
+++ /dev/null
@@ -1,120 +0,0 @@
-systray is a cross-platform Go library to place an icon and menu in the notification area.
-
-## Features
-
-* Supported on Windows, macOS, and Linux
-* Menu items can be checked and/or disabled
-* Methods may be called from any Goroutine
-
-## API
-
-```go
-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
-}
-```
-
-See [full API](https://pkg.go.dev/github.com/getlantern/systray?tab=doc) as well as [CHANGELOG](https://github.com/getlantern/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/getlantern/systray
-cd 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/skratchdot/open-golang latest
-go: finding github.com/getlantern/systray latest
-go: finding github.com/getlantern/golog latest
-```
-
-Now look for *Awesome App* in your menu bar!
-
-
-
-## The Webview example
-
-The code under `webview_example` is to demostrate how it can co-exist with other UI elements. Note that the example doesn't work on macOS versions older than 10.15 Catalina.
-
-## Platform notes
-
-### Linux
-
-* Building apps requires gcc as well as the `gtk3` and `libappindicator3` development headers to be installed. For Debian or Ubuntu, you may install these using:
-
-```sh
-sudo apt-get install gcc libgtk-3-dev libappindicator3-dev
-```
-
-On Linux Mint, `libxapp-dev` is also required .
-
-To build `webview_example`, you also need to install `libwebkit2gtk-4.0-dev` and remove `webview_example/rsrc.syso` which is required on Windows.
-
-### Windows
-
-* To avoid opening a console at application startup, 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 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/xilp/systray
-- https://github.com/cratonica/trayhost
diff --git a/src/systray/_systray_linux.c b/src/systray/_systray_linux.c
deleted file mode 100644
index 8ec3e76..0000000
--- a/src/systray/_systray_linux.c
+++ /dev/null
@@ -1,268 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include "systray.h"
-
-static AppIndicator *global_app_indicator;
-static GtkWidget *global_tray_menu = NULL;
-static GList *global_menu_items = NULL;
-static char temp_file_name[PATH_MAX] = "";
-
-typedef struct {
- GtkWidget *menu_item;
- int menu_id;
- long signalHandlerId;
-} MenuItemNode;
-
-typedef struct {
- int menu_id;
- int parent_menu_id;
- char* title;
- char* tooltip;
- short disabled;
- short checked;
- short isCheckable;
-} MenuItemInfo;
-
-void registerSystray(void) {
- gtk_init(0, NULL);
- global_app_indicator = app_indicator_new("systray", "", APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
- app_indicator_set_status(global_app_indicator, APP_INDICATOR_STATUS_ACTIVE);
- global_tray_menu = gtk_menu_new();
- app_indicator_set_menu(global_app_indicator, GTK_MENU(global_tray_menu));
- systray_ready();
-}
-
-int nativeLoop(void) {
- gtk_main();
- systray_on_exit();
- return 0;
-}
-
-void _unlink_temp_file() {
- if (strlen(temp_file_name) != 0) {
- int ret = unlink(temp_file_name);
- if (ret == -1) {
- printf("failed to remove temp icon file %s: %s\n", temp_file_name, strerror(errno));
- }
- temp_file_name[0] = '\0';
- }
-}
-
-// runs in main thread, should always return FALSE to prevent gtk to execute it again
-gboolean do_set_icon(gpointer data) {
- _unlink_temp_file();
- char *tmpdir = getenv("TMPDIR");
- if (NULL == tmpdir) {
- tmpdir = "/tmp";
- }
- strncpy(temp_file_name, tmpdir, PATH_MAX-1);
- strncat(temp_file_name, "/systray_XXXXXX", PATH_MAX-1);
- temp_file_name[PATH_MAX-1] = '\0';
-
- GBytes* bytes = (GBytes*)data;
- int fd = mkstemp(temp_file_name);
- if (fd == -1) {
- printf("failed to create temp icon file %s: %s\n", temp_file_name, strerror(errno));
- return FALSE;
- }
- gsize size = 0;
- gconstpointer icon_data = g_bytes_get_data(bytes, &size);
- ssize_t written = write(fd, icon_data, size);
- close(fd);
- if(written != size) {
- printf("failed to write temp icon file %s: %s\n", temp_file_name, strerror(errno));
- return FALSE;
- }
- app_indicator_set_icon_full(global_app_indicator, temp_file_name, "");
- app_indicator_set_attention_icon_full(global_app_indicator, temp_file_name, "");
- g_bytes_unref(bytes);
- return FALSE;
-}
-
-void _systray_menu_item_selected(int *id) {
- systray_menu_item_selected(*id);
-}
-
-GtkMenuItem* find_menu_by_id(int id) {
- GList* it;
- for(it = global_menu_items; it != NULL; it = it->next) {
- MenuItemNode* item = (MenuItemNode*)(it->data);
- if(item->menu_id == id) {
- return GTK_MENU_ITEM(item->menu_item);
- }
- }
- return NULL;
-}
-
-// runs in main thread, should always return FALSE to prevent gtk to execute it again
-gboolean do_add_or_update_menu_item(gpointer data) {
- MenuItemInfo *mii = (MenuItemInfo*)data;
- GList* it;
- for(it = global_menu_items; it != NULL; it = it->next) {
- MenuItemNode* item = (MenuItemNode*)(it->data);
- if(item->menu_id == mii->menu_id) {
- gtk_menu_item_set_label(GTK_MENU_ITEM(item->menu_item), mii->title);
-
- if (mii->isCheckable) {
- // We need to block the "activate" event, to emulate the same behaviour as in the windows version
- // A Check/Uncheck does change the checkbox, but does not trigger the checkbox menuItem channel
- g_signal_handler_block(GTK_CHECK_MENU_ITEM(item->menu_item), item->signalHandlerId);
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item->menu_item), mii->checked == 1);
- g_signal_handler_unblock(GTK_CHECK_MENU_ITEM(item->menu_item), item->signalHandlerId);
- }
- break;
- }
- }
-
- // menu id doesn't exist, add new item
- if(it == NULL) {
- GtkWidget *menu_item;
- if (mii->isCheckable) {
- menu_item = gtk_check_menu_item_new_with_label(mii->title);
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), mii->checked == 1);
- } else {
- menu_item = gtk_menu_item_new_with_label(mii->title);
- }
- int *id = malloc(sizeof(int));
- *id = mii->menu_id;
- long signalHandlerId = g_signal_connect_swapped(
- G_OBJECT(menu_item),
- "activate",
- G_CALLBACK(_systray_menu_item_selected),
- id
- );
-
- if (mii->parent_menu_id == 0) {
- gtk_menu_shell_append(GTK_MENU_SHELL(global_tray_menu), menu_item);
- } else {
- GtkMenuItem* parentMenuItem = find_menu_by_id(mii->parent_menu_id);
- GtkWidget* parentMenu = gtk_menu_item_get_submenu(parentMenuItem);
-
- if(parentMenu == NULL) {
- parentMenu = gtk_menu_new();
- gtk_menu_item_set_submenu(parentMenuItem, parentMenu);
- }
-
- gtk_menu_shell_append(GTK_MENU_SHELL(parentMenu), menu_item);
- }
-
- MenuItemNode* new_item = malloc(sizeof(MenuItemNode));
- new_item->menu_id = mii->menu_id;
- new_item->signalHandlerId = signalHandlerId;
- new_item->menu_item = menu_item;
- GList* new_node = malloc(sizeof(GList));
- new_node->data = new_item;
- new_node->next = global_menu_items;
- if(global_menu_items != NULL) {
- global_menu_items->prev = new_node;
- }
- global_menu_items = new_node;
- it = new_node;
- }
- GtkWidget* menu_item = GTK_WIDGET(((MenuItemNode*)(it->data))->menu_item);
- gtk_widget_set_sensitive(menu_item, mii->disabled != 1);
- gtk_widget_show(menu_item);
-
- free(mii->title);
- free(mii->tooltip);
- free(mii);
- return FALSE;
-}
-
-gboolean do_add_separator(gpointer data) {
- GtkWidget *separator = gtk_separator_menu_item_new();
- gtk_menu_shell_append(GTK_MENU_SHELL(global_tray_menu), separator);
- gtk_widget_show(separator);
- return FALSE;
-}
-
-// runs in main thread, should always return FALSE to prevent gtk to execute it again
-gboolean do_hide_menu_item(gpointer data) {
- MenuItemInfo *mii = (MenuItemInfo*)data;
- GList* it;
- for(it = global_menu_items; it != NULL; it = it->next) {
- MenuItemNode* item = (MenuItemNode*)(it->data);
- if(item->menu_id == mii->menu_id){
- gtk_widget_hide(GTK_WIDGET(item->menu_item));
- break;
- }
- }
- return FALSE;
-}
-
-// runs in main thread, should always return FALSE to prevent gtk to execute it again
-gboolean do_show_menu_item(gpointer data) {
- MenuItemInfo *mii = (MenuItemInfo*)data;
- GList* it;
- for(it = global_menu_items; it != NULL; it = it->next) {
- MenuItemNode* item = (MenuItemNode*)(it->data);
- if(item->menu_id == mii->menu_id){
- gtk_widget_show(GTK_WIDGET(item->menu_item));
- break;
- }
- }
- return FALSE;
-}
-
-// runs in main thread, should always return FALSE to prevent gtk to execute it again
-gboolean do_quit(gpointer data) {
- _unlink_temp_file();
- // app indicator doesn't provide a way to remove it, hide it as a workaround
- app_indicator_set_status(global_app_indicator, APP_INDICATOR_STATUS_PASSIVE);
- gtk_main_quit();
- return FALSE;
-}
-
-void setIcon(const char* iconBytes, int length, bool template) {
- GBytes* bytes = g_bytes_new_static(iconBytes, length);
- g_idle_add(do_set_icon, bytes);
-}
-
-void setTitle(char* ctitle) {
- app_indicator_set_label(global_app_indicator, ctitle, "");
- free(ctitle);
-}
-
-void setTooltip(char* ctooltip) {
- free(ctooltip);
-}
-
-void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template) {
-}
-
-void add_or_update_menu_item(int menu_id, int parent_menu_id, char* title, char* tooltip, short disabled, short checked, short isCheckable) {
- MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
- mii->menu_id = menu_id;
- mii->parent_menu_id = parent_menu_id;
- mii->title = title;
- mii->tooltip = tooltip;
- mii->disabled = disabled;
- mii->checked = checked;
- mii->isCheckable = isCheckable;
- g_idle_add(do_add_or_update_menu_item, mii);
-}
-
-void add_separator(int menu_id) {
- MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
- mii->menu_id = menu_id;
- g_idle_add(do_add_separator, mii);
-}
-
-void hide_menu_item(int menu_id) {
- MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
- mii->menu_id = menu_id;
- g_idle_add(do_hide_menu_item, mii);
-}
-
-void show_menu_item(int menu_id) {
- MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
- mii->menu_id = menu_id;
- g_idle_add(do_show_menu_item, mii);
-}
-
-void quit() {
- g_idle_add(do_quit, NULL);
-}
diff --git a/src/systray/systray_darwin.go b/src/systray/systray_darwin.go
deleted file mode 100644
index 740ec5b..0000000
--- a/src/systray/systray_darwin.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package systray
-
-/*
-#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc
-#cgo darwin LDFLAGS: -framework Cocoa -framework WebKit
-
-#include "systray.h"
-*/
-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)
-}
diff --git a/src/systray/systray_linux.go b/src/systray/systray_linux.go
deleted file mode 100644
index 2d490bd..0000000
--- a/src/systray/systray_linux.go
+++ /dev/null
@@ -1,32 +0,0 @@
-//go:build never
-// +build never
-
-package systray
-
-/*
-#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc
-#cgo darwin LDFLAGS: -framework Cocoa -framework WebKit
-
-#include "systray.h"
-*/
-import "C"
-
-// 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)
-}
-
-// 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) {
-}
-
-// 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) {
-}
diff --git a/src/systray/systray_nonwindows.go b/src/systray/systray_nonwindows.go
deleted file mode 100644
index e17358a..0000000
--- a/src/systray/systray_nonwindows.go
+++ /dev/null
@@ -1,106 +0,0 @@
-//go:build darwin
-// +build darwin
-
-package systray
-
-/*
-#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc
-#cgo darwin LDFLAGS: -framework Cocoa
-
-#include "systray.h"
-*/
-import "C"
-
-import (
- "unsafe"
-)
-
-func registerSystray() {
- C.registerSystray()
-}
-
-func nativeLoop() {
- C.nativeLoop()
-}
-
-func quit() {
- C.quit()
-}
-
-// 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/src/systray/systray_windows_test.go b/src/systray/systray_windows_test.go
deleted file mode 100644
index ca2a9c9..0000000
--- a/src/systray/systray_windows_test.go
+++ /dev/null
@@ -1,133 +0,0 @@
-//go:build windows
-// +build windows
-
-package systray
-
-import (
- "io/ioutil"
- "runtime"
- "sync/atomic"
- "testing"
- "time"
- "unsafe"
-
- "golang.org/x/sys/windows"
-)
-
-const iconFilePath = "example/icon/iconwin.ico"
-
-func TestBaseWindowsTray(t *testing.T) {
- systrayReady = func() {}
- systrayExit = func() {}
-
- runtime.LockOSThread()
-
- if err := wt.initInstance(); err != nil {
- t.Fatalf("initInstance failed: %s", err)
- }
-
- if err := wt.createMenu(); err != nil {
- t.Fatalf("createMenu failed: %s", err)
- }
-
- defer func() {
- pDestroyWindow.Call(uintptr(wt.window))
- wt.wcex.unregister()
- }()
-
- if err := wt.setIcon(iconFilePath); err != nil {
- t.Errorf("SetIcon failed: %s", err)
- }
-
- if err := wt.setTooltip("Cyrillic tooltip тест:)"); err != nil {
- t.Errorf("SetIcon failed: %s", err)
- }
-
- var id int32 = 0
- err := wt.addOrUpdateMenuItem(atomic.AddInt32(&id, 1), "Simple enabled", false, false)
- if err != nil {
- t.Errorf("mergeMenuItem failed: %s", err)
- }
- err = wt.addOrUpdateMenuItem(atomic.AddInt32(&id, 1), "Simple disabled", true, false)
- if err != nil {
- t.Errorf("mergeMenuItem failed: %s", err)
- }
- err = wt.addSeparatorMenuItem(atomic.AddInt32(&id, 1))
- if err != nil {
- t.Errorf("addSeparatorMenuItem failed: %s", err)
- }
- err = wt.addOrUpdateMenuItem(atomic.AddInt32(&id, 1), "Simple checked enabled", false, true)
- if err != nil {
- t.Errorf("mergeMenuItem failed: %s", err)
- }
- err = wt.addOrUpdateMenuItem(atomic.AddInt32(&id, 1), "Simple checked disabled", true, true)
- if err != nil {
- t.Errorf("mergeMenuItem failed: %s", err)
- }
-
- err = wt.hideMenuItem(1)
- if err != nil {
- t.Errorf("hideMenuItem failed: %s", err)
- }
-
- err = wt.hideMenuItem(100)
- if err == nil {
- t.Error("hideMenuItem failed: must return error on invalid item id")
- }
-
- err = wt.addOrUpdateMenuItem(2, "Simple disabled update", true, false)
- if err != nil {
- t.Errorf("mergeMenuItem failed: %s", err)
- }
-
- time.AfterFunc(1*time.Second, quit)
-
- m := struct {
- WindowHandle windows.Handle
- Message uint32
- Wparam uintptr
- Lparam uintptr
- Time uint32
- Pt point
- }{}
- for {
- ret, _, err := pGetMessage.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0)
- res := int32(ret)
- if res == -1 {
- t.Errorf("win32 GetMessage failed: %v", err)
- return
- } else if res == 0 {
- break
- }
- pTranslateMessage.Call(uintptr(unsafe.Pointer(&m)))
- pDispatchMessage.Call(uintptr(unsafe.Pointer(&m)))
- }
-}
-
-func TestWindowsRun(t *testing.T) {
- onReady := func() {
- b, err := ioutil.ReadFile(iconFilePath)
- if err != nil {
- t.Fatalf("Can't load icon file: %v", err)
- }
- SetIcon(b)
- SetTitle("Test title с кириллицей")
-
- bSomeBtn := AddMenuItem("Йа кнопко", "")
- bSomeBtn.Check()
- AddSeparator()
- bQuit := AddMenuItem("Quit", "Quit the whole app")
- go func() {
- <-bQuit.ClickedCh
- t.Log("Quit reqested")
- Quit()
- }()
- time.AfterFunc(1*time.Second, Quit)
- }
-
- onExit := func() {
- t.Log("Exit success")
- }
-
- Run(onReady, onExit)
-}
diff --git a/src/systray/.gitignore b/vendor/fyne.io/systray/.gitignore
similarity index 95%
rename from src/systray/.gitignore
rename to vendor/fyne.io/systray/.gitignore
index 2e85ef6..2ff640b 100644
--- a/src/systray/.gitignore
+++ b/vendor/fyne.io/systray/.gitignore
@@ -10,3 +10,4 @@ dll/systray_unsigned.dll
out.txt
.vs
on_exit*.txt
+.vscode
\ No newline at end of file
diff --git a/src/systray/CHANGELOG.md b/vendor/fyne.io/systray/CHANGELOG.md
similarity index 100%
rename from src/systray/CHANGELOG.md
rename to vendor/fyne.io/systray/CHANGELOG.md
diff --git a/src/systray/LICENSE b/vendor/fyne.io/systray/LICENSE
similarity index 100%
rename from src/systray/LICENSE
rename to vendor/fyne.io/systray/LICENSE
diff --git a/src/systray/Makefile b/vendor/fyne.io/systray/Makefile
similarity index 100%
rename from src/systray/Makefile
rename to vendor/fyne.io/systray/Makefile
diff --git a/vendor/fyne.io/systray/README.md b/vendor/fyne.io/systray/README.md
new file mode 100644
index 0000000..6881e95
--- /dev/null
+++ b/vendor/fyne.io/systray/README.md
@@ -0,0 +1,147 @@
+# 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, Linux and many BSD systems
+* Menu items can be checked and/or disabled
+* Methods may be called from any Goroutine
+
+## API
+
+```go
+package main
+
+import "fyne.io/systray"
+import "fyne.io/systray/example/icon"
+
+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.
+ mQuit.SetIcon(icon.Data)
+}
+
+func onExit() {
+ // clean up here
+}
+```
+
+### Running in a Fyne app
+
+This repository is designed to allow any toolkit to integrate system tray without any additional dependencies.
+It is maintained by the Fyne team, but if you are using Fyne there is an even easier to use API in the main repository that wraps this project.
+
+In your app you can use a standard `fyne.Menu` structure and pass it to `SetSystemTrayMenu` when your app is a desktop app, as follows:
+
+```go
+ menu := fyne.NewMenu("MyApp",
+ fyne.NewMenuItem("Show", func() {
+ log.Println("Tapped show")
+ }))
+
+ if desk, ok := myApp.(desktop.App); ok {
+ desk.SetSystemTrayMenu(menu)
+ }
+```
+
+You can find out more in the toolkit documentation:
+[System Tray Menu](https://developer.fyne.io/explore/systray).
+
+### 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).
+
+Note: this package requires cgo, so make sure you set `CGO_ENABLED=1` before building.
+
+## Try the example app!
+
+Have go v1.12+ or higher installed? Here's an example to get started on macOS or Linux:
+
+```sh
+git clone https://github.com/fyne-io/systray
+cd systray/example
+go run .
+```
+
+On Windows, you should follow the instructions above, but use the followign run command:
+
+```
+go run -ldflags "-H=windowsgui" .
+```
+
+Now look for *Awesome App* in your menu bar!
+
+
+
+## Platform notes
+
+### Linux/BSD
+
+This implementation uses DBus to communicate through the SystemNotifier/AppIndicator spec, older tray implementations may not load the icon.
+
+If you are running an older desktop environment, or system tray provider, you may require a proxy app which can convert the new DBus calls to the old format.
+The recommended tool for Gnome based trays is [snixembed](https://git.sr.ht/~steef/snixembed), others are available.
+Search for "StatusNotifierItems XEmbedded" in your package manager.
+
+### 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
+```
+
+If bundling manually, 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).
+
+On macOS, it's possible to set the underlying
+[`NSStatusItemBehavior`](https://developer.apple.com/documentation/appkit/nsstatusitembehavior?language=objc)
+with `systray.SetRemovalAllowed(true)`. When enabled, the user can cmd-drag the
+icon off the menu bar.
+
+## Credits
+
+- https://github.com/getlantern/systray
+- https://github.com/xilp/systray
+- https://github.com/cratonica/trayhost
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 0000000..1b896d3
--- /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 0000000..230c40d
--- /dev/null
+++ b/vendor/fyne.io/systray/internal/generated/notifier/status_notifier_item.go
@@ -0,0 +1,633 @@
+// 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"},
+ {Name: "ToolTip", Type: "(sa(iiay)ss)", Access: "read", Annotations: []introspect.Annotation{
+ {Name: "org.qtproject.QtDBus.QtTypeName", Value: "KDbusToolTipStruct"},
+ }},
+ },
+ 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
+}
+
+// GetToolTip gets org.kde.StatusNotifierItem.ToolTip property.
+//
+// Annotations:
+// @org.qtproject.QtDBus.QtTypeName = KDbusToolTipStruct
+func (o *StatusNotifierItem) GetToolTip(ctx context.Context) (toolTip struct {
+ V0 string
+ V1 []struct {
+ V0 int32
+ V1 int32
+ V2 []byte
+ }
+ V2 string
+ V3 string
+}, err error) {
+ err = o.object.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, InterfaceStatusNotifierItem, "ToolTip").Store(&toolTip)
+ 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/src/systray/systray.go b/vendor/fyne.io/systray/systray.go
similarity index 70%
rename from src/systray/systray.go
rename to vendor/fyne.io/systray/systray.go
index 490003d..17ac656 100644
--- a/src/systray/systray.go
+++ b/vendor/fyne.io/systray/systray.go
@@ -1,9 +1,4 @@
-//go:build darwin || windows
-// +build darwin windows
-
-/*
-Package systray is a cross-platform Go library to place an icon and menu in the notification area.
-*/
+// Package systray is a cross-platform Go library to place an icon and menu in the notification area.
package systray
import (
@@ -15,15 +10,29 @@ import (
)
var (
- systrayReady func()
- systrayExit func()
- menuItems = make(map[uint32]*MenuItem)
- menuItemsLock sync.RWMutex
+ systrayReady, systrayExit func()
+ tappedLeft, tappedRight func()
+ systrayExitCalled bool
+ menuItems = make(map[uint32]*MenuItem)
+ menuItemsLock sync.RWMutex
- currentID = uint32(0)
- quitOnce sync.Once
+ initialMenuBuilt sync.WaitGroup
+ currentID atomic.Uint32
+ quitOnce sync.Once
+
+ // TrayOpenedCh receives an entry each time the system tray menu is opened.
+ TrayOpenedCh = make(chan struct{})
)
+// This helper function allows us to call systrayExit only once,
+// without accidentally calling it twice in the same lifetime.
+func runSystrayExit() {
+ if !systrayExitCalled {
+ systrayExitCalled = true
+ systrayExit()
+ }
+}
+
func init() {
runtime.LockOSThread()
}
@@ -61,7 +70,7 @@ func (item *MenuItem) String() string {
func newMenuItem(title string, tooltip string, parent *MenuItem) *MenuItem {
return &MenuItem{
ClickedCh: make(chan struct{}),
- id: atomic.AddUint32(¤tID, 1),
+ id: currentID.Add(1),
title: title,
tooltip: tooltip,
disabled: false,
@@ -73,11 +82,24 @@ func newMenuItem(title string, tooltip string, parent *MenuItem) *MenuItem {
// Run initializes GUI and starts the event loop, then invokes the onReady
// callback. It blocks until systray.Quit() is called.
-func Run(onReady func(), onExit func()) {
+func Run(onReady, onExit func()) {
+ setInternalLoop(true)
Register(onReady, onExit)
+
nativeLoop()
}
+// RunWithExternalLoop allows the system tray module to operate with other toolkits.
+// 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, func() {
+ nativeEnd()
+ Quit()
+ }
+}
+
// 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.
@@ -89,9 +111,11 @@ func Register(onReady func(), onExit func()) {
} else {
// Run onReady on separate goroutine to avoid blocking event loop
readyCh := make(chan interface{})
+ initialMenuBuilt.Add(1)
go func() {
<-readyCh
onReady()
+ initialMenuBuilt.Done()
}()
systrayReady = func() {
close(readyCh)
@@ -103,14 +127,36 @@ func Register(onReady func(), onExit func()) {
onExit = func() {}
}
systrayExit = onExit
+ systrayExitCalled = false
registerSystray()
}
+// ResetMenu will remove all menu items
+func ResetMenu() {
+ menuItemsLock.Lock()
+ id := currentID.Load()
+ menuItemsLock.Unlock()
+ for i, item := range menuItems {
+ if i < id && item.parent == nil {
+ item.Remove()
+ }
+ }
+ resetMenu()
+}
+
// Quit the systray
func Quit() {
quitOnce.Do(quit)
}
+func SetOnTapped(f func()) {
+ tappedLeft = f
+}
+
+func SetOnSecondaryTapped(f func()) {
+ tappedRight = f
+}
+
// 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
@@ -121,8 +167,8 @@ func AddMenuItem(title string, tooltip string) *MenuItem {
}
// AddMenuItemCheckbox adds a menu item with the designated title and tooltip and a checkbox for Linux.
+// On other platforms there will be a check indicated next to the item if `checked` is true.
// 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
@@ -133,7 +179,12 @@ func AddMenuItemCheckbox(title string, tooltip string, checked bool) *MenuItem {
// AddSeparator adds a separator bar to the menu
func AddSeparator() {
- addSeparator(atomic.AddUint32(¤tID, 1))
+ addSeparator(currentID.Add(1), 0)
+}
+
+// AddSeparator adds a separator bar to the submenu
+func (item *MenuItem) AddSeparator() {
+ addSeparator(currentID.Add(1), item.id)
}
// AddSubMenuItem adds a nested sub-menu item with the designated title and tooltip.
@@ -190,6 +241,30 @@ func (item *MenuItem) Hide() {
hideMenuItem(item)
}
+// Remove removes a menu item
+func (item *MenuItem) Remove() {
+ menuItemsLock.RLock()
+ var childList []*MenuItem
+ for _, child := range menuItems {
+ if child.parent == item {
+ childList = append(childList, child)
+ }
+ }
+ menuItemsLock.RUnlock()
+ for _, child := range childList {
+ child.Remove()
+ }
+ removeMenuItem(item)
+ menuItemsLock.Lock()
+ delete(menuItems, item.id)
+ select {
+ case <-item.ClickedCh:
+ default:
+ }
+ close(item.ClickedCh)
+ menuItemsLock.Unlock()
+}
+
// Show shows a previously hidden menu item
func (item *MenuItem) Show() {
showMenuItem(item)
@@ -225,7 +300,7 @@ func systrayMenuItemSelected(id uint32) {
item, ok := menuItems[id]
menuItemsLock.RUnlock()
if !ok {
- log.Printf("No menu item with ID %v", id)
+ log.Printf("systray error: no menu item with ID %d\n", id)
return
}
select {
diff --git a/src/systray/systray.h b/vendor/fyne.io/systray/systray.h
similarity index 66%
rename from src/systray/systray.h
rename to vendor/fyne.io/systray/systray.h
index 888c829..96200bd 100644
--- a/src/systray/systray.h
+++ b/vendor/fyne.io/systray/systray.h
@@ -2,16 +2,25 @@
extern void systray_ready();
extern void systray_on_exit();
+extern void systray_left_click();
+extern void systray_right_click();
extern void systray_menu_item_selected(int menu_id);
+extern void systray_menu_will_open();
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 setRemovalAllowed(bool allowed);
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 add_separator(int menuId, int parentId);
void hide_menu_item(int menuId);
+void remove_menu_item(int menuId);
void show_menu_item(int menuId);
+void reset_menu();
+void show_menu();
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 0000000..612e75a
--- /dev/null
+++ b/vendor/fyne.io/systray/systray_darwin.go
@@ -0,0 +1,213 @@
+//go:build !ios
+
+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 (
+ "fmt"
+ "os"
+ "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)
+}
+
+// SetIconFromFilePath sets the icon of a menu item from a file path.
+// iconFilePath should be the path to a .ico for windows and .ico/.jpg/.png for other platforms.
+func (item *MenuItem) SetIconFromFilePath(iconFilePath string) error {
+ iconBytes, err := os.ReadFile(iconFilePath)
+ if err != nil {
+ return fmt.Errorf("failed to read icon file: %v", err)
+ }
+ item.SetIcon(iconBytes)
+ return nil
+}
+
+// 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)
+}
+
+// SetRemovalAllowed sets whether a user can remove the systray icon or not.
+// This is only supported on macOS.
+func SetRemovalAllowed(allowed bool) {
+ C.setRemovalAllowed((C.bool)(allowed))
+}
+
+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)
+}
+
+// SetIconFromFilePath sets the systray icon from a file path.
+// iconFilePath should be the path to a .ico for windows and .ico/.jpg/.png for other platforms.
+func SetIconFromFilePath(iconFilePath string) error {
+ bytes, err := os.ReadFile(iconFilePath)
+ if err != nil {
+ return fmt.Errorf("failed to read icon file: %v", err)
+ }
+ SetIcon(bytes)
+ return nil
+}
+
+// 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, parent uint32) {
+ C.add_separator(C.int(id), C.int(parent))
+}
+
+func hideMenuItem(item *MenuItem) {
+ C.hide_menu_item(
+ C.int(item.id),
+ )
+}
+
+func showMenuItem(item *MenuItem) {
+ C.show_menu_item(
+ C.int(item.id),
+ )
+}
+
+func removeMenuItem(item *MenuItem) {
+ C.remove_menu_item(
+ C.int(item.id),
+ )
+}
+
+func resetMenu() {
+ C.reset_menu()
+}
+
+//export systray_left_click
+func systray_left_click() {
+ if fn := tappedLeft; fn != nil {
+ fn()
+ return
+ }
+
+ C.show_menu()
+}
+
+//export systray_right_click
+func systray_right_click() {
+ if fn := tappedRight; fn != nil {
+ fn()
+ return
+ }
+
+ C.show_menu()
+}
+
+//export systray_ready
+func systray_ready() {
+ systrayReady()
+}
+
+//export systray_on_exit
+func systray_on_exit() {
+ runSystrayExit()
+}
+
+//export systray_menu_item_selected
+func systray_menu_item_selected(cID C.int) {
+ systrayMenuItemSelected(uint32(cID))
+}
+
+//export systray_menu_will_open
+func systray_menu_will_open() {
+ select {
+ case TrayOpenedCh <- struct{}{}:
+ default:
+ }
+}
diff --git a/src/systray/systray_darwin.m b/vendor/fyne.io/systray/systray_darwin.m
similarity index 55%
rename from src/systray/systray_darwin.m
rename to vendor/fyne.io/systray/systray_darwin.m
index 884fa43..b0e9ee8 100644
--- a/src/systray/systray_darwin.m
+++ b/vendor/fyne.io/systray/systray_darwin.m
@@ -1,3 +1,5 @@
+//go:build !ios
+
#import
#include "systray.h"
@@ -50,13 +52,33 @@ withParentMenuId: (int)theParentMenuId
}
@end
-@interface AppDelegate: NSObject
+@interface RightClickDetector : NSView
+
+@property (copy) void (^onRightClicked)(NSEvent *);
+
+@end
+
+@implementation RightClickDetector
+
+- (void)rightMouseUp:(NSEvent *)theEvent {
+ if (!self.onRightClicked) {
+ return;
+ }
+
+ self.onRightClicked(theEvent);
+}
+
+@end
+
+
+@interface SystrayAppDelegate: NSObject
- (void) add_or_update_menu_item:(MenuItem*) item;
- (IBAction)menuHandler:(id)sender;
+ - (void)menuWillOpen:(NSMenu*)menu;
@property (assign) IBOutlet NSWindow *window;
- @end
+@end
- @implementation AppDelegate
+@implementation SystrayAppDelegate
{
NSStatusItem *statusItem;
NSMenu *menu;
@@ -68,17 +90,73 @@ withParentMenuId: (int)theParentMenuId
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self->statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
+
self->menu = [[NSMenu alloc] init];
- [self->menu setAutoenablesItems: FALSE];
- [self->statusItem setMenu:self->menu];
+ self->menu.delegate = self;
+ self->menu.autoenablesItems = FALSE;
+ // Once the user has removed it, the item needs to be explicitly brought back,
+ // even restarting the application is insufficient.
+ // Since the interface from Go is relatively simple, for now we ensure it's
+ // always visible at application startup.
+ self->statusItem.visible = TRUE;
+
+ NSStatusBarButton *button = self->statusItem.button;
+ button.action = @selector(leftMouseClicked);
+
+ [NSEvent addLocalMonitorForEventsMatchingMask: (NSEventTypeLeftMouseDown|NSEventTypeRightMouseDown)
+ handler: ^NSEvent *(NSEvent *event) {
+ if (event.window != self->statusItem.button.window) {
+ return event;
+ }
+
+ [self leftMouseClicked];
+
+ return nil;
+ }];
+
+ NSSize size = [button frame].size;
+ NSRect frame = CGRectMake(0, 0, size.width, size.height);
+ RightClickDetector *rightClicker = [[RightClickDetector alloc] initWithFrame:frame];
+ rightClicker.onRightClicked = ^(NSEvent *event) {
+ [self rightMouseClicked];
+ };
+
+ rightClicker.autoresizingMask = (NSViewWidthSizable |
+ NSViewHeightSizable);
+ button.autoresizesSubviews = YES;
+ [button addSubview:rightClicker];
+
systray_ready();
}
+- (void)rightMouseClicked {
+ systray_right_click();
+}
+
+- (void)leftMouseClicked {
+ systray_left_click();
+}
+
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
systray_on_exit();
}
+- (void)setRemovalAllowed {
+ NSStatusItemBehavior behavior = [self->statusItem behavior];
+ behavior |= NSStatusItemBehaviorRemovalAllowed;
+ self->statusItem.behavior = behavior;
+}
+
+- (void)setRemovalForbidden {
+ NSStatusItemBehavior behavior = [self->statusItem behavior];
+ behavior &= ~NSStatusItemBehaviorRemovalAllowed;
+ // Ensure the menu item is visible if it was removed, since we're now
+ // disallowing removal.
+ self->statusItem.visible = TRUE;
+ self->statusItem.behavior = behavior;
+}
+
- (void)setIcon:(NSImage *)image {
statusItem.button.image = image;
[self updateTitleButtonStyle];
@@ -89,7 +167,7 @@ withParentMenuId: (int)theParentMenuId
[self updateTitleButtonStyle];
}
--(void)updateTitleButtonStyle {
+- (void)updateTitleButtonStyle {
if (statusItem.button.image != nil) {
if ([statusItem.button.title length] == 0) {
statusItem.button.imagePosition = NSImageOnly;
@@ -112,6 +190,10 @@ withParentMenuId: (int)theParentMenuId
systray_menu_item_selected(menuId.intValue);
}
+- (void)menuWillOpen:(NSMenu *)menu {
+ systray_menu_will_open();
+}
+
- (void)add_or_update_menu_item:(MenuItem *)item {
NSMenu *theMenu = self->menu;
NSMenuItem *parentItem;
@@ -125,9 +207,8 @@ withParentMenuId: (int)theParentMenuId
[parentItem setSubmenu:theMenu];
}
}
-
- NSMenuItem *menuItem;
- menuItem = find_menu_item(theMenu, item->menuId);
+
+ NSMenuItem *menuItem = find_menu_item(theMenu, item->menuId);
if (menuItem == NULL) {
menuItem = [theMenu addItemWithTitle:item->title
action:@selector(menuHandler:)
@@ -170,8 +251,15 @@ NSMenuItem *find_menu_item(NSMenu *ourMenu, NSNumber *menuId) {
return NULL;
};
-- (void) add_separator:(NSNumber*) menuId
+- (void) add_separator:(NSNumber*) parentMenuId
{
+ if (parentMenuId.integerValue != 0) {
+ NSMenuItem* menuItem = find_menu_item(menu, parentMenuId);
+ if (menuItem != NULL) {
+ [menuItem.submenu addItem: [NSMenuItem separatorItem]];
+ return;
+ }
+ }
[menu addItem: [NSMenuItem separatorItem]];
}
@@ -195,6 +283,13 @@ NSMenuItem *find_menu_item(NSMenu *ourMenu, NSNumber *menuId) {
menuItem.image = image;
}
+- (void)show_menu
+{
+ [self->menu popUpMenuPositioningItem:nil
+ atLocation:NSMakePoint(0, self->statusItem.button.bounds.size.height+6)
+ inView:self->statusItem.button];
+}
+
- (void) show_menu_item:(NSNumber*) menuId
{
NSMenuItem* menuItem = find_menu_item(menu, menuId);
@@ -203,16 +298,55 @@ NSMenuItem *find_menu_item(NSMenu *ourMenu, NSNumber *menuId) {
}
}
+- (void) remove_menu_item:(NSNumber*) menuId
+{
+ NSMenuItem* menuItem = find_menu_item(menu, menuId);
+ if (menuItem != NULL) {
+ [menuItem.menu removeItem:menuItem];
+ }
+}
+
+- (void) reset_menu
+{
+ [self->menu removeAllItems];
+}
+
- (void) quit
{
- [NSApp terminate:self];
+ // This tells the app event loop to stop after processing remaining messages.
+ [NSApp stop:self];
+ // The event loop won't return until it processes another event.
+ // https://stackoverflow.com/a/48064752/149482
+ NSPoint eventLocation = NSMakePoint(0, 0);
+ NSEvent *customEvent = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
+ location:eventLocation
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:nil
+ subtype:0
+ data1:0
+ data2:0];
+ [NSApp postEvent:customEvent atStart:NO];
}
@end
+bool internalLoop = false;
+SystrayAppDelegate *owner;
+
+void setInternalLoop(bool i) {
+ internalLoop = i;
+}
+
void registerSystray(void) {
- AppDelegate *delegate = [[AppDelegate alloc] init];
- [[NSApplication sharedApplication] setDelegate:delegate];
+ if (!internalLoop) { // with an external loop we don't take ownership of the app
+ return;
+ }
+
+ owner = [[SystrayAppDelegate 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.
@@ -221,6 +355,10 @@ void registerSystray(void) {
}
}
+void nativeEnd(void) {
+ systray_on_exit();
+}
+
int nativeLoop(void) {
if (floor(NSAppKitVersionNumber) > /*NSAppKitVersionNumber10_14*/ 1671){
[NSApp run];
@@ -228,8 +366,16 @@ int nativeLoop(void) {
return EXIT_SUCCESS;
}
+void nativeStart(void) {
+ owner = [[SystrayAppDelegate alloc] init];
+
+ NSNotification *launched = [NSNotification notificationWithName:NSApplicationDidFinishLaunchingNotification
+ object:[NSApplication sharedApplication]];
+ [owner applicationDidFinishLaunching:launched];
+}
+
void runInMainThread(SEL method, id object) {
- [(AppDelegate*)[NSApp delegate]
+ [owner
performSelectorOnMainThread:method
withObject:object
waitUntilDone: YES];
@@ -237,19 +383,23 @@ void runInMainThread(SEL method, id object) {
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);
+ @autoreleasepool {
+ 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]);
+ @autoreleasepool {
+ 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) {
@@ -266,6 +416,14 @@ void setTooltip(char* ctooltip) {
runInMainThread(@selector(setTooltip:), (id)tooltip);
}
+void setRemovalAllowed(bool allowed) {
+ if (allowed) {
+ runInMainThread(@selector(setRemovalAllowed), nil);
+ } else {
+ runInMainThread(@selector(setRemovalForbidden), nil);
+ }
+}
+
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);
@@ -273,9 +431,9 @@ void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* to
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 add_separator(int menuId, int parentId) {
+ NSNumber *pId = [NSNumber numberWithInt:parentId];
+ runInMainThread(@selector(add_separator:), (id)pId);
}
void hide_menu_item(int menuId) {
@@ -283,11 +441,24 @@ void hide_menu_item(int menuId) {
runInMainThread(@selector(hide_menu_item:), (id)mId);
}
+void remove_menu_item(int menuId) {
+ NSNumber *mId = [NSNumber numberWithInt:menuId];
+ runInMainThread(@selector(remove_menu_item:), (id)mId);
+}
+
+void show_menu() {
+ runInMainThread(@selector(show_menu), nil);
+}
+
void show_menu_item(int menuId) {
NSNumber *mId = [NSNumber numberWithInt:menuId];
runInMainThread(@selector(show_menu_item:), (id)mId);
}
+void reset_menu() {
+ runInMainThread(@selector(reset_menu), nil);
+}
+
void quit() {
runInMainThread(@selector(quit), nil);
}
diff --git a/vendor/fyne.io/systray/systray_menu_unix.go b/vendor/fyne.io/systray/systray_menu_unix.go
new file mode 100644
index 0000000..ae7d242
--- /dev/null
+++ b/vendor/fyne.io/systray/systray_menu_unix.go
@@ -0,0 +1,370 @@
+//go:build (linux || freebsd || openbsd || netbsd) && !android
+
+package systray
+
+import (
+ "fmt"
+ "log"
+ "os"
+
+ "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.
+// iconBytes should be the content of .ico/.jpg/.png
+func (item *MenuItem) SetIcon(iconBytes []byte) {
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+ m, exists := findLayout(int32(item.id))
+ if exists {
+ m.V1["icon-data"] = dbus.MakeVariant(iconBytes)
+ refresh()
+ }
+}
+
+// SetIconFromFilePath sets the icon of a menu item from a file path.
+// iconFilePath should be the path to a .ico for windows and .ico/.jpg/.png for other platforms.
+func (item *MenuItem) SetIconFromFilePath(iconFilePath string) error {
+ iconBytes, err := os.ReadFile(iconFilePath)
+ if err != nil {
+ return fmt.Errorf("failed to read icon file: %v", err)
+ }
+ item.SetIcon(iconBytes)
+ return nil
+}
+
+// copyLayout makes full copy of layout
+func copyLayout(in *menuLayout, depth int32) *menuLayout {
+ out := menuLayout{
+ V0: in.V0,
+ V1: make(map[string]dbus.Variant, len(in.V1)),
+ }
+ for k, v := range in.V1 {
+ out.V1[k] = v
+ }
+ if depth != 0 {
+ depth--
+ out.V2 = make([]dbus.Variant, len(in.V2))
+ for i, v := range in.V2 {
+ out.V2[i] = dbus.MakeVariant(copyLayout(v.Value().(*menuLayout), depth))
+ }
+ } else {
+ out.V2 = []dbus.Variant{}
+ }
+ return &out
+}
+
+// GetLayout is com.canonical.dbusmenu.GetLayout method.
+func (t *tray) GetLayout(parentID int32, recursionDepth int32, propertyNames []string) (revision uint32, layout menuLayout, err *dbus.Error) {
+ initialMenuBuilt.Wait()
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+ if m, ok := findLayout(parentID); ok {
+ // return copy of menu layout to prevent panic from cuncurrent access to layout
+ return instance.menuVersion, *copyLayout(m, recursionDepth), nil
+ }
+ return
+}
+
+// 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) {
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+ for _, id := range ids {
+ if m, ok := findLayout(id); ok {
+ p := struct {
+ V0 int32
+ V1 map[string]dbus.Variant
+ }{
+ V0: m.V0,
+ V1: make(map[string]dbus.Variant, len(m.V1)),
+ }
+ for k, v := range m.V1 {
+ p.V1[k] = v
+ }
+ properties = append(properties, p)
+ }
+ }
+ return
+}
+
+// GetProperty is com.canonical.dbusmenu.GetProperty method.
+func (t *tray) GetProperty(id int32, name string) (value dbus.Variant, err *dbus.Error) {
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+ if m, ok := findLayout(id); ok {
+ if p, ok := m.V1[name]; ok {
+ return p, nil
+ }
+ }
+ return
+}
+
+// Event is com.canonical.dbusmenu.Event method.
+func (t *tray) Event(id int32, eventID string, data dbus.Variant, timestamp uint32) (err *dbus.Error) {
+ switch eventID {
+ case "clicked":
+ systrayMenuItemSelected(uint32(id))
+ case "opened":
+ t.menuLock.RLock()
+ rootMenuID := t.menu.V0
+ t.menuLock.RUnlock()
+
+ if id == rootMenuID {
+ select {
+ case TrayOpenedCh <- struct{}{}:
+ default:
+ }
+ }
+ }
+ 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) {
+ for _, event := range events {
+ if event.V1 == "clicked" {
+ systrayMenuItemSelected(uint32(event.V0))
+ }
+ }
+ 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 {
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+ return map[string]map[string]*prop.Prop{
+ "com.canonical.dbusmenu": {
+ "Version": {
+ Value: instance.menuVersion,
+ Writable: true,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "TextDirection": {
+ Value: "ltr",
+ Writable: false,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "Status": {
+ Value: "normal",
+ Writable: false,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "IconThemePath": {
+ Value: []string{},
+ Writable: false,
+ Emit: prop.EmitTrue,
+ Callback: 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
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+ 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, parent uint32) {
+ menu, _ := findLayout(int32(parent))
+
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+ layout := &menuLayout{
+ V0: int32(id),
+ V1: map[string]dbus.Variant{
+ "type": dbus.MakeVariant("separator"),
+ },
+ V2: []dbus.Variant{},
+ }
+ menu.V2 = append(menu.V2, dbus.MakeVariant(layout))
+ refresh()
+}
+
+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) {
+ if id == 0 {
+ return instance.menu, true
+ }
+ 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 removeSubLayout(id int32, vals []dbus.Variant) ([]dbus.Variant, bool) {
+ for idx, i := range vals {
+ item := i.Value().(*menuLayout)
+ if item.V0 == id {
+ return append(vals[:idx], vals[idx+1:]...), true
+ }
+
+ if len(item.V2) > 0 {
+ if child, removed := removeSubLayout(id, item.V2); removed {
+ return child, true
+ }
+ }
+ }
+
+ return vals, false
+}
+
+func removeMenuItem(item *MenuItem) {
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+
+ parent := instance.menu
+ if item.parent != nil {
+ m, ok := findLayout(int32(item.parent.id))
+ if !ok {
+ return
+ }
+ parent = m
+ }
+
+ if items, removed := removeSubLayout(int32(item.id), parent.V2); removed {
+ parent.V2 = items
+ refresh()
+ }
+}
+
+func hideMenuItem(item *MenuItem) {
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+ m, exists := findLayout(int32(item.id))
+ if exists {
+ m.V1["visible"] = dbus.MakeVariant(false)
+ refresh()
+ }
+}
+
+func showMenuItem(item *MenuItem) {
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+ m, exists := findLayout(int32(item.id))
+ if exists {
+ m.V1["visible"] = dbus.MakeVariant(true)
+ refresh()
+ }
+}
+
+func refresh() {
+ instance.lock.Lock()
+ defer instance.lock.Unlock()
+ if instance.conn == nil || instance.menuProps == nil {
+ return
+ }
+ instance.menuVersion++
+ dbusErr := instance.menuProps.Set("com.canonical.dbusmenu", "Version",
+ dbus.MakeVariant(instance.menuVersion))
+ if dbusErr != nil {
+ log.Printf("systray error: failed to update menu version: %v\n", dbusErr)
+ return
+ }
+ err := menu.Emit(instance.conn, &menu.Dbusmenu_LayoutUpdatedSignal{
+ Path: menuPath,
+ Body: &menu.Dbusmenu_LayoutUpdatedSignalBody{
+ Revision: instance.menuVersion,
+ },
+ })
+ if err != nil {
+ log.Printf("systray error: failed to emit layout updated signal: %v\n", err)
+ }
+
+}
+
+func resetMenu() {
+ instance.menuLock.Lock()
+ defer instance.menuLock.Unlock()
+ instance.menu = &menuLayout{}
+ instance.menuVersion++
+ refresh()
+}
diff --git a/vendor/fyne.io/systray/systray_notifier_unix.go b/vendor/fyne.io/systray/systray_notifier_unix.go
new file mode 100644
index 0000000..eb84493
--- /dev/null
+++ b/vendor/fyne.io/systray/systray_notifier_unix.go
@@ -0,0 +1,44 @@
+package systray
+
+import (
+ "fyne.io/systray/internal/generated/notifier"
+ "github.com/godbus/dbus/v5"
+)
+
+type leftRightNotifierItem struct {
+}
+
+func newLeftRightNotifierItem() notifier.StatusNotifierItemer {
+ return &leftRightNotifierItem{}
+}
+
+func (i *leftRightNotifierItem) Activate(_, _ int32) *dbus.Error {
+ if f := tappedLeft; f == nil {
+ return &dbus.ErrMsgUnknownMethod
+ }
+
+ tappedLeft()
+ return nil
+}
+
+func (i *leftRightNotifierItem) ContextMenu(_, _ int32) *dbus.Error {
+ if f := tappedRight; f == nil {
+ return &dbus.ErrMsgUnknownMethod
+ }
+
+ tappedRight()
+ return nil
+}
+
+func (i *leftRightNotifierItem) SecondaryActivate(_, _ int32) *dbus.Error {
+ if f := tappedRight; f == nil {
+ return &dbus.ErrMsgUnknownMethod
+ }
+
+ tappedRight()
+ return nil
+}
+
+func (i *leftRightNotifierItem) Scroll(_ int32, _ string) *dbus.Error {
+ return &dbus.ErrMsgUnknownMethod
+}
diff --git a/vendor/fyne.io/systray/systray_unix.go b/vendor/fyne.io/systray/systray_unix.go
new file mode 100644
index 0000000..bde7d9c
--- /dev/null
+++ b/vendor/fyne.io/systray/systray_unix.go
@@ -0,0 +1,435 @@
+//go:build (linux || freebsd || openbsd || netbsd) && !android
+
+//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" // used only here
+ "log"
+ "os"
+ "sync"
+
+ "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 = "/StatusNotifierItem/menu"
+)
+
+var (
+ // to signal quitting the internal main loop
+ quitChan = make(chan struct{})
+
+ // instance is the current instance of our DBus tray server
+ instance = &tray{menu: &menuLayout{}, menuVersion: 1}
+)
+
+// 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) {
+ instance.lock.Lock()
+ instance.iconData = iconBytes
+ props := instance.props
+ conn := instance.conn
+ defer instance.lock.Unlock()
+
+ if props == nil {
+ return
+ }
+
+ props.SetMust("org.kde.StatusNotifierItem", "IconPixmap",
+ []PX{convertToPixels(iconBytes)})
+ if conn == nil {
+ return
+ }
+
+ err := notifier.Emit(conn, ¬ifier.StatusNotifierItem_NewIconSignal{
+ Path: path,
+ Body: ¬ifier.StatusNotifierItem_NewIconSignalBody{},
+ })
+ if err != nil {
+ log.Printf("systray error: failed to emit new icon signal: %s\n", err)
+ return
+ }
+}
+
+// SetIconFromFilePath sets the systray icon from a file path.
+// iconFilePath should be the path to a .ico for windows and .ico/.jpg/.png for other platforms.
+func SetIconFromFilePath(iconFilePath string) error {
+ bytes, err := os.ReadFile(iconFilePath)
+ if err != nil {
+ return fmt.Errorf("failed to read icon file: %v", err)
+ }
+ SetIcon(bytes)
+ return nil
+}
+
+// SetTitle sets the systray title, only available on Mac and Linux.
+func SetTitle(t string) {
+ instance.lock.Lock()
+ instance.title = t
+ props := instance.props
+ conn := instance.conn
+ defer instance.lock.Unlock()
+
+ if props == nil {
+ return
+ }
+ dbusErr := props.Set("org.kde.StatusNotifierItem", "Title",
+ dbus.MakeVariant(t))
+ if dbusErr != nil {
+ log.Printf("systray error: failed to set Title prop: %s\n", dbusErr)
+ return
+ }
+
+ if conn == nil {
+ return
+ }
+
+ err := notifier.Emit(conn, ¬ifier.StatusNotifierItem_NewTitleSignal{
+ Path: path,
+ Body: ¬ifier.StatusNotifierItem_NewTitleSignalBody{},
+ })
+ if err != nil {
+ log.Printf("systray error: failed to emit new title signal: %s\n", err)
+ return
+ }
+}
+
+// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon,
+// only available on Mac and Windows.
+func SetTooltip(tooltipTitle string) {
+ instance.lock.Lock()
+ instance.tooltipTitle = tooltipTitle
+ props := instance.props
+ defer instance.lock.Unlock()
+
+ if props == nil {
+ return
+ }
+ dbusErr := props.Set("org.kde.StatusNotifierItem", "ToolTip",
+ dbus.MakeVariant(tooltip{V2: tooltipTitle}))
+ if dbusErr != nil {
+ log.Printf("systray error: failed to set ToolTip prop: %s\n", dbusErr)
+ return
+ }
+}
+
+// SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows and
+// Linux, it falls back to the regular icon bytes.
+// 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)
+}
+
+// SetRemovalAllowed sets whether a user can remove the systray icon or not.
+// This is only supported on macOS.
+func SetRemovalAllowed(allowed bool) {
+}
+
+func setInternalLoop(_ bool) {
+ // nothing to action on Linux
+}
+
+func registerSystray() {
+}
+
+func nativeLoop() int {
+ nativeStart()
+ <-quitChan
+ nativeEnd()
+ return 0
+}
+
+func nativeEnd() {
+ runSystrayExit()
+ instance.conn.Close()
+}
+
+func quit() {
+ close(quitChan)
+}
+
+func nativeStart() {
+ systrayReady()
+ conn, err := dbus.SessionBus()
+ if err != nil {
+ log.Printf("systray error: failed to connect to DBus: %v\n", err)
+ return
+ }
+ err = notifier.ExportStatusNotifierItem(conn, path, newLeftRightNotifierItem())
+ if err != nil {
+ log.Printf("systray error: failed to export status notifier item: %v\n", err)
+ }
+ err = menu.ExportDbusmenu(conn, menuPath, instance)
+ if err != nil {
+ log.Printf("systray error: failed to export status notifier menu: %v\n", err)
+ return
+ }
+
+ 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 {
+ log.Printf("systray error: failed to request name: %s\n", err)
+ // it's not critical error: continue
+ }
+ props, err := prop.Export(conn, path, instance.createPropSpec())
+ if err != nil {
+ log.Printf("systray error: failed to export notifier item properties to bus: %s\n", err)
+ return
+ }
+ menuProps, err := prop.Export(conn, menuPath, createMenuPropSpec())
+ if err != nil {
+ log.Printf("systray error: failed to export notifier menu properties to bus: %s\n", err)
+ 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("systray error: failed to export node introspection: %s\n", err)
+ 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("systray error: failed to export menu node introspection: %s\n", err)
+ return
+ }
+
+ instance.lock.Lock()
+ instance.conn = conn
+ instance.props = props
+ instance.menuProps = menuProps
+ instance.lock.Unlock()
+
+ go stayRegistered()
+}
+
+func register() bool {
+ obj := instance.conn.Object("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher")
+ call := obj.Call("org.kde.StatusNotifierWatcher.RegisterStatusNotifierItem", 0, path)
+ if call.Err != nil {
+ log.Printf("systray error: failed to register: %v\n", call.Err)
+ return false
+ }
+
+ return true
+}
+
+func stayRegistered() {
+ register()
+
+ conn := instance.conn
+ if err := conn.AddMatchSignal(
+ dbus.WithMatchObjectPath("/org/freedesktop/DBus"),
+ dbus.WithMatchInterface("org.freedesktop.DBus"),
+ dbus.WithMatchSender("org.freedesktop.DBus"),
+ dbus.WithMatchMember("NameOwnerChanged"),
+ dbus.WithMatchArg(0, "org.kde.StatusNotifierWatcher"),
+ ); err != nil {
+ log.Printf("systray error: failed to register signal matching: %v\n", err)
+ // If we can't monitor signals, there is no point in
+ // us being here. we're either registered or not (per
+ // above) and will roll the dice from here...
+ return
+ }
+
+ sc := make(chan *dbus.Signal, 10)
+ conn.Signal(sc)
+
+ for {
+ select {
+ case sig := <-sc:
+ if sig == nil {
+ return // We get a nil signal when closing the window.
+ } else if len(sig.Body) < 3 {
+ return // malformed signal?
+ }
+
+ // sig.Body has the args, which are [name old_owner new_owner]
+ if s, ok := sig.Body[2].(string); ok && s != "" {
+ register()
+ }
+ case <-quitChan:
+ return
+ }
+ }
+}
+
+// tray is a basic type that handles the dbus functionality
+type tray struct {
+ // the DBus connection that we will use
+ conn *dbus.Conn
+
+ // icon data for the main systray icon
+ iconData []byte
+ // title and tooltip state
+ title, tooltipTitle string
+
+ lock sync.Mutex
+ menu *menuLayout
+ menuLock sync.RWMutex
+ props, menuProps *prop.Properties
+ menuVersion uint32
+}
+
+func (t *tray) createPropSpec() map[string]map[string]*prop.Prop {
+ t.lock.Lock()
+ defer t.lock.Unlock()
+ id := t.title
+ if id == "" {
+ id = fmt.Sprintf("systray_%d", os.Getpid())
+ }
+ return map[string]map[string]*prop.Prop{
+ "org.kde.StatusNotifierItem": {
+ "Status": {
+ Value: "Active", // Passive, Active or NeedsAttention
+ Writable: false,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "Title": {
+ Value: t.title,
+ Writable: true,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "Id": {
+ Value: id,
+ Writable: false,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "Category": {
+ Value: "ApplicationStatus",
+ Writable: false,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "IconName": {
+ Value: "",
+ Writable: false,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "IconPixmap": {
+ Value: []PX{convertToPixels(t.iconData)},
+ Writable: true,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "IconThemePath": {
+ Value: "",
+ Writable: false,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "ItemIsMenu": {
+ Value: tappedLeft == nil && tappedRight == nil,
+ Writable: false,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "Menu": {
+ Value: dbus.ObjectPath(menuPath),
+ Writable: true,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ "ToolTip": {
+ Value: tooltip{V2: t.tooltipTitle},
+ Writable: true,
+ Emit: prop.EmitTrue,
+ Callback: nil,
+ },
+ }}
+}
+
+// PX is picture pix map structure with width and high
+type PX struct {
+ W, H int
+ Pix []byte
+}
+
+// tooltip is our data for a tooltip property.
+// Param names need to match the generated code...
+type tooltip = struct {
+ V0 string // name
+ V1 []PX // icons
+ V2 string // title
+ V3 string // description
+}
+
+func convertToPixels(data []byte) PX {
+ if len(data) == 0 {
+ return PX{}
+ }
+
+ img, _, err := image.Decode(bytes.NewReader(data))
+ 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
+}
diff --git a/src/systray/systray_windows.go b/vendor/fyne.io/systray/systray_windows.go
similarity index 75%
rename from src/systray/systray_windows.go
rename to vendor/fyne.io/systray/systray_windows.go
index d4c2ee7..84ee6a2 100644
--- a/src/systray/systray_windows.go
+++ b/vendor/fyne.io/systray/systray_windows.go
@@ -1,17 +1,19 @@
//go:build windows
-// +build windows
package systray
import (
"crypto/md5"
"encoding/hex"
+ "errors"
+ "fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"sort"
"sync"
+ "sync/atomic"
"syscall"
"unsafe"
@@ -24,6 +26,7 @@ var (
g32 = windows.NewLazySystemDLL("Gdi32.dll")
pCreateCompatibleBitmap = g32.NewProc("CreateCompatibleBitmap")
pCreateCompatibleDC = g32.NewProc("CreateCompatibleDC")
+ pCreateDIBSection = g32.NewProc("CreateDIBSection")
pDeleteDC = g32.NewProc("DeleteDC")
pSelectObject = g32.NewProc("SelectObject")
@@ -39,12 +42,13 @@ var (
pCreateWindowEx = u32.NewProc("CreateWindowExW")
pDefWindowProc = u32.NewProc("DefWindowProcW")
pDeleteMenu = u32.NewProc("DeleteMenu")
+ pDestroyMenu = u32.NewProc("DestroyMenu")
+ pRemoveMenu = u32.NewProc("RemoveMenu")
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")
@@ -64,6 +68,9 @@ var (
pTranslateMessage = u32.NewProc("TranslateMessage")
pUnregisterClass = u32.NewProc("UnregisterClassW")
pUpdateWindow = u32.NewProc("UpdateWindow")
+
+ // ErrTrayNotReadyYet is returned by functions when they are called before the tray has been initialized.
+ ErrTrayNotReadyYet = errors.New("tray not ready yet")
)
// Contains window class information.
@@ -175,6 +182,30 @@ type point struct {
X, Y int32
}
+// The BITMAPINFO structure defines the dimensions and color information for a DIB.
+// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfo
+type bitmapInfo struct {
+ BmiHeader bitmapInfoHeader
+ BmiColors windows.Handle
+}
+
+// The BITMAPINFOHEADER structure contains information about the dimensions and color format of a device-independent bitmap (DIB).
+// https://learn.microsoft.com/en-us/previous-versions/dd183376(v=vs.85)
+// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader
+type bitmapInfoHeader struct {
+ BiSize uint32
+ BiWidth int32
+ BiHeight int32
+ BiPlanes uint16
+ BiBitCount uint16
+ BiCompression uint32
+ BiSizeImage uint32
+ BiXPelsPerMeter int32
+ BiYPelsPerMeter int32
+ BiClrUsed uint32
+ BiClrImportant uint32
+}
+
// Contains information about loaded resources
type winTray struct {
instance,
@@ -205,11 +236,22 @@ type winTray struct {
wmSystrayMessage,
wmTaskbarCreated uint32
+
+ initialized atomic.Bool
+}
+
+// isReady checks if the tray as already been initialized. It is not goroutine safe with in regard to the initialization function, but prevents a panic when functions are called too early.
+func (t *winTray) isReady() bool {
+ return t.initialized.Load()
}
// 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 {
+ if !wt.isReady() {
+ return ErrTrayNotReadyYet
+ }
+
const NIF_ICON = 0x00000002
h, err := t.loadIconFrom(src)
@@ -229,6 +271,10 @@ func (t *winTray) setIcon(src string) error {
// 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 {
+ if !wt.isReady() {
+ return ErrTrayNotReadyYet
+ }
+
const NIF_TIP = 0x00000004
b, err := windows.UTF16FromString(src)
if err != nil {
@@ -244,15 +290,11 @@ func (t *winTray) setTooltip(src string) error {
return t.nid.modify()
}
-var wt winTray
+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) {
+func (t *winTray) wndProc(hWnd windows.Handle, message uint32, wParam, lParam uintptr) (lResult uintptr) {
const (
WM_RBUTTONUP = 0x0205
WM_LBUTTONUP = 0x0202
@@ -260,11 +302,8 @@ func (t *winTray) wndProc(
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
@@ -284,11 +323,13 @@ func (t *winTray) wndProc(
t.nid.delete()
}
t.muNID.Unlock()
- systrayExit()
+ runSystrayExit()
case t.wmSystrayMessage:
switch lParam {
- case WM_RBUTTONUP, WM_LBUTTONUP:
- t.showMenu()
+ case WM_LBUTTONUP:
+ systrayLeftClick()
+ case WM_RBUTTONUP:
+ systrayRightClick()
}
case t.wmTaskbarCreated: // on explorer.exe restarts
t.muNID.Lock()
@@ -498,12 +539,16 @@ func (t *winTray) convertToSubMenu(menuItemId uint32) (windows.Handle, error) {
return menu, nil
}
-func (t *winTray) addOrUpdateMenuItem(
- menuItemId uint32,
- parentId uint32,
- title string,
- disabled, checked bool,
-) error {
+// SetRemovalAllowed sets whether a user can remove the systray icon or not.
+// This is only supported on macOS.
+func SetRemovalAllowed(allowed bool) {
+}
+
+func (t *winTray) addOrUpdateMenuItem(menuItemId uint32, parentId uint32, title string, disabled, checked bool) error {
+ if !wt.isReady() {
+ return ErrTrayNotReadyYet
+ }
+
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
const (
MIIM_FTYPE = 0x00000100
@@ -568,6 +613,14 @@ func (t *winTray) addOrUpdateMenuItem(
}
if res == 0 {
+ // Menu item does not already exist, create it
+ t.muMenus.RLock()
+ submenu, exists := t.menus[menuItemId]
+ t.muMenus.RUnlock()
+ if exists {
+ mi.Mask |= MIIM_SUBMENU
+ mi.SubMenu = submenu
+ }
t.addToVisibleItems(parentId, menuItemId)
position := t.getVisibleItemIndex(parentId, menuItemId)
res, _, err = pInsertMenuItem.Call(
@@ -589,6 +642,10 @@ func (t *winTray) addOrUpdateMenuItem(
}
func (t *winTray) addSeparatorMenuItem(menuItemId, parentId uint32) error {
+ if !wt.isReady() {
+ return ErrTrayNotReadyYet
+ }
+
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
const (
MIIM_FTYPE = 0x00000100
@@ -623,8 +680,11 @@ func (t *winTray) addSeparatorMenuItem(menuItemId, parentId uint32) error {
return nil
}
-func (t *winTray) hideMenuItem(menuItemId, parentId uint32) error {
- // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647629(v=vs.85).aspx
+func (t *winTray) removeMenuItem(menuItemId, parentId uint32) error {
+ if !wt.isReady() {
+ return ErrTrayNotReadyYet
+ }
+
const MF_BYCOMMAND = 0x00000000
const ERROR_SUCCESS syscall.Errno = 0
@@ -644,7 +704,35 @@ func (t *winTray) hideMenuItem(menuItemId, parentId uint32) error {
return nil
}
+func (t *winTray) hideMenuItem(menuItemId, parentId uint32) error {
+ if !wt.isReady() {
+ return ErrTrayNotReadyYet
+ }
+
+ const MF_BYCOMMAND = 0x00000000
+ const ERROR_SUCCESS syscall.Errno = 0
+
+ t.muMenus.RLock()
+ menu := uintptr(t.menus[parentId])
+ t.muMenus.RUnlock()
+ res, _, err := pRemoveMenu.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 {
+ if !wt.isReady() {
+ return ErrTrayNotReadyYet
+ }
+
const (
TPM_BOTTOMALIGN = 0x0020
TPM_LEFTALIGN = 0x0000
@@ -678,7 +766,7 @@ func (t *winTray) delFromVisibleItems(parent, val uint32) {
visibleItems := t.visibleItems[parent]
for i, itemval := range visibleItems {
if val == itemval {
- visibleItems = append(visibleItems[:i], visibleItems[i+1:]...)
+ t.visibleItems[parent] = append(visibleItems[:i], visibleItems[i+1:]...)
break
}
}
@@ -710,6 +798,10 @@ func (t *winTray) getVisibleItemIndex(parent, val uint32) int {
// 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) {
+ if !wt.isReady() {
+ return 0, ErrTrayNotReadyYet
+ }
+
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
@@ -742,7 +834,7 @@ func (t *winTray) loadIconFrom(src string) (windows.Handle, error) {
return h, nil
}
-func (t *winTray) iconToBitmap(hIcon windows.Handle) (windows.Handle, error) {
+func iconToBitmap(hIcon windows.Handle) (windows.Handle, error) {
const SM_CXSMICON = 49
const SM_CYSMICON = 50
const DI_NORMAL = 0x3
@@ -758,10 +850,7 @@ func (t *winTray) iconToBitmap(hIcon windows.Handle) (windows.Handle, error) {
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
- }
+ hMemBmp, err := create32BitHBitmap(hMemDC, int32(cx), int32(cy))
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)
@@ -771,49 +860,94 @@ func (t *winTray) iconToBitmap(hIcon windows.Handle) (windows.Handle, error) {
return windows.Handle(hMemBmp), nil
}
+// https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createdibsection
+func create32BitHBitmap(hDC uintptr, cx, cy int32) (uintptr, error) {
+ const BI_RGB uint32 = 0
+ const DIB_RGB_COLORS = 0
+ bmi := bitmapInfo{
+ BmiHeader: bitmapInfoHeader{
+ BiPlanes: 1,
+ BiCompression: BI_RGB,
+ BiWidth: cx,
+ BiHeight: cy,
+ BiBitCount: 32,
+ },
+ }
+ bmi.BmiHeader.BiSize = uint32(unsafe.Sizeof(bmi.BmiHeader))
+ var bits uintptr
+ hBitmap, _, err := pCreateDIBSection.Call(
+ hDC,
+ uintptr(unsafe.Pointer(&bmi)),
+ DIB_RGB_COLORS,
+ uintptr(unsafe.Pointer(&bits)),
+ uintptr(0),
+ 0,
+ )
+ if hBitmap == 0 {
+ return 0, err
+ }
+ return hBitmap, nil
+}
+
func registerSystray() {
if err := wt.initInstance(); err != nil {
- log.Printf("Unable to init instance: %v", err)
+ log.Printf("systray error: unable to init instance: %s\n", err)
return
}
if err := wt.createMenu(); err != nil {
- log.Printf("Unable to create menu: %v", err)
+ log.Printf("systray error: unable to create menu: %s\n", err)
return
}
+ wt.initialized.Store(true)
+ systrayReady()
}
-func nativeLoop() {
- // Main message pump.
- m := &struct {
- WindowHandle windows.Handle
- Message uint32
- Wparam uintptr
- Lparam uintptr
- Time uint32
- Pt point
- }{}
- for {
- ret, _, err := pGetMessage.Call(uintptr(unsafe.Pointer(m)), 0, 0, 0)
+var m = &struct {
+ WindowHandle windows.Handle
+ Message uint32
+ Wparam uintptr
+ Lparam uintptr
+ Time uint32
+ Pt point
+}{}
- // 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
- case 0:
- return
- default:
- pTranslateMessage.Call(uintptr(unsafe.Pointer(m)))
- pDispatchMessage.Call(uintptr(unsafe.Pointer(m)))
- }
+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("systray error: message loop failure: %s\n", 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
@@ -823,6 +957,16 @@ func quit() {
0,
0,
)
+
+ wt.muNID.Lock()
+ if wt.nid != nil {
+ wt.nid.delete()
+ }
+ wt.muNID.Unlock()
+ runSystrayExit()
+}
+
+func setInternalLoop(bool) {
}
func iconBytesToFilePath(iconBytes []byte) (string, error) {
@@ -844,15 +988,25 @@ func iconBytesToFilePath(iconBytes []byte) (string, error) {
func SetIcon(iconBytes []byte) {
iconFilePath, err := iconBytesToFilePath(iconBytes)
if err != nil {
- log.Printf("Unable to write icon data to temp file: %v", err)
+ log.Printf("systray error: unable to write icon data to temp file: %s\n", err)
return
}
if err := wt.setIcon(iconFilePath); err != nil {
- log.Printf("Unable to set icon: %v", err)
+ log.Printf("systray error: unable to set icon: %s\n", err)
return
}
}
+// SetIconFromFilePath sets the systray icon from a file path.
+// iconFilePath should be the path to a .ico for windows and .ico/.jpg/.png for other platforms.
+func SetIconFromFilePath(iconFilePath string) error {
+ err := wt.setIcon(iconFilePath)
+ if err != nil {
+ return fmt.Errorf("failed to set icon: %v", err)
+ }
+ return nil
+}
+
// 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
@@ -878,57 +1032,53 @@ func (item *MenuItem) parentId() uint32 {
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)
+ log.Printf("systray error: unable to write icon data to temp file: %s\n", err)
return
}
+ err = item.SetIconFromFilePath(iconFilePath)
+ if err != nil {
+ log.Printf("systray error: %s\n", err)
+ return
+ }
+}
+
+// SetIconFromFilePath sets the icon of a menu item from a file path.
+// iconFilePath should be the path to a .ico for windows and .ico/.jpg/.png for other platforms.
+func (item *MenuItem) SetIconFromFilePath(iconFilePath string) error {
h, err := wt.loadIconFrom(iconFilePath)
if err != nil {
- log.Printf("Unable to load icon from temp file: %v", err)
- return
+ return fmt.Errorf("unable to load icon from file: %s", err)
}
- h, err = wt.iconToBitmap(h)
+ h, err = iconToBitmap(h)
if err != nil {
- log.Printf("Unable to convert icon to bitmap: %v", err)
- return
+ return fmt.Errorf("unable to convert icon to bitmap: %s", err)
}
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,
- )
+ 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
+ return fmt.Errorf("unable to addOrUpdateMenuItem: %s", err)
}
+ return nil
}
// 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)
+ log.Printf("systray error: unable to set tooltip: %s\n", err)
return
}
}
func addOrUpdateMenuItem(item *MenuItem) {
- err := wt.addOrUpdateMenuItem(
- uint32(item.id),
- item.parentId(),
- item.title,
- item.disabled,
- item.checked,
- )
+ err := wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
if err != nil {
- log.Printf("Unable to addOrUpdateMenuItem: %v", err)
+ log.Printf("systray error: unable to addOrUpdateMenuItem: %s\n", err)
return
}
}
@@ -941,10 +1091,10 @@ func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes
item.SetIcon(regularIconBytes)
}
-func addSeparator(id uint32) {
- err := wt.addSeparatorMenuItem(id, 0)
+func addSeparator(id uint32, parent uint32) {
+ err := wt.addSeparatorMenuItem(id, parent)
if err != nil {
- log.Printf("Unable to addSeparator: %v", err)
+ log.Printf("systray error: unable to addSeparator: %s\n", err)
return
}
}
@@ -952,7 +1102,15 @@ func addSeparator(id uint32) {
func hideMenuItem(item *MenuItem) {
err := wt.hideMenuItem(uint32(item.id), item.parentId())
if err != nil {
- log.Printf("Unable to hideMenuItem: %v", err)
+ log.Printf("systray error: unable to hideMenuItem: %s\n", err)
+ return
+ }
+}
+
+func removeMenuItem(item *MenuItem) {
+ err := wt.removeMenuItem(uint32(item.id), item.parentId())
+ if err != nil {
+ log.Printf("systray error: unable to removeMenuItem: %s\n", err)
return
}
}
@@ -960,3 +1118,30 @@ func hideMenuItem(item *MenuItem) {
func showMenuItem(item *MenuItem) {
addOrUpdateMenuItem(item)
}
+
+func resetMenu() {
+ _, _, _ = pDestroyMenu.Call(uintptr(wt.menus[0]))
+ wt.visibleItems = make(map[uint32][]uint32)
+ wt.menus = make(map[uint32]windows.Handle)
+ wt.menuOf = make(map[uint32]windows.Handle)
+ wt.menuItemIcons = make(map[uint32]windows.Handle)
+ wt.createMenu()
+}
+
+func systrayLeftClick() {
+ if fn := tappedLeft; fn != nil {
+ fn()
+ return
+ }
+
+ wt.showMenu()
+}
+
+func systrayRightClick() {
+ if fn := tappedRight; fn != nil {
+ fn()
+ return
+ }
+
+ wt.showMenu()
+}
diff --git a/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md
new file mode 100644
index 0000000..c88f9b2
--- /dev/null
+++ b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md
@@ -0,0 +1,50 @@
+# How to Contribute
+
+## Getting Started
+
+- Fork the repository on GitHub
+- Read the [README](README.markdown) for build and test instructions
+- Play with the project, submit bugs, submit patches!
+
+## Contribution Flow
+
+This is a rough outline of what a contributor's workflow looks like:
+
+- Create a topic branch from where you want to base your work (usually master).
+- Make commits of logical units.
+- Make sure your commit messages are in the proper format (see below).
+- Push your changes to a topic branch in your fork of the repository.
+- Make sure the tests pass, and add any new tests as appropriate.
+- Submit a pull request to the original repository.
+
+Thanks for your contributions!
+
+### Format of the Commit Message
+
+We follow a rough convention for commit messages that is designed to answer two
+questions: what changed and why. The subject line should feature the what and
+the body of the commit should describe the why.
+
+```
+scripts: add the test-cluster command
+
+this uses tmux to setup a test cluster that you can easily kill and
+start for debugging.
+
+Fixes #38
+```
+
+The format can be described more formally as follows:
+
+```
+:
+
+
+
+