| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- //go:build linux || freebsd || openbsd || netbsd
- // +build linux freebsd openbsd netbsd
- package systray
- import (
- "log"
- "github.com/godbus/dbus/v5"
- "github.com/godbus/dbus/v5/prop"
- "fyne.io/systray/internal/generated/menu"
- )
- // SetIcon sets the icon of a menu item.
- // 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, 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() {
- 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()
- }
|