Get the title of the current active Window/Document in Mac OS X

Question:

Refering to a previously asked question, I would like to know how to get the title of the current active document.

I tried the script mention in the answers to the question above. This works, but only gives me the name of the application. For example, I am writing this question: When I fire up the script it gives me the name of the application, i.e. “Firefox”. This is pretty neat, but does not really help. I would rather like to capture the title of my current active document. See the image.

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

I am using Leopard, so no backward compatibility needed. Also I am using Python’s Appkit to gain access to the NSWorkspace class, but if you tell me the Objective-C code, I could figure out the translation to Python.


Ok, I’ve got a solution which is not very satisfing, thats why I don’t mark Koen Bok’s answer. At least not yet.

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

Save as script and invoke it with osascript from the shell.

Asked By: sebastiangeiger

||

Answers:

As far as I know your best bet is wrapping an AppleScript. But AppleScript is magic to me so I leave it as an exercise for the questioner 🙂

This might help a little: A script to resize frontmost two windows to fill screen – Mac OS X Hints

Answered By: Koen Bok

In Objective-C, the short answer, using a little Cocoa and mostly the Carbon Accessibility API is:

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

Alternatively, it may be simpler to use the CGWindow API, as alluded to in this StackOverflow answer.

Answered By: sdt

refered to https://stackoverflow.com/a/23451568/11185460

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
}


I then found this property wont change after it is called.
By this, only after we implement NSWorkspaceDidActivateApplicationNotification, we can monitor the change of activity window. But I didn’t find any solution which can implement NSWorkspaceDidActivateApplicationNotification in golang.

A workaround method is compile one go program and call it by another go program. I then try full Objective-C code in here

Answered By: Sailist

To get the active application:

➜ osascript -e 'tell application "System Events" to tell (first process whose frontmost is true) to return name'

To get the active window’s title:

➜ osascript -e 'tell application "System Events" to tell (first process whose frontmost is true) to return name of window 1'

And to get both (to avoid a race condition where the user changes windows between the two calls):

➜ osascript -e 'tell application "System Events" to tell (first process whose frontmost is true) to return {name, name of window 1}'

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

Answered By: Christopher Shroba
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.