Add display public API wrappers
This commit is contained in:
parent
7aaabf4b77
commit
d080c6f50c
1 changed files with 586 additions and 0 deletions
586
pkg/display/api.go
Normal file
586
pkg/display/api.go
Normal file
|
|
@ -0,0 +1,586 @@
|
|||
package display
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
"forge.lthn.ai/core/gui/pkg/clipboard"
|
||||
"forge.lthn.ai/core/gui/pkg/dialog"
|
||||
"forge.lthn.ai/core/gui/pkg/environment"
|
||||
"forge.lthn.ai/core/gui/pkg/notification"
|
||||
"forge.lthn.ai/core/gui/pkg/screen"
|
||||
"forge.lthn.ai/core/gui/pkg/systray"
|
||||
)
|
||||
|
||||
// Screen is the public display-screen shape used by the display service API.
|
||||
type Screen struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
ScaleFactor float64 `json:"scaleFactor"`
|
||||
IsPrimary bool `json:"isPrimary"`
|
||||
}
|
||||
|
||||
// WorkArea is a usable screen rectangle.
|
||||
type WorkArea = screen.Rect
|
||||
|
||||
// FileFilter describes a file type filter for the display service dialogs.
|
||||
type FileFilter struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
Pattern string `json:"pattern"`
|
||||
}
|
||||
|
||||
// OpenFileOptions configures an open-file dialog.
|
||||
type OpenFileOptions struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
DefaultDirectory string `json:"defaultDirectory,omitempty"`
|
||||
DefaultFilename string `json:"defaultFilename,omitempty"`
|
||||
AllowMultiple bool `json:"allowMultiple,omitempty"`
|
||||
Filters []FileFilter `json:"filters,omitempty"`
|
||||
}
|
||||
|
||||
// SaveFileOptions configures a save-file dialog.
|
||||
type SaveFileOptions struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
DefaultDirectory string `json:"defaultDirectory,omitempty"`
|
||||
DefaultFilename string `json:"defaultFilename,omitempty"`
|
||||
Filters []FileFilter `json:"filters,omitempty"`
|
||||
}
|
||||
|
||||
// OpenDirectoryOptions configures a folder picker dialog.
|
||||
type OpenDirectoryOptions struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
DefaultDirectory string `json:"defaultDirectory,omitempty"`
|
||||
}
|
||||
|
||||
// TrayMenuItem describes a tray menu entry.
|
||||
type TrayMenuItem struct {
|
||||
Label string `json:"label,omitempty"`
|
||||
ActionID string `json:"actionId,omitempty"`
|
||||
IsSeparator bool `json:"isSeparator,omitempty"`
|
||||
Children []TrayMenuItem `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
// NotificationOptions configures a native notification.
|
||||
type NotificationOptions struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Subtitle string `json:"subtitle,omitempty"`
|
||||
}
|
||||
|
||||
// Theme reports whether the active theme is dark.
|
||||
type Theme struct {
|
||||
IsDark bool `json:"isDark"`
|
||||
}
|
||||
|
||||
func (s *Service) GetScreens() []*Screen {
|
||||
r := s.Core().QUERY(screen.QueryAll{})
|
||||
if !r.OK {
|
||||
return nil
|
||||
}
|
||||
screens, ok := r.Value.([]screen.Screen)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
result := make([]*Screen, 0, len(screens))
|
||||
for i := range screens {
|
||||
result = append(result, screenToDisplay(&screens[i]))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *Service) GetScreen(id string) (*Screen, error) {
|
||||
r := s.Core().QUERY(screen.QueryByID{ID: id})
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
scr, _ := r.Value.(*screen.Screen)
|
||||
return screenToDisplay(scr), nil
|
||||
}
|
||||
|
||||
func (s *Service) GetPrimaryScreen() (*Screen, error) {
|
||||
r := s.Core().QUERY(screen.QueryPrimary{})
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
scr, _ := r.Value.(*screen.Screen)
|
||||
return screenToDisplay(scr), nil
|
||||
}
|
||||
|
||||
func (s *Service) GetScreenAtPoint(x, y int) (*Screen, error) {
|
||||
r := s.Core().QUERY(screen.QueryAtPoint{X: x, Y: y})
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
scr, _ := r.Value.(*screen.Screen)
|
||||
return screenToDisplay(scr), nil
|
||||
}
|
||||
|
||||
func (s *Service) GetScreenForWindow(name string) (*Screen, error) {
|
||||
info, err := s.GetWindowInfo(name)
|
||||
if err != nil || info == nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.GetScreenAtPoint(info.X+max(info.Width/2, 1), info.Y+max(info.Height/2, 1))
|
||||
}
|
||||
|
||||
func (s *Service) GetWorkAreas() []*WorkArea {
|
||||
r := s.Core().QUERY(screen.QueryWorkAreas{})
|
||||
if !r.OK {
|
||||
return nil
|
||||
}
|
||||
areas, ok := r.Value.([]screen.Rect)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
result := make([]*WorkArea, 0, len(areas))
|
||||
for i := range areas {
|
||||
area := areas[i]
|
||||
result = append(result, &area)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *Service) OpenSingleFileDialog(opts OpenFileOptions) (string, error) {
|
||||
paths, err := s.OpenFileDialog(opts)
|
||||
if err != nil || len(paths) == 0 {
|
||||
return "", err
|
||||
}
|
||||
return paths[0], nil
|
||||
}
|
||||
|
||||
func (s *Service) OpenFileDialog(opts OpenFileOptions) ([]string, error) {
|
||||
result := s.Core().Action("dialog.openFile").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: dialog.TaskOpenFile{Options: toDialogOpenFileOptions(opts)}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, coreerr.E("display.OpenFileDialog", "dialog.openFile action failed", nil)
|
||||
}
|
||||
paths, _ := result.Value.([]string)
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func (s *Service) SaveFileDialog(opts SaveFileOptions) (string, error) {
|
||||
result := s.Core().Action("dialog.saveFile").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: dialog.TaskSaveFile{Options: toDialogSaveFileOptions(opts)}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return "", err
|
||||
}
|
||||
return "", coreerr.E("display.SaveFileDialog", "dialog.saveFile action failed", nil)
|
||||
}
|
||||
path, _ := result.Value.(string)
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (s *Service) OpenDirectoryDialog(opts OpenDirectoryOptions) (string, error) {
|
||||
result := s.Core().Action("dialog.openDirectory").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: dialog.TaskOpenDirectory{Options: toDialogOpenDirectoryOptions(opts)}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return "", err
|
||||
}
|
||||
return "", coreerr.E("display.OpenDirectoryDialog", "dialog.openDirectory action failed", nil)
|
||||
}
|
||||
path, _ := result.Value.(string)
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (s *Service) ConfirmDialog(title, message string) (bool, error) {
|
||||
result := s.Core().Action("dialog.question").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: dialog.TaskQuestion{
|
||||
Title: title,
|
||||
Message: message,
|
||||
Buttons: []string{"Yes", "No"},
|
||||
}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return false, err
|
||||
}
|
||||
return false, coreerr.E("display.ConfirmDialog", "dialog.question action failed", nil)
|
||||
}
|
||||
button, _ := result.Value.(string)
|
||||
return button == "Yes", nil
|
||||
}
|
||||
|
||||
func (s *Service) PromptDialog(title, message string) (string, bool, error) {
|
||||
result := s.Core().Action("dialog.prompt").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: dialog.TaskPrompt{Title: title, Message: message}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return "", false, err
|
||||
}
|
||||
return "", false, coreerr.E("display.PromptDialog", "dialog.prompt action failed", nil)
|
||||
}
|
||||
prompt, ok := result.Value.(dialog.PromptResult)
|
||||
if !ok {
|
||||
return "", false, coreerr.E("display.PromptDialog", "unexpected result type", nil)
|
||||
}
|
||||
return prompt.Value, prompt.Confirmed, nil
|
||||
}
|
||||
|
||||
func (s *Service) SetTrayIcon(icon []byte) error {
|
||||
result := s.Core().Action("systray.setIcon").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: systray.TaskSetTrayIcon{Data: icon}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.SetTrayIcon", "systray.setIcon action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SetTrayTooltip(tooltip string) error {
|
||||
result := s.Core().Action("systray.setTooltip").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: systray.TaskSetTrayTooltip{Tooltip: tooltip}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.SetTrayTooltip", "systray.setTooltip action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SetTrayLabel(label string) error {
|
||||
result := s.Core().Action("systray.setLabel").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: systray.TaskSetTrayLabel{Label: label}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.SetTrayLabel", "systray.setLabel action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SetTrayMenu(items []TrayMenuItem) error {
|
||||
result := s.Core().Action("systray.setMenu").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: systray.TaskSetTrayMenu{Items: trayMenuItemsToSystray(items)}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.SetTrayMenu", "systray.setMenu action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetTrayInfo() map[string]any {
|
||||
r := s.Core().QUERY(systray.QueryInfo{})
|
||||
if !r.OK {
|
||||
return nil
|
||||
}
|
||||
info, _ := r.Value.(map[string]any)
|
||||
return info
|
||||
}
|
||||
|
||||
func (s *Service) ShowTrayMessage(title, message string) error {
|
||||
result := s.Core().Action("systray.showMessage").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: systray.TaskShowMessage{Title: title, Message: message}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.ShowTrayMessage", "systray.showMessage action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) ReadClipboard() (string, error) {
|
||||
r := s.Core().QUERY(clipboard.QueryText{})
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
return "", err
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
content, ok := r.Value.(clipboard.ClipboardContent)
|
||||
if !ok {
|
||||
return "", coreerr.E("display.ReadClipboard", "unexpected result type", nil)
|
||||
}
|
||||
return content.Text, nil
|
||||
}
|
||||
|
||||
func (s *Service) WriteClipboard(text string) error {
|
||||
result := s.Core().Action("clipboard.setText").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: clipboard.TaskSetText{Text: text}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.WriteClipboard", "clipboard.setText action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) HasClipboard() bool {
|
||||
text, err := s.ReadClipboard()
|
||||
return err == nil && text != ""
|
||||
}
|
||||
|
||||
func (s *Service) ClearClipboard() error {
|
||||
result := s.Core().Action("clipboard.clear").Run(context.Background(), core.NewOptions())
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.ClearClipboard", "clipboard.clear action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) ReadClipboardImage() ([]byte, error) {
|
||||
r := s.Core().QUERY(clipboard.QueryImage{})
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
content, ok := r.Value.(clipboard.ImageContent)
|
||||
if !ok {
|
||||
return nil, coreerr.E("display.ReadClipboardImage", "unexpected result type", nil)
|
||||
}
|
||||
if !content.HasImage {
|
||||
return nil, nil
|
||||
}
|
||||
return append([]byte(nil), content.Data...), nil
|
||||
}
|
||||
|
||||
func (s *Service) WriteClipboardImage(data []byte) error {
|
||||
if len(data) == 0 {
|
||||
return coreerr.E("display.WriteClipboardImage", "clipboard image data is required", nil)
|
||||
}
|
||||
if len(data) > clipboard.MaxImageBytes {
|
||||
return coreerr.E("display.WriteClipboardImage", "clipboard image exceeds maximum size", nil)
|
||||
}
|
||||
result := s.Core().Action("clipboard.setImage").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "data", Value: append([]byte(nil), data...)},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.WriteClipboardImage", "clipboard.setImage action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) ShowNotification(opts NotificationOptions) error {
|
||||
return s.sendNotification(notification.NotificationOptions{
|
||||
ID: opts.ID,
|
||||
Title: opts.Title,
|
||||
Message: opts.Message,
|
||||
Subtitle: opts.Subtitle,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) ShowInfoNotification(title, message string) error {
|
||||
return s.sendNotification(notification.NotificationOptions{Title: title, Message: message})
|
||||
}
|
||||
|
||||
func (s *Service) ShowWarningNotification(title, message string) error {
|
||||
return s.sendNotification(notification.NotificationOptions{
|
||||
Title: title,
|
||||
Message: message,
|
||||
Severity: notification.SeverityWarning,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) ShowErrorNotification(title, message string) error {
|
||||
return s.sendNotification(notification.NotificationOptions{
|
||||
Title: title,
|
||||
Message: message,
|
||||
Severity: notification.SeverityError,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) RequestNotificationPermission() (bool, error) {
|
||||
r := s.Core().Action("notification.requestPermission").Run(context.Background(), core.NewOptions())
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
return false, err
|
||||
}
|
||||
return false, coreerr.E("display.RequestNotificationPermission", "notification.requestPermission action failed", nil)
|
||||
}
|
||||
granted, _ := r.Value.(bool)
|
||||
return granted, nil
|
||||
}
|
||||
|
||||
func (s *Service) CheckNotificationPermission() (bool, error) {
|
||||
r := s.Core().QUERY(notification.QueryPermission{})
|
||||
if !r.OK {
|
||||
if err, ok := r.Value.(error); ok {
|
||||
return false, err
|
||||
}
|
||||
return false, coreerr.E("display.CheckNotificationPermission", "notification query failed", nil)
|
||||
}
|
||||
status, ok := r.Value.(notification.PermissionStatus)
|
||||
if !ok {
|
||||
return false, coreerr.E("display.CheckNotificationPermission", "unexpected result type", nil)
|
||||
}
|
||||
return status.Granted, nil
|
||||
}
|
||||
|
||||
func (s *Service) ClearNotifications() error {
|
||||
result := s.Core().Action("notification.clear").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: notification.TaskClear{}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.ClearNotifications", "notification.clear action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SetTheme(theme string) error {
|
||||
result := s.Core().Action("environment.setTheme").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: environment.TaskSetTheme{Theme: theme}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.SetTheme", "environment.setTheme action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetTheme() *Theme {
|
||||
r := s.Core().QUERY(environment.QueryTheme{})
|
||||
if !r.OK {
|
||||
return nil
|
||||
}
|
||||
info, _ := r.Value.(environment.ThemeInfo)
|
||||
return &Theme{IsDark: info.IsDark}
|
||||
}
|
||||
|
||||
func (s *Service) GetSystemTheme() string {
|
||||
r := s.Core().QUERY(environment.QueryTheme{})
|
||||
if !r.OK {
|
||||
return ""
|
||||
}
|
||||
info, _ := r.Value.(environment.ThemeInfo)
|
||||
return info.Theme
|
||||
}
|
||||
|
||||
func (s *Service) sendNotification(opts notification.NotificationOptions) error {
|
||||
result := s.Core().Action("notification.send").Run(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "task", Value: notification.TaskSend{Options: opts}},
|
||||
))
|
||||
if !result.OK {
|
||||
if err, ok := result.Value.(error); ok {
|
||||
return err
|
||||
}
|
||||
return coreerr.E("display.sendNotification", "notification.send action failed", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func screenToDisplay(scr *screen.Screen) *Screen {
|
||||
if scr == nil {
|
||||
return nil
|
||||
}
|
||||
return &Screen{
|
||||
ID: scr.ID,
|
||||
Name: scr.Name,
|
||||
X: scr.Bounds.X,
|
||||
Y: scr.Bounds.Y,
|
||||
Width: scr.Bounds.Width,
|
||||
Height: scr.Bounds.Height,
|
||||
ScaleFactor: scr.ScaleFactor,
|
||||
IsPrimary: scr.IsPrimary,
|
||||
}
|
||||
}
|
||||
|
||||
func toDialogOpenFileOptions(opts OpenFileOptions) dialog.OpenFileOptions {
|
||||
return dialog.OpenFileOptions{
|
||||
Title: opts.Title,
|
||||
Directory: opts.DefaultDirectory,
|
||||
Filename: opts.DefaultFilename,
|
||||
AllowMultiple: opts.AllowMultiple,
|
||||
Filters: toDialogFileFilters(opts.Filters),
|
||||
}
|
||||
}
|
||||
|
||||
func toDialogSaveFileOptions(opts SaveFileOptions) dialog.SaveFileOptions {
|
||||
return dialog.SaveFileOptions{
|
||||
Title: opts.Title,
|
||||
Directory: opts.DefaultDirectory,
|
||||
Filename: opts.DefaultFilename,
|
||||
Filters: toDialogFileFilters(opts.Filters),
|
||||
}
|
||||
}
|
||||
|
||||
func toDialogOpenDirectoryOptions(opts OpenDirectoryOptions) dialog.OpenDirectoryOptions {
|
||||
return dialog.OpenDirectoryOptions{
|
||||
Title: opts.Title,
|
||||
Directory: opts.DefaultDirectory,
|
||||
}
|
||||
}
|
||||
|
||||
func toDialogFileFilters(filters []FileFilter) []dialog.FileFilter {
|
||||
if len(filters) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]dialog.FileFilter, 0, len(filters))
|
||||
for _, filter := range filters {
|
||||
result = append(result, dialog.FileFilter{
|
||||
DisplayName: filter.DisplayName,
|
||||
Pattern: filter.Pattern,
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func trayMenuItemsToSystray(items []TrayMenuItem) []systray.TrayMenuItem {
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]systray.TrayMenuItem, 0, len(items))
|
||||
for _, item := range items {
|
||||
converted := systray.TrayMenuItem{
|
||||
Label: item.Label,
|
||||
ActionID: item.ActionID,
|
||||
}
|
||||
if item.IsSeparator {
|
||||
converted.Type = "separator"
|
||||
}
|
||||
if len(item.Children) > 0 {
|
||||
converted.Submenu = trayMenuItemsToSystray(item.Children)
|
||||
}
|
||||
result = append(result, converted)
|
||||
}
|
||||
return result
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue