Skip to content

Instantly share code, notes, and snippets.

@hym3242
Last active May 10, 2024 19:45
Show Gist options
  • Save hym3242/4605bf8dc30edbe6da9c29289eac36d9 to your computer and use it in GitHub Desktop.
Save hym3242/4605bf8dc30edbe6da9c29289eac36d9 to your computer and use it in GitHub Desktop.
Dump full text content (incl. scrollback) of a window of Terminal.app with partial name of the window title.
// I have set my terminal to set wintitle with escape sequence every time a new window is created so that I can dump window by just tty name.
// I have this command in .profile: wintitle $(basename $(tty))
// where wintitle is:
// $ type wintitle
// wintitle is a function
// wintitle ()
// {
// echo -en "\e]0;$1\x01\e\\"
// }
// escape sequence source: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html. For more interesting escape sequence magics, checkout vttest(1) (also by Thomas E. Dickey)
// mostly written by chatgpt 4. No right reserved. Plz star!
#import <Cocoa/Cocoa.h>
#import <ApplicationServices/ApplicationServices.h>
void dumpFirstAXText(AXUIElementRef element, BOOL *found) {
if (*found) return; // Stop if already found a text area
CFTypeRef elementRole;
AXUIElementCopyAttributeValue(element, kAXRoleAttribute, &elementRole);
if (elementRole) {
NSString *role = CFBridgingRelease(elementRole);
if ([role isEqualToString:(NSString *)kAXTextAreaRole]) {
CFTypeRef value;
AXError error = AXUIElementCopyAttributeValue(element, kAXValueAttribute, &value);
if (error == kAXErrorSuccess && value) {
NSString *text = CFBridgingRelease(value);
printf("%s\n", [text UTF8String]); // Print to stdout
*found = YES; // Mark as found
return; // Stop after finding the first match
}
}
}
// Recursively search child elements if not found
CFArrayRef children;
AXError error = AXUIElementCopyAttributeValue(element, kAXChildrenAttribute, (CFTypeRef *)&children);
if (error == kAXErrorSuccess && children) {
for (CFIndex i = 0; i < CFArrayGetCount(children) && !*found; i++) {
AXUIElementRef child = (AXUIElementRef)CFArrayGetValueAtIndex(children, i);
dumpFirstAXText(child, found);
}
CFRelease(children);
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
if (argc != 2) {
fprintf(stderr, "Usage: %s <window title substring>\n", argv[0]);
return 1;
}
// Copy argv[1] before overwriting
NSString *searchString = [NSString stringWithUTF8String:argv[1]];
// Overwrite argv[1] in-place, to avoid dumping itself. (arguments appear in window title)
char* p = *(argv+1);
while(*p != '\0'){
*p = 'x';
p++;
}
NSArray *runningApps = [[NSWorkspace sharedWorkspace] runningApplications];
for (NSRunningApplication *app in runningApps) {
if ([[app bundleIdentifier] isEqualToString:@"com.apple.Terminal"]) {
AXUIElementRef appElement = AXUIElementCreateApplication([app processIdentifier]);
CFArrayRef windows;
AXError error = AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute, (CFTypeRef *)&windows);
if (error == kAXErrorSuccess && windows) {
for (CFIndex i = 0; i < CFArrayGetCount(windows); i++) {
AXUIElementRef window = (AXUIElementRef)CFArrayGetValueAtIndex(windows, i);
CFTypeRef title;
AXError error = AXUIElementCopyAttributeValue(window, kAXTitleAttribute, &title);
if (error == kAXErrorSuccess && title && CFStringFind(title, (__bridge CFStringRef)(searchString), 0).location != kCFNotFound) {
fprintf(stderr, "Found window: %s\n", [(__bridge NSString *)(title) UTF8String]);
BOOL found = NO;
dumpFirstAXText(window, &found); // Dump content of the first AXTextArea
if (found) break; // Exit if found
}
if (title) CFRelease(title);
}
CFRelease(windows);
}
CFRelease(appElement);
break; // Stop after processing Terminal
}
}
}
return 0;
}
@hym3242
Copy link
Author

hym3242 commented May 10, 2024

compile with:
cc dumpTerminalWindowByPartialName.m -o dumpTerminalWindowByPartialName -framework Cocoa (you will need Xcode commandline tools)

then run with: ./dumpTerminalWindowByPartialName YOURWINDOWNAMEHERE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment