在Mac OS X中获取当前活动窗口/文档的标题

24

参考之前提出的一个问题,我想知道如何获取当前活动文档的标题。

我尝试了上面问题中提供的脚本。它确实可以使用,但是只给出了应用程序的名称。例如,我正在编写这个问题:当我运行该脚本时,它会将应用程序的名称输出,即“Firefox”。这很不错,但并没有真正帮助我。我更愿意捕捉当前活动文档的标题。请参见下面的图片。

Firefox 标题 http://img.skitch.com/20090126-nq2egknhjr928d1s74i9xixckf.jpg

我正在使用Leopard,因此不需要向后兼容性。另外,我正在使用Python的Appkit来访问NSWorkspace类,但是如果您告诉我Objective-C代码,我可以找出将其转化为Python的方法。


好的,我已经有了一个解决方案,但是还不是非常满意,这就是为什么我还没有标记Koen Bok的答案的原因。

tell application "System Events"
set frontApp to name of first application process whose frontmost is true
end tell
tell application frontApp
if the (count of windows) is not 0 then
    set window_name to name of front window
end if
end tell

将其保存为脚本并使用osascript从Shell中调用。


1
谢谢你提供的AppleScript解决方案。你有没有找到用Python实现的方法? - Amjith
4个回答

8

8
在Objective-C中,使用一些Cocoa和主要是 Carbon Accessibility API,简短的答案是:
// 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; // we don't have accessibility permissions

// Get the accessibility element corresponding to the frontmost application.
AXUIElementRef appElem = AXUIElementCreateApplication(pid);
if (!appElem) {
  return;
}

// 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;
}

// Finally, get the title of the frontmost window.
CFStringRef title = NULL;
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;
}

// Success! Now, do something with the title, e.g. copy it somewhere.

// Once we're done with the title, release it.
CFRelease(title);

另外,使用CGWindow API可能会更简单,正如这个StackOverflow答案所提到的。


0
参考https://dev59.com/ynRB5IYBdhLWcg3w4bAo#23451568
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) // pid
    println(ps.Name()) // process name
    println(cfstringGo(title_ref)) // active window title
}


我发现这个属性在被调用后不会改变。 只有在我们实现了NSWorkspaceDidActivateApplicationNotification之后,通过this,我们才能监视活动窗口的变化。但是我没有找到任何可以在golang中实现NSWorkspaceDidActivateApplicationNotification的解决方案。

一个解决方法是编译一个go程序并通过另一个go程序调用它。然后我尝试在这里使用完整的Objective-C代码。


0
获取当前活动应用程序:
➜ osascript -e 'tell application "System Events" to tell (first process whose frontmost is true) to return name'

获取当前活动窗口的标题:
➜ osascript -e 'tell application "System Events" to tell (first process whose frontmost is true) to return name of window 1'

为了同时获取两者(避免用户在两次调用之间更改窗口时出现竞争条件),可以这样做:
➜ osascript -e 'tell application "System Events" to tell (first process whose frontmost is true) to return {name, name of window 1}'

来源: https://forum.keyboardmaestro.com/t/how-do-i-get-the-name-of-the-frontmost-window/2711/2


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接