簡體   English   中英

如何從 macOS 命令行工具使用 Apple 的 GameController 框架?

[英]How do I use Apple's GameController framework from a macOS Command Line Tool?

我正在嘗試將以下代碼用作 macOS 命令行工具。 重要的是這不是 Cocoa 應用程序,所以這不是一個選項。

相同的代碼在具有 Cocoa App 目標的同一個項目中完美運行並檢測到兼容的控制器,但是當作為命令行工具目標運行時,沒有任何反應並且 API 顯示沒有連接控制器。

顯然,其中一些是人為的……這只是我可以將其歸結為最簡單的方法,並在實際工作時對發生的事情有一些指示。

#import <Cocoa/Cocoa.h>
#import <GameController/GameController.h>


int main( int argc, const char * argv[] )
{
    @autoreleasepool
    {
        NSApplication * application = [NSApplication sharedApplication];

        NSNotificationCenter * center = [NSNotificationCenter defaultCenter];

        [center addObserverForName: GCControllerDidConnectNotification
                            object: nil
                             queue: nil
                        usingBlock: ^(NSNotification * note) {
                            GCController * controller = note.object;
                            printf( "ATTACHED: %s\n", controller.vendorName.UTF8String );
                        }
         ];

        [application finishLaunching];

        bool shouldKeepRunning = true;
        while (shouldKeepRunning)
        {
            printf( "." );

            while (true)
            {
                NSEvent * event = [application
                                   nextEventMatchingMask: NSEventMaskAny
                                   untilDate: nil
                                   inMode: NSDefaultRunLoopMode
                                   dequeue: YES];
                if (event == NULL)
                {
                    break;
                }
                else
                {
                    [application sendEvent: event];
                }
            }

            usleep( 100 * 1000 );
        }
    }

    return 0;
}

我猜這與 Cocoa 應用程序的設置方式或事件循環的處理方式有關。 或者可能有一些內部觸發器初始化 GameController 框架。 API 似乎沒有任何明確的方法來初始化它。

https://developer.apple.com/documentation/gamecontroller?language=objc

任何人都可以闡明我如何讓這個工作?

最終,這段代碼確實需要在 Core Foundation 包中工作,所以如果它實際上可以與 Core Foundation 運行循環一起工作,那將是理想的。

- 編輯 -

我做了一個測試項目來更清楚地說明問題。 有兩個構建目標。 Cocoa 應用程序構建目標工作並接收控制器連接事件。 另一個構建目標,只是一個簡單的 CLI 應用程序,不起作用。 它們都使用相同的源文件。 它還包括兩個代碼路徑,一個是傳統的【NSApp run】,第二個是上面的手動事件循環。 結果是一樣的。

https://www.dropbox.com/s/a6fw3nuegq7bg8x/ControllerTest.zip?dl=0

盡管每個線程都會創建一個運行循環(Cocoa 應用程序的NSRunLoop )來處理輸入事件,但該循環不會自動啟動。 下面的代碼使它與[application run]調用一起[application run] 當運行循環處理正確的事件時,會引發通知。 我在應用程序委托中安裝觀察者只是為了確保所有其他系統此時都已完成初始化。

#import <Cocoa/Cocoa.h>
#import <GameController/GameController.h>

@interface AppDelegate : NSObject <NSApplicationDelegate> @end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)notification {
    NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
    [center addObserverForName: GCControllerDidConnectNotification
                        object: nil
                         queue: nil
                    usingBlock: ^(NSNotification * note) {
                        GCController * controller = note.object;
                        printf( "ATTACHED: %s\n", controller.vendorName.UTF8String );
                    }
     ];
}

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSApplication * application = [NSApplication sharedApplication]; // You can get rid of the variable and just use the global NSApp below instead
        AppDelegate *delegate = [[AppDelegate alloc] init];
        [application setDelegate:delegate];
        [application run];
    }
    return 0;
}

更新對不起,我誤解了這個問題。 上面的代碼適用於連接和斷開控制器,但它沒有使用應用程序啟動時已經連接的設備正確初始化[GCController controllers]數組。

正如您所指出的,連接的設備在 Cocoa 應用程序上使用相同的代碼發送通知,但不在命令行上發送通知。 不同之處在於 Cocoa 應用程序獲得didBecomeActive通知,這會導致私有_GCControllerManager (負責處理NSXPCConnection發布的GameControllerDaemon )接收填充controllers數組的CBApplicationDidBecomeActive消息。

無論如何,我嘗試使命令行應用程序處於活動狀態,以便它路由這些消息,但這不起作用; 應用程序需要在啟動期間盡早發送didBecomeActive消息。

然后我嘗試創建自己的_GCGameController並手動發送CBApplicationDidBecomeActive 那種工作,除了應用程序最終有 2 個這樣的控制器,並且連接被重復。

我需要的是訪問私有_GCGameController對象,但我不知道誰擁有它,所以我無法直接引用它。

所以最后,我使用了方法 swizzling。 下面的代碼更改了在終端應用程序中初始化時調用的最后一個方法_GCGameController startIdleWatchTimer ,因此它之后會發送CBApplicationDidBecomeActive

我知道這不是一個很好的解決方案,使用各種 Apple 的內部代碼,但也許它可以幫助某人獲得更好的結果。 將以下代碼添加到之前的代碼中:

#import <objc/runtime.h>


@interface _GCControllerManager : NSObject
-(void) CBApplicationDidBecomeActive;
-(void) startIdleWatchTimer;
@end

@implementation _GCControllerManager (Extras)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(startIdleWatchTimer);
        SEL swizzledSelector = @selector(myStartIdleWatchTimer);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void) myStartIdleWatchTimer {
    [self myStartIdleWatchTimer];
    [self CBApplicationDidBecomeActive];
}

@end

我有這個與以下 main.m 文件一起工作:

#import <AppKit/AppKit.h>
#import <GameController/GameController.h>

@interface AppDelegate : NSObject<NSApplicationDelegate> @end

@implementation AppDelegate
- (void) applicationDidFinishLaunching: (NSNotification*) notification {
    [NSApp stop: nil]; // Allows [app run] to return
}
@end

int main() {
    NSApplication* app = [NSApplication sharedApplication];
    [app setActivationPolicy: NSApplicationActivationPolicyRegular];
    [app setDelegate: [[AppDelegate alloc] init]];
    [app run];

    // 1 with a DualShock 4 plugged in
    printf("controllers %lu\n", [[GCController controllers] count]);

    // Do stuff here

    return 0;
}

編譯: clang -framework AppKit -framework GameController main.m

我不知道為什么,但我需要在構建輸出目錄中有一個 Info.plist 文件。 沒有它,控制器數組不會被填充。 這是我的整個文件:

<dict>
    <key>CFBundleIdentifier</key>
    <string>your.bundle.id</string>
</dict>

我不確定提供 Info.plist 可能有什么影響,但如果它在那里,我可以正常運行 a.out 可執行文件並獲得我的控制器數組。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM