diff --git a/tools/cmd/mouse_demo/main.go b/tools/cmd/mouse_demo/main.go index 534786733..a80e8d095 100644 --- a/tools/cmd/mouse_demo/main.go +++ b/tools/cmd/mouse_demo/main.go @@ -10,14 +10,15 @@ import ( "encoding/hex" "fmt" "net/url" - "os" "path/filepath" "strconv" "strings" - kitty "github.com/kovidgoyal/kitty" + "github.com/kovidgoyal/kitty" "github.com/kovidgoyal/kitty/tools/tty" "github.com/kovidgoyal/kitty/tools/tui/loop" + "github.com/kovidgoyal/kitty/tools/utils" + "github.com/kovidgoyal/kitty/tools/utils/machine_id" ) var _ = fmt.Print @@ -41,13 +42,12 @@ func dnd_escape(metadata, payload string) string { // get_machine_id returns the machine id in the format expected by the DnD // protocol ("1:" followed by HMAC-SHA256 of /etc/machine-id). func get_machine_id() string { - data, err := os.ReadFile("/etc/machine-id") + ans, err := machine_id.MachineId() if err != nil { return "" } - data = bytes.TrimRight(data, "\n") mac := hmac.New(sha256.New, []byte("tty-dnd-protocol-machine-id")) - mac.Write(data) + mac.Write(utils.UnsafeStringToBytes(ans)) return "1:" + hex.EncodeToString(mac.Sum(nil)) } diff --git a/tools/utils/machine_id/api.go b/tools/utils/machine_id/api.go new file mode 100644 index 000000000..8dc4f8f9d --- /dev/null +++ b/tools/utils/machine_id/api.go @@ -0,0 +1,10 @@ +package machine_id + +import ( + "fmt" + "sync" +) + +var _ = fmt.Print + +var MachineId = sync.OnceValues(read_machine_id) diff --git a/tools/utils/machine_id/api_test.go b/tools/utils/machine_id/api_test.go new file mode 100644 index 000000000..fa01b76f5 --- /dev/null +++ b/tools/utils/machine_id/api_test.go @@ -0,0 +1,15 @@ +package machine_id + +import ( + "fmt" + "testing" +) + +var _ = fmt.Print + +func TestMachineId(t *testing.T) { + _, err := MachineId() + if err != nil { + t.Fatal(err) + } +} diff --git a/tools/utils/machine_id/darwin.go b/tools/utils/machine_id/darwin.go new file mode 100644 index 000000000..be937e6b4 --- /dev/null +++ b/tools/utils/machine_id/darwin.go @@ -0,0 +1,81 @@ +//go:build darwin + +package machine_id + +import ( + "fmt" + "strings" + + "github.com/ebitengine/purego" +) + +var _ = fmt.Print + +func read_machine_id() (string, error) { + const kIOMainPortDefault uint32 = 0 + const kCFStringEncodingUTF8 uint32 = 0x08000100 + + // 1. Load System Frameworks + iokit, err := purego.Dlopen("/System/Library/Frameworks/IOKit.framework/IOKit", purego.RTLD_NOW) + if err != nil { + return "", err + } + defer purego.Dlclose(iokit) + corefoundation, err := purego.Dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", purego.RTLD_NOW) + if err != nil { + return "", err + } + defer purego.Dlclose(corefoundation) + + // 2. Register CoreFoundation Functions + var cfStringCreateWithCString func(uintptr, *byte, uint32) uintptr + purego.RegisterLibFunc(&cfStringCreateWithCString, corefoundation, "CFStringCreateWithCString") + + var cfStringGetCString func(uintptr, *byte, int64, uint32) bool + purego.RegisterLibFunc(&cfStringGetCString, corefoundation, "CFStringGetCString") + + var cfRelease func(uintptr) + purego.RegisterLibFunc(&cfRelease, corefoundation, "CFRelease") + + // 3. Register IOKit Functions + var ioServiceMatching func(*byte) uintptr + purego.RegisterLibFunc(&ioServiceMatching, iokit, "IOServiceMatching") + + var ioServiceGetMatchingService func(uint32, uintptr) uint32 + purego.RegisterLibFunc(&ioServiceGetMatchingService, iokit, "IOServiceGetMatchingService") + + var ioRegistryEntryCreateCFProperty func(uint32, uintptr, uintptr, uint32) uintptr + purego.RegisterLibFunc(&ioRegistryEntryCreateCFProperty, iokit, "IORegistryEntryCreateCFProperty") + + var ioObjectRelease func(uint32) + purego.RegisterLibFunc(&ioObjectRelease, iokit, "IOObjectRelease") + + // 4. Retrieve the UUID + // Convert Go string to CFStringRef for the property key + keyName := "IOPlatformUUID\x00" + cfKey := cfStringCreateWithCString(0, &[]byte(keyName)[0], kCFStringEncodingUTF8) + defer cfRelease(cfKey) + + // Look up the IOPlatformExpertDevice service + className := "IOPlatformExpertDevice\x00" + matchingDict := ioServiceMatching(&[]byte(className)[0]) + service := ioServiceGetMatchingService(kIOMainPortDefault, matchingDict) + if service == 0 { + return "", fmt.Errorf("failed to find IOPlatformExpertDevice service") + } + defer ioObjectRelease(service) + + // Get the property from the registry + cfValue := ioRegistryEntryCreateCFProperty(service, cfKey, 0, 0) + if cfValue == 0 { + return "", fmt.Errorf("failed to find IOPlatformUUID property") + } + defer cfRelease(cfValue) + + // Convert the resulting CFString back to a Go string + buf := make([]byte, 128) + if cfStringGetCString(cfValue, &buf[0], 128, kCFStringEncodingUTF8) { + return strings.TrimRight(string(buf), "\x00"), nil + } + return "", fmt.Errorf("failed to extract string from CFProperty") +} diff --git a/tools/utils/machine_id/unix.go b/tools/utils/machine_id/unix.go new file mode 100644 index 000000000..81ba10343 --- /dev/null +++ b/tools/utils/machine_id/unix.go @@ -0,0 +1,23 @@ +//go:build !darwin + +package machine_id + +import ( + "fmt" + "os" + "strings" + "unicode" + + "github.com/kovidgoyal/kitty/tools/utils" +) + +var _ = fmt.Print + +func read_machine_id() (string, error) { + if data, err := os.ReadFile("/etc/machine-id"); err == nil { + ans := utils.UnsafeBytesToString(data) + return strings.TrimRightFunc(ans, unicode.IsSpace), nil + } else { + return "", err + } +}