switch to fyne.io/systray

This commit is contained in:
nkanaev
2026-04-25 22:57:32 +01:00
parent f1bdbbc0af
commit 1bae41a350
74 changed files with 10196 additions and 846 deletions

View File

@@ -16,11 +16,6 @@ The licenses are included, and the authorship comments are left intact.
- allowed uri schemes - allowed uri schemes
- added svg whitelist - added svg whitelist
- systray
https://github.com/getlantern/systray (commit:2c0986d) Apache 2.0
removed golog dependency
- fixconsole - fixconsole
https://github.com/apenwarr/fixconsole (commit:5a9f648) Apache 2.0 https://github.com/apenwarr/fixconsole (commit:5a9f648) Apache 2.0

6
go.mod
View File

@@ -5,9 +5,13 @@ go 1.23.0
toolchain go1.23.5 toolchain go1.23.5
require ( require (
fyne.io/systray v1.12.0
github.com/mattn/go-sqlite3 v1.14.24 github.com/mattn/go-sqlite3 v1.14.24
golang.org/x/net v0.38.0 golang.org/x/net v0.38.0
golang.org/x/sys v0.31.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
)

4
go.sum
View File

@@ -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 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=

View File

@@ -3,8 +3,8 @@
package platform package platform
import ( import (
"fyne.io/systray"
"github.com/nkanaev/yarr/src/server" "github.com/nkanaev/yarr/src/server"
"github.com/nkanaev/yarr/src/systray"
) )
func Start(s *server.Server) { func Start(s *server.Server) {

View File

@@ -1,12 +0,0 @@
taken from:
repo:
https://github.com/getlantern/systray
hash:
2c0986dda9aea361e925f90e848d9036be7b5367
changes:
-removed `getlantern/golog` dependency
-prevent from compiling in linux

View File

@@ -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!
![Awesome App screenshot](example/screenshot.png)
## 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
<!-- avoid having a blurry icon and text -->
<key>NSHighResolutionCapable</key>
<string>True</string>
<!-- avoid showing the app on the Dock -->
<key>LSUIElement</key>
<string>1</string>
```
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

View File

@@ -1,268 +0,0 @@
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <libappindicator/app-indicator.h>
#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);
}

View File

@@ -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)
}

View File

@@ -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) {
}

View File

@@ -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))
}

View File

@@ -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)
}

View File

@@ -10,3 +10,4 @@ dll/systray_unsigned.dll
out.txt out.txt
.vs .vs
on_exit*.txt on_exit*.txt
.vscode

147
vendor/fyne.io/systray/README.md generated vendored Normal file
View File

@@ -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!
![Awesome App screenshot](example/screenshot.png)
## 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
<!-- avoid having a blurry icon and text -->
<key>NSHighResolutionCapable</key>
<string>True</string>
<!-- avoid showing the app on the Dock -->
<key>LSUIElement</key>
<string>1</string>
```
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

View File

@@ -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
}

View File

@@ -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 {
}

View File

@@ -1,9 +1,4 @@
//go:build darwin || windows // Package systray is a cross-platform Go library to place an icon and menu in the notification area.
// +build darwin windows
/*
Package systray is a cross-platform Go library to place an icon and menu in the notification area.
*/
package systray package systray
import ( import (
@@ -15,15 +10,29 @@ import (
) )
var ( var (
systrayReady func() systrayReady, systrayExit func()
systrayExit func() tappedLeft, tappedRight func()
systrayExitCalled bool
menuItems = make(map[uint32]*MenuItem) menuItems = make(map[uint32]*MenuItem)
menuItemsLock sync.RWMutex menuItemsLock sync.RWMutex
currentID = uint32(0) initialMenuBuilt sync.WaitGroup
currentID atomic.Uint32
quitOnce sync.Once 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() { func init() {
runtime.LockOSThread() runtime.LockOSThread()
} }
@@ -61,7 +70,7 @@ func (item *MenuItem) String() string {
func newMenuItem(title string, tooltip string, parent *MenuItem) *MenuItem { func newMenuItem(title string, tooltip string, parent *MenuItem) *MenuItem {
return &MenuItem{ return &MenuItem{
ClickedCh: make(chan struct{}), ClickedCh: make(chan struct{}),
id: atomic.AddUint32(&currentID, 1), id: currentID.Add(1),
title: title, title: title,
tooltip: tooltip, tooltip: tooltip,
disabled: false, 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 // Run initializes GUI and starts the event loop, then invokes the onReady
// callback. It blocks until systray.Quit() is called. // callback. It blocks until systray.Quit() is called.
func Run(onReady func(), onExit func()) { func Run(onReady, onExit func()) {
setInternalLoop(true)
Register(onReady, onExit) Register(onReady, onExit)
nativeLoop() 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 // 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 // caller to run the event loop somewhere else. It's useful if the program
// needs to show other UI elements, for example, webview. // needs to show other UI elements, for example, webview.
@@ -89,9 +111,11 @@ func Register(onReady func(), onExit func()) {
} else { } else {
// Run onReady on separate goroutine to avoid blocking event loop // Run onReady on separate goroutine to avoid blocking event loop
readyCh := make(chan interface{}) readyCh := make(chan interface{})
initialMenuBuilt.Add(1)
go func() { go func() {
<-readyCh <-readyCh
onReady() onReady()
initialMenuBuilt.Done()
}() }()
systrayReady = func() { systrayReady = func() {
close(readyCh) close(readyCh)
@@ -103,14 +127,36 @@ func Register(onReady func(), onExit func()) {
onExit = func() {} onExit = func() {}
} }
systrayExit = onExit systrayExit = onExit
systrayExitCalled = false
registerSystray() 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 // Quit the systray
func Quit() { func Quit() {
quitOnce.Do(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. // AddMenuItem adds a menu item with the designated title and tooltip.
// It can be safely invoked from different goroutines. // 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 // 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. // 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. // 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 { func AddMenuItemCheckbox(title string, tooltip string, checked bool) *MenuItem {
item := newMenuItem(title, tooltip, nil) item := newMenuItem(title, tooltip, nil)
item.isCheckable = true item.isCheckable = true
@@ -133,7 +179,12 @@ func AddMenuItemCheckbox(title string, tooltip string, checked bool) *MenuItem {
// AddSeparator adds a separator bar to the menu // AddSeparator adds a separator bar to the menu
func AddSeparator() { func AddSeparator() {
addSeparator(atomic.AddUint32(&currentID, 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. // AddSubMenuItem adds a nested sub-menu item with the designated title and tooltip.
@@ -190,6 +241,30 @@ func (item *MenuItem) Hide() {
hideMenuItem(item) 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 // Show shows a previously hidden menu item
func (item *MenuItem) Show() { func (item *MenuItem) Show() {
showMenuItem(item) showMenuItem(item)
@@ -225,7 +300,7 @@ func systrayMenuItemSelected(id uint32) {
item, ok := menuItems[id] item, ok := menuItems[id]
menuItemsLock.RUnlock() menuItemsLock.RUnlock()
if !ok { if !ok {
log.Printf("No menu item with ID %v", id) log.Printf("systray error: no menu item with ID %d\n", id)
return return
} }
select { select {

View File

@@ -2,16 +2,25 @@
extern void systray_ready(); extern void systray_ready();
extern void systray_on_exit(); 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_item_selected(int menu_id);
extern void systray_menu_will_open();
void registerSystray(void); void registerSystray(void);
void nativeEnd(void);
int nativeLoop(void); int nativeLoop(void);
void nativeStart(void);
void setIcon(const char* iconBytes, int length, bool template); void setIcon(const char* iconBytes, int length, bool template);
void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template); void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template);
void setTitle(char* title); void setTitle(char* title);
void setTooltip(char* tooltip); 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_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 hide_menu_item(int menuId);
void remove_menu_item(int menuId);
void show_menu_item(int menuId); void show_menu_item(int menuId);
void reset_menu();
void show_menu();
void quit(); void quit();

213
vendor/fyne.io/systray/systray_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,213 @@
//go:build !ios
package systray
/*
#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc
#cgo darwin LDFLAGS: -framework Cocoa
#include <stdbool.h>
#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:
}
}

View File

@@ -1,3 +1,5 @@
//go:build !ios
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#include "systray.h" #include "systray.h"
@@ -50,13 +52,33 @@ withParentMenuId: (int)theParentMenuId
} }
@end @end
@interface AppDelegate: NSObject <NSApplicationDelegate> @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 <NSApplicationDelegate, NSMenuDelegate>
- (void) add_or_update_menu_item:(MenuItem*) item; - (void) add_or_update_menu_item:(MenuItem*) item;
- (IBAction)menuHandler:(id)sender; - (IBAction)menuHandler:(id)sender;
- (void)menuWillOpen:(NSMenu*)menu;
@property (assign) IBOutlet NSWindow *window; @property (assign) IBOutlet NSWindow *window;
@end @end
@implementation AppDelegate @implementation SystrayAppDelegate
{ {
NSStatusItem *statusItem; NSStatusItem *statusItem;
NSMenu *menu; NSMenu *menu;
@@ -68,17 +90,73 @@ withParentMenuId: (int)theParentMenuId
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{ {
self->statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength]; self->statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
self->menu = [[NSMenu alloc] init]; self->menu = [[NSMenu alloc] init];
[self->menu setAutoenablesItems: FALSE]; self->menu.delegate = self;
[self->statusItem setMenu:self->menu]; 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(); systray_ready();
} }
- (void)rightMouseClicked {
systray_right_click();
}
- (void)leftMouseClicked {
systray_left_click();
}
- (void)applicationWillTerminate:(NSNotification *)aNotification - (void)applicationWillTerminate:(NSNotification *)aNotification
{ {
systray_on_exit(); 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 { - (void)setIcon:(NSImage *)image {
statusItem.button.image = image; statusItem.button.image = image;
[self updateTitleButtonStyle]; [self updateTitleButtonStyle];
@@ -89,7 +167,7 @@ withParentMenuId: (int)theParentMenuId
[self updateTitleButtonStyle]; [self updateTitleButtonStyle];
} }
-(void)updateTitleButtonStyle { - (void)updateTitleButtonStyle {
if (statusItem.button.image != nil) { if (statusItem.button.image != nil) {
if ([statusItem.button.title length] == 0) { if ([statusItem.button.title length] == 0) {
statusItem.button.imagePosition = NSImageOnly; statusItem.button.imagePosition = NSImageOnly;
@@ -112,6 +190,10 @@ withParentMenuId: (int)theParentMenuId
systray_menu_item_selected(menuId.intValue); systray_menu_item_selected(menuId.intValue);
} }
- (void)menuWillOpen:(NSMenu *)menu {
systray_menu_will_open();
}
- (void)add_or_update_menu_item:(MenuItem *)item { - (void)add_or_update_menu_item:(MenuItem *)item {
NSMenu *theMenu = self->menu; NSMenu *theMenu = self->menu;
NSMenuItem *parentItem; NSMenuItem *parentItem;
@@ -126,8 +208,7 @@ withParentMenuId: (int)theParentMenuId
} }
} }
NSMenuItem *menuItem; NSMenuItem *menuItem = find_menu_item(theMenu, item->menuId);
menuItem = find_menu_item(theMenu, item->menuId);
if (menuItem == NULL) { if (menuItem == NULL) {
menuItem = [theMenu addItemWithTitle:item->title menuItem = [theMenu addItemWithTitle:item->title
action:@selector(menuHandler:) action:@selector(menuHandler:)
@@ -170,8 +251,15 @@ NSMenuItem *find_menu_item(NSMenu *ourMenu, NSNumber *menuId) {
return NULL; 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]]; [menu addItem: [NSMenuItem separatorItem]];
} }
@@ -195,6 +283,13 @@ NSMenuItem *find_menu_item(NSMenu *ourMenu, NSNumber *menuId) {
menuItem.image = image; 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 - (void) show_menu_item:(NSNumber*) menuId
{ {
NSMenuItem* menuItem = find_menu_item(menu, 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 - (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 @end
bool internalLoop = false;
SystrayAppDelegate *owner;
void setInternalLoop(bool i) {
internalLoop = i;
}
void registerSystray(void) { void registerSystray(void) {
AppDelegate *delegate = [[AppDelegate alloc] init]; if (!internalLoop) { // with an external loop we don't take ownership of the app
[[NSApplication sharedApplication] setDelegate:delegate]; return;
}
owner = [[SystrayAppDelegate alloc] init];
[[NSApplication sharedApplication] setDelegate:owner];
// A workaround to avoid crashing on macOS versions before Catalina. Somehow // A workaround to avoid crashing on macOS versions before Catalina. Somehow
// SIGSEGV would happen inside AppKit if [NSApp run] is called from a // SIGSEGV would happen inside AppKit if [NSApp run] is called from a
// different function, even if that function is called right after this. // 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) { int nativeLoop(void) {
if (floor(NSAppKitVersionNumber) > /*NSAppKitVersionNumber10_14*/ 1671){ if (floor(NSAppKitVersionNumber) > /*NSAppKitVersionNumber10_14*/ 1671){
[NSApp run]; [NSApp run];
@@ -228,8 +366,16 @@ int nativeLoop(void) {
return EXIT_SUCCESS; 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) { void runInMainThread(SEL method, id object) {
[(AppDelegate*)[NSApp delegate] [owner
performSelectorOnMainThread:method performSelectorOnMainThread:method
withObject:object withObject:object
waitUntilDone: YES]; waitUntilDone: YES];
@@ -237,19 +383,23 @@ void runInMainThread(SEL method, id object) {
void setIcon(const char* iconBytes, int length, bool template) { void setIcon(const char* iconBytes, int length, bool template) {
NSData* buffer = [NSData dataWithBytes: iconBytes length:length]; NSData* buffer = [NSData dataWithBytes: iconBytes length:length];
@autoreleasepool {
NSImage *image = [[NSImage alloc] initWithData:buffer]; NSImage *image = [[NSImage alloc] initWithData:buffer];
[image setSize:NSMakeSize(16, 16)]; [image setSize:NSMakeSize(16, 16)];
image.template = template; image.template = template;
runInMainThread(@selector(setIcon:), (id)image); runInMainThread(@selector(setIcon:), (id)image);
}
} }
void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template) { void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template) {
NSData* buffer = [NSData dataWithBytes: iconBytes length:length]; NSData* buffer = [NSData dataWithBytes: iconBytes length:length];
@autoreleasepool {
NSImage *image = [[NSImage alloc] initWithData:buffer]; NSImage *image = [[NSImage alloc] initWithData:buffer];
[image setSize:NSMakeSize(16, 16)]; [image setSize:NSMakeSize(16, 16)];
image.template = template; image.template = template;
NSNumber *mId = [NSNumber numberWithInt:menuId]; NSNumber *mId = [NSNumber numberWithInt:menuId];
runInMainThread(@selector(setMenuItemIcon:), @[image, (id)mId]); runInMainThread(@selector(setMenuItemIcon:), @[image, (id)mId]);
}
} }
void setTitle(char* ctitle) { void setTitle(char* ctitle) {
@@ -266,6 +416,14 @@ void setTooltip(char* ctooltip) {
runInMainThread(@selector(setTooltip:), (id)tooltip); 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) { 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]; MenuItem* item = [[MenuItem alloc] initWithId: menuId withParentMenuId: parentMenuId withTitle: title withTooltip: tooltip withDisabled: disabled withChecked: checked];
free(title); 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); runInMainThread(@selector(add_or_update_menu_item:), (id)item);
} }
void add_separator(int menuId) { void add_separator(int menuId, int parentId) {
NSNumber *mId = [NSNumber numberWithInt:menuId]; NSNumber *pId = [NSNumber numberWithInt:parentId];
runInMainThread(@selector(add_separator:), (id)mId); runInMainThread(@selector(add_separator:), (id)pId);
} }
void hide_menu_item(int menuId) { void hide_menu_item(int menuId) {
@@ -283,11 +441,24 @@ void hide_menu_item(int menuId) {
runInMainThread(@selector(hide_menu_item:), (id)mId); 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) { void show_menu_item(int menuId) {
NSNumber *mId = [NSNumber numberWithInt:menuId]; NSNumber *mId = [NSNumber numberWithInt:menuId];
runInMainThread(@selector(show_menu_item:), (id)mId); runInMainThread(@selector(show_menu_item:), (id)mId);
} }
void reset_menu() {
runInMainThread(@selector(reset_menu), nil);
}
void quit() { void quit() {
runInMainThread(@selector(quit), nil); runInMainThread(@selector(quit), nil);
} }

370
vendor/fyne.io/systray/systray_menu_unix.go generated vendored Normal file
View File

@@ -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()
}

44
vendor/fyne.io/systray/systray_notifier_unix.go generated vendored Normal file
View File

@@ -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
}

435
vendor/fyne.io/systray/systray_unix.go generated vendored Normal file
View File

@@ -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, &notifier.StatusNotifierItem_NewIconSignal{
Path: path,
Body: &notifier.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, &notifier.StatusNotifierItem_NewTitleSignal{
Path: path,
Body: &notifier.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
}

View File

@@ -1,17 +1,19 @@
//go:build windows //go:build windows
// +build windows
package systray package systray
import ( import (
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"errors"
"fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"sync" "sync"
"sync/atomic"
"syscall" "syscall"
"unsafe" "unsafe"
@@ -24,6 +26,7 @@ var (
g32 = windows.NewLazySystemDLL("Gdi32.dll") g32 = windows.NewLazySystemDLL("Gdi32.dll")
pCreateCompatibleBitmap = g32.NewProc("CreateCompatibleBitmap") pCreateCompatibleBitmap = g32.NewProc("CreateCompatibleBitmap")
pCreateCompatibleDC = g32.NewProc("CreateCompatibleDC") pCreateCompatibleDC = g32.NewProc("CreateCompatibleDC")
pCreateDIBSection = g32.NewProc("CreateDIBSection")
pDeleteDC = g32.NewProc("DeleteDC") pDeleteDC = g32.NewProc("DeleteDC")
pSelectObject = g32.NewProc("SelectObject") pSelectObject = g32.NewProc("SelectObject")
@@ -39,12 +42,13 @@ var (
pCreateWindowEx = u32.NewProc("CreateWindowExW") pCreateWindowEx = u32.NewProc("CreateWindowExW")
pDefWindowProc = u32.NewProc("DefWindowProcW") pDefWindowProc = u32.NewProc("DefWindowProcW")
pDeleteMenu = u32.NewProc("DeleteMenu") pDeleteMenu = u32.NewProc("DeleteMenu")
pDestroyMenu = u32.NewProc("DestroyMenu")
pRemoveMenu = u32.NewProc("RemoveMenu")
pDestroyWindow = u32.NewProc("DestroyWindow") pDestroyWindow = u32.NewProc("DestroyWindow")
pDispatchMessage = u32.NewProc("DispatchMessageW") pDispatchMessage = u32.NewProc("DispatchMessageW")
pDrawIconEx = u32.NewProc("DrawIconEx") pDrawIconEx = u32.NewProc("DrawIconEx")
pGetCursorPos = u32.NewProc("GetCursorPos") pGetCursorPos = u32.NewProc("GetCursorPos")
pGetDC = u32.NewProc("GetDC") pGetDC = u32.NewProc("GetDC")
pGetMenuItemID = u32.NewProc("GetMenuItemID")
pGetMessage = u32.NewProc("GetMessageW") pGetMessage = u32.NewProc("GetMessageW")
pGetSystemMetrics = u32.NewProc("GetSystemMetrics") pGetSystemMetrics = u32.NewProc("GetSystemMetrics")
pInsertMenuItem = u32.NewProc("InsertMenuItemW") pInsertMenuItem = u32.NewProc("InsertMenuItemW")
@@ -64,6 +68,9 @@ var (
pTranslateMessage = u32.NewProc("TranslateMessage") pTranslateMessage = u32.NewProc("TranslateMessage")
pUnregisterClass = u32.NewProc("UnregisterClassW") pUnregisterClass = u32.NewProc("UnregisterClassW")
pUpdateWindow = u32.NewProc("UpdateWindow") 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. // Contains window class information.
@@ -175,6 +182,30 @@ type point struct {
X, Y int32 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 // Contains information about loaded resources
type winTray struct { type winTray struct {
instance, instance,
@@ -205,11 +236,22 @@ type winTray struct {
wmSystrayMessage, wmSystrayMessage,
wmTaskbarCreated uint32 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. // 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 // Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx
func (t *winTray) setIcon(src string) error { func (t *winTray) setIcon(src string) error {
if !wt.isReady() {
return ErrTrayNotReadyYet
}
const NIF_ICON = 0x00000002 const NIF_ICON = 0x00000002
h, err := t.loadIconFrom(src) h, err := t.loadIconFrom(src)
@@ -229,6 +271,10 @@ func (t *winTray) setIcon(src string) error {
// Sets tooltip on icon. // Sets tooltip on icon.
// Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx // Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx
func (t *winTray) setTooltip(src string) error { func (t *winTray) setTooltip(src string) error {
if !wt.isReady() {
return ErrTrayNotReadyYet
}
const NIF_TIP = 0x00000004 const NIF_TIP = 0x00000004
b, err := windows.UTF16FromString(src) b, err := windows.UTF16FromString(src)
if err != nil { if err != nil {
@@ -244,15 +290,11 @@ func (t *winTray) setTooltip(src string) error {
return t.nid.modify() return t.nid.modify()
} }
var wt winTray var wt = winTray{}
// WindowProc callback function that processes messages sent to a window. // WindowProc callback function that processes messages sent to a window.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx // https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx
func (t *winTray) wndProc( func (t *winTray) wndProc(hWnd windows.Handle, message uint32, wParam, lParam uintptr) (lResult uintptr) {
hWnd windows.Handle,
message uint32,
wParam, lParam uintptr,
) (lResult uintptr) {
const ( const (
WM_RBUTTONUP = 0x0205 WM_RBUTTONUP = 0x0205
WM_LBUTTONUP = 0x0202 WM_LBUTTONUP = 0x0202
@@ -260,11 +302,8 @@ func (t *winTray) wndProc(
WM_ENDSESSION = 0x0016 WM_ENDSESSION = 0x0016
WM_CLOSE = 0x0010 WM_CLOSE = 0x0010
WM_DESTROY = 0x0002 WM_DESTROY = 0x0002
WM_CREATE = 0x0001
) )
switch message { switch message {
case WM_CREATE:
systrayReady()
case WM_COMMAND: case WM_COMMAND:
menuItemId := int32(wParam) menuItemId := int32(wParam)
// https://docs.microsoft.com/en-us/windows/win32/menurc/wm-command#menus // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-command#menus
@@ -284,11 +323,13 @@ func (t *winTray) wndProc(
t.nid.delete() t.nid.delete()
} }
t.muNID.Unlock() t.muNID.Unlock()
systrayExit() runSystrayExit()
case t.wmSystrayMessage: case t.wmSystrayMessage:
switch lParam { switch lParam {
case WM_RBUTTONUP, WM_LBUTTONUP: case WM_LBUTTONUP:
t.showMenu() systrayLeftClick()
case WM_RBUTTONUP:
systrayRightClick()
} }
case t.wmTaskbarCreated: // on explorer.exe restarts case t.wmTaskbarCreated: // on explorer.exe restarts
t.muNID.Lock() t.muNID.Lock()
@@ -498,12 +539,16 @@ func (t *winTray) convertToSubMenu(menuItemId uint32) (windows.Handle, error) {
return menu, nil return menu, nil
} }
func (t *winTray) addOrUpdateMenuItem( // SetRemovalAllowed sets whether a user can remove the systray icon or not.
menuItemId uint32, // This is only supported on macOS.
parentId uint32, func SetRemovalAllowed(allowed bool) {
title string, }
disabled, checked bool,
) error { 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 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
const ( const (
MIIM_FTYPE = 0x00000100 MIIM_FTYPE = 0x00000100
@@ -568,6 +613,14 @@ func (t *winTray) addOrUpdateMenuItem(
} }
if res == 0 { 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) t.addToVisibleItems(parentId, menuItemId)
position := t.getVisibleItemIndex(parentId, menuItemId) position := t.getVisibleItemIndex(parentId, menuItemId)
res, _, err = pInsertMenuItem.Call( res, _, err = pInsertMenuItem.Call(
@@ -589,6 +642,10 @@ func (t *winTray) addOrUpdateMenuItem(
} }
func (t *winTray) addSeparatorMenuItem(menuItemId, parentId uint32) error { 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 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx
const ( const (
MIIM_FTYPE = 0x00000100 MIIM_FTYPE = 0x00000100
@@ -623,8 +680,11 @@ func (t *winTray) addSeparatorMenuItem(menuItemId, parentId uint32) error {
return nil return nil
} }
func (t *winTray) hideMenuItem(menuItemId, parentId uint32) error { func (t *winTray) removeMenuItem(menuItemId, parentId uint32) error {
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms647629(v=vs.85).aspx if !wt.isReady() {
return ErrTrayNotReadyYet
}
const MF_BYCOMMAND = 0x00000000 const MF_BYCOMMAND = 0x00000000
const ERROR_SUCCESS syscall.Errno = 0 const ERROR_SUCCESS syscall.Errno = 0
@@ -644,7 +704,35 @@ func (t *winTray) hideMenuItem(menuItemId, parentId uint32) error {
return nil 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 { func (t *winTray) showMenu() error {
if !wt.isReady() {
return ErrTrayNotReadyYet
}
const ( const (
TPM_BOTTOMALIGN = 0x0020 TPM_BOTTOMALIGN = 0x0020
TPM_LEFTALIGN = 0x0000 TPM_LEFTALIGN = 0x0000
@@ -678,7 +766,7 @@ func (t *winTray) delFromVisibleItems(parent, val uint32) {
visibleItems := t.visibleItems[parent] visibleItems := t.visibleItems[parent]
for i, itemval := range visibleItems { for i, itemval := range visibleItems {
if val == itemval { if val == itemval {
visibleItems = append(visibleItems[:i], visibleItems[i+1:]...) t.visibleItems[parent] = append(visibleItems[:i], visibleItems[i+1:]...)
break 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. // 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 // LoadImage: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx
func (t *winTray) loadIconFrom(src string) (windows.Handle, error) { func (t *winTray) loadIconFrom(src string) (windows.Handle, error) {
if !wt.isReady() {
return 0, ErrTrayNotReadyYet
}
const IMAGE_ICON = 1 // Loads an icon const IMAGE_ICON = 1 // Loads an icon
const LR_LOADFROMFILE = 0x00000010 // Loads the stand-alone image from the file 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 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 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_CXSMICON = 49
const SM_CYSMICON = 50 const SM_CYSMICON = 50
const DI_NORMAL = 0x3 const DI_NORMAL = 0x3
@@ -758,10 +850,7 @@ func (t *winTray) iconToBitmap(hIcon windows.Handle) (windows.Handle, error) {
defer pDeleteDC.Call(hMemDC) defer pDeleteDC.Call(hMemDC)
cx, _, _ := pGetSystemMetrics.Call(SM_CXSMICON) cx, _, _ := pGetSystemMetrics.Call(SM_CXSMICON)
cy, _, _ := pGetSystemMetrics.Call(SM_CYSMICON) cy, _, _ := pGetSystemMetrics.Call(SM_CYSMICON)
hMemBmp, _, err := pCreateCompatibleBitmap.Call(hDC, cx, cy) hMemBmp, err := create32BitHBitmap(hMemDC, int32(cx), int32(cy))
if hMemBmp == 0 {
return 0, err
}
hOriginalBmp, _, _ := pSelectObject.Call(hMemDC, hMemBmp) hOriginalBmp, _, _ := pSelectObject.Call(hMemDC, hMemBmp)
defer pSelectObject.Call(hMemDC, hOriginalBmp) defer pSelectObject.Call(hMemDC, hOriginalBmp)
res, _, err := pDrawIconEx.Call(hMemDC, 0, 0, uintptr(hIcon), cx, cy, 0, uintptr(0), DI_NORMAL) res, _, err := pDrawIconEx.Call(hMemDC, 0, 0, uintptr(hIcon), cx, cy, 0, uintptr(0), DI_NORMAL)
@@ -771,30 +860,75 @@ func (t *winTray) iconToBitmap(hIcon windows.Handle) (windows.Handle, error) {
return windows.Handle(hMemBmp), nil 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() { func registerSystray() {
if err := wt.initInstance(); err != nil { 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 return
} }
if err := wt.createMenu(); err != nil { 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 return
} }
wt.initialized.Store(true)
systrayReady()
} }
func nativeLoop() { var m = &struct {
// Main message pump.
m := &struct {
WindowHandle windows.Handle WindowHandle windows.Handle
Message uint32 Message uint32
Wparam uintptr Wparam uintptr
Lparam uintptr Lparam uintptr
Time uint32 Time uint32
Pt point Pt point
}{} }{}
for {
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) 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 a message other than WM_QUIT, the return value is nonzero.
@@ -803,15 +937,15 @@ func nativeLoop() {
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx
switch int32(ret) { switch int32(ret) {
case -1: case -1:
log.Printf("Error at message loop: %v", err) log.Printf("systray error: message loop failure: %s\n", err)
return return false
case 0: case 0:
return return false
default: default:
pTranslateMessage.Call(uintptr(unsafe.Pointer(m))) pTranslateMessage.Call(uintptr(unsafe.Pointer(m)))
pDispatchMessage.Call(uintptr(unsafe.Pointer(m))) pDispatchMessage.Call(uintptr(unsafe.Pointer(m)))
} }
} return true
} }
func quit() { func quit() {
@@ -823,6 +957,16 @@ func quit() {
0, 0,
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) { func iconBytesToFilePath(iconBytes []byte) (string, error) {
@@ -844,15 +988,25 @@ func iconBytesToFilePath(iconBytes []byte) (string, error) {
func SetIcon(iconBytes []byte) { func SetIcon(iconBytes []byte) {
iconFilePath, err := iconBytesToFilePath(iconBytes) iconFilePath, err := iconBytesToFilePath(iconBytes)
if err != nil { 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 return
} }
if err := wt.setIcon(iconFilePath); err != nil { 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 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 // SetTemplateIcon sets the systray icon as a template icon (on macOS), falling back
// to a regular icon on other platforms. // to a regular icon on other platforms.
// templateIconBytes and iconBytes should be the content of .ico for windows and // 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) { func (item *MenuItem) SetIcon(iconBytes []byte) {
iconFilePath, err := iconBytesToFilePath(iconBytes) iconFilePath, err := iconBytesToFilePath(iconBytes)
if err != nil { 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 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) h, err := wt.loadIconFrom(iconFilePath)
if err != nil { if err != nil {
log.Printf("Unable to load icon from temp file: %v", err) return fmt.Errorf("unable to load icon from file: %s", err)
return
} }
h, err = wt.iconToBitmap(h) h, err = iconToBitmap(h)
if err != nil { if err != nil {
log.Printf("Unable to convert icon to bitmap: %v", err) return fmt.Errorf("unable to convert icon to bitmap: %s", err)
return
} }
wt.muMenuItemIcons.Lock() wt.muMenuItemIcons.Lock()
wt.menuItemIcons[uint32(item.id)] = h wt.menuItemIcons[uint32(item.id)] = h
wt.muMenuItemIcons.Unlock() wt.muMenuItemIcons.Unlock()
err = wt.addOrUpdateMenuItem( err = wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
uint32(item.id),
item.parentId(),
item.title,
item.disabled,
item.checked,
)
if err != nil { if err != nil {
log.Printf("Unable to addOrUpdateMenuItem: %v", err) return fmt.Errorf("unable to addOrUpdateMenuItem: %s", err)
return
} }
return nil
} }
// SetTooltip sets the systray tooltip to display on mouse hover of the tray icon, // SetTooltip sets the systray tooltip to display on mouse hover of the tray icon,
// only available on Mac and Windows. // only available on Mac and Windows.
func SetTooltip(tooltip string) { func SetTooltip(tooltip string) {
if err := wt.setTooltip(tooltip); err != nil { 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 return
} }
} }
func addOrUpdateMenuItem(item *MenuItem) { func addOrUpdateMenuItem(item *MenuItem) {
err := wt.addOrUpdateMenuItem( err := wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
uint32(item.id),
item.parentId(),
item.title,
item.disabled,
item.checked,
)
if err != nil { if err != nil {
log.Printf("Unable to addOrUpdateMenuItem: %v", err) log.Printf("systray error: unable to addOrUpdateMenuItem: %s\n", err)
return return
} }
} }
@@ -941,10 +1091,10 @@ func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes
item.SetIcon(regularIconBytes) item.SetIcon(regularIconBytes)
} }
func addSeparator(id uint32) { func addSeparator(id uint32, parent uint32) {
err := wt.addSeparatorMenuItem(id, 0) err := wt.addSeparatorMenuItem(id, parent)
if err != nil { if err != nil {
log.Printf("Unable to addSeparator: %v", err) log.Printf("systray error: unable to addSeparator: %s\n", err)
return return
} }
} }
@@ -952,7 +1102,15 @@ func addSeparator(id uint32) {
func hideMenuItem(item *MenuItem) { func hideMenuItem(item *MenuItem) {
err := wt.hideMenuItem(uint32(item.id), item.parentId()) err := wt.hideMenuItem(uint32(item.id), item.parentId())
if err != nil { 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 return
} }
} }
@@ -960,3 +1118,30 @@ func hideMenuItem(item *MenuItem) {
func showMenuItem(item *MenuItem) { func showMenuItem(item *MenuItem) {
addOrUpdateMenuItem(item) 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()
}

50
vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md generated vendored Normal file
View File

@@ -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:
```
<subsystem>: <what changed>
<BLANK LINE>
<why this change was made>
<BLANK LINE>
<footer>
```
The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.

25
vendor/github.com/godbus/dbus/v5/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>), Google
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

3
vendor/github.com/godbus/dbus/v5/MAINTAINERS generated vendored Normal file
View File

@@ -0,0 +1,3 @@
Brandon Philips <brandon@ifup.org> (@philips)
Brian Waldon <brian@waldon.cc> (@bcwaldon)
John Southworth <jsouthwo@brocade.com> (@jsouthworth)

46
vendor/github.com/godbus/dbus/v5/README.md generated vendored Normal file
View File

@@ -0,0 +1,46 @@
![Build Status](https://github.com/godbus/dbus/workflows/Go/badge.svg)
dbus
----
dbus is a simple library that implements native Go client bindings for the
D-Bus message bus system.
### Features
* Complete native implementation of the D-Bus message protocol
* Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections)
* Subpackages that help with the introspection / property interfaces
### Installation
This packages requires Go 1.12 or later. It can be installed by running the command below:
```
go get github.com/godbus/dbus/v5
```
### Usage
The complete package documentation and some simple examples are available at
[godoc.org](http://godoc.org/github.com/godbus/dbus). Also, the
[_examples](https://github.com/godbus/dbus/tree/master/_examples) directory
gives a short overview over the basic usage.
#### Projects using godbus
- [fyne](https://github.com/fyne-io/fyne) a cross platform GUI in Go inspired by Material Design.
- [fynedesk](https://github.com/fyne-io/fynedesk) a full desktop environment for Linux/Unix using Fyne.
- [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API.
- [iwd](https://github.com/shibumi/iwd) go bindings for the internet wireless daemon "iwd".
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
- [playerbm](https://github.com/altdesktop/playerbm) a bookmark utility for media players.
Please note that the API is considered unstable for now and may change without
further notice.
### License
go.dbus is available under the Simplified BSD License; see LICENSE for the full
text.
Nearly all of the credit for this library goes to github.com/guelfey/go.dbus.

257
vendor/github.com/godbus/dbus/v5/auth.go generated vendored Normal file
View File

@@ -0,0 +1,257 @@
package dbus
import (
"bufio"
"bytes"
"errors"
"io"
"os"
"strconv"
)
// AuthStatus represents the Status of an authentication mechanism.
type AuthStatus byte
const (
// AuthOk signals that authentication is finished; the next command
// from the server should be an OK.
AuthOk AuthStatus = iota
// AuthContinue signals that additional data is needed; the next command
// from the server should be a DATA.
AuthContinue
// AuthError signals an error; the server sent invalid data or some
// other unexpected thing happened and the current authentication
// process should be aborted.
AuthError
)
type authState byte
const (
waitingForData authState = iota
waitingForOk
waitingForReject
)
// Auth defines the behaviour of an authentication mechanism.
type Auth interface {
// Return the name of the mechanism, the argument to the first AUTH command
// and the next status.
FirstData() (name, resp []byte, status AuthStatus)
// Process the given DATA command, and return the argument to the DATA
// command and the next status. If len(resp) == 0, no DATA command is sent.
HandleData(data []byte) (resp []byte, status AuthStatus)
}
// Auth authenticates the connection, trying the given list of authentication
// mechanisms (in that order). If nil is passed, the EXTERNAL and
// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private
// connections, this method must be called before sending any messages to the
// bus. Auth must not be called on shared connections.
func (conn *Conn) Auth(methods []Auth) error {
if methods == nil {
uid := strconv.Itoa(os.Geteuid())
methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())}
}
in := bufio.NewReader(conn.transport)
err := conn.transport.SendNullByte()
if err != nil {
return err
}
err = authWriteLine(conn.transport, []byte("AUTH"))
if err != nil {
return err
}
s, err := authReadLine(in)
if err != nil {
return err
}
if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) {
return errors.New("dbus: authentication protocol error")
}
s = s[1:]
for _, v := range s {
for _, m := range methods {
if name, _, status := m.FirstData(); bytes.Equal(v, name) {
var ok bool
err = authWriteLine(conn.transport, []byte("AUTH"), v)
if err != nil {
return err
}
switch status {
case AuthOk:
err, ok = conn.tryAuth(m, waitingForOk, in)
case AuthContinue:
err, ok = conn.tryAuth(m, waitingForData, in)
default:
panic("dbus: invalid authentication status")
}
if err != nil {
return err
}
if ok {
if conn.transport.SupportsUnixFDs() {
err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD"))
if err != nil {
return err
}
line, err := authReadLine(in)
if err != nil {
return err
}
switch {
case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")):
conn.EnableUnixFDs()
conn.unixFD = true
case bytes.Equal(line[0], []byte("ERROR")):
default:
return errors.New("dbus: authentication protocol error")
}
}
err = authWriteLine(conn.transport, []byte("BEGIN"))
if err != nil {
return err
}
go conn.inWorker()
return nil
}
}
}
}
return errors.New("dbus: authentication failed")
}
// tryAuth tries to authenticate with m as the mechanism, using state as the
// initial authState and in for reading input. It returns (nil, true) on
// success, (nil, false) on a REJECTED and (someErr, false) if some other
// error occurred.
func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) {
for {
s, err := authReadLine(in)
if err != nil {
return err, false
}
switch {
case state == waitingForData && string(s[0]) == "DATA":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
continue
}
data, status := m.HandleData(s[1])
switch status {
case AuthOk, AuthContinue:
if len(data) != 0 {
err = authWriteLine(conn.transport, []byte("DATA"), data)
if err != nil {
return err, false
}
}
if status == AuthOk {
state = waitingForOk
}
case AuthError:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
}
case state == waitingForData && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForData && string(s[0]) == "ERROR":
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
case state == waitingForData && string(s[0]) == "OK":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
} else {
conn.uuid = string(s[1])
return nil, true
}
case state == waitingForData:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
case state == waitingForOk && string(s[0]) == "OK":
if len(s) != 2 {
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
} else {
conn.uuid = string(s[1])
return nil, true
}
case state == waitingForOk && string(s[0]) == "DATA":
err = authWriteLine(conn.transport, []byte("DATA"))
if err != nil {
return err, false
}
case state == waitingForOk && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForOk && string(s[0]) == "ERROR":
err = authWriteLine(conn.transport, []byte("CANCEL"))
if err != nil {
return err, false
}
state = waitingForReject
case state == waitingForOk:
err = authWriteLine(conn.transport, []byte("ERROR"))
if err != nil {
return err, false
}
case state == waitingForReject && string(s[0]) == "REJECTED":
return nil, false
case state == waitingForReject:
return errors.New("dbus: authentication protocol error"), false
default:
panic("dbus: invalid auth state")
}
}
}
// authReadLine reads a line and separates it into its fields.
func authReadLine(in *bufio.Reader) ([][]byte, error) {
data, err := in.ReadBytes('\n')
if err != nil {
return nil, err
}
data = bytes.TrimSuffix(data, []byte("\r\n"))
return bytes.Split(data, []byte{' '}), nil
}
// authWriteLine writes the given line in the authentication protocol format
// (elements of data separated by a " " and terminated by "\r\n").
func authWriteLine(out io.Writer, data ...[]byte) error {
buf := make([]byte, 0)
for i, v := range data {
buf = append(buf, v...)
if i != len(data)-1 {
buf = append(buf, ' ')
}
}
buf = append(buf, '\r')
buf = append(buf, '\n')
n, err := out.Write(buf)
if err != nil {
return err
}
if n != len(buf) {
return io.ErrUnexpectedEOF
}
return nil
}

16
vendor/github.com/godbus/dbus/v5/auth_anonymous.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
package dbus
// AuthAnonymous returns an Auth that uses the ANONYMOUS mechanism.
func AuthAnonymous() Auth {
return &authAnonymous{}
}
type authAnonymous struct{}
func (a *authAnonymous) FirstData() (name, resp []byte, status AuthStatus) {
return []byte("ANONYMOUS"), nil, AuthOk
}
func (a *authAnonymous) HandleData(data []byte) (resp []byte, status AuthStatus) {
return nil, AuthError
}

26
vendor/github.com/godbus/dbus/v5/auth_external.go generated vendored Normal file
View File

@@ -0,0 +1,26 @@
package dbus
import (
"encoding/hex"
)
// AuthExternal returns an Auth that authenticates as the given user with the
// EXTERNAL mechanism.
func AuthExternal(user string) Auth {
return authExternal{user}
}
// AuthExternal implements the EXTERNAL authentication mechanism.
type authExternal struct {
user string
}
func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) {
b := make([]byte, 2*len(a.user))
hex.Encode(b, []byte(a.user))
return []byte("EXTERNAL"), b, AuthOk
}
func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) {
return nil, AuthError
}

102
vendor/github.com/godbus/dbus/v5/auth_sha1.go generated vendored Normal file
View File

@@ -0,0 +1,102 @@
package dbus
import (
"bufio"
"bytes"
"crypto/rand"
"crypto/sha1"
"encoding/hex"
"os"
)
// AuthCookieSha1 returns an Auth that authenticates as the given user with the
// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home
// directory of the user.
func AuthCookieSha1(user, home string) Auth {
return authCookieSha1{user, home}
}
type authCookieSha1 struct {
user, home string
}
func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) {
b := make([]byte, 2*len(a.user))
hex.Encode(b, []byte(a.user))
return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue
}
func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) {
challenge := make([]byte, len(data)/2)
_, err := hex.Decode(challenge, data)
if err != nil {
return nil, AuthError
}
b := bytes.Split(challenge, []byte{' '})
if len(b) != 3 {
return nil, AuthError
}
context := b[0]
id := b[1]
svchallenge := b[2]
cookie := a.getCookie(context, id)
if cookie == nil {
return nil, AuthError
}
clchallenge := a.generateChallenge()
if clchallenge == nil {
return nil, AuthError
}
hash := sha1.New()
hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'}))
hexhash := make([]byte, 2*hash.Size())
hex.Encode(hexhash, hash.Sum(nil))
data = append(clchallenge, ' ')
data = append(data, hexhash...)
resp := make([]byte, 2*len(data))
hex.Encode(resp, data)
return resp, AuthOk
}
// getCookie searches for the cookie identified by id in context and returns
// the cookie content or nil. (Since HandleData can't return a specific error,
// but only whether an error occurred, this function also doesn't bother to
// return an error.)
func (a authCookieSha1) getCookie(context, id []byte) []byte {
file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context))
if err != nil {
return nil
}
defer file.Close()
rd := bufio.NewReader(file)
for {
line, err := rd.ReadBytes('\n')
if err != nil {
return nil
}
line = line[:len(line)-1]
b := bytes.Split(line, []byte{' '})
if len(b) != 3 {
return nil
}
if bytes.Equal(b[0], id) {
return b[2]
}
}
}
// generateChallenge returns a random, hex-encoded challenge, or nil on error
// (see above).
func (a authCookieSha1) generateChallenge() []byte {
b := make([]byte, 16)
n, err := rand.Read(b)
if err != nil {
return nil
}
if n != 16 {
return nil
}
enc := make([]byte, 32)
hex.Encode(enc, b)
return enc
}

69
vendor/github.com/godbus/dbus/v5/call.go generated vendored Normal file
View File

@@ -0,0 +1,69 @@
package dbus
import (
"context"
"errors"
)
var errSignature = errors.New("dbus: mismatched signature")
// Call represents a pending or completed method call.
type Call struct {
Destination string
Path ObjectPath
Method string
Args []interface{}
// Strobes when the call is complete.
Done chan *Call
// After completion, the error status. If this is non-nil, it may be an
// error message from the peer (with Error as its type) or some other error.
Err error
// Holds the response once the call is done.
Body []interface{}
// ResponseSequence stores the sequence number of the DBus message containing
// the call response (or error). This can be compared to the sequence number
// of other call responses and signals on this connection to determine their
// relative ordering on the underlying DBus connection.
// For errors, ResponseSequence is populated only if the error came from a
// DBusMessage that was received or if there was an error receiving. In case of
// failure to make the call, ResponseSequence will be NoSequence.
ResponseSequence Sequence
// tracks context and canceler
ctx context.Context
ctxCanceler context.CancelFunc
}
func (c *Call) Context() context.Context {
if c.ctx == nil {
return context.Background()
}
return c.ctx
}
func (c *Call) ContextCancel() {
if c.ctxCanceler != nil {
c.ctxCanceler()
}
}
// Store stores the body of the reply into the provided pointers. It returns
// an error if the signatures of the body and retvalues don't match, or if
// the error status is not nil.
func (c *Call) Store(retvalues ...interface{}) error {
if c.Err != nil {
return c.Err
}
return Store(c.Body, retvalues...)
}
func (c *Call) done() {
c.Done <- c
c.ContextCancel()
}

996
vendor/github.com/godbus/dbus/v5/conn.go generated vendored Normal file
View File

@@ -0,0 +1,996 @@
package dbus
import (
"context"
"errors"
"io"
"os"
"strings"
"sync"
)
var (
systemBus *Conn
systemBusLck sync.Mutex
sessionBus *Conn
sessionBusLck sync.Mutex
)
// ErrClosed is the error returned by calls on a closed connection.
var ErrClosed = errors.New("dbus: connection closed by user")
// Conn represents a connection to a message bus (usually, the system or
// session bus).
//
// Connections are either shared or private. Shared connections
// are shared between calls to the functions that return them. As a result,
// the methods Close, Auth and Hello must not be called on them.
//
// Multiple goroutines may invoke methods on a connection simultaneously.
type Conn struct {
transport
ctx context.Context
cancelCtx context.CancelFunc
closeOnce sync.Once
closeErr error
busObj BusObject
unixFD bool
uuid string
handler Handler
signalHandler SignalHandler
serialGen SerialGenerator
inInt Interceptor
outInt Interceptor
auth []Auth
names *nameTracker
calls *callTracker
outHandler *outputHandler
eavesdropped chan<- *Message
eavesdroppedLck sync.Mutex
}
// SessionBus returns a shared connection to the session bus, connecting to it
// if not already done.
func SessionBus() (conn *Conn, err error) {
sessionBusLck.Lock()
defer sessionBusLck.Unlock()
if sessionBus != nil &&
sessionBus.Connected() {
return sessionBus, nil
}
defer func() {
if conn != nil {
sessionBus = conn
}
}()
conn, err = ConnectSessionBus()
return
}
func getSessionBusAddress(autolaunch bool) (string, error) {
if address := os.Getenv("DBUS_SESSION_BUS_ADDRESS"); address != "" && address != "autolaunch:" {
return address, nil
} else if address := tryDiscoverDbusSessionBusAddress(); address != "" {
os.Setenv("DBUS_SESSION_BUS_ADDRESS", address)
return address, nil
}
if !autolaunch {
return "", errors.New("dbus: couldn't determine address of session bus")
}
return getSessionBusPlatformAddress()
}
// SessionBusPrivate returns a new private connection to the session bus.
func SessionBusPrivate(opts ...ConnOption) (*Conn, error) {
address, err := getSessionBusAddress(true)
if err != nil {
return nil, err
}
return Dial(address, opts...)
}
// SessionBusPrivate returns a new private connection to the session bus. If
// the session bus is not already open, do not attempt to launch it.
func SessionBusPrivateNoAutoStartup(opts ...ConnOption) (*Conn, error) {
address, err := getSessionBusAddress(false)
if err != nil {
return nil, err
}
return Dial(address, opts...)
}
// SessionBusPrivate returns a new private connection to the session bus.
//
// Deprecated: use SessionBusPrivate with options instead.
func SessionBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Conn, error) {
return SessionBusPrivate(WithHandler(handler), WithSignalHandler(signalHandler))
}
// SystemBus returns a shared connection to the system bus, connecting to it if
// not already done.
func SystemBus() (conn *Conn, err error) {
systemBusLck.Lock()
defer systemBusLck.Unlock()
if systemBus != nil &&
systemBus.Connected() {
return systemBus, nil
}
defer func() {
if conn != nil {
systemBus = conn
}
}()
conn, err = ConnectSystemBus()
return
}
// ConnectSessionBus connects to the session bus.
func ConnectSessionBus(opts ...ConnOption) (*Conn, error) {
address, err := getSessionBusAddress(true)
if err != nil {
return nil, err
}
return Connect(address, opts...)
}
// ConnectSystemBus connects to the system bus.
func ConnectSystemBus(opts ...ConnOption) (*Conn, error) {
return Connect(getSystemBusPlatformAddress(), opts...)
}
// Connect connects to the given address.
//
// Returned connection is ready to use and doesn't require calling
// Auth and Hello methods to make it usable.
func Connect(address string, opts ...ConnOption) (*Conn, error) {
conn, err := Dial(address, opts...)
if err != nil {
return nil, err
}
if err = conn.Auth(conn.auth); err != nil {
_ = conn.Close()
return nil, err
}
if err = conn.Hello(); err != nil {
_ = conn.Close()
return nil, err
}
return conn, nil
}
// SystemBusPrivate returns a new private connection to the system bus.
// Note: this connection is not ready to use. One must perform Auth and Hello
// on the connection before it is usable.
func SystemBusPrivate(opts ...ConnOption) (*Conn, error) {
return Dial(getSystemBusPlatformAddress(), opts...)
}
// SystemBusPrivateHandler returns a new private connection to the system bus, using the provided handlers.
//
// Deprecated: use SystemBusPrivate with options instead.
func SystemBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Conn, error) {
return SystemBusPrivate(WithHandler(handler), WithSignalHandler(signalHandler))
}
// Dial establishes a new private connection to the message bus specified by address.
func Dial(address string, opts ...ConnOption) (*Conn, error) {
tr, err := getTransport(address)
if err != nil {
return nil, err
}
return newConn(tr, opts...)
}
// DialHandler establishes a new private connection to the message bus specified by address, using the supplied handlers.
//
// Deprecated: use Dial with options instead.
func DialHandler(address string, handler Handler, signalHandler SignalHandler) (*Conn, error) {
return Dial(address, WithHandler(handler), WithSignalHandler(signalHandler))
}
// ConnOption is a connection option.
type ConnOption func(conn *Conn) error
// WithHandler overrides the default handler.
func WithHandler(handler Handler) ConnOption {
return func(conn *Conn) error {
conn.handler = handler
return nil
}
}
// WithSignalHandler overrides the default signal handler.
func WithSignalHandler(handler SignalHandler) ConnOption {
return func(conn *Conn) error {
conn.signalHandler = handler
return nil
}
}
// WithSerialGenerator overrides the default signals generator.
func WithSerialGenerator(gen SerialGenerator) ConnOption {
return func(conn *Conn) error {
conn.serialGen = gen
return nil
}
}
// WithAuth sets authentication methods for the auth conversation.
func WithAuth(methods ...Auth) ConnOption {
return func(conn *Conn) error {
conn.auth = methods
return nil
}
}
// Interceptor intercepts incoming and outgoing messages.
type Interceptor func(msg *Message)
// WithIncomingInterceptor sets the given interceptor for incoming messages.
func WithIncomingInterceptor(interceptor Interceptor) ConnOption {
return func(conn *Conn) error {
conn.inInt = interceptor
return nil
}
}
// WithOutgoingInterceptor sets the given interceptor for outgoing messages.
func WithOutgoingInterceptor(interceptor Interceptor) ConnOption {
return func(conn *Conn) error {
conn.outInt = interceptor
return nil
}
}
// WithContext overrides the default context for the connection.
func WithContext(ctx context.Context) ConnOption {
return func(conn *Conn) error {
conn.ctx = ctx
return nil
}
}
// NewConn creates a new private *Conn from an already established connection.
func NewConn(conn io.ReadWriteCloser, opts ...ConnOption) (*Conn, error) {
return newConn(genericTransport{conn}, opts...)
}
// NewConnHandler creates a new private *Conn from an already established connection, using the supplied handlers.
//
// Deprecated: use NewConn with options instead.
func NewConnHandler(conn io.ReadWriteCloser, handler Handler, signalHandler SignalHandler) (*Conn, error) {
return NewConn(genericTransport{conn}, WithHandler(handler), WithSignalHandler(signalHandler))
}
// newConn creates a new *Conn from a transport.
func newConn(tr transport, opts ...ConnOption) (*Conn, error) {
conn := new(Conn)
conn.transport = tr
for _, opt := range opts {
if err := opt(conn); err != nil {
return nil, err
}
}
if conn.ctx == nil {
conn.ctx = context.Background()
}
conn.ctx, conn.cancelCtx = context.WithCancel(conn.ctx)
conn.calls = newCallTracker()
if conn.handler == nil {
conn.handler = NewDefaultHandler()
}
if conn.signalHandler == nil {
conn.signalHandler = NewDefaultSignalHandler()
}
if conn.serialGen == nil {
conn.serialGen = newSerialGenerator()
}
conn.outHandler = &outputHandler{conn: conn}
conn.names = newNameTracker()
conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
go func() {
<-conn.ctx.Done()
conn.Close()
}()
return conn, nil
}
// BusObject returns the object owned by the bus daemon which handles
// administrative requests.
func (conn *Conn) BusObject() BusObject {
return conn.busObj
}
// Close closes the connection. Any blocked operations will return with errors
// and the channels passed to Eavesdrop and Signal are closed. This method must
// not be called on shared connections.
func (conn *Conn) Close() error {
conn.closeOnce.Do(func() {
conn.outHandler.close()
if term, ok := conn.signalHandler.(Terminator); ok {
term.Terminate()
}
if term, ok := conn.handler.(Terminator); ok {
term.Terminate()
}
conn.eavesdroppedLck.Lock()
if conn.eavesdropped != nil {
close(conn.eavesdropped)
}
conn.eavesdroppedLck.Unlock()
conn.cancelCtx()
conn.closeErr = conn.transport.Close()
})
return conn.closeErr
}
// Context returns the context associated with the connection. The
// context will be cancelled when the connection is closed.
func (conn *Conn) Context() context.Context {
return conn.ctx
}
// Connected returns whether conn is connected
func (conn *Conn) Connected() bool {
return conn.ctx.Err() == nil
}
// Eavesdrop causes conn to send all incoming messages to the given channel
// without further processing. Method replies, errors and signals will not be
// sent to the appropriate channels and method calls will not be handled. If nil
// is passed, the normal behaviour is restored.
//
// The caller has to make sure that ch is sufficiently buffered;
// if a message arrives when a write to ch is not possible, the message is
// discarded.
func (conn *Conn) Eavesdrop(ch chan<- *Message) {
conn.eavesdroppedLck.Lock()
conn.eavesdropped = ch
conn.eavesdroppedLck.Unlock()
}
// getSerial returns an unused serial.
func (conn *Conn) getSerial() uint32 {
return conn.serialGen.GetSerial()
}
// Hello sends the initial org.freedesktop.DBus.Hello call. This method must be
// called after authentication, but before sending any other messages to the
// bus. Hello must not be called for shared connections.
func (conn *Conn) Hello() error {
var s string
err := conn.busObj.Call("org.freedesktop.DBus.Hello", 0).Store(&s)
if err != nil {
return err
}
conn.names.acquireUniqueConnectionName(s)
return nil
}
// inWorker runs in an own goroutine, reading incoming messages from the
// transport and dispatching them appropriately.
func (conn *Conn) inWorker() {
sequenceGen := newSequenceGenerator()
for {
msg, err := conn.ReadMessage()
if err != nil {
if _, ok := err.(InvalidMessageError); !ok {
// Some read error occurred (usually EOF); we can't really do
// anything but to shut down all stuff and returns errors to all
// pending replies.
conn.Close()
conn.calls.finalizeAllWithError(sequenceGen, err)
return
}
// invalid messages are ignored
continue
}
conn.eavesdroppedLck.Lock()
if conn.eavesdropped != nil {
select {
case conn.eavesdropped <- msg:
default:
}
conn.eavesdroppedLck.Unlock()
continue
}
conn.eavesdroppedLck.Unlock()
dest, _ := msg.Headers[FieldDestination].value.(string)
found := dest == "" ||
!conn.names.uniqueNameIsKnown() ||
conn.names.isKnownName(dest)
if !found {
// Eavesdropped a message, but no channel for it is registered.
// Ignore it.
continue
}
if conn.inInt != nil {
conn.inInt(msg)
}
sequence := sequenceGen.next()
switch msg.Type {
case TypeError:
conn.serialGen.RetireSerial(conn.calls.handleDBusError(sequence, msg))
case TypeMethodReply:
conn.serialGen.RetireSerial(conn.calls.handleReply(sequence, msg))
case TypeSignal:
conn.handleSignal(sequence, msg)
case TypeMethodCall:
go conn.handleCall(msg)
}
}
}
func (conn *Conn) handleSignal(sequence Sequence, msg *Message) {
iface := msg.Headers[FieldInterface].value.(string)
member := msg.Headers[FieldMember].value.(string)
// as per http://dbus.freedesktop.org/doc/dbus-specification.html ,
// sender is optional for signals.
sender, _ := msg.Headers[FieldSender].value.(string)
if iface == "org.freedesktop.DBus" && sender == "org.freedesktop.DBus" {
if member == "NameLost" {
// If we lost the name on the bus, remove it from our
// tracking list.
name, ok := msg.Body[0].(string)
if !ok {
panic("Unable to read the lost name")
}
conn.names.loseName(name)
} else if member == "NameAcquired" {
// If we acquired the name on the bus, add it to our
// tracking list.
name, ok := msg.Body[0].(string)
if !ok {
panic("Unable to read the acquired name")
}
conn.names.acquireName(name)
}
}
signal := &Signal{
Sender: sender,
Path: msg.Headers[FieldPath].value.(ObjectPath),
Name: iface + "." + member,
Body: msg.Body,
Sequence: sequence,
}
conn.signalHandler.DeliverSignal(iface, member, signal)
}
// Names returns the list of all names that are currently owned by this
// connection. The slice is always at least one element long, the first element
// being the unique name of the connection.
func (conn *Conn) Names() []string {
return conn.names.listKnownNames()
}
// Object returns the object identified by the given destination name and path.
func (conn *Conn) Object(dest string, path ObjectPath) BusObject {
return &Object{conn, dest, path}
}
func (conn *Conn) sendMessageAndIfClosed(msg *Message, ifClosed func()) {
if msg.serial == 0 {
msg.serial = conn.getSerial()
}
if conn.outInt != nil {
conn.outInt(msg)
}
err := conn.outHandler.sendAndIfClosed(msg, ifClosed)
if err != nil {
conn.handleSendError(msg, err)
} else if msg.Type != TypeMethodCall {
conn.serialGen.RetireSerial(msg.serial)
}
}
func (conn *Conn) handleSendError(msg *Message, err error) {
if msg.Type == TypeMethodCall {
conn.calls.handleSendError(msg, err)
} else if msg.Type == TypeMethodReply {
if _, ok := err.(FormatError); ok {
conn.sendError(err, msg.Headers[FieldDestination].value.(string), msg.Headers[FieldReplySerial].value.(uint32))
}
}
conn.serialGen.RetireSerial(msg.serial)
}
// Send sends the given message to the message bus. You usually don't need to
// use this; use the higher-level equivalents (Call / Go, Emit and Export)
// instead. If msg is a method call and NoReplyExpected is not set, a non-nil
// call is returned and the same value is sent to ch (which must be buffered)
// once the call is complete. Otherwise, ch is ignored and a Call structure is
// returned of which only the Err member is valid.
func (conn *Conn) Send(msg *Message, ch chan *Call) *Call {
return conn.send(context.Background(), msg, ch)
}
// SendWithContext acts like Send but takes a context
func (conn *Conn) SendWithContext(ctx context.Context, msg *Message, ch chan *Call) *Call {
return conn.send(ctx, msg, ch)
}
func (conn *Conn) send(ctx context.Context, msg *Message, ch chan *Call) *Call {
if ctx == nil {
panic("nil context")
}
if ch == nil {
ch = make(chan *Call, 1)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Conn).Send")
}
var call *Call
ctx, canceler := context.WithCancel(ctx)
msg.serial = conn.getSerial()
if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 {
call = new(Call)
call.Destination, _ = msg.Headers[FieldDestination].value.(string)
call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath)
iface, _ := msg.Headers[FieldInterface].value.(string)
member, _ := msg.Headers[FieldMember].value.(string)
call.Method = iface + "." + member
call.Args = msg.Body
call.Done = ch
call.ctx = ctx
call.ctxCanceler = canceler
conn.calls.track(msg.serial, call)
if ctx.Err() != nil {
// short path: don't even send the message if context already cancelled
conn.calls.handleSendError(msg, ctx.Err())
return call
}
go func() {
<-ctx.Done()
conn.calls.handleSendError(msg, ctx.Err())
}()
conn.sendMessageAndIfClosed(msg, func() {
conn.calls.handleSendError(msg, ErrClosed)
canceler()
})
} else {
canceler()
call = &Call{Err: nil, Done: ch}
ch <- call
conn.sendMessageAndIfClosed(msg, func() {
call = &Call{Err: ErrClosed}
})
}
return call
}
// sendError creates an error message corresponding to the parameters and sends
// it to conn.out.
func (conn *Conn) sendError(err error, dest string, serial uint32) {
var e *Error
switch em := err.(type) {
case Error:
e = &em
case *Error:
e = em
case DBusError:
name, body := em.DBusError()
e = NewError(name, body)
default:
e = MakeFailedError(err)
}
msg := new(Message)
msg.Type = TypeError
msg.Headers = make(map[HeaderField]Variant)
if dest != "" {
msg.Headers[FieldDestination] = MakeVariant(dest)
}
msg.Headers[FieldErrorName] = MakeVariant(e.Name)
msg.Headers[FieldReplySerial] = MakeVariant(serial)
msg.Body = e.Body
if len(e.Body) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...))
}
conn.sendMessageAndIfClosed(msg, nil)
}
// sendReply creates a method reply message corresponding to the parameters and
// sends it to conn.out.
func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
msg := new(Message)
msg.Type = TypeMethodReply
msg.Headers = make(map[HeaderField]Variant)
if dest != "" {
msg.Headers[FieldDestination] = MakeVariant(dest)
}
msg.Headers[FieldReplySerial] = MakeVariant(serial)
msg.Body = values
if len(values) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
}
conn.sendMessageAndIfClosed(msg, nil)
}
// AddMatchSignal registers the given match rule to receive broadcast
// signals based on their contents.
func (conn *Conn) AddMatchSignal(options ...MatchOption) error {
return conn.AddMatchSignalContext(context.Background(), options...)
}
// AddMatchSignalContext acts like AddMatchSignal but takes a context.
func (conn *Conn) AddMatchSignalContext(ctx context.Context, options ...MatchOption) error {
options = append([]MatchOption{withMatchType("signal")}, options...)
return conn.busObj.CallWithContext(
ctx,
"org.freedesktop.DBus.AddMatch", 0,
formatMatchOptions(options),
).Store()
}
// RemoveMatchSignal removes the first rule that matches previously registered with AddMatchSignal.
func (conn *Conn) RemoveMatchSignal(options ...MatchOption) error {
return conn.RemoveMatchSignalContext(context.Background(), options...)
}
// RemoveMatchSignalContext acts like RemoveMatchSignal but takes a context.
func (conn *Conn) RemoveMatchSignalContext(ctx context.Context, options ...MatchOption) error {
options = append([]MatchOption{withMatchType("signal")}, options...)
return conn.busObj.CallWithContext(
ctx,
"org.freedesktop.DBus.RemoveMatch", 0,
formatMatchOptions(options),
).Store()
}
// Signal registers the given channel to be passed all received signal messages.
//
// Multiple of these channels can be registered at the same time. The channel is
// closed if the Conn is closed; it should not be closed by the caller before
// RemoveSignal was called on it.
//
// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a
// channel for eavesdropped messages, this channel receives all signals, and
// none of the channels passed to Signal will receive any signals.
//
// Panics if the signal handler is not a `SignalRegistrar`.
func (conn *Conn) Signal(ch chan<- *Signal) {
handler, ok := conn.signalHandler.(SignalRegistrar)
if !ok {
panic("cannot use this method with a non SignalRegistrar handler")
}
handler.AddSignal(ch)
}
// RemoveSignal removes the given channel from the list of the registered channels.
//
// Panics if the signal handler is not a `SignalRegistrar`.
func (conn *Conn) RemoveSignal(ch chan<- *Signal) {
handler, ok := conn.signalHandler.(SignalRegistrar)
if !ok {
panic("cannot use this method with a non SignalRegistrar handler")
}
handler.RemoveSignal(ch)
}
// SupportsUnixFDs returns whether the underlying transport supports passing of
// unix file descriptors. If this is false, method calls containing unix file
// descriptors will return an error and emitted signals containing them will
// not be sent.
func (conn *Conn) SupportsUnixFDs() bool {
return conn.unixFD
}
// Error represents a D-Bus message of type Error.
type Error struct {
Name string
Body []interface{}
}
func NewError(name string, body []interface{}) *Error {
return &Error{name, body}
}
func (e Error) Error() string {
if len(e.Body) >= 1 {
s, ok := e.Body[0].(string)
if ok {
return s
}
}
return e.Name
}
// Signal represents a D-Bus message of type Signal. The name member is given in
// "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost.
type Signal struct {
Sender string
Path ObjectPath
Name string
Body []interface{}
Sequence Sequence
}
// transport is a D-Bus transport.
type transport interface {
// Read and Write raw data (for example, for the authentication protocol).
io.ReadWriteCloser
// Send the initial null byte used for the EXTERNAL mechanism.
SendNullByte() error
// Returns whether this transport supports passing Unix FDs.
SupportsUnixFDs() bool
// Signal the transport that Unix FD passing is enabled for this connection.
EnableUnixFDs()
// Read / send a message, handling things like Unix FDs.
ReadMessage() (*Message, error)
SendMessage(*Message) error
}
var (
transports = make(map[string]func(string) (transport, error))
)
func getTransport(address string) (transport, error) {
var err error
var t transport
addresses := strings.Split(address, ";")
for _, v := range addresses {
i := strings.IndexRune(v, ':')
if i == -1 {
err = errors.New("dbus: invalid bus address (no transport)")
continue
}
f := transports[v[:i]]
if f == nil {
err = errors.New("dbus: invalid bus address (invalid or unsupported transport)")
continue
}
t, err = f(v[i+1:])
if err == nil {
return t, nil
}
}
return nil, err
}
// getKey gets a key from a the list of keys. Returns "" on error / not found...
func getKey(s, key string) string {
for _, keyEqualsValue := range strings.Split(s, ",") {
keyValue := strings.SplitN(keyEqualsValue, "=", 2)
if len(keyValue) == 2 && keyValue[0] == key {
val, err := UnescapeBusAddressValue(keyValue[1])
if err != nil {
// No way to return an error.
return ""
}
return val
}
}
return ""
}
type outputHandler struct {
conn *Conn
sendLck sync.Mutex
closed struct {
isClosed bool
lck sync.RWMutex
}
}
func (h *outputHandler) sendAndIfClosed(msg *Message, ifClosed func()) error {
h.closed.lck.RLock()
defer h.closed.lck.RUnlock()
if h.closed.isClosed {
if ifClosed != nil {
ifClosed()
}
return nil
}
h.sendLck.Lock()
defer h.sendLck.Unlock()
return h.conn.SendMessage(msg)
}
func (h *outputHandler) close() {
h.closed.lck.Lock()
defer h.closed.lck.Unlock()
h.closed.isClosed = true
}
type serialGenerator struct {
lck sync.Mutex
nextSerial uint32
serialUsed map[uint32]bool
}
func newSerialGenerator() *serialGenerator {
return &serialGenerator{
serialUsed: map[uint32]bool{0: true},
nextSerial: 1,
}
}
func (gen *serialGenerator) GetSerial() uint32 {
gen.lck.Lock()
defer gen.lck.Unlock()
n := gen.nextSerial
for gen.serialUsed[n] {
n++
}
gen.serialUsed[n] = true
gen.nextSerial = n + 1
return n
}
func (gen *serialGenerator) RetireSerial(serial uint32) {
gen.lck.Lock()
defer gen.lck.Unlock()
delete(gen.serialUsed, serial)
}
type nameTracker struct {
lck sync.RWMutex
unique string
names map[string]struct{}
}
func newNameTracker() *nameTracker {
return &nameTracker{names: map[string]struct{}{}}
}
func (tracker *nameTracker) acquireUniqueConnectionName(name string) {
tracker.lck.Lock()
defer tracker.lck.Unlock()
tracker.unique = name
}
func (tracker *nameTracker) acquireName(name string) {
tracker.lck.Lock()
defer tracker.lck.Unlock()
tracker.names[name] = struct{}{}
}
func (tracker *nameTracker) loseName(name string) {
tracker.lck.Lock()
defer tracker.lck.Unlock()
delete(tracker.names, name)
}
func (tracker *nameTracker) uniqueNameIsKnown() bool {
tracker.lck.RLock()
defer tracker.lck.RUnlock()
return tracker.unique != ""
}
func (tracker *nameTracker) isKnownName(name string) bool {
tracker.lck.RLock()
defer tracker.lck.RUnlock()
_, ok := tracker.names[name]
return ok || name == tracker.unique
}
func (tracker *nameTracker) listKnownNames() []string {
tracker.lck.RLock()
defer tracker.lck.RUnlock()
out := make([]string, 0, len(tracker.names)+1)
out = append(out, tracker.unique)
for k := range tracker.names {
out = append(out, k)
}
return out
}
type callTracker struct {
calls map[uint32]*Call
lck sync.RWMutex
}
func newCallTracker() *callTracker {
return &callTracker{calls: map[uint32]*Call{}}
}
func (tracker *callTracker) track(sn uint32, call *Call) {
tracker.lck.Lock()
tracker.calls[sn] = call
tracker.lck.Unlock()
}
func (tracker *callTracker) handleReply(sequence Sequence, msg *Message) uint32 {
serial := msg.Headers[FieldReplySerial].value.(uint32)
tracker.lck.RLock()
_, ok := tracker.calls[serial]
tracker.lck.RUnlock()
if ok {
tracker.finalizeWithBody(serial, sequence, msg.Body)
}
return serial
}
func (tracker *callTracker) handleDBusError(sequence Sequence, msg *Message) uint32 {
serial := msg.Headers[FieldReplySerial].value.(uint32)
tracker.lck.RLock()
_, ok := tracker.calls[serial]
tracker.lck.RUnlock()
if ok {
name, _ := msg.Headers[FieldErrorName].value.(string)
tracker.finalizeWithError(serial, sequence, Error{name, msg.Body})
}
return serial
}
func (tracker *callTracker) handleSendError(msg *Message, err error) {
if err == nil {
return
}
tracker.lck.RLock()
_, ok := tracker.calls[msg.serial]
tracker.lck.RUnlock()
if ok {
tracker.finalizeWithError(msg.serial, NoSequence, err)
}
}
// finalize was the only func that did not strobe Done
func (tracker *callTracker) finalize(sn uint32) {
tracker.lck.Lock()
defer tracker.lck.Unlock()
c, ok := tracker.calls[sn]
if ok {
delete(tracker.calls, sn)
c.ContextCancel()
}
}
func (tracker *callTracker) finalizeWithBody(sn uint32, sequence Sequence, body []interface{}) {
tracker.lck.Lock()
c, ok := tracker.calls[sn]
if ok {
delete(tracker.calls, sn)
}
tracker.lck.Unlock()
if ok {
c.Body = body
c.ResponseSequence = sequence
c.done()
}
}
func (tracker *callTracker) finalizeWithError(sn uint32, sequence Sequence, err error) {
tracker.lck.Lock()
c, ok := tracker.calls[sn]
if ok {
delete(tracker.calls, sn)
}
tracker.lck.Unlock()
if ok {
c.Err = err
c.ResponseSequence = sequence
c.done()
}
}
func (tracker *callTracker) finalizeAllWithError(sequenceGen *sequenceGenerator, err error) {
tracker.lck.Lock()
closedCalls := make([]*Call, 0, len(tracker.calls))
for sn := range tracker.calls {
closedCalls = append(closedCalls, tracker.calls[sn])
}
tracker.calls = map[uint32]*Call{}
tracker.lck.Unlock()
for _, call := range closedCalls {
call.Err = err
call.ResponseSequence = sequenceGen.next()
call.done()
}
}

37
vendor/github.com/godbus/dbus/v5/conn_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
package dbus
import (
"errors"
"fmt"
"os"
"os/exec"
)
const defaultSystemBusAddress = "unix:path=/opt/local/var/run/dbus/system_bus_socket"
func getSessionBusPlatformAddress() (string, error) {
cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET")
b, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
if len(b) == 0 {
return "", errors.New("dbus: couldn't determine address of session bus")
}
return "unix:path=" + string(b[:len(b)-1]), nil
}
func getSystemBusPlatformAddress() string {
address := os.Getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET")
if address != "" {
return fmt.Sprintf("unix:path=%s", address)
}
return defaultSystemBusAddress
}
func tryDiscoverDbusSessionBusAddress() string {
return ""
}

90
vendor/github.com/godbus/dbus/v5/conn_other.go generated vendored Normal file
View File

@@ -0,0 +1,90 @@
// +build !darwin
package dbus
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"os/user"
"path"
"strings"
)
var execCommand = exec.Command
func getSessionBusPlatformAddress() (string, error) {
cmd := execCommand("dbus-launch")
b, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
i := bytes.IndexByte(b, '=')
j := bytes.IndexByte(b, '\n')
if i == -1 || j == -1 || i > j {
return "", errors.New("dbus: couldn't determine address of session bus")
}
env, addr := string(b[0:i]), string(b[i+1:j])
os.Setenv(env, addr)
return addr, nil
}
// tryDiscoverDbusSessionBusAddress tries to discover an existing dbus session
// and return the value of its DBUS_SESSION_BUS_ADDRESS.
// It tries different techniques employed by different operating systems,
// returning the first valid address it finds, or an empty string.
//
// * /run/user/<uid>/bus if this exists, it *is* the bus socket. present on
// Ubuntu 18.04
// * /run/user/<uid>/dbus-session: if this exists, it can be parsed for the bus
// address. present on Ubuntu 16.04
//
// See https://dbus.freedesktop.org/doc/dbus-launch.1.html
func tryDiscoverDbusSessionBusAddress() string {
if runtimeDirectory, err := getRuntimeDirectory(); err == nil {
if runUserBusFile := path.Join(runtimeDirectory, "bus"); fileExists(runUserBusFile) {
// if /run/user/<uid>/bus exists, that file itself
// *is* the unix socket, so return its path
return fmt.Sprintf("unix:path=%s", EscapeBusAddressValue(runUserBusFile))
}
if runUserSessionDbusFile := path.Join(runtimeDirectory, "dbus-session"); fileExists(runUserSessionDbusFile) {
// if /run/user/<uid>/dbus-session exists, it's a
// text file // containing the address of the socket, e.g.:
// DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-E1c73yNqrG
if f, err := ioutil.ReadFile(runUserSessionDbusFile); err == nil {
fileContent := string(f)
prefix := "DBUS_SESSION_BUS_ADDRESS="
if strings.HasPrefix(fileContent, prefix) {
address := strings.TrimRight(strings.TrimPrefix(fileContent, prefix), "\n\r")
return address
}
}
}
}
return ""
}
func getRuntimeDirectory() (string, error) {
if currentUser, err := user.Current(); err != nil {
return "", err
} else {
return fmt.Sprintf("/run/user/%s", currentUser.Uid), nil
}
}
func fileExists(filename string) bool {
_, err := os.Stat(filename)
return !os.IsNotExist(err)
}

17
vendor/github.com/godbus/dbus/v5/conn_unix.go generated vendored Normal file
View File

@@ -0,0 +1,17 @@
//+build !windows,!solaris,!darwin
package dbus
import (
"os"
)
const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
func getSystemBusPlatformAddress() string {
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
if address != "" {
return address
}
return defaultSystemBusAddress
}

15
vendor/github.com/godbus/dbus/v5/conn_windows.go generated vendored Normal file
View File

@@ -0,0 +1,15 @@
//+build windows
package dbus
import "os"
const defaultSystemBusAddress = "tcp:host=127.0.0.1,port=12434"
func getSystemBusPlatformAddress() string {
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
if address != "" {
return address
}
return defaultSystemBusAddress
}

430
vendor/github.com/godbus/dbus/v5/dbus.go generated vendored Normal file
View File

@@ -0,0 +1,430 @@
package dbus
import (
"errors"
"fmt"
"reflect"
"strings"
)
var (
byteType = reflect.TypeOf(byte(0))
boolType = reflect.TypeOf(false)
uint8Type = reflect.TypeOf(uint8(0))
int16Type = reflect.TypeOf(int16(0))
uint16Type = reflect.TypeOf(uint16(0))
intType = reflect.TypeOf(int(0))
uintType = reflect.TypeOf(uint(0))
int32Type = reflect.TypeOf(int32(0))
uint32Type = reflect.TypeOf(uint32(0))
int64Type = reflect.TypeOf(int64(0))
uint64Type = reflect.TypeOf(uint64(0))
float64Type = reflect.TypeOf(float64(0))
stringType = reflect.TypeOf("")
signatureType = reflect.TypeOf(Signature{""})
objectPathType = reflect.TypeOf(ObjectPath(""))
variantType = reflect.TypeOf(Variant{Signature{""}, nil})
interfacesType = reflect.TypeOf([]interface{}{})
interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
unixFDType = reflect.TypeOf(UnixFD(0))
unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
errType = reflect.TypeOf((*error)(nil)).Elem()
)
// An InvalidTypeError signals that a value which cannot be represented in the
// D-Bus wire format was passed to a function.
type InvalidTypeError struct {
Type reflect.Type
}
func (e InvalidTypeError) Error() string {
return "dbus: invalid type " + e.Type.String()
}
// Store copies the values contained in src to dest, which must be a slice of
// pointers. It converts slices of interfaces from src to corresponding structs
// in dest. An error is returned if the lengths of src and dest or the types of
// their elements don't match.
func Store(src []interface{}, dest ...interface{}) error {
if len(src) != len(dest) {
return errors.New("dbus.Store: length mismatch")
}
for i := range src {
if err := storeInterfaces(src[i], dest[i]); err != nil {
return err
}
}
return nil
}
func storeInterfaces(src, dest interface{}) error {
return store(reflect.ValueOf(dest), reflect.ValueOf(src))
}
func store(dest, src reflect.Value) error {
if dest.Kind() == reflect.Ptr {
if dest.IsNil() {
dest.Set(reflect.New(dest.Type().Elem()))
}
return store(dest.Elem(), src)
}
switch src.Kind() {
case reflect.Slice:
return storeSlice(dest, src)
case reflect.Map:
return storeMap(dest, src)
default:
return storeBase(dest, src)
}
}
func storeBase(dest, src reflect.Value) error {
return setDest(dest, src)
}
func setDest(dest, src reflect.Value) error {
if !isVariant(src.Type()) && isVariant(dest.Type()) {
//special conversion for dbus.Variant
dest.Set(reflect.ValueOf(MakeVariant(src.Interface())))
return nil
}
if isVariant(src.Type()) && !isVariant(dest.Type()) {
src = getVariantValue(src)
return store(dest, src)
}
if !src.Type().ConvertibleTo(dest.Type()) {
return fmt.Errorf(
"dbus.Store: type mismatch: cannot convert %s to %s",
src.Type(), dest.Type())
}
dest.Set(src.Convert(dest.Type()))
return nil
}
func kindsAreCompatible(dest, src reflect.Type) bool {
switch {
case isVariant(dest):
return true
case dest.Kind() == reflect.Interface:
return true
default:
return dest.Kind() == src.Kind()
}
}
func isConvertibleTo(dest, src reflect.Type) bool {
switch {
case isVariant(dest):
return true
case dest.Kind() == reflect.Interface:
return true
case dest.Kind() == reflect.Slice:
return src.Kind() == reflect.Slice &&
isConvertibleTo(dest.Elem(), src.Elem())
case dest.Kind() == reflect.Ptr:
dest = dest.Elem()
return isConvertibleTo(dest, src)
case dest.Kind() == reflect.Struct:
return src == interfacesType || dest.Kind() == src.Kind()
default:
return src.ConvertibleTo(dest)
}
}
func storeMap(dest, src reflect.Value) error {
switch {
case !kindsAreCompatible(dest.Type(), src.Type()):
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"map: cannot store a value of %s into %s",
src.Type(), dest.Type())
case isVariant(dest.Type()):
return storeMapIntoVariant(dest, src)
case dest.Kind() == reflect.Interface:
return storeMapIntoInterface(dest, src)
case isConvertibleTo(dest.Type().Key(), src.Type().Key()) &&
isConvertibleTo(dest.Type().Elem(), src.Type().Elem()):
return storeMapIntoMap(dest, src)
default:
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"map: cannot convert a value of %s into %s",
src.Type(), dest.Type())
}
}
func storeMapIntoVariant(dest, src reflect.Value) error {
dv := reflect.MakeMap(src.Type())
err := store(dv, src)
if err != nil {
return err
}
return storeBase(dest, dv)
}
func storeMapIntoInterface(dest, src reflect.Value) error {
var dv reflect.Value
if isVariant(src.Type().Elem()) {
//Convert variants to interface{} recursively when converting
//to interface{}
dv = reflect.MakeMap(
reflect.MapOf(src.Type().Key(), interfaceType))
} else {
dv = reflect.MakeMap(src.Type())
}
err := store(dv, src)
if err != nil {
return err
}
return storeBase(dest, dv)
}
func storeMapIntoMap(dest, src reflect.Value) error {
if dest.IsNil() {
dest.Set(reflect.MakeMap(dest.Type()))
}
keys := src.MapKeys()
for _, key := range keys {
dkey := key.Convert(dest.Type().Key())
dval := reflect.New(dest.Type().Elem()).Elem()
err := store(dval, getVariantValue(src.MapIndex(key)))
if err != nil {
return err
}
dest.SetMapIndex(dkey, dval)
}
return nil
}
func storeSlice(dest, src reflect.Value) error {
switch {
case src.Type() == interfacesType && dest.Kind() == reflect.Struct:
//The decoder always decodes structs as slices of interface{}
return storeStruct(dest, src)
case !kindsAreCompatible(dest.Type(), src.Type()):
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"slice: cannot store a value of %s into %s",
src.Type(), dest.Type())
case isVariant(dest.Type()):
return storeSliceIntoVariant(dest, src)
case dest.Kind() == reflect.Interface:
return storeSliceIntoInterface(dest, src)
case isConvertibleTo(dest.Type().Elem(), src.Type().Elem()):
return storeSliceIntoSlice(dest, src)
default:
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"slice: cannot convert a value of %s into %s",
src.Type(), dest.Type())
}
}
func storeStruct(dest, src reflect.Value) error {
if isVariant(dest.Type()) {
return storeBase(dest, src)
}
dval := make([]interface{}, 0, dest.NumField())
dtype := dest.Type()
for i := 0; i < dest.NumField(); i++ {
field := dest.Field(i)
ftype := dtype.Field(i)
if ftype.PkgPath != "" {
continue
}
if ftype.Tag.Get("dbus") == "-" {
continue
}
dval = append(dval, field.Addr().Interface())
}
if src.Len() != len(dval) {
return fmt.Errorf(
"dbus.Store: type mismatch: "+
"destination struct does not have "+
"enough fields need: %d have: %d",
src.Len(), len(dval))
}
return Store(src.Interface().([]interface{}), dval...)
}
func storeSliceIntoVariant(dest, src reflect.Value) error {
dv := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
err := store(dv, src)
if err != nil {
return err
}
return storeBase(dest, dv)
}
func storeSliceIntoInterface(dest, src reflect.Value) error {
var dv reflect.Value
if isVariant(src.Type().Elem()) {
//Convert variants to interface{} recursively when converting
//to interface{}
dv = reflect.MakeSlice(reflect.SliceOf(interfaceType),
src.Len(), src.Cap())
} else {
dv = reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
}
err := store(dv, src)
if err != nil {
return err
}
return storeBase(dest, dv)
}
func storeSliceIntoSlice(dest, src reflect.Value) error {
if dest.IsNil() || dest.Len() < src.Len() {
dest.Set(reflect.MakeSlice(dest.Type(), src.Len(), src.Cap()))
} else if dest.Len() > src.Len() {
dest.Set(dest.Slice(0, src.Len()))
}
for i := 0; i < src.Len(); i++ {
err := store(dest.Index(i), getVariantValue(src.Index(i)))
if err != nil {
return err
}
}
return nil
}
func getVariantValue(in reflect.Value) reflect.Value {
if isVariant(in.Type()) {
return reflect.ValueOf(in.Interface().(Variant).Value())
}
return in
}
func isVariant(t reflect.Type) bool {
return t == variantType
}
// An ObjectPath is an object path as defined by the D-Bus spec.
type ObjectPath string
// IsValid returns whether the object path is valid.
func (o ObjectPath) IsValid() bool {
s := string(o)
if len(s) == 0 {
return false
}
if s[0] != '/' {
return false
}
if s[len(s)-1] == '/' && len(s) != 1 {
return false
}
// probably not used, but technically possible
if s == "/" {
return true
}
split := strings.Split(s[1:], "/")
for _, v := range split {
if len(v) == 0 {
return false
}
for _, c := range v {
if !isMemberChar(c) {
return false
}
}
}
return true
}
// A UnixFD is a Unix file descriptor sent over the wire. See the package-level
// documentation for more information about Unix file descriptor passsing.
type UnixFD int32
// A UnixFDIndex is the representation of a Unix file descriptor in a message.
type UnixFDIndex uint32
// alignment returns the alignment of values of type t.
func alignment(t reflect.Type) int {
switch t {
case variantType:
return 1
case objectPathType:
return 4
case signatureType:
return 1
case interfacesType:
return 4
}
switch t.Kind() {
case reflect.Uint8:
return 1
case reflect.Uint16, reflect.Int16:
return 2
case reflect.Uint, reflect.Int, reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
return 4
case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
return 8
case reflect.Ptr:
return alignment(t.Elem())
}
return 1
}
// isKeyType returns whether t is a valid type for a D-Bus dict.
func isKeyType(t reflect.Type) bool {
switch t.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
reflect.String, reflect.Uint, reflect.Int:
return true
}
return false
}
// isValidInterface returns whether s is a valid name for an interface.
func isValidInterface(s string) bool {
if len(s) == 0 || len(s) > 255 || s[0] == '.' {
return false
}
elem := strings.Split(s, ".")
if len(elem) < 2 {
return false
}
for _, v := range elem {
if len(v) == 0 {
return false
}
if v[0] >= '0' && v[0] <= '9' {
return false
}
for _, c := range v {
if !isMemberChar(c) {
return false
}
}
}
return true
}
// isValidMember returns whether s is a valid name for a member.
func isValidMember(s string) bool {
if len(s) == 0 || len(s) > 255 {
return false
}
i := strings.Index(s, ".")
if i != -1 {
return false
}
if s[0] >= '0' && s[0] <= '9' {
return false
}
for _, c := range s {
if !isMemberChar(c) {
return false
}
}
return true
}
func isMemberChar(c rune) bool {
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') || c == '_'
}

292
vendor/github.com/godbus/dbus/v5/decoder.go generated vendored Normal file
View File

@@ -0,0 +1,292 @@
package dbus
import (
"encoding/binary"
"io"
"reflect"
)
type decoder struct {
in io.Reader
order binary.ByteOrder
pos int
fds []int
}
// newDecoder returns a new decoder that reads values from in. The input is
// expected to be in the given byte order.
func newDecoder(in io.Reader, order binary.ByteOrder, fds []int) *decoder {
dec := new(decoder)
dec.in = in
dec.order = order
dec.fds = fds
return dec
}
// align aligns the input to the given boundary and panics on error.
func (dec *decoder) align(n int) {
if dec.pos%n != 0 {
newpos := (dec.pos + n - 1) & ^(n - 1)
empty := make([]byte, newpos-dec.pos)
if _, err := io.ReadFull(dec.in, empty); err != nil {
panic(err)
}
dec.pos = newpos
}
}
// Calls binary.Read(dec.in, dec.order, v) and panics on read errors.
func (dec *decoder) binread(v interface{}) {
if err := binary.Read(dec.in, dec.order, v); err != nil {
panic(err)
}
}
func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) {
defer func() {
var ok bool
v := recover()
if err, ok = v.(error); ok {
if err == io.EOF || err == io.ErrUnexpectedEOF {
err = FormatError("unexpected EOF")
}
}
}()
vs = make([]interface{}, 0)
s := sig.str
for s != "" {
err, rem := validSingle(s, &depthCounter{})
if err != nil {
return nil, err
}
v := dec.decode(s[:len(s)-len(rem)], 0)
vs = append(vs, v)
s = rem
}
return vs, nil
}
func (dec *decoder) decode(s string, depth int) interface{} {
dec.align(alignment(typeFor(s)))
switch s[0] {
case 'y':
var b [1]byte
if _, err := dec.in.Read(b[:]); err != nil {
panic(err)
}
dec.pos++
return b[0]
case 'b':
i := dec.decode("u", depth).(uint32)
switch {
case i == 0:
return false
case i == 1:
return true
default:
panic(FormatError("invalid value for boolean"))
}
case 'n':
var i int16
dec.binread(&i)
dec.pos += 2
return i
case 'i':
var i int32
dec.binread(&i)
dec.pos += 4
return i
case 'x':
var i int64
dec.binread(&i)
dec.pos += 8
return i
case 'q':
var i uint16
dec.binread(&i)
dec.pos += 2
return i
case 'u':
var i uint32
dec.binread(&i)
dec.pos += 4
return i
case 't':
var i uint64
dec.binread(&i)
dec.pos += 8
return i
case 'd':
var f float64
dec.binread(&f)
dec.pos += 8
return f
case 's':
length := dec.decode("u", depth).(uint32)
b := make([]byte, int(length)+1)
if _, err := io.ReadFull(dec.in, b); err != nil {
panic(err)
}
dec.pos += int(length) + 1
return string(b[:len(b)-1])
case 'o':
return ObjectPath(dec.decode("s", depth).(string))
case 'g':
length := dec.decode("y", depth).(byte)
b := make([]byte, int(length)+1)
if _, err := io.ReadFull(dec.in, b); err != nil {
panic(err)
}
dec.pos += int(length) + 1
sig, err := ParseSignature(string(b[:len(b)-1]))
if err != nil {
panic(err)
}
return sig
case 'v':
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
var variant Variant
sig := dec.decode("g", depth).(Signature)
if len(sig.str) == 0 {
panic(FormatError("variant signature is empty"))
}
err, rem := validSingle(sig.str, &depthCounter{})
if err != nil {
panic(err)
}
if rem != "" {
panic(FormatError("variant signature has multiple types"))
}
variant.sig = sig
variant.value = dec.decode(sig.str, depth+1)
return variant
case 'h':
idx := dec.decode("u", depth).(uint32)
if int(idx) < len(dec.fds) {
return UnixFD(dec.fds[idx])
}
return UnixFDIndex(idx)
case 'a':
if len(s) > 1 && s[1] == '{' {
ksig := s[2:3]
vsig := s[3 : len(s)-1]
v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig)))
if depth >= 63 {
panic(FormatError("input exceeds container depth limit"))
}
length := dec.decode("u", depth).(uint32)
// Even for empty maps, the correct padding must be included
dec.align(8)
spos := dec.pos
for dec.pos < spos+int(length) {
dec.align(8)
if !isKeyType(v.Type().Key()) {
panic(InvalidTypeError{v.Type()})
}
kv := dec.decode(ksig, depth+2)
vv := dec.decode(vsig, depth+2)
v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
}
return v.Interface()
}
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
sig := s[1:]
length := dec.decode("u", depth).(uint32)
// capacity can be determined only for fixed-size element types
var capacity int
if s := sigByteSize(sig); s != 0 {
capacity = int(length) / s
}
v := reflect.MakeSlice(reflect.SliceOf(typeFor(sig)), 0, capacity)
// Even for empty arrays, the correct padding must be included
align := alignment(typeFor(s[1:]))
if len(s) > 1 && s[1] == '(' {
//Special case for arrays of structs
//structs decode as a slice of interface{} values
//but the dbus alignment does not match this
align = 8
}
dec.align(align)
spos := dec.pos
for dec.pos < spos+int(length) {
ev := dec.decode(s[1:], depth+1)
v = reflect.Append(v, reflect.ValueOf(ev))
}
return v.Interface()
case '(':
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
dec.align(8)
v := make([]interface{}, 0)
s = s[1 : len(s)-1]
for s != "" {
err, rem := validSingle(s, &depthCounter{})
if err != nil {
panic(err)
}
ev := dec.decode(s[:len(s)-len(rem)], depth+1)
v = append(v, ev)
s = rem
}
return v
default:
panic(SignatureError{Sig: s})
}
}
// sigByteSize tries to calculates size of the given signature in bytes.
//
// It returns zero when it can't, for example when it contains non-fixed size
// types such as strings, maps and arrays that require reading of the transmitted
// data, for that we would need to implement the unread method for Decoder first.
func sigByteSize(sig string) int {
var total int
for offset := 0; offset < len(sig); {
switch sig[offset] {
case 'y':
total += 1
offset += 1
case 'n', 'q':
total += 2
offset += 1
case 'b', 'i', 'u', 'h':
total += 4
offset += 1
case 'x', 't', 'd':
total += 8
offset += 1
case '(':
i := 1
depth := 1
for i < len(sig[offset:]) && depth != 0 {
if sig[offset+i] == '(' {
depth++
} else if sig[offset+i] == ')' {
depth--
}
i++
}
s := sigByteSize(sig[offset+1 : offset+i-1])
if s == 0 {
return 0
}
total += s
offset += i
default:
return 0
}
}
return total
}
// A FormatError is an error in the wire format.
type FormatError string
func (e FormatError) Error() string {
return "dbus: wire format error: " + string(e)
}

342
vendor/github.com/godbus/dbus/v5/default_handler.go generated vendored Normal file
View File

@@ -0,0 +1,342 @@
package dbus
import (
"bytes"
"reflect"
"strings"
"sync"
)
func newIntrospectIntf(h *defaultHandler) *exportedIntf {
methods := make(map[string]Method)
methods["Introspect"] = exportedMethod{
reflect.ValueOf(func(msg Message) (string, *Error) {
path := msg.Headers[FieldPath].value.(ObjectPath)
return h.introspectPath(path), nil
}),
}
return newExportedIntf(methods, true)
}
//NewDefaultHandler returns an instance of the default
//call handler. This is useful if you want to implement only
//one of the two handlers but not both.
//
// Deprecated: this is the default value, don't use it, it will be unexported.
func NewDefaultHandler() *defaultHandler {
h := &defaultHandler{
objects: make(map[ObjectPath]*exportedObj),
defaultIntf: make(map[string]*exportedIntf),
}
h.defaultIntf["org.freedesktop.DBus.Introspectable"] = newIntrospectIntf(h)
return h
}
type defaultHandler struct {
sync.RWMutex
objects map[ObjectPath]*exportedObj
defaultIntf map[string]*exportedIntf
}
func (h *defaultHandler) PathExists(path ObjectPath) bool {
_, ok := h.objects[path]
return ok
}
func (h *defaultHandler) introspectPath(path ObjectPath) string {
subpath := make(map[string]struct{})
var xml bytes.Buffer
xml.WriteString("<node>")
for obj := range h.objects {
p := string(path)
if p != "/" {
p += "/"
}
if strings.HasPrefix(string(obj), p) {
node_name := strings.Split(string(obj[len(p):]), "/")[0]
subpath[node_name] = struct{}{}
}
}
for s := range subpath {
xml.WriteString("\n\t<node name=\"" + s + "\"/>")
}
xml.WriteString("\n</node>")
return xml.String()
}
func (h *defaultHandler) LookupObject(path ObjectPath) (ServerObject, bool) {
h.RLock()
defer h.RUnlock()
object, ok := h.objects[path]
if ok {
return object, ok
}
// If an object wasn't found for this exact path,
// look for a matching subtree registration
subtreeObject := newExportedObject()
path = path[:strings.LastIndex(string(path), "/")]
for len(path) > 0 {
object, ok = h.objects[path]
if ok {
for name, iface := range object.interfaces {
// Only include this handler if it registered for the subtree
if iface.isFallbackInterface() {
subtreeObject.interfaces[name] = iface
}
}
break
}
path = path[:strings.LastIndex(string(path), "/")]
}
for name, intf := range h.defaultIntf {
if _, exists := subtreeObject.interfaces[name]; exists {
continue
}
subtreeObject.interfaces[name] = intf
}
return subtreeObject, true
}
func (h *defaultHandler) AddObject(path ObjectPath, object *exportedObj) {
h.Lock()
h.objects[path] = object
h.Unlock()
}
func (h *defaultHandler) DeleteObject(path ObjectPath) {
h.Lock()
delete(h.objects, path)
h.Unlock()
}
type exportedMethod struct {
reflect.Value
}
func (m exportedMethod) Call(args ...interface{}) ([]interface{}, error) {
t := m.Type()
params := make([]reflect.Value, len(args))
for i := 0; i < len(args); i++ {
params[i] = reflect.ValueOf(args[i]).Elem()
}
ret := m.Value.Call(params)
var err error
nilErr := false // The reflection will find almost-nils, let's only pass back clean ones!
if t.NumOut() > 0 {
if e, ok := ret[t.NumOut()-1].Interface().(*Error); ok { // godbus *Error
nilErr = ret[t.NumOut()-1].IsNil()
ret = ret[:t.NumOut()-1]
err = e
} else if ret[t.NumOut()-1].Type().Implements(errType) { // Go error
i := ret[t.NumOut()-1].Interface()
if i == nil {
nilErr = ret[t.NumOut()-1].IsNil()
} else {
err = i.(error)
}
ret = ret[:t.NumOut()-1]
}
}
out := make([]interface{}, len(ret))
for i, val := range ret {
out[i] = val.Interface()
}
if nilErr || err == nil {
//concrete type to interface nil is a special case
return out, nil
}
return out, err
}
func (m exportedMethod) NumArguments() int {
return m.Value.Type().NumIn()
}
func (m exportedMethod) ArgumentValue(i int) interface{} {
return reflect.Zero(m.Type().In(i)).Interface()
}
func (m exportedMethod) NumReturns() int {
return m.Value.Type().NumOut()
}
func (m exportedMethod) ReturnValue(i int) interface{} {
return reflect.Zero(m.Type().Out(i)).Interface()
}
func newExportedObject() *exportedObj {
return &exportedObj{
interfaces: make(map[string]*exportedIntf),
}
}
type exportedObj struct {
mu sync.RWMutex
interfaces map[string]*exportedIntf
}
func (obj *exportedObj) LookupInterface(name string) (Interface, bool) {
if name == "" {
return obj, true
}
obj.mu.RLock()
defer obj.mu.RUnlock()
intf, exists := obj.interfaces[name]
return intf, exists
}
func (obj *exportedObj) AddInterface(name string, iface *exportedIntf) {
obj.mu.Lock()
defer obj.mu.Unlock()
obj.interfaces[name] = iface
}
func (obj *exportedObj) DeleteInterface(name string) {
obj.mu.Lock()
defer obj.mu.Unlock()
delete(obj.interfaces, name)
}
func (obj *exportedObj) LookupMethod(name string) (Method, bool) {
obj.mu.RLock()
defer obj.mu.RUnlock()
for _, intf := range obj.interfaces {
method, exists := intf.LookupMethod(name)
if exists {
return method, exists
}
}
return nil, false
}
func (obj *exportedObj) isFallbackInterface() bool {
return false
}
func newExportedIntf(methods map[string]Method, includeSubtree bool) *exportedIntf {
return &exportedIntf{
methods: methods,
includeSubtree: includeSubtree,
}
}
type exportedIntf struct {
methods map[string]Method
// Whether or not this export is for the entire subtree
includeSubtree bool
}
func (obj *exportedIntf) LookupMethod(name string) (Method, bool) {
out, exists := obj.methods[name]
return out, exists
}
func (obj *exportedIntf) isFallbackInterface() bool {
return obj.includeSubtree
}
//NewDefaultSignalHandler returns an instance of the default
//signal handler. This is useful if you want to implement only
//one of the two handlers but not both.
//
// Deprecated: this is the default value, don't use it, it will be unexported.
func NewDefaultSignalHandler() *defaultSignalHandler {
return &defaultSignalHandler{}
}
type defaultSignalHandler struct {
mu sync.RWMutex
closed bool
signals []*signalChannelData
}
func (sh *defaultSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
sh.mu.RLock()
defer sh.mu.RUnlock()
if sh.closed {
return
}
for _, scd := range sh.signals {
scd.deliver(signal)
}
}
func (sh *defaultSignalHandler) Terminate() {
sh.mu.Lock()
defer sh.mu.Unlock()
if sh.closed {
return
}
for _, scd := range sh.signals {
scd.close()
close(scd.ch)
}
sh.closed = true
sh.signals = nil
}
func (sh *defaultSignalHandler) AddSignal(ch chan<- *Signal) {
sh.mu.Lock()
defer sh.mu.Unlock()
if sh.closed {
return
}
sh.signals = append(sh.signals, &signalChannelData{
ch: ch,
done: make(chan struct{}),
})
}
func (sh *defaultSignalHandler) RemoveSignal(ch chan<- *Signal) {
sh.mu.Lock()
defer sh.mu.Unlock()
if sh.closed {
return
}
for i := len(sh.signals) - 1; i >= 0; i-- {
if ch == sh.signals[i].ch {
sh.signals[i].close()
copy(sh.signals[i:], sh.signals[i+1:])
sh.signals[len(sh.signals)-1] = nil
sh.signals = sh.signals[:len(sh.signals)-1]
}
}
}
type signalChannelData struct {
wg sync.WaitGroup
ch chan<- *Signal
done chan struct{}
}
func (scd *signalChannelData) deliver(signal *Signal) {
select {
case scd.ch <- signal:
case <-scd.done:
return
default:
scd.wg.Add(1)
go scd.deferredDeliver(signal)
}
}
func (scd *signalChannelData) deferredDeliver(signal *Signal) {
select {
case scd.ch <- signal:
case <-scd.done:
}
scd.wg.Done()
}
func (scd *signalChannelData) close() {
close(scd.done)
scd.wg.Wait() // wait until all spawned goroutines return
}

71
vendor/github.com/godbus/dbus/v5/doc.go generated vendored Normal file
View File

@@ -0,0 +1,71 @@
/*
Package dbus implements bindings to the D-Bus message bus system.
To use the message bus API, you first need to connect to a bus (usually the
session or system bus). The acquired connection then can be used to call methods
on remote objects and emit or receive signals. Using the Export method, you can
arrange D-Bus methods calls to be directly translated to method calls on a Go
value.
Conversion Rules
For outgoing messages, Go types are automatically converted to the
corresponding D-Bus types. See the official specification at
https://dbus.freedesktop.org/doc/dbus-specification.html#type-system for more
information on the D-Bus type system. The following types are directly encoded
as their respective D-Bus equivalents:
Go type | D-Bus type
------------+-----------
byte | BYTE
bool | BOOLEAN
int16 | INT16
uint16 | UINT16
int | INT32
uint | UINT32
int32 | INT32
uint32 | UINT32
int64 | INT64
uint64 | UINT64
float64 | DOUBLE
string | STRING
ObjectPath | OBJECT_PATH
Signature | SIGNATURE
Variant | VARIANT
interface{} | VARIANT
UnixFDIndex | UNIX_FD
Slices and arrays encode as ARRAYs of their element type.
Maps encode as DICTs, provided that their key type can be used as a key for
a DICT.
Structs other than Variant and Signature encode as a STRUCT containing their
exported fields in order. Fields whose tags contain `dbus:"-"` and unexported
fields will be skipped.
Pointers encode as the value they're pointed to.
Types convertible to one of the base types above will be mapped as the
base type.
Trying to encode any other type or a slice, map or struct containing an
unsupported type will result in an InvalidTypeError.
For incoming messages, the inverse of these rules are used, with the exception
of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces
containing the struct fields in the correct order. The Store function can be
used to convert such values to Go structs.
Unix FD passing
Handling Unix file descriptors deserves special mention. To use them, you should
first check that they are supported on a connection by calling SupportsUnixFDs.
If it returns true, all method of Connection will translate messages containing
UnixFD's to messages that are accompanied by the given file descriptors with the
UnixFD values being substituted by the correct indices. Similarly, the indices
of incoming messages are automatically resolved. It shouldn't be necessary to use
UnixFDIndex.
*/
package dbus

235
vendor/github.com/godbus/dbus/v5/encoder.go generated vendored Normal file
View File

@@ -0,0 +1,235 @@
package dbus
import (
"bytes"
"encoding/binary"
"io"
"reflect"
"strings"
"unicode/utf8"
)
// An encoder encodes values to the D-Bus wire format.
type encoder struct {
out io.Writer
fds []int
order binary.ByteOrder
pos int
}
// NewEncoder returns a new encoder that writes to out in the given byte order.
func newEncoder(out io.Writer, order binary.ByteOrder, fds []int) *encoder {
enc := newEncoderAtOffset(out, 0, order, fds)
return enc
}
// newEncoderAtOffset returns a new encoder that writes to out in the given
// byte order. Specify the offset to initialize pos for proper alignment
// computation.
func newEncoderAtOffset(out io.Writer, offset int, order binary.ByteOrder, fds []int) *encoder {
enc := new(encoder)
enc.out = out
enc.order = order
enc.pos = offset
enc.fds = fds
return enc
}
// Aligns the next output to be on a multiple of n. Panics on write errors.
func (enc *encoder) align(n int) {
pad := enc.padding(0, n)
if pad > 0 {
empty := make([]byte, pad)
if _, err := enc.out.Write(empty); err != nil {
panic(err)
}
enc.pos += pad
}
}
// pad returns the number of bytes of padding, based on current position and additional offset.
// and alignment.
func (enc *encoder) padding(offset, algn int) int {
abs := enc.pos + offset
if abs%algn != 0 {
newabs := (abs + algn - 1) & ^(algn - 1)
return newabs - abs
}
return 0
}
// Calls binary.Write(enc.out, enc.order, v) and panics on write errors.
func (enc *encoder) binwrite(v interface{}) {
if err := binary.Write(enc.out, enc.order, v); err != nil {
panic(err)
}
}
// Encode encodes the given values to the underlying reader. All written values
// are aligned properly as required by the D-Bus spec.
func (enc *encoder) Encode(vs ...interface{}) (err error) {
defer func() {
err, _ = recover().(error)
}()
for _, v := range vs {
enc.encode(reflect.ValueOf(v), 0)
}
return nil
}
// encode encodes the given value to the writer and panics on error. depth holds
// the depth of the container nesting.
func (enc *encoder) encode(v reflect.Value, depth int) {
if depth > 64 {
panic(FormatError("input exceeds depth limitation"))
}
enc.align(alignment(v.Type()))
switch v.Kind() {
case reflect.Uint8:
var b [1]byte
b[0] = byte(v.Uint())
if _, err := enc.out.Write(b[:]); err != nil {
panic(err)
}
enc.pos++
case reflect.Bool:
if v.Bool() {
enc.encode(reflect.ValueOf(uint32(1)), depth)
} else {
enc.encode(reflect.ValueOf(uint32(0)), depth)
}
case reflect.Int16:
enc.binwrite(int16(v.Int()))
enc.pos += 2
case reflect.Uint16:
enc.binwrite(uint16(v.Uint()))
enc.pos += 2
case reflect.Int, reflect.Int32:
if v.Type() == unixFDType {
fd := v.Int()
idx := len(enc.fds)
enc.fds = append(enc.fds, int(fd))
enc.binwrite(uint32(idx))
} else {
enc.binwrite(int32(v.Int()))
}
enc.pos += 4
case reflect.Uint, reflect.Uint32:
enc.binwrite(uint32(v.Uint()))
enc.pos += 4
case reflect.Int64:
enc.binwrite(v.Int())
enc.pos += 8
case reflect.Uint64:
enc.binwrite(v.Uint())
enc.pos += 8
case reflect.Float64:
enc.binwrite(v.Float())
enc.pos += 8
case reflect.String:
str := v.String()
if !utf8.ValidString(str) {
panic(FormatError("input has a not-utf8 char in string"))
}
if strings.IndexByte(str, byte(0)) != -1 {
panic(FormatError("input has a null char('\\000') in string"))
}
if v.Type() == objectPathType {
if !ObjectPath(str).IsValid() {
panic(FormatError("invalid object path"))
}
}
enc.encode(reflect.ValueOf(uint32(len(str))), depth)
b := make([]byte, v.Len()+1)
copy(b, str)
b[len(b)-1] = 0
n, err := enc.out.Write(b)
if err != nil {
panic(err)
}
enc.pos += n
case reflect.Ptr:
enc.encode(v.Elem(), depth)
case reflect.Slice, reflect.Array:
// Lookahead offset: 4 bytes for uint32 length (with alignment),
// plus alignment for elements.
n := enc.padding(0, 4) + 4
offset := enc.pos + n + enc.padding(n, alignment(v.Type().Elem()))
var buf bytes.Buffer
bufenc := newEncoderAtOffset(&buf, offset, enc.order, enc.fds)
for i := 0; i < v.Len(); i++ {
bufenc.encode(v.Index(i), depth+1)
}
if buf.Len() > 1<<26 {
panic(FormatError("input exceeds array size limitation"))
}
enc.fds = bufenc.fds
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
length := buf.Len()
enc.align(alignment(v.Type().Elem()))
if _, err := buf.WriteTo(enc.out); err != nil {
panic(err)
}
enc.pos += length
case reflect.Struct:
switch t := v.Type(); t {
case signatureType:
str := v.Field(0)
enc.encode(reflect.ValueOf(byte(str.Len())), depth)
b := make([]byte, str.Len()+1)
copy(b, str.String())
b[len(b)-1] = 0
n, err := enc.out.Write(b)
if err != nil {
panic(err)
}
enc.pos += n
case variantType:
variant := v.Interface().(Variant)
enc.encode(reflect.ValueOf(variant.sig), depth+1)
enc.encode(reflect.ValueOf(variant.value), depth+1)
default:
for i := 0; i < v.Type().NumField(); i++ {
field := t.Field(i)
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
enc.encode(v.Field(i), depth+1)
}
}
}
case reflect.Map:
// Maps are arrays of structures, so they actually increase the depth by
// 2.
if !isKeyType(v.Type().Key()) {
panic(InvalidTypeError{v.Type()})
}
keys := v.MapKeys()
// Lookahead offset: 4 bytes for uint32 length (with alignment),
// plus 8-byte alignment
n := enc.padding(0, 4) + 4
offset := enc.pos + n + enc.padding(n, 8)
var buf bytes.Buffer
bufenc := newEncoderAtOffset(&buf, offset, enc.order, enc.fds)
for _, k := range keys {
bufenc.align(8)
bufenc.encode(k, depth+2)
bufenc.encode(v.MapIndex(k), depth+2)
}
enc.fds = bufenc.fds
enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
length := buf.Len()
enc.align(8)
if _, err := buf.WriteTo(enc.out); err != nil {
panic(err)
}
enc.pos += length
case reflect.Interface:
enc.encode(reflect.ValueOf(MakeVariant(v.Interface())), depth)
default:
panic(InvalidTypeError{v.Type()})
}
}

84
vendor/github.com/godbus/dbus/v5/escape.go generated vendored Normal file
View File

@@ -0,0 +1,84 @@
package dbus
import "net/url"
// EscapeBusAddressValue implements a requirement to escape the values
// in D-Bus server addresses, as defined by the D-Bus specification at
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses.
func EscapeBusAddressValue(val string) string {
toEsc := strNeedsEscape(val)
if toEsc == 0 {
// Avoid unneeded allocation/copying.
return val
}
// Avoid allocation for short paths.
var buf [64]byte
var out []byte
// Every to-be-escaped byte needs 2 extra bytes.
required := len(val) + 2*toEsc
if required <= len(buf) {
out = buf[:required]
} else {
out = make([]byte, required)
}
j := 0
for i := 0; i < len(val); i++ {
if ch := val[i]; needsEscape(ch) {
// Convert ch to %xx, where xx is hex value.
out[j] = '%'
out[j+1] = hexchar(ch >> 4)
out[j+2] = hexchar(ch & 0x0F)
j += 3
} else {
out[j] = ch
j++
}
}
return string(out)
}
// UnescapeBusAddressValue unescapes values in D-Bus server addresses,
// as defined by the D-Bus specification at
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses.
func UnescapeBusAddressValue(val string) (string, error) {
// Looks like url.PathUnescape does exactly what is required.
return url.PathUnescape(val)
}
// hexchar returns an octal representation of a n, where n < 16.
// For invalid values of n, the function panics.
func hexchar(n byte) byte {
const hex = "0123456789abcdef"
// For n >= len(hex), runtime will panic.
return hex[n]
}
// needsEscape tells if a byte is NOT one of optionally-escaped bytes.
func needsEscape(c byte) bool {
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' {
return false
}
switch c {
case '-', '_', '/', '\\', '.', '*':
return false
}
return true
}
// strNeedsEscape tells how many bytes in the string need escaping.
func strNeedsEscape(val string) int {
count := 0
for i := 0; i < len(val); i++ {
if needsEscape(val[i]) {
count++
}
}
return count
}

463
vendor/github.com/godbus/dbus/v5/export.go generated vendored Normal file
View File

@@ -0,0 +1,463 @@
package dbus
import (
"errors"
"fmt"
"os"
"reflect"
"strings"
)
var (
ErrMsgInvalidArg = Error{
"org.freedesktop.DBus.Error.InvalidArgs",
[]interface{}{"Invalid type / number of args"},
}
ErrMsgNoObject = Error{
"org.freedesktop.DBus.Error.NoSuchObject",
[]interface{}{"No such object"},
}
ErrMsgUnknownMethod = Error{
"org.freedesktop.DBus.Error.UnknownMethod",
[]interface{}{"Unknown / invalid method"},
}
ErrMsgUnknownInterface = Error{
"org.freedesktop.DBus.Error.UnknownInterface",
[]interface{}{"Object does not implement the interface"},
}
)
func MakeNoObjectError(path ObjectPath) Error {
return Error{
"org.freedesktop.DBus.Error.NoSuchObject",
[]interface{}{fmt.Sprintf("No such object '%s'", string(path))},
}
}
func MakeUnknownMethodError(methodName string) Error {
return Error{
"org.freedesktop.DBus.Error.UnknownMethod",
[]interface{}{fmt.Sprintf("Unknown / invalid method '%s'", methodName)},
}
}
func MakeUnknownInterfaceError(ifaceName string) Error {
return Error{
"org.freedesktop.DBus.Error.UnknownInterface",
[]interface{}{fmt.Sprintf("Object does not implement the interface '%s'", ifaceName)},
}
}
func MakeFailedError(err error) *Error {
return &Error{
"org.freedesktop.DBus.Error.Failed",
[]interface{}{err.Error()},
}
}
// Sender is a type which can be used in exported methods to receive the message
// sender.
type Sender string
func computeMethodName(name string, mapping map[string]string) string {
newname, ok := mapping[name]
if ok {
name = newname
}
return name
}
func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
if in == nil {
return nil
}
methods := make(map[string]reflect.Value)
val := reflect.ValueOf(in)
typ := val.Type()
for i := 0; i < typ.NumMethod(); i++ {
methtype := typ.Method(i)
method := val.Method(i)
t := method.Type()
// only track valid methods must return *Error as last arg
// and must be exported
if t.NumOut() == 0 ||
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) ||
methtype.PkgPath != "" {
continue
}
// map names while building table
methods[computeMethodName(methtype.Name, mapping)] = method
}
return methods
}
func getAllMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
if in == nil {
return nil
}
methods := make(map[string]reflect.Value)
val := reflect.ValueOf(in)
typ := val.Type()
for i := 0; i < typ.NumMethod(); i++ {
methtype := typ.Method(i)
method := val.Method(i)
// map names while building table
methods[computeMethodName(methtype.Name, mapping)] = method
}
return methods
}
func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) {
pointers := make([]interface{}, m.NumArguments())
decode := make([]interface{}, 0, len(body))
for i := 0; i < m.NumArguments(); i++ {
tp := reflect.TypeOf(m.ArgumentValue(i))
val := reflect.New(tp)
pointers[i] = val.Interface()
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
val.Elem().SetString(sender)
} else if tp == reflect.TypeOf((*Message)(nil)).Elem() {
val.Elem().Set(reflect.ValueOf(*msg))
} else {
decode = append(decode, pointers[i])
}
}
if len(decode) != len(body) {
return nil, ErrMsgInvalidArg
}
if err := Store(body, decode...); err != nil {
return nil, ErrMsgInvalidArg
}
return pointers, nil
}
func (conn *Conn) decodeArguments(m Method, sender string, msg *Message) ([]interface{}, error) {
if decoder, ok := m.(ArgumentDecoder); ok {
return decoder.DecodeArguments(conn, sender, msg, msg.Body)
}
return standardMethodArgumentDecode(m, sender, msg, msg.Body)
}
// handleCall handles the given method call (i.e. looks if it's one of the
// pre-implemented ones and searches for a corresponding handler if not).
func (conn *Conn) handleCall(msg *Message) {
name := msg.Headers[FieldMember].value.(string)
path := msg.Headers[FieldPath].value.(ObjectPath)
ifaceName, _ := msg.Headers[FieldInterface].value.(string)
sender, hasSender := msg.Headers[FieldSender].value.(string)
serial := msg.serial
if len(name) == 0 {
conn.sendError(ErrMsgUnknownMethod, sender, serial)
}
if ifaceName == "org.freedesktop.DBus.Peer" {
switch name {
case "Ping":
conn.sendReply(sender, serial)
case "GetMachineId":
conn.sendReply(sender, serial, conn.uuid)
default:
conn.sendError(MakeUnknownMethodError(name), sender, serial)
}
return
}
object, ok := conn.handler.LookupObject(path)
if !ok {
conn.sendError(MakeNoObjectError(path), sender, serial)
return
}
iface, exists := object.LookupInterface(ifaceName)
if !exists {
conn.sendError(MakeUnknownInterfaceError(ifaceName), sender, serial)
return
}
m, exists := iface.LookupMethod(name)
if !exists {
conn.sendError(MakeUnknownMethodError(name), sender, serial)
return
}
args, err := conn.decodeArguments(m, sender, msg)
if err != nil {
conn.sendError(err, sender, serial)
return
}
ret, err := m.Call(args...)
if err != nil {
conn.sendError(err, sender, serial)
return
}
if msg.Flags&FlagNoReplyExpected == 0 {
reply := new(Message)
reply.Type = TypeMethodReply
reply.Headers = make(map[HeaderField]Variant)
if hasSender {
reply.Headers[FieldDestination] = msg.Headers[FieldSender]
}
reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
reply.Body = make([]interface{}, len(ret))
for i := 0; i < len(ret); i++ {
reply.Body[i] = ret[i]
}
reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
if err := reply.IsValid(); err != nil {
fmt.Fprintf(os.Stderr, "dbus: dropping invalid reply to %s.%s on obj %s: %s\n", ifaceName, name, path, err)
} else {
conn.sendMessageAndIfClosed(reply, nil)
}
}
}
// Emit emits the given signal on the message bus. The name parameter must be
// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error {
i := strings.LastIndex(name, ".")
if i == -1 {
return errors.New("dbus: invalid method name")
}
iface := name[:i]
member := name[i+1:]
msg := new(Message)
msg.Type = TypeSignal
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldInterface] = MakeVariant(iface)
msg.Headers[FieldMember] = MakeVariant(member)
msg.Headers[FieldPath] = MakeVariant(path)
msg.Body = values
if len(values) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
}
if err := msg.IsValid(); err != nil {
return err
}
var closed bool
conn.sendMessageAndIfClosed(msg, func() {
closed = true
})
if closed {
return ErrClosed
}
return nil
}
// Export registers the given value to be exported as an object on the
// message bus.
//
// If a method call on the given path and interface is received, an exported
// method with the same name is called with v as the receiver if the
// parameters match and the last return value is of type *Error. If this
// *Error is not nil, it is sent back to the caller as an error.
// Otherwise, a method reply is sent with the other return values as its body.
//
// Any parameters with the special type Sender are set to the sender of the
// dbus message when the method is called. Parameters of this type do not
// contribute to the dbus signature of the method (i.e. the method is exposed
// as if the parameters of type Sender were not there).
//
// Similarly, any parameters with the type Message are set to the raw message
// received on the bus. Again, parameters of this type do not contribute to the
// dbus signature of the method.
//
// Every method call is executed in a new goroutine, so the method may be called
// in multiple goroutines at once.
//
// Method calls on the interface org.freedesktop.DBus.Peer will be automatically
// handled for every object.
//
// Passing nil as the first parameter will cause conn to cease handling calls on
// the given combination of path and interface.
//
// Export returns an error if path is not a valid path name.
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
return conn.ExportWithMap(v, nil, path, iface)
}
// ExportAll registers all exported methods defined by the given object on
// the message bus.
//
// Unlike Export there is no requirement to have the last parameter as type
// *Error. If you want to be able to return error then you can append an error
// type parameter to your method signature. If the error returned is not nil,
// it is sent back to the caller as an error. Otherwise, a method reply is
// sent with the other return values as its body.
func (conn *Conn) ExportAll(v interface{}, path ObjectPath, iface string) error {
return conn.export(getAllMethods(v, nil), path, iface, false)
}
// ExportWithMap works exactly like Export but provides the ability to remap
// method names (e.g. export a lower-case method).
//
// The keys in the map are the real method names (exported on the struct), and
// the values are the method names to be exported on DBus.
func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
return conn.export(getMethods(v, mapping), path, iface, false)
}
// ExportSubtree works exactly like Export but registers the given value for
// an entire subtree rather under the root path provided.
//
// In order to make this useful, one parameter in each of the value's exported
// methods should be a Message, in which case it will contain the raw message
// (allowing one to get access to the path that caused the method to be called).
//
// Note that more specific export paths take precedence over less specific. For
// example, a method call using the ObjectPath /foo/bar/baz will call a method
// exported on /foo/bar before a method exported on /foo.
func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error {
return conn.ExportSubtreeWithMap(v, nil, path, iface)
}
// ExportSubtreeWithMap works exactly like ExportSubtree but provides the
// ability to remap method names (e.g. export a lower-case method).
//
// The keys in the map are the real method names (exported on the struct), and
// the values are the method names to be exported on DBus.
func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
return conn.export(getMethods(v, mapping), path, iface, true)
}
// ExportMethodTable like Export registers the given methods as an object
// on the message bus. Unlike Export the it uses a method table to define
// the object instead of a native go object.
//
// The method table is a map from method name to function closure
// representing the method. This allows an object exported on the bus to not
// necessarily be a native go object. It can be useful for generating exposed
// methods on the fly.
//
// Any non-function objects in the method table are ignored.
func (conn *Conn) ExportMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
return conn.exportMethodTable(methods, path, iface, false)
}
// Like ExportSubtree, but with the same caveats as ExportMethodTable.
func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
return conn.exportMethodTable(methods, path, iface, true)
}
func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error {
var out map[string]reflect.Value
if methods != nil {
out = make(map[string]reflect.Value)
for name, method := range methods {
rval := reflect.ValueOf(method)
if rval.Kind() != reflect.Func {
continue
}
t := rval.Type()
// only track valid methods must return *Error as last arg
if t.NumOut() == 0 ||
t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) {
continue
}
out[name] = rval
}
}
return conn.export(out, path, iface, includeSubtree)
}
func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) error {
if h.PathExists(path) {
obj := h.objects[path]
obj.DeleteInterface(iface)
if len(obj.interfaces) == 0 {
h.DeleteObject(path)
}
}
return nil
}
// export is the worker function for all exports/registrations.
func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error {
h, ok := conn.handler.(*defaultHandler)
if !ok {
return fmt.Errorf(
`dbus: export only allowed on the default handler. Received: %T"`,
conn.handler)
}
if !path.IsValid() {
return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
}
// Remove a previous export if the interface is nil
if methods == nil {
return conn.unexport(h, path, iface)
}
// If this is the first handler for this path, make a new map to hold all
// handlers for this path.
if !h.PathExists(path) {
h.AddObject(path, newExportedObject())
}
exportedMethods := make(map[string]Method)
for name, method := range methods {
exportedMethods[name] = exportedMethod{method}
}
// Finally, save this handler
obj := h.objects[path]
obj.AddInterface(iface, newExportedIntf(exportedMethods, includeSubtree))
return nil
}
// ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response.
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
var r uint32
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
if err != nil {
return 0, err
}
return ReleaseNameReply(r), nil
}
// RequestName calls org.freedesktop.DBus.RequestName and awaits a response.
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
var r uint32
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
if err != nil {
return 0, err
}
return RequestNameReply(r), nil
}
// ReleaseNameReply is the reply to a ReleaseName call.
type ReleaseNameReply uint32
const (
ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
ReleaseNameReplyNonExistent
ReleaseNameReplyNotOwner
)
// RequestNameFlags represents the possible flags for a RequestName call.
type RequestNameFlags uint32
const (
NameFlagAllowReplacement RequestNameFlags = 1 << iota
NameFlagReplaceExisting
NameFlagDoNotQueue
)
// RequestNameReply is the reply to a RequestName call.
type RequestNameReply uint32
const (
RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
RequestNameReplyInQueue
RequestNameReplyExists
RequestNameReplyAlreadyOwner
)

25
vendor/github.com/godbus/dbus/v5/homedir.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
package dbus
import (
"os"
"os/user"
)
// Get returns the home directory of the current user, which is usually the
// value of HOME environment variable. In case it is not set or empty, os/user
// package is used.
//
// If linking statically with cgo enabled against glibc, make sure the
// osusergo build tag is used.
//
// If needing to do nss lookups, do not disable cgo or set osusergo.
func getHomeDir() string {
homeDir := os.Getenv("HOME")
if homeDir != "" {
return homeDir
}
if u, err := user.Current(); err == nil {
return u.HomeDir
}
return "/"
}

28
vendor/github.com/godbus/dbus/v5/introspect/call.go generated vendored Normal file
View File

@@ -0,0 +1,28 @@
package introspect
import (
"encoding/xml"
"strings"
"github.com/godbus/dbus/v5"
)
// Call calls org.freedesktop.Introspectable.Introspect on a remote object
// and returns the introspection data.
func Call(o dbus.BusObject) (*Node, error) {
var xmldata string
var node Node
err := o.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&xmldata)
if err != nil {
return nil, err
}
err = xml.NewDecoder(strings.NewReader(xmldata)).Decode(&node)
if err != nil {
return nil, err
}
if node.Name == "" {
node.Name = string(o.Path())
}
return &node, nil
}

View File

@@ -0,0 +1,86 @@
// Package introspect provides some utilities for dealing with the DBus
// introspection format.
package introspect
import "encoding/xml"
// The introspection data for the org.freedesktop.DBus.Introspectable interface.
var IntrospectData = Interface{
Name: "org.freedesktop.DBus.Introspectable",
Methods: []Method{
{
Name: "Introspect",
Args: []Arg{
{"out", "s", "out"},
},
},
},
}
// XML document type declaration of the introspection format version 1.0
const IntrospectDeclarationString = `
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
`
// The introspection data for the org.freedesktop.DBus.Introspectable interface,
// as a string.
const IntrospectDataString = `
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg name="out" direction="out" type="s"/>
</method>
</interface>
`
// Node is the root element of an introspection.
type Node struct {
XMLName xml.Name `xml:"node"`
Name string `xml:"name,attr,omitempty"`
Interfaces []Interface `xml:"interface"`
Children []Node `xml:"node,omitempty"`
}
// Interface describes a DBus interface that is available on the message bus.
type Interface struct {
Name string `xml:"name,attr"`
Methods []Method `xml:"method"`
Signals []Signal `xml:"signal"`
Properties []Property `xml:"property"`
Annotations []Annotation `xml:"annotation"`
}
// Method describes a Method on an Interface as returned by an introspection.
type Method struct {
Name string `xml:"name,attr"`
Args []Arg `xml:"arg"`
Annotations []Annotation `xml:"annotation"`
}
// Signal describes a Signal emitted on an Interface.
type Signal struct {
Name string `xml:"name,attr"`
Args []Arg `xml:"arg"`
Annotations []Annotation `xml:"annotation"`
}
// Property describes a property of an Interface.
type Property struct {
Name string `xml:"name,attr"`
Type string `xml:"type,attr"`
Access string `xml:"access,attr"`
Annotations []Annotation `xml:"annotation"`
}
// Arg represents an argument of a method or a signal.
type Arg struct {
Name string `xml:"name,attr,omitempty"`
Type string `xml:"type,attr"`
Direction string `xml:"direction,attr,omitempty"`
}
// Annotation is an annotation in the introspection format.
type Annotation struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}

View File

@@ -0,0 +1,77 @@
package introspect
import (
"encoding/xml"
"reflect"
"strings"
"github.com/godbus/dbus/v5"
)
// Introspectable implements org.freedesktop.Introspectable.
//
// You can create it by converting the XML-formatted introspection data from a
// string to an Introspectable or call NewIntrospectable with a Node. Then,
// export it as org.freedesktop.Introspectable on you object.
type Introspectable string
// NewIntrospectable returns an Introspectable that returns the introspection
// data that corresponds to the given Node. If n.Interfaces doesn't contain the
// data for org.freedesktop.DBus.Introspectable, it is added automatically.
func NewIntrospectable(n *Node) Introspectable {
found := false
for _, v := range n.Interfaces {
if v.Name == "org.freedesktop.DBus.Introspectable" {
found = true
break
}
}
if !found {
n.Interfaces = append(n.Interfaces, IntrospectData)
}
b, err := xml.Marshal(n)
if err != nil {
panic(err)
}
return Introspectable(strings.TrimSpace(IntrospectDeclarationString) + string(b))
}
// Introspect implements org.freedesktop.Introspectable.Introspect.
func (i Introspectable) Introspect() (string, *dbus.Error) {
return string(i), nil
}
// Methods returns the description of the methods of v. This can be used to
// create a Node which can be passed to NewIntrospectable.
func Methods(v interface{}) []Method {
t := reflect.TypeOf(v)
ms := make([]Method, 0, t.NumMethod())
for i := 0; i < t.NumMethod(); i++ {
if t.Method(i).PkgPath != "" {
continue
}
mt := t.Method(i).Type
if mt.NumOut() == 0 ||
mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{}) {
continue
}
var m Method
m.Name = t.Method(i).Name
m.Args = make([]Arg, 0, mt.NumIn()+mt.NumOut()-2)
for j := 1; j < mt.NumIn(); j++ {
if mt.In(j) != reflect.TypeOf((*dbus.Sender)(nil)).Elem() &&
mt.In(j) != reflect.TypeOf((*dbus.Message)(nil)).Elem() {
arg := Arg{"", dbus.SignatureOfType(mt.In(j)).String(), "in"}
m.Args = append(m.Args, arg)
}
}
for j := 0; j < mt.NumOut()-1; j++ {
arg := Arg{"", dbus.SignatureOfType(mt.Out(j)).String(), "out"}
m.Args = append(m.Args, arg)
}
m.Annotations = make([]Annotation, 0)
ms = append(ms, m)
}
return ms
}

89
vendor/github.com/godbus/dbus/v5/match.go generated vendored Normal file
View File

@@ -0,0 +1,89 @@
package dbus
import (
"strconv"
"strings"
)
// MatchOption specifies option for dbus routing match rule. Options can be constructed with WithMatch* helpers.
// For full list of available options consult
// https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
type MatchOption struct {
key string
value string
}
func formatMatchOptions(options []MatchOption) string {
items := make([]string, 0, len(options))
for _, option := range options {
items = append(items, option.key+"='"+option.value+"'")
}
return strings.Join(items, ",")
}
// WithMatchOption creates match option with given key and value
func WithMatchOption(key, value string) MatchOption {
return MatchOption{key, value}
}
// doesn't make sense to export this option because clients can only
// subscribe to messages with signal type.
func withMatchType(typ string) MatchOption {
return WithMatchOption("type", typ)
}
// WithMatchSender sets sender match option.
func WithMatchSender(sender string) MatchOption {
return WithMatchOption("sender", sender)
}
// WithMatchSender sets interface match option.
func WithMatchInterface(iface string) MatchOption {
return WithMatchOption("interface", iface)
}
// WithMatchMember sets member match option.
func WithMatchMember(member string) MatchOption {
return WithMatchOption("member", member)
}
// WithMatchObjectPath creates match option that filters events based on given path
func WithMatchObjectPath(path ObjectPath) MatchOption {
return WithMatchOption("path", string(path))
}
// WithMatchPathNamespace sets path_namespace match option.
func WithMatchPathNamespace(namespace ObjectPath) MatchOption {
return WithMatchOption("path_namespace", string(namespace))
}
// WithMatchDestination sets destination match option.
func WithMatchDestination(destination string) MatchOption {
return WithMatchOption("destination", destination)
}
// WithMatchArg sets argN match option, range of N is 0 to 63.
func WithMatchArg(argIdx int, value string) MatchOption {
if argIdx < 0 || argIdx > 63 {
panic("range of argument index is 0 to 63")
}
return WithMatchOption("arg"+strconv.Itoa(argIdx), value)
}
// WithMatchArgPath sets argN path match option, range of N is 0 to 63.
func WithMatchArgPath(argIdx int, path string) MatchOption {
if argIdx < 0 || argIdx > 63 {
panic("range of argument index is 0 to 63")
}
return WithMatchOption("arg"+strconv.Itoa(argIdx)+"path", path)
}
// WithMatchArg0Namespace sets arg0namespace match option.
func WithMatchArg0Namespace(arg0Namespace string) MatchOption {
return WithMatchOption("arg0namespace", arg0Namespace)
}
// WithMatchEavesdrop sets eavesdrop match option.
func WithMatchEavesdrop(eavesdrop bool) MatchOption {
return WithMatchOption("eavesdrop", strconv.FormatBool(eavesdrop))
}

390
vendor/github.com/godbus/dbus/v5/message.go generated vendored Normal file
View File

@@ -0,0 +1,390 @@
package dbus
import (
"bytes"
"encoding/binary"
"errors"
"io"
"reflect"
"strconv"
)
const protoVersion byte = 1
// Flags represents the possible flags of a D-Bus message.
type Flags byte
const (
// FlagNoReplyExpected signals that the message is not expected to generate
// a reply. If this flag is set on outgoing messages, any possible reply
// will be discarded.
FlagNoReplyExpected Flags = 1 << iota
// FlagNoAutoStart signals that the message bus should not automatically
// start an application when handling this message.
FlagNoAutoStart
// FlagAllowInteractiveAuthorization may be set on a method call
// message to inform the receiving side that the caller is prepared
// to wait for interactive authorization, which might take a
// considerable time to complete. For instance, if this flag is set,
// it would be appropriate to query the user for passwords or
// confirmation via Polkit or a similar framework.
FlagAllowInteractiveAuthorization
)
// Type represents the possible types of a D-Bus message.
type Type byte
const (
TypeMethodCall Type = 1 + iota
TypeMethodReply
TypeError
TypeSignal
typeMax
)
func (t Type) String() string {
switch t {
case TypeMethodCall:
return "method call"
case TypeMethodReply:
return "reply"
case TypeError:
return "error"
case TypeSignal:
return "signal"
}
return "invalid"
}
// HeaderField represents the possible byte codes for the headers
// of a D-Bus message.
type HeaderField byte
const (
FieldPath HeaderField = 1 + iota
FieldInterface
FieldMember
FieldErrorName
FieldReplySerial
FieldDestination
FieldSender
FieldSignature
FieldUnixFDs
fieldMax
)
// An InvalidMessageError describes the reason why a D-Bus message is regarded as
// invalid.
type InvalidMessageError string
func (e InvalidMessageError) Error() string {
return "dbus: invalid message: " + string(e)
}
// fieldType are the types of the various header fields.
var fieldTypes = [fieldMax]reflect.Type{
FieldPath: objectPathType,
FieldInterface: stringType,
FieldMember: stringType,
FieldErrorName: stringType,
FieldReplySerial: uint32Type,
FieldDestination: stringType,
FieldSender: stringType,
FieldSignature: signatureType,
FieldUnixFDs: uint32Type,
}
// requiredFields lists the header fields that are required by the different
// message types.
var requiredFields = [typeMax][]HeaderField{
TypeMethodCall: {FieldPath, FieldMember},
TypeMethodReply: {FieldReplySerial},
TypeError: {FieldErrorName, FieldReplySerial},
TypeSignal: {FieldPath, FieldInterface, FieldMember},
}
// Message represents a single D-Bus message.
type Message struct {
Type
Flags
Headers map[HeaderField]Variant
Body []interface{}
serial uint32
}
type header struct {
Field byte
Variant
}
func DecodeMessageWithFDs(rd io.Reader, fds []int) (msg *Message, err error) {
var order binary.ByteOrder
var hlength, length uint32
var typ, flags, proto byte
var headers []header
b := make([]byte, 1)
_, err = rd.Read(b)
if err != nil {
return
}
switch b[0] {
case 'l':
order = binary.LittleEndian
case 'B':
order = binary.BigEndian
default:
return nil, InvalidMessageError("invalid byte order")
}
dec := newDecoder(rd, order, fds)
dec.pos = 1
msg = new(Message)
vs, err := dec.Decode(Signature{"yyyuu"})
if err != nil {
return nil, err
}
if err = Store(vs, &typ, &flags, &proto, &length, &msg.serial); err != nil {
return nil, err
}
msg.Type = Type(typ)
msg.Flags = Flags(flags)
// get the header length separately because we need it later
b = make([]byte, 4)
_, err = io.ReadFull(rd, b)
if err != nil {
return nil, err
}
binary.Read(bytes.NewBuffer(b), order, &hlength)
if hlength+length+16 > 1<<27 {
return nil, InvalidMessageError("message is too long")
}
dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order, fds)
dec.pos = 12
vs, err = dec.Decode(Signature{"a(yv)"})
if err != nil {
return nil, err
}
if err = Store(vs, &headers); err != nil {
return nil, err
}
msg.Headers = make(map[HeaderField]Variant)
for _, v := range headers {
msg.Headers[HeaderField(v.Field)] = v.Variant
}
dec.align(8)
body := make([]byte, int(length))
if length != 0 {
_, err := io.ReadFull(rd, body)
if err != nil {
return nil, err
}
}
if err = msg.IsValid(); err != nil {
return nil, err
}
sig, _ := msg.Headers[FieldSignature].value.(Signature)
if sig.str != "" {
buf := bytes.NewBuffer(body)
dec = newDecoder(buf, order, fds)
vs, err := dec.Decode(sig)
if err != nil {
return nil, err
}
msg.Body = vs
}
return
}
// DecodeMessage tries to decode a single message in the D-Bus wire format
// from the given reader. The byte order is figured out from the first byte.
// The possibly returned error can be an error of the underlying reader, an
// InvalidMessageError or a FormatError.
func DecodeMessage(rd io.Reader) (msg *Message, err error) {
return DecodeMessageWithFDs(rd, make([]int, 0))
}
type nullwriter struct{}
func (nullwriter) Write(p []byte) (cnt int, err error) {
return len(p), nil
}
func (msg *Message) CountFds() (int, error) {
if len(msg.Body) == 0 {
return 0, nil
}
enc := newEncoder(nullwriter{}, nativeEndian, make([]int, 0))
err := enc.Encode(msg.Body...)
return len(enc.fds), err
}
func (msg *Message) EncodeToWithFDs(out io.Writer, order binary.ByteOrder) (fds []int, err error) {
if err := msg.validateHeader(); err != nil {
return nil, err
}
var vs [7]interface{}
switch order {
case binary.LittleEndian:
vs[0] = byte('l')
case binary.BigEndian:
vs[0] = byte('B')
default:
return nil, errors.New("dbus: invalid byte order")
}
body := new(bytes.Buffer)
fds = make([]int, 0)
enc := newEncoder(body, order, fds)
if len(msg.Body) != 0 {
err = enc.Encode(msg.Body...)
if err != nil {
return
}
}
vs[1] = msg.Type
vs[2] = msg.Flags
vs[3] = protoVersion
vs[4] = uint32(len(body.Bytes()))
vs[5] = msg.serial
headers := make([]header, 0, len(msg.Headers))
for k, v := range msg.Headers {
headers = append(headers, header{byte(k), v})
}
vs[6] = headers
var buf bytes.Buffer
enc = newEncoder(&buf, order, enc.fds)
err = enc.Encode(vs[:]...)
if err != nil {
return
}
enc.align(8)
body.WriteTo(&buf)
if buf.Len() > 1<<27 {
return make([]int, 0), InvalidMessageError("message is too long")
}
if _, err := buf.WriteTo(out); err != nil {
return make([]int, 0), err
}
return enc.fds, nil
}
// EncodeTo encodes and sends a message to the given writer. The byte order must
// be either binary.LittleEndian or binary.BigEndian. If the message is not
// valid or an error occurs when writing, an error is returned.
func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) (err error) {
_, err = msg.EncodeToWithFDs(out, order)
return err
}
// IsValid checks whether msg is a valid message and returns an
// InvalidMessageError or FormatError if it is not.
func (msg *Message) IsValid() error {
var b bytes.Buffer
return msg.EncodeTo(&b, nativeEndian)
}
func (msg *Message) validateHeader() error {
if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected|FlagAllowInteractiveAuthorization) != 0 {
return InvalidMessageError("invalid flags")
}
if msg.Type == 0 || msg.Type >= typeMax {
return InvalidMessageError("invalid message type")
}
for k, v := range msg.Headers {
if k == 0 || k >= fieldMax {
return InvalidMessageError("invalid header")
}
if reflect.TypeOf(v.value) != fieldTypes[k] {
return InvalidMessageError("invalid type of header field")
}
}
for _, v := range requiredFields[msg.Type] {
if _, ok := msg.Headers[v]; !ok {
return InvalidMessageError("missing required header")
}
}
if path, ok := msg.Headers[FieldPath]; ok {
if !path.value.(ObjectPath).IsValid() {
return InvalidMessageError("invalid path name")
}
}
if iface, ok := msg.Headers[FieldInterface]; ok {
if !isValidInterface(iface.value.(string)) {
return InvalidMessageError("invalid interface name")
}
}
if member, ok := msg.Headers[FieldMember]; ok {
if !isValidMember(member.value.(string)) {
return InvalidMessageError("invalid member name")
}
}
if errname, ok := msg.Headers[FieldErrorName]; ok {
if !isValidInterface(errname.value.(string)) {
return InvalidMessageError("invalid error name")
}
}
if len(msg.Body) != 0 {
if _, ok := msg.Headers[FieldSignature]; !ok {
return InvalidMessageError("missing signature")
}
}
return nil
}
// Serial returns the message's serial number. The returned value is only valid
// for messages received by eavesdropping.
func (msg *Message) Serial() uint32 {
return msg.serial
}
// String returns a string representation of a message similar to the format of
// dbus-monitor.
func (msg *Message) String() string {
if err := msg.IsValid(); err != nil {
return "<invalid>"
}
s := msg.Type.String()
if v, ok := msg.Headers[FieldSender]; ok {
s += " from " + v.value.(string)
}
if v, ok := msg.Headers[FieldDestination]; ok {
s += " to " + v.value.(string)
}
s += " serial " + strconv.FormatUint(uint64(msg.serial), 10)
if v, ok := msg.Headers[FieldReplySerial]; ok {
s += " reply_serial " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
}
if v, ok := msg.Headers[FieldUnixFDs]; ok {
s += " unixfds " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
}
if v, ok := msg.Headers[FieldPath]; ok {
s += " path " + string(v.value.(ObjectPath))
}
if v, ok := msg.Headers[FieldInterface]; ok {
s += " interface " + v.value.(string)
}
if v, ok := msg.Headers[FieldErrorName]; ok {
s += " error " + v.value.(string)
}
if v, ok := msg.Headers[FieldMember]; ok {
s += " member " + v.value.(string)
}
if len(msg.Body) != 0 {
s += "\n"
}
for i, v := range msg.Body {
s += " " + MakeVariant(v).String()
if i != len(msg.Body)-1 {
s += "\n"
}
}
return s
}

174
vendor/github.com/godbus/dbus/v5/object.go generated vendored Normal file
View File

@@ -0,0 +1,174 @@
package dbus
import (
"context"
"errors"
"strings"
)
// BusObject is the interface of a remote object on which methods can be
// invoked.
type BusObject interface {
Call(method string, flags Flags, args ...interface{}) *Call
CallWithContext(ctx context.Context, method string, flags Flags, args ...interface{}) *Call
Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call
GoWithContext(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call
AddMatchSignal(iface, member string, options ...MatchOption) *Call
RemoveMatchSignal(iface, member string, options ...MatchOption) *Call
GetProperty(p string) (Variant, error)
StoreProperty(p string, value interface{}) error
SetProperty(p string, v interface{}) error
Destination() string
Path() ObjectPath
}
// Object represents a remote object on which methods can be invoked.
type Object struct {
conn *Conn
dest string
path ObjectPath
}
// Call calls a method with (*Object).Go and waits for its reply.
func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
return <-o.createCall(context.Background(), method, flags, make(chan *Call, 1), args...).Done
}
// CallWithContext acts like Call but takes a context
func (o *Object) CallWithContext(ctx context.Context, method string, flags Flags, args ...interface{}) *Call {
return <-o.createCall(ctx, method, flags, make(chan *Call, 1), args...).Done
}
// AddMatchSignal subscribes BusObject to signals from specified interface,
// method (member). Additional filter rules can be added via WithMatch* option constructors.
// Note: To filter events by object path you have to specify this path via an option.
//
// Deprecated: use (*Conn) AddMatchSignal instead.
func (o *Object) AddMatchSignal(iface, member string, options ...MatchOption) *Call {
base := []MatchOption{
withMatchType("signal"),
WithMatchInterface(iface),
WithMatchMember(member),
}
options = append(base, options...)
return o.conn.BusObject().Call(
"org.freedesktop.DBus.AddMatch",
0,
formatMatchOptions(options),
)
}
// RemoveMatchSignal unsubscribes BusObject from signals from specified interface,
// method (member). Additional filter rules can be added via WithMatch* option constructors
//
// Deprecated: use (*Conn) RemoveMatchSignal instead.
func (o *Object) RemoveMatchSignal(iface, member string, options ...MatchOption) *Call {
base := []MatchOption{
withMatchType("signal"),
WithMatchInterface(iface),
WithMatchMember(member),
}
options = append(base, options...)
return o.conn.BusObject().Call(
"org.freedesktop.DBus.RemoveMatch",
0,
formatMatchOptions(options),
)
}
// Go calls a method with the given arguments asynchronously. It returns a
// Call structure representing this method call. The passed channel will
// return the same value once the call is done. If ch is nil, a new channel
// will be allocated. Otherwise, ch has to be buffered or Go will panic.
//
// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
// is returned with any error in Err and a closed channel in Done containing
// the returned Call as it's one entry.
//
// If the method parameter contains a dot ('.'), the part before the last dot
// specifies the interface on which the method is called.
func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
return o.createCall(context.Background(), method, flags, ch, args...)
}
// GoWithContext acts like Go but takes a context
func (o *Object) GoWithContext(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
return o.createCall(ctx, method, flags, ch, args...)
}
func (o *Object) createCall(ctx context.Context, method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
if ctx == nil {
panic("nil context")
}
iface := ""
i := strings.LastIndex(method, ".")
if i != -1 {
iface = method[:i]
}
method = method[i+1:]
msg := new(Message)
msg.Type = TypeMethodCall
msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldPath] = MakeVariant(o.path)
msg.Headers[FieldDestination] = MakeVariant(o.dest)
msg.Headers[FieldMember] = MakeVariant(method)
if iface != "" {
msg.Headers[FieldInterface] = MakeVariant(iface)
}
msg.Body = args
if len(args) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
}
return o.conn.SendWithContext(ctx, msg, ch)
}
// GetProperty calls org.freedesktop.DBus.Properties.Get on the given
// object. The property name must be given in interface.member notation.
func (o *Object) GetProperty(p string) (Variant, error) {
var result Variant
err := o.StoreProperty(p, &result)
return result, err
}
// StoreProperty calls org.freedesktop.DBus.Properties.Get on the given
// object. The property name must be given in interface.member notation.
// It stores the returned property into the provided value.
func (o *Object) StoreProperty(p string, value interface{}) error {
idx := strings.LastIndex(p, ".")
if idx == -1 || idx+1 == len(p) {
return errors.New("dbus: invalid property " + p)
}
iface := p[:idx]
prop := p[idx+1:]
return o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).
Store(value)
}
// SetProperty calls org.freedesktop.DBus.Properties.Set on the given
// object. The property name must be given in interface.member notation.
func (o *Object) SetProperty(p string, v interface{}) error {
idx := strings.LastIndex(p, ".")
if idx == -1 || idx+1 == len(p) {
return errors.New("dbus: invalid property " + p)
}
iface := p[:idx]
prop := p[idx+1:]
return o.Call("org.freedesktop.DBus.Properties.Set", 0, iface, prop, v).Err
}
// Destination returns the destination that calls on (o *Object) are sent to.
func (o *Object) Destination() string {
return o.dest
}
// Path returns the path that calls on (o *Object") are sent to.
func (o *Object) Path() ObjectPath {
return o.path
}

348
vendor/github.com/godbus/dbus/v5/prop/prop.go generated vendored Normal file
View File

@@ -0,0 +1,348 @@
// Package prop provides the Properties struct which can be used to implement
// org.freedesktop.DBus.Properties.
package prop
import (
"reflect"
"sync"
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/introspect"
)
// EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is
// emitted for a property. If it is EmitTrue, the signal is emitted. If it is
// EmitInvalidates, the signal is also emitted, but the new value of the property
// is not disclosed. If it is EmitConst, the property never changes value during
// the lifetime of the object it belongs to, and hence the signal is never emitted
// for it.
type EmitType byte
const (
EmitFalse EmitType = iota
EmitTrue
EmitInvalidates
EmitConst
)
func (e EmitType) String() (str string) {
switch e {
case EmitFalse:
str = "false"
case EmitTrue:
str = "true"
case EmitInvalidates:
str = "invalidates"
case EmitConst:
str = "const"
default:
panic("invalid value for EmitType")
}
return
}
// ErrIfaceNotFound is the error returned to peers who try to access properties
// on interfaces that aren't found.
var ErrIfaceNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil)
// ErrPropNotFound is the error returned to peers trying to access properties
// that aren't found.
var ErrPropNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil)
// ErrReadOnly is the error returned to peers trying to set a read-only
// property.
var ErrReadOnly = dbus.NewError("org.freedesktop.DBus.Properties.Error.ReadOnly", nil)
// ErrInvalidArg is returned to peers if the type of the property that is being
// changed and the argument don't match.
var ErrInvalidArg = dbus.NewError("org.freedesktop.DBus.Properties.Error.InvalidArg", nil)
// The introspection data for the org.freedesktop.DBus.Properties interface.
var IntrospectData = introspect.Interface{
Name: "org.freedesktop.DBus.Properties",
Methods: []introspect.Method{
{
Name: "Get",
Args: []introspect.Arg{
{Name: "interface", Type: "s", Direction: "in"},
{Name: "property", Type: "s", Direction: "in"},
{Name: "value", Type: "v", Direction: "out"},
},
},
{
Name: "GetAll",
Args: []introspect.Arg{
{Name: "interface", Type: "s", Direction: "in"},
{Name: "props", Type: "a{sv}", Direction: "out"},
},
},
{
Name: "Set",
Args: []introspect.Arg{
{Name: "interface", Type: "s", Direction: "in"},
{Name: "property", Type: "s", Direction: "in"},
{Name: "value", Type: "v", Direction: "in"},
},
},
},
Signals: []introspect.Signal{
{
Name: "PropertiesChanged",
Args: []introspect.Arg{
{Name: "interface", Type: "s", Direction: "out"},
{Name: "changed_properties", Type: "a{sv}", Direction: "out"},
{Name: "invalidates_properties", Type: "as", Direction: "out"},
},
},
},
}
// The introspection data for the org.freedesktop.DBus.Properties interface, as
// a string.
const IntrospectDataString = `
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg name="interface" direction="in" type="s"/>
<arg name="property" direction="in" type="s"/>
<arg name="value" direction="out" type="v"/>
</method>
<method name="GetAll">
<arg name="interface" direction="in" type="s"/>
<arg name="props" direction="out" type="a{sv}"/>
</method>
<method name="Set">
<arg name="interface" direction="in" type="s"/>
<arg name="property" direction="in" type="s"/>
<arg name="value" direction="in" type="v"/>
</method>
<signal name="PropertiesChanged">
<arg name="interface" type="s"/>
<arg name="changed_properties" type="a{sv}"/>
<arg name="invalidates_properties" type="as"/>
</signal>
</interface>
`
// Prop represents a single property. It is used for creating a Properties
// value.
type Prop struct {
// Initial value. Must be a DBus-representable type. This is not modified
// after Properties has been initialized; use Get or GetMust to access the
// value.
Value interface{}
// If true, the value can be modified by calls to Set.
Writable bool
// Controls how org.freedesktop.DBus.Properties.PropertiesChanged is
// emitted if this property changes.
Emit EmitType
// If not nil, anytime this property is changed by Set, this function is
// called with an appropriate Change as its argument. If the returned error
// is not nil, it is sent back to the caller of Set and the property is not
// changed.
Callback func(*Change) *dbus.Error
}
// Introspection returns the introspection data for p.
// The "name" argument is used as the property's name in the resulting data.
func (p *Prop) Introspection(name string) introspect.Property {
var result = introspect.Property{Name: name, Type: dbus.SignatureOf(p.Value).String()}
if p.Writable {
result.Access = "readwrite"
} else {
result.Access = "read"
}
result.Annotations = []introspect.Annotation{
{
Name: "org.freedesktop.DBus.Property.EmitsChangedSignal",
Value: p.Emit.String(),
},
}
return result
}
// Change represents a change of a property by a call to Set.
type Change struct {
Props *Properties
Iface string
Name string
Value interface{}
}
// Properties is a set of values that can be made available to the message bus
// using the org.freedesktop.DBus.Properties interface. It is safe for
// concurrent use by multiple goroutines.
type Properties struct {
m Map
mut sync.RWMutex
conn *dbus.Conn
path dbus.ObjectPath
}
// New falls back to Export, but it returns nil if properties export fails,
// swallowing the error, shouldn't be used.
//
// Deprecated: use Export instead.
func New(conn *dbus.Conn, path dbus.ObjectPath, props Map) *Properties {
p, err := Export(conn, path, props)
if err != nil {
return nil
}
return p
}
// Export returns a new Properties structure that manages the given properties.
// The key for the first-level map of props is the name of the interface; the
// second-level key is the name of the property. The returned structure will be
// exported as org.freedesktop.DBus.Properties on path.
func Export(
conn *dbus.Conn, path dbus.ObjectPath, props Map,
) (*Properties, error) {
p := &Properties{m: copyProps(props), conn: conn, path: path}
if err := conn.Export(p, path, "org.freedesktop.DBus.Properties"); err != nil {
return nil, err
}
return p, nil
}
// Map is a helper type for supplying the configuration of properties to be handled.
type Map = map[string]map[string]*Prop
func copyProps(in Map) Map {
out := make(Map, len(in))
for intf, props := range in {
out[intf] = make(map[string]*Prop)
for name, prop := range props {
out[intf][name] = new(Prop)
*out[intf][name] = *prop
val := reflect.New(reflect.TypeOf(prop.Value))
val.Elem().Set(reflect.ValueOf(prop.Value))
out[intf][name].Value = val.Interface()
}
}
return out
}
// Get implements org.freedesktop.DBus.Properties.Get.
func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) {
p.mut.RLock()
defer p.mut.RUnlock()
m, ok := p.m[iface]
if !ok {
return dbus.Variant{}, ErrIfaceNotFound
}
prop, ok := m[property]
if !ok {
return dbus.Variant{}, ErrPropNotFound
}
return dbus.MakeVariant(reflect.ValueOf(prop.Value).Elem().Interface()), nil
}
// GetAll implements org.freedesktop.DBus.Properties.GetAll.
func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
p.mut.RLock()
defer p.mut.RUnlock()
m, ok := p.m[iface]
if !ok {
return nil, ErrIfaceNotFound
}
rm := make(map[string]dbus.Variant, len(m))
for k, v := range m {
rm[k] = dbus.MakeVariant(reflect.ValueOf(v.Value).Elem().Interface())
}
return rm, nil
}
// GetMust returns the value of the given property and panics if either the
// interface or the property name are invalid.
func (p *Properties) GetMust(iface, property string) interface{} {
p.mut.RLock()
defer p.mut.RUnlock()
return reflect.ValueOf(p.m[iface][property].Value).Elem().Interface()
}
// Introspection returns the introspection data that represents the properties
// of iface.
func (p *Properties) Introspection(iface string) []introspect.Property {
p.mut.RLock()
defer p.mut.RUnlock()
m := p.m[iface]
s := make([]introspect.Property, 0, len(m))
for name, prop := range m {
s = append(s, prop.Introspection(name))
}
return s
}
// set sets the given property and emits PropertyChanged if appropriate. p.mut
// must already be locked.
func (p *Properties) set(iface, property string, v interface{}) error {
prop := p.m[iface][property]
err := dbus.Store([]interface{}{v}, prop.Value)
if err != nil {
return err
}
return p.emitChange(iface, property)
}
func (p *Properties) emitChange(iface, property string) error {
prop := p.m[iface][property]
switch prop.Emit {
case EmitFalse:
return nil // do nothing
case EmitInvalidates:
return p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
iface, map[string]dbus.Variant{}, []string{property})
case EmitTrue:
return p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
iface, map[string]dbus.Variant{property: dbus.MakeVariant(prop.Value)},
[]string{})
case EmitConst:
return nil
default:
panic("invalid value for EmitType")
}
}
// Set implements org.freedesktop.Properties.Set.
func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error {
p.mut.Lock()
defer p.mut.Unlock()
m, ok := p.m[iface]
if !ok {
return ErrIfaceNotFound
}
prop, ok := m[property]
if !ok {
return ErrPropNotFound
}
if !prop.Writable {
return ErrReadOnly
}
if newv.Signature() != dbus.SignatureOf(prop.Value) {
return ErrInvalidArg
}
if prop.Callback != nil {
err := prop.Callback(&Change{p, iface, property, newv.Value()})
if err != nil {
return err
}
}
if err := p.set(iface, property, newv.Value()); err != nil {
return dbus.MakeFailedError(err)
}
return nil
}
// SetMust sets the value of the given property and panics if the interface or
// the property name are invalid.
func (p *Properties) SetMust(iface, property string, v interface{}) {
p.mut.Lock()
defer p.mut.Unlock() // unlock in case of panic
err := p.set(iface, property, v)
if err != nil {
panic(err)
}
}

24
vendor/github.com/godbus/dbus/v5/sequence.go generated vendored Normal file
View File

@@ -0,0 +1,24 @@
package dbus
// Sequence represents the value of a monotonically increasing counter.
type Sequence uint64
const (
// NoSequence indicates the absence of a sequence value.
NoSequence Sequence = 0
)
// sequenceGenerator represents a monotonically increasing counter.
type sequenceGenerator struct {
nextSequence Sequence
}
func (generator *sequenceGenerator) next() Sequence {
result := generator.nextSequence
generator.nextSequence++
return result
}
func newSequenceGenerator() *sequenceGenerator {
return &sequenceGenerator{nextSequence: 1}
}

125
vendor/github.com/godbus/dbus/v5/sequential_handler.go generated vendored Normal file
View File

@@ -0,0 +1,125 @@
package dbus
import (
"sync"
)
// NewSequentialSignalHandler returns an instance of a new
// signal handler that guarantees sequential processing of signals. It is a
// guarantee of this signal handler that signals will be written to
// channels in the order they are received on the DBus connection.
func NewSequentialSignalHandler() SignalHandler {
return &sequentialSignalHandler{}
}
type sequentialSignalHandler struct {
mu sync.RWMutex
closed bool
signals []*sequentialSignalChannelData
}
func (sh *sequentialSignalHandler) DeliverSignal(intf, name string, signal *Signal) {
sh.mu.RLock()
defer sh.mu.RUnlock()
if sh.closed {
return
}
for _, scd := range sh.signals {
scd.deliver(signal)
}
}
func (sh *sequentialSignalHandler) Terminate() {
sh.mu.Lock()
defer sh.mu.Unlock()
if sh.closed {
return
}
for _, scd := range sh.signals {
scd.close()
close(scd.ch)
}
sh.closed = true
sh.signals = nil
}
func (sh *sequentialSignalHandler) AddSignal(ch chan<- *Signal) {
sh.mu.Lock()
defer sh.mu.Unlock()
if sh.closed {
return
}
sh.signals = append(sh.signals, newSequentialSignalChannelData(ch))
}
func (sh *sequentialSignalHandler) RemoveSignal(ch chan<- *Signal) {
sh.mu.Lock()
defer sh.mu.Unlock()
if sh.closed {
return
}
for i := len(sh.signals) - 1; i >= 0; i-- {
if ch == sh.signals[i].ch {
sh.signals[i].close()
copy(sh.signals[i:], sh.signals[i+1:])
sh.signals[len(sh.signals)-1] = nil
sh.signals = sh.signals[:len(sh.signals)-1]
}
}
}
type sequentialSignalChannelData struct {
ch chan<- *Signal
in chan *Signal
done chan struct{}
}
func newSequentialSignalChannelData(ch chan<- *Signal) *sequentialSignalChannelData {
scd := &sequentialSignalChannelData{
ch: ch,
in: make(chan *Signal),
done: make(chan struct{}),
}
go scd.bufferSignals()
return scd
}
func (scd *sequentialSignalChannelData) bufferSignals() {
defer close(scd.done)
// Ensure that signals are delivered to scd.ch in the same
// order they are received from scd.in.
var queue []*Signal
for {
if len(queue) == 0 {
signal, ok := <- scd.in
if !ok {
return
}
queue = append(queue, signal)
}
select {
case scd.ch <- queue[0]:
copy(queue, queue[1:])
queue[len(queue)-1] = nil
queue = queue[:len(queue)-1]
case signal, ok := <-scd.in:
if !ok {
return
}
queue = append(queue, signal)
}
}
}
func (scd *sequentialSignalChannelData) deliver(signal *Signal) {
scd.in <- signal
}
func (scd *sequentialSignalChannelData) close() {
close(scd.in)
// Ensure that bufferSignals() has exited and won't attempt
// any future sends on scd.ch
<-scd.done
}

107
vendor/github.com/godbus/dbus/v5/server_interfaces.go generated vendored Normal file
View File

@@ -0,0 +1,107 @@
package dbus
// Terminator allows a handler to implement a shutdown mechanism that
// is called when the connection terminates.
type Terminator interface {
Terminate()
}
// Handler is the representation of a D-Bus Application.
//
// The Handler must have a way to lookup objects given
// an ObjectPath. The returned object must implement the
// ServerObject interface.
type Handler interface {
LookupObject(path ObjectPath) (ServerObject, bool)
}
// ServerObject is the representation of an D-Bus Object.
//
// Objects are registered at a path for a given Handler.
// The Objects implement D-Bus interfaces. The semantics
// of Interface lookup is up to the implementation of
// the ServerObject. The ServerObject implementation may
// choose to implement empty string as a valid interface
// represeting all methods or not per the D-Bus specification.
type ServerObject interface {
LookupInterface(name string) (Interface, bool)
}
// An Interface is the representation of a D-Bus Interface.
//
// Interfaces are a grouping of methods implemented by the Objects.
// Interfaces are responsible for routing method calls.
type Interface interface {
LookupMethod(name string) (Method, bool)
}
// A Method represents the exposed methods on D-Bus.
type Method interface {
// Call requires that all arguments are decoded before being passed to it.
Call(args ...interface{}) ([]interface{}, error)
NumArguments() int
NumReturns() int
// ArgumentValue returns a representative value for the argument at position
// it should be of the proper type. reflect.Zero would be a good mechanism
// to use for this Value.
ArgumentValue(position int) interface{}
// ReturnValue returns a representative value for the return at position
// it should be of the proper type. reflect.Zero would be a good mechanism
// to use for this Value.
ReturnValue(position int) interface{}
}
// An Argument Decoder can decode arguments using the non-standard mechanism
//
// If a method implements this interface then the non-standard
// decoder will be used.
//
// Method arguments must be decoded from the message.
// The mechanism for doing this will vary based on the
// implementation of the method. A normal approach is provided
// as part of this library, but may be replaced with
// any other decoding scheme.
type ArgumentDecoder interface {
// To decode the arguments of a method the sender and message are
// provided in case the semantics of the implementer provides access
// to these as part of the method invocation.
DecodeArguments(conn *Conn, sender string, msg *Message, args []interface{}) ([]interface{}, error)
}
// A SignalHandler is responsible for delivering a signal.
//
// Signal delivery may be changed from the default channel
// based approach by Handlers implementing the SignalHandler
// interface.
type SignalHandler interface {
DeliverSignal(iface, name string, signal *Signal)
}
// SignalRegistrar manages signal delivery channels.
//
// This is an optional set of methods for `SignalHandler`.
type SignalRegistrar interface {
AddSignal(ch chan<- *Signal)
RemoveSignal(ch chan<- *Signal)
}
// A DBusError is used to convert a generic object to a D-Bus error.
//
// Any custom error mechanism may implement this interface to provide
// a custom encoding of the error on D-Bus. By default if a normal
// error is returned, it will be encoded as the generic
// "org.freedesktop.DBus.Error.Failed" error. By implementing this
// interface as well a custom encoding may be provided.
type DBusError interface {
DBusError() (string, []interface{})
}
// SerialGenerator is responsible for serials generation.
//
// Different approaches for the serial generation can be used,
// maintaining a map guarded with a mutex (the standard way) or
// simply increment an atomic counter.
type SerialGenerator interface {
GetSerial() uint32
RetireSerial(serial uint32)
}

293
vendor/github.com/godbus/dbus/v5/sig.go generated vendored Normal file
View File

@@ -0,0 +1,293 @@
package dbus
import (
"fmt"
"reflect"
"strings"
)
var sigToType = map[byte]reflect.Type{
'y': byteType,
'b': boolType,
'n': int16Type,
'q': uint16Type,
'i': int32Type,
'u': uint32Type,
'x': int64Type,
't': uint64Type,
'd': float64Type,
's': stringType,
'g': signatureType,
'o': objectPathType,
'v': variantType,
'h': unixFDIndexType,
}
// Signature represents a correct type signature as specified by the D-Bus
// specification. The zero value represents the empty signature, "".
type Signature struct {
str string
}
// SignatureOf returns the concatenation of all the signatures of the given
// values. It panics if one of them is not representable in D-Bus.
func SignatureOf(vs ...interface{}) Signature {
var s string
for _, v := range vs {
s += getSignature(reflect.TypeOf(v), &depthCounter{})
}
return Signature{s}
}
// SignatureOfType returns the signature of the given type. It panics if the
// type is not representable in D-Bus.
func SignatureOfType(t reflect.Type) Signature {
return Signature{getSignature(t, &depthCounter{})}
}
// getSignature returns the signature of the given type and panics on unknown types.
func getSignature(t reflect.Type, depth *depthCounter) (sig string) {
if !depth.Valid() {
panic("container nesting too deep")
}
defer func() {
if len(sig) > 255 {
panic("signature exceeds the length limitation")
}
}()
// handle simple types first
switch t.Kind() {
case reflect.Uint8:
return "y"
case reflect.Bool:
return "b"
case reflect.Int16:
return "n"
case reflect.Uint16:
return "q"
case reflect.Int, reflect.Int32:
if t == unixFDType {
return "h"
}
return "i"
case reflect.Uint, reflect.Uint32:
if t == unixFDIndexType {
return "h"
}
return "u"
case reflect.Int64:
return "x"
case reflect.Uint64:
return "t"
case reflect.Float64:
return "d"
case reflect.Ptr:
return getSignature(t.Elem(), depth)
case reflect.String:
if t == objectPathType {
return "o"
}
return "s"
case reflect.Struct:
if t == variantType {
return "v"
} else if t == signatureType {
return "g"
}
var s string
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
s += getSignature(t.Field(i).Type, depth.EnterStruct())
}
}
if len(s) == 0 {
panic(InvalidTypeError{t})
}
return "(" + s + ")"
case reflect.Array, reflect.Slice:
return "a" + getSignature(t.Elem(), depth.EnterArray())
case reflect.Map:
if !isKeyType(t.Key()) {
panic(InvalidTypeError{t})
}
return "a{" + getSignature(t.Key(), depth.EnterArray().EnterDictEntry()) + getSignature(t.Elem(), depth.EnterArray().EnterDictEntry()) + "}"
case reflect.Interface:
return "v"
}
panic(InvalidTypeError{t})
}
// ParseSignature returns the signature represented by this string, or a
// SignatureError if the string is not a valid signature.
func ParseSignature(s string) (sig Signature, err error) {
if len(s) == 0 {
return
}
if len(s) > 255 {
return Signature{""}, SignatureError{s, "too long"}
}
sig.str = s
for err == nil && len(s) != 0 {
err, s = validSingle(s, &depthCounter{})
}
if err != nil {
sig = Signature{""}
}
return
}
// ParseSignatureMust behaves like ParseSignature, except that it panics if s
// is not valid.
func ParseSignatureMust(s string) Signature {
sig, err := ParseSignature(s)
if err != nil {
panic(err)
}
return sig
}
// Empty returns whether the signature is the empty signature.
func (s Signature) Empty() bool {
return s.str == ""
}
// Single returns whether the signature represents a single, complete type.
func (s Signature) Single() bool {
err, r := validSingle(s.str, &depthCounter{})
return err != nil && r == ""
}
// String returns the signature's string representation.
func (s Signature) String() string {
return s.str
}
// A SignatureError indicates that a signature passed to a function or received
// on a connection is not a valid signature.
type SignatureError struct {
Sig string
Reason string
}
func (e SignatureError) Error() string {
return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason)
}
type depthCounter struct {
arrayDepth, structDepth, dictEntryDepth int
}
func (cnt *depthCounter) Valid() bool {
return cnt.arrayDepth <= 32 && cnt.structDepth <= 32 && cnt.dictEntryDepth <= 32
}
func (cnt depthCounter) EnterArray() *depthCounter {
cnt.arrayDepth++
return &cnt
}
func (cnt depthCounter) EnterStruct() *depthCounter {
cnt.structDepth++
return &cnt
}
func (cnt depthCounter) EnterDictEntry() *depthCounter {
cnt.dictEntryDepth++
return &cnt
}
// Try to read a single type from this string. If it was successful, err is nil
// and rem is the remaining unparsed part. Otherwise, err is a non-nil
// SignatureError and rem is "". depth is the current recursion depth which may
// not be greater than 64 and should be given as 0 on the first call.
func validSingle(s string, depth *depthCounter) (err error, rem string) {
if s == "" {
return SignatureError{Sig: s, Reason: "empty signature"}, ""
}
if !depth.Valid() {
return SignatureError{Sig: s, Reason: "container nesting too deep"}, ""
}
switch s[0] {
case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v', 'h':
return nil, s[1:]
case 'a':
if len(s) > 1 && s[1] == '{' {
i := findMatching(s[1:], '{', '}')
if i == -1 {
return SignatureError{Sig: s, Reason: "unmatched '{'"}, ""
}
i++
rem = s[i+1:]
s = s[2:i]
if err, _ = validSingle(s[:1], depth.EnterArray().EnterDictEntry()); err != nil {
return err, ""
}
err, nr := validSingle(s[1:], depth.EnterArray().EnterDictEntry())
if err != nil {
return err, ""
}
if nr != "" {
return SignatureError{Sig: s, Reason: "too many types in dict"}, ""
}
return nil, rem
}
return validSingle(s[1:], depth.EnterArray())
case '(':
i := findMatching(s, '(', ')')
if i == -1 {
return SignatureError{Sig: s, Reason: "unmatched ')'"}, ""
}
rem = s[i+1:]
s = s[1:i]
for err == nil && s != "" {
err, s = validSingle(s, depth.EnterStruct())
}
if err != nil {
rem = ""
}
return
}
return SignatureError{Sig: s, Reason: "invalid type character"}, ""
}
func findMatching(s string, left, right rune) int {
n := 0
for i, v := range s {
if v == left {
n++
} else if v == right {
n--
}
if n == 0 {
return i
}
}
return -1
}
// typeFor returns the type of the given signature. It ignores any left over
// characters and panics if s doesn't start with a valid type signature.
func typeFor(s string) (t reflect.Type) {
err, _ := validSingle(s, &depthCounter{})
if err != nil {
panic(err)
}
if t, ok := sigToType[s[0]]; ok {
return t
}
switch s[0] {
case 'a':
if s[1] == '{' {
i := strings.LastIndex(s, "}")
t = reflect.MapOf(sigToType[s[2]], typeFor(s[3:i]))
} else {
t = reflect.SliceOf(typeFor(s[1:]))
}
case '(':
t = interfacesType
}
return
}

6
vendor/github.com/godbus/dbus/v5/transport_darwin.go generated vendored Normal file
View File

@@ -0,0 +1,6 @@
package dbus
func (t *unixTransport) SendNullByte() error {
_, err := t.Write([]byte{0})
return err
}

52
vendor/github.com/godbus/dbus/v5/transport_generic.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
package dbus
import (
"encoding/binary"
"errors"
"io"
"unsafe"
)
var nativeEndian binary.ByteOrder
func detectEndianness() binary.ByteOrder {
var x uint32 = 0x01020304
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
return binary.BigEndian
}
return binary.LittleEndian
}
func init() {
nativeEndian = detectEndianness()
}
type genericTransport struct {
io.ReadWriteCloser
}
func (t genericTransport) SendNullByte() error {
_, err := t.Write([]byte{0})
return err
}
func (t genericTransport) SupportsUnixFDs() bool {
return false
}
func (t genericTransport) EnableUnixFDs() {}
func (t genericTransport) ReadMessage() (*Message, error) {
return DecodeMessage(t)
}
func (t genericTransport) SendMessage(msg *Message) error {
fds, err := msg.CountFds()
if err != nil {
return err
}
if fds != 0 {
return errors.New("dbus: unix fd passing not enabled")
}
return msg.EncodeTo(t, nativeEndian)
}

View File

@@ -0,0 +1,39 @@
//+build !windows
package dbus
import (
"errors"
"io/ioutil"
"net"
)
func init() {
transports["nonce-tcp"] = newNonceTcpTransport
}
func newNonceTcpTransport(keys string) (transport, error) {
host := getKey(keys, "host")
port := getKey(keys, "port")
noncefile := getKey(keys, "noncefile")
if host == "" || port == "" || noncefile == "" {
return nil, errors.New("dbus: unsupported address (must set host, port and noncefile)")
}
protocol, err := tcpFamily(keys)
if err != nil {
return nil, err
}
socket, err := net.Dial(protocol, net.JoinHostPort(host, port))
if err != nil {
return nil, err
}
b, err := ioutil.ReadFile(noncefile)
if err != nil {
return nil, err
}
_, err = socket.Write(b)
if err != nil {
return nil, err
}
return NewConn(socket)
}

41
vendor/github.com/godbus/dbus/v5/transport_tcp.go generated vendored Normal file
View File

@@ -0,0 +1,41 @@
package dbus
import (
"errors"
"net"
)
func init() {
transports["tcp"] = newTcpTransport
}
func tcpFamily(keys string) (string, error) {
switch getKey(keys, "family") {
case "":
return "tcp", nil
case "ipv4":
return "tcp4", nil
case "ipv6":
return "tcp6", nil
default:
return "", errors.New("dbus: invalid tcp family (must be ipv4 or ipv6)")
}
}
func newTcpTransport(keys string) (transport, error) {
host := getKey(keys, "host")
port := getKey(keys, "port")
if host == "" || port == "" {
return nil, errors.New("dbus: unsupported address (must set host and port)")
}
protocol, err := tcpFamily(keys)
if err != nil {
return nil, err
}
socket, err := net.Dial(protocol, net.JoinHostPort(host, port))
if err != nil {
return nil, err
}
return NewConn(socket)
}

212
vendor/github.com/godbus/dbus/v5/transport_unix.go generated vendored Normal file
View File

@@ -0,0 +1,212 @@
//+build !windows,!solaris
package dbus
import (
"bytes"
"encoding/binary"
"errors"
"io"
"net"
"syscall"
)
type oobReader struct {
conn *net.UnixConn
oob []byte
buf [4096]byte
}
func (o *oobReader) Read(b []byte) (n int, err error) {
n, oobn, flags, _, err := o.conn.ReadMsgUnix(b, o.buf[:])
if err != nil {
return n, err
}
if flags&syscall.MSG_CTRUNC != 0 {
return n, errors.New("dbus: control data truncated (too many fds received)")
}
o.oob = append(o.oob, o.buf[:oobn]...)
return n, nil
}
type unixTransport struct {
*net.UnixConn
rdr *oobReader
hasUnixFDs bool
}
func newUnixTransport(keys string) (transport, error) {
var err error
t := new(unixTransport)
abstract := getKey(keys, "abstract")
path := getKey(keys, "path")
switch {
case abstract == "" && path == "":
return nil, errors.New("dbus: invalid address (neither path nor abstract set)")
case abstract != "" && path == "":
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: "@" + abstract, Net: "unix"})
if err != nil {
return nil, err
}
return t, nil
case abstract == "" && path != "":
t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: path, Net: "unix"})
if err != nil {
return nil, err
}
return t, nil
default:
return nil, errors.New("dbus: invalid address (both path and abstract set)")
}
}
func init() {
transports["unix"] = newUnixTransport
}
func (t *unixTransport) EnableUnixFDs() {
t.hasUnixFDs = true
}
func (t *unixTransport) ReadMessage() (*Message, error) {
var (
blen, hlen uint32
csheader [16]byte
headers []header
order binary.ByteOrder
unixfds uint32
)
// To be sure that all bytes of out-of-band data are read, we use a special
// reader that uses ReadUnix on the underlying connection instead of Read
// and gathers the out-of-band data in a buffer.
if t.rdr == nil {
t.rdr = &oobReader{conn: t.UnixConn}
} else {
t.rdr.oob = nil
}
// read the first 16 bytes (the part of the header that has a constant size),
// from which we can figure out the length of the rest of the message
if _, err := io.ReadFull(t.rdr, csheader[:]); err != nil {
return nil, err
}
switch csheader[0] {
case 'l':
order = binary.LittleEndian
case 'B':
order = binary.BigEndian
default:
return nil, InvalidMessageError("invalid byte order")
}
// csheader[4:8] -> length of message body, csheader[12:16] -> length of
// header fields (without alignment)
binary.Read(bytes.NewBuffer(csheader[4:8]), order, &blen)
binary.Read(bytes.NewBuffer(csheader[12:]), order, &hlen)
if hlen%8 != 0 {
hlen += 8 - (hlen % 8)
}
// decode headers and look for unix fds
headerdata := make([]byte, hlen+4)
copy(headerdata, csheader[12:])
if _, err := io.ReadFull(t.rdr, headerdata[4:]); err != nil {
return nil, err
}
dec := newDecoder(bytes.NewBuffer(headerdata), order, make([]int, 0))
dec.pos = 12
vs, err := dec.Decode(Signature{"a(yv)"})
if err != nil {
return nil, err
}
Store(vs, &headers)
for _, v := range headers {
if v.Field == byte(FieldUnixFDs) {
unixfds, _ = v.Variant.value.(uint32)
}
}
all := make([]byte, 16+hlen+blen)
copy(all, csheader[:])
copy(all[16:], headerdata[4:])
if _, err := io.ReadFull(t.rdr, all[16+hlen:]); err != nil {
return nil, err
}
if unixfds != 0 {
if !t.hasUnixFDs {
return nil, errors.New("dbus: got unix fds on unsupported transport")
}
// read the fds from the OOB data
scms, err := syscall.ParseSocketControlMessage(t.rdr.oob)
if err != nil {
return nil, err
}
if len(scms) != 1 {
return nil, errors.New("dbus: received more than one socket control message")
}
fds, err := syscall.ParseUnixRights(&scms[0])
if err != nil {
return nil, err
}
msg, err := DecodeMessageWithFDs(bytes.NewBuffer(all), fds)
if err != nil {
return nil, err
}
// substitute the values in the message body (which are indices for the
// array receiver via OOB) with the actual values
for i, v := range msg.Body {
switch index := v.(type) {
case UnixFDIndex:
if uint32(index) >= unixfds {
return nil, InvalidMessageError("invalid index for unix fd")
}
msg.Body[i] = UnixFD(fds[index])
case []UnixFDIndex:
fdArray := make([]UnixFD, len(index))
for k, j := range index {
if uint32(j) >= unixfds {
return nil, InvalidMessageError("invalid index for unix fd")
}
fdArray[k] = UnixFD(fds[j])
}
msg.Body[i] = fdArray
}
}
return msg, nil
}
return DecodeMessage(bytes.NewBuffer(all))
}
func (t *unixTransport) SendMessage(msg *Message) error {
fdcnt, err := msg.CountFds()
if err != nil {
return err
}
if fdcnt != 0 {
if !t.hasUnixFDs {
return errors.New("dbus: unix fd passing not enabled")
}
msg.Headers[FieldUnixFDs] = MakeVariant(uint32(fdcnt))
buf := new(bytes.Buffer)
fds, err := msg.EncodeToWithFDs(buf, nativeEndian)
if err != nil {
return err
}
oob := syscall.UnixRights(fds...)
n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil)
if err != nil {
return err
}
if n != buf.Len() || oobn != len(oob) {
return io.ErrShortWrite
}
} else {
if err := msg.EncodeTo(t, nativeEndian); err != nil {
return err
}
}
return nil
}
func (t *unixTransport) SupportsUnixFDs() bool {
return true
}

View File

@@ -0,0 +1,95 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
// Local implementation of the UnixCredentials system call for DragonFly BSD
package dbus
/*
#include <sys/ucred.h>
*/
import "C"
import (
"io"
"os"
"syscall"
"unsafe"
)
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
// http://golang.org/src/pkg/syscall/ztypes_dragonfly_amd64.go
type Ucred struct {
Pid int32
Uid uint32
Gid uint32
}
// http://golang.org/src/pkg/syscall/types_linux.go
// http://golang.org/src/pkg/syscall/types_dragonfly.go
// https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/sys/ucred.h
const (
SizeofUcred = C.sizeof_struct_ucred
)
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgAlignOf(salen int) int {
// From http://golang.org/src/pkg/syscall/sockcmsg_unix.go
//salign := sizeofPtr
// NOTE: It seems like 64-bit Darwin and DragonFly BSD kernels
// still require 32-bit aligned access to network subsystem.
//if darwin64Bit || dragonfly64Bit {
// salign = 4
//}
salign := 4
return (salen + salign - 1) & ^(salign - 1)
}
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// UnixCredentials encodes credentials into a socket control message
// for sending to another process. This can be used for
// authentication.
func UnixCredentials(ucred *Ucred) []byte {
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
h.Level = syscall.SOL_SOCKET
h.Type = syscall.SCM_CREDS
h.SetLen(syscall.CmsgLen(SizeofUcred))
*((*Ucred)(cmsgData(h))) = *ucred
return b
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// ParseUnixCredentials decodes a socket control message that contains
// credentials in a Ucred structure. To receive such a message, the
// SO_PASSCRED option must be enabled on the socket.
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
if m.Header.Level != syscall.SOL_SOCKET {
return nil, syscall.EINVAL
}
if m.Header.Type != syscall.SCM_CREDS {
return nil, syscall.EINVAL
}
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
return &ucred, nil
}
func (t *unixTransport) SendNullByte() error {
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := UnixCredentials(ucred)
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
if err != nil {
return err
}
if oobn != len(b) {
return io.ErrShortWrite
}
return nil
}

View File

@@ -0,0 +1,92 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
// Local implementation of the UnixCredentials system call for FreeBSD
package dbus
/*
const int sizeofPtr = sizeof(void*);
#define _WANT_UCRED
#include <sys/types.h>
#include <sys/ucred.h>
*/
import "C"
import (
"io"
"os"
"syscall"
"unsafe"
)
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
// https://golang.org/src/syscall/ztypes_freebsd_amd64.go
type Ucred struct {
Pid int32
Uid uint32
Gid uint32
}
// http://golang.org/src/pkg/syscall/types_linux.go
// https://golang.org/src/syscall/types_freebsd.go
// https://github.com/freebsd/freebsd/blob/master/sys/sys/ucred.h
const (
SizeofUcred = C.sizeof_struct_ucred
)
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgAlignOf(salen int) int {
salign := C.sizeofPtr
return (salen + salign - 1) & ^(salign - 1)
}
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// UnixCredentials encodes credentials into a socket control message
// for sending to another process. This can be used for
// authentication.
func UnixCredentials(ucred *Ucred) []byte {
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
h.Level = syscall.SOL_SOCKET
h.Type = syscall.SCM_CREDS
h.SetLen(syscall.CmsgLen(SizeofUcred))
*((*Ucred)(cmsgData(h))) = *ucred
return b
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// ParseUnixCredentials decodes a socket control message that contains
// credentials in a Ucred structure. To receive such a message, the
// SO_PASSCRED option must be enabled on the socket.
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
if m.Header.Level != syscall.SOL_SOCKET {
return nil, syscall.EINVAL
}
if m.Header.Type != syscall.SCM_CREDS {
return nil, syscall.EINVAL
}
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
return &ucred, nil
}
func (t *unixTransport) SendNullByte() error {
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := UnixCredentials(ucred)
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
if err != nil {
return err
}
if oobn != len(b) {
return io.ErrShortWrite
}
return nil
}

View File

@@ -0,0 +1,25 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
package dbus
import (
"io"
"os"
"syscall"
)
func (t *unixTransport) SendNullByte() error {
ucred := &syscall.Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := syscall.UnixCredentials(ucred)
_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
if err != nil {
return err
}
if oobn != len(b) {
return io.ErrShortWrite
}
return nil
}

View File

@@ -0,0 +1,14 @@
package dbus
import "io"
func (t *unixTransport) SendNullByte() error {
n, _, err := t.UnixConn.WriteMsgUnix([]byte{0}, nil, nil)
if err != nil {
return err
}
if n != 1 {
return io.ErrShortWrite
}
return nil
}

View File

@@ -0,0 +1,14 @@
package dbus
import "io"
func (t *unixTransport) SendNullByte() error {
n, _, err := t.UnixConn.WriteMsgUnix([]byte{0}, nil, nil)
if err != nil {
return err
}
if n != 1 {
return io.ErrShortWrite
}
return nil
}

6
vendor/github.com/godbus/dbus/v5/transport_zos.go generated vendored Normal file
View File

@@ -0,0 +1,6 @@
package dbus
func (t *unixTransport) SendNullByte() error {
_, err := t.Write([]byte{0})
return err
}

150
vendor/github.com/godbus/dbus/v5/variant.go generated vendored Normal file
View File

@@ -0,0 +1,150 @@
package dbus
import (
"bytes"
"fmt"
"reflect"
"sort"
"strconv"
)
// Variant represents the D-Bus variant type.
type Variant struct {
sig Signature
value interface{}
}
// MakeVariant converts the given value to a Variant. It panics if v cannot be
// represented as a D-Bus type.
func MakeVariant(v interface{}) Variant {
return MakeVariantWithSignature(v, SignatureOf(v))
}
// MakeVariantWithSignature converts the given value to a Variant.
func MakeVariantWithSignature(v interface{}, s Signature) Variant {
return Variant{s, v}
}
// ParseVariant parses the given string as a variant as described at
// https://developer.gnome.org/glib/stable/gvariant-text.html. If sig is not
// empty, it is taken to be the expected signature for the variant.
func ParseVariant(s string, sig Signature) (Variant, error) {
tokens := varLex(s)
p := &varParser{tokens: tokens}
n, err := varMakeNode(p)
if err != nil {
return Variant{}, err
}
if sig.str == "" {
sig, err = varInfer(n)
if err != nil {
return Variant{}, err
}
}
v, err := n.Value(sig)
if err != nil {
return Variant{}, err
}
return MakeVariant(v), nil
}
// format returns a formatted version of v and whether this string can be parsed
// unambiguously.
func (v Variant) format() (string, bool) {
switch v.sig.str[0] {
case 'b', 'i':
return fmt.Sprint(v.value), true
case 'n', 'q', 'u', 'x', 't', 'd', 'h':
return fmt.Sprint(v.value), false
case 's':
return strconv.Quote(v.value.(string)), true
case 'o':
return strconv.Quote(string(v.value.(ObjectPath))), false
case 'g':
return strconv.Quote(v.value.(Signature).str), false
case 'v':
s, unamb := v.value.(Variant).format()
if !unamb {
return "<@" + v.value.(Variant).sig.str + " " + s + ">", true
}
return "<" + s + ">", true
case 'y':
return fmt.Sprintf("%#x", v.value.(byte)), false
}
rv := reflect.ValueOf(v.value)
switch rv.Kind() {
case reflect.Slice:
if rv.Len() == 0 {
return "[]", false
}
unamb := true
buf := bytes.NewBuffer([]byte("["))
for i := 0; i < rv.Len(); i++ {
// TODO: slooow
s, b := MakeVariant(rv.Index(i).Interface()).format()
unamb = unamb && b
buf.WriteString(s)
if i != rv.Len()-1 {
buf.WriteString(", ")
}
}
buf.WriteByte(']')
return buf.String(), unamb
case reflect.Map:
if rv.Len() == 0 {
return "{}", false
}
unamb := true
var buf bytes.Buffer
kvs := make([]string, rv.Len())
for i, k := range rv.MapKeys() {
s, b := MakeVariant(k.Interface()).format()
unamb = unamb && b
buf.Reset()
buf.WriteString(s)
buf.WriteString(": ")
s, b = MakeVariant(rv.MapIndex(k).Interface()).format()
unamb = unamb && b
buf.WriteString(s)
kvs[i] = buf.String()
}
buf.Reset()
buf.WriteByte('{')
sort.Strings(kvs)
for i, kv := range kvs {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(kv)
}
buf.WriteByte('}')
return buf.String(), unamb
}
return `"INVALID"`, true
}
// Signature returns the D-Bus signature of the underlying value of v.
func (v Variant) Signature() Signature {
return v.sig
}
// String returns the string representation of the underlying value of v as
// described at https://developer.gnome.org/glib/stable/gvariant-text.html.
func (v Variant) String() string {
s, unamb := v.format()
if !unamb {
return "@" + v.sig.str + " " + s
}
return s
}
// Value returns the underlying value of v.
func (v Variant) Value() interface{} {
return v.value
}
// Store converts the variant into a native go type using the same
// mechanism as the "Store" function.
func (v Variant) Store(value interface{}) error {
return storeInterfaces(v.value, value)
}

284
vendor/github.com/godbus/dbus/v5/variant_lexer.go generated vendored Normal file
View File

@@ -0,0 +1,284 @@
package dbus
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// Heavily inspired by the lexer from text/template.
type varToken struct {
typ varTokenType
val string
}
type varTokenType byte
const (
tokEOF varTokenType = iota
tokError
tokNumber
tokString
tokBool
tokArrayStart
tokArrayEnd
tokDictStart
tokDictEnd
tokVariantStart
tokVariantEnd
tokComma
tokColon
tokType
tokByteString
)
type varLexer struct {
input string
start int
pos int
width int
tokens []varToken
}
type lexState func(*varLexer) lexState
func varLex(s string) []varToken {
l := &varLexer{input: s}
l.run()
return l.tokens
}
func (l *varLexer) accept(valid string) bool {
if strings.ContainsRune(valid, l.next()) {
return true
}
l.backup()
return false
}
func (l *varLexer) backup() {
l.pos -= l.width
}
func (l *varLexer) emit(t varTokenType) {
l.tokens = append(l.tokens, varToken{t, l.input[l.start:l.pos]})
l.start = l.pos
}
func (l *varLexer) errorf(format string, v ...interface{}) lexState {
l.tokens = append(l.tokens, varToken{
tokError,
fmt.Sprintf(format, v...),
})
return nil
}
func (l *varLexer) ignore() {
l.start = l.pos
}
func (l *varLexer) next() rune {
var r rune
if l.pos >= len(l.input) {
l.width = 0
return -1
}
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
l.pos += l.width
return r
}
func (l *varLexer) run() {
for state := varLexNormal; state != nil; {
state = state(l)
}
}
func (l *varLexer) peek() rune {
r := l.next()
l.backup()
return r
}
func varLexNormal(l *varLexer) lexState {
for {
r := l.next()
switch {
case r == -1:
l.emit(tokEOF)
return nil
case r == '[':
l.emit(tokArrayStart)
case r == ']':
l.emit(tokArrayEnd)
case r == '{':
l.emit(tokDictStart)
case r == '}':
l.emit(tokDictEnd)
case r == '<':
l.emit(tokVariantStart)
case r == '>':
l.emit(tokVariantEnd)
case r == ':':
l.emit(tokColon)
case r == ',':
l.emit(tokComma)
case r == '\'' || r == '"':
l.backup()
return varLexString
case r == '@':
l.backup()
return varLexType
case unicode.IsSpace(r):
l.ignore()
case unicode.IsNumber(r) || r == '+' || r == '-':
l.backup()
return varLexNumber
case r == 'b':
pos := l.start
if n := l.peek(); n == '"' || n == '\'' {
return varLexByteString
}
// not a byte string; try to parse it as a type or bool below
l.pos = pos + 1
l.width = 1
fallthrough
default:
// either a bool or a type. Try bools first.
l.backup()
if l.pos+4 <= len(l.input) {
if l.input[l.pos:l.pos+4] == "true" {
l.pos += 4
l.emit(tokBool)
continue
}
}
if l.pos+5 <= len(l.input) {
if l.input[l.pos:l.pos+5] == "false" {
l.pos += 5
l.emit(tokBool)
continue
}
}
// must be a type.
return varLexType
}
}
}
var varTypeMap = map[string]string{
"boolean": "b",
"byte": "y",
"int16": "n",
"uint16": "q",
"int32": "i",
"uint32": "u",
"int64": "x",
"uint64": "t",
"double": "f",
"string": "s",
"objectpath": "o",
"signature": "g",
}
func varLexByteString(l *varLexer) lexState {
q := l.next()
Loop:
for {
switch l.next() {
case '\\':
if r := l.next(); r != -1 {
break
}
fallthrough
case -1:
return l.errorf("unterminated bytestring")
case q:
break Loop
}
}
l.emit(tokByteString)
return varLexNormal
}
func varLexNumber(l *varLexer) lexState {
l.accept("+-")
digits := "0123456789"
if l.accept("0") {
if l.accept("x") {
digits = "0123456789abcdefABCDEF"
} else {
digits = "01234567"
}
}
for strings.ContainsRune(digits, l.next()) {
}
l.backup()
if l.accept(".") {
for strings.ContainsRune(digits, l.next()) {
}
l.backup()
}
if l.accept("eE") {
l.accept("+-")
for strings.ContainsRune("0123456789", l.next()) {
}
l.backup()
}
if r := l.peek(); unicode.IsLetter(r) {
l.next()
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
}
l.emit(tokNumber)
return varLexNormal
}
func varLexString(l *varLexer) lexState {
q := l.next()
Loop:
for {
switch l.next() {
case '\\':
if r := l.next(); r != -1 {
break
}
fallthrough
case -1:
return l.errorf("unterminated string")
case q:
break Loop
}
}
l.emit(tokString)
return varLexNormal
}
func varLexType(l *varLexer) lexState {
at := l.accept("@")
for {
r := l.next()
if r == -1 {
break
}
if unicode.IsSpace(r) {
l.backup()
break
}
}
if at {
if _, err := ParseSignature(l.input[l.start+1 : l.pos]); err != nil {
return l.errorf("%s", err)
}
} else {
if _, ok := varTypeMap[l.input[l.start:l.pos]]; ok {
l.emit(tokType)
return varLexNormal
}
return l.errorf("unrecognized type %q", l.input[l.start:l.pos])
}
l.emit(tokType)
return varLexNormal
}

817
vendor/github.com/godbus/dbus/v5/variant_parser.go generated vendored Normal file
View File

@@ -0,0 +1,817 @@
package dbus
import (
"bytes"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"strings"
"unicode/utf8"
)
type varParser struct {
tokens []varToken
i int
}
func (p *varParser) backup() {
p.i--
}
func (p *varParser) next() varToken {
if p.i < len(p.tokens) {
t := p.tokens[p.i]
p.i++
return t
}
return varToken{typ: tokEOF}
}
type varNode interface {
Infer() (Signature, error)
String() string
Sigs() sigSet
Value(Signature) (interface{}, error)
}
func varMakeNode(p *varParser) (varNode, error) {
var sig Signature
for {
t := p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokNumber:
return varMakeNumNode(t, sig)
case tokString:
return varMakeStringNode(t, sig)
case tokBool:
if sig.str != "" && sig.str != "b" {
return nil, varTypeError{t.val, sig}
}
b, err := strconv.ParseBool(t.val)
if err != nil {
return nil, err
}
return boolNode(b), nil
case tokArrayStart:
return varMakeArrayNode(p, sig)
case tokVariantStart:
return varMakeVariantNode(p, sig)
case tokDictStart:
return varMakeDictNode(p, sig)
case tokType:
if sig.str != "" {
return nil, errors.New("unexpected type annotation")
}
if t.val[0] == '@' {
sig.str = t.val[1:]
} else {
sig.str = varTypeMap[t.val]
}
case tokByteString:
if sig.str != "" && sig.str != "ay" {
return nil, varTypeError{t.val, sig}
}
b, err := varParseByteString(t.val)
if err != nil {
return nil, err
}
return byteStringNode(b), nil
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
}
}
type varTypeError struct {
val string
sig Signature
}
func (e varTypeError) Error() string {
return fmt.Sprintf("dbus: can't parse %q as type %q", e.val, e.sig.str)
}
type sigSet map[Signature]bool
func (s sigSet) Empty() bool {
return len(s) == 0
}
func (s sigSet) Intersect(s2 sigSet) sigSet {
r := make(sigSet)
for k := range s {
if s2[k] {
r[k] = true
}
}
return r
}
func (s sigSet) Single() (Signature, bool) {
if len(s) == 1 {
for k := range s {
return k, true
}
}
return Signature{}, false
}
func (s sigSet) ToArray() sigSet {
r := make(sigSet, len(s))
for k := range s {
r[Signature{"a" + k.str}] = true
}
return r
}
type numNode struct {
sig Signature
str string
val interface{}
}
var numSigSet = sigSet{
Signature{"y"}: true,
Signature{"n"}: true,
Signature{"q"}: true,
Signature{"i"}: true,
Signature{"u"}: true,
Signature{"x"}: true,
Signature{"t"}: true,
Signature{"d"}: true,
}
func (n numNode) Infer() (Signature, error) {
if strings.ContainsAny(n.str, ".e") {
return Signature{"d"}, nil
}
return Signature{"i"}, nil
}
func (n numNode) String() string {
return n.str
}
func (n numNode) Sigs() sigSet {
if n.sig.str != "" {
return sigSet{n.sig: true}
}
if strings.ContainsAny(n.str, ".e") {
return sigSet{Signature{"d"}: true}
}
return numSigSet
}
func (n numNode) Value(sig Signature) (interface{}, error) {
if n.sig.str != "" && n.sig != sig {
return nil, varTypeError{n.str, sig}
}
if n.val != nil {
return n.val, nil
}
return varNumAs(n.str, sig)
}
func varMakeNumNode(tok varToken, sig Signature) (varNode, error) {
if sig.str == "" {
return numNode{str: tok.val}, nil
}
num, err := varNumAs(tok.val, sig)
if err != nil {
return nil, err
}
return numNode{sig: sig, val: num}, nil
}
func varNumAs(s string, sig Signature) (interface{}, error) {
isUnsigned := false
size := 32
switch sig.str {
case "n":
size = 16
case "i":
case "x":
size = 64
case "y":
size = 8
isUnsigned = true
case "q":
size = 16
isUnsigned = true
case "u":
isUnsigned = true
case "t":
size = 64
isUnsigned = true
case "d":
d, err := strconv.ParseFloat(s, 64)
if err != nil {
return nil, err
}
return d, nil
default:
return nil, varTypeError{s, sig}
}
base := 10
if strings.HasPrefix(s, "0x") {
base = 16
s = s[2:]
}
if strings.HasPrefix(s, "0") && len(s) != 1 {
base = 8
s = s[1:]
}
if isUnsigned {
i, err := strconv.ParseUint(s, base, size)
if err != nil {
return nil, err
}
var v interface{} = i
switch sig.str {
case "y":
v = byte(i)
case "q":
v = uint16(i)
case "u":
v = uint32(i)
}
return v, nil
}
i, err := strconv.ParseInt(s, base, size)
if err != nil {
return nil, err
}
var v interface{} = i
switch sig.str {
case "n":
v = int16(i)
case "i":
v = int32(i)
}
return v, nil
}
type stringNode struct {
sig Signature
str string // parsed
val interface{} // has correct type
}
var stringSigSet = sigSet{
Signature{"s"}: true,
Signature{"g"}: true,
Signature{"o"}: true,
}
func (n stringNode) Infer() (Signature, error) {
return Signature{"s"}, nil
}
func (n stringNode) String() string {
return n.str
}
func (n stringNode) Sigs() sigSet {
if n.sig.str != "" {
return sigSet{n.sig: true}
}
return stringSigSet
}
func (n stringNode) Value(sig Signature) (interface{}, error) {
if n.sig.str != "" && n.sig != sig {
return nil, varTypeError{n.str, sig}
}
if n.val != nil {
return n.val, nil
}
switch {
case sig.str == "g":
return Signature{n.str}, nil
case sig.str == "o":
return ObjectPath(n.str), nil
case sig.str == "s":
return n.str, nil
default:
return nil, varTypeError{n.str, sig}
}
}
func varMakeStringNode(tok varToken, sig Signature) (varNode, error) {
if sig.str != "" && sig.str != "s" && sig.str != "g" && sig.str != "o" {
return nil, fmt.Errorf("invalid type %q for string", sig.str)
}
s, err := varParseString(tok.val)
if err != nil {
return nil, err
}
n := stringNode{str: s}
if sig.str == "" {
return stringNode{str: s}, nil
}
n.sig = sig
switch sig.str {
case "o":
n.val = ObjectPath(s)
case "g":
n.val = Signature{s}
case "s":
n.val = s
}
return n, nil
}
func varParseString(s string) (string, error) {
// quotes are guaranteed to be there
s = s[1 : len(s)-1]
buf := new(bytes.Buffer)
for len(s) != 0 {
r, size := utf8.DecodeRuneInString(s)
if r == utf8.RuneError && size == 1 {
return "", errors.New("invalid UTF-8")
}
s = s[size:]
if r != '\\' {
buf.WriteRune(r)
continue
}
r, size = utf8.DecodeRuneInString(s)
if r == utf8.RuneError && size == 1 {
return "", errors.New("invalid UTF-8")
}
s = s[size:]
switch r {
case 'a':
buf.WriteRune(0x7)
case 'b':
buf.WriteRune(0x8)
case 'f':
buf.WriteRune(0xc)
case 'n':
buf.WriteRune('\n')
case 'r':
buf.WriteRune('\r')
case 't':
buf.WriteRune('\t')
case '\n':
case 'u':
if len(s) < 4 {
return "", errors.New("short unicode escape")
}
r, err := strconv.ParseUint(s[:4], 16, 32)
if err != nil {
return "", err
}
buf.WriteRune(rune(r))
s = s[4:]
case 'U':
if len(s) < 8 {
return "", errors.New("short unicode escape")
}
r, err := strconv.ParseUint(s[:8], 16, 32)
if err != nil {
return "", err
}
buf.WriteRune(rune(r))
s = s[8:]
default:
buf.WriteRune(r)
}
}
return buf.String(), nil
}
var boolSigSet = sigSet{Signature{"b"}: true}
type boolNode bool
func (boolNode) Infer() (Signature, error) {
return Signature{"b"}, nil
}
func (b boolNode) String() string {
if b {
return "true"
}
return "false"
}
func (boolNode) Sigs() sigSet {
return boolSigSet
}
func (b boolNode) Value(sig Signature) (interface{}, error) {
if sig.str != "b" {
return nil, varTypeError{b.String(), sig}
}
return bool(b), nil
}
type arrayNode struct {
set sigSet
children []varNode
val interface{}
}
func (n arrayNode) Infer() (Signature, error) {
for _, v := range n.children {
csig, err := varInfer(v)
if err != nil {
continue
}
return Signature{"a" + csig.str}, nil
}
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
}
func (n arrayNode) String() string {
s := "["
for i, v := range n.children {
s += v.String()
if i != len(n.children)-1 {
s += ", "
}
}
return s + "]"
}
func (n arrayNode) Sigs() sigSet {
return n.set
}
func (n arrayNode) Value(sig Signature) (interface{}, error) {
if n.set.Empty() {
// no type information whatsoever, so this must be an empty slice
return reflect.MakeSlice(typeFor(sig.str), 0, 0).Interface(), nil
}
if !n.set[sig] {
return nil, varTypeError{n.String(), sig}
}
s := reflect.MakeSlice(typeFor(sig.str), len(n.children), len(n.children))
for i, v := range n.children {
rv, err := v.Value(Signature{sig.str[1:]})
if err != nil {
return nil, err
}
s.Index(i).Set(reflect.ValueOf(rv))
}
return s.Interface(), nil
}
func varMakeArrayNode(p *varParser, sig Signature) (varNode, error) {
var n arrayNode
if sig.str != "" {
n.set = sigSet{sig: true}
}
if t := p.next(); t.typ == tokArrayEnd {
return n, nil
} else {
p.backup()
}
Loop:
for {
t := p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
}
p.backup()
cn, err := varMakeNode(p)
if err != nil {
return nil, err
}
if cset := cn.Sigs(); !cset.Empty() {
if n.set.Empty() {
n.set = cset.ToArray()
} else {
nset := cset.ToArray().Intersect(n.set)
if nset.Empty() {
return nil, fmt.Errorf("can't parse %q with given type information", cn.String())
}
n.set = nset
}
}
n.children = append(n.children, cn)
switch t := p.next(); t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokArrayEnd:
break Loop
case tokComma:
continue
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
}
return n, nil
}
type variantNode struct {
n varNode
}
var variantSet = sigSet{
Signature{"v"}: true,
}
func (variantNode) Infer() (Signature, error) {
return Signature{"v"}, nil
}
func (n variantNode) String() string {
return "<" + n.n.String() + ">"
}
func (variantNode) Sigs() sigSet {
return variantSet
}
func (n variantNode) Value(sig Signature) (interface{}, error) {
if sig.str != "v" {
return nil, varTypeError{n.String(), sig}
}
sig, err := varInfer(n.n)
if err != nil {
return nil, err
}
v, err := n.n.Value(sig)
if err != nil {
return nil, err
}
return MakeVariant(v), nil
}
func varMakeVariantNode(p *varParser, sig Signature) (varNode, error) {
n, err := varMakeNode(p)
if err != nil {
return nil, err
}
if t := p.next(); t.typ != tokVariantEnd {
return nil, fmt.Errorf("unexpected %q", t.val)
}
vn := variantNode{n}
if sig.str != "" && sig.str != "v" {
return nil, varTypeError{vn.String(), sig}
}
return variantNode{n}, nil
}
type dictEntry struct {
key, val varNode
}
type dictNode struct {
kset, vset sigSet
children []dictEntry
val interface{}
}
func (n dictNode) Infer() (Signature, error) {
for _, v := range n.children {
ksig, err := varInfer(v.key)
if err != nil {
continue
}
vsig, err := varInfer(v.val)
if err != nil {
continue
}
return Signature{"a{" + ksig.str + vsig.str + "}"}, nil
}
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
}
func (n dictNode) String() string {
s := "{"
for i, v := range n.children {
s += v.key.String() + ": " + v.val.String()
if i != len(n.children)-1 {
s += ", "
}
}
return s + "}"
}
func (n dictNode) Sigs() sigSet {
r := sigSet{}
for k := range n.kset {
for v := range n.vset {
sig := "a{" + k.str + v.str + "}"
r[Signature{sig}] = true
}
}
return r
}
func (n dictNode) Value(sig Signature) (interface{}, error) {
set := n.Sigs()
if set.Empty() {
// no type information -> empty dict
return reflect.MakeMap(typeFor(sig.str)).Interface(), nil
}
if !set[sig] {
return nil, varTypeError{n.String(), sig}
}
m := reflect.MakeMap(typeFor(sig.str))
ksig := Signature{sig.str[2:3]}
vsig := Signature{sig.str[3 : len(sig.str)-1]}
for _, v := range n.children {
kv, err := v.key.Value(ksig)
if err != nil {
return nil, err
}
vv, err := v.val.Value(vsig)
if err != nil {
return nil, err
}
m.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
}
return m.Interface(), nil
}
func varMakeDictNode(p *varParser, sig Signature) (varNode, error) {
var n dictNode
if sig.str != "" {
if len(sig.str) < 5 {
return nil, fmt.Errorf("invalid signature %q for dict type", sig)
}
ksig := Signature{string(sig.str[2])}
vsig := Signature{sig.str[3 : len(sig.str)-1]}
n.kset = sigSet{ksig: true}
n.vset = sigSet{vsig: true}
}
if t := p.next(); t.typ == tokDictEnd {
return n, nil
} else {
p.backup()
}
Loop:
for {
t := p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
}
p.backup()
kn, err := varMakeNode(p)
if err != nil {
return nil, err
}
if kset := kn.Sigs(); !kset.Empty() {
if n.kset.Empty() {
n.kset = kset
} else {
n.kset = kset.Intersect(n.kset)
if n.kset.Empty() {
return nil, fmt.Errorf("can't parse %q with given type information", kn.String())
}
}
}
t = p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokColon:
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
t = p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
}
p.backup()
vn, err := varMakeNode(p)
if err != nil {
return nil, err
}
if vset := vn.Sigs(); !vset.Empty() {
if n.vset.Empty() {
n.vset = vset
} else {
n.vset = n.vset.Intersect(vset)
if n.vset.Empty() {
return nil, fmt.Errorf("can't parse %q with given type information", vn.String())
}
}
}
n.children = append(n.children, dictEntry{kn, vn})
t = p.next()
switch t.typ {
case tokEOF:
return nil, io.ErrUnexpectedEOF
case tokError:
return nil, errors.New(t.val)
case tokDictEnd:
break Loop
case tokComma:
continue
default:
return nil, fmt.Errorf("unexpected %q", t.val)
}
}
return n, nil
}
type byteStringNode []byte
var byteStringSet = sigSet{
Signature{"ay"}: true,
}
func (byteStringNode) Infer() (Signature, error) {
return Signature{"ay"}, nil
}
func (b byteStringNode) String() string {
return string(b)
}
func (b byteStringNode) Sigs() sigSet {
return byteStringSet
}
func (b byteStringNode) Value(sig Signature) (interface{}, error) {
if sig.str != "ay" {
return nil, varTypeError{b.String(), sig}
}
return []byte(b), nil
}
func varParseByteString(s string) ([]byte, error) {
// quotes and b at start are guaranteed to be there
b := make([]byte, 0, 1)
s = s[2 : len(s)-1]
for len(s) != 0 {
c := s[0]
s = s[1:]
if c != '\\' {
b = append(b, c)
continue
}
c = s[0]
s = s[1:]
switch c {
case 'a':
b = append(b, 0x7)
case 'b':
b = append(b, 0x8)
case 'f':
b = append(b, 0xc)
case 'n':
b = append(b, '\n')
case 'r':
b = append(b, '\r')
case 't':
b = append(b, '\t')
case 'x':
if len(s) < 2 {
return nil, errors.New("short escape")
}
n, err := strconv.ParseUint(s[:2], 16, 8)
if err != nil {
return nil, err
}
b = append(b, byte(n))
s = s[2:]
case '0':
if len(s) < 3 {
return nil, errors.New("short escape")
}
n, err := strconv.ParseUint(s[:3], 8, 8)
if err != nil {
return nil, err
}
b = append(b, byte(n))
s = s[3:]
default:
b = append(b, c)
}
}
return append(b, 0), nil
}
func varInfer(n varNode) (Signature, error) {
if sig, ok := n.Sigs().Single(); ok {
return sig, nil
}
return n.Infer()
}

10
vendor/modules.txt vendored
View File

@@ -1,3 +1,13 @@
# fyne.io/systray v1.12.0
## explicit; go 1.19
fyne.io/systray
fyne.io/systray/internal/generated/menu
fyne.io/systray/internal/generated/notifier
# github.com/godbus/dbus/v5 v5.1.0
## explicit; go 1.12
github.com/godbus/dbus/v5
github.com/godbus/dbus/v5/introspect
github.com/godbus/dbus/v5/prop
# github.com/mattn/go-sqlite3 v1.14.24 # github.com/mattn/go-sqlite3 v1.14.24
## explicit; go 1.19 ## explicit; go 1.19
github.com/mattn/go-sqlite3 github.com/mattn/go-sqlite3