首先从 Go 语言本身去找,无果
随后找一些 low-level 的库的支持,golang 的 sys 似乎可以,但是好像没有 Mac 支持
一度想通过 Python、 Apple Script 等方式调用的方式绕过,觉得不优雅。
最后了解到 go 可以直接运行 C 语言,得到这样一串代码
https://stackoverflow.com/a/23451568/11185460
因此就开始了对 Object-C 的学习。
首先通过一个 hello world 代码初步调通 Object-C 和 go 的编译和类型转换
package main
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation
#import <Foundation/Foundation.h>
void hello() {
NSLog(@"Hello World");
}
*/
import "C"
func main() {
C.hello()
}
然后注意到解决方案里提供的 object-c 的脚本里有一个 CFStringRef 类型。
这个 Ref 就是个指针嘛,一开始想的是怎么把 Ref 直接变成 char 类型,因此找到了NSString 类型,并且了解到了 CFStringRef 和 NSString 类型的转换。
后来发现 NSString 类型定义时似乎必须用指针形式,所以一度卡主。
后来找到了一个代码片段,提供了将 CFStringRef 转换为 go string 的代码,明白了语言之间因为数据类型存储位不统一,不能简单的直接转换,而是应该直接从内存中拿值的方式解决。
https://gist.github.com/groob/34da9aa005dfc5a3360f65744ae52861
最后改出了这一份代码a
package main
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa
#import <Cocoa/Cocoa.h>
int
GetFrontMostAppPid(void){
NSRunningApplication* app = [[NSWorkspace sharedWorkspace]
frontmostApplication];
pid_t pid = [app processIdentifier];
return pid;
}
CFStringRef
GetAppTitle(pid_t pid) {
CFStringRef title = NULL;
// Get the process ID of the frontmost application.
// NSRunningApplication* app = [[NSWorkspace sharedWorkspace]
// frontmostApplication];
// pid_t pid = [app processIdentifier];
// See if we have accessibility permissions, and if not, prompt the user to
// visit System Preferences.
NSDictionary *options = @{(id)kAXTrustedCheckOptionPrompt: @YES};
Boolean appHasPermission = AXIsProcessTrustedWithOptions(
(__bridge CFDictionaryRef)options);
if (!appHasPermission) {
return title; // we don't have accessibility permissions
}
// Get the accessibility element corresponding to the frontmost application.
AXUIElementRef appElem = AXUIElementCreateApplication(pid);
if (!appElem) {
return title;
}
// Get the accessibility element corresponding to the frontmost window
// of the frontmost application.
AXUIElementRef window = NULL;
if (AXUIElementCopyAttributeValue(appElem,
kAXFocusedWindowAttribute, (CFTypeRef*)&window) != kAXErrorSuccess) {
CFRelease(appElem);
return title;
}
// Finally, get the title of the frontmost window.
AXError result = AXUIElementCopyAttributeValue(window, kAXTitleAttribute,
(CFTypeRef*)&title);
// At this point, we don't need window and appElem anymore.
CFRelease(window);
CFRelease(appElem);
if (result != kAXErrorSuccess) {
// Failed to get the window title.
return title;
}
// Success! Now, do something with the title, e.g. copy it somewhere.
// Once we're done with the title, release it.
CFRelease(title);
return title;
}
static inline CFIndex cfstring_utf8_length(CFStringRef str, CFIndex *need) {
CFIndex n, usedBufLen;
CFRange rng = CFRangeMake(0, CFStringGetLength(str));
return CFStringGetBytes(str, rng, kCFStringEncodingUTF8, 0, 0, NULL, 0, need);
}
*/
import "C"
import (
"github.com/shirou/gopsutil/v3/process"
"reflect"
"unsafe"
)
//import "github.com/shirou/gopsutil/v3/process"
func cfstringGo(cfs C.CFStringRef) string {
var usedBufLen C.CFIndex
n := C.cfstring_utf8_length(cfs, &usedBufLen)
if n <= 0 {
return ""
}
rng := C.CFRange{location: C.CFIndex(0), length: n}
buf := make([]byte, int(usedBufLen))
bufp := unsafe.Pointer(&buf[0])
C.CFStringGetBytes(cfs, rng, C.kCFStringEncodingUTF8, 0, 0, (*C.UInt8)(bufp), C.CFIndex(len(buf)), &usedBufLen)
sh := &reflect.StringHeader{
Data: uintptr(bufp),
Len: int(usedBufLen),
}
return *(*string)(unsafe.Pointer(sh))
}
func main() {
pid := C.GetFrontMostAppPid()
ps, _ := process.NewProcess(int32(pid))
title_ref := C.CFStringRef(C.GetAppTitle(pid))
println(pid)
println(ps.Name())
println(cfstringGo(title_ref))
}