Replace the SDL-derived controller support with my own JoyKit framework. Adds rumble support, LED support, better manual and automatic configurations, analog speed controls.
This commit is contained in:
parent
7d6cdf3819
commit
0ece21bca7
@ -2,6 +2,7 @@
|
||||
#include "GBButtons.h"
|
||||
#include <Core/gb.h>
|
||||
#import <Carbon/Carbon.h>
|
||||
#import <JoyKit/JoyKit.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
{
|
||||
@ -41,6 +42,11 @@
|
||||
@"GBCGBModel": @(GB_MODEL_CGB_E),
|
||||
@"GBSGBModel": @(GB_MODEL_SGB2),
|
||||
}];
|
||||
|
||||
[JOYController startOnRunLoop:[NSRunLoop currentRunLoop] withOptions:@{
|
||||
JOYAxes2DEmulateButtonsKey: @YES,
|
||||
JOYHatsEmulateButtonsKey: @YES,
|
||||
}];
|
||||
}
|
||||
|
||||
- (IBAction)toggleDeveloperMode:(id)sender
|
||||
|
@ -74,6 +74,7 @@ enum model {
|
||||
topMargin:(unsigned) topMargin bottomMargin: (unsigned) bottomMargin
|
||||
exposure:(unsigned) exposure;
|
||||
- (void) gotNewSample:(GB_sample_t *)sample;
|
||||
- (void) rumbleChanged:(double)amp;
|
||||
@end
|
||||
|
||||
static void vblank(GB_gameboy_t *gb)
|
||||
@ -131,6 +132,12 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
|
||||
[self gotNewSample:sample];
|
||||
}
|
||||
|
||||
static void rumbleCallback(GB_gameboy_t *gb, double amp)
|
||||
{
|
||||
Document *self = (__bridge Document *)GB_get_user_data(gb);
|
||||
[self rumbleChanged:amp];
|
||||
}
|
||||
|
||||
@implementation Document
|
||||
{
|
||||
GB_gameboy_t gb;
|
||||
@ -199,6 +206,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
|
||||
GB_set_highpass_filter_mode(&gb, (GB_highpass_mode_t) [[NSUserDefaults standardUserDefaults] integerForKey:@"GBHighpassFilter"]);
|
||||
GB_set_rewind_length(&gb, [[NSUserDefaults standardUserDefaults] integerForKey:@"GBRewindLength"]);
|
||||
GB_apu_set_sample_callback(&gb, audioCallback);
|
||||
GB_set_rumble_callback(&gb, rumbleCallback);
|
||||
}
|
||||
|
||||
- (void) vblank
|
||||
@ -244,6 +252,11 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
|
||||
[audioLock unlock];
|
||||
}
|
||||
|
||||
- (void)rumbleChanged:(double)amp
|
||||
{
|
||||
[_view setRumble:amp];
|
||||
}
|
||||
|
||||
- (void) run
|
||||
{
|
||||
running = true;
|
||||
@ -295,6 +308,7 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
|
||||
self.audioClient = nil;
|
||||
self.view.mouseHidingEnabled = NO;
|
||||
GB_save_battery(&gb, [[[self.fileName stringByDeletingPathExtension] stringByAppendingPathExtension:@"sav"] UTF8String]);
|
||||
[_view setRumble:false];
|
||||
stopping = false;
|
||||
}
|
||||
|
||||
@ -1563,5 +1577,4 @@ static void audioCallback(GB_gameboy_t *gb, GB_sample_t *sample)
|
||||
self.consoleWindow.title = [NSString stringWithFormat:@"Debug Console – %@", [[fileURL path] lastPathComponent]];
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -19,6 +19,11 @@ typedef enum : NSUInteger {
|
||||
|
||||
extern NSString const *GBButtonNames[GBButtonCount];
|
||||
|
||||
static inline NSString *n2s(uint64_t number)
|
||||
{
|
||||
return [NSString stringWithFormat:@"%llx", number];
|
||||
}
|
||||
|
||||
static inline NSString *button_to_preference_name(GBButton button, unsigned player)
|
||||
{
|
||||
if (player) {
|
||||
|
@ -1,9 +0,0 @@
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
@protocol GBJoystickListener <NSObject>
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state;
|
||||
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value;
|
||||
- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) value;
|
||||
|
||||
@end
|
@ -1,9 +1,10 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "GBJoystickListener.h"
|
||||
#import <JoyKit/JoyKit.h>
|
||||
|
||||
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, GBJoystickListener>
|
||||
@interface GBPreferencesWindow : NSWindow <NSTableViewDelegate, NSTableViewDataSource, JOYListener>
|
||||
@property IBOutlet NSTableView *controlsTableView;
|
||||
@property IBOutlet NSPopUpButton *graphicsFilterPopupButton;
|
||||
@property (strong) IBOutlet NSButton *analogControlsCheckbox;
|
||||
@property (strong) IBOutlet NSButton *aspectRatioCheckbox;
|
||||
@property (strong) IBOutlet NSPopUpButton *highpassFilterPopupButton;
|
||||
@property (strong) IBOutlet NSPopUpButton *colorCorrectionPopupButton;
|
||||
|
@ -9,13 +9,14 @@
|
||||
NSInteger button_being_modified;
|
||||
signed joystick_configuration_state;
|
||||
NSString *joystick_being_configured;
|
||||
signed last_axis;
|
||||
bool joypad_wait;
|
||||
|
||||
NSPopUpButton *_graphicsFilterPopupButton;
|
||||
NSPopUpButton *_highpassFilterPopupButton;
|
||||
NSPopUpButton *_colorCorrectionPopupButton;
|
||||
NSPopUpButton *_rewindPopupButton;
|
||||
NSButton *_aspectRatioCheckbox;
|
||||
NSButton *_analogControlsCheckbox;
|
||||
NSEventModifierFlags previousModifiers;
|
||||
|
||||
NSPopUpButton *_dmgPopupButton, *_sgbPopupButton, *_cgbPopupButton;
|
||||
@ -51,7 +52,7 @@
|
||||
joystick_configuration_state = -1;
|
||||
[self.configureJoypadButton setEnabled:YES];
|
||||
[self.skipButton setEnabled:NO];
|
||||
[self.configureJoypadButton setTitle:@"Configure Joypad"];
|
||||
[self.configureJoypadButton setTitle:@"Configure Controller"];
|
||||
[super close];
|
||||
}
|
||||
|
||||
@ -184,6 +185,12 @@
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"GBHighpassFilterChanged" object:nil];
|
||||
}
|
||||
|
||||
- (IBAction)changeAnalogControls:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] == NSOnState
|
||||
forKey:@"GBAnalogControls"];
|
||||
}
|
||||
|
||||
- (IBAction)changeAspectRatio:(id)sender
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setBool: [(NSButton *)sender state] != NSOnState
|
||||
@ -212,7 +219,6 @@
|
||||
[self.skipButton setEnabled:YES];
|
||||
joystick_being_configured = nil;
|
||||
[self advanceConfigurationStateMachine];
|
||||
last_axis = -1;
|
||||
}
|
||||
|
||||
- (IBAction) skipButton:(id)sender
|
||||
@ -223,11 +229,11 @@
|
||||
- (void) advanceConfigurationStateMachine
|
||||
{
|
||||
joystick_configuration_state++;
|
||||
if (joystick_configuration_state < GBButtonCount) {
|
||||
[self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]];
|
||||
if (joystick_configuration_state == GBUnderclock) {
|
||||
[self.configureJoypadButton setTitle:@"Press Button for Slo-Mo"]; // Full name is too long :<
|
||||
}
|
||||
else if (joystick_configuration_state == GBButtonCount) {
|
||||
[self.configureJoypadButton setTitle:@"Move the Analog Stick"];
|
||||
else if (joystick_configuration_state < GBButtonCount) {
|
||||
[self.configureJoypadButton setTitle:[NSString stringWithFormat:@"Press Button for %@", GBButtonNames[joystick_configuration_state]]];
|
||||
}
|
||||
else {
|
||||
joystick_configuration_state = -1;
|
||||
@ -237,112 +243,97 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state
|
||||
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
|
||||
{
|
||||
if (!state) return;
|
||||
/* Debounce */
|
||||
if (joypad_wait) return;
|
||||
joypad_wait = true;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
joypad_wait = false;
|
||||
});
|
||||
|
||||
NSLog(@"%@", button);
|
||||
|
||||
if (!button.isPressed) return;
|
||||
if (joystick_configuration_state == -1) return;
|
||||
if (joystick_configuration_state == GBButtonCount) return;
|
||||
if (!joystick_being_configured) {
|
||||
joystick_being_configured = joystick_name;
|
||||
joystick_being_configured = controller.uniqueID;
|
||||
}
|
||||
else if (![joystick_being_configured isEqualToString:joystick_name]) {
|
||||
else if (![joystick_being_configured isEqualToString:controller.uniqueID]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy];
|
||||
NSMutableDictionary *instance_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"] mutableCopy];
|
||||
|
||||
if (!all_mappings) {
|
||||
all_mappings = [[NSMutableDictionary alloc] init];
|
||||
NSMutableDictionary *name_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"] mutableCopy];
|
||||
|
||||
|
||||
if (!instance_mappings) {
|
||||
instance_mappings = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy];
|
||||
if (!name_mappings) {
|
||||
name_mappings = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
if (!mapping) {
|
||||
NSMutableDictionary *mapping = nil;
|
||||
if (joystick_configuration_state != 0) {
|
||||
mapping = [instance_mappings[controller.uniqueID] mutableCopy];
|
||||
}
|
||||
else {
|
||||
mapping = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
mapping[GBButtonNames[joystick_configuration_state]] = @(button);
|
||||
|
||||
all_mappings[joystick_name] = mapping;
|
||||
[[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"];
|
||||
[self refreshJoypadMenu:nil];
|
||||
static const unsigned gb_to_joykit[] = {
|
||||
[GBRight]=JOYButtonUsageDPadRight,
|
||||
[GBLeft]=JOYButtonUsageDPadLeft,
|
||||
[GBUp]=JOYButtonUsageDPadUp,
|
||||
[GBDown]=JOYButtonUsageDPadDown,
|
||||
[GBA]=JOYButtonUsageA,
|
||||
[GBB]=JOYButtonUsageB,
|
||||
[GBSelect]=JOYButtonUsageSelect,
|
||||
[GBStart]=JOYButtonUsageStart,
|
||||
[GBTurbo]=JOYButtonUsageL1,
|
||||
[GBRewind]=JOYButtonUsageL2,
|
||||
[GBUnderclock]=JOYButtonUsageR1,
|
||||
};
|
||||
|
||||
if (joystick_configuration_state == GBUnderclock) {
|
||||
for (JOYAxis *axis in controller.axes) {
|
||||
if (axis.value > 0.5) {
|
||||
mapping[@"AnalogUnderclock"] = @(axis.uniqueID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (joystick_configuration_state == GBTurbo) {
|
||||
for (JOYAxis *axis in controller.axes) {
|
||||
if (axis.value > 0.5) {
|
||||
mapping[@"AnalogTurbo"] = @(axis.uniqueID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mapping[n2s(button.uniqueID)] = @(gb_to_joykit[joystick_configuration_state]);
|
||||
|
||||
instance_mappings[controller.uniqueID] = mapping;
|
||||
name_mappings[controller.deviceName] = mapping;
|
||||
[[NSUserDefaults standardUserDefaults] setObject:instance_mappings forKey:@"JoyKitInstanceMapping"];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:name_mappings forKey:@"JoyKitNameMapping"];
|
||||
[self advanceConfigurationStateMachine];
|
||||
}
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value
|
||||
- (NSButton *)analogControlsCheckbox
|
||||
{
|
||||
if (abs(value) < 0x4000) return;
|
||||
if (joystick_configuration_state != GBButtonCount) return;
|
||||
if (!joystick_being_configured) {
|
||||
joystick_being_configured = joystick_name;
|
||||
}
|
||||
else if (![joystick_being_configured isEqualToString:joystick_name]) {
|
||||
return;
|
||||
return _analogControlsCheckbox;
|
||||
}
|
||||
|
||||
if (last_axis == -1) {
|
||||
last_axis = axis;
|
||||
return;
|
||||
}
|
||||
|
||||
if (axis == last_axis) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy];
|
||||
|
||||
if (!all_mappings) {
|
||||
all_mappings = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy];
|
||||
|
||||
if (!mapping) {
|
||||
mapping = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
mapping[@"XAxis"] = @(MIN(axis, last_axis));
|
||||
mapping[@"YAxis"] = @(MAX(axis, last_axis));
|
||||
|
||||
all_mappings[joystick_name] = mapping;
|
||||
[[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"];
|
||||
[self advanceConfigurationStateMachine];
|
||||
}
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) state
|
||||
- (void)setAnalogControlsCheckbox:(NSButton *)analogControlsCheckbox
|
||||
{
|
||||
/* Hats are always mapped to the D-pad, ignore them on non-Dpad keys and skip the D-pad configuration if used*/
|
||||
if (!state) return;
|
||||
if (joystick_configuration_state == -1) return;
|
||||
if (joystick_configuration_state > GBDown) return;
|
||||
if (!joystick_being_configured) {
|
||||
joystick_being_configured = joystick_name;
|
||||
}
|
||||
else if (![joystick_being_configured isEqualToString:joystick_name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *all_mappings = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] mutableCopy];
|
||||
|
||||
if (!all_mappings) {
|
||||
all_mappings = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
NSMutableDictionary *mapping = [[all_mappings objectForKey:joystick_name] mutableCopy];
|
||||
|
||||
if (!mapping) {
|
||||
mapping = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
for (joystick_configuration_state = 0;; joystick_configuration_state++) {
|
||||
[mapping removeObjectForKey:GBButtonNames[joystick_configuration_state]];
|
||||
if (joystick_configuration_state == GBDown) break;
|
||||
}
|
||||
|
||||
all_mappings[joystick_name] = mapping;
|
||||
[[NSUserDefaults standardUserDefaults] setObject:all_mappings forKey:@"GBJoypadMappings"];
|
||||
[self refreshJoypadMenu:nil];
|
||||
[self advanceConfigurationStateMachine];
|
||||
_analogControlsCheckbox = analogControlsCheckbox;
|
||||
[_analogControlsCheckbox setState: [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]];
|
||||
}
|
||||
|
||||
- (NSButton *)aspectRatioCheckbox
|
||||
@ -361,10 +352,13 @@
|
||||
[super awakeFromNib];
|
||||
[self updateBootROMFolderButton];
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserver:self.controlsTableView selector:@selector(reloadData) name:(NSString*)kTISNotifySelectedKeyboardInputSourceChanged object:nil];
|
||||
[JOYController registerListener:self];
|
||||
joystick_configuration_state = -1;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[JOYController unregisterListener:self];
|
||||
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self.controlsTableView];
|
||||
}
|
||||
|
||||
@ -483,21 +477,47 @@
|
||||
return _preferredJoypadButton;
|
||||
}
|
||||
|
||||
- (void)controllerConnected:(JOYController *)controller
|
||||
{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self refreshJoypadMenu:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)controllerDisconnected:(JOYController *)controller
|
||||
{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self refreshJoypadMenu:nil];
|
||||
});
|
||||
}
|
||||
|
||||
- (IBAction)refreshJoypadMenu:(id)sender
|
||||
{
|
||||
NSArray *joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"] allKeys];
|
||||
for (NSString *joypad in joypads) {
|
||||
if ([self.preferredJoypadButton indexOfItemWithTitle:joypad] == -1) {
|
||||
[self.preferredJoypadButton addItemWithTitle:joypad];
|
||||
bool preferred_is_connected = false;
|
||||
NSString *player_string = n2s(self.playerListButton.selectedTag);
|
||||
NSString *selected_controller = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"][player_string];
|
||||
|
||||
[self.preferredJoypadButton removeAllItems];
|
||||
[self.preferredJoypadButton addItemWithTitle:@"None"];
|
||||
for (JOYController *controller in [JOYController allControllers]) {
|
||||
[self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"%@ (%@)", controller.deviceName, controller.uniqueID]];
|
||||
|
||||
self.preferredJoypadButton.lastItem.identifier = controller.uniqueID;
|
||||
|
||||
if ([controller.uniqueID isEqualToString:selected_controller]) {
|
||||
preferred_is_connected = true;
|
||||
[self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem];
|
||||
}
|
||||
}
|
||||
|
||||
NSString *player_string = [NSString stringWithFormat: @"%ld", (long)self.playerListButton.selectedTag];
|
||||
NSString *selected_joypad = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"][player_string];
|
||||
if (selected_joypad && [self.preferredJoypadButton indexOfItemWithTitle:selected_joypad] != -1) {
|
||||
[self.preferredJoypadButton selectItemWithTitle:selected_joypad];
|
||||
if (!preferred_is_connected && selected_controller) {
|
||||
[self.preferredJoypadButton addItemWithTitle:[NSString stringWithFormat:@"Unavailable Controller (%@)", selected_controller]];
|
||||
self.preferredJoypadButton.lastItem.identifier = selected_controller;
|
||||
[self.preferredJoypadButton selectItem:self.preferredJoypadButton.lastItem];
|
||||
}
|
||||
else {
|
||||
|
||||
|
||||
if (!selected_controller) {
|
||||
[self.preferredJoypadButton selectItemWithTitle:@"None"];
|
||||
}
|
||||
[self.controlsTableView reloadData];
|
||||
@ -505,18 +525,18 @@
|
||||
|
||||
- (IBAction)changeDefaultJoypad:(id)sender
|
||||
{
|
||||
NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"] mutableCopy];
|
||||
NSMutableDictionary *default_joypads = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"] mutableCopy];
|
||||
if (!default_joypads) {
|
||||
default_joypads = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
NSString *player_string = [NSString stringWithFormat: @"%ld", self.playerListButton.selectedTag];
|
||||
NSString *player_string = n2s(self.playerListButton.selectedTag);
|
||||
if ([[sender titleOfSelectedItem] isEqualToString:@"None"]) {
|
||||
[default_joypads removeObjectForKey:player_string];
|
||||
}
|
||||
else {
|
||||
default_joypads[player_string] = [sender titleOfSelectedItem];
|
||||
default_joypads[player_string] = [[sender selectedItem] identifier];
|
||||
}
|
||||
[[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"GBDefaultJoypads"];
|
||||
[[NSUserDefaults standardUserDefaults] setObject:default_joypads forKey:@"JoyKitDefaultControllers"];
|
||||
}
|
||||
@end
|
||||
|
@ -1,8 +1,8 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <Core/gb.h>
|
||||
#import "GBJoystickListener.h"
|
||||
#import <JoyKit/JoyKit.h>
|
||||
|
||||
@interface GBView<GBJoystickListener> : NSView
|
||||
@interface GBView : NSView<JOYListener>
|
||||
- (void) flip;
|
||||
- (uint32_t *) pixels;
|
||||
@property GB_gameboy_t *gb;
|
||||
@ -14,4 +14,5 @@
|
||||
- (uint32_t *)currentBuffer;
|
||||
- (uint32_t *)previousBuffer;
|
||||
- (void)screenSizeChanged;
|
||||
- (void)setRumble: (bool)on;
|
||||
@end
|
||||
|
192
Cocoa/GBView.m
192
Cocoa/GBView.m
@ -1,4 +1,4 @@
|
||||
#import <Carbon/Carbon.h>
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#import "GBView.h"
|
||||
#import "GBViewGL.h"
|
||||
#import "GBViewMetal.h"
|
||||
@ -18,7 +18,10 @@
|
||||
bool axisActive[2];
|
||||
bool underclockKeyDown;
|
||||
double clockMultiplier;
|
||||
double analogClockMultiplier;
|
||||
bool analogClockMultiplierValid;
|
||||
NSEventModifierFlags previousModifiers;
|
||||
JOYController *lastController;
|
||||
}
|
||||
|
||||
+ (instancetype)alloc
|
||||
@ -55,6 +58,7 @@
|
||||
[self createInternalView];
|
||||
[self addSubview:self.internalView];
|
||||
self.internalView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
[JOYController registerListener:self];
|
||||
}
|
||||
|
||||
- (void)screenSizeChanged
|
||||
@ -100,6 +104,8 @@
|
||||
[NSCursor unhide];
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
[lastController setRumbleAmplitude:0];
|
||||
[JOYController unregisterListener:self];
|
||||
}
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder
|
||||
{
|
||||
@ -147,6 +153,13 @@
|
||||
|
||||
- (void) flip
|
||||
{
|
||||
if (analogClockMultiplierValid && [[NSUserDefaults standardUserDefaults] boolForKey:@"GBAnalogControls"]) {
|
||||
GB_set_clock_multiplier(_gb, analogClockMultiplier);
|
||||
if (analogClockMultiplier == 1.0) {
|
||||
analogClockMultiplierValid = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (underclockKeyDown && clockMultiplier > 0.5) {
|
||||
clockMultiplier -= 1.0/16;
|
||||
GB_set_clock_multiplier(_gb, clockMultiplier);
|
||||
@ -155,6 +168,7 @@
|
||||
clockMultiplier += 1.0/16;
|
||||
GB_set_clock_multiplier(_gb, clockMultiplier);
|
||||
}
|
||||
}
|
||||
current_buffer = (current_buffer + 1) % self.numberOfBuffers;
|
||||
}
|
||||
|
||||
@ -180,6 +194,7 @@
|
||||
switch (button) {
|
||||
case GBTurbo:
|
||||
GB_set_turbo_mode(_gb, true, self.isRewinding);
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
case GBRewind:
|
||||
@ -189,6 +204,7 @@
|
||||
|
||||
case GBUnderclock:
|
||||
underclockKeyDown = true;
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -221,6 +237,7 @@
|
||||
switch (button) {
|
||||
case GBTurbo:
|
||||
GB_set_turbo_mode(_gb, false, false);
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
case GBRewind:
|
||||
@ -229,6 +246,7 @@
|
||||
|
||||
case GBUnderclock:
|
||||
underclockKeyDown = false;
|
||||
analogClockMultiplierValid = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -243,125 +261,99 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name button: (unsigned)button changedState: (bool) state
|
||||
- (void)setRumble:(bool)on
|
||||
{
|
||||
[lastController setRumbleAmplitude:(double)on];
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller movedAxis:(JOYAxis *)axis
|
||||
{
|
||||
if (![self.window isMainWindow]) return;
|
||||
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
|
||||
if (!mapping) {
|
||||
mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
|
||||
}
|
||||
|
||||
if ((axis.usage == JOYAxisUsageR1 && !mapping) ||
|
||||
axis.uniqueID == [mapping[@"AnalogUnderclock"] unsignedLongValue]){
|
||||
analogClockMultiplier = MIN(MAX(1 - axis.value + 0.2, 1.0 / 3), 1.0);
|
||||
analogClockMultiplierValid = true;
|
||||
}
|
||||
|
||||
else if ((axis.usage == JOYAxisUsageL1 && !mapping) ||
|
||||
axis.uniqueID == [mapping[@"AnalogTurbo"] unsignedLongValue]){
|
||||
analogClockMultiplier = MIN(MAX(axis.value * 3 + 0.8, 1.0), 3.0);
|
||||
analogClockMultiplierValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)controller:(JOYController *)controller buttonChangedState:(JOYButton *)button
|
||||
{
|
||||
if (![self.window isMainWindow]) return;
|
||||
if (controller != lastController) {
|
||||
[lastController setRumbleAmplitude:0];
|
||||
lastController = controller;
|
||||
}
|
||||
|
||||
|
||||
unsigned player_count = GB_get_player_count(_gb);
|
||||
|
||||
UpdateSystemActivity(UsrActivity);
|
||||
IOPMAssertionID assertionID;
|
||||
IOPMAssertionDeclareUserActivity(CFSTR(""), kIOPMUserActiveLocal, &assertionID);
|
||||
|
||||
for (unsigned player = 0; player < player_count; player++) {
|
||||
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"]
|
||||
objectForKey:[NSString stringWithFormat:@"%u", player]];
|
||||
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitDefaultControllers"]
|
||||
objectForKey:n2s(player)];
|
||||
if (player_count != 1 && // Single player, accpet inputs from all joypads
|
||||
!(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads
|
||||
![preferred_joypad isEqualToString:joystick_name]) {
|
||||
![preferred_joypad isEqualToString:controller.uniqueID]) {
|
||||
continue;
|
||||
}
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name];
|
||||
[controller setPlayerLEDs:1 << player];
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitInstanceMapping"][controller.uniqueID];
|
||||
if (!mapping) {
|
||||
mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"JoyKitNameMapping"][controller.deviceName];
|
||||
}
|
||||
|
||||
for (GBButton i = 0; i < GBButtonCount; i++) {
|
||||
NSNumber *mapped_button = [mapping objectForKey:GBButtonNames[i]];
|
||||
if (mapped_button && [mapped_button integerValue] == button) {
|
||||
switch (i) {
|
||||
case GBTurbo:
|
||||
GB_set_turbo_mode(_gb, state, state && self.isRewinding);
|
||||
break;
|
||||
JOYButtonUsage usage = ((JOYButtonUsage)[mapping[n2s(button.uniqueID)] unsignedIntValue]) ?: button.usage;
|
||||
if (!mapping && usage >= JOYButtonUsageGeneric0) {
|
||||
usage = (const JOYButtonUsage[]){JOYButtonUsageY, JOYButtonUsageA, JOYButtonUsageB, JOYButtonUsageX}[(usage - JOYButtonUsageGeneric0) & 3];
|
||||
}
|
||||
|
||||
case GBRewind:
|
||||
self.isRewinding = state;
|
||||
if (state) {
|
||||
switch (usage) {
|
||||
|
||||
case JOYButtonUsageNone: break;
|
||||
case JOYButtonUsageA: GB_set_key_state_for_player(_gb, GB_KEY_A, player, button.isPressed); break;
|
||||
case JOYButtonUsageB: GB_set_key_state_for_player(_gb, GB_KEY_B, player, button.isPressed); break;
|
||||
case JOYButtonUsageC: break;
|
||||
case JOYButtonUsageStart:
|
||||
case JOYButtonUsageX: GB_set_key_state_for_player(_gb, GB_KEY_START, player, button.isPressed); break;
|
||||
case JOYButtonUsageSelect:
|
||||
case JOYButtonUsageY: GB_set_key_state_for_player(_gb, GB_KEY_SELECT, player, button.isPressed); break;
|
||||
case JOYButtonUsageR2:
|
||||
case JOYButtonUsageL2:
|
||||
case JOYButtonUsageZ: {
|
||||
self.isRewinding = button.isPressed;
|
||||
if (button.isPressed) {
|
||||
GB_set_turbo_mode(_gb, false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GBUnderclock:
|
||||
underclockKeyDown = state;
|
||||
break;
|
||||
case JOYButtonUsageL1: GB_set_turbo_mode(_gb, button.isPressed, button.isPressed && self.isRewinding); break;
|
||||
|
||||
case JOYButtonUsageR1: underclockKeyDown = button.isPressed; break;
|
||||
case JOYButtonUsageDPadLeft: GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, button.isPressed); break;
|
||||
case JOYButtonUsageDPadRight: GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, button.isPressed); break;
|
||||
case JOYButtonUsageDPadUp: GB_set_key_state_for_player(_gb, GB_KEY_UP, player, button.isPressed); break;
|
||||
case JOYButtonUsageDPadDown: GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, button.isPressed); break;
|
||||
|
||||
default:
|
||||
GB_set_key_state_for_player(_gb, (GB_key_t)i, player, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name axis: (unsigned)axis movedTo: (signed) value
|
||||
{
|
||||
unsigned player_count = GB_get_player_count(_gb);
|
||||
|
||||
UpdateSystemActivity(UsrActivity);
|
||||
for (unsigned player = 0; player < player_count; player++) {
|
||||
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"]
|
||||
objectForKey:[NSString stringWithFormat:@"%u", player]];
|
||||
if (player_count != 1 && // Single player, accpet inputs from all joypads
|
||||
!(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads
|
||||
![preferred_joypad isEqualToString:joystick_name]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSDictionary *mapping = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBJoypadMappings"][joystick_name];
|
||||
NSNumber *x_axis = [mapping objectForKey:@"XAxis"];
|
||||
NSNumber *y_axis = [mapping objectForKey:@"YAxis"];
|
||||
|
||||
if (axis == [x_axis integerValue]) {
|
||||
if (value > JOYSTICK_HIGH) {
|
||||
axisActive[0] = true;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, true);
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, false);
|
||||
}
|
||||
else if (value < -JOYSTICK_HIGH) {
|
||||
axisActive[0] = true;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, false);
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, true);
|
||||
}
|
||||
else if (axisActive[0] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) {
|
||||
axisActive[0] = false;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, false);
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, false);
|
||||
}
|
||||
}
|
||||
else if (axis == [y_axis integerValue]) {
|
||||
if (value > JOYSTICK_HIGH) {
|
||||
axisActive[1] = true;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, true);
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, false);
|
||||
}
|
||||
else if (value < -JOYSTICK_HIGH) {
|
||||
axisActive[1] = true;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, false);
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, true);
|
||||
}
|
||||
else if (axisActive[1] && value < JOYSTICK_LOW && value > -JOYSTICK_LOW) {
|
||||
axisActive[1] = false;
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, false);
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) joystick:(NSString *)joystick_name hat: (unsigned)hat changedState: (int8_t) state
|
||||
{
|
||||
unsigned player_count = GB_get_player_count(_gb);
|
||||
|
||||
UpdateSystemActivity(UsrActivity);
|
||||
for (unsigned player = 0; player < player_count; player++) {
|
||||
NSString *preferred_joypad = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"GBDefaultJoypads"]
|
||||
objectForKey:[NSString stringWithFormat:@"%u", player]];
|
||||
if (player_count != 1 && // Single player, accpet inputs from all joypads
|
||||
!(player == 0 && !preferred_joypad) && // Multiplayer, but player 1 has no joypad configured, so it takes inputs from all joypads
|
||||
![preferred_joypad isEqualToString:joystick_name]) {
|
||||
continue;
|
||||
}
|
||||
assert(state + 1 < 9);
|
||||
/* - N NE E SE S SW W NW */
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_UP, player, (bool []){0, 1, 1, 0, 0, 0, 0, 0, 1}[state + 1]);
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_RIGHT, player, (bool []){0, 0, 1, 1, 1, 0, 0, 0, 0}[state + 1]);
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_DOWN, player, (bool []){0, 0, 0, 0, 1, 1, 1, 0, 0}[state + 1]);
|
||||
GB_set_key_state_for_player(_gb, GB_KEY_LEFT, player, (bool []){0, 0, 0, 0, 0, 0, 1, 1, 1}[state + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@ -17,7 +17,7 @@
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="GBPreferencesWindow">
|
||||
<window title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="GBPreferencesWindow">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<windowCollectionBehavior key="collectionBehavior" fullScreenAuxiliary="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
@ -58,6 +58,7 @@
|
||||
</defaultToolbarItems>
|
||||
</toolbar>
|
||||
<connections>
|
||||
<outlet property="analogControlsCheckbox" destination="RuW-Db-dzW" id="FRE-hI-mnU"/>
|
||||
<outlet property="aspectRatioCheckbox" destination="Vfj-tg-7OP" id="Yw0-xS-DBr"/>
|
||||
<outlet property="bootROMsButton" destination="T3Y-Ln-Onl" id="tdL-Yv-E2K"/>
|
||||
<outlet property="bootROMsFolderItem" destination="Dzv-Gc-zoL" id="yhV-ZI-avD"/>
|
||||
@ -369,22 +370,11 @@
|
||||
<point key="canvasLocation" x="-176" y="890"/>
|
||||
</customView>
|
||||
<customView id="8TU-6J-NCg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="376"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="292" height="401"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Qa7-Z7-yfO">
|
||||
<rect key="frame" x="20" y="9" width="188" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Configure a Joypad" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Utu-t4-cLx">
|
||||
<rect key="frame" x="10" y="339" width="122" height="17"/>
|
||||
<rect key="frame" x="10" y="364" width="122" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Control settings for" id="YqW-Ds-VIC">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -393,7 +383,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<scrollView focusRingType="none" fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" verticalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PBp-dj-EIa">
|
||||
<rect key="frame" x="32" y="117" width="240" height="211"/>
|
||||
<rect key="frame" x="32" y="142" width="240" height="211"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<clipView key="contentView" focusRingType="none" ambiguous="YES" drawsBackground="NO" id="AMs-PO-nid">
|
||||
<rect key="frame" x="1" y="1" width="238" height="209"/>
|
||||
@ -441,28 +431,28 @@
|
||||
</subviews>
|
||||
<nil key="backgroundColor"/>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="31h-at-Znm">
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="31h-at-Znm">
|
||||
<rect key="frame" x="-100" y="-100" width="210" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="JkP-U1-jdy">
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="JkP-U1-jdy">
|
||||
<rect key="frame" x="-100" y="-100" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fcF-wc-KwM">
|
||||
<rect key="frame" x="30" y="92" width="187" height="17"/>
|
||||
<rect key="frame" x="30" y="117" width="203" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Joypad for multiplayer games:" id="AJA-9b-VKI">
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Controller for multiplayer games:" id="AJA-9b-VKI">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Az-0R-oNw">
|
||||
<rect key="frame" x="42" y="61" width="208" height="26"/>
|
||||
<rect key="frame" x="42" y="86" width="208" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="hy8-cr-RrE" id="uEC-vN-8Jq">
|
||||
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingMiddle" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="hy8-cr-RrE" id="uEC-vN-8Jq">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="vzY-GQ-t9J">
|
||||
@ -476,11 +466,11 @@
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="VEc-Ed-Z6f">
|
||||
<rect key="frame" x="12" y="48" width="268" height="5"/>
|
||||
<rect key="frame" x="12" y="73" width="268" height="5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</box>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ReM-uo-H0r">
|
||||
<rect key="frame" x="215" y="339" width="8" height="17"/>
|
||||
<rect key="frame" x="215" y="364" width="8" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title=":" id="VhO-3T-glt">
|
||||
<font key="font" metaFont="system"/>
|
||||
@ -489,7 +479,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="gWx-7h-0xq">
|
||||
<rect key="frame" x="131" y="332" width="87" height="26"/>
|
||||
<rect key="frame" x="131" y="357" width="87" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Player 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="TO3-R7-9HN" id="pbt-Lr-bU1">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
@ -507,10 +497,21 @@
|
||||
<action selector="refreshJoypadMenu:" target="QvC-M9-y7g" id="5hY-tg-9VE"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb">
|
||||
<rect key="frame" x="212" y="9" width="60" height="32"/>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RuW-Db-dzW">
|
||||
<rect key="frame" x="18" y="44" width="264" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Skip" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw">
|
||||
<buttonCell key="cell" type="check" title="Analog turbo and slow-motion controls" bezelStyle="regularSquare" imagePosition="left" lineBreakMode="charWrapping" inset="2" id="Mvp-oc-N3t">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="changeAnalogControls:" target="QvC-M9-y7g" id="1xR-gY-WKo"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d2I-jU-sLb">
|
||||
<rect key="frame" x="206" y="13" width="72" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Clear" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="sug-xy-tbw">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
@ -518,8 +519,19 @@
|
||||
<action selector="skipButton:" target="QvC-M9-y7g" id="aw8-sw-yJw"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Qa7-Z7-yfO">
|
||||
<rect key="frame" x="18" y="13" width="188" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Configure a Controller" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="GdK-tQ-Wim">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="configureJoypad:" target="QvC-M9-y7g" id="IfY-Kc-PKU"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="-159" y="1116"/>
|
||||
<point key="canvasLocation" x="-159" y="1128.5"/>
|
||||
</customView>
|
||||
</objects>
|
||||
<resources>
|
||||
|
748
Cocoa/joypad.m
748
Cocoa/joypad.m
@ -1,748 +0,0 @@
|
||||
/*
|
||||
Joypad support is based on a stripped-down version of SDL's Darwin implementation
|
||||
of the Joystick API, under the following license:
|
||||
*/
|
||||
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include <AppKit/AppKit.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
#include "GBJoystickListener.h"
|
||||
|
||||
typedef signed SDL_JoystickID;
|
||||
typedef struct _SDL_Joystick SDL_Joystick;
|
||||
|
||||
typedef struct _SDL_JoystickAxisInfo
|
||||
{
|
||||
int16_t initial_value; /* Initial axis state */
|
||||
int16_t value; /* Current axis state */
|
||||
int16_t zero; /* Zero point on the axis (-32768 for triggers) */
|
||||
bool has_initial_value; /* Whether we've seen a value on the axis yet */
|
||||
bool sent_initial_value; /* Whether we've sent the initial axis value */
|
||||
} SDL_JoystickAxisInfo;
|
||||
|
||||
struct _SDL_Joystick
|
||||
{
|
||||
SDL_JoystickID instance_id; /* Device instance, monotonically increasing from 0 */
|
||||
char *name; /* Joystick name - system dependent */
|
||||
|
||||
int naxes; /* Number of axis controls on the joystick */
|
||||
SDL_JoystickAxisInfo *axes;
|
||||
|
||||
int nbuttons; /* Number of buttons on the joystick */
|
||||
uint8_t *buttons; /* Current button states */
|
||||
|
||||
int nhats;
|
||||
uint8_t *hats;
|
||||
|
||||
struct joystick_hwdata *hwdata; /* Driver dependent information */
|
||||
|
||||
int ref_count; /* Reference count for multiple opens */
|
||||
|
||||
bool is_game_controller;
|
||||
bool force_recentering; /* SDL_TRUE if this device needs to have its state reset to 0 */
|
||||
struct _SDL_Joystick *next; /* pointer to next joystick we have allocated */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[16];
|
||||
} SDL_JoystickGUID;
|
||||
|
||||
struct recElement
|
||||
{
|
||||
IOHIDElementRef elementRef;
|
||||
IOHIDElementCookie cookie;
|
||||
uint32_t usagePage, usage; /* HID usage */
|
||||
SInt32 min; /* reported min value possible */
|
||||
SInt32 max; /* reported max value possible */
|
||||
|
||||
/* runtime variables used for auto-calibration */
|
||||
SInt32 minReport; /* min returned value */
|
||||
SInt32 maxReport; /* max returned value */
|
||||
|
||||
struct recElement *pNext; /* next element in list */
|
||||
};
|
||||
typedef struct recElement recElement;
|
||||
|
||||
struct joystick_hwdata
|
||||
{
|
||||
IOHIDDeviceRef deviceRef; /* HIDManager device handle */
|
||||
io_service_t ffservice; /* Interface for force feedback, 0 = no ff */
|
||||
|
||||
char product[256]; /* name of product */
|
||||
uint32_t usage; /* usage page from IOUSBHID Parser.h which defines general usage */
|
||||
uint32_t usagePage; /* usage within above page from IOUSBHID Parser.h which defines specific usage */
|
||||
|
||||
int axes; /* number of axis (calculated, not reported by device) */
|
||||
int buttons; /* number of buttons (calculated, not reported by device) */
|
||||
int hats;
|
||||
int elements; /* number of total elements (should be total of above) (calculated, not reported by device) */
|
||||
|
||||
recElement *firstAxis;
|
||||
recElement *firstButton;
|
||||
recElement *firstHat;
|
||||
|
||||
bool removed;
|
||||
|
||||
int instance_id;
|
||||
SDL_JoystickGUID guid;
|
||||
|
||||
SDL_Joystick joystick;
|
||||
};
|
||||
typedef struct joystick_hwdata recDevice;
|
||||
|
||||
/* The base object of the HID Manager API */
|
||||
static IOHIDManagerRef hidman = NULL;
|
||||
|
||||
/* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */
|
||||
static int s_joystick_instance_id = -1;
|
||||
|
||||
#define SDL_JOYSTICK_AXIS_MAX 32767
|
||||
|
||||
void SDL_PrivateJoystickAxis(SDL_Joystick * joystick, uint8_t axis, int16_t value)
|
||||
{
|
||||
/* Make sure we're not getting garbage or duplicate events */
|
||||
if (axis >= joystick->naxes) {
|
||||
return;
|
||||
}
|
||||
if (!joystick->axes[axis].has_initial_value) {
|
||||
joystick->axes[axis].initial_value = value;
|
||||
joystick->axes[axis].value = value;
|
||||
joystick->axes[axis].zero = value;
|
||||
joystick->axes[axis].has_initial_value = true;
|
||||
}
|
||||
if (value == joystick->axes[axis].value) {
|
||||
return;
|
||||
}
|
||||
if (!joystick->axes[axis].sent_initial_value) {
|
||||
/* Make sure we don't send motion until there's real activity on this axis */
|
||||
const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 controller needed 96 */
|
||||
if (abs(value - joystick->axes[axis].value) <= MAX_ALLOWED_JITTER) {
|
||||
return;
|
||||
}
|
||||
joystick->axes[axis].sent_initial_value = true;
|
||||
joystick->axes[axis].value = value; /* Just so we pass the check above */
|
||||
SDL_PrivateJoystickAxis(joystick, axis, joystick->axes[axis].initial_value);
|
||||
}
|
||||
|
||||
/* Update internal joystick state */
|
||||
joystick->axes[axis].value = value;
|
||||
|
||||
NSResponder<GBJoystickListener> *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder];
|
||||
while (responder) {
|
||||
if ([responder respondsToSelector:@selector(joystick:axis:movedTo:)]) {
|
||||
[responder joystick:@(joystick->name) axis:axis movedTo:value];
|
||||
break;
|
||||
}
|
||||
responder = (typeof(responder)) [responder nextResponder];
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_PrivateJoystickButton(SDL_Joystick *joystick, uint8_t button, uint8_t state)
|
||||
{
|
||||
|
||||
/* Make sure we're not getting garbage or duplicate events */
|
||||
if (button >= joystick->nbuttons) {
|
||||
return;
|
||||
}
|
||||
if (state == joystick->buttons[button]) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update internal joystick state */
|
||||
joystick->buttons[button] = state;
|
||||
|
||||
NSResponder<GBJoystickListener> *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder];
|
||||
while (responder) {
|
||||
if ([responder respondsToSelector:@selector(joystick:button:changedState:)]) {
|
||||
[responder joystick:@(joystick->name) button:button changedState:state];
|
||||
break;
|
||||
}
|
||||
responder = (typeof(responder)) [responder nextResponder];
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_PrivateJoystickHat(SDL_Joystick *joystick, uint8_t hat, uint8_t state)
|
||||
{
|
||||
|
||||
/* Make sure we're not getting garbage or duplicate events */
|
||||
if (hat >= joystick->nhats) {
|
||||
return;
|
||||
}
|
||||
if (state == joystick->hats[hat]) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update internal joystick state */
|
||||
joystick->hats[hat] = state;
|
||||
|
||||
NSResponder<GBJoystickListener> *responder = (typeof(responder)) [[NSApp keyWindow] firstResponder];
|
||||
while (responder) {
|
||||
if ([responder respondsToSelector:@selector(joystick:button:changedState:)]) {
|
||||
[responder joystick:@(joystick->name) hat:hat changedState:state];
|
||||
break;
|
||||
}
|
||||
responder = (typeof(responder)) [responder nextResponder];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
FreeElementList(recElement *pElement)
|
||||
{
|
||||
while (pElement) {
|
||||
recElement *pElementNext = pElement->pNext;
|
||||
free(pElement);
|
||||
pElement = pElementNext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static recDevice *
|
||||
FreeDevice(recDevice *removeDevice)
|
||||
{
|
||||
recDevice *pDeviceNext = NULL;
|
||||
if (removeDevice) {
|
||||
if (removeDevice->deviceRef) {
|
||||
IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
removeDevice->deviceRef = NULL;
|
||||
}
|
||||
|
||||
/* free element lists */
|
||||
FreeElementList(removeDevice->firstAxis);
|
||||
FreeElementList(removeDevice->firstButton);
|
||||
FreeElementList(removeDevice->firstHat);
|
||||
|
||||
free(removeDevice);
|
||||
}
|
||||
return pDeviceNext;
|
||||
}
|
||||
|
||||
static SInt32
|
||||
GetHIDElementState(recDevice *pDevice, recElement *pElement)
|
||||
{
|
||||
SInt32 value = 0;
|
||||
|
||||
if (pDevice && pElement) {
|
||||
IOHIDValueRef valueRef;
|
||||
if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
|
||||
value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
|
||||
|
||||
/* record min and max for auto calibration */
|
||||
if (value < pElement->minReport) {
|
||||
pElement->minReport = value;
|
||||
}
|
||||
if (value > pElement->maxReport) {
|
||||
pElement->maxReport = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static SInt32
|
||||
GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max)
|
||||
{
|
||||
const float deviceScale = max - min;
|
||||
const float readScale = pElement->maxReport - pElement->minReport;
|
||||
const SInt32 value = GetHIDElementState(pDevice, pElement);
|
||||
if (readScale == 0) {
|
||||
return value; /* no scaling at all */
|
||||
}
|
||||
return ((value - pElement->minReport) * deviceScale / readScale) + min;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
|
||||
{
|
||||
recDevice *device = (recDevice *) ctx;
|
||||
device->removed = true;
|
||||
device->deviceRef = NULL; // deviceRef was invalidated due to the remove
|
||||
FreeDevice(device);
|
||||
}
|
||||
|
||||
|
||||
static void AddHIDElement(const void *value, void *parameter);
|
||||
|
||||
/* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
|
||||
static void
|
||||
AddHIDElements(CFArrayRef array, recDevice *pDevice)
|
||||
{
|
||||
const CFRange range = { 0, CFArrayGetCount(array) };
|
||||
CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
|
||||
}
|
||||
|
||||
static bool
|
||||
ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
|
||||
while (listitem) {
|
||||
if (listitem->cookie == cookie) {
|
||||
return true;
|
||||
}
|
||||
listitem = listitem->pNext;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* See if we care about this HID element, and if so, note it in our recDevice. */
|
||||
static void
|
||||
AddHIDElement(const void *value, void *parameter)
|
||||
{
|
||||
recDevice *pDevice = (recDevice *) parameter;
|
||||
IOHIDElementRef refElement = (IOHIDElementRef) value;
|
||||
const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
|
||||
|
||||
if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
|
||||
const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
|
||||
const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
|
||||
const uint32_t usage = IOHIDElementGetUsage(refElement);
|
||||
recElement *element = NULL;
|
||||
recElement **headElement = NULL;
|
||||
|
||||
/* look at types of interest */
|
||||
switch (IOHIDElementGetType(refElement)) {
|
||||
case kIOHIDElementTypeInput_Misc:
|
||||
case kIOHIDElementTypeInput_Button:
|
||||
case kIOHIDElementTypeInput_Axis: {
|
||||
switch (usagePage) { /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
|
||||
case kHIDPage_GenericDesktop:
|
||||
switch (usage) {
|
||||
case kHIDUsage_GD_X:
|
||||
case kHIDUsage_GD_Y:
|
||||
case kHIDUsage_GD_Z:
|
||||
case kHIDUsage_GD_Rx:
|
||||
case kHIDUsage_GD_Ry:
|
||||
case kHIDUsage_GD_Rz:
|
||||
case kHIDUsage_GD_Slider:
|
||||
case kHIDUsage_GD_Dial:
|
||||
case kHIDUsage_GD_Wheel:
|
||||
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
|
||||
element = (recElement *) calloc(1, sizeof (recElement));
|
||||
if (element) {
|
||||
pDevice->axes++;
|
||||
headElement = &(pDevice->firstAxis);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kHIDUsage_GD_Hatswitch:
|
||||
if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
|
||||
element = (recElement *) calloc(1, sizeof (recElement));
|
||||
if (element) {
|
||||
pDevice->hats++;
|
||||
headElement = &(pDevice->firstHat);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case kHIDUsage_GD_DPadUp:
|
||||
case kHIDUsage_GD_DPadDown:
|
||||
case kHIDUsage_GD_DPadRight:
|
||||
case kHIDUsage_GD_DPadLeft:
|
||||
case kHIDUsage_GD_Start:
|
||||
case kHIDUsage_GD_Select:
|
||||
case kHIDUsage_GD_SystemMainMenu:
|
||||
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
|
||||
element = (recElement *) calloc(1, sizeof (recElement));
|
||||
if (element) {
|
||||
pDevice->buttons++;
|
||||
headElement = &(pDevice->firstButton);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case kHIDPage_Simulation:
|
||||
switch (usage) {
|
||||
case kHIDUsage_Sim_Rudder:
|
||||
case kHIDUsage_Sim_Throttle:
|
||||
case kHIDUsage_Sim_Accelerator:
|
||||
case kHIDUsage_Sim_Brake:
|
||||
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
|
||||
element = (recElement *) calloc(1, sizeof (recElement));
|
||||
if (element) {
|
||||
pDevice->axes++;
|
||||
headElement = &(pDevice->firstAxis);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case kHIDPage_Button:
|
||||
case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
|
||||
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
|
||||
element = (recElement *) calloc(1, sizeof (recElement));
|
||||
if (element) {
|
||||
pDevice->buttons++;
|
||||
headElement = &(pDevice->firstButton);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case kIOHIDElementTypeCollection: {
|
||||
CFArrayRef array = IOHIDElementGetChildren(refElement);
|
||||
if (array) {
|
||||
AddHIDElements(array, pDevice);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (element && headElement) { /* add to list */
|
||||
recElement *elementPrevious = NULL;
|
||||
recElement *elementCurrent = *headElement;
|
||||
while (elementCurrent && usage >= elementCurrent->usage) {
|
||||
elementPrevious = elementCurrent;
|
||||
elementCurrent = elementCurrent->pNext;
|
||||
}
|
||||
if (elementPrevious) {
|
||||
elementPrevious->pNext = element;
|
||||
} else {
|
||||
*headElement = element;
|
||||
}
|
||||
|
||||
element->elementRef = refElement;
|
||||
element->usagePage = usagePage;
|
||||
element->usage = usage;
|
||||
element->pNext = elementCurrent;
|
||||
|
||||
element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
|
||||
element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
|
||||
element->cookie = IOHIDElementGetCookie(refElement);
|
||||
|
||||
pDevice->elements++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
|
||||
{
|
||||
const uint16_t BUS_USB = 0x03;
|
||||
const uint16_t BUS_BLUETOOTH = 0x05;
|
||||
int32_t vendor = 0;
|
||||
int32_t product = 0;
|
||||
int32_t version = 0;
|
||||
CFTypeRef refCF = NULL;
|
||||
CFArrayRef array = NULL;
|
||||
uint16_t *guid16 = (uint16_t *)pDevice->guid.data;
|
||||
|
||||
/* get usage page and usage */
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
|
||||
if (refCF) {
|
||||
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
|
||||
}
|
||||
if (pDevice->usagePage != kHIDPage_GenericDesktop) {
|
||||
return false; /* Filter device list to non-keyboard/mouse stuff */
|
||||
}
|
||||
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
|
||||
if (refCF) {
|
||||
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
|
||||
}
|
||||
|
||||
if ((pDevice->usage != kHIDUsage_GD_Joystick &&
|
||||
pDevice->usage != kHIDUsage_GD_GamePad &&
|
||||
pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
|
||||
return false; /* Filter device list to non-keyboard/mouse stuff */
|
||||
}
|
||||
|
||||
pDevice->deviceRef = hidDevice;
|
||||
|
||||
/* get device name */
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
|
||||
if (!refCF) {
|
||||
/* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
|
||||
}
|
||||
if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
|
||||
strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
|
||||
}
|
||||
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
|
||||
if (refCF) {
|
||||
CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
|
||||
}
|
||||
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
|
||||
if (refCF) {
|
||||
CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
|
||||
}
|
||||
|
||||
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
|
||||
if (refCF) {
|
||||
CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
|
||||
}
|
||||
|
||||
memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
|
||||
|
||||
if (vendor && product) {
|
||||
*guid16++ = BUS_USB;
|
||||
*guid16++ = 0;
|
||||
*guid16++ = vendor;
|
||||
*guid16++ = 0;
|
||||
*guid16++ = product;
|
||||
*guid16++ = 0;
|
||||
*guid16++ = version;
|
||||
*guid16++ = 0;
|
||||
} else {
|
||||
*guid16++ = BUS_BLUETOOTH;
|
||||
*guid16++ = 0;
|
||||
strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
|
||||
}
|
||||
|
||||
array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
|
||||
if (array) {
|
||||
AddHIDElements(array, pDevice);
|
||||
CFRelease(array);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
|
||||
{
|
||||
recDevice *device = joystick->hwdata;
|
||||
recElement *element;
|
||||
SInt32 value;
|
||||
int i;
|
||||
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (device->removed) { /* device was unplugged; ignore it. */
|
||||
if (joystick->hwdata) {
|
||||
joystick->force_recentering = true;
|
||||
joystick->hwdata = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
element = device->firstAxis;
|
||||
i = 0;
|
||||
while (element) {
|
||||
value = GetHIDScaledCalibratedState(device, element, -32768, 32767);
|
||||
SDL_PrivateJoystickAxis(joystick, i, value);
|
||||
element = element->pNext;
|
||||
++i;
|
||||
}
|
||||
|
||||
element = device->firstButton;
|
||||
i = 0;
|
||||
while (element) {
|
||||
value = GetHIDElementState(device, element);
|
||||
if (value > 1) { /* handle pressure-sensitive buttons */
|
||||
value = 1;
|
||||
}
|
||||
SDL_PrivateJoystickButton(joystick, i, value);
|
||||
element = element->pNext;
|
||||
++i;
|
||||
}
|
||||
|
||||
element = device->firstHat;
|
||||
i = 0;
|
||||
while (element) {
|
||||
signed range = (element->max - element->min + 1);
|
||||
value = GetHIDElementState(device, element) - element->min;
|
||||
if (range == 4) { /* 4 position hatswitch - scale up value */
|
||||
value *= 2;
|
||||
} else if (range != 8) { /* Neither a 4 nor 8 positions - fall back to default position (centered) */
|
||||
value = -1;
|
||||
}
|
||||
if ((unsigned)value >= 8) {
|
||||
value = -1;
|
||||
}
|
||||
|
||||
SDL_PrivateJoystickHat(joystick, i, value);
|
||||
|
||||
element = element->pNext;
|
||||
++i;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void JoystickInputCallback(
|
||||
SDL_Joystick * joystick,
|
||||
IOReturn result,
|
||||
void * _Nullable sender,
|
||||
IOHIDReportType type,
|
||||
uint32_t reportID,
|
||||
uint8_t * report,
|
||||
CFIndex reportLength)
|
||||
{
|
||||
SDL_SYS_JoystickUpdate(joystick);
|
||||
}
|
||||
|
||||
static void
|
||||
JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
|
||||
{
|
||||
recDevice *device;
|
||||
io_service_t ioservice;
|
||||
|
||||
if (res != kIOReturnSuccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
device = (recDevice *) calloc(1, sizeof(recDevice));
|
||||
|
||||
if (!device) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
|
||||
free(device);
|
||||
return; /* not a device we care about, probably. */
|
||||
}
|
||||
|
||||
SDL_Joystick *joystick = &device->joystick;
|
||||
|
||||
joystick->instance_id = device->instance_id;
|
||||
joystick->hwdata = device;
|
||||
joystick->name = device->product;
|
||||
|
||||
joystick->naxes = device->axes;
|
||||
joystick->nbuttons = device->buttons;
|
||||
joystick->nhats = device->hats;
|
||||
|
||||
if (joystick->naxes > 0) {
|
||||
joystick->axes = (SDL_JoystickAxisInfo *) calloc(joystick->naxes, sizeof(SDL_JoystickAxisInfo));
|
||||
}
|
||||
if (joystick->nbuttons > 0) {
|
||||
joystick->buttons = (uint8_t *) calloc(joystick->nbuttons, 1);
|
||||
}
|
||||
if (joystick->nhats > 0) {
|
||||
joystick->hats = (uint8_t *) calloc(joystick->nhats, 1);
|
||||
}
|
||||
|
||||
/* Get notified when this device is disconnected. */
|
||||
IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
|
||||
static uint8_t junk[80];
|
||||
IOHIDDeviceRegisterInputReportCallback(ioHIDDeviceObject, junk, sizeof(junk), (IOHIDReportCallback) JoystickInputCallback, joystick);
|
||||
IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
|
||||
/* Allocate an instance ID for this device */
|
||||
device->instance_id = ++s_joystick_instance_id;
|
||||
|
||||
/* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
|
||||
ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
|
||||
}
|
||||
|
||||
static bool
|
||||
ConfigHIDManager(CFArrayRef matchingArray)
|
||||
{
|
||||
CFRunLoopRef runloop = CFRunLoopGetCurrent();
|
||||
|
||||
if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
|
||||
IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
|
||||
IOHIDManagerScheduleWithRunLoop(hidman, runloop, kCFRunLoopDefaultMode);
|
||||
|
||||
return true; /* good to go. */
|
||||
}
|
||||
|
||||
|
||||
static CFDictionaryRef
|
||||
CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
|
||||
{
|
||||
CFDictionaryRef retval = NULL;
|
||||
CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
|
||||
CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
|
||||
const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
|
||||
const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
|
||||
|
||||
if (pageNumRef && usageNumRef) {
|
||||
retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
}
|
||||
|
||||
if (pageNumRef) {
|
||||
CFRelease(pageNumRef);
|
||||
}
|
||||
if (usageNumRef) {
|
||||
CFRelease(usageNumRef);
|
||||
}
|
||||
|
||||
if (!retval) {
|
||||
*okay = 0;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static bool
|
||||
CreateHIDManager(void)
|
||||
{
|
||||
bool retval = false;
|
||||
int okay = 1;
|
||||
const void *vals[] = {
|
||||
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
|
||||
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
|
||||
(void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
|
||||
};
|
||||
const size_t numElements = sizeof(vals) / sizeof(vals[0]);
|
||||
CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < numElements; i++) {
|
||||
if (vals[i]) {
|
||||
CFRelease((CFTypeRef) vals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (array) {
|
||||
hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
||||
if (hidman != NULL) {
|
||||
retval = ConfigHIDManager(array);
|
||||
}
|
||||
CFRelease(array);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
void __attribute__((constructor)) SDL_SYS_JoystickInit(void)
|
||||
{
|
||||
if (!CreateHIDManager()) {
|
||||
fprintf(stderr, "Joystick: Couldn't initialize HID Manager");
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
int main(int argc, const char * argv[]) {
|
||||
int main(int argc, const char * argv[])
|
||||
{
|
||||
return NSApplicationMain(argc, argv);
|
||||
}
|
||||
|
@ -1448,7 +1448,7 @@ static bool mbc(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugg
|
||||
}
|
||||
|
||||
if (cartridge->has_rumble) {
|
||||
GB_log(gb, "Cart contains a rumble pak\n");
|
||||
GB_log(gb, "Cart contains a Rumble Pak\n");
|
||||
}
|
||||
|
||||
if (cartridge->has_rtc) {
|
||||
|
@ -151,6 +151,13 @@ static void display_vblank(GB_gameboy_t *gb)
|
||||
}
|
||||
}
|
||||
|
||||
if (gb->rumble_callback) {
|
||||
if (gb->rumble_on_cycles + gb->rumble_off_cycles) {
|
||||
gb->rumble_callback(gb, gb->rumble_on_cycles / (double)(gb->rumble_on_cycles + gb->rumble_off_cycles));
|
||||
gb->rumble_on_cycles = gb->rumble_off_cycles = 0;
|
||||
}
|
||||
}
|
||||
|
||||
gb->vblank_callback(gb);
|
||||
GB_timing_sync(gb);
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_a
|
||||
typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb);
|
||||
typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b);
|
||||
typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on, long cycles_since_last_update);
|
||||
typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, bool rumble_on);
|
||||
typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, double rumble_amplitude);
|
||||
typedef void (*GB_serial_transfer_bit_start_callback_t)(GB_gameboy_t *gb, bool bit_to_send);
|
||||
typedef bool (*GB_serial_transfer_bit_end_callback_t)(GB_gameboy_t *gb);
|
||||
typedef void (*GB_update_input_hint_callback_t)(GB_gameboy_t *gb);
|
||||
@ -614,6 +614,8 @@ struct GB_gameboy_internal_s {
|
||||
bool vblank_just_occured; // For slow operations involving syscalls; these should only run once per vblank
|
||||
uint8_t cycles_since_run; // How many cycles have passed since the last call to GB_run(), in 8MHz units
|
||||
double clock_multiplier;
|
||||
uint32_t rumble_on_cycles;
|
||||
uint32_t rumble_off_cycles;
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -478,9 +478,6 @@ static void write_mbc(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
|
||||
if (gb->cartridge_type->has_rumble) {
|
||||
if (!!(value & 8) != gb->rumble_state) {
|
||||
gb->rumble_state = !gb->rumble_state;
|
||||
if (gb->rumble_callback) {
|
||||
gb->rumble_callback(gb, gb->rumble_state);
|
||||
}
|
||||
}
|
||||
value &= 7;
|
||||
}
|
||||
|
@ -252,10 +252,6 @@ int GB_load_state(GB_gameboy_t *gb, const char *path)
|
||||
|
||||
errno = 0;
|
||||
|
||||
if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
|
||||
gb->rumble_callback(gb, gb->rumble_state);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 32; i++) {
|
||||
GB_palette_changed(gb, false, i * 2);
|
||||
GB_palette_changed(gb, true, i * 2);
|
||||
@ -357,10 +353,6 @@ int GB_load_state_from_buffer(GB_gameboy_t *gb, const uint8_t *buffer, size_t le
|
||||
|
||||
memcpy(gb, &save, sizeof(save));
|
||||
|
||||
if (gb->cartridge_type->has_rumble && gb->rumble_callback) {
|
||||
gb->rumble_callback(gb, gb->rumble_state);
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < 32; i++) {
|
||||
GB_palette_changed(gb, false, i * 2);
|
||||
GB_palette_changed(gb, true, i * 2);
|
||||
|
@ -232,6 +232,14 @@ void GB_advance_cycles(GB_gameboy_t *gb, uint8_t cycles)
|
||||
gb->cycles_since_input_ir_change += cycles;
|
||||
gb->cycles_since_last_sync += cycles;
|
||||
gb->cycles_since_run += cycles;
|
||||
|
||||
if (gb->rumble_state) {
|
||||
gb->rumble_on_cycles++;
|
||||
}
|
||||
else {
|
||||
gb->rumble_off_cycles++;
|
||||
}
|
||||
|
||||
if (!gb->stopped) { // TODO: Verify what happens in STOP mode
|
||||
GB_dma_run(gb);
|
||||
GB_hdma_run(gb);
|
||||
|
369
JoyKit/ControllerConfiguration.inc
Normal file
369
JoyKit/ControllerConfiguration.inc
Normal file
@ -0,0 +1,369 @@
|
||||
#define BUTTON(x) @(JOYButtonUsageGeneric0 + (x))
|
||||
#define AXIS(x) @(JOYAxisUsageGeneric0 + (x))
|
||||
#define AXES2D(x) @(JOYAxes2DUsageGeneric0 + (x))
|
||||
|
||||
hacksByManufacturer = @{
|
||||
@(0x045E): @{ // Microsoft
|
||||
/* Generally untested, but Microsoft goes by the book when it comes to HID report descriptors, so
|
||||
it should work out of the box. The hack is only here for automatic mapping */
|
||||
|
||||
JOYAxisGroups: @{
|
||||
@(kHIDUsage_GD_X): @(0),
|
||||
@(kHIDUsage_GD_Y): @(0),
|
||||
@(kHIDUsage_GD_Z): @(2),
|
||||
@(kHIDUsage_GD_Rx): @(1),
|
||||
@(kHIDUsage_GD_Ry): @(1),
|
||||
@(kHIDUsage_GD_Rz): @(3),
|
||||
},
|
||||
|
||||
JOYButtonUsageMapping: @{
|
||||
BUTTON(1): @(JOYButtonUsageA),
|
||||
BUTTON(2): @(JOYButtonUsageB),
|
||||
BUTTON(3): @(JOYButtonUsageX),
|
||||
BUTTON(4): @(JOYButtonUsageY),
|
||||
BUTTON(5): @(JOYButtonUsageL1),
|
||||
BUTTON(6): @(JOYButtonUsageR1),
|
||||
BUTTON(7): @(JOYButtonUsageLStick),
|
||||
BUTTON(8): @(JOYButtonUsageRStick),
|
||||
BUTTON(9): @(JOYButtonUsageStart),
|
||||
BUTTON(10): @(JOYButtonUsageSelect),
|
||||
BUTTON(11): @(JOYButtonUsageHome),
|
||||
},
|
||||
|
||||
JOYAxisUsageMapping: @{
|
||||
AXIS(3): @(JOYAxisUsageL1),
|
||||
AXIS(6): @(JOYAxisUsageR1),
|
||||
},
|
||||
|
||||
JOYAxes2DUsageMapping: @{
|
||||
AXES2D(1): @(JOYAxes2DUsageLeftStick),
|
||||
AXES2D(4): @(JOYAxes2DUsageRightStick),
|
||||
},
|
||||
},
|
||||
|
||||
@(0x054C): @{ // Sony
|
||||
/* Generally untested, but should work */
|
||||
|
||||
JOYAxisGroups: @{
|
||||
@(kHIDUsage_GD_X): @(0),
|
||||
@(kHIDUsage_GD_Y): @(0),
|
||||
@(kHIDUsage_GD_Z): @(1),
|
||||
@(kHIDUsage_GD_Rx): @(2),
|
||||
@(kHIDUsage_GD_Ry): @(3),
|
||||
@(kHIDUsage_GD_Rz): @(1),
|
||||
},
|
||||
|
||||
JOYButtonUsageMapping: @{
|
||||
BUTTON(1): @(JOYButtonUsageY),
|
||||
BUTTON(2): @(JOYButtonUsageB),
|
||||
BUTTON(3): @(JOYButtonUsageA),
|
||||
BUTTON(4): @(JOYButtonUsageX),
|
||||
BUTTON(5): @(JOYButtonUsageL1),
|
||||
BUTTON(6): @(JOYButtonUsageR1),
|
||||
BUTTON(7): @(JOYButtonUsageL2),
|
||||
BUTTON(8): @(JOYButtonUsageR2),
|
||||
BUTTON(9): @(JOYButtonUsageSelect),
|
||||
BUTTON(10): @(JOYButtonUsageStart),
|
||||
BUTTON(11): @(JOYButtonUsageLStick),
|
||||
BUTTON(12): @(JOYButtonUsageRStick),
|
||||
BUTTON(13): @(JOYButtonUsageHome),
|
||||
BUTTON(14): @(JOYButtonUsageMisc),
|
||||
},
|
||||
|
||||
JOYAxisUsageMapping: @{
|
||||
AXIS(4): @(JOYAxisUsageL1),
|
||||
AXIS(5): @(JOYAxisUsageR1),
|
||||
},
|
||||
|
||||
JOYAxes2DUsageMapping: @{
|
||||
AXES2D(1): @(JOYAxes2DUsageLeftStick),
|
||||
AXES2D(4): @(JOYAxes2DUsageRightStick),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
hacksByName = @{
|
||||
@"WUP-028": @{ // Nintendo GameCube Controller Adapter
|
||||
JOYReportIDFilters: @[@[@1], @[@2], @[@3], @[@4]],
|
||||
JOYButtonUsageMapping: @{
|
||||
BUTTON(1): @(JOYButtonUsageA),
|
||||
BUTTON(2): @(JOYButtonUsageB),
|
||||
BUTTON(3): @(JOYButtonUsageX),
|
||||
BUTTON(4): @(JOYButtonUsageY),
|
||||
BUTTON(5): @(JOYButtonUsageStart),
|
||||
BUTTON(6): @(JOYButtonUsageZ),
|
||||
BUTTON(7): @(JOYButtonUsageR1),
|
||||
BUTTON(8): @(JOYButtonUsageL1),
|
||||
},
|
||||
|
||||
JOYAxisUsageMapping: @{
|
||||
AXIS(3): @(JOYAxisUsageL1),
|
||||
AXIS(6): @(JOYAxisUsageR1),
|
||||
},
|
||||
|
||||
JOYAxes2DUsageMapping: @{
|
||||
AXES2D(1): @(JOYAxes2DUsageLeftStick),
|
||||
AXES2D(4): @(JOYAxes2DUsageRightStick),
|
||||
},
|
||||
|
||||
JOYAxisGroups: @{
|
||||
@(kHIDUsage_GD_X): @(0),
|
||||
@(kHIDUsage_GD_Y): @(0),
|
||||
@(kHIDUsage_GD_Z): @(2),
|
||||
@(kHIDUsage_GD_Rx): @(1),
|
||||
@(kHIDUsage_GD_Ry): @(1),
|
||||
@(kHIDUsage_GD_Rz): @(3),
|
||||
},
|
||||
|
||||
JOYRumbleUsage: @1,
|
||||
JOYRumbleUsagePage: @0xFF00,
|
||||
|
||||
JOYConnectedUsage: @2,
|
||||
JOYConnectedUsagePage: @0xFF00,
|
||||
|
||||
JOYSubElementStructs: @{
|
||||
|
||||
// Rumble
|
||||
@(1364): @[
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@0, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1},
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@1, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1},
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@2, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1},
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@3, @"usagePage":@(0xFF00), @"usage":@1, @"min": @0, @"max": @1},
|
||||
],
|
||||
|
||||
@(11): @[
|
||||
|
||||
// Player 1
|
||||
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@4, @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1},
|
||||
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@8, @"usagePage":@(kHIDPage_Button), @"usage":@1},
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@9, @"usagePage":@(kHIDPage_Button), @"usage":@2},
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@10, @"usagePage":@(kHIDPage_Button), @"usage":@3},
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@11, @"usagePage":@(kHIDPage_Button), @"usage":@4},
|
||||
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@12, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)},
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@13, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)},
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@14, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)},
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@15, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)},
|
||||
|
||||
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@16, @"usagePage":@(kHIDPage_Button), @"usage":@5},
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@17, @"usagePage":@(kHIDPage_Button), @"usage":@6},
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@18, @"usagePage":@(kHIDPage_Button), @"usage":@7},
|
||||
@{@"reportID": @(1), @"size":@1, @"offset":@19, @"usagePage":@(kHIDPage_Button), @"usage":@8},
|
||||
|
||||
@{@"reportID": @(1), @"size":@8, @"offset":@24, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(1), @"size":@8, @"offset":@32, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0},
|
||||
|
||||
@{@"reportID": @(1), @"size":@8, @"offset":@40, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(1), @"size":@8, @"offset":@48, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0},
|
||||
|
||||
@{@"reportID": @(1), @"size":@8, @"offset":@56, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(1), @"size":@8, @"offset":@64, @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255},
|
||||
|
||||
// Player 2
|
||||
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(4 + 72), @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1},
|
||||
|
||||
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(8 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@1},
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(9 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@2},
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(10 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@3},
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(11 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@4},
|
||||
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(12 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)},
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(13 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)},
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(14 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)},
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(15 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)},
|
||||
|
||||
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(16 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@5},
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(17 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@6},
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(18 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@7},
|
||||
@{@"reportID": @(2), @"size":@1, @"offset":@(19 + 72), @"usagePage":@(kHIDPage_Button), @"usage":@8},
|
||||
|
||||
@{@"reportID": @(2), @"size":@8, @"offset":@(24 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(2), @"size":@8, @"offset":@(32 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0},
|
||||
|
||||
@{@"reportID": @(2), @"size":@8, @"offset":@(40 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(2), @"size":@8, @"offset":@(48 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0},
|
||||
|
||||
@{@"reportID": @(2), @"size":@8, @"offset":@(56 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(2), @"size":@8, @"offset":@(64 + 72), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255},
|
||||
|
||||
// Player 3
|
||||
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(4 + 144), @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1},
|
||||
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(8 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@1},
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(9 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@2},
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(10 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@3},
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(11 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@4},
|
||||
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(12 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)},
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(13 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)},
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(14 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)},
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(15 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)},
|
||||
|
||||
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(16 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@5},
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(17 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@6},
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(18 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@7},
|
||||
@{@"reportID": @(3), @"size":@1, @"offset":@(19 + 144), @"usagePage":@(kHIDPage_Button), @"usage":@8},
|
||||
|
||||
@{@"reportID": @(3), @"size":@8, @"offset":@(24 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(3), @"size":@8, @"offset":@(32 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0},
|
||||
|
||||
@{@"reportID": @(3), @"size":@8, @"offset":@(40 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(3), @"size":@8, @"offset":@(48 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0},
|
||||
|
||||
@{@"reportID": @(3), @"size":@8, @"offset":@(56 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(3), @"size":@8, @"offset":@(64 + 144), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255},
|
||||
|
||||
// Player 4
|
||||
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(4 + 216), @"usagePage":@(0xFF00), @"usage":@2, @"min": @0, @"max": @1},
|
||||
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(8 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@1},
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(9 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@2},
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(10 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@3},
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(11 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@4},
|
||||
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(12 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadLeft)},
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(13 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadRight)},
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(14 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadDown)},
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(15 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_DPadUp)},
|
||||
|
||||
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(16 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@5},
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(17 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@6},
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(18 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@7},
|
||||
@{@"reportID": @(4), @"size":@1, @"offset":@(19 + 216), @"usagePage":@(kHIDPage_Button), @"usage":@8},
|
||||
|
||||
@{@"reportID": @(4), @"size":@8, @"offset":@(24 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_X), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(4), @"size":@8, @"offset":@(32 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Y), @"min": @255, @"max": @0},
|
||||
|
||||
@{@"reportID": @(4), @"size":@8, @"offset":@(40 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rx), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(4), @"size":@8, @"offset":@(48 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Ry), @"min": @255, @"max": @0},
|
||||
|
||||
@{@"reportID": @(4), @"size":@8, @"offset":@(56 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Z), @"min": @0, @"max": @255},
|
||||
@{@"reportID": @(4), @"size":@8, @"offset":@(64 + 216), @"usagePage":@(kHIDPage_GenericDesktop), @"usage":@(kHIDUsage_GD_Rz), @"min": @0, @"max": @255},
|
||||
|
||||
|
||||
]},
|
||||
},
|
||||
|
||||
@"GameCube Controller Adapter": @{ // GameCube Controller PC Adapter
|
||||
JOYAxisGroups: @{
|
||||
@(kHIDUsage_GD_X): @(0),
|
||||
@(kHIDUsage_GD_Y): @(0),
|
||||
@(kHIDUsage_GD_Z): @(1),
|
||||
@(kHIDUsage_GD_Rx): @(2),
|
||||
@(kHIDUsage_GD_Ry): @(3),
|
||||
@(kHIDUsage_GD_Rz): @(1),
|
||||
},
|
||||
JOYReportIDFilters: @[@[@1], @[@2], @[@3], @[@4]],
|
||||
JOYButtonUsageMapping: @{
|
||||
BUTTON(1): @(JOYButtonUsageX),
|
||||
BUTTON(2): @(JOYButtonUsageA),
|
||||
BUTTON(3): @(JOYButtonUsageB),
|
||||
BUTTON(4): @(JOYButtonUsageY),
|
||||
BUTTON(5): @(JOYButtonUsageL1),
|
||||
BUTTON(6): @(JOYButtonUsageR1),
|
||||
BUTTON(8): @(JOYButtonUsageZ),
|
||||
BUTTON(10): @(JOYButtonUsageStart),
|
||||
BUTTON(13): @(JOYButtonUsageDPadUp),
|
||||
BUTTON(14): @(JOYButtonUsageDPadRight),
|
||||
BUTTON(15): @(JOYButtonUsageDPadDown),
|
||||
BUTTON(16): @(JOYButtonUsageDPadLeft),
|
||||
},
|
||||
|
||||
JOYAxisUsageMapping: @{
|
||||
AXIS(4): @(JOYAxisUsageL1),
|
||||
AXIS(5): @(JOYAxisUsageR1),
|
||||
},
|
||||
|
||||
JOYAxes2DUsageMapping: @{
|
||||
AXES2D(1): @(JOYAxes2DUsageLeftStick),
|
||||
AXES2D(3): @(JOYAxes2DUsageRightStick),
|
||||
},
|
||||
|
||||
JOYRumbleUsage: @1,
|
||||
JOYRumbleUsagePage: @0xFF00,
|
||||
JOYRumbleMin: @0,
|
||||
JOYRumbleMax: @255,
|
||||
JOYSwapZRz: @YES,
|
||||
},
|
||||
|
||||
@"Twin USB Joystick": @{ // DualShock PC Adapter
|
||||
JOYAxisGroups: @{
|
||||
@(kHIDUsage_GD_X): @(0),
|
||||
@(kHIDUsage_GD_Y): @(0),
|
||||
@(kHIDUsage_GD_Z): @(1),
|
||||
@(kHIDUsage_GD_Rx): @(2),
|
||||
@(kHIDUsage_GD_Ry): @(2),
|
||||
@(kHIDUsage_GD_Rz): @(1),
|
||||
},
|
||||
JOYReportIDFilters: @[@[@1], @[@2]],
|
||||
JOYButtonUsageMapping: @{
|
||||
BUTTON(1): @(JOYButtonUsageX),
|
||||
BUTTON(2): @(JOYButtonUsageA),
|
||||
BUTTON(3): @(JOYButtonUsageB),
|
||||
BUTTON(4): @(JOYButtonUsageY),
|
||||
BUTTON(5): @(JOYButtonUsageL2),
|
||||
BUTTON(6): @(JOYButtonUsageR2),
|
||||
BUTTON(7): @(JOYButtonUsageL1),
|
||||
BUTTON(8): @(JOYButtonUsageR1),
|
||||
BUTTON(9): @(JOYButtonUsageSelect),
|
||||
BUTTON(10): @(JOYButtonUsageStart),
|
||||
BUTTON(11): @(JOYButtonUsageLStick),
|
||||
BUTTON(12): @(JOYButtonUsageRStick),
|
||||
BUTTON(13): @(JOYButtonUsageDPadUp),
|
||||
BUTTON(14): @(JOYButtonUsageDPadRight),
|
||||
BUTTON(15): @(JOYButtonUsageDPadDown),
|
||||
BUTTON(16): @(JOYButtonUsageDPadLeft),
|
||||
},
|
||||
|
||||
JOYAxes2DUsageMapping: @{
|
||||
AXES2D(1): @(JOYAxes2DUsageLeftStick),
|
||||
AXES2D(6): @(JOYAxes2DUsageRightStick),
|
||||
},
|
||||
|
||||
JOYSwapZRz: @YES,
|
||||
},
|
||||
|
||||
@"Pro Controller": @{ // Switch Pro Controller
|
||||
JOYIsSwitch: @YES,
|
||||
JOYAxisGroups: @{
|
||||
@(kHIDUsage_GD_X): @(0),
|
||||
@(kHIDUsage_GD_Y): @(0),
|
||||
@(kHIDUsage_GD_Z): @(0),
|
||||
@(kHIDUsage_GD_Rx): @(1),
|
||||
@(kHIDUsage_GD_Ry): @(1),
|
||||
@(kHIDUsage_GD_Rz): @(1),
|
||||
},
|
||||
|
||||
JOYButtonUsageMapping: @{
|
||||
BUTTON(1): @(JOYButtonUsageB),
|
||||
BUTTON(2): @(JOYButtonUsageA),
|
||||
BUTTON(3): @(JOYButtonUsageY),
|
||||
BUTTON(4): @(JOYButtonUsageX),
|
||||
BUTTON(5): @(JOYButtonUsageL1),
|
||||
BUTTON(6): @(JOYButtonUsageR1),
|
||||
BUTTON(7): @(JOYButtonUsageL2),
|
||||
BUTTON(8): @(JOYButtonUsageR2),
|
||||
BUTTON(9): @(JOYButtonUsageSelect),
|
||||
BUTTON(10): @(JOYButtonUsageStart),
|
||||
BUTTON(11): @(JOYButtonUsageLStick),
|
||||
BUTTON(12): @(JOYButtonUsageRStick),
|
||||
BUTTON(13): @(JOYButtonUsageHome),
|
||||
BUTTON(14): @(JOYButtonUsageMisc),
|
||||
},
|
||||
|
||||
JOYAxes2DUsageMapping: @{
|
||||
AXES2D(1): @(JOYAxes2DUsageLeftStick),
|
||||
AXES2D(4): @(JOYAxes2DUsageRightStick),
|
||||
},
|
||||
},
|
||||
};
|
24
JoyKit/JOYAxes2D.h
Normal file
24
JoyKit/JOYAxes2D.h
Normal file
@ -0,0 +1,24 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef enum {
|
||||
JOYAxes2DUsageNone,
|
||||
JOYAxes2DUsageLeftStick,
|
||||
JOYAxes2DUsageRightStick,
|
||||
JOYAxes2DUsageMiddleStick,
|
||||
JOYAxes2DUsagePointer,
|
||||
JOYAxes2DUsageNonGenericMax,
|
||||
|
||||
JOYAxes2DUsageGeneric0 = 0x10000,
|
||||
} JOYAxes2DUsage;
|
||||
|
||||
@interface JOYAxes2D : NSObject
|
||||
- (NSString *)usageString;
|
||||
+ (NSString *)usageToString: (JOYAxes2DUsage) usage;
|
||||
- (uint64_t)uniqueID;
|
||||
- (double)distance;
|
||||
- (double)angle;
|
||||
- (NSPoint)value;
|
||||
@property JOYAxes2DUsage usage;
|
||||
@end
|
||||
|
||||
|
168
JoyKit/JOYAxes2D.m
Normal file
168
JoyKit/JOYAxes2D.m
Normal file
@ -0,0 +1,168 @@
|
||||
#import "JOYAxes2D.h"
|
||||
#import "JOYElement.h"
|
||||
|
||||
@implementation JOYAxes2D
|
||||
{
|
||||
JOYElement *_element1, *_element2;
|
||||
double _state1, _state2;
|
||||
int32_t initialX, initialY;
|
||||
int32_t minX, minY;
|
||||
int32_t maxX, maxY;
|
||||
|
||||
}
|
||||
|
||||
+ (NSString *)usageToString: (JOYAxes2DUsage) usage
|
||||
{
|
||||
if (usage < JOYAxes2DUsageNonGenericMax) {
|
||||
return (NSString *[]) {
|
||||
@"None",
|
||||
@"Left Stick",
|
||||
@"Right Stick",
|
||||
@"Middle Stick",
|
||||
@"Pointer",
|
||||
}[usage];
|
||||
}
|
||||
if (usage >= JOYAxes2DUsageGeneric0) {
|
||||
return [NSString stringWithFormat:@"Generic 2D Analog Control %d", usage - JOYAxes2DUsageGeneric0];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"Unknown Usage 2D Axes %d", usage];
|
||||
}
|
||||
|
||||
- (NSString *)usageString
|
||||
{
|
||||
return [self.class usageToString:_usage];
|
||||
}
|
||||
|
||||
- (uint64_t)uniqueID
|
||||
{
|
||||
return _element1.uniqueID;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %.2f%%, %.2f degrees>", self.className, self, self.usageString, self.uniqueID, self.distance * 100, self.angle];
|
||||
}
|
||||
|
||||
- (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) return self;
|
||||
|
||||
_element1 = element1;
|
||||
_element2 = element2;
|
||||
|
||||
|
||||
if (element1.usagePage == kHIDPage_GenericDesktop) {
|
||||
uint16_t usage = element1.usage;
|
||||
_usage = JOYAxes2DUsageGeneric0 + usage - kHIDUsage_GD_X + 1;
|
||||
}
|
||||
initialX = [_element1 value];
|
||||
initialY = [_element2 value];
|
||||
minX = element1.max;
|
||||
minY = element2.max;
|
||||
maxX = element1.min;
|
||||
maxY = element2.min;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSPoint)value
|
||||
{
|
||||
return NSMakePoint(_state1, _state2);
|
||||
}
|
||||
|
||||
-(int32_t) effectiveMinX
|
||||
{
|
||||
int32_t rawMin = _element1.min;
|
||||
int32_t rawMax = _element1.max;
|
||||
if (initialX == 0) return rawMin;
|
||||
if (minX <= (rawMin * 2 + initialX) / 3 && maxX >= (rawMax * 2 + initialX) / 3 ) return minX;
|
||||
if ((initialX - rawMin) < (rawMax - initialX)) return rawMin;
|
||||
return initialX - (rawMax - initialX);
|
||||
}
|
||||
|
||||
-(int32_t) effectiveMinY
|
||||
{
|
||||
int32_t rawMin = _element2.min;
|
||||
int32_t rawMax = _element2.max;
|
||||
if (initialY == 0) return rawMin;
|
||||
if (minX <= (rawMin * 2 + initialY) / 3 && maxY >= (rawMax * 2 + initialY) / 3 ) return minY;
|
||||
if ((initialY - rawMin) < (rawMax - initialY)) return rawMin;
|
||||
return initialY - (rawMax - initialY);
|
||||
}
|
||||
|
||||
-(int32_t) effectiveMaxX
|
||||
{
|
||||
int32_t rawMin = _element1.min;
|
||||
int32_t rawMax = _element1.max;
|
||||
if (initialX == 0) return rawMax;
|
||||
if (minX <= (rawMin * 2 + initialX) / 3 && maxX >= (rawMax * 2 + initialX) / 3 ) return maxX;
|
||||
if ((initialX - rawMin) > (rawMax - initialX)) return rawMax;
|
||||
return initialX + (initialX - rawMin);
|
||||
}
|
||||
|
||||
-(int32_t) effectiveMaxY
|
||||
{
|
||||
int32_t rawMin = _element2.min;
|
||||
int32_t rawMax = _element2.max;
|
||||
if (initialY == 0) return rawMax;
|
||||
if (minX <= (rawMin * 2 + initialY) / 3 && maxY >= (rawMax * 2 + initialY) / 3 ) return maxY;
|
||||
if ((initialY - rawMin) > (rawMax - initialY)) return rawMax;
|
||||
return initialY + (initialY - rawMin);
|
||||
}
|
||||
|
||||
- (bool)updateState
|
||||
{
|
||||
int32_t x = [_element1 value];
|
||||
int32_t y = [_element2 value];
|
||||
if (x == 0 && y == 0) return false;
|
||||
|
||||
if (initialX == 0 && initialY == 0) {
|
||||
initialX = x;
|
||||
initialY = y;
|
||||
}
|
||||
|
||||
double old1 = _state1, old2 = _state2;
|
||||
{
|
||||
double min = [self effectiveMinX];
|
||||
double max = [self effectiveMaxX];
|
||||
if (min == max) return false;
|
||||
int32_t value = x;
|
||||
|
||||
if (initialX != 0) {
|
||||
minX = MIN(value, minX);
|
||||
maxX = MAX(value, maxX);
|
||||
}
|
||||
|
||||
_state1 = (value - min) / (max - min) * 2 - 1;
|
||||
}
|
||||
|
||||
{
|
||||
double min = [self effectiveMinY];
|
||||
double max = [self effectiveMaxY];
|
||||
if (min == max) return false;
|
||||
int32_t value = y;
|
||||
|
||||
if (initialY != 0) {
|
||||
minY = MIN(value, minY);
|
||||
maxY = MAX(value, maxY);
|
||||
}
|
||||
|
||||
_state2 = (value - min) / (max - min) * 2 - 1;
|
||||
}
|
||||
|
||||
return old1 != _state1 || old2 != _state2;
|
||||
}
|
||||
|
||||
- (double)distance
|
||||
{
|
||||
return MIN(sqrt(_state1 * _state1 + _state2 * _state2), 1.0);
|
||||
}
|
||||
|
||||
- (double)angle {
|
||||
double temp = atan2(_state2, _state1) * 180 / M_PI;
|
||||
if (temp >= 0) return temp;
|
||||
return temp + 360;
|
||||
}
|
||||
@end
|
29
JoyKit/JOYAxis.h
Normal file
29
JoyKit/JOYAxis.h
Normal file
@ -0,0 +1,29 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
typedef enum {
|
||||
JOYAxisUsageNone,
|
||||
JOYAxisUsageL1,
|
||||
JOYAxisUsageL2,
|
||||
JOYAxisUsageL3,
|
||||
JOYAxisUsageR1,
|
||||
JOYAxisUsageR2,
|
||||
JOYAxisUsageR3,
|
||||
JOYAxisUsageWheel,
|
||||
JOYAxisUsageRudder,
|
||||
JOYAxisUsageThrottle,
|
||||
JOYAxisUsageAccelerator,
|
||||
JOYAxisUsageBrake,
|
||||
JOYAxisUsageNonGenericMax,
|
||||
|
||||
JOYAxisUsageGeneric0 = 0x10000,
|
||||
} JOYAxisUsage;
|
||||
|
||||
@interface JOYAxis : NSObject
|
||||
- (NSString *)usageString;
|
||||
+ (NSString *)usageToString: (JOYAxisUsage) usage;
|
||||
- (uint64_t)uniqueID;
|
||||
- (double)value;
|
||||
@property JOYAxisUsage usage;
|
||||
@end
|
||||
|
||||
|
90
JoyKit/JOYAxis.m
Normal file
90
JoyKit/JOYAxis.m
Normal file
@ -0,0 +1,90 @@
|
||||
#import "JOYAxis.h"
|
||||
#import "JOYElement.h"
|
||||
|
||||
@implementation JOYAxis
|
||||
{
|
||||
JOYElement *_element;
|
||||
double _state;
|
||||
double _min;
|
||||
}
|
||||
|
||||
+ (NSString *)usageToString: (JOYAxisUsage) usage
|
||||
{
|
||||
if (usage < JOYAxisUsageNonGenericMax) {
|
||||
return (NSString *[]) {
|
||||
@"None",
|
||||
@"Analog L1",
|
||||
@"Analog L2",
|
||||
@"Analog L3",
|
||||
@"Analog R1",
|
||||
@"Analog R2",
|
||||
@"Analog R3",
|
||||
@"Wheel",
|
||||
@"Rudder",
|
||||
@"Throttle",
|
||||
@"Accelerator",
|
||||
@"Brake",
|
||||
}[usage];
|
||||
}
|
||||
if (usage >= JOYAxisUsageGeneric0) {
|
||||
return [NSString stringWithFormat:@"Generic Analog Control %d", usage - JOYAxisUsageGeneric0];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"Unknown Usage Axis %d", usage];
|
||||
}
|
||||
|
||||
- (NSString *)usageString
|
||||
{
|
||||
return [self.class usageToString:_usage];
|
||||
}
|
||||
|
||||
- (uint64_t)uniqueID
|
||||
{
|
||||
return _element.uniqueID;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %f%%>", self.className, self, self.usageString, self.uniqueID, _state * 100];
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(JOYElement *)element
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) return self;
|
||||
|
||||
_element = element;
|
||||
|
||||
|
||||
if (element.usagePage == kHIDPage_GenericDesktop) {
|
||||
uint16_t usage = element.usage;
|
||||
_usage = JOYAxisUsageGeneric0 + usage - kHIDUsage_GD_X + 1;
|
||||
}
|
||||
|
||||
_min = 1.0;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (double) value
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
|
||||
- (bool)updateState
|
||||
{
|
||||
double min = _element.min;
|
||||
double max = _element.max;
|
||||
if (min == max) return false;
|
||||
double old = _state;
|
||||
double unnormalized = ([_element value] - min) / (max - min);
|
||||
if (unnormalized < _min) {
|
||||
_min = unnormalized;
|
||||
}
|
||||
if (_min != 1) {
|
||||
_state = (unnormalized - _min) / (1 - _min);
|
||||
}
|
||||
return old != _state;
|
||||
}
|
||||
|
||||
@end
|
42
JoyKit/JOYButton.h
Normal file
42
JoyKit/JOYButton.h
Normal file
@ -0,0 +1,42 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
|
||||
typedef enum {
|
||||
JOYButtonUsageNone,
|
||||
JOYButtonUsageA,
|
||||
JOYButtonUsageB,
|
||||
JOYButtonUsageC,
|
||||
JOYButtonUsageX,
|
||||
JOYButtonUsageY,
|
||||
JOYButtonUsageZ,
|
||||
JOYButtonUsageStart,
|
||||
JOYButtonUsageSelect,
|
||||
JOYButtonUsageHome,
|
||||
JOYButtonUsageMisc,
|
||||
JOYButtonUsageLStick,
|
||||
JOYButtonUsageRStick,
|
||||
JOYButtonUsageL1,
|
||||
JOYButtonUsageL2,
|
||||
JOYButtonUsageL3,
|
||||
JOYButtonUsageR1,
|
||||
JOYButtonUsageR2,
|
||||
JOYButtonUsageR3,
|
||||
JOYButtonUsageDPadLeft,
|
||||
JOYButtonUsageDPadRight,
|
||||
JOYButtonUsageDPadUp,
|
||||
JOYButtonUsageDPadDown,
|
||||
JOYButtonUsageNonGenericMax,
|
||||
|
||||
JOYButtonUsageGeneric0 = 0x10000,
|
||||
} JOYButtonUsage;
|
||||
|
||||
@interface JOYButton : NSObject
|
||||
- (NSString *)usageString;
|
||||
+ (NSString *)usageToString: (JOYButtonUsage) usage;
|
||||
- (uint64_t)uniqueID;
|
||||
- (bool) isPressed;
|
||||
@property JOYButtonUsage usage;
|
||||
@end
|
||||
|
||||
|
102
JoyKit/JOYButton.m
Normal file
102
JoyKit/JOYButton.m
Normal file
@ -0,0 +1,102 @@
|
||||
#import "JOYButton.h"
|
||||
#import "JOYElement.h"
|
||||
|
||||
@implementation JOYButton
|
||||
{
|
||||
JOYElement *_element;
|
||||
bool _state;
|
||||
}
|
||||
|
||||
+ (NSString *)usageToString: (JOYButtonUsage) usage
|
||||
{
|
||||
if (usage < JOYButtonUsageNonGenericMax) {
|
||||
return (NSString *[]) {
|
||||
@"None",
|
||||
@"A",
|
||||
@"B",
|
||||
@"C",
|
||||
@"X",
|
||||
@"Y",
|
||||
@"Z",
|
||||
@"Start",
|
||||
@"Select",
|
||||
@"Home",
|
||||
@"Misc",
|
||||
@"Left Stick",
|
||||
@"Right Stick",
|
||||
@"L1",
|
||||
@"L2",
|
||||
@"L3",
|
||||
@"R1",
|
||||
@"R2",
|
||||
@"R3",
|
||||
@"D-Pad Left",
|
||||
@"D-Pad Right",
|
||||
@"D-Pad Up",
|
||||
@"D-Pad Down",
|
||||
}[usage];
|
||||
}
|
||||
if (usage >= JOYButtonUsageGeneric0) {
|
||||
return [NSString stringWithFormat:@"Generic Button %d", usage - JOYButtonUsageGeneric0];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"Unknown Usage Button %d", usage];
|
||||
}
|
||||
|
||||
- (NSString *)usageString
|
||||
{
|
||||
return [self.class usageToString:_usage];
|
||||
}
|
||||
|
||||
- (uint64_t)uniqueID
|
||||
{
|
||||
return _element.uniqueID;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p, %@ (%llu); State: %s>", self.className, self, self.usageString, self.uniqueID, _state? "Presssed" : "Released"];
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(JOYElement *)element
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) return self;
|
||||
|
||||
_element = element;
|
||||
|
||||
if (element.usagePage == kHIDPage_Button) {
|
||||
uint16_t usage = element.usage;
|
||||
_usage = JOYButtonUsageGeneric0 + usage;
|
||||
}
|
||||
else if (element.usagePage == kHIDPage_GenericDesktop) {
|
||||
switch (element.usage) {
|
||||
case kHIDUsage_GD_DPadUp: _usage = JOYButtonUsageDPadUp; break;
|
||||
case kHIDUsage_GD_DPadDown: _usage = JOYButtonUsageDPadDown; break;
|
||||
case kHIDUsage_GD_DPadRight: _usage = JOYButtonUsageDPadRight; break;
|
||||
case kHIDUsage_GD_DPadLeft: _usage = JOYButtonUsageDPadLeft; break;
|
||||
case kHIDUsage_GD_Start: _usage = JOYButtonUsageStart; break;
|
||||
case kHIDUsage_GD_Select: _usage = JOYButtonUsageSelect; break;
|
||||
case kHIDUsage_GD_SystemMainMenu: _usage = JOYButtonUsageHome; break;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (bool) isPressed
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
|
||||
- (bool)updateState
|
||||
{
|
||||
bool state = [_element value];
|
||||
if (_state != state) {
|
||||
_state = state;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@end
|
41
JoyKit/JOYController.h
Normal file
41
JoyKit/JOYController.h
Normal file
@ -0,0 +1,41 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "JOYButton.h"
|
||||
#import "JOYAxis.h"
|
||||
#import "JOYAxes2D.h"
|
||||
#import "JOYHat.h"
|
||||
|
||||
static NSString const *JOYAxesEmulateButtonsKey = @"JOYAxesEmulateButtons";
|
||||
static NSString const *JOYAxes2DEmulateButtonsKey = @"JOYAxes2DEmulateButtons";
|
||||
static NSString const *JOYHatsEmulateButtonsKey = @"JOYHatsEmulateButtons";
|
||||
|
||||
@class JOYController;
|
||||
|
||||
@protocol JOYListener <NSObject>
|
||||
|
||||
@optional
|
||||
-(void) controllerConnected:(JOYController *)controller;
|
||||
-(void) controllerDisconnected:(JOYController *)controller;
|
||||
-(void) controller:(JOYController *)controller buttonChangedState:(JOYButton *)button;
|
||||
-(void) controller:(JOYController *)controller movedAxis:(JOYAxis *)axis;
|
||||
-(void) controller:(JOYController *)controller movedAxes2D:(JOYAxes2D *)axes;
|
||||
-(void) controller:(JOYController *)controller movedHat:(JOYHat *)hat;
|
||||
|
||||
@end
|
||||
|
||||
@interface JOYController : NSObject
|
||||
+ (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options;
|
||||
+ (NSArray<JOYController *> *) allControllers;
|
||||
+ (void) registerListener:(id<JOYListener>)listener;
|
||||
+ (void) unregisterListener:(id<JOYListener>)listener;
|
||||
- (NSString *)deviceName;
|
||||
- (NSString *)uniqueID;
|
||||
- (NSArray<JOYButton *> *) buttons;
|
||||
- (NSArray<JOYAxis *> *) axes;
|
||||
- (NSArray<JOYAxes2D *> *) axes2D;
|
||||
- (NSArray<JOYHat *> *) hats;
|
||||
- (void)setRumbleAmplitude:(double)amp;
|
||||
- (void)setPlayerLEDs:(uint8_t)mask;
|
||||
@property (readonly, getter=isConnected) bool connected;
|
||||
@end
|
||||
|
||||
|
760
JoyKit/JOYController.m
Normal file
760
JoyKit/JOYController.m
Normal file
@ -0,0 +1,760 @@
|
||||
#import "JOYController.h"
|
||||
#import "JOYMultiplayerController.h"
|
||||
#import "JOYElement.h"
|
||||
#import "JOYSubElement.h"
|
||||
#import "JOYEmulatedButton.h"
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
|
||||
static NSString const *JOYAxisGroups = @"JOYAxisGroups";
|
||||
static NSString const *JOYReportIDFilters = @"JOYReportIDFilters";
|
||||
static NSString const *JOYButtonUsageMapping = @"JOYButtonUsageMapping";
|
||||
static NSString const *JOYAxisUsageMapping = @"JOYAxisUsageMapping";
|
||||
static NSString const *JOYAxes2DUsageMapping = @"JOYAxes2DUsageMapping";
|
||||
static NSString const *JOYSubElementStructs = @"JOYSubElementStructs";
|
||||
static NSString const *JOYIsSwitch = @"JOYIsSwitch";
|
||||
static NSString const *JOYRumbleUsage = @"JOYRumbleUsage";
|
||||
static NSString const *JOYRumbleUsagePage = @"JOYRumbleUsagePage";
|
||||
static NSString const *JOYConnectedUsage = @"JOYConnectedUsage";
|
||||
static NSString const *JOYConnectedUsagePage = @"JOYConnectedUsagePage";
|
||||
static NSString const *JOYRumbleMin = @"JOYRumbleMin";
|
||||
static NSString const *JOYRumbleMax = @"JOYRumbleMax";
|
||||
static NSString const *JOYSwapZRz = @"JOYSwapZRz";
|
||||
|
||||
|
||||
static NSMutableDictionary<id, JOYController *> *controllers; // Physical controllers
|
||||
static NSMutableArray<JOYController *> *exposedControllers; // Logical controllers
|
||||
|
||||
static NSDictionary *hacksByName = nil;
|
||||
static NSDictionary *hacksByManufacturer = nil;
|
||||
|
||||
static NSMutableSet<id<JOYListener>> *listeners = nil;
|
||||
|
||||
static bool axesEmulateButtons = false;
|
||||
static bool axes2DEmulateButtons = false;
|
||||
static bool hatsEmulateButtons = false;
|
||||
|
||||
@interface JOYController ()
|
||||
+ (void)controllerAdded:(IOHIDDeviceRef) device;
|
||||
+ (void)controllerRemoved:(IOHIDDeviceRef) device;
|
||||
- (void)elementChanged:(IOHIDElementRef) element;
|
||||
@end
|
||||
|
||||
@interface JOYButton ()
|
||||
- (instancetype)initWithElement:(JOYElement *)element;
|
||||
- (bool)updateState;
|
||||
@end
|
||||
|
||||
@interface JOYAxis ()
|
||||
- (instancetype)initWithElement:(JOYElement *)element;
|
||||
- (bool)updateState;
|
||||
@end
|
||||
|
||||
@interface JOYHat ()
|
||||
- (instancetype)initWithElement:(JOYElement *)element;
|
||||
- (bool)updateState;
|
||||
@end
|
||||
|
||||
@interface JOYAxes2D ()
|
||||
- (instancetype)initWithFirstElement:(JOYElement *)element1 secondElement:(JOYElement *)element2;
|
||||
- (bool)updateState;
|
||||
@end
|
||||
|
||||
static NSDictionary *CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage)
|
||||
{
|
||||
return @{
|
||||
@kIOHIDDeviceUsagePageKey: @(page),
|
||||
@kIOHIDDeviceUsageKey: @(usage),
|
||||
};
|
||||
}
|
||||
|
||||
static void HIDDeviceAdded(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
|
||||
{
|
||||
[JOYController controllerAdded:device];
|
||||
}
|
||||
|
||||
static void HIDDeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDeviceRef device)
|
||||
{
|
||||
[JOYController controllerRemoved:device];
|
||||
}
|
||||
|
||||
static void HIDInput(void *context, IOReturn result, void *sender, IOHIDValueRef value)
|
||||
{
|
||||
[(__bridge JOYController *)context elementChanged:IOHIDValueGetElement(value)];
|
||||
}
|
||||
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t reportID;
|
||||
uint8_t sequence;
|
||||
uint8_t rumbleData[8];
|
||||
uint8_t command;
|
||||
uint8_t commandData[26];
|
||||
} JOYSwitchPacket;
|
||||
|
||||
@implementation JOYController
|
||||
{
|
||||
IOHIDDeviceRef _device;
|
||||
NSMutableDictionary<JOYElement *, JOYButton *> *_buttons;
|
||||
NSMutableDictionary<JOYElement *, JOYAxis *> *_axes;
|
||||
NSMutableDictionary<JOYElement *, JOYAxes2D *> *_axes2D;
|
||||
NSMutableDictionary<JOYElement *, JOYHat *> *_hats;
|
||||
NSMutableDictionary<JOYElement *, NSArray<JOYElement *> *> *_multiElements;
|
||||
|
||||
// Button emulation
|
||||
NSMutableDictionary<NSNumber *, JOYEmulatedButton *> *_axisEmulatedButtons;
|
||||
NSMutableDictionary<NSNumber *, NSArray <JOYEmulatedButton *> *> *_axes2DEmulatedButtons;
|
||||
NSMutableDictionary<NSNumber *, NSArray <JOYEmulatedButton *> *> *_hatEmulatedButtons;
|
||||
|
||||
JOYElement *_rumbleElement;
|
||||
JOYElement *_connectedElement;
|
||||
NSMutableDictionary<NSValue *, JOYElement *> *_iokitToJOY;
|
||||
NSString *_serialSuffix;
|
||||
bool _isSwitch; // Does thie controller use the Switch protocol?
|
||||
JOYSwitchPacket _lastSwitchPacket;
|
||||
NSCondition *_rumblePWMThreadLock;
|
||||
volatile double _rumblePWMRatio;
|
||||
bool _physicallyConnected;
|
||||
bool _logicallyConnected;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDevice:(IOHIDDeviceRef) device
|
||||
{
|
||||
return [self initWithDevice:device reportIDFilter:nil serialSuffix:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray <NSNumber *> *) filter serialSuffix:(NSString *)suffix
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) return self;
|
||||
|
||||
_physicallyConnected = true;
|
||||
_logicallyConnected = true;
|
||||
_device = device;
|
||||
_serialSuffix = suffix;
|
||||
|
||||
IOHIDDeviceRegisterInputValueCallback(device, HIDInput, (void *)self);
|
||||
IOHIDDeviceScheduleWithRunLoop(device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
|
||||
|
||||
NSArray *array = CFBridgingRelease(IOHIDDeviceCopyMatchingElements(device, NULL, kIOHIDOptionsTypeNone));
|
||||
_buttons = [NSMutableDictionary dictionary];
|
||||
_axes = [NSMutableDictionary dictionary];
|
||||
_axes2D = [NSMutableDictionary dictionary];
|
||||
_hats = [NSMutableDictionary dictionary];
|
||||
_axisEmulatedButtons = [NSMutableDictionary dictionary];
|
||||
_axes2DEmulatedButtons = [NSMutableDictionary dictionary];
|
||||
_hatEmulatedButtons = [NSMutableDictionary dictionary];
|
||||
_multiElements = [NSMutableDictionary dictionary];
|
||||
_iokitToJOY = [NSMutableDictionary dictionary];
|
||||
_rumblePWMThreadLock = [[NSCondition alloc] init];
|
||||
|
||||
|
||||
//NSMutableArray *axes3d = [NSMutableArray array];
|
||||
|
||||
NSDictionary *axisGroups = @{
|
||||
@(kHIDUsage_GD_X): @(0),
|
||||
@(kHIDUsage_GD_Y): @(0),
|
||||
@(kHIDUsage_GD_Z): @(1),
|
||||
@(kHIDUsage_GD_Rx): @(2),
|
||||
@(kHIDUsage_GD_Ry): @(2),
|
||||
@(kHIDUsage_GD_Rz): @(1),
|
||||
};
|
||||
NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
|
||||
NSDictionary *hacks = hacksByName[name];
|
||||
if (!hacks) {
|
||||
hacks = hacksByManufacturer[(__bridge NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDVendorIDKey))];
|
||||
}
|
||||
axisGroups = hacks[JOYAxisGroups] ?: axisGroups;
|
||||
_isSwitch = [hacks[JOYIsSwitch] boolValue];
|
||||
uint16_t rumbleUsagePage = (uint16_t)[hacks[JOYRumbleUsagePage] unsignedIntValue];
|
||||
uint16_t rumbleUsage = (uint16_t)[hacks[JOYRumbleUsage] unsignedIntValue];
|
||||
uint16_t connectedUsagePage = (uint16_t)[hacks[JOYConnectedUsagePage] unsignedIntValue];
|
||||
uint16_t connectedUsage = (uint16_t)[hacks[JOYConnectedUsage] unsignedIntValue];
|
||||
|
||||
JOYElement *previousAxisElement = nil;
|
||||
id previous = nil;
|
||||
for (id _element in array) {
|
||||
if (_element == previous) continue; // Some elements are reported twice for some reason
|
||||
previous = _element;
|
||||
NSArray *elements = nil;
|
||||
JOYElement *element = [[JOYElement alloc] initWithElement:(__bridge IOHIDElementRef)_element];
|
||||
|
||||
NSArray<NSDictionary <NSString *,NSNumber *>*> *subElementDefs = hacks[JOYSubElementStructs][@(element.uniqueID)];
|
||||
|
||||
bool isOutput = false;
|
||||
if (subElementDefs && element.uniqueID != element.parentID) {
|
||||
elements = [NSMutableArray array];
|
||||
for (NSDictionary<NSString *,NSNumber *> *virtualInput in subElementDefs) {
|
||||
if (filter && virtualInput[@"reportID"] && ![filter containsObject:virtualInput[@"reportID"]]) continue;
|
||||
[(NSMutableArray *)elements addObject:[[JOYSubElement alloc] initWithRealElement:element
|
||||
size:virtualInput[@"size"].unsignedLongValue
|
||||
offset:virtualInput[@"offset"].unsignedLongValue
|
||||
usagePage:virtualInput[@"usagePage"].unsignedLongValue
|
||||
usage:virtualInput[@"usage"].unsignedLongValue
|
||||
min:virtualInput[@"min"].unsignedIntValue
|
||||
max:virtualInput[@"max"].unsignedIntValue]];
|
||||
}
|
||||
isOutput = IOHIDElementGetType((__bridge IOHIDElementRef)_element) == kIOHIDElementTypeOutput;
|
||||
[_multiElements setObject:elements forKey:element];
|
||||
}
|
||||
else {
|
||||
if (filter && ![filter containsObject:@(element.reportID)]) continue;
|
||||
|
||||
switch (IOHIDElementGetType((__bridge IOHIDElementRef)_element)) {
|
||||
/* Handled */
|
||||
case kIOHIDElementTypeInput_Misc:
|
||||
case kIOHIDElementTypeInput_Button:
|
||||
case kIOHIDElementTypeInput_Axis:
|
||||
break;
|
||||
case kIOHIDElementTypeOutput:
|
||||
isOutput = true;
|
||||
break;
|
||||
/* Ignored */
|
||||
default:
|
||||
case kIOHIDElementTypeInput_ScanCodes:
|
||||
case kIOHIDElementTypeInput_NULL:
|
||||
case kIOHIDElementTypeFeature:
|
||||
case kIOHIDElementTypeCollection:
|
||||
continue;
|
||||
}
|
||||
if (IOHIDElementIsArray((__bridge IOHIDElementRef)_element)) continue;
|
||||
|
||||
elements = @[element];
|
||||
}
|
||||
|
||||
_iokitToJOY[@(IOHIDElementGetCookie((__bridge IOHIDElementRef)_element))] = element;
|
||||
|
||||
for (JOYElement *element in elements) {
|
||||
if (isOutput) {
|
||||
if (!_rumbleElement && rumbleUsage && rumbleUsagePage && element.usage == rumbleUsage && element.usagePage == rumbleUsagePage) {
|
||||
if (hacks[JOYRumbleMin]) {
|
||||
element.min = [hacks[JOYRumbleMin] unsignedIntValue];
|
||||
}
|
||||
if (hacks[JOYRumbleMax]) {
|
||||
element.max = [hacks[JOYRumbleMax] unsignedIntValue];
|
||||
}
|
||||
_rumbleElement = element;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
if (!_connectedElement && connectedUsage && connectedUsagePage && element.usage == connectedUsage && element.usagePage == connectedUsagePage) {
|
||||
_connectedElement = element;
|
||||
_logicallyConnected = element.value != element.min;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (element.usagePage == kHIDPage_Button) {
|
||||
button: {
|
||||
JOYButton *button = [[JOYButton alloc] initWithElement: element];
|
||||
[_buttons setObject:button forKey:element];
|
||||
NSNumber *replacementUsage = hacks[JOYButtonUsageMapping][@(button.usage)];
|
||||
if (replacementUsage) {
|
||||
button.usage = [replacementUsage unsignedIntValue];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (element.usagePage == kHIDPage_GenericDesktop) {
|
||||
switch (element.usage) {
|
||||
case kHIDUsage_GD_X:
|
||||
case kHIDUsage_GD_Y:
|
||||
case kHIDUsage_GD_Z:
|
||||
case kHIDUsage_GD_Rx:
|
||||
case kHIDUsage_GD_Ry:
|
||||
case kHIDUsage_GD_Rz: {
|
||||
|
||||
JOYElement *other = previousAxisElement;
|
||||
previousAxisElement = element;
|
||||
if (!other) goto single;
|
||||
if (other.usage >= element.usage) goto single;
|
||||
if (other.reportID != element.reportID) goto single;
|
||||
if (![axisGroups[@(other.usage)] isEqualTo: axisGroups[@(element.usage)]]) goto single;
|
||||
if (other.parentID != element.parentID) goto single;
|
||||
|
||||
JOYAxes2D *axes = nil;
|
||||
if (other.usage == kHIDUsage_GD_Z && element.usage == kHIDUsage_GD_Rz && [hacks[JOYSwapZRz] boolValue]) {
|
||||
axes = [[JOYAxes2D alloc] initWithFirstElement:element secondElement:other];
|
||||
}
|
||||
else {
|
||||
axes = [[JOYAxes2D alloc] initWithFirstElement:other secondElement:element];
|
||||
}
|
||||
NSNumber *replacementUsage = hacks[JOYAxes2DUsageMapping][@(axes.usage)];
|
||||
if (replacementUsage) {
|
||||
axes.usage = [replacementUsage unsignedIntValue];
|
||||
}
|
||||
|
||||
[_axisEmulatedButtons removeObjectForKey:@(_axes[other].uniqueID)];
|
||||
[_axes removeObjectForKey:other];
|
||||
previousAxisElement = nil;
|
||||
_axes2D[other] = axes;
|
||||
_axes2D[element] = axes;
|
||||
|
||||
if (axes2DEmulateButtons) {
|
||||
_axes2DEmulatedButtons[@(axes.uniqueID)] = @[
|
||||
[[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:axes.uniqueID | 0x100000000L],
|
||||
[[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:axes.uniqueID | 0x200000000L],
|
||||
[[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:axes.uniqueID | 0x300000000L],
|
||||
[[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:axes.uniqueID | 0x400000000L],
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
for (NSArray *group in axes2d) {
|
||||
break;
|
||||
IOHIDElementRef first = (__bridge IOHIDElementRef)group[0];
|
||||
IOHIDElementRef second = (__bridge IOHIDElementRef)group[1];
|
||||
if (IOHIDElementGetUsage(first) > element.usage) continue;
|
||||
if (IOHIDElementGetUsage(second) > element.usage) continue;
|
||||
if (IOHIDElementGetReportID(first) != IOHIDElementGetReportID(element)) continue;
|
||||
if ((IOHIDElementGetUsage(first) - kHIDUsage_GD_X) / 3 != (element.usage - kHIDUsage_GD_X) / 3) continue;
|
||||
if (IOHIDElementGetParent(first) != IOHIDElementGetParent(element)) continue;
|
||||
|
||||
[axes2d removeObject:group];
|
||||
[axes3d addObject:@[(__bridge id)first, (__bridge id)second, _element]];
|
||||
found = true;
|
||||
break;
|
||||
}*/
|
||||
break;
|
||||
}
|
||||
single:
|
||||
case kHIDUsage_GD_Slider:
|
||||
case kHIDUsage_GD_Dial:
|
||||
case kHIDUsage_GD_Wheel: {
|
||||
JOYAxis *axis = [[JOYAxis alloc] initWithElement: element];
|
||||
[_axes setObject:axis forKey:element];
|
||||
|
||||
NSNumber *replacementUsage = hacks[JOYAxisUsageMapping][@(axis.usage)];
|
||||
if (replacementUsage) {
|
||||
axis.usage = [replacementUsage unsignedIntValue];
|
||||
}
|
||||
|
||||
if (axesEmulateButtons && axis.usage >= JOYAxisUsageL1 && axis.usage <= JOYAxisUsageR3) {
|
||||
_axisEmulatedButtons[@(axis.uniqueID)] =
|
||||
[[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageL1 + JOYButtonUsageL1 uniqueID:axis.uniqueID];
|
||||
}
|
||||
|
||||
if (axesEmulateButtons && axis.usage >= JOYAxisUsageGeneric0) {
|
||||
_axisEmulatedButtons[@(axis.uniqueID)] =
|
||||
[[JOYEmulatedButton alloc] initWithUsage:axis.usage - JOYAxisUsageGeneric0 + JOYButtonUsageGeneric0 uniqueID:axis.uniqueID];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kHIDUsage_GD_DPadUp:
|
||||
case kHIDUsage_GD_DPadDown:
|
||||
case kHIDUsage_GD_DPadRight:
|
||||
case kHIDUsage_GD_DPadLeft:
|
||||
case kHIDUsage_GD_Start:
|
||||
case kHIDUsage_GD_Select:
|
||||
case kHIDUsage_GD_SystemMainMenu:
|
||||
goto button;
|
||||
|
||||
case kHIDUsage_GD_Hatswitch: {
|
||||
JOYHat *hat = [[JOYHat alloc] initWithElement: element];
|
||||
[_hats setObject:hat forKey:element];
|
||||
if (hatsEmulateButtons) {
|
||||
_hatEmulatedButtons[@(hat.uniqueID)] = @[
|
||||
[[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadLeft uniqueID:hat.uniqueID | 0x100000000L],
|
||||
[[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadRight uniqueID:hat.uniqueID | 0x200000000L],
|
||||
[[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadUp uniqueID:hat.uniqueID | 0x300000000L],
|
||||
[[JOYEmulatedButton alloc] initWithUsage:JOYButtonUsageDPadDown uniqueID:hat.uniqueID | 0x400000000L],
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[exposedControllers addObject:self];
|
||||
if (_logicallyConnected) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controllerConnected:)]) {
|
||||
[listener controllerConnected:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)deviceName
|
||||
{
|
||||
return IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductKey));
|
||||
}
|
||||
|
||||
- (NSString *)uniqueID
|
||||
{
|
||||
NSString *serial = (__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDSerialNumberKey));
|
||||
if (!serial || [(__bridge NSString *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDTransportKey)) isEqualToString:@"USB"]) {
|
||||
serial = [NSString stringWithFormat:@"%04x%04x%08x",
|
||||
[(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDVendorIDKey)) unsignedIntValue],
|
||||
[(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDProductIDKey)) unsignedIntValue],
|
||||
[(__bridge NSNumber *)IOHIDDeviceGetProperty(_device, CFSTR(kIOHIDLocationIDKey)) unsignedIntValue]];
|
||||
}
|
||||
if (_serialSuffix) {
|
||||
return [NSString stringWithFormat:@"%@-%@", serial, _serialSuffix];
|
||||
}
|
||||
return serial;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@: %p, %@, %@>", self.className, self, self.deviceName, self.uniqueID];
|
||||
}
|
||||
|
||||
- (NSArray<JOYButton *> *)buttons
|
||||
{
|
||||
NSMutableArray *ret = [[_buttons allValues] mutableCopy];
|
||||
[ret addObjectsFromArray:_axisEmulatedButtons.allValues];
|
||||
for (NSArray *array in _axes2DEmulatedButtons.allValues) {
|
||||
[ret addObjectsFromArray:array];
|
||||
}
|
||||
for (NSArray *array in _hatEmulatedButtons.allValues) {
|
||||
[ret addObjectsFromArray:array];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (NSArray<JOYAxis *> *)axes
|
||||
{
|
||||
return [_axes allValues];
|
||||
}
|
||||
|
||||
- (NSArray<JOYAxes2D *> *)axes2D
|
||||
{
|
||||
return [[NSSet setWithArray:[_axes2D allValues]] allObjects];
|
||||
}
|
||||
|
||||
- (NSArray<JOYHat *> *)hats
|
||||
{
|
||||
return [_hats allValues];
|
||||
}
|
||||
|
||||
- (void)elementChanged:(IOHIDElementRef)element
|
||||
{
|
||||
JOYElement *_element = _iokitToJOY[@(IOHIDElementGetCookie(element))];
|
||||
if (_element) {
|
||||
[self _elementChanged:_element];
|
||||
}
|
||||
else {
|
||||
//NSLog(@"Unhandled usage %x (Cookie: %x, Usage: %x)", IOHIDElementGetUsage(element), IOHIDElementGetCookie(element), IOHIDElementGetUsage(element));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_elementChanged:(JOYElement *)element
|
||||
{
|
||||
if (element == _connectedElement) {
|
||||
bool old = self.connected;
|
||||
_logicallyConnected = _connectedElement.value != _connectedElement.min;
|
||||
if (!old && self.connected) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controllerConnected:)]) {
|
||||
[listener controllerConnected:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (old && !self.connected) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
|
||||
[listener controllerDisconnected:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
NSArray<JOYElement *> *subElements = _multiElements[element];
|
||||
if (subElements) {
|
||||
for (JOYElement *subElement in subElements) {
|
||||
[self _elementChanged:subElement];
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.connected) return;
|
||||
{
|
||||
JOYButton *button = _buttons[element];
|
||||
if (button) {
|
||||
if ([button updateState]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
|
||||
[listener controller:self buttonChangedState:button];
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
JOYAxis *axis = _axes[element];
|
||||
if (axis) {
|
||||
if ([axis updateState]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controller:movedAxis:)]) {
|
||||
[listener controller:self movedAxis:axis];
|
||||
}
|
||||
}
|
||||
JOYEmulatedButton *button = _axisEmulatedButtons[@(axis.uniqueID)];
|
||||
if ([button updateStateFromAxis:axis]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
|
||||
[listener controller:self buttonChangedState:button];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
JOYAxes2D *axes = _axes2D[element];
|
||||
if (axes) {
|
||||
if ([axes updateState]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controller:movedAxes2D:)]) {
|
||||
[listener controller:self movedAxes2D:axes];
|
||||
}
|
||||
}
|
||||
NSArray <JOYEmulatedButton *> *buttons = _axes2DEmulatedButtons[@(axes.uniqueID)];
|
||||
for (JOYEmulatedButton *button in buttons) {
|
||||
if ([button updateStateFromAxes2D:axes]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
|
||||
[listener controller:self buttonChangedState:button];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
JOYHat *hat = _hats[element];
|
||||
if (hat) {
|
||||
if ([hat updateState]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controller:movedHat:)]) {
|
||||
[listener controller:self movedHat:hat];
|
||||
}
|
||||
}
|
||||
|
||||
NSArray <JOYEmulatedButton *> *buttons = _hatEmulatedButtons[@(hat.uniqueID)];
|
||||
for (JOYEmulatedButton *button in buttons) {
|
||||
if ([button updateStateFromHat:hat]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controller:buttonChangedState:)]) {
|
||||
[listener controller:self buttonChangedState:button];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)disconnected
|
||||
{
|
||||
if (_logicallyConnected && [exposedControllers containsObject:self]) {
|
||||
for (id<JOYListener> listener in listeners) {
|
||||
if ([listener respondsToSelector:@selector(controllerDisconnected:)]) {
|
||||
[listener controllerDisconnected:self];
|
||||
}
|
||||
}
|
||||
}
|
||||
_physicallyConnected = false;
|
||||
[self setRumbleAmplitude:0]; // Stop the rumble thread.
|
||||
[exposedControllers removeObject:self];
|
||||
_device = nil;
|
||||
}
|
||||
|
||||
- (void)sendReport:(NSData *)report
|
||||
{
|
||||
if (!report.length) return;
|
||||
IOHIDDeviceSetReport(_device, kIOHIDReportTypeOutput, *(uint8_t *)report.bytes, report.bytes, report.length);
|
||||
}
|
||||
|
||||
- (void)setPlayerLEDs:(uint8_t)mask
|
||||
{
|
||||
mask &= 0xF;
|
||||
if (_isSwitch) {
|
||||
_lastSwitchPacket.reportID = 0x1; // Rumble and LEDs
|
||||
_lastSwitchPacket.sequence++;
|
||||
_lastSwitchPacket.sequence &= 0xF;
|
||||
_lastSwitchPacket.command = 0x30; // LED
|
||||
_lastSwitchPacket.commandData[0] = mask;
|
||||
[self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pwmThread
|
||||
{
|
||||
while (_rumblePWMRatio != 0) {
|
||||
[_rumbleElement setValue:1];
|
||||
[NSThread sleepForTimeInterval:_rumblePWMRatio / 10];
|
||||
[_rumbleElement setValue:0];
|
||||
[NSThread sleepForTimeInterval:(1 - _rumblePWMRatio) / 10];
|
||||
}
|
||||
[_rumblePWMThreadLock lock];
|
||||
[_rumblePWMThreadLock signal];
|
||||
[_rumblePWMThreadLock unlock];
|
||||
}
|
||||
|
||||
- (void)setRumbleAmplitude:(double)amp /* andFrequency: (double)frequency */
|
||||
{
|
||||
double frequency = 144; // I have no idea what I'm doing.
|
||||
|
||||
if (amp < 0) amp = 0;
|
||||
if (amp > 1) amp = 1;
|
||||
if (_isSwitch) {
|
||||
if (amp == 0) {
|
||||
amp = 1;
|
||||
frequency = 0;
|
||||
}
|
||||
|
||||
uint8_t highAmp = amp * 0x64;
|
||||
uint8_t lowAmp = amp * 0x32 + 0x40;
|
||||
if (frequency < 0) frequency = 0;
|
||||
if (frequency > 1252) frequency = 1252;
|
||||
uint8_t encodedFrequency = (uint8_t)round(log2(frequency / 10.0) * 32.0);
|
||||
|
||||
uint16_t highFreq = (encodedFrequency - 0x60) * 4;
|
||||
uint8_t lowFreq = encodedFrequency - 0x40;
|
||||
|
||||
//if (frequency < 82 || frequency > 312) {
|
||||
if (amp) {
|
||||
highAmp = 0;
|
||||
}
|
||||
|
||||
if (frequency < 40 || frequency > 626) {
|
||||
lowAmp = 0;
|
||||
}
|
||||
|
||||
_lastSwitchPacket.rumbleData[0] = _lastSwitchPacket.rumbleData[4] = highFreq & 0xFF;
|
||||
_lastSwitchPacket.rumbleData[1] = _lastSwitchPacket.rumbleData[5] = (highAmp << 1) + ((highFreq >> 8) & 0x1);
|
||||
_lastSwitchPacket.rumbleData[2] = _lastSwitchPacket.rumbleData[6] = lowFreq;
|
||||
_lastSwitchPacket.rumbleData[3] = _lastSwitchPacket.rumbleData[7] = lowAmp;
|
||||
|
||||
|
||||
_lastSwitchPacket.reportID = 0x10; // Rumble only
|
||||
_lastSwitchPacket.sequence++;
|
||||
_lastSwitchPacket.sequence &= 0xF;
|
||||
_lastSwitchPacket.command = 0; // LED
|
||||
[self sendReport:[NSData dataWithBytes:&_lastSwitchPacket length:sizeof(_lastSwitchPacket)]];
|
||||
}
|
||||
else {
|
||||
if (_rumbleElement.max == 1 && _rumbleElement.min == 0) {
|
||||
if (_rumblePWMRatio == 0) { // PWM thread not running, start it.
|
||||
if (amp != 0) {
|
||||
_rumblePWMRatio = amp;
|
||||
[self performSelectorInBackground:@selector(pwmThread) withObject:nil];
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (amp == 0) { // Thread is running, signal it to stop
|
||||
[_rumblePWMThreadLock lock];
|
||||
_rumblePWMRatio = 0;
|
||||
[_rumblePWMThreadLock wait];
|
||||
[_rumblePWMThreadLock unlock];
|
||||
}
|
||||
else {
|
||||
_rumblePWMRatio = amp;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
[_rumbleElement setValue:amp * (_rumbleElement.max - _rumbleElement.min) + _rumbleElement.min];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (bool)isConnected
|
||||
{
|
||||
return _logicallyConnected && _physicallyConnected;
|
||||
}
|
||||
|
||||
+ (void)controllerAdded:(IOHIDDeviceRef) device
|
||||
{
|
||||
NSString *name = (__bridge NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
|
||||
NSDictionary *hacks = hacksByName[name];
|
||||
NSArray *filters = hacks[JOYReportIDFilters];
|
||||
if (filters) {
|
||||
JOYController *controller = [[JOYMultiplayerController alloc] initWithDevice:device
|
||||
reportIDFilters:filters];
|
||||
[controllers setObject:controller forKey:[NSValue valueWithPointer:device]];
|
||||
}
|
||||
else {
|
||||
[controllers setObject:[[JOYController alloc] initWithDevice:device] forKey:[NSValue valueWithPointer:device]];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)controllerRemoved:(IOHIDDeviceRef) device
|
||||
{
|
||||
[[controllers objectForKey:[NSValue valueWithPointer:device]] disconnected];
|
||||
[controllers removeObjectForKey:[NSValue valueWithPointer:device]];
|
||||
}
|
||||
|
||||
+ (NSArray<JOYController *> *)allControllers
|
||||
{
|
||||
return exposedControllers;
|
||||
}
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
#include "ControllerConfiguration.inc"
|
||||
}
|
||||
|
||||
+(void)registerListener:(id<JOYListener>)listener
|
||||
{
|
||||
[listeners addObject:listener];
|
||||
}
|
||||
|
||||
+(void)unregisterListener:(id<JOYListener>)listener
|
||||
{
|
||||
[listeners removeObject:listener];
|
||||
}
|
||||
|
||||
+ (void)startOnRunLoop:(NSRunLoop *)runloop withOptions: (NSDictionary *)options
|
||||
{
|
||||
axesEmulateButtons = [options[JOYAxesEmulateButtonsKey] boolValue];
|
||||
axes2DEmulateButtons = [options[JOYAxes2DEmulateButtonsKey] boolValue];
|
||||
hatsEmulateButtons = [options[JOYHatsEmulateButtonsKey] boolValue];
|
||||
|
||||
controllers = [NSMutableDictionary dictionary];
|
||||
exposedControllers = [NSMutableArray array];
|
||||
NSArray *array = @[
|
||||
CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
|
||||
CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
|
||||
CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController),
|
||||
@{@kIOHIDDeviceUsagePageKey: @(kHIDPage_Game)},
|
||||
];
|
||||
|
||||
listeners = [NSMutableSet set];
|
||||
static IOHIDManagerRef manager = nil;
|
||||
if (manager) {
|
||||
CFRelease(manager); // Stop the previous session
|
||||
}
|
||||
manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
|
||||
|
||||
if (!manager) return;
|
||||
if (IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone)) {
|
||||
CFRelease(manager);
|
||||
return;
|
||||
}
|
||||
|
||||
IOHIDManagerSetDeviceMatchingMultiple(manager, (__bridge CFArrayRef)array);
|
||||
IOHIDManagerRegisterDeviceMatchingCallback(manager, HIDDeviceAdded, NULL);
|
||||
IOHIDManagerRegisterDeviceRemovalCallback(manager, HIDDeviceRemoved, NULL);
|
||||
IOHIDManagerScheduleWithRunLoop(manager, [runloop getCFRunLoop], kCFRunLoopDefaultMode);
|
||||
}
|
||||
@end
|
20
JoyKit/JOYElement.h
Normal file
20
JoyKit/JOYElement.h
Normal file
@ -0,0 +1,20 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
|
||||
@interface JOYElement : NSObject<NSCopying>
|
||||
- (instancetype)initWithElement:(IOHIDElementRef)element;
|
||||
- (int32_t)value;
|
||||
- (NSData *)dataValue;
|
||||
- (void)setValue:(uint32_t)value;
|
||||
- (void)setDataValue:(NSData *)value;
|
||||
@property (readonly) uint16_t usage;
|
||||
@property (readonly) uint16_t usagePage;
|
||||
@property (readonly) uint32_t uniqueID;
|
||||
@property int32_t min;
|
||||
@property int32_t max;
|
||||
@property (readonly) int32_t reportID;
|
||||
@property (readonly) int32_t parentID;
|
||||
|
||||
@end
|
||||
|
||||
|
96
JoyKit/JOYElement.m
Normal file
96
JoyKit/JOYElement.m
Normal file
@ -0,0 +1,96 @@
|
||||
#import "JOYElement.h"
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
|
||||
@implementation JOYElement
|
||||
{
|
||||
id _element;
|
||||
IOHIDDeviceRef _device;
|
||||
int32_t _min, _max;
|
||||
}
|
||||
|
||||
- (int32_t)min
|
||||
{
|
||||
return MIN(_min, _max);
|
||||
}
|
||||
|
||||
- (int32_t)max
|
||||
{
|
||||
return MAX(_max, _min);
|
||||
}
|
||||
|
||||
-(void)setMin:(int32_t)min
|
||||
{
|
||||
_min = min;
|
||||
}
|
||||
|
||||
- (void)setMax:(int32_t)max
|
||||
{
|
||||
_max = max;
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(IOHIDElementRef)element
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_element = (__bridge id)element;
|
||||
_usage = IOHIDElementGetUsage(element);
|
||||
_usagePage = IOHIDElementGetUsagePage(element);
|
||||
_uniqueID = (uint32_t)IOHIDElementGetCookie(element);
|
||||
_min = (int32_t) IOHIDElementGetLogicalMin(element);
|
||||
_max = (int32_t) IOHIDElementGetLogicalMax(element);
|
||||
_reportID = IOHIDElementGetReportID(element);
|
||||
IOHIDElementRef parent = IOHIDElementGetParent(element);
|
||||
_parentID = parent? (uint32_t)IOHIDElementGetCookie(parent) : -1;
|
||||
_device = IOHIDElementGetDevice(element);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (int32_t)value
|
||||
{
|
||||
IOHIDValueRef value = NULL;
|
||||
IOHIDDeviceGetValue(_device, (__bridge IOHIDElementRef)_element, &value);
|
||||
if (!value) return 0;
|
||||
CFRelease(CFRetain(value)); // For some reason, this is required to prevent leaks.
|
||||
return (int32_t)IOHIDValueGetIntegerValue(value);
|
||||
}
|
||||
|
||||
- (NSData *)dataValue
|
||||
{
|
||||
IOHIDValueRef value = NULL;
|
||||
IOHIDDeviceGetValue(_device, (__bridge IOHIDElementRef)_element, &value);
|
||||
if (!value) return 0;
|
||||
CFRelease(CFRetain(value)); // For some reason, this is required to prevent leaks.
|
||||
return [NSData dataWithBytes:IOHIDValueGetBytePtr(value) length:IOHIDValueGetLength(value)];
|
||||
}
|
||||
|
||||
- (void)setValue:(uint32_t)value
|
||||
{
|
||||
IOHIDValueRef ivalue = IOHIDValueCreateWithIntegerValue(NULL, (__bridge IOHIDElementRef)_element, 0, value);
|
||||
IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue);
|
||||
CFRelease(ivalue);
|
||||
}
|
||||
|
||||
- (void)setDataValue:(NSData *)value
|
||||
{
|
||||
IOHIDValueRef ivalue = IOHIDValueCreateWithBytes(NULL, (__bridge IOHIDElementRef)_element, 0, value.bytes, value.length);
|
||||
IOHIDDeviceSetValue(_device, (__bridge IOHIDElementRef)_element, ivalue);
|
||||
CFRelease(ivalue);
|
||||
}
|
||||
|
||||
/* For use as a dictionary key */
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
return self.uniqueID;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)object
|
||||
{
|
||||
return self->_element == object;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(nullable NSZone *)zone;
|
||||
{
|
||||
return self;
|
||||
}
|
||||
@end
|
11
JoyKit/JOYEmulatedButton.h
Normal file
11
JoyKit/JOYEmulatedButton.h
Normal file
@ -0,0 +1,11 @@
|
||||
#import "JOYButton.h"
|
||||
#import "JOYAxis.h"
|
||||
#import "JOYAxes2D.h"
|
||||
#import "JOYHat.h"
|
||||
|
||||
@interface JOYEmulatedButton : JOYButton
|
||||
- (instancetype)initWithUsage:(JOYButtonUsage)usage uniqueID:(uint64_t)uniqueID;
|
||||
- (bool)updateStateFromAxis:(JOYAxis *)axis;
|
||||
- (bool)updateStateFromAxes2D:(JOYAxes2D *)axes;
|
||||
- (bool)updateStateFromHat:(JOYHat *)hat;
|
||||
@end
|
91
JoyKit/JOYEmulatedButton.m
Normal file
91
JoyKit/JOYEmulatedButton.m
Normal file
@ -0,0 +1,91 @@
|
||||
#import "JOYEmulatedButton.h"
|
||||
|
||||
@interface JOYButton ()
|
||||
{
|
||||
@public bool _state;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation JOYEmulatedButton
|
||||
{
|
||||
uint64_t _uniqueID;
|
||||
}
|
||||
|
||||
- (instancetype)initWithUsage:(JOYButtonUsage)usage uniqueID:(uint64_t)uniqueID;
|
||||
{
|
||||
self = [super init];
|
||||
self.usage = usage;
|
||||
_uniqueID = uniqueID;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (uint64_t)uniqueID
|
||||
{
|
||||
return _uniqueID;
|
||||
}
|
||||
|
||||
- (bool)updateStateFromAxis:(JOYAxis *)axis
|
||||
{
|
||||
bool old = _state;
|
||||
_state = [axis value] > 0.5;
|
||||
return _state != old;
|
||||
}
|
||||
|
||||
- (bool)updateStateFromAxes2D:(JOYAxes2D *)axes
|
||||
{
|
||||
bool old = _state;
|
||||
if (axes.distance < 0.5) {
|
||||
_state = false;
|
||||
}
|
||||
else {
|
||||
unsigned direction = ((unsigned)round(axes.angle / 360 * 8)) & 7;
|
||||
switch (self.usage) {
|
||||
case JOYButtonUsageDPadLeft:
|
||||
_state = direction >= 3 && direction <= 5;
|
||||
break;
|
||||
case JOYButtonUsageDPadRight:
|
||||
_state = direction <= 1 || direction == 7;
|
||||
break;
|
||||
case JOYButtonUsageDPadUp:
|
||||
_state = direction >= 5;
|
||||
break;
|
||||
case JOYButtonUsageDPadDown:
|
||||
_state = direction <= 3 && direction >= 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return _state != old;
|
||||
}
|
||||
|
||||
- (bool)updateStateFromHat:(JOYHat *)hat
|
||||
{
|
||||
bool old = _state;
|
||||
if (!hat.pressed) {
|
||||
_state = false;
|
||||
}
|
||||
else {
|
||||
unsigned direction = ((unsigned)round(hat.angle / 360 * 8)) & 7;
|
||||
switch (self.usage) {
|
||||
case JOYButtonUsageDPadLeft:
|
||||
_state = direction >= 3 && direction <= 5;
|
||||
break;
|
||||
case JOYButtonUsageDPadRight:
|
||||
_state = direction <= 1 || direction == 7;
|
||||
break;
|
||||
case JOYButtonUsageDPadUp:
|
||||
_state = direction >= 5;
|
||||
break;
|
||||
case JOYButtonUsageDPadDown:
|
||||
_state = direction <= 3 && direction >= 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return _state != old;
|
||||
}
|
||||
|
||||
@end
|
11
JoyKit/JOYHat.h
Normal file
11
JoyKit/JOYHat.h
Normal file
@ -0,0 +1,11 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface JOYHat : NSObject
|
||||
- (uint64_t)uniqueID;
|
||||
- (double)angle;
|
||||
- (unsigned)resolution;
|
||||
@property (readonly, getter=isPressed) bool pressed;
|
||||
|
||||
@end
|
||||
|
||||
|
60
JoyKit/JOYHat.m
Normal file
60
JoyKit/JOYHat.m
Normal file
@ -0,0 +1,60 @@
|
||||
#import "JOYHat.h"
|
||||
#import "JOYElement.h"
|
||||
|
||||
@implementation JOYHat
|
||||
{
|
||||
JOYElement *_element;
|
||||
double _state;
|
||||
}
|
||||
|
||||
- (uint64_t)uniqueID
|
||||
{
|
||||
return _element.uniqueID;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
if (self.isPressed) {
|
||||
return [NSString stringWithFormat:@"<%@: %p (%llu); State: %f degrees>", self.className, self, self.uniqueID, self.angle];
|
||||
}
|
||||
return [NSString stringWithFormat:@"<%@: %p (%llu); State: released>", self.className, self, self.uniqueID];
|
||||
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(JOYElement *)element
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) return self;
|
||||
|
||||
_element = element;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (bool)isPressed
|
||||
{
|
||||
return _state >= 0 && _state < 360;
|
||||
}
|
||||
|
||||
- (double)angle
|
||||
{
|
||||
if (self.isPressed) return fmod((_state + 270), 360);
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (unsigned)resolution
|
||||
{
|
||||
return _element.max - _element.min + 1;
|
||||
}
|
||||
|
||||
- (bool)updateState
|
||||
{
|
||||
unsigned state = ([_element value] - _element.min) * 360.0 / self.resolution;
|
||||
if (_state != state) {
|
||||
_state = state;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@end
|
8
JoyKit/JOYMultiplayerController.h
Normal file
8
JoyKit/JOYMultiplayerController.h
Normal file
@ -0,0 +1,8 @@
|
||||
#import "JOYController.h"
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
|
||||
@interface JOYMultiplayerController : JOYController
|
||||
- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray <NSArray <NSNumber *> *>*) reportIDFilters;
|
||||
@end
|
||||
|
||||
|
44
JoyKit/JOYMultiplayerController.m
Normal file
44
JoyKit/JOYMultiplayerController.m
Normal file
@ -0,0 +1,44 @@
|
||||
#import "JOYMultiplayerController.h"
|
||||
|
||||
@interface JOYController ()
|
||||
- (instancetype)initWithDevice:(IOHIDDeviceRef)device reportIDFilter:(NSArray <NSNumber *> *) filter serialSuffix:(NSString *)suffix;
|
||||
- (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value;
|
||||
- (void)disconnected;
|
||||
@end
|
||||
|
||||
@implementation JOYMultiplayerController
|
||||
{
|
||||
NSMutableArray <JOYController *> *_children;
|
||||
}
|
||||
|
||||
- (instancetype)initWithDevice:(IOHIDDeviceRef) device reportIDFilters:(NSArray <NSArray <NSNumber *> *>*) reportIDFilters
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) return self;
|
||||
|
||||
_children = [NSMutableArray array];
|
||||
|
||||
unsigned index = 1;
|
||||
for (NSArray *filter in reportIDFilters) {
|
||||
JOYController *controller = [[JOYController alloc] initWithDevice:device reportIDFilter:filter serialSuffix:[NSString stringWithFormat:@"%d", index]];
|
||||
[_children addObject:controller];
|
||||
index++;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)elementChanged:(IOHIDElementRef) element toValue:(IOHIDValueRef) value
|
||||
{
|
||||
for (JOYController *child in _children) {
|
||||
[child elementChanged:element toValue:value];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)disconnected
|
||||
{
|
||||
for (JOYController *child in _children) {
|
||||
[child disconnected];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
14
JoyKit/JOYSubElement.h
Normal file
14
JoyKit/JOYSubElement.h
Normal file
@ -0,0 +1,14 @@
|
||||
#import "JOYElement.h"
|
||||
|
||||
@interface JOYSubElement : JOYElement
|
||||
- (instancetype)initWithRealElement:(JOYElement *)element
|
||||
size:(size_t) size // in bits
|
||||
offset:(size_t) offset // in bits
|
||||
usagePage:(uint16_t)usagePage
|
||||
usage:(uint16_t)usage
|
||||
min:(int32_t)min
|
||||
max:(int32_t)max;
|
||||
|
||||
@end
|
||||
|
||||
|
99
JoyKit/JOYSubElement.m
Normal file
99
JoyKit/JOYSubElement.m
Normal file
@ -0,0 +1,99 @@
|
||||
#import "JOYSubElement.h"
|
||||
|
||||
@interface JOYElement ()
|
||||
{
|
||||
@public uint16_t _usage;
|
||||
@public uint16_t _usagePage;
|
||||
@public uint32_t _uniqueID;
|
||||
@public int32_t _min;
|
||||
@public int32_t _max;
|
||||
@public int32_t _reportID;
|
||||
@public int32_t _parentID;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation JOYSubElement
|
||||
{
|
||||
JOYElement *_parent;
|
||||
size_t _size; // in bits
|
||||
size_t _offset; // in bits
|
||||
}
|
||||
|
||||
- (instancetype)initWithRealElement:(JOYElement *)element
|
||||
size:(size_t) size // in bits
|
||||
offset:(size_t) offset // in bits
|
||||
usagePage:(uint16_t)usagePage
|
||||
usage:(uint16_t)usage
|
||||
min:(int32_t)min
|
||||
max:(int32_t)max
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_parent = element;
|
||||
_size = size;
|
||||
_offset = offset;
|
||||
_usage = usage;
|
||||
_usagePage = usagePage;
|
||||
_uniqueID = (uint32_t)((_parent.uniqueID << 16) | offset);
|
||||
_min = min;
|
||||
_max = max;
|
||||
_reportID = _parent.reportID;
|
||||
_parentID = _parent.parentID;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (int32_t)value
|
||||
{
|
||||
NSData *parentValue = [_parent dataValue];
|
||||
if (!parentValue) return 0;
|
||||
if (_size > 32) return 0;
|
||||
if (_size + (_offset % 8) > 32) return 0;
|
||||
size_t parentLength = parentValue.length;
|
||||
if (_size > parentLength * 8) return 0;
|
||||
if (_size + _offset >= parentLength * 8) return 0;
|
||||
const uint8_t *bytes = parentValue.bytes;
|
||||
|
||||
uint8_t temp[4] = {0,};
|
||||
memcpy(temp, bytes + _offset / 8, (_offset + _size - 1) / 8 - _offset / 8 + 1);
|
||||
uint32_t ret = (*(uint32_t *)temp) >> (_offset % 8);
|
||||
ret &= (1 << _size) - 1;
|
||||
|
||||
if (_max < _min) {
|
||||
return _max + _min - ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (void)setValue: (uint32_t) value
|
||||
{
|
||||
NSMutableData *dataValue = [[_parent dataValue] mutableCopy];
|
||||
if (!dataValue) return;
|
||||
if (_size > 32) return;
|
||||
if (_size + (_offset % 8) > 32) return;
|
||||
size_t parentLength = dataValue.length;
|
||||
if (_size > parentLength * 8) return;
|
||||
if (_size + _offset >= parentLength * 8) return;
|
||||
uint8_t *bytes = dataValue.mutableBytes;
|
||||
|
||||
uint8_t temp[4] = {0,};
|
||||
memcpy(temp, bytes + _offset / 8, (_offset + _size - 1) / 8 - _offset / 8 + 1);
|
||||
(*(uint32_t *)temp) &= ~((1 << (_size - 1)) << (_offset % 8));
|
||||
(*(uint32_t *)temp) |= (value) << (_offset % 8);
|
||||
memcpy(bytes + _offset / 8, temp, (_offset + _size - 1) / 8 - _offset / 8 + 1);
|
||||
[_parent setDataValue:dataValue];
|
||||
}
|
||||
|
||||
- (NSData *)dataValue
|
||||
{
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setDataValue:(NSData *)data
|
||||
{
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
|
||||
@end
|
6
JoyKit/JoyKit.h
Normal file
6
JoyKit/JoyKit.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef JoyKit_h
|
||||
#define JoyKit_h
|
||||
|
||||
#include "JOYController.h"
|
||||
|
||||
#endif
|
2
Makefile
2
Makefile
@ -140,7 +140,7 @@ SDL_SOURCES := $(shell ls SDL/*.c) $(OPEN_DIALOG)
|
||||
TESTER_SOURCES := $(shell ls Tester/*.c)
|
||||
|
||||
ifeq ($(PLATFORM),Darwin)
|
||||
COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m)
|
||||
COCOA_SOURCES := $(shell ls Cocoa/*.m) $(shell ls HexFiend/*.m) $(shell ls JoyKit/*.m)
|
||||
QUICKLOOK_SOURCES := $(shell ls QuickLook/*.m) $(shell ls QuickLook/*.c)
|
||||
endif
|
||||
|
||||
|
@ -141,12 +141,17 @@ static void GB_update_keys_status(GB_gameboy_t *gb, unsigned port)
|
||||
GB_set_key_state_for_player(gb, GB_KEY_START, emulated_devices == 1 ? port : 0,
|
||||
input_state_cb(port, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START));
|
||||
|
||||
if (gb->rumble_state)
|
||||
rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 65535);
|
||||
else
|
||||
rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 0);
|
||||
}
|
||||
|
||||
static void rumble_callback(GB_gameboy_t *gb, double amplitude)
|
||||
{
|
||||
if (gb == &gameboy[0]) {
|
||||
rumble.set_rumble_state(0, RETRO_RUMBLE_STRONG, 65535 * amplitude);
|
||||
}
|
||||
else if (gb == &gameboy[1]) {
|
||||
rumble.set_rumble_state(1, RETRO_RUMBLE_STRONG, 65535 * amplitude);
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_callback(GB_gameboy_t *gb, GB_sample_t *sample)
|
||||
{
|
||||
@ -370,6 +375,8 @@ static void init_for_current_model(unsigned id)
|
||||
GB_set_rgb_encode_callback(&gameboy[i], rgb_encode);
|
||||
GB_set_sample_rate(&gameboy[i], AUDIO_FREQUENCY);
|
||||
GB_apu_set_sample_callback(&gameboy[i], audio_callback);
|
||||
GB_set_rumble_callback(&gameboy[i], rumble_callback);
|
||||
|
||||
|
||||
/* todo: attempt to make these more generic */
|
||||
GB_set_vblank_callback(&gameboy[0], (GB_vblank_callback_t) vblank1);
|
||||
|
Loading…
Reference in New Issue
Block a user