├── .gitignore ├── MenuItem.h ├── MenuItem.m ├── README.md ├── UIAccess.h ├── UIAccess.m ├── main.m ├── menudump-Prefix.pch ├── menudump.1 ├── menudump.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── ctwise.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── WorkspaceSettings.xcsettings └── xcuserdata │ └── ctwise.xcuserdatad │ ├── xcdebugger │ └── Breakpoints.xcbkptlist │ └── xcschemes │ ├── menudump.xcscheme │ └── xcschememanagement.plist └── menudump ├── Logger.h ├── Logger.m ├── MenuItem.h ├── MenuItem.m ├── UIAccess.h ├── UIAccess.m ├── main.m ├── menudump-Prefix.pch └── menudump.1 /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.idea/ 3 | /*.iml 4 | /target 5 | /projectFilesBackup 6 | -------------------------------------------------------------------------------- /MenuItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuItem.h 3 | // menudump 4 | // 5 | // Created by Charles Wise on 4/12/13. 6 | // 7 | 8 | #import 9 | 10 | @interface MenuItem : NSObject 11 | 12 | @property(readwrite) NSString *name; 13 | @property(readwrite) NSArray *children; 14 | @property(readwrite) int depth; 15 | @property(readwrite) NSString *shortcut; 16 | @property(readwrite) NSString *locator; 17 | @property(readwrite) NSString *path; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /MenuItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // MenuItem.m 3 | // menudump 4 | // 5 | // Created by Charles Wise on 4/12/13. 6 | // 7 | 8 | #import "MenuItem.h" 9 | 10 | @implementation MenuItem { 11 | @private 12 | NSString *_name; 13 | NSArray *_children; 14 | int _depth; 15 | NSString *_shortcut; 16 | NSString *_locator; 17 | NSString *_path; 18 | } 19 | 20 | @synthesize name = _name; 21 | @synthesize children = _children; 22 | @synthesize depth = _depth; 23 | @synthesize shortcut = _shortcut; 24 | @synthesize locator = _locator; 25 | @synthesize path = _path; 26 | @end 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | menudump 2 | ======== 3 | 4 | Dumps the menu entries for an OS/X application. 5 | 6 | Usage: menudump [--pid ] [--yaml] [--help] 7 | Dumps the menu contents of a given application in JSON format. Defaults to the front-most application. 8 | --pid to target a specific application. 9 | --yaml to output in YAML format instead. 10 | --help print this message. 11 | 12 | -------------------------------------------------------------------------------- /UIAccess.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIAccess.h 3 | // menudump 4 | // 5 | // Created by Charles Wise on 3/3/13. 6 | // Copyright (c) 2013 Charles Wise. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIAccess : NSObject 12 | 13 | - (NSArray *)getAppMenu:(NSRunningApplication *) pid; 14 | - (NSString *)convertMenuToJSON:(NSArray *)menu app:(NSRunningApplication *)menuApp; 15 | - (NSString *)convertMenuToYAML:(NSArray *)menu app:(NSRunningApplication *)menuApp; 16 | @end 17 | -------------------------------------------------------------------------------- /UIAccess.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIAccess.m 3 | // menudump 4 | // 5 | // Created by Charles Wise on 3/3/13. 6 | // Copyright (c) 2013 Charles Wise. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "UIAccess.h" 11 | #import "MenuItem.h" 12 | 13 | @implementation UIAccess 14 | 15 | id getAttribute(AXUIElementRef element, CFStringRef attribute) { 16 | CFTypeRef value = nil; 17 | if (AXUIElementCopyAttributeValue(element, attribute, (CFTypeRef *) &value) != kAXErrorSuccess) return nil; 18 | if (AXValueGetType((AXValueRef) value) == kAXValueAXErrorType) return nil; 19 | return value; 20 | } 21 | 22 | NSString *decodeKeyMask(long cmdModifiers) { 23 | NSString *result = @""; 24 | if (cmdModifiers == 0x18) { 25 | result = @"fn fn"; 26 | } else { 27 | if (cmdModifiers & 0x04) { 28 | result = [result stringByAppendingString:@"⌃"]; 29 | } 30 | if (cmdModifiers & 0x02) { 31 | result = [result stringByAppendingString:@"⌥"]; 32 | } 33 | if (cmdModifiers & 0x01) { 34 | result = [result stringByAppendingString:@"⇧"]; 35 | } 36 | if (!(cmdModifiers & 0x08)) { 37 | result = [result stringByAppendingString:@"⌘"]; 38 | } 39 | } 40 | return result; 41 | } 42 | 43 | NSString *getMenuItemTitle(AXUIElementRef element) { 44 | return (NSString *) getAttribute(element, kAXTitleAttribute); 45 | } 46 | 47 | long getLongAttribute(AXUIElementRef element, CFStringRef attribute) { 48 | CFNumberRef valueRef = getAttribute(element, attribute); 49 | long result = 0; 50 | if (valueRef) { 51 | CFNumberGetValue(valueRef, kCFNumberLongType, &result); 52 | } 53 | return result; 54 | } 55 | 56 | NSString *getMenuItemShortcut(AXUIElementRef element, NSDictionary *virtualKeys) { 57 | NSString *result = nil; 58 | 59 | NSString *cmdChar = getAttribute(element, kAXMenuItemCmdCharAttribute); 60 | NSString *base = cmdChar; 61 | long cmdModifiers = getLongAttribute(element, kAXMenuItemCmdModifiersAttribute); 62 | long cmdVirtualKey = getLongAttribute(element, kAXMenuItemCmdVirtualKeyAttribute); 63 | 64 | if (base) { 65 | if ([base characterAtIndex:0] == 0x7f) { 66 | base = @"⌦"; 67 | } 68 | } else if (cmdVirtualKey > 0) { 69 | NSString *virtualLookup = [virtualKeys objectForKey:[NSNumber numberWithLong:cmdVirtualKey]]; 70 | if (virtualLookup) { 71 | base = virtualLookup; 72 | } 73 | } 74 | // NSString *cmdGlyph = (NSString *) getAttribute(element, kAXMenuItemCmdGlyphAttribute); 75 | // NSString *cmdMark = (NSString *) getAttribute(element, kAXMenuItemMarkCharAttribute); 76 | NSString *modifiers = decodeKeyMask(cmdModifiers); 77 | if (base) { 78 | result = [modifiers stringByAppendingString:base]; 79 | } 80 | return result; 81 | } 82 | 83 | Boolean isEnabled(AXUIElementRef element) { 84 | CFTypeRef enabled = NULL; 85 | if (AXUIElementCopyAttributeValue(element, kAXEnabledAttribute, &enabled) != kAXErrorSuccess) return false; 86 | return CFBooleanGetValue(enabled); 87 | } 88 | 89 | NSArray *menuItemsForElement(AXUIElementRef element, NSInteger depth, NSString *elementName, NSInteger maxDepth, NSDictionary *virtualKeys) { 90 | NSArray *children = nil; 91 | AXUIElementCopyAttributeValue(element, kAXChildrenAttribute, (CFTypeRef *) &children); 92 | 93 | NSMutableArray *menuItems = [NSMutableArray array]; 94 | for (id child in children) { 95 | // We don't have focus in Alfred, so we can't use this. 96 | // if (!isEnabled((AXUIElementRef) child)) continue; 97 | 98 | NSString *name = getMenuItemTitle((AXUIElementRef) child); 99 | 100 | // Skip the top-level Apple menu 101 | if (depth == 0 && [name isEqualToString:@"Apple"]) { 102 | continue; 103 | } 104 | 105 | // Skip the Services menu 106 | if (depth == 2 && [name isEqualToString:@"Services"]) { 107 | continue; 108 | } 109 | 110 | NSArray *mChildren = nil; 111 | AXUIElementCopyAttributeValue(element, kAXChildrenAttribute, (CFTypeRef *) &mChildren); 112 | NSUInteger mChildrenCount = [mChildren count]; 113 | 114 | MenuItem *menuItem = [[MenuItem alloc] init]; 115 | menuItem.name = name; 116 | menuItem.depth = depth; 117 | menuItem.shortcut = getMenuItemShortcut((AXUIElementRef) child, virtualKeys); 118 | 119 | if (mChildrenCount > 0 || mChildrenCount < 40 || depth < maxDepth) { 120 | menuItem.children = menuItemsForElement((AXUIElementRef) child, depth + 1, name, maxDepth, virtualKeys); 121 | } 122 | 123 | if (menuItem.name && [menuItem.name length] > 0) { 124 | [menuItems addObject:menuItem]; 125 | } else { 126 | // This isn't a menu item, skip below it and get its children 127 | [menuItems addObjectsFromArray:menuItem.children]; 128 | } 129 | } 130 | 131 | return menuItems; 132 | } 133 | 134 | NSString *escape(NSString *text) { 135 | NSString *result = [text stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; 136 | result = [result stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; 137 | return result; 138 | } 139 | 140 | NSString *padding(int length) { 141 | NSMutableString *padding = [NSMutableString stringWithString:@""]; 142 | for (int i = 0; i < length; i++) { 143 | [padding appendString:@" "]; 144 | } 145 | return padding; 146 | } 147 | 148 | NSString *buildLocator(MenuItem *item, NSArray *parents) { 149 | bool needLocator = parents && [parents count] > 0; 150 | NSArray *children = item.children; 151 | needLocator = needLocator && (!children || [children count] == 0); 152 | if (needLocator) { 153 | NSString *name = item.name; 154 | NSString *menuItem = [NSString stringWithFormat:@"menu item \"%@\"", name]; 155 | NSMutableString *buffer = [NSMutableString stringWithString:menuItem]; 156 | 157 | int pathCount = [parents count]; 158 | for (NSString *parent in [parents reverseObjectEnumerator]) { 159 | NSString *menuType = @"menu bar item"; 160 | if (pathCount > 1) { 161 | menuType = @"menu item"; 162 | } 163 | NSString *menu = [NSString stringWithFormat:@" of menu \"%@\" of %@ \"%@\"", parent, menuType, parent]; 164 | [buffer appendString:menu]; 165 | pathCount--; 166 | } 167 | 168 | [buffer appendString:@" of menu bar 1"]; 169 | 170 | return buffer; 171 | } else { 172 | return [NSMutableString stringWithString:@""]; 173 | } 174 | } 175 | 176 | NSString *buildMenuPath(NSArray *parents) { 177 | if (!parents || [parents count] == 0) { 178 | return [NSMutableString stringWithString:@""]; 179 | } else { 180 | NSMutableString *buffer = [NSMutableString stringWithString:@""]; 181 | 182 | for (NSString *parent in parents) { 183 | if ([buffer length] > 0) { 184 | [buffer appendString:@" > "]; 185 | } 186 | [buffer appendString:parent]; 187 | } 188 | 189 | return buffer; 190 | } 191 | } 192 | 193 | NSString *menuToJSON(NSArray *menu, int depth, NSArray *parents) { 194 | NSMutableString *buffer = [NSMutableString stringWithString:@""]; 195 | NSString *offset = padding(depth * 2); 196 | NSString *offset2 = padding((depth + 1) * 2); 197 | NSString *offset3 = padding((depth + 2) * 2); 198 | if (depth > 0) { 199 | [buffer appendString:@" "]; 200 | } 201 | [buffer appendString:@"[\n"]; 202 | for (MenuItem *item in menu) { 203 | [buffer appendString:offset2]; 204 | [buffer appendString:@"{\n"]; 205 | 206 | NSString *shortcut = item.shortcut; 207 | if (!shortcut) { 208 | shortcut = @""; 209 | } 210 | [buffer appendString:offset3]; 211 | [buffer appendString:[NSString stringWithFormat:@"\"name\": \"%@\",\n", escape(item.name)]]; 212 | [buffer appendString:offset3]; 213 | [buffer appendString:[NSString stringWithFormat:@"\"shortcut\": \"%@\",\n", escape(shortcut)]]; 214 | 215 | NSString *children = @"[]"; 216 | if (item.children && ([item.children count] > 0)) { 217 | NSArray *childParents = [NSArray arrayWithArray:parents]; 218 | childParents = [childParents arrayByAddingObject:item.name]; 219 | children = menuToJSON(item.children, depth + 2, childParents); 220 | } 221 | 222 | [buffer appendString:offset3]; 223 | [buffer appendString:[NSString stringWithFormat:@"\"locator\": \"%@\",\n", escape(buildLocator(item, parents))]]; 224 | [buffer appendString:offset3]; 225 | [buffer appendString:[NSString stringWithFormat:@"\"menuPath\": \"%@\",\n", escape(buildMenuPath(parents))]]; 226 | [buffer appendString:offset3]; 227 | [buffer appendString:[NSString stringWithFormat:@"\"children\": %@\n", children]]; 228 | 229 | [buffer appendString:offset2]; 230 | [buffer appendString:@"},\n"]; 231 | } 232 | [buffer appendString:offset]; 233 | [buffer appendString:@"]\n"]; 234 | 235 | return buffer; 236 | } 237 | 238 | NSString *menuToYAML(NSArray *menu, int startingOffset, NSArray *parents) { 239 | NSMutableString *buffer = [NSMutableString stringWithString:@""]; 240 | NSString *offset = padding(startingOffset + 4); 241 | NSString *offset2 = padding(startingOffset + 6); 242 | for (MenuItem *item in menu) { 243 | [buffer appendString:offset]; 244 | [buffer appendString:@"- "]; 245 | NSString *shortcut = item.shortcut; 246 | if (!shortcut) { 247 | shortcut = @""; 248 | } 249 | [buffer appendString:[NSString stringWithFormat:@"name: \"%@\"\n", escape(item.name)]]; 250 | [buffer appendString:offset2]; 251 | [buffer appendString:[NSString stringWithFormat:@"shortcut: \"%@\"\n", escape(shortcut)]]; 252 | 253 | NSString *children = @""; 254 | if (item.children && ([item.children count] > 0)) { 255 | NSArray *childParents = [NSArray arrayWithArray:parents]; 256 | childParents = [childParents arrayByAddingObject:item.name]; 257 | children = menuToYAML(item.children, startingOffset + 6, childParents); 258 | } 259 | 260 | [buffer appendString:offset2]; 261 | [buffer appendString:[NSString stringWithFormat:@"locator: \"%@\"\n", escape(buildLocator(item, parents))]]; 262 | [buffer appendString:offset2]; 263 | [buffer appendString:[NSString stringWithFormat:@"menuPath: \"%@\"\n", escape(buildMenuPath(parents))]]; 264 | if ([children length] > 0) { 265 | [buffer appendString:offset2]; 266 | [buffer appendString:[NSString stringWithFormat:@"children:\n%@", children]]; 267 | } 268 | } 269 | 270 | return buffer; 271 | } 272 | 273 | - (NSArray *)getAppMenu:(NSRunningApplication *)menuApp { 274 | AXUIElementRef app = AXUIElementCreateApplication(menuApp.processIdentifier); 275 | AXUIElementRef menuBar; 276 | AXUIElementCopyAttributeValue(app, kAXMenuBarAttribute, (CFTypeRef *) &menuBar); 277 | 278 | NSMutableDictionary *virtualKeys = [NSMutableDictionary dictionary]; 279 | 280 | [virtualKeys setObject:@"↩" forKey:[NSNumber numberWithLong:0x24]]; // kVK_Return 281 | [virtualKeys setObject:@"⌤" forKey:[NSNumber numberWithLong:0x4C]]; // kVK_ANSI_KeypadEnter 282 | [virtualKeys setObject:@"⌧" forKey:[NSNumber numberWithLong:0x47]]; // kVK_ANSI_KeypadClear 283 | [virtualKeys setObject:@"⇥" forKey:[NSNumber numberWithLong:0x30]]; // kVK_Tab 284 | [virtualKeys setObject:@"␣" forKey:[NSNumber numberWithLong:0x31]]; // kVK_Space 285 | [virtualKeys setObject:@"⌫" forKey:[NSNumber numberWithLong:0x33]]; // kVK_Delete 286 | [virtualKeys setObject:@"⎋" forKey:[NSNumber numberWithLong:0x35]]; // kVK_Escape 287 | [virtualKeys setObject:@"⇪" forKey:[NSNumber numberWithLong:0x39]]; // kVK_CapsLock 288 | [virtualKeys setObject:@"fn" forKey:[NSNumber numberWithLong:0x3F]]; // kVK_Function 289 | [virtualKeys setObject:@"F1" forKey:[NSNumber numberWithLong:0x7A]]; // kVK_F1 290 | [virtualKeys setObject:@"F2" forKey:[NSNumber numberWithLong:0x78]]; // kVK_F2 291 | [virtualKeys setObject:@"F3" forKey:[NSNumber numberWithLong:0x63]]; // kVK_F3 292 | [virtualKeys setObject:@"F4" forKey:[NSNumber numberWithLong:0x76]]; // kVK_F4 293 | [virtualKeys setObject:@"F5" forKey:[NSNumber numberWithLong:0x60]]; // kVK_F5 294 | [virtualKeys setObject:@"F6" forKey:[NSNumber numberWithLong:0x61]]; // kVK_F6 295 | [virtualKeys setObject:@"F7" forKey:[NSNumber numberWithLong:0x62]]; // kVK_F7 296 | [virtualKeys setObject:@"F8" forKey:[NSNumber numberWithLong:0x64]]; // kVK_F8 297 | [virtualKeys setObject:@"F9" forKey:[NSNumber numberWithLong:0x65]]; // kVK_F9 298 | [virtualKeys setObject:@"F10" forKey:[NSNumber numberWithLong:0x6D]]; // kVK_F10 299 | [virtualKeys setObject:@"F11" forKey:[NSNumber numberWithLong:0x67]]; // kVK_F11 300 | [virtualKeys setObject:@"F12" forKey:[NSNumber numberWithLong:0x6F]]; // kVK_F12 301 | [virtualKeys setObject:@"F13" forKey:[NSNumber numberWithLong:0x69]]; // kVK_F13 302 | [virtualKeys setObject:@"F14" forKey:[NSNumber numberWithLong:0x6B]]; // kVK_F14 303 | [virtualKeys setObject:@"F15" forKey:[NSNumber numberWithLong:0x71]]; // kVK_F15 304 | [virtualKeys setObject:@"F16" forKey:[NSNumber numberWithLong:0x6A]]; // kVK_F16 305 | [virtualKeys setObject:@"F17" forKey:[NSNumber numberWithLong:0x40]]; // kVK_F17 306 | [virtualKeys setObject:@"F18" forKey:[NSNumber numberWithLong:0x4F]]; // kVK_F18 307 | [virtualKeys setObject:@"F19" forKey:[NSNumber numberWithLong:0x50]]; // kVK_F19 308 | [virtualKeys setObject:@"F20" forKey:[NSNumber numberWithLong:0x5A]]; // kVK_F20 309 | [virtualKeys setObject:@"↖" forKey:[NSNumber numberWithLong:0x73]]; // kVK_Home 310 | [virtualKeys setObject:@"⇞" forKey:[NSNumber numberWithLong:0x74]]; // kVK_PageUp 311 | [virtualKeys setObject:@"⌦" forKey:[NSNumber numberWithLong:0x75]]; // kVK_ForwardDelete 312 | [virtualKeys setObject:@"↘" forKey:[NSNumber numberWithLong:0x77]]; // kVK_End 313 | [virtualKeys setObject:@"⇟" forKey:[NSNumber numberWithLong:0x79]]; // kVK_PageDown 314 | [virtualKeys setObject:@"←" forKey:[NSNumber numberWithLong:0x7B]]; // kVK_LeftArrow 315 | [virtualKeys setObject:@"→" forKey:[NSNumber numberWithLong:0x7C]]; // kVK_RightArrow 316 | [virtualKeys setObject:@"↓" forKey:[NSNumber numberWithLong:0x7D]]; // kVK_DownArrow 317 | [virtualKeys setObject:@"↑" forKey:[NSNumber numberWithLong:0x7E]]; // kVK_UpArrow 318 | 319 | return menuItemsForElement(menuBar, 0, @"Top", 5, virtualKeys); 320 | } 321 | 322 | - (NSString *)convertMenuToJSON:(NSArray *)menu app:(NSRunningApplication *)menuApp { 323 | NSMutableString *buffer = [NSMutableString stringWithString:@"{\n"]; 324 | [buffer appendString:@" \"name\": \""]; 325 | [buffer appendString:menuApp.localizedName]; 326 | [buffer appendString:@"\"\n"]; 327 | [buffer appendString:@" \"bundleIdentifier\": \""]; 328 | [buffer appendString:menuApp.bundleIdentifier]; 329 | [buffer appendString:@"\"\n"]; 330 | [buffer appendString:@" \"bundlePath\": \""]; 331 | [buffer appendString:menuApp.bundleURL.path]; 332 | [buffer appendString:@"\"\n"]; 333 | [buffer appendString:@" \"executablePath\": \""]; 334 | [buffer appendString:menuApp.executableURL.path]; 335 | [buffer appendString:@"\"\n"]; 336 | [buffer appendString:@" \"menus\":"]; 337 | [buffer appendString:menuToJSON(menu, 2, [[NSArray alloc] init])]; 338 | [buffer appendString:@"}"]; 339 | return buffer; 340 | } 341 | 342 | - (NSString *)convertMenuToYAML:(NSArray *)menu app:(NSRunningApplication *)menuApp { 343 | NSMutableString *buffer = [NSMutableString stringWithString:@"application:\n"]; 344 | [buffer appendString:@" name: \""]; 345 | [buffer appendString:menuApp.localizedName]; 346 | [buffer appendString:@"\"\n"]; 347 | [buffer appendString:@" bundleIdentifier: \""]; 348 | [buffer appendString:menuApp.bundleIdentifier]; 349 | [buffer appendString:@"\"\n"]; 350 | [buffer appendString:@" bundlePath: \""]; 351 | [buffer appendString:menuApp.bundleURL.path]; 352 | [buffer appendString:@"\"\n"]; 353 | [buffer appendString:@" executablePath: \""]; 354 | [buffer appendString:menuApp.executableURL.path]; 355 | [buffer appendString:@"\"\n"]; 356 | [buffer appendString:@"menus:\n"]; 357 | [buffer appendString:menuToYAML(menu, 0, [[NSArray alloc] init])]; 358 | return buffer; 359 | } 360 | @end 361 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // menudump 4 | // 5 | // Created by Charles Wise on 2/17/13. 6 | // 7 | 8 | #import 9 | #import 10 | #include "System Events.h" 11 | #include "UIAccess.h" 12 | 13 | pid_t getActiveApp() { 14 | NSRunningApplication *app = [[NSWorkspace sharedWorkspace] menuBarOwningApplication]; 15 | NSString *name = app.localizedName; 16 | NSString *bundleId = app.bundleIdentifier; 17 | NSString *bundlePathL = app.bundleURL.path; 18 | NSString *executablePath = app.executableURL.path; 19 | 20 | return app.processIdentifier; 21 | } 22 | 23 | NSRunningApplication *getAppByPid(pid_t pid) { 24 | NSArray *appNames = [[NSWorkspace sharedWorkspace] runningApplications]; 25 | for (NSRunningApplication *app in appNames) { 26 | if (app.processIdentifier == pid) { 27 | return app; 28 | } 29 | } 30 | return nil; 31 | } 32 | 33 | int main(int argc, const char *argv[]) { 34 | @autoreleasepool { 35 | pid_t pid = -1; 36 | bool showHelp = false; 37 | bool outputJson = true; 38 | 39 | int offset = 1; 40 | while (offset < argc) { 41 | char const *value = argv[offset]; 42 | if (strncasecmp(value, "--help", 6) == 0) { 43 | showHelp = true; 44 | offset++; 45 | } else if (strncasecmp(value, "--yaml", 6) == 0) { 46 | outputJson = false; 47 | offset++; 48 | } else if (strncasecmp(value, "--pid", 5) == 0) { 49 | if (argc >= (offset + 1)) { 50 | pid = atol(argv[offset + 1]); 51 | offset = offset + 2; 52 | } 53 | } else { 54 | offset++; 55 | } 56 | } 57 | 58 | if (showHelp) { 59 | printf("Usage: menudump [--pid ] [--yaml] [--help]\n"); 60 | printf(" Dumps the menu contents of a given application in JSON format. Defaults to the front-most application.\n"); 61 | printf(" --pid to target a specific application.\n"); 62 | printf(" --yaml to output in YAML format instead.\n"); 63 | printf(" --help print this message\n"); 64 | exit(1); 65 | } 66 | 67 | NSRunningApplication *menuApp = nil; 68 | if (pid == -1) { 69 | menuApp = [[NSWorkspace sharedWorkspace] menuBarOwningApplication]; 70 | } else { 71 | menuApp = getAppByPid(pid); 72 | } 73 | 74 | if (menuApp) { 75 | UIAccess *ui = [UIAccess new]; 76 | NSArray *menu = [ui getAppMenu:menuApp]; 77 | NSString *contents; 78 | if (outputJson) { 79 | contents = [ui convertMenuToJSON:menu app:menuApp]; 80 | } else { 81 | contents = [ui convertMenuToYAML:menu app:menuApp]; 82 | } 83 | printf("%s", [contents UTF8String]); 84 | } else { 85 | printf("Unable to find app that matches the pid"); 86 | exit(1); 87 | } 88 | } 89 | 90 | return 0; 91 | } 92 | 93 | -------------------------------------------------------------------------------- /menudump-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'menudump' target in the 'menudump' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /menudump.1: -------------------------------------------------------------------------------- 1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. 2 | .\"See Also: 3 | .\"man mdoc.samples for a complete listing of options 4 | .\"man mdoc for the short list of editing options 5 | .\"/usr/share/misc/mdoc.template 6 | .Dd 2/17/13 \" DATE 7 | .Dt menudump 1 \" Program name and manual section number 8 | .Os Darwin 9 | .Sh NAME \" Section Header - required - don't modify 10 | .Nm menudump, 11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key 12 | .\" words here as the database is built based on the words here and in the .ND line. 13 | .Nm Other_name_for_same_program(), 14 | .Nm Yet another name for the same program. 15 | .\" Use .Nm macro to designate other names for the documented program. 16 | .Nd This line parsed for whatis database. 17 | .Sh SYNOPSIS \" Section Header - required - don't modify 18 | .Nm 19 | .Op Fl abcd \" [-abcd] 20 | .Op Fl a Ar path \" [-a path] 21 | .Op Ar file \" [file] 22 | .Op Ar \" [file ...] 23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline 24 | arg2 ... \" Arguments 25 | .Sh DESCRIPTION \" Section Header - required - don't modify 26 | Use the .Nm macro to refer to your program throughout the man page like such: 27 | .Nm 28 | Underlining is accomplished with the .Ar macro like this: 29 | .Ar underlined text . 30 | .Pp \" Inserts a space 31 | A list of items with descriptions: 32 | .Bl -tag -width -indent \" Begins a tagged list 33 | .It item a \" Each item preceded by .It macro 34 | Description of item a 35 | .It item b 36 | Description of item b 37 | .El \" Ends the list 38 | .Pp 39 | A list of flags and their descriptions: 40 | .Bl -tag -width -indent \" Differs from above in tag removed 41 | .It Fl a \"-a flag as a list item 42 | Description of -a flag 43 | .It Fl b 44 | Description of -b flag 45 | .El \" Ends the list 46 | .Pp 47 | .\" .Sh ENVIRONMENT \" May not be needed 48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 49 | .\" .It Ev ENV_VAR_1 50 | .\" Description of ENV_VAR_1 51 | .\" .It Ev ENV_VAR_2 52 | .\" Description of ENV_VAR_2 53 | .\" .El 54 | .Sh FILES \" File used or created by the topic of the man page 55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact 56 | .It Pa /usr/share/file_name 57 | FILE_1 description 58 | .It Pa /Users/joeuser/Library/really_long_file_name 59 | FILE_2 description 60 | .El \" Ends the list 61 | .\" .Sh DIAGNOSTICS \" May not be needed 62 | .\" .Bl -diag 63 | .\" .It Diagnostic Tag 64 | .\" Diagnostic informtion here. 65 | .\" .It Diagnostic Tag 66 | .\" Diagnostic informtion here. 67 | .\" .El 68 | .Sh SEE ALSO 69 | .\" List links in ascending order by section, alphabetically within a section. 70 | .\" Please do not reference files that do not exist without filing a bug report 71 | .Xr a 1 , 72 | .Xr b 1 , 73 | .Xr c 1 , 74 | .Xr a 2 , 75 | .Xr b 2 , 76 | .Xr a 3 , 77 | .Xr b 3 78 | .\" .Sh BUGS \" Document known, unremedied bugs 79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner -------------------------------------------------------------------------------- /menudump.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 150A869416D127BC00DE69E3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 150A869316D127BC00DE69E3 /* Foundation.framework */; }; 11 | 150A869716D127BC00DE69E3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 150A869616D127BC00DE69E3 /* main.m */; }; 12 | 150A86A216D1284800DE69E3 /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 150A86A116D1284800DE69E3 /* ScriptingBridge.framework */; }; 13 | 150A86A616D12D3200DE69E3 /* System Events.app in Sources */ = {isa = PBXBuildFile; fileRef = 150A86A516D12D0D00DE69E3 /* System Events.app */; }; 14 | 151D1108171A1FC7007F052E /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 151D1107171A1FC7007F052E /* AppKit.framework */; }; 15 | 15B0176016E3F7B200464C5B /* UIAccess.m in Sources */ = {isa = PBXBuildFile; fileRef = 15B0175E16E3F7B200464C5B /* UIAccess.m */; }; 16 | 15CB4B2C16EA5F110079928D /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15CB4B2B16EA5F110079928D /* ApplicationServices.framework */; }; 17 | 15DC2D351718947A00361B29 /* MenuItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 15DC2D341718947A00361B29 /* MenuItem.m */; }; 18 | AF8A100E44B7CAE564B62049 /* Logger.m in Sources */ = {isa = PBXBuildFile; fileRef = AF8A11BABFA13790B9AD743D /* Logger.m */; }; 19 | AF8A1862A711F8EE19D945CD /* Logger.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = AF8A15818A46FE60AFE79627 /* Logger.h */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXBuildRule section */ 23 | 150A86A316D12A9900DE69E3 /* PBXBuildRule */ = { 24 | isa = PBXBuildRule; 25 | compilerSpec = com.apple.compilers.proxy.script; 26 | filePatterns = "*.app"; 27 | fileType = pattern.proxy; 28 | isEditable = 1; 29 | outputFiles = ( 30 | "$(DERIVED_FILES_DIR)/$(INPUT_FILE_BASE).h", 31 | ); 32 | script = "sdef \"$INPUT_FILE_PATH\" | sdp -fh -o \"$DERIVED_FILES_DIR\" --basename \"$INPUT_FILE_BASE\" --bundleid `defaults read \"$INPUT_FILE_PATH/Contents/Info\" CFBundleIdentifier`"; 33 | }; 34 | /* End PBXBuildRule section */ 35 | 36 | /* Begin PBXCopyFilesBuildPhase section */ 37 | 150A868E16D127BC00DE69E3 /* CopyFiles */ = { 38 | isa = PBXCopyFilesBuildPhase; 39 | buildActionMask = 2147483647; 40 | dstPath = /usr/share/man/man1/; 41 | dstSubfolderSpec = 0; 42 | files = ( 43 | AF8A1862A711F8EE19D945CD /* Logger.h in CopyFiles */, 44 | ); 45 | runOnlyForDeploymentPostprocessing = 1; 46 | }; 47 | /* End PBXCopyFilesBuildPhase section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | 150A869016D127BC00DE69E3 /* menudump */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = menudump; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 150A869316D127BC00DE69E3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 52 | 150A869616D127BC00DE69E3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 53 | 150A869916D127BC00DE69E3 /* menudump-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "menudump-Prefix.pch"; sourceTree = ""; }; 54 | 150A86A116D1284800DE69E3 /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = System/Library/Frameworks/ScriptingBridge.framework; sourceTree = SDKROOT; }; 55 | 150A86A516D12D0D00DE69E3 /* System Events.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; name = "System Events.app"; path = "../../../../../System/Library/CoreServices/System Events.app"; sourceTree = ""; }; 56 | 151D1107171A1FC7007F052E /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 57 | 15B0175D16E3F7B200464C5B /* UIAccess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIAccess.h; sourceTree = ""; }; 58 | 15B0175E16E3F7B200464C5B /* UIAccess.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIAccess.m; sourceTree = ""; }; 59 | 15CB4B2B16EA5F110079928D /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = System/Library/Frameworks/ApplicationServices.framework; sourceTree = SDKROOT; }; 60 | 15DC2D331718947A00361B29 /* MenuItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MenuItem.h; sourceTree = ""; }; 61 | 15DC2D341718947A00361B29 /* MenuItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MenuItem.m; sourceTree = ""; }; 62 | AF8A11BABFA13790B9AD743D /* Logger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Logger.m; sourceTree = ""; }; 63 | AF8A13F4E656879E540CC4F5 /* menudump.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; path = menudump.1; sourceTree = ""; }; 64 | AF8A15818A46FE60AFE79627 /* Logger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logger.h; sourceTree = ""; }; 65 | /* End PBXFileReference section */ 66 | 67 | /* Begin PBXFrameworksBuildPhase section */ 68 | 150A868D16D127BC00DE69E3 /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | 150A86A216D1284800DE69E3 /* ScriptingBridge.framework in Frameworks */, 73 | 150A869416D127BC00DE69E3 /* Foundation.framework in Frameworks */, 74 | 15CB4B2C16EA5F110079928D /* ApplicationServices.framework in Frameworks */, 75 | 151D1108171A1FC7007F052E /* AppKit.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | /* End PBXFrameworksBuildPhase section */ 80 | 81 | /* Begin PBXGroup section */ 82 | 150A868716D127BC00DE69E3 = { 83 | isa = PBXGroup; 84 | children = ( 85 | 150A86A116D1284800DE69E3 /* ScriptingBridge.framework */, 86 | 150A869516D127BC00DE69E3 /* menudump */, 87 | 150A869216D127BC00DE69E3 /* Frameworks */, 88 | 150A869116D127BC00DE69E3 /* Products */, 89 | 150A86A516D12D0D00DE69E3 /* System Events.app */, 90 | ); 91 | sourceTree = ""; 92 | }; 93 | 150A869116D127BC00DE69E3 /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 150A869016D127BC00DE69E3 /* menudump */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 150A869216D127BC00DE69E3 /* Frameworks */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 151D1107171A1FC7007F052E /* AppKit.framework */, 105 | 15CB4B2B16EA5F110079928D /* ApplicationServices.framework */, 106 | 150A869316D127BC00DE69E3 /* Foundation.framework */, 107 | ); 108 | name = Frameworks; 109 | sourceTree = ""; 110 | }; 111 | 150A869516D127BC00DE69E3 /* menudump */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 150A869616D127BC00DE69E3 /* main.m */, 115 | 150A869816D127BC00DE69E3 /* Supporting Files */, 116 | 15B0175D16E3F7B200464C5B /* UIAccess.h */, 117 | 15B0175E16E3F7B200464C5B /* UIAccess.m */, 118 | 15DC2D331718947A00361B29 /* MenuItem.h */, 119 | 15DC2D341718947A00361B29 /* MenuItem.m */, 120 | AF8A13F4E656879E540CC4F5 /* menudump.1 */, 121 | AF8A11BABFA13790B9AD743D /* Logger.m */, 122 | AF8A15818A46FE60AFE79627 /* Logger.h */, 123 | ); 124 | path = menudump; 125 | sourceTree = ""; 126 | }; 127 | 150A869816D127BC00DE69E3 /* Supporting Files */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 150A869916D127BC00DE69E3 /* menudump-Prefix.pch */, 131 | ); 132 | name = "Supporting Files"; 133 | sourceTree = ""; 134 | }; 135 | /* End PBXGroup section */ 136 | 137 | /* Begin PBXNativeTarget section */ 138 | 150A868F16D127BC00DE69E3 /* menudump */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = 150A869E16D127BC00DE69E3 /* Build configuration list for PBXNativeTarget "menudump" */; 141 | buildPhases = ( 142 | 150A868C16D127BC00DE69E3 /* Sources */, 143 | 150A868D16D127BC00DE69E3 /* Frameworks */, 144 | 150A868E16D127BC00DE69E3 /* CopyFiles */, 145 | ); 146 | buildRules = ( 147 | 150A86A316D12A9900DE69E3 /* PBXBuildRule */, 148 | ); 149 | dependencies = ( 150 | ); 151 | name = menudump; 152 | productName = menudump; 153 | productReference = 150A869016D127BC00DE69E3 /* menudump */; 154 | productType = "com.apple.product-type.tool"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | 150A868816D127BC00DE69E3 /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | LastUpgradeCheck = 0460; 163 | ORGANIZATIONNAME = "Charles Wise"; 164 | }; 165 | buildConfigurationList = 150A868B16D127BC00DE69E3 /* Build configuration list for PBXProject "menudump" */; 166 | compatibilityVersion = "Xcode 3.2"; 167 | developmentRegion = English; 168 | hasScannedForEncodings = 0; 169 | knownRegions = ( 170 | en, 171 | ); 172 | mainGroup = 150A868716D127BC00DE69E3; 173 | productRefGroup = 150A869116D127BC00DE69E3 /* Products */; 174 | projectDirPath = ""; 175 | projectRoot = ""; 176 | targets = ( 177 | 150A868F16D127BC00DE69E3 /* menudump */, 178 | ); 179 | }; 180 | /* End PBXProject section */ 181 | 182 | /* Begin PBXSourcesBuildPhase section */ 183 | 150A868C16D127BC00DE69E3 /* Sources */ = { 184 | isa = PBXSourcesBuildPhase; 185 | buildActionMask = 2147483647; 186 | files = ( 187 | 150A869716D127BC00DE69E3 /* main.m in Sources */, 188 | 150A86A616D12D3200DE69E3 /* System Events.app in Sources */, 189 | 15B0176016E3F7B200464C5B /* UIAccess.m in Sources */, 190 | 15DC2D351718947A00361B29 /* MenuItem.m in Sources */, 191 | AF8A100E44B7CAE564B62049 /* Logger.m in Sources */, 192 | ); 193 | runOnlyForDeploymentPostprocessing = 0; 194 | }; 195 | /* End PBXSourcesBuildPhase section */ 196 | 197 | /* Begin XCBuildConfiguration section */ 198 | 150A869C16D127BC00DE69E3 /* Debug */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | ALWAYS_SEARCH_USER_PATHS = NO; 202 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 204 | CLANG_CXX_LIBRARY = "libc++"; 205 | CLANG_WARN_CONSTANT_CONVERSION = YES; 206 | CLANG_WARN_EMPTY_BODY = YES; 207 | CLANG_WARN_ENUM_CONVERSION = YES; 208 | CLANG_WARN_INT_CONVERSION = YES; 209 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 210 | COPY_PHASE_STRIP = NO; 211 | CURRENT_PROJECT_VERSION = 1.1; 212 | GCC_C_LANGUAGE_STANDARD = gnu99; 213 | GCC_DYNAMIC_NO_PIC = NO; 214 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 215 | GCC_OPTIMIZATION_LEVEL = 0; 216 | GCC_PREPROCESSOR_DEFINITIONS = ( 217 | "DEBUG=1", 218 | "$(inherited)", 219 | ); 220 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 221 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 222 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 223 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 224 | GCC_WARN_UNUSED_VARIABLE = YES; 225 | MACOSX_DEPLOYMENT_TARGET = 10.7; 226 | ONLY_ACTIVE_ARCH = YES; 227 | SDKROOT = macosx; 228 | }; 229 | name = Debug; 230 | }; 231 | 150A869D16D127BC00DE69E3 /* Release */ = { 232 | isa = XCBuildConfiguration; 233 | buildSettings = { 234 | ALWAYS_SEARCH_USER_PATHS = NO; 235 | ARCHS = "$(ARCHS_STANDARD_64_BIT)"; 236 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 237 | CLANG_CXX_LIBRARY = "libc++"; 238 | CLANG_WARN_CONSTANT_CONVERSION = YES; 239 | CLANG_WARN_EMPTY_BODY = YES; 240 | CLANG_WARN_ENUM_CONVERSION = YES; 241 | CLANG_WARN_INT_CONVERSION = YES; 242 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 243 | COPY_PHASE_STRIP = YES; 244 | CURRENT_PROJECT_VERSION = 1.1; 245 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 246 | GCC_C_LANGUAGE_STANDARD = gnu99; 247 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 248 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 249 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 250 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 251 | GCC_WARN_UNUSED_VARIABLE = YES; 252 | MACOSX_DEPLOYMENT_TARGET = 10.7; 253 | SDKROOT = macosx; 254 | }; 255 | name = Release; 256 | }; 257 | 150A869F16D127BC00DE69E3 /* Debug */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | FRAMEWORK_SEARCH_PATHS = ( 261 | "$(inherited)", 262 | "\"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/Frameworks/ApplicationServices.framework/Versions/A/Frameworks\"", 263 | ); 264 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 265 | GCC_PREFIX_HEADER = "menudump/menudump-Prefix.pch"; 266 | PRODUCT_NAME = "$(TARGET_NAME)"; 267 | }; 268 | name = Debug; 269 | }; 270 | 150A86A016D127BC00DE69E3 /* Release */ = { 271 | isa = XCBuildConfiguration; 272 | buildSettings = { 273 | FRAMEWORK_SEARCH_PATHS = ( 274 | "$(inherited)", 275 | "\"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/Frameworks/ApplicationServices.framework/Versions/A/Frameworks\"", 276 | ); 277 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 278 | GCC_PREFIX_HEADER = "menudump/menudump-Prefix.pch"; 279 | PRODUCT_NAME = "$(TARGET_NAME)"; 280 | }; 281 | name = Release; 282 | }; 283 | /* End XCBuildConfiguration section */ 284 | 285 | /* Begin XCConfigurationList section */ 286 | 150A868B16D127BC00DE69E3 /* Build configuration list for PBXProject "menudump" */ = { 287 | isa = XCConfigurationList; 288 | buildConfigurations = ( 289 | 150A869C16D127BC00DE69E3 /* Debug */, 290 | 150A869D16D127BC00DE69E3 /* Release */, 291 | ); 292 | defaultConfigurationIsVisible = 0; 293 | defaultConfigurationName = Release; 294 | }; 295 | 150A869E16D127BC00DE69E3 /* Build configuration list for PBXNativeTarget "menudump" */ = { 296 | isa = XCConfigurationList; 297 | buildConfigurations = ( 298 | 150A869F16D127BC00DE69E3 /* Debug */, 299 | 150A86A016D127BC00DE69E3 /* Release */, 300 | ); 301 | defaultConfigurationIsVisible = 0; 302 | defaultConfigurationName = Release; 303 | }; 304 | /* End XCConfigurationList section */ 305 | }; 306 | rootObject = 150A868816D127BC00DE69E3 /* Project object */; 307 | } 308 | -------------------------------------------------------------------------------- /menudump.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /menudump.xcodeproj/project.xcworkspace/xcuserdata/ctwise.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctwise/menudump/173580b33e7f79fbac9cd6cca64d97a8e999adfc/menudump.xcodeproj/project.xcworkspace/xcuserdata/ctwise.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /menudump.xcodeproj/project.xcworkspace/xcuserdata/ctwise.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | IssueFilterStyle 12 | ShowActiveSchemeOnly 13 | LiveSourceIssuesEnabled 14 | 15 | SnapshotAutomaticallyBeforeSignificantChanges 16 | 17 | SnapshotLocationStyle 18 | Default 19 | 20 | 21 | -------------------------------------------------------------------------------- /menudump.xcodeproj/xcuserdata/ctwise.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 18 | 19 | 31 | 32 | 44 | 45 | 57 | 58 | 70 | 71 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /menudump.xcodeproj/xcuserdata/ctwise.xcuserdatad/xcschemes/menudump.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 66 | 67 | 70 | 71 | 74 | 75 | 76 | 77 | 83 | 84 | 90 | 91 | 92 | 93 | 95 | 96 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /menudump.xcodeproj/xcuserdata/ctwise.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | menudump.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 150A868F16D127BC00DE69E3 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /menudump/Logger.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Charles Wise on 4/16/13. 3 | // 4 | 5 | #import 6 | 7 | #define trace(args...) [[Logger singleton] debugWithLevel:kTrace line:__LINE__ funcName:__PRETTY_FUNCTION__ message:args]; 8 | #define debug(args...) [[Logger singleton] debugWithLevel:kDebug line:__LINE__ funcName:__PRETTY_FUNCTION__ message:args]; 9 | #define info(args...) [[Logger singleton] debugWithLevel:kInfo line:__LINE__ funcName:__PRETTY_FUNCTION__ message:args]; 10 | #define warn(args...) [[Logger singleton] debugWithLevel:kWarn line:__LINE__ funcName:__PRETTY_FUNCTION__ message:args]; 11 | #define error(args...) [[Logger singleton] debugWithLevel:kError line:__LINE__ funcName:__PRETTY_FUNCTION__ message:args]; 12 | 13 | typedef enum { 14 | kTrace=0, kDebug=1, kInfo=2, kWarn=3, kError=4, kSilent=5 15 | } LoggerLevel; 16 | 17 | @interface Logger : NSObject 18 | 19 | @property (nonatomic, assign) LoggerLevel logThreshold; 20 | @property (nonatomic, assign, getter=isAsync) BOOL async; 21 | 22 | +(Logger *) singleton; 23 | 24 | -(void) debugWithLevel:(LoggerLevel)level 25 | line:(int)line 26 | funcName:(const char *)funcName 27 | message:(NSString *)msg, ...; 28 | 29 | @end -------------------------------------------------------------------------------- /menudump/Logger.m: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Charles Wise on 4/16/13. 3 | // 4 | 5 | #import "Logger.h" 6 | 7 | @implementation Logger 8 | 9 | @synthesize logThreshold, async; 10 | 11 | +(Logger *) singleton { 12 | static dispatch_once_t pred; 13 | static Logger *shared = nil; 14 | dispatch_once(&pred, ^{ 15 | shared = [[Logger alloc] init]; 16 | shared.logThreshold = (LoggerLevel) kSilent; 17 | shared.async = FALSE; 18 | }); 19 | return shared; 20 | } 21 | 22 | -(void) debugWithLevel:(LoggerLevel)level 23 | line:(int)line 24 | funcName:(const char *)funcName 25 | message:(NSString *)msg, ... { 26 | 27 | const char* const levelName[6] = { "TRACE", "DEBUG", " INFO", " WARN", "ERROR", "SILENT" }; 28 | 29 | va_list ap; 30 | va_start (ap, msg); 31 | msg = [[NSString alloc] initWithFormat:msg arguments:ap]; 32 | va_end (ap); 33 | 34 | if (level>=logThreshold){ 35 | msg = [NSString stringWithFormat:@"%5s %50s:%3d - %@", levelName[level], funcName, line, msg]; 36 | 37 | if ([self isAsync]){ 38 | // change the queues if you like 39 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 40 | dispatch_async(dispatch_get_main_queue(), ^{ 41 | NSLog(@"%@", msg); 42 | }); 43 | }); 44 | } else { 45 | NSLog(@"%@", msg); 46 | } 47 | 48 | } 49 | } 50 | 51 | @end -------------------------------------------------------------------------------- /menudump/MenuItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // MenuItem.h 3 | // menudump 4 | // 5 | // Created by Charles Wise on 4/12/13. 6 | // 7 | 8 | #import 9 | 10 | @interface MenuItem : NSObject 11 | 12 | @property (retain) NSString *name; 13 | @property (retain) NSArray *children; 14 | @property (retain) NSString *shortcut; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /menudump/MenuItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // MenuItem.m 3 | // menudump 4 | // 5 | // Created by Charles Wise on 4/12/13. 6 | // 7 | 8 | #import "MenuItem.h" 9 | 10 | @implementation MenuItem { 11 | @private 12 | NSString *_name; 13 | NSArray *_children; 14 | NSString *_shortcut; 15 | } 16 | 17 | @synthesize name = _name; 18 | @synthesize children = _children; 19 | @synthesize shortcut = _shortcut; 20 | @end 21 | -------------------------------------------------------------------------------- /menudump/UIAccess.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIAccess.h 3 | // menudump 4 | // 5 | // Created by Charles Wise on 3/3/13. 6 | // 7 | 8 | #import 9 | 10 | @interface UIAccess : NSObject 11 | 12 | - (NSArray *)getAppMenu:(NSRunningApplication *) pid; 13 | - (NSString *)convertMenuToJSON:(NSArray *)menu app:(NSRunningApplication *)menuApp; 14 | - (NSString *)convertMenuToYAML:(NSArray *)menu app:(NSRunningApplication *)menuApp; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /menudump/UIAccess.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIAccess.m 3 | // menudump 4 | // 5 | // Created by Charles Wise on 3/3/13. 6 | // 7 | 8 | #import "UIAccess.h" 9 | #import "MenuItem.h" 10 | #import "Logger.h" 11 | 12 | @implementation UIAccess 13 | 14 | id getAttribute(AXUIElementRef element, CFStringRef attribute) { 15 | trace(@"Getting attribute %@", attribute); 16 | CFTypeRef value = nil; 17 | if (AXUIElementCopyAttributeValue(element, attribute, &value) != kAXErrorSuccess) return nil; 18 | if (AXValueGetType((AXValueRef) value) == kAXValueAXErrorType) return nil; 19 | return value; 20 | } 21 | 22 | bool __unused isEnabled(AXUIElementRef element) { 23 | CFTypeRef enabled = NULL; 24 | if (AXUIElementCopyAttributeValue(element, kAXEnabledAttribute, &enabled) != kAXErrorSuccess) return false; 25 | return CFBooleanGetValue(enabled); 26 | } 27 | 28 | long getLongAttribute(AXUIElementRef element, CFStringRef attribute) { 29 | CFNumberRef valueRef = (CFNumberRef) getAttribute(element, attribute); 30 | long result = 0; 31 | if (valueRef) { 32 | CFNumberGetValue(valueRef, kCFNumberLongType, &result); 33 | } 34 | return result; 35 | } 36 | 37 | NSString *decodeKeyMask(long cmdModifiers) { 38 | trace(@"Decoding key mask %li", cmdModifiers) 39 | NSString *result = @""; 40 | if (cmdModifiers == 0x18) { 41 | result = @"fn fn"; 42 | } else { 43 | if (cmdModifiers & 0x04) { 44 | result = [result stringByAppendingString:@"⌃"]; 45 | } 46 | if (cmdModifiers & 0x02) { 47 | result = [result stringByAppendingString:@"⌥"]; 48 | } 49 | if (cmdModifiers & 0x01) { 50 | result = [result stringByAppendingString:@"⇧"]; 51 | } 52 | if (!(cmdModifiers & 0x08)) { 53 | result = [result stringByAppendingString:@"⌘"]; 54 | } 55 | } 56 | return result; 57 | } 58 | 59 | NSString *getMenuItemShortcut(AXUIElementRef element, NSDictionary *virtualKeys) { 60 | trace(@"Getting menu item shortcut"); 61 | NSString *result = nil; 62 | 63 | NSString *cmdChar = getAttribute(element, kAXMenuItemCmdCharAttribute); 64 | NSString *base = cmdChar; 65 | long cmdModifiers = getLongAttribute(element, kAXMenuItemCmdModifiersAttribute); 66 | long cmdVirtualKey = getLongAttribute(element, kAXMenuItemCmdVirtualKeyAttribute); 67 | 68 | if (base) { 69 | if ([base characterAtIndex:0] == 0x7f) { 70 | base = @"⌦"; 71 | } 72 | } else if (cmdVirtualKey > 0) { 73 | NSString *virtualLookup = [virtualKeys objectForKey:[NSNumber numberWithLong:cmdVirtualKey]]; 74 | if (virtualLookup) { 75 | base = virtualLookup; 76 | } 77 | } 78 | NSString *modifiers = decodeKeyMask(cmdModifiers); 79 | if (base) { 80 | result = [modifiers stringByAppendingString:base]; 81 | } 82 | trace(@"Shortcut is %@", result); 83 | return result; 84 | } 85 | 86 | bool shouldSkip(NSString * bundleIdentifier, NSInteger depth, NSString * name) { 87 | bool result = false; 88 | 89 | // Skip the top-level Apple menu 90 | if (depth == 0 && [name isEqualToString:@"Apple"]) { 91 | result = true; 92 | } else if (depth == 2 && [name isEqualToString:@"Services"]) { 93 | result = true; 94 | } else if (depth == 0 && [bundleIdentifier isEqualToString:@"com.apple.Safari"]) { 95 | // These two menus are time-sucks in Safari 96 | if ([name isEqualToString:@"History"] || [name isEqualToString:@"Bookmarks"]) { 97 | result = true; 98 | } 99 | } 100 | 101 | trace(@"Check to see if we should skip menu item %@, at depth %i, for app %@", name, depth, bundleIdentifier); 102 | 103 | return result; 104 | } 105 | 106 | NSArray *menuItemsForElement(NSString *bundleIdentifier, AXUIElementRef element, NSInteger depth, NSInteger maxDepth, NSDictionary *virtualKeys) { 107 | debug(@"%@Looking at menu at depth %i", padding((int) depth), depth); 108 | NSArray *children = nil; 109 | AXUIElementCopyAttributeValue(element, kAXChildrenAttribute, (CFTypeRef *) &children); 110 | 111 | NSMutableArray *menuItems = [NSMutableArray array]; 112 | for (id child in children) { 113 | // We don't have focus, so we can't skip disabled menu entries. 114 | // if (!isEnabled((AXUIElementRef) child)) continue; 115 | 116 | NSString *name = getAttribute((AXUIElementRef) child, kAXTitleAttribute); 117 | 118 | if (shouldSkip(bundleIdentifier, depth, name)) { 119 | continue; 120 | } 121 | 122 | // Get any children for this menu entry 123 | NSArray *mChildren = nil; 124 | AXUIElementCopyAttributeValue(element, kAXChildrenAttribute, (CFTypeRef *) &mChildren); 125 | NSUInteger mChildrenCount = [mChildren count]; 126 | 127 | // Create the menu item tracking object 128 | MenuItem *menuItem = [[[MenuItem alloc] init] autorelease]; 129 | menuItem.name = name; 130 | 131 | // Don't recurse further if a menu entry has too many children or we've hit max depth 132 | if (mChildrenCount > 0 || mChildrenCount < 40 || depth < maxDepth) { 133 | // Otherwise recurse on this menu item's children 134 | menuItem.children = menuItemsForElement(bundleIdentifier, (AXUIElementRef) child, depth + 1, maxDepth, virtualKeys); 135 | } 136 | 137 | // Save this menu item to the list if it has a name. 138 | if (name && [name length] > 0) { 139 | menuItem.shortcut = getMenuItemShortcut((AXUIElementRef) child, virtualKeys); 140 | 141 | [menuItems addObject:menuItem]; 142 | } else { 143 | // This isn't a menu item, just save its children 144 | 145 | debug(@"%@Skipping menu item", padding((int)(depth + 1))); 146 | 147 | [menuItems addObjectsFromArray:menuItem.children]; 148 | } 149 | } 150 | 151 | return menuItems; 152 | } 153 | 154 | NSString *escape(NSString *text) { 155 | NSString *result = [text stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; 156 | result = [result stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; 157 | return result; 158 | } 159 | 160 | NSString *padding(int length) { 161 | NSMutableString *padding = [NSMutableString stringWithString:@""]; 162 | for (int i = 0; i < length; i++) { 163 | [padding appendString:@" "]; 164 | } 165 | return padding; 166 | } 167 | 168 | NSString *buildLocator(MenuItem *item, NSArray *parents) { 169 | bool needLocator = parents && [parents count] > 0; 170 | NSArray *children = item.children; 171 | needLocator = needLocator && (!children || [children count] == 0); 172 | if (needLocator) { 173 | NSString *name = item.name; 174 | NSString *menuItem = [NSString stringWithFormat:@"menu item \"%@\"", name]; 175 | NSMutableString *buffer = [NSMutableString stringWithString:menuItem]; 176 | 177 | unsigned long pathCount = [parents count]; 178 | for (NSString *parent in [parents reverseObjectEnumerator]) { 179 | NSString *menuType = @"menu bar item"; 180 | if (pathCount > 1) { 181 | menuType = @"menu item"; 182 | } 183 | NSString *menu = [NSString stringWithFormat:@" of menu \"%@\" of %@ \"%@\"", parent, menuType, parent]; 184 | [buffer appendString:menu]; 185 | pathCount--; 186 | } 187 | 188 | [buffer appendString:@" of menu bar 1"]; 189 | 190 | return buffer; 191 | } else { 192 | return [NSMutableString stringWithString:@""]; 193 | } 194 | } 195 | 196 | NSString *buildMenuPath(NSArray *parents) { 197 | if (!parents || [parents count] == 0) { 198 | return [NSMutableString stringWithString:@""]; 199 | } else { 200 | NSMutableString *buffer = [NSMutableString stringWithString:@""]; 201 | 202 | for (NSString *parent in parents) { 203 | if ([buffer length] > 0) { 204 | [buffer appendString:@" > "]; 205 | } 206 | [buffer appendString:parent]; 207 | } 208 | 209 | return buffer; 210 | } 211 | } 212 | 213 | NSString *menuToJSON(NSArray *menu, int depth, NSArray *parents) { 214 | NSMutableString *buffer = [NSMutableString stringWithString:@""]; 215 | NSString *offset = padding(depth * 2); 216 | NSString *offset2 = padding((depth + 1) * 2); 217 | NSString *offset3 = padding((depth + 2) * 2); 218 | if (depth > 0) { 219 | [buffer appendString:@" "]; 220 | } 221 | [buffer appendString:@"[\n"]; 222 | for (MenuItem *item in menu) { 223 | [buffer appendString:offset2]; 224 | [buffer appendString:@"{\n"]; 225 | 226 | NSString *shortcut = item.shortcut; 227 | if (!shortcut) { 228 | shortcut = @""; 229 | } 230 | [buffer appendString:offset3]; 231 | [buffer appendString:[NSString stringWithFormat:@"\"name\": \"%@\",\n", escape(item.name)]]; 232 | [buffer appendString:offset3]; 233 | [buffer appendString:[NSString stringWithFormat:@"\"shortcut\": \"%@\",\n", escape(shortcut)]]; 234 | 235 | NSString *children = @"[]"; 236 | if (item.children && ([item.children count] > 0)) { 237 | NSArray *childParents = [NSArray arrayWithArray:parents]; 238 | childParents = [childParents arrayByAddingObject:item.name]; 239 | children = menuToJSON(item.children, depth + 2, childParents); 240 | } 241 | 242 | [buffer appendString:offset3]; 243 | [buffer appendString:[NSString stringWithFormat:@"\"locator\": \"%@\",\n", escape(buildLocator(item, parents))]]; 244 | [buffer appendString:offset3]; 245 | [buffer appendString:[NSString stringWithFormat:@"\"menuPath\": \"%@\",\n", escape(buildMenuPath(parents))]]; 246 | [buffer appendString:offset3]; 247 | [buffer appendString:[NSString stringWithFormat:@"\"children\": %@\n", children]]; 248 | 249 | [buffer appendString:offset2]; 250 | [buffer appendString:@"},\n"]; 251 | } 252 | [buffer appendString:offset]; 253 | [buffer appendString:@"]\n"]; 254 | 255 | return buffer; 256 | } 257 | 258 | NSString *menuToYAML(NSArray *menu, int startingOffset, NSArray *parents) { 259 | NSMutableString *buffer = [NSMutableString stringWithString:@""]; 260 | NSString *offset = padding(startingOffset + 4); 261 | NSString *offset2 = padding(startingOffset + 6); 262 | for (MenuItem *item in menu) { 263 | [buffer appendString:offset]; 264 | [buffer appendString:@"- "]; 265 | NSString *shortcut = item.shortcut; 266 | if (!shortcut) { 267 | shortcut = @""; 268 | } 269 | [buffer appendString:[NSString stringWithFormat:@"name: \"%@\"\n", escape(item.name)]]; 270 | [buffer appendString:offset2]; 271 | [buffer appendString:[NSString stringWithFormat:@"shortcut: \"%@\"\n", escape(shortcut)]]; 272 | 273 | NSString *children = @""; 274 | if (item.children && ([item.children count] > 0)) { 275 | NSArray *childParents = [NSArray arrayWithArray:parents]; 276 | childParents = [childParents arrayByAddingObject:item.name]; 277 | children = menuToYAML(item.children, startingOffset + 6, childParents); 278 | } 279 | 280 | [buffer appendString:offset2]; 281 | [buffer appendString:[NSString stringWithFormat:@"locator: \"%@\"\n", escape(buildLocator(item, parents))]]; 282 | [buffer appendString:offset2]; 283 | [buffer appendString:[NSString stringWithFormat:@"menuPath: \"%@\"\n", escape(buildMenuPath(parents))]]; 284 | if ([children length] > 0) { 285 | [buffer appendString:offset2]; 286 | [buffer appendString:[NSString stringWithFormat:@"children:\n%@", children]]; 287 | } 288 | } 289 | 290 | return buffer; 291 | } 292 | 293 | NSMutableDictionary * buildVirtualKeyDictionary() { 294 | NSMutableDictionary *virtualKeys = [NSMutableDictionary dictionary]; 295 | 296 | [virtualKeys setObject:@"↩" forKey:[NSNumber numberWithLong:0x24]]; // kVK_Return 297 | [virtualKeys setObject:@"⇥" forKey:[NSNumber numberWithLong:0x30]]; // kVK_Tab 298 | [virtualKeys setObject:@"␣" forKey:[NSNumber numberWithLong:0x31]]; // kVK_Space 299 | [virtualKeys setObject:@"⌫" forKey:[NSNumber numberWithLong:0x33]]; // kVK_Delete 300 | [virtualKeys setObject:@"⎋" forKey:[NSNumber numberWithLong:0x35]]; // kVK_Escape 301 | [virtualKeys setObject:@"⇪" forKey:[NSNumber numberWithLong:0x39]]; // kVK_CapsLock 302 | [virtualKeys setObject:@"fn" forKey:[NSNumber numberWithLong:0x3F]]; // kVK_Function 303 | [virtualKeys setObject:@"F17" forKey:[NSNumber numberWithLong:0x40]]; // kVK_F17 304 | [virtualKeys setObject:@"⌧" forKey:[NSNumber numberWithLong:0x47]]; // kVK_ANSI_KeypadClear 305 | [virtualKeys setObject:@"⌤" forKey:[NSNumber numberWithLong:0x4C]]; // kVK_ANSI_KeypadEnter 306 | [virtualKeys setObject:@"F18" forKey:[NSNumber numberWithLong:0x4F]]; // kVK_F18 307 | [virtualKeys setObject:@"F19" forKey:[NSNumber numberWithLong:0x50]]; // kVK_F19 308 | [virtualKeys setObject:@"F20" forKey:[NSNumber numberWithLong:0x5A]]; // kVK_F20 309 | [virtualKeys setObject:@"F5" forKey:[NSNumber numberWithLong:0x60]]; // kVK_F5 310 | [virtualKeys setObject:@"F6" forKey:[NSNumber numberWithLong:0x61]]; // kVK_F6 311 | [virtualKeys setObject:@"F7" forKey:[NSNumber numberWithLong:0x62]]; // kVK_F7 312 | [virtualKeys setObject:@"F3" forKey:[NSNumber numberWithLong:0x63]]; // kVK_F3 313 | [virtualKeys setObject:@"F8" forKey:[NSNumber numberWithLong:0x64]]; // kVK_F8 314 | [virtualKeys setObject:@"F9" forKey:[NSNumber numberWithLong:0x65]]; // kVK_F9 315 | [virtualKeys setObject:@"F11" forKey:[NSNumber numberWithLong:0x67]]; // kVK_F11 316 | [virtualKeys setObject:@"F13" forKey:[NSNumber numberWithLong:0x69]]; // kVK_F13 317 | [virtualKeys setObject:@"F16" forKey:[NSNumber numberWithLong:0x6A]]; // kVK_F16 318 | [virtualKeys setObject:@"F14" forKey:[NSNumber numberWithLong:0x6B]]; // kVK_F14 319 | [virtualKeys setObject:@"F10" forKey:[NSNumber numberWithLong:0x6D]]; // kVK_F10 320 | [virtualKeys setObject:@"F12" forKey:[NSNumber numberWithLong:0x6F]]; // kVK_F12 321 | [virtualKeys setObject:@"F15" forKey:[NSNumber numberWithLong:0x71]]; // kVK_F15 322 | [virtualKeys setObject:@"INS" forKey:[NSNumber numberWithLong:0x72]]; // Insert 323 | [virtualKeys setObject:@"↖" forKey:[NSNumber numberWithLong:0x73]]; // kVK_Home 324 | [virtualKeys setObject:@"⇞" forKey:[NSNumber numberWithLong:0x74]]; // kVK_PageUp 325 | [virtualKeys setObject:@"⌦" forKey:[NSNumber numberWithLong:0x75]]; // kVK_ForwardDelete 326 | [virtualKeys setObject:@"F4" forKey:[NSNumber numberWithLong:0x76]]; // kVK_F4 327 | [virtualKeys setObject:@"↘" forKey:[NSNumber numberWithLong:0x77]]; // kVK_End 328 | [virtualKeys setObject:@"F2" forKey:[NSNumber numberWithLong:0x78]]; // kVK_F2 329 | [virtualKeys setObject:@"⇟" forKey:[NSNumber numberWithLong:0x79]]; // kVK_PageDown 330 | [virtualKeys setObject:@"F1" forKey:[NSNumber numberWithLong:0x7A]]; // kVK_F1 331 | [virtualKeys setObject:@"←" forKey:[NSNumber numberWithLong:0x7B]]; // kVK_LeftArrow 332 | [virtualKeys setObject:@"→" forKey:[NSNumber numberWithLong:0x7C]]; // kVK_RightArrow 333 | [virtualKeys setObject:@"↓" forKey:[NSNumber numberWithLong:0x7D]]; // kVK_DownArrow 334 | [virtualKeys setObject:@"↑" forKey:[NSNumber numberWithLong:0x7E]]; // kVK_UpArrow 335 | 336 | return virtualKeys; 337 | } 338 | 339 | - (NSArray *)getAppMenu:(NSRunningApplication *)menuApp { 340 | AXUIElementRef app = AXUIElementCreateApplication(menuApp.processIdentifier); 341 | AXUIElementRef menuBar; 342 | AXUIElementCopyAttributeValue(app, kAXMenuBarAttribute, (CFTypeRef *) &menuBar); 343 | 344 | return menuItemsForElement(menuApp.bundleIdentifier, menuBar, 0, 5, buildVirtualKeyDictionary()); 345 | } 346 | 347 | - (NSString *)convertMenuToJSON:(NSArray *)menu app:(NSRunningApplication *)menuApp { 348 | debug(@"Converting menu to JSON"); 349 | NSMutableString *buffer = [NSMutableString stringWithString:@"{\n"]; 350 | [buffer appendString:@" \"name\": \""]; 351 | [buffer appendString:menuApp.localizedName]; 352 | [buffer appendString:@"\"\n"]; 353 | [buffer appendString:@" \"bundleIdentifier\": \""]; 354 | [buffer appendString:menuApp.bundleIdentifier]; 355 | [buffer appendString:@"\"\n"]; 356 | [buffer appendString:@" \"bundlePath\": \""]; 357 | [buffer appendString:menuApp.bundleURL.path]; 358 | [buffer appendString:@"\"\n"]; 359 | [buffer appendString:@" \"executablePath\": \""]; 360 | [buffer appendString:menuApp.executableURL.path]; 361 | [buffer appendString:@"\"\n"]; 362 | [buffer appendString:@" \"menus\":"]; 363 | [buffer appendString:menuToJSON(menu, 2, [[[NSArray alloc] init] autorelease])]; 364 | [buffer appendString:@"}"]; 365 | return buffer; 366 | } 367 | 368 | - (NSString *)convertMenuToYAML:(NSArray *)menu app:(NSRunningApplication *)menuApp { 369 | debug(@"Converting menu to YAML"); 370 | NSMutableString *buffer = [NSMutableString stringWithString:@"application:\n"]; 371 | [buffer appendString:@" name: \""]; 372 | [buffer appendString:menuApp.localizedName]; 373 | [buffer appendString:@"\"\n"]; 374 | [buffer appendString:@" bundleIdentifier: \""]; 375 | [buffer appendString:menuApp.bundleIdentifier]; 376 | [buffer appendString:@"\"\n"]; 377 | [buffer appendString:@" bundlePath: \""]; 378 | [buffer appendString:menuApp.bundleURL.path]; 379 | [buffer appendString:@"\"\n"]; 380 | [buffer appendString:@" executablePath: \""]; 381 | [buffer appendString:menuApp.executableURL.path]; 382 | [buffer appendString:@"\"\n"]; 383 | [buffer appendString:@"menus:\n"]; 384 | [buffer appendString:menuToYAML(menu, 0, [[[NSArray alloc] init] autorelease])]; 385 | return buffer; 386 | } 387 | @end 388 | -------------------------------------------------------------------------------- /menudump/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // menudump 4 | // 5 | // Created by Charles Wise on 2/17/13. 6 | // 7 | 8 | #include "UIAccess.h" 9 | #include "Logger.h" 10 | 11 | NSRunningApplication *getAppByPid(pid_t pid) { 12 | debug(@"Searching through NSWorkspace for pid %i", pid); 13 | NSArray *appNames = [[NSWorkspace sharedWorkspace] runningApplications]; 14 | for (NSRunningApplication *app in appNames) { 15 | if (app.processIdentifier == pid) { 16 | debug(@"Found NSRunningApplication for pid %i", pid); 17 | return app; 18 | } 19 | } 20 | return nil; 21 | } 22 | 23 | int main(int argc, const char *argv[]) { 24 | @autoreleasepool { 25 | pid_t pid = -1; 26 | bool showHelp = false; 27 | bool outputJson = true; 28 | 29 | int debugLevel = 0; 30 | int offset = 1; 31 | while (offset < argc) { 32 | char const *value = argv[offset]; 33 | if (strncasecmp(value, "--help", 6) == 0) { 34 | showHelp = true; 35 | offset++; 36 | } else if (strncasecmp(value, "--yaml", 6) == 0) { 37 | outputJson = false; 38 | offset++; 39 | } else if (strncasecmp(value, "--pid", 5) == 0) { 40 | if (argc >= (offset + 1)) { 41 | pid = atoi(argv[offset + 1]); 42 | offset = offset + 2; 43 | } 44 | } else if (strncasecmp(value, "--debug", 7) == 0) { 45 | debugLevel++; 46 | offset++; 47 | } else { 48 | offset++; 49 | } 50 | } 51 | 52 | if (debugLevel >=2) { 53 | [Logger singleton].logThreshold = kTrace; 54 | } else if (debugLevel == 1) { 55 | [Logger singleton].logThreshold = kDebug; 56 | } 57 | 58 | if (showHelp) { 59 | printf("menudump v1.1\n"); 60 | printf("Usage: menudump [--pid ] [--yaml] [--help]\n"); 61 | printf(" Dumps the menu contents of a given application in JSON format. Defaults to the front-most application.\n"); 62 | printf(" --pid to target a specific application.\n"); 63 | printf(" --yaml to output in YAML format instead.\n"); 64 | printf(" --debug to turn on debug output.\n"); 65 | printf(" --help print this message\n"); 66 | exit(1); 67 | } 68 | 69 | NSRunningApplication *menuApp = nil; 70 | if (pid == -1) { 71 | menuApp = [[NSWorkspace sharedWorkspace] menuBarOwningApplication]; 72 | if (!menuApp) { 73 | printf("Unable to find the app that owns the menu bar"); 74 | exit(1); 75 | } 76 | } else { 77 | menuApp = getAppByPid(pid); 78 | } 79 | 80 | if (menuApp) { 81 | UIAccess *ui = [[UIAccess new] autorelease]; 82 | NSArray *menu = [ui getAppMenu:menuApp]; 83 | if (menu && [menu count] == 0) { 84 | printf("The menu structure wasn't readable. Make sure that 'Enable access for assistive devices' is checked in OS/X Settings."); 85 | exit(1); 86 | } 87 | NSString *contents; 88 | if (outputJson) { 89 | contents = [ui convertMenuToJSON:menu app:menuApp]; 90 | } else { 91 | contents = [ui convertMenuToYAML:menu app:menuApp]; 92 | } 93 | printf("%s", [contents UTF8String]); 94 | } else { 95 | printf("Unable to find the app that matches the pid"); 96 | exit(1); 97 | } 98 | } 99 | 100 | return 0; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /menudump/menudump-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'menudump' target in the 'menudump' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /menudump/menudump.1: -------------------------------------------------------------------------------- 1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. 2 | .\"See Also: 3 | .\"man mdoc.samples for a complete listing of options 4 | .\"man mdoc for the short list of editing options 5 | .\"/usr/share/misc/mdoc.template 6 | .Dd 2/17/13 \" DATE 7 | .Dt menudump 1 \" Program name and manual section number 8 | .Os Darwin 9 | .Sh NAME \" Section Header - required - don't modify 10 | .Nm menudump, 11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key 12 | .\" words here as the database is built based on the words here and in the .ND line. 13 | .Nm Other_name_for_same_program(), 14 | .Nm Yet another name for the same program. 15 | .\" Use .Nm macro to designate other names for the documented program. 16 | .Nd This line parsed for whatis database. 17 | .Sh SYNOPSIS \" Section Header - required - don't modify 18 | .Nm 19 | .Op Fl abcd \" [-abcd] 20 | .Op Fl a Ar path \" [-a path] 21 | .Op Ar file \" [file] 22 | .Op Ar \" [file ...] 23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline 24 | arg2 ... \" Arguments 25 | .Sh DESCRIPTION \" Section Header - required - don't modify 26 | Use the .Nm macro to refer to your program throughout the man page like such: 27 | .Nm 28 | Underlining is accomplished with the .Ar macro like this: 29 | .Ar underlined text . 30 | .Pp \" Inserts a space 31 | A list of items with descriptions: 32 | .Bl -tag -width -indent \" Begins a tagged list 33 | .It item a \" Each item preceded by .It macro 34 | Description of item a 35 | .It item b 36 | Description of item b 37 | .El \" Ends the list 38 | .Pp 39 | A list of flags and their descriptions: 40 | .Bl -tag -width -indent \" Differs from above in tag removed 41 | .It Fl a \"-a flag as a list item 42 | Description of -a flag 43 | .It Fl b 44 | Description of -b flag 45 | .El \" Ends the list 46 | .Pp 47 | .\" .Sh ENVIRONMENT \" May not be needed 48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 49 | .\" .It Ev ENV_VAR_1 50 | .\" Description of ENV_VAR_1 51 | .\" .It Ev ENV_VAR_2 52 | .\" Description of ENV_VAR_2 53 | .\" .El 54 | .Sh FILES \" File used or created by the topic of the man page 55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact 56 | .It Pa /usr/share/file_name 57 | FILE_1 description 58 | .It Pa /Users/joeuser/Library/really_long_file_name 59 | FILE_2 description 60 | .El \" Ends the list 61 | .\" .Sh DIAGNOSTICS \" May not be needed 62 | .\" .Bl -diag 63 | .\" .It Diagnostic Tag 64 | .\" Diagnostic informtion here. 65 | .\" .It Diagnostic Tag 66 | .\" Diagnostic informtion here. 67 | .\" .El 68 | .Sh SEE ALSO 69 | .\" List links in ascending order by section, alphabetically within a section. 70 | .\" Please do not reference files that do not exist without filing a bug report 71 | .Xr a 1 , 72 | .Xr b 1 , 73 | .Xr c 1 , 74 | .Xr a 2 , 75 | .Xr b 2 , 76 | .Xr a 3 , 77 | .Xr b 3 78 | .\" .Sh BUGS \" Document known, unremedied bugs 79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner --------------------------------------------------------------------------------