Skip to content

Demystify CGO usage #69

@sarumaj

Description

@sarumaj

I was wondering if one could replace the CGO binding on some platforms by using ebitengine/purego?

Here an example for clipboard_darwin.go (not tested though, generated with an LLM to give an idea):

package systray

import (
	"time"
	"unsafe"

	"github.com/ebitengine/purego"
	"github.com/ebitengine/purego/objc"
)

var st = &systray{}

type systray struct {
}

// Objective-C selectors
var (
	showMenuSelector          objc.Selector
	setIconSelector           objc.Selector
	setMenuItemIconSelector   objc.Selector
	registerSystraySelector   objc.Selector
	nativeLoopSelector        objc.Selector
	nativeEndSelector         objc.Selector
	nativeStartSelector       objc.Selector
	quitSelector              objc.Selector
	setInternalLoopSelector   objc.Selector
	createMenuSelector        objc.Selector
	setMenuNilSelector        objc.Selector
	resetMenuSelector         objc.Selector
	addOrUpdateMenuItemSelector objc.Selector
)

func init() {
	objc.LoadFramework("Cocoa", "/System/Library/Frameworks/Cocoa.framework")

	showMenuSelector = objc.GetSelector("showMenu")
	setIconSelector = objc.GetSelector("setIcon:withLength:template:")
	setMenuItemIconSelector = objc.GetSelector("setMenuItemIcon:withLength:id:template:")
	registerSystraySelector = objc.GetSelector("registerSystray")
	nativeLoopSelector = objc.GetSelector("nativeLoop")
	nativeEndSelector = objc.GetSelector("nativeEnd")
	nativeStartSelector = objc.GetSelector("nativeStart")
	quitSelector = objc.GetSelector("quit")
	setInternalLoopSelector = objc.GetSelector("setInternalLoop:")
	createMenuSelector = objc.GetSelector("createMenu")
	setMenuNilSelector = objc.GetSelector("setMenuNil")
	resetMenuSelector = objc.GetSelector("resetMenu")
	addOrUpdateMenuItemSelector = objc.GetSelector("addOrUpdateMenuItemWithID:parentID:title:tooltip:shortcut:disabled:checked:checkable:")
}

func (m *systray) ShowMenu() error {
	objc.Call("SystrayManager", showMenuSelector)
	return nil
}

func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
	iconPtr := unsafe.Pointer(&templateIconBytes[0])
	objc.Call("SystrayManager", setIconSelector, iconPtr, len(templateIconBytes), true)
}

func (item *MenuItem) SetIcon(iconBytes []byte) {
	iconPtr := unsafe.Pointer(&iconBytes[0])
	objc.Call("SystrayManager", setMenuItemIconSelector, iconPtr, len(iconBytes), item.id, false)
}

func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
	iconPtr := unsafe.Pointer(&templateIconBytes[0])
	objc.Call("SystrayManager", setMenuItemIconSelector, iconPtr, len(templateIconBytes), item.id, true)
}

func registerSystray() {
	objc.Call("SystrayManager", registerSystraySelector)
}

func nativeLoop() {
	objc.Call("SystrayManager", nativeLoopSelector)
}

func nativeEnd() {
	objc.Call("SystrayManager", nativeEndSelector)
}

func nativeStart() {
	objc.Call("SystrayManager", nativeStartSelector)
}

func quit() {
	objc.Call("SystrayManager", quitSelector)
}

func setInternalLoop(internal bool) {
	objc.Call("SystrayManager", setInternalLoopSelector, internal)
}

func CreateMenu() {
	objc.Call("SystrayManager", createMenuSelector)
}

func SetMenuNil() {
	objc.Call("SystrayManager", setMenuNilSelector)
}

func SetIcon(iconBytes []byte) {
	iconPtr := unsafe.Pointer(&iconBytes[0])
	objc.Call("SystrayManager", setIconSelector, iconPtr, len(iconBytes), false)
}

func SetTitle(title string) {
	titlePtr := objc.StringToNSString(title)
	objc.Call("SystrayManager", "setTitle:", titlePtr)
}

func SetTooltip(tooltip string) {
	tooltipPtr := objc.StringToNSString(tooltip)
	objc.Call("SystrayManager", "setTooltip:", tooltipPtr)
}

func addOrUpdateMenuItem(item *MenuItem) {
	var disabled, checked, isCheckable int
	if item.disabled {
		disabled = 1
	}
	if item.checked {
		checked = 1
	}
	if item.isCheckable {
		isCheckable = 1
	}
	parentID := 0
	if item.parent != nil {
		parentID = int(item.parent.id)
	}
	objc.Call("SystrayManager", addOrUpdateMenuItemSelector, item.id, parentID, item.title, item.tooltip, item.shortcutKey, disabled, checked, isCheckable)
}

func addSeparator(id uint32) {
	objc.Call("SystrayManager", "addSeparator:", id)
}

func hideMenuItem(item *MenuItem) {
	objc.Call("SystrayManager", "hideMenuItem:", item.id)
}

func showMenuItem(item *MenuItem) {
	objc.Call("SystrayManager", "showMenuItem:", item.id)
}
}

func resetMenu() {
	objc.Call("SystrayManager", resetMenuSelector)
}

func enableOnClick() {
	if !isEnableOnClick {
		isEnableOnClick = true
		objc.Call("SystrayManager", "enableOnClick")
	}
}

func systray_ready() {
	if systrayReady != nil {
		systrayReady()
	}
}

func systray_on_exit() {
	systrayExit()
}

func systray_menu_item_selected(cID int) {
	systrayMenuItemSelected(uint32(cID))
}

func systray_on_click() {
	if dClickTime == 0 {
		dClickTime = time.Now().UnixMilli()
	} else {
		nowMilli := time.Now().UnixMilli()
		if nowMilli-dClickTime < dClickTimeMinInterval {
			dClickTime = dClickTimeMinInterval
			if onDClick != nil {
				onDClick(st)
				return
			}
		} else {
			dClickTime = nowMilli
		}
	}
	if onClick != nil {
		onClick(st)
	}
}

func systray_on_rclick() {
	if onRClick != nil {
		onRClick(st)
	} else {
		objc.Call("SystrayManager", showMenuSelector)
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions