5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-05 06:41:37 +08:00
wails/v3/pkg/application/systemtray_darwin.go
2023-08-05 14:04:49 +10:00

246 lines
6.0 KiB
Go

//go:build darwin
package application
/*
#cgo CFLAGS: -mmacosx-version-min=10.13 -x objective-c
#cgo LDFLAGS: -framework Cocoa -framework WebKit
#include "Cocoa/Cocoa.h"
#include "menuitem_darwin.h"
#include "systemtray_darwin.h"
*/
import "C"
import (
"github.com/leaanthony/go-ansi-parser"
"unsafe"
)
type macosSystemTray struct {
id uint
label string
icon []byte
menu *Menu
nsStatusItem unsafe.Pointer
nsImage unsafe.Pointer
nsMenu unsafe.Pointer
iconPosition int
isTemplateIcon bool
parent *SystemTray
}
type button int
const (
leftButtonDown button = 1
rightButtonDown button = 3
)
// system tray map
var systemTrayMap = make(map[uint]*macosSystemTray)
//export systrayClickCallback
func systrayClickCallback(id C.long, buttonID C.int) {
// Get the system tray
systemTray := systemTrayMap[uint(id)]
if systemTray == nil {
println("system tray not found")
return
}
systemTray.processClick(button(buttonID))
}
func (s *macosSystemTray) setIconPosition(position int) {
s.iconPosition = position
}
func (s *macosSystemTray) setMenu(menu *Menu) {
s.menu = menu
}
func (s *macosSystemTray) positionWindow(window *WebviewWindow, offset int) error {
// Get the trayBounds of this system tray
trayBounds, err := s.bounds()
if err != nil {
return err
}
// Get the current screen trayBounds
currentScreen, err := s.getScreen()
if err != nil {
return err
}
screenBounds := currentScreen.Bounds
// Get the center height of the window
windowWidthCenter := window.Width() / 2
// Get the center height of the system tray
systemTrayWidthCenter := trayBounds.Width / 2
// The Y will be 0 and the X will make the center of the window line up with the center of the system tray
windowX := trayBounds.X + systemTrayWidthCenter - windowWidthCenter
// If the end of the window goes off-screen, move it back enough to be on screen
if windowX+window.Width() > screenBounds.Width {
windowX = screenBounds.Width - window.Width()
}
window.SetRelativePosition(windowX, int(C.statusBarHeight())+offset)
return nil
}
func (s *macosSystemTray) getScreen() (*Screen, error) {
return getScreenForSystray(s)
}
func (s *macosSystemTray) bounds() (*Rect, error) {
var rect C.NSRect
C.systemTrayGetBounds(s.nsStatusItem, &rect)
// Get the screen height for the screen that the systray is on
screen, err := getScreenForSystray(s)
if err != nil {
return nil, err
}
// Invert Y axis based on screen height
rect.origin.y = C.double(screen.Bounds.Height) - rect.origin.y - rect.size.height
return &Rect{
X: int(rect.origin.x),
Y: int(rect.origin.y),
Width: int(rect.size.width),
Height: int(rect.size.height),
}, nil
}
func (s *macosSystemTray) run() {
globalApplication.dispatchOnMainThread(func() {
if s.nsStatusItem != nil {
Fatal("System tray '%d' already running", s.id)
}
s.nsStatusItem = unsafe.Pointer(C.systemTrayNew(C.long(s.id)))
if s.label != "" {
s.setLabel(s.label)
}
if s.icon != nil {
s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&s.icon[0]), C.int(len(s.icon))))
C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon))
}
if s.menu != nil {
s.menu.Update()
// Convert impl to macosMenu object
s.nsMenu = (s.menu.impl).(*macosMenu).nsMenu
// We only set the tray menu if we don't have an attached
// window. If we do, then we manually operate the menu using
// the right mouse button
if s.parent.attachedWindow.Window == nil {
C.systemTraySetMenu(s.nsStatusItem, s.nsMenu)
}
}
})
}
func (s *macosSystemTray) setIcon(icon []byte) {
s.icon = icon
globalApplication.dispatchOnMainThread(func() {
s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon))))
C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon))
})
}
func (s *macosSystemTray) setDarkModeIcon(icon []byte) {
s.setIcon(icon)
}
func (s *macosSystemTray) setTemplateIcon(icon []byte) {
s.icon = icon
s.isTemplateIcon = true
globalApplication.dispatchOnMainThread(func() {
s.nsImage = unsafe.Pointer(C.imageFromBytes((*C.uchar)(&icon[0]), C.int(len(icon))))
C.systemTraySetIcon(s.nsStatusItem, s.nsImage, C.int(s.iconPosition), C.bool(s.isTemplateIcon))
})
}
func newSystemTrayImpl(s *SystemTray) systemTrayImpl {
result := &macosSystemTray{
parent: s,
id: s.id,
label: s.label,
icon: s.icon,
menu: s.menu,
iconPosition: s.iconPosition,
isTemplateIcon: s.isTemplateIcon,
}
systemTrayMap[s.id] = result
return result
}
func (s *macosSystemTray) setLabel(label string) {
s.label = label
if !ansi.HasEscapeCodes(label) {
C.systemTraySetLabel(s.nsStatusItem, C.CString(label))
} else {
parsed, err := ansi.Parse(label)
if err != nil {
C.systemTraySetLabel(s.nsStatusItem, C.CString(label))
return
}
//TODO: Support more than one colour
newLabel := parsed[0].Label
var FG string
if parsed[0].FgCol != nil {
FG = parsed[0].FgCol.Hex
}
var BG string
if parsed[0].BgCol != nil {
BG = parsed[0].BgCol.Hex
}
C.systemTraySetANSILabel(s.nsStatusItem, C.CString(newLabel), C.CString(FG), C.CString(BG))
}
}
func (s *macosSystemTray) destroy() {
// Remove the status item from the status bar and its associated menu
C.systemTrayDestroy(s.nsStatusItem)
}
func (s *macosSystemTray) processClick(b button) {
switch b {
case leftButtonDown:
// Check if we have a callback
if s.parent.clickHandler != nil {
s.parent.clickHandler()
return
}
if s.parent.attachedWindow.Window != nil {
s.parent.defaultClickHandler()
return
}
if s.menu != nil {
C.showMenu(s.nsStatusItem, s.nsMenu)
}
case rightButtonDown:
// Check if we have a callback
if s.parent.rightClickHandler != nil {
s.parent.rightClickHandler()
return
}
if s.menu != nil {
if s.parent.attachedWindow.Window != nil {
s.parent.attachedWindow.Window.Hide()
}
C.showMenu(s.nsStatusItem, s.nsMenu)
return
}
if s.parent.attachedWindow.Window != nil {
s.parent.defaultClickHandler()
}
}
}