首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何从macOS命令行工具使用苹果的GameController框架?

如何从macOS命令行工具使用苹果的GameController框架?
EN

Stack Overflow用户
提问于 2019-03-19 00:54:45
回答 2查看 1.5K关注 0票数 6

我正在尝试将以下代码用作macOS命令行工具。重要的是,这不是一个Cocoa应用程序,所以这不是一个选项。

这段代码在同一个项目中使用Cocoa App目标完美地工作,并检测兼容的控制器,但是当作为命令行工具目标运行时,什么也不发生,API显示没有连接的控制器。

很明显,其中一些是人为的。这只是我能归结为的最简单的事情,并在它实际工作时对发生的事情有一些指示。

代码语言:javascript
复制
#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 runloop一起工作,那将是最理想的。

-编辑--

我已经做了一个测试项目来更清楚地说明这个问题。有两个构建目标。Cocoa应用程序构建目标工作并接收控制器连接事件。另一个构建目标,只是一个简单的CLI应用程序,不起作用。它们都使用相同的源文件。它还包括两个代码路径,其中一个是传统的NSApp运行,第二个是上面的手动事件循环。结果是一样的。

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

EN

回答 2

Stack Overflow用户

发布于 2019-08-09 00:00:40

尽管每个线程都会创建一个run循环(对于Cocoa应用程序是NSRunLoop)来处理输入事件,但该循环不会自动启动。下面的代码使其与[application run]调用一起运行。当运行循环处理适当的事件时,将引发通知。我将观察器安装在Application委托中,只是为了确保所有其他系统都在此时完成了初始化。

代码语言:javascript
复制
#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 (负责GameControllerDaemon发布的NSXPCConnection的对象)接收填充controllers数组的CBApplicationDidBecomeActive消息。

无论如何,我尝试激活命令行应用程序,以便它路由这些消息,但这并不起作用;应用程序需要在启动时尽早发送didBecomeActive消息。

我需要的是访问私有_GCGameController对象,但是我不知道谁拥有它,所以我不能直接引用它。

所以在最后,我选择了swizzling方法。下面的代码更改了在终端应用程序中初始化时调用的最后一个方法_GCGameController startIdleWatchTimer,因此它随后会发送CBApplicationDidBecomeActive

我知道这不是一个很好的解决方案,使用各种苹果的内部代码,但也许它可以帮助某人获得更好的东西。将以下代码添加到前面的代码中:

代码语言:javascript
复制
#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
票数 1
EN

Stack Overflow用户

发布于 2019-11-09 02:38:38

我用下面的main.m文件实现了这个功能:

代码语言:javascript
复制
#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文件。如果没有它,控制器数组就不会被填充。这是我的整个文件:

代码语言:javascript
复制
<dict>
    <key>CFBundleIdentifier</key>
    <string>your.bundle.id</string>
</dict>

我不确定提供控制器可能会有什么影响,但如果它存在,我就可以正常运行a.out可执行文件,并得到我的Info.plist数组。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55226373

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档