7

私のコードでは、 ( )AXMenuItemsからの配列を取得しようとしています。メニューは正常にログに記録されます。これが私のコードです。AXMenuAXUIElementRef

NSArray *anArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"];
AXUIElementRef anAXDockApp = AXUIElementCreateApplication([[anArray objectAtIndex:0] processIdentifier]);

CFTypeRef aChildren;
AXUIElementCopyAttributeValue(anAXDockApp, kAXChildrenAttribute, &aChildren);

SafeCFRelease(anAXDockApp);

CFTypeRef aMenu = CFArrayGetValueAtIndex(aChildren, 0);

NSLog(@"aMenu: %@", aMenu);

                    // Get menu items
CFTypeRef aMenuChildren;
AXUIElementCopyAttributeValue(aMenu, kAXVisibleChildrenAttribute, &aMenuChildren);

for (NSInteger i = 0; i < CFArrayGetCount(aMenuChildren); i++) {

    AXUIElementRef aMenuItem = [self copyAXUIElementFrom:aMenu role:kAXMenuItemRole atIndex:i];

    NSLog(@"aMenuItem: %@", aMenuItem); // logs (null)

    CFTypeRef aTitle;
    AXUIElementCopyAttributeValue(aMenuItem, kAXTitleAttribute, &aTitle);


    if ([(__bridge NSString *)aTitle isEqualToString:@"New Window"] || [(__bridge NSString *)aTitle isEqualToString:@"New Finder Window"]) /* Crashes here (i can see why)*/{

        AXUIElementPerformAction(aMenuItem, kAXPressAction);

        [NSThread sleepForTimeInterval:1];

        break;

    }

}

のリストを取得する正しい方法は何AXMenuItemsですか?

アクセシビリティ インスペクターのスクリーンショット:

検査官

4

2 に答える 2

20

メニューを取得するために使用する@Willekeの回答を使用して、回答を見つけました。AXUIElementCopyElementAtPosition()複数のドックの向きと非表示があったため、0、1、または 2 よりも読みやすいため、.h ファイルに列挙型を作成する必要がありました。

// .h
typedef enum {
    kDockPositionBottom,
    kDockPositionLeft,
    kDockPositionRight,
    kDockPositionUnknown
} DockPosition;

typedef enum {
    kDockAutohideOn,
    kDockAutohideOff
} DockAutoHideState;

次に、これらの状態を取得するメソッドを.m

// .m
- (DockPosition)dockPosition
{
    NSRect screenRect = [[NSScreen mainScreen] frame];

    NSRect visibleRect = [[NSScreen mainScreen] visibleFrame];

    // Dont need to remove menubar height
    visibleRect.origin.y = 0;

    if (visibleRect.origin.x > screenRect.origin.x) {
        return kDockPositionLeft;
    } else if (visibleRect.size.width < screenRect.size.width) {
        return kDockPositionRight;
    } else if (visibleRect.size.height < screenRect.size.height) {
        return kDockPositionBottom;
    }
    return kDockPositionUnknown;
}

- (DockAutoHideState)dockHidden
{
    NSString *plistPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Preferences/com.apple.dock.plist"];
    NSDictionary *dockDict = [NSDictionary dictionaryWithContentsOfFile:plistPath];

    CFBooleanRef autohide = CFDictionaryGetValue((__bridge CFDictionaryRef)dockDict, @"autohide");
    if (CFBooleanGetValue(autohide) == true) {
        return kDockAutohideOn;
    }
    return kDockAutohideOff;
}

ドックの位置が不明なため、画面の位置から計算できなかった場合に備えて追加しました。

次に、メソッドを使用してメニューバーからドック項目を取得しました。

- (AXUIElementRef)getDockItemWithName:(NSString *)name
{
    NSArray *anArray = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"];
    AXUIElementRef anAXDockApp = AXUIElementCreateApplication([[anArray objectAtIndex:0] processIdentifier]);

    AXUIElementRef aList = [self copyAXUIElementFrom:anAXDockApp role:kAXListRole atIndex:0];

    CFTypeRef aChildren;
    AXUIElementCopyAttributeValue(aList, kAXChildrenAttribute, &aChildren);
    NSInteger itemIndex = -1;

    for (NSInteger i = 0; i < CFArrayGetCount(aChildren); i++) {

        AXUIElementRef anElement = CFArrayGetValueAtIndex(aChildren, i);

        CFTypeRef aResult;

        AXUIElementCopyAttributeValue(anElement, kAXTitleAttribute, &aResult);

        if ([(__bridge NSString *)aResult isEqualToString:name]) {

            itemIndex = i;
        }
    }
    SafeCFRelease(aChildren);

    if (itemIndex == -1) return nil;

    // We have index now do something with it

    AXUIElementRef aReturnItem = [self copyAXUIElementFrom:aList role:kAXDockItemRole atIndex:itemIndex];
    SafeCFRelease(aList);
    return  aReturnItem;
}

このSafeCFRelease()メソッドは、渡された値が nil でないかどうかを確認してから解放する、非常に単純なメソッドです (以前はいくつかの問題がありました)。

void SafeCFRelease( CFTypeRef cf )
{
    if (cf) CFRelease(cf);
}

そして、このメソッド[copyAXUIElementFrom: role: atIndex:]は、私の質問の別の1つからの@Willekeの回答からのメソッドです:

- (AXUIElementRef)copyAXUIElementFrom:(AXUIElementRef)theContainer role:(CFStringRef)theRole atIndex:(NSInteger)theIndex {
    AXUIElementRef aResultElement = NULL;
    CFTypeRef aChildren;
    AXError anAXError = AXUIElementCopyAttributeValue(theContainer, kAXChildrenAttribute, &aChildren);
    if (anAXError == kAXErrorSuccess) {
        NSUInteger anIndex = -1;
        for (id anElement in (__bridge NSArray *)aChildren) {
            if (theRole) {
                CFTypeRef aRole;
                anAXError = AXUIElementCopyAttributeValue((__bridge AXUIElementRef)anElement, kAXRoleAttribute, &aRole);
                if (anAXError == kAXErrorSuccess) {
                    if (CFStringCompare(aRole, theRole, 0) == kCFCompareEqualTo)
                        anIndex++;
                    SafeCFRelease(aRole);
                }
            }
            else
                anIndex++;
            if (anIndex == theIndex) {
                aResultElement = (AXUIElementRef)CFRetain((__bridge CFTypeRef)(anElement));
                break;
            }
        }
        SafeCFRelease(aChildren);
    }
    return aResultElement;
}

このすべてのコードを取得して、メソッドの 1 つに入れました。

// ドックにあるかどうかを確認します (そうでない場合は実行できません)

                if ([self isAppOfNameInDock:[appDict objectForKey:@"AppName"]]) {

                    // Get dock item

                    AXUIElementRef aDockItem = [self getDockItemWithName:[appDict objectForKey:@"AppName"]];

                    AXUIElementPerformAction(aDockItem, kAXShowMenuAction);

                    [NSThread sleepForTimeInterval:0.5];

                    CGRect aRect;

                    CFTypeRef aPosition;
                    AXUIElementCopyAttributeValue(aDockItem, kAXPositionAttribute, &aPosition);
                    AXValueGetValue(aPosition, kAXValueCGPointType, &aRect.origin);
                    SafeCFRelease(aPosition);

                    CFTypeRef aSize;
                    AXUIElementCopyAttributeValue(aDockItem, kAXSizeAttribute, &aSize);
                    AXValueGetValue(aSize, kAXValueCGSizeType, &aRect.size);
                    SafeCFRelease(aSize);

                    SafeCFRelease(aDockItem);

                    CGPoint aMenuPoint;

                    if ([self dockHidden] == kDockAutohideOff) {
                        switch ([self dockPosition]) {
                            case kDockPositionRight:
                                aMenuPoint = CGPointMake(aRect.origin.x - 18, aRect.origin.y + (aRect.size.height / 2));
                                break;
                            case kDockPositionLeft:
                                aMenuPoint = CGPointMake(aRect.origin.x + aRect.size.width + 18, aRect.origin.y + (aRect.size.height / 2));
                                break;
                            case kDockPositionBottom:
                                aMenuPoint = CGPointMake(aRect.origin.x + (aRect.size.width / 2), aRect.origin.y - 18);
                                break;
                            case kDockPositionUnknown:
                                aMenuPoint = CGPointMake(0, 0);
                                break;
                        }
                    } else {

                        NSRect screenFrame = [[NSScreen mainScreen] frame];

                        switch ([self dockPosition]) {
                            case kDockPositionRight:
                                aMenuPoint = CGPointMake(screenFrame.size.width - 18, aRect.origin.y + (aRect.size.height / 2));
                                break;
                            case kDockPositionLeft:
                                aMenuPoint = CGPointMake(screenFrame.origin.x + 18, aRect.origin.y + (aRect.size.height / 2));
                                break;
                            case kDockPositionBottom:
                                aMenuPoint = CGPointMake(aRect.origin.x + (aRect.size.width / 2), screenFrame.size.height - 18);
                                break;
                            case kDockPositionUnknown:
                                aMenuPoint = CGPointMake(0, 0);
                                break;
                        }
                    }

                    if ((aMenuPoint.x != 0) && (aMenuPoint.y != 0)) {

                        AXUIElementRef _systemWideElement = AXUIElementCreateSystemWide();

                        AXUIElementRef aMenu;
                        AXUIElementCopyElementAtPosition(_systemWideElement, aMenuPoint.x, aMenuPoint.y, &aMenu);

                        SafeCFRelease(_systemWideElement);

                        // Get menu items
                        CFTypeRef aMenuChildren;
                        AXUIElementCopyAttributeValue(aMenu, kAXVisibleChildrenAttribute, &aMenuChildren);

                        NSRunningApplication *app = [[NSRunningApplication runningApplicationsWithBundleIdentifier:[appDict objectForKey:@"BundleID"]] objectAtIndex:0];

                        for (NSInteger i = 0; i < CFArrayGetCount(aMenuChildren); i++) {

                            AXUIElementRef aMenuItem = [self copyAXUIElementFrom:aMenu role:kAXMenuItemRole atIndex:i];

                            CFTypeRef aTitle;
                            AXUIElementCopyAttributeValue(aMenuItem, kAXTitleAttribute, &aTitle);

                            // Supports chrome, safari, and finder
                            if ([(__bridge NSString *)aTitle isEqualToString:@"New Window"] || [(__bridge NSString *)aTitle isEqualToString:@"New Finder Window"]) {

                                AXUIElementPerformAction(aMenuItem, kAXPressAction);

                                NSInteger numberOfWindows = [self numberOfWindowsOpenFromApplicationWithPID:[app processIdentifier]];
                                // Wait until open
                                while ([self numberOfWindowsOpenFromApplicationWithPID:[app processIdentifier]] <= numberOfWindows) {
                                }
                                break;
                            }
                        }
                        SafeCFRelease(aMenu);
                        SafeCFRelease(aMenuChildren);
                    }
                }

これはかなり複雑ですが、うまくいきます。説明できないかもしれませんが、このコードのストレス テストを行ったところ、問題なく動作しました。

于 2016-03-20T14:50:27.610 に答える
1

「AXMenu から AXMenuItems の配列を取得する方法は?」という質問への回答:aMenuChildrenメニュー項目のリストです。role でフィルタリングできることを確認してくださいkAXMenuItemRole

「なぜ動かないの?」という質問への回答: メニューはメニューではありません。Axccessibility Inspector でメニューを調べると、「Parent does not report element as one of its children」という警告が表示されます。Dock アプリには、ドック項目のリストという子が 1 つあります。

于 2016-03-06T13:42:52.867 に答える