mirror of
https://gitee.com/energye/energy.git
synced 2024-12-04 20:58:21 +08:00
352 lines
8.1 KiB
Go
352 lines
8.1 KiB
Go
//----------------------------------------
|
|
//
|
|
// Copyright © yanghy. All Rights Reserved.
|
|
//
|
|
// Licensed under Apache License Version 2.0, January 2004
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
//----------------------------------------
|
|
|
|
//go:build linux || freebsd || openbsd || netbsd
|
|
// +build linux freebsd openbsd netbsd
|
|
|
|
package systray
|
|
|
|
import (
|
|
"github.com/godbus/dbus/v5"
|
|
"log"
|
|
|
|
"github.com/godbus/dbus/v5/prop"
|
|
|
|
"github.com/energye/energy/v2/pkgs/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()
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
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) {
|
|
if eventID == "clicked" {
|
|
systrayMenuItemSelected(uint32(id))
|
|
}
|
|
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) {
|
|
instance.menuLock.Lock()
|
|
defer instance.menuLock.Unlock()
|
|
layout := &menuLayout{
|
|
V0: int32(id),
|
|
V1: map[string]dbus.Variant{
|
|
"type": dbus.MakeVariant("separator"),
|
|
},
|
|
V2: []dbus.Variant{},
|
|
}
|
|
instance.menu.V2 = append(instance.menu.V2, dbus.MakeVariant(layout))
|
|
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() {
|
|
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: %s\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: %s\n", err)
|
|
}
|
|
|
|
}
|
|
|
|
func resetMenu() {
|
|
instance.menuLock.Lock()
|
|
defer instance.menuLock.Unlock()
|
|
instance.menu = &menuLayout{}
|
|
instance.menuVersion++
|
|
refresh()
|
|
}
|