#objective-c #macos #cocoa #core-foundation #gamecontroller
#objective-c #macos #cocoa #ядро-основа #gamecontroller
Вопрос:
Я пытаюсь заставить следующий код работать в качестве инструмента командной строки macOS. Важно, чтобы это не было приложением Cocoa, поэтому это не вариант.
Этот же код отлично работает в том же проекте с целевым приложением Cocoa и обнаруживает совместимый контроллер, но при запуске в качестве целевого инструмента командной строки ничего не происходит, и 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: %sn", 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 ?язык = objc
Кто-нибудь может пролить свет на то, как я мог бы заставить это работать?
В конечном счете, этот код действительно должен работать внутри пакета Core Foundation, поэтому, если бы он действительно мог работать с Core Foundation runloop, это было бы идеально.
— РЕДАКТИРОВАТЬ —
Я создал тестовый проект, чтобы более наглядно проиллюстрировать проблему. Есть две цели сборки. Цель сборки приложения Cocoa работает и получает событие подключения к контроллеру. Другая цель сборки, простое приложение CLI, не работает. Они оба используют один и тот же исходный файл. Она также включает в себя два пути кода, один из которых является традиционным [запуск NSApp], второй — описанный выше цикл событий вручную. Результат тот же.
https://www.dropbox.com/s/a6fw3nuegq7bg8x/ControllerTest.zip?dl=0
Комментарии:
1. Никогда не использовал эту платформу, но теперь я склонен попробовать. У меня беспроводная консоль Xbox 360, ControllersLite распознает ее. Однако, похоже, я не могу обнаружить это с помощью GameController. У вас есть минимальный пример проекта, который гарантированно будет работать?
2. GameController поддерживает только официальные контроллеры MFi, сертифицированные Apple. Для macOS есть только два, о которых я знаю: SteelSeries Nimbus и Horipad Ultimate.
3. Черт возьми. Это означает, что я не могу здесь помочь. Ладно, пусть хотя бы будет вознаграждение.
Ответ №1:
Хотя каждый поток создает цикл выполнения ( NSRunLoop
для приложения Cocoa) для обработки входных событий, цикл не запускается автоматически. Приведенный ниже код запускает ее с помощью [application run]
вызова. Когда соответствующее событие обрабатывается циклом выполнения, выдается уведомление. Я устанавливаю observer в делегате приложения, просто чтобы убедиться, что все другие системы завершили инициализацию на этом этапе.
#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: %sn", 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
уведомления, и это приводит к тому, что private _GCControllerManager
(объект, который обрабатывает NSXPCConnection
сообщения, отправленные GameControllerDaemon
) получает CBApplicationDidBecomeActive
сообщение, которое заполняет controllers
массив.
В любом случае, я попытался активировать приложение командной строки, чтобы оно перенаправляло эти сообщения, но это не сработало; приложению необходимо отправить didBecomeActive
сообщение раньше во время запуска.
Затем я попытался создать свою собственную, _GCGameController
и отправить CBApplicationDidBecomeActive
вручную; это вроде сработало, за исключением того, что приложение заканчивается с двумя из этих контроллеров, и соединения дублируются.
Что мне было нужно, так это доступ к закрытому _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(amp;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. Добро пожаловать в SO. Пожалуйста, не могли бы вы предоставить объяснение вместе со своим кодом, чтобы помочь другим.
2. К сожалению, это не работает, если не создано как пакет приложений Cocoa. Я отредактировал свой первоначальный вопрос, включив в него тестовый проект, чтобы показать проблему.
3. Я скачал ваш проект, и он работает у меня. Но я на всякий случай попробовал еще раз с нуля: я только что создал новый проект инструмента командной строки в Xcode (версия 10.3 10G8), заменил код в main.m на приведенный выше, запустил его, подключил свой контроллер PS4 через Bluetooth, и он выдает «ПРИКРЕПЛЕННЫЙ» на консоль (ваш проект также печатается ОТДЕЛЬНО, как и ожидалось). Я использую macOS 10.14.6 (18G87) на MacBook Air (13-дюймовый, середина 2012 года).
4. Это так странно. Я тоже пробовал с нуля, и у меня все еще не работает. Те же версии Xcode macOS на Mac mini (2018) с контроллером PS4 и Horipad Ultimate. Также это никогда раньше не работало у меня на MacBook Pro (я полагаю, в конце 2014 года). Опять же, все отлично работает со сборкой приложения Cocoa.
5. Я добился небольшого прогресса. Я заставил вашу версию работать … вроде как. Он не обнаруживает никаких ранее подключенных контроллеров при запуске приложения в качестве инструмента командной строки. [[Количество контроллеров GCController] равно нулю, и никаких уведомлений не происходит. Если я отключу и повторно подключу контроллер, он отобразится. В приложении Cocoa все контроллеры обнаруживаются при запуске.
Ответ №2:
У меня это работает со следующим файлом 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 %lun", [[GCController controllers] count]);
// Do stuff here
return 0;
}
Скомпилирован с: clang -framework AppKit -framework GameController main.m
Я понятия не имею, зачем, но мне нужен файл Info.plist в каталоге вывода сборки. Без этого массив controllers не заполняется. Это весь мой файл:
<dict>
<key>CFBundleIdentifier</key>
<string>your.bundle.id</string>
</dict>
Я не уверен, какие последствия может иметь предоставление Info.plist, но если он есть, я могу запустить исполняемый файл a.out как обычно, и я получу свой массив контроллеров.